diff --git a/.docker/README.md b/.docker/README.md
new file mode 100644
index 0000000000..66b76b0f2d
--- /dev/null
+++ b/.docker/README.md
@@ -0,0 +1,17 @@
+## What is this directory?
+This directory is a space for mounting directories to docker containers, allowing the mounts to be specified in committed code, but the contents of the mounts to remain ignored by git.
+
+### postgres
+The `postgres` directory is mounted to `/docker-entrypoint-initdb.d`. Any `.sh` or `.sql` files will be executed when the container is first started with a new data volume. You may read more regarding this functionality on the [Docker Hub page](https://hub.docker.com/_/postgres), under _Initialization scripts_.
+
+When running docker services through the Makefile commands, it specifies a docker-compose project name that depends on the name of the current git branch. This causes the volumes to change when the branch changes, which is helpful when switching between many branches that might have incompatible database schema changes. The downside is that whenever you start a new branch, you'll have to re-initialize the database again, like with `yarn run devsetup`. Creating a SQL dump from an existing, initialized database and placing it in this directory will allow you to skip this step.
+
+To create a SQL dump of your preferred database data useful for local testing, run `make .docker/postgres/init.sql` while the docker postgres container is running.
+
+> Note: you will likely need to run `make migrate` to ensure your database schema is up-to-date when using this technique.
+
+#### pgpass
+Stores the postgres authentication for the docker service for scripting access without manually providing a password, created by `make .docker/pgpass`
+
+### minio
+The `minio` directory is mounted to `/data`, since it isn't necessarily useful to have this data isolated based off the current git branch.
diff --git a/.editorconfig b/.editorconfig
index 1f49431c53..8db7923734 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,5 +1,8 @@
root = true
+[*]
+max_line_length = 100
+
[*.js]
indent_size = 2
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 3ba13e0cec..0b2ccf668e 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1 +1,8 @@
blank_issues_enabled: false
+contact_links:
+ - name: Studio GitHub Discussions
+ url: https://github.com/learningequality/studio/discussions
+ about: Please ask general questions about contributing to Studio or report development server issues here.
+ - name: Learning Equality Community Forum
+ url: https://community.learningequality.org/
+ about: Ask and answer questions about Learning Equality's products and tools, share your experiences using Kolibri, and connect with users around the world.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 128ae4fe41..24349f8d83 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -7,10 +7,30 @@ updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
- interval: "daily"
+ interval: "weekly"
+ day: "wednesday"
+ time: "00:00"
# Maintain dependencies for Javascript
- package-ecosystem: "npm"
directory: "/"
schedule:
- interval: "daily"
+ interval: "weekly"
+ day: "wednesday"
+ time: "00:00"
+ groups:
+ babel:
+ patterns:
+ - "@babel/*"
+
+ # Maintain dependencies for Github Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "wednesday"
+ time: "00:00"
+ groups:
+ github:
+ patterns:
+ - "actions/*"
diff --git a/.github/workflows/containerbuild.yml b/.github/workflows/containerbuild.yml
new file mode 100644
index 0000000000..361b0fad36
--- /dev/null
+++ b/.github/workflows/containerbuild.yml
@@ -0,0 +1,105 @@
+name: Container Build
+
+on:
+ push:
+ branches:
+ - unstable
+ - hotfixes
+ - master
+ tags:
+ - 'v*'
+ pull_request:
+
+jobs:
+ pre_postgres:
+ name: Path match check - postgres
+ runs-on: ubuntu-latest
+ # Map a step output to a job output
+ outputs:
+ should_skip: ${{ steps.skip_check.outputs.should_skip }}
+ steps:
+ - id: skip_check
+ uses: fkirc/skip-duplicate-actions@master
+ with:
+ skip_after_successful_duplicate: false
+ github_token: ${{ github.token }}
+ paths: '["docker/Dockerfile.postgres.dev", ".github/workflows/containerbuild.yml"]'
+
+ build_and_push_postgres:
+ name: Postgres - build and push Docker image to GitHub Container Registry
+ needs: pre_postgres
+ if: ${{ needs.pre_postgres.outputs.should_skip != 'true' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout codebase
+ uses: actions/checkout@v4
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Docker Hub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ghcr.io/learningequality/postgres
+ env:
+ DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: ./docker
+ file: ./docker/Dockerfile.postgres.dev
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ annotations: ${{ steps.meta.outputs.annotations }}
+
+ pre_nginx:
+ name: Path match check - nginx
+ runs-on: ubuntu-latest
+ # Map a step output to a job output
+ outputs:
+ should_skip: ${{ steps.skip_check.outputs.should_skip }}
+ steps:
+ - id: skip_check
+ uses: fkirc/skip-duplicate-actions@master
+ with:
+ skip_after_successful_duplicate: false
+ github_token: ${{ github.token }}
+ paths: '["k8s/images/nginx/*", ".github/workflows/containerbuild.yml"]'
+
+ build_nginx:
+ name: nginx - test build of nginx Docker image
+ needs: pre_nginx
+ if: ${{ needs.pre_nginx.outputs.should_skip != 'true' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout codebase
+ uses: actions/checkout@v4
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: ./
+ file: ./k8s/images/nginx/Dockerfile
+ platforms: linux/amd64
+ push: false
diff --git a/.github/workflows/deploytest.yml b/.github/workflows/deploytest.yml
index 11768ed489..71b3b9296c 100644
--- a/.github/workflows/deploytest.yml
+++ b/.github/workflows/deploytest.yml
@@ -27,13 +27,13 @@ jobs:
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Use Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v4
with:
node-version: '16.x'
- name: Cache Node.js modules
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -51,13 +51,13 @@ jobs:
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 3.9
- uses: actions/setup-python@v2
+ - uses: actions/checkout@v4
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v5
with:
- python-version: 3.9
+ python-version: '3.10'
- name: pip cache
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pyprod-${{ hashFiles('requirements.txt') }}
@@ -69,11 +69,11 @@ jobs:
pip install pip-tools
pip-sync requirements.txt
- name: Use Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v4
with:
node-version: '16.x'
- name: Cache Node.js modules
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }}
diff --git a/.github/workflows/frontendlint.yml b/.github/workflows/frontendlint.yml
index 6de4d701bd..c28a80937a 100644
--- a/.github/workflows/frontendlint.yml
+++ b/.github/workflows/frontendlint.yml
@@ -27,13 +27,13 @@ jobs:
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Use Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v4
with:
node-version: '16.x'
- name: Cache Node.js modules
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -49,7 +49,7 @@ jobs:
if: github.event.pull_request && github.event.pull_request.head.repo.full_name == github.repository
id: git-check
run: echo ::set-output name=modified::$(git diff-index --name-only HEAD)
- - uses: tibdex/github-app-token@v1
+ - uses: tibdex/github-app-token@v2
if: github.event.pull_request && github.event.pull_request.head.repo.full_name == github.repository && steps.git-check.outputs.modified != ''
id: generate-token
with:
diff --git a/.github/workflows/frontendtest.yml b/.github/workflows/frontendtest.yml
index c9ed46672c..e83ac316d8 100644
--- a/.github/workflows/frontendtest.yml
+++ b/.github/workflows/frontendtest.yml
@@ -27,13 +27,13 @@ jobs:
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Use Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v4
with:
node-version: '16.x'
- name: Cache Node.js modules
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }}
diff --git a/.github/workflows/notify_team_new_comment.yml b/.github/workflows/notify_team_new_comment.yml
new file mode 100644
index 0000000000..5c4f675d09
--- /dev/null
+++ b/.github/workflows/notify_team_new_comment.yml
@@ -0,0 +1,35 @@
+name: Send a slack notification when a contributor comments on issue
+
+on:
+ issue_comment:
+ types: [created]
+
+jobs:
+ contributor_issue_comment:
+ name: Contributor issue comment
+
+ if: >-
+ ${{
+ !github.event.issue.pull_request &&
+ github.event.comment.author_association != 'MEMBER' &&
+ github.event.comment.author_association != 'OWNER'
+ }}
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Escape title double quotes
+ id: escape_title
+ run: |
+ title='${{ github.event.issue.title }}'
+ echo "ISSUE_TITLE=${title//\"/\\\"}" >> "$GITHUB_OUTPUT"
+
+ - name: Send message to Slack channel
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+ SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
+ uses: slackapi/slack-github-action@v1.25.0
+ with:
+ payload: |
+ {
+ "text": "*[Studio] New comment on issue: <${{ github.event.issue.html_url }}#issuecomment-${{ github.event.comment.id }}|${{ steps.escape_title.outputs.ISSUE_TITLE }} by ${{ github.event.comment.user.login }}>*"
+ }
diff --git a/.github/workflows/pythontest.yml b/.github/workflows/pythontest.yml
index 217399ea8e..443e445b4e 100644
--- a/.github/workflows/pythontest.yml
+++ b/.github/workflows/pythontest.yml
@@ -61,7 +61,7 @@ jobs:
# Maps port 6379 on service container to the host
- 6379:6379
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up minio
run: |
docker run -d -p 9000:9000 --name minio \
@@ -70,12 +70,12 @@ jobs:
-v /tmp/minio_data:/data \
-v /tmp/minio_config:/root/.minio \
minio/minio server /data
- - name: Set up Python 3.9
- uses: actions/setup-python@v2
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v5
with:
- python-version: 3.9
+ python-version: '3.10'
- name: pip cache
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pytest-${{ hashFiles('requirements.txt', 'requirements-dev.txt') }}
diff --git a/.gitignore b/.gitignore
index b5e0261f09..8d869357f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,8 @@ var/
# IntelliJ IDE, except project config
.idea/*
!.idea/studio.iml
+# ignore future updates to run configuration
+.run/devserver.run.xml
# PyInstaller
# Usually these files are written by a python script from a template
@@ -95,8 +97,11 @@ contentcuration/csvs/
# Ignore the TAGS file generated by some editors
TAGS
-# Ignore Vagrant-created files
-/.vagrant/
+# Services
+.vagrant/
+.docker/minio/*
+.docker/postgres/*
+.docker/pgpass
# Ignore test files
/contentcuration/contentcuration/proxy_settings.py
diff --git a/.run/devserver.run.xml b/.run/devserver.run.xml
new file mode 100644
index 0000000000..1c94ee6402
--- /dev/null
+++ b/.run/devserver.run.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..35d0e2c4c1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,28 @@
+
+## How can I contribute?
+
+1. š **Skim through the [Developer documentation](./docs/_index.md)** to understand where to refer later on.
+2. š» **Follow the [Local development instructions](./docs/local_dev_docker.md) to set up your development server.**
+3. š **Search for issues tagged as [help wanted](https://github.com/learningequality/studio/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22+no%3Aassignee) or [good first issue](https://github.com/learningequality/studio/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+no%3Aassignee).**
+4. š£ļø **Ask us for an assignment in the comments of an issue you've chosen.** Please request assignment of a reasonable amount of issues at a time. Once you finish your current issue or two, you are welcome to ask for more.
+
+**ā Where to ask questions**
+
+- For anything development related, refer to the [Developer documentation](./docs/_index.md) at first. Some answers may already be there.
+- For questions related to a specific issue or assignment requests, use the corresponding issue's comments section.
+- Visit [GitHub Discussions](https://github.com/learningequality/studio/discussions) to ask about anything related to contributing or to troubleshoot development server issues.
+
+**š„ How to connect**
+
+- We encourage you to visit [GitHub Discussions](https://github.com/learningequality/studio/discussions) to connect with the Learning Equality team as well as with other contributors.
+- If you'd like to contribute on a regular basis, we are happy to invite you to our open-source community Slack channel. Get in touch with us at info@learningequality.org to receive an invitation.
+
+---
+
+š Please allow us a few days to reply to your comments. If you don't hear from us within a week, reach out via [GitHub Discussions](https://github.com/learningequality/studio/discussions).
+
+As soon as you open a pull request, it may take us a week or two to review it as we're a small team. We appreciate your contribution and will provide feedback.
+
+---
+
+*Thank you for your interest in contributing! Learning Equality was founded by volunteers dedicated to helping make educational materials more accessible to those in need, and every contribution makes a difference.*
diff --git a/Makefile b/Makefile
index 73fca6ea8c..cebcf1b79f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,11 @@
+# standalone install method
+DOCKER_COMPOSE = docker-compose
+
+# support new plugin installation for docker-compose
+ifeq (, $(shell which docker-compose))
+DOCKER_COMPOSE = docker compose
+endif
+
###############################################################
# PRODUCTION COMMANDS #########################################
###############################################################
@@ -19,10 +27,6 @@ gunicornserver: NUM_PROCS:=1
gunicornserver:
cd contentcuration/ && gunicorn contentcuration.wsgi:application --timeout=4000 --error-logfile=/var/log/gunicorn-error.log --workers=${NUM_PROCS} --bind=0.0.0.0:8081 --pid=/tmp/contentcuration.pid --log-level=debug || sleep infinity
-
-contentnodegc:
- cd contentcuration/ && python manage.py garbage_collect
-
prodceleryworkers:
cd contentcuration/ && celery -A contentcuration worker -l info --concurrency=3 --task-events
@@ -36,6 +40,21 @@ migrate:
python contentcuration/manage.py migrate || true
python contentcuration/manage.py loadconstants
+# This is a special command that is we'll reuse to run data migrations outside of the normal
+# django migration process. This is useful for long running migrations which we don't want to block
+# the CD build. Do not delete!
+# Procedure:
+# 1) Add a new management command for the migration
+# 2) Call it here
+# 3) Perform the release
+# 4) Remove the management command from this `deploy-migrate` recipe
+# 5) Repeat!
+deploy-migrate:
+ echo "Nothing to do here!"
+
+contentnodegc:
+ python contentcuration/manage.py garbage_collect
+
filedurations:
python contentcuration/manage.py set_file_duration
@@ -46,6 +65,10 @@ set-tsvectors:
python contentcuration/manage.py set_channel_tsvectors
python contentcuration/manage.py set_contentnode_tsvectors --published
+reconcile:
+ python contentcuration/manage.py reconcile_publishing_status
+ python contentcuration/manage.py reconcile_change_tasks
+
###############################################################
# END PRODUCTION COMMANDS #####################################
###############################################################
@@ -66,10 +89,10 @@ i18n-extract: i18n-extract-frontend i18n-extract-backend
i18n-transfer-context:
yarn transfercontext
-#i18n-django-compilemessages:
- # Change working directory to kolibri/ such that compilemessages
+i18n-django-compilemessages:
+ # Change working directory to contentcuration/ such that compilemessages
# finds only the .po files nested there.
- #cd kolibri && PYTHONPATH="..:$$PYTHONPATH" python -m kolibri manage compilemessages
+ cd contentcuration && python manage.py compilemessages
i18n-upload: i18n-extract
python node_modules/kolibri-tools/lib/i18n/crowdin.py upload-sources ${branch}
@@ -80,27 +103,15 @@ i18n-pretranslate:
i18n-pretranslate-approve-all:
python node_modules/kolibri-tools/lib/i18n/crowdin.py pretranslate ${branch} --approve-all
-i18n-convert:
- python node_modules/kolibri-tools/lib/i18n/crowdin.py convert-files
-
i18n-download-translations:
python node_modules/kolibri-tools/lib/i18n/crowdin.py rebuild-translations ${branch}
python node_modules/kolibri-tools/lib/i18n/crowdin.py download-translations ${branch}
- node node_modules/kolibri-tools/lib/i18n/intl_code_gen.js
- python node_modules/kolibri-tools/lib/i18n/crowdin.py convert-files
- # TODO: is this necessary? # Manual hack to add es language by copying es_ES to es
- # cp -r contentcuration/locale/es_ES contentcuration/locale/es
+ yarn exec kolibri-tools i18n-code-gen -- --output-dir ./contentcuration/contentcuration/frontend/shared/i18n
+ $(MAKE) i18n-django-compilemessages
+ yarn exec kolibri-tools i18n-create-message-files -- --namespace contentcuration --searchPath ./contentcuration/contentcuration/frontend
i18n-download: i18n-download-translations
-i18n-update:
- echo "WARNING: i18n-update has been renamed to i18n-download"
- $(MAKE) i18n-download
- echo "WARNING: i18n-update has been renamed to i18n-download"
-
-i18n-stats:
- python node_modules/kolibri-tools/lib/i18n/crowdin.py translation-stats ${branch}
-
i18n-download-glossary:
python node_modules/kolibri-tools/lib/i18n/crowdin.py download-glossary
@@ -137,11 +148,13 @@ dummyusers:
hascaptions:
python contentcuration/manage.py set_orm_based_has_captions
-export COMPOSE_PROJECT_NAME=studio_$(shell git rev-parse --abbrev-ref HEAD)
+BRANCH_NAME := $(shell git rev-parse --abbrev-ref HEAD | sed 's/[^a-zA-Z0-9_-]/-/g')
-purge-postgres:
- -PGPASSWORD=kolibri dropdb -U learningequality "kolibri-studio" --port 5432 -h localhost
- PGPASSWORD=kolibri createdb -U learningequality "kolibri-studio" --port 5432 -h localhost
+export COMPOSE_PROJECT_NAME=studio_$(BRANCH_NAME)
+
+purge-postgres: .docker/pgpass
+ -PGPASSFILE=.docker/pgpass dropdb -U learningequality "kolibri-studio" --port 5432 -h localhost
+ PGPASSFILE=.docker/pgpass createdb -U learningequality "kolibri-studio" --port 5432 -h localhost
destroy-and-recreate-database: purge-postgres setup
@@ -151,39 +164,56 @@ devceleryworkers:
run-services:
$(MAKE) -j 2 dcservicesup devceleryworkers
+.docker/minio:
+ mkdir -p $@
+
+.docker/postgres:
+ mkdir -p $@
+
+.docker/pgpass:
+ echo "localhost:5432:kolibri-studio:learningequality:kolibri" > $@
+ chmod 600 $@
+
+.docker/postgres/init.sql: .docker/pgpass
+ # assumes postgres is running in a docker container
+ PGPASSFILE=.docker/pgpass pg_dump --host localhost --port 5432 --username learningequality --dbname "kolibri-studio" --exclude-table-data=contentcuration_change --file $@
+
dcbuild:
# build all studio docker image and all dependent services using docker-compose
- docker-compose build
+ $(DOCKER_COMPOSE) build
-dcup:
+dcup: .docker/minio .docker/postgres
# run all services except for cloudprober
- docker-compose up studio-app celery-worker
+ $(DOCKER_COMPOSE) up studio-app celery-worker
-dcup-cloudprober:
+dcup-cloudprober: .docker/minio .docker/postgres
# run all services including cloudprober
- docker-compose up
+ $(DOCKER_COMPOSE) up
dcdown:
- # run make deverver in foreground with all dependent services using docker-compose
- docker-compose down
+ # run make deverver in foreground with all dependent services using $(DOCKER_COMPOSE)
+ $(DOCKER_COMPOSE) down
dcclean:
# stop all containers and delete volumes
- docker-compose down -v
+ $(DOCKER_COMPOSE) down -v
docker image prune -f
dcshell:
# bash shell inside the (running!) studio-app container
- docker-compose exec studio-app /usr/bin/fish
+ $(DOCKER_COMPOSE) exec studio-app /usr/bin/fish
+
+dcpsql: .docker/pgpass
+ PGPASSFILE=.docker/pgpass psql --host localhost --port 5432 --username learningequality --dbname "kolibri-studio"
-dctest:
+dctest: .docker/minio .docker/postgres
# run backend tests inside docker, in new instances
- docker-compose run studio-app make test
+ $(DOCKER_COMPOSE) run studio-app make test
-dcservicesup:
+dcservicesup: .docker/minio .docker/postgres
# launch all studio's dependent services using docker-compose
- docker-compose -f docker-compose.yml -f docker-compose.alt.yml up minio postgres redis
+ $(DOCKER_COMPOSE) -f docker-compose.yml -f docker-compose.alt.yml up minio postgres redis
dcservicesdown:
# stop services that were started using dcservicesup
- docker-compose -f docker-compose.yml -f docker-compose.alt.yml down
+ $(DOCKER_COMPOSE) -f docker-compose.yml -f docker-compose.alt.yml down
diff --git a/README.md b/README.md
index 821aba3b88..c7bd366d4e 100644
--- a/README.md
+++ b/README.md
@@ -13,258 +13,31 @@ Kolibri Studio uses the [Django framework](https://www.djangoproject.com/) for t
If you are looking for help setting up custom content channels, uploading and organizing resources using Kolibri Studio, please refer to the [User Guide](https://kolibri-studio.readthedocs.io/en/latest/).
-## Local development instructions
-The following guide utilizes docker and docker-compose to run select services required for Studio to function. If you would rather install these services on your host, please follow the [host-setup guide](docs/host_services_setup.md).
+
+## How can I contribute?
-### Prerequisites
-Please install these prerequisites, or alternatives for setting up your local development environment:
-- [volta](https://docs.volta.sh/guide/getting-started) or a different node.js manager
-- [pyenv](https://kolibri-dev.readthedocs.io/en/develop/howtos/installing_pyenv.html) and [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv#installation)
-- [docker](https://docs.docker.com/install/) and [docker-compose](https://docs.docker.com/compose/install/)
+1. š **Skim through the [Developer documentation](./docs/_index.md)** to understand where to refer later on.
+2. š» **Follow the [Local development instructions](./docs/local_dev_docker.md) to set up your development server.**
+3. š **Search for issues tagged as [help wanted](https://github.com/learningequality/studio/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22+no%3Aassignee) or [good first issue](https://github.com/learningequality/studio/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+no%3Aassignee).**
+4. š£ļø **Ask us for an assignment in the comments of an issue you've chosen.** Please request assignment of a reasonable amount of issues at a time. Once you finish your current issue or two, you are welcome to ask for more.
+**ā Where to ask questions**
-### Build your python virtual environment
-To determine the preferred version of Python, you can check the `runtime.txt` file:
-```bash
-$ cat runtime.txt
-# This is the required version of Python to run Studio currently.
-# This is determined by the default Python 3 version that is installed
-# inside Ubuntu Bionic, which is used to build images for Studio.
-# We encode it here so that it can be picked up by Github's dependabot
-# to manage automated package upgrades.
-python-3.9.13
-```
-Use `pyenv` to install the version of Python listed in that file, and to also set up a virtual environment:
-```bash
-pyenv install 3.9.13
-pyenv virtualenv 3.9.13 studio-py3.9
-pyenv activate studio-py3.9
-```
-Now you may install Studio's Python dependencies:
-```bash
-pip install -r requirements.txt -r requirements-dev.txt
-```
-To deactivate the virtual environment, when you're finished developing on Studio for the time being:
-```bash
-pyenv deactivate
-```
+- For anything development related, refer to the [Developer documentation](./docs/_index.md) at first. Some answers may already be there.
+- For questions related to a specific issue or assignment requests, use the corresponding issue's comments section.
+- Visit [GitHub Discussions](https://github.com/learningequality/studio/discussions) to ask about anything related to contributing or to troubleshoot development server issues.
-#### A note about dependencies on Apple Silicon M1+
-If you run into an error with `pip install` related to the `grcpio` package, it is because it currently [does not support M1 with the version for `grcpio` Studio uses](https://github.com/grpc/grpc/issues/25082). In order to fix it, you will need to add the following environmental variables before running `pip install`:
-```bash
-export GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1
-export GRPC_PYTHON_BUILD_SYSTEM_ZLIB=1
-export CFLAGS="-I/opt/homebrew/opt/openssl/include"
-export LDFLAGS="-L/opt/homebrew/opt/openssl/lib"
-```
+**š„ How to connect**
-### Install frontend dependencies
-Install the version of node.js supported by Studio, and install `yarn`:
-```bash
-volta install node@16
-volta install yarn
-```
-After installing `yarn`, you may now install frontend dependencies:
-```bash
-yarn install
-```
+- We encourage you to visit [GitHub Discussions](https://github.com/learningequality/studio/discussions) to connect with the Learning Equality team as well as with other contributors.
+- If you'd like to contribute on a regular basis, we are happy to invite you to our open-source community Slack channel. Get in touch with us at info@learningequality.org to receive an invitation.
-### Install and run services
+---
-Studio requires some background services to be running:
+š Please allow us a few days to reply to your comments. If you don't hear from us within a week, reach out via [GitHub Discussions](https://github.com/learningequality/studio/discussions).
-* Minio - a local S3 storage emulation
-* PostgreSQL (postgres) - a relational database
-* Redis - a fast key/value store useful for caching
-* Celery - the task manager and executor, which relies on the Studio codebase
+As soon as you open a pull request, it may take us a week or two to review it as we're a small team. We appreciate your contribution and will provide feedback.
-Generally speaking, you'll want to open a separate terminal/terminal-tab to run the services. With docker and docker-compose installed, running the above services is as easy as:
-```bash
-make run-services
-```
+---
-The above command may take longer the first time it's run. It includes starting the `celery` workers, and the other dependent services through docker, which can be done separately with the following two commands:
-
-```bash
-make dcservicesup
-make devceleryworkers
-```
-
-To confirm that docker-based services are running, you should see three containers when executing `docker ps`. For example:
-
-```bash
-> docker ps
-CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
-e09c5c203b93 redis:6.0.9 "docker-entrypoint.sā¦" 51 seconds ago Up 49 seconds 0.0.0.0:6379->6379/tcp studio_vue-refactor_redis_1
-6164371efb6b minio/minio "minio server /data" 51 seconds ago Up 49 seconds 0.0.0.0:9000->9000/tcp studio_vue-refactor_minio_1
-c86bbfa3a59e postgres:12.10 "docker-entrypoint.sā¦" 51 seconds ago Up 49 seconds 0.0.0.0:5432->5432/tcp studio_vue-refactor_postgres_1
-```
-
-To stop the services, press Ctrl + C in the terminal where you ran `make run-services` (or `dcservicesup`). Once you've done that, you may run the following command to remove the docker containers (they will be recreated when you run `run-services` or `dcservicesup` again):
-```bash
-make dcservicesdown
-```
-
-### Initializing Studio
-With the services running, in a separate terminal/terminal-tab, we can now initialize the database for Studio development purposes. The command below will initialize the database tables, import constants, and a user account for development:
-```bash
-yarn run devsetup
-```
-
-### Running the development server
-With the services running, in a separate terminal/terminal-tab, and the database initialized, we can start the dev server:
-```bash
-yarn run devserver:hot # with Vue hot module reloading
-# or
-yarn run devserver # without hot module reloading
-```
-
-Either of the above commands will take a few moments to build the frontend. When it finishes, you can sign in with the account created by the `yarn run devsetup` command:
-- url: `http://localhost:8080/accounts/login/`
-- username: `a@a.com`
-- password: `a`
-
-### Running the celery service
-Studio uses `celery` for executing asynchronous tasks, which are integral to Studio's channel editing architecture. The celery service does not reload when there are Python changes like the Django devserver does, so it's often preferred to run it separately. If you are developing changes against a task or the celery configuration, you'll need to use `make dcservicesup` to run only the docker-based services.
-
-In a separate terminal/terminal-tab, run the following to start the service and press Ctrl + C to stop it:
-```bash
-make devceleryworkers
-```
-
-Stop and restart the above to reload your changes.
-
-## Adding or updating dependencies
-
-We use `pip-tools` to ensure all our dependencies use the same versions on all deployments.
-
-To add a dependency, add it to either `requirements.in` or `requirements-dev.in`, then
-run `pip-compile requirements[-dev|-docs].in` to generate the .txt file. Please make sure that
-both the `.in` and `.txt` file changes are part of the commit when updating dependencies.
-
-To update a dependency, use `pip-compile --upgrade-package [package-name] requirements[-dev|-docs].in`
-
-For more details, please see the [pip-tools docs on Github](https://github.com/jazzband/pip-tools).
-
-## Additional tools
-
-### Running tests
-
-With Studio's services running, you may run tests with the following commands:
-
-```bash
-# backend
-make test
-# frontend
-yarn run test
-```
-
-View [more testing tips](docs/running_tests.md)
-
-### Linting
-
-Front-end linting is run using:
-
-```bash
-yarn run lint-frontend
-```
-
-Some linting errors can be fixed automatically by running:
-
-```bash
-yarn run lint-frontend:format
-```
-
-Make sure you've set up pre-commit hooks as described above. This will ensure that linting is automatically run on staged changes before every commit.
-
-### Profiling and local production testing
-
-If you want to test the performance of your changes, you can start up a local server with settings closer to a production environment like so:
-
-```bash
-# build frontend dependencies
-yarn run build
-# run the server (no webpack)
-yarn run runserver
-# or for profiling production more closely
-yarn run runserver:prod-profiling
-```
-
-Once the local production server is running, you can also use Locust to test your changes under scenarios of high demand like so:
-
-```bash
-cd deploy/chaos/loadtest
-make timed_run
-make stop_slaves # mac: killall python
-```
-
-#### Profiling
-
-In case you need to profile the application to know which part of the code are more time consuming, there are two different profilers available to work in two different modes. Both will store the profiling output in a directory that's determined by the `PROFILE_DIR` env variable. If this variable is not set, the output files will be store in a folder called profiler inside the OS temp folder (`/tmp/profile` usually)
-Note that both profiling modes are incompatible: you can either use one or the other, but not both at the same time. In case the env variables are set for both modes, _All request profiling mode_ will be used.
-
-##### All requests profiling mode
-
-This mode will create interactive html files with all the profiling information for every request the Studio server receives. The name of the files will contain the total execution time, the endpoint name and a timestamp.
-
-To activate it an env variable called `PROFILE_STUDIO_FULL` must be set.
-
-Example of use:
-
-`PROFILE_STUDIO_FULL=y yarn runserver`
-
-Afterwards no further treatment of the generated files is needed. You can open directly the html files in your browser.
-
-##### Endpoint profiling mode
-
-When using the _all requests mode_ it's usual that the profile folder is soon full of information for requests that are not interesting for the developer, obscuring the files for specific endpoints.
-
-If an env variable called `PROFILE_STUDIO_FILTER` is used, the profiler will be executed only on the http requests containing the text stated by the variable.
-
-Example of use:
-
-`PROFILE_STUDIO_FILTER=edit yarn localprodserver`
-
-For this case, only html requests having the text _edit_ in their request path will be profiled. The profile folder will not have html files, but binary dump files (with the timestamp as filename) of the profiler information that can be later seen by different profiling tools (`snakeviz` that can be installed using pip is recommended). Also while the server is running, the ten most time consuming lines of code of the filtered request will be shown in the console where Studio has been launched.
-
-Example of snakeviz use:
-
-`snakeviz /tmp/profile/studio\:20200909161405011678.prof`
-
-will open the browser with an interactive diagram with all the profiling information
-
-### Storybook
-
-Storybook is a development environment for UI components. If this is your first encounter with this tool, you can check [this presentation](https://docs.google.com/presentation/d/10JL4C9buygWsTbT62Ym149Yh9zSR9nY_ZqFumBKUY0o/edit?usp=sharing) or [its website](https://storybook.js.org/). You are encouraged to use it any time you need to develop a new UI component. It is especially suitable for smaller to middle size components that represent basic UI building blocks.
-
-An example is worth a thousand words so please have a look at these simple [stories of an example component](./contentcuration/contentcuration/frontend/shared/views/details/DetailsRow.stories.js) to see how to write yours. For detailed information on writing stories you can [go through this tutorial](https://www.learnstorybook.com/intro-to-storybook/).
-
-You can also check [official addons](https://storybook.js.org/addons/).
-
-**Run development server**
-
-```bash
-yarn run storybook
-```
-
-With detailed webpack information (useful when debugging loaders, addons and similar):
-
-```bash
-yarn run storybook:debug
-```
-
-**Bundle**
-
-```bash
-yarn run storybook:build
-```
-
-The output is saved to *storybook-static/*.
-
-### Current usage notes
-
-We've decided not to push our stories to the codebase and keep them locally in the near future. Although this limits the number of advantages Storybook provides, it allows us to start using it as soon as possible without the need to agree on all conventions and it also gives the whole team enough time to test the development workflow so we can decide later if we want to adopt this tool in a larger scale.
-
-Taking into account the above-mentioned, all stories except of example *DetailsRow.stories.js* will be ignored by git as long as you use a naming convention for Storybook source files: *\*.stories.js*.
-
-Although we don't share stories at this point, Storybook is installed and configured in the codebase to prevent the need for everyone to configure everything locally. If you update Storybook Webpack settings, install a new plugin and similar, you are welcome to share such updates with other members of the team.
+*Thank you for your interest in contributing! Learning Equality was founded by volunteers dedicated to helping make educational materials more accessible to those in need, and every contribution makes a difference.*
diff --git a/bin/run_minio.py b/bin/run_minio.py
deleted file mode 100755
index 42adf31562..0000000000
--- a/bin/run_minio.py
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import pathlib
-import subprocess
-
-MINIO_RUN_TYPES = ["LOCAL", "GCS_PROXY"]
-
-MINIO_LOCAL_HOME_STORAGE = pathlib.Path("/app") / "contentworkshop_content"
-
-MINIO_CONFIG_DIR = MINIO_LOCAL_HOME_STORAGE / ".minio"
-
-GOOGLE_APPLICATION_CREDENTIALS_PATH = os.getenv("GOOGLE_APPLICATION_CREDENTIALS")
-
-GOOGLE_GCS_PROJECT_ID = os.getenv("GOOGLE_GCS_PROJECT_ID")
-
-
-if __name__ == "__main__":
-
- run_type = os.getenv("MINIO_RUN_TYPE")
-
- if run_type not in MINIO_RUN_TYPES:
- raise AssertionError("MINIO_RUN_TYPE must be one of {}".format(MINIO_RUN_TYPES))
-
- if run_type == "LOCAL":
- cmd = ["minio", "server", "-C", str(MINIO_CONFIG_DIR), str(MINIO_LOCAL_HOME_STORAGE)]
- elif run_type == "GCS_PROXY":
-
- if not os.path.exists(GOOGLE_APPLICATION_CREDENTIALS_PATH):
- raise AssertionError("the env var GOOGLE_APPLICATION_CREDENTIALS must be defined," " and pointing to a credentials file for your project.")
-
- if not GOOGLE_GCS_PROJECT_ID:
- raise AssertionError("$GOOGLE_GCS_PROJECT_ID must be defined with the project" " id where you store your objects.")
- cmd = ["minio", "gateway", "gcs", GOOGLE_GCS_PROJECT_ID]
- else:
- raise Exception("Unhandled run_type type: {}".format(run_type))
-
- subprocess.check_call(cmd)
-
-
diff --git a/contentcuration/automation/__init__.py b/contentcuration/automation/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contentcuration/automation/admin.py b/contentcuration/automation/admin.py
new file mode 100644
index 0000000000..4185d360e9
--- /dev/null
+++ b/contentcuration/automation/admin.py
@@ -0,0 +1,3 @@
+# from django.contrib import admin
+
+# Register your models here.
diff --git a/contentcuration/automation/apps.py b/contentcuration/automation/apps.py
new file mode 100644
index 0000000000..eaa1d3d4e1
--- /dev/null
+++ b/contentcuration/automation/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class AutomationConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'automation'
diff --git a/contentcuration/automation/migrations/__init__.py b/contentcuration/automation/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contentcuration/automation/models.py b/contentcuration/automation/models.py
new file mode 100644
index 0000000000..0b4331b362
--- /dev/null
+++ b/contentcuration/automation/models.py
@@ -0,0 +1,3 @@
+# from django.db import models
+
+# Create your models here.
diff --git a/contentcuration/automation/tests.py b/contentcuration/automation/tests.py
new file mode 100644
index 0000000000..a79ca8be56
--- /dev/null
+++ b/contentcuration/automation/tests.py
@@ -0,0 +1,3 @@
+# from django.test import TestCase
+
+# Create your tests here.
diff --git a/contentcuration/automation/tests/appnexus/__init__.py b/contentcuration/automation/tests/appnexus/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contentcuration/automation/tests/appnexus/test_base.py b/contentcuration/automation/tests/appnexus/test_base.py
new file mode 100644
index 0000000000..7944e00e4f
--- /dev/null
+++ b/contentcuration/automation/tests/appnexus/test_base.py
@@ -0,0 +1,48 @@
+import pytest
+
+from automation.utils.appnexus.base import Adapter
+from automation.utils.appnexus.base import Backend
+
+
+class MockBackend(Backend):
+ def connect(self) -> None:
+ return super().connect()
+
+ def make_request(self, request):
+ return super().make_request(request)
+
+ @classmethod
+ def _create_instance(cls) -> 'MockBackend':
+ return cls()
+
+
+class MockAdapter(Adapter):
+ def mockoperation(self):
+ pass
+
+
+def test_backend_error():
+ with pytest.raises(NotImplementedError) as error:
+ Backend.get_instance()
+ assert "Subclasses should implement the creation of instance" in str(error.value)
+
+def test_backend_singleton():
+ b1, b2 = MockBackend.get_instance(), MockBackend.get_instance()
+ assert id(b1) == id(b2)
+
+
+def test_adapter_creation():
+ a = MockAdapter(backend=MockBackend)
+ assert isinstance(a, Adapter)
+
+
+def test_adapter_backend_default():
+ b = MockBackend()
+ adapter = Adapter(backend=b)
+ assert isinstance(adapter.backend, Backend)
+
+
+def test_adapter_backend_custom():
+ b = MockBackend()
+ a = Adapter(backend=b)
+ assert a.backend is b
diff --git a/contentcuration/automation/utils/appnexus/APILayer.md b/contentcuration/automation/utils/appnexus/APILayer.md
new file mode 100644
index 0000000000..4e82e5b3f3
--- /dev/null
+++ b/contentcuration/automation/utils/appnexus/APILayer.md
@@ -0,0 +1,161 @@
+## API Layer Documentation
+
+### Overview
+
+Within the `contentcuration` app in Studio, we want to build an API layer that acts as a communication bridge with different backends like Docker Images, Google Cloud Platform's Vertex AI, and VM instances, cloud storage services, etc. The goal is to make sure this API layer can work with these backends, regardless of where or how they do the job. As long as the input and output formats stay the same, this setup provides flexibility in choosing and using backend resources.
+
+### Description and outcomes
+
+The stand-alone deployed backend service(s) will not have direct access to `contentcuration` models or the database for that matter, so this API layer facilitates access to these resources by receiving and returning a standardized requests and responses, irrespective of the backend interacted with.
+
+#### The Architecture
+
+
+
+The key components of this architecture are as follows:
+
+#### 1. Creating the Backend Interface
+
+The Backend class serves as an abstract interface that outlines the operations all backends must support. It implements the Singleton pattern to ensure that only one instance of each backend type can exist. The methods defined by the Backend class are:
+
+```python
+ABSTRACT CLASS Backend:
+ _instance = None # Private variable to hold the instance
+
+ ABSTRACT METHOD connect()
+ # Provides blue print to connect
+ pass
+
+ ABSTRACT METHOD make_request(params)
+ # provide blue print to make request
+ pass
+
+ ABSTRACT METHOD request(params)
+ # provide blue print for the request object
+ pass
+
+ ABSTRACT METHOD response(params)
+ # provides blue print for the response object
+ pass
+
+ CLASS METHOD get_instance(cls)
+ IF cls._instance is None:
+ cls._instance = cls._create_instance()
+ return cls._instance
+
+ CLASS METHOD _create_instance(cls)
+ raise NotImplementedError # concrete class must implement
+```
+
+Different backends can now be created by implementing the base `Backend` class:
+
+```python
+# Implement CONCRETE CLASS using ABSTRACT Backend class
+CLASS GCS IMPLEMENTS Backend:
+ METHOD make_request(request):
+ # make request to Google Cloud Storage services
+
+ METHOD connect(params):
+ # Implement the connect method for GCS
+
+ CLASS METHOD _create_instance(cls)
+ # initialize a GCS Backend instance
+
+CLASS ML IMPLEMENTS Backend:
+ METHOD make_request(request):
+ # make request to DeepLearning models hosted as service
+
+ METHOD connect(params):
+ # Implement the connect method for hosted ML service
+
+ CLASS METHOD _create_instance(cls)
+ # initialize a ML Backend instance
+
+CLASS OtherBackend IMPLEMENTS Backend:
+ ...
+ [you get the idea]
+```
+
+To create an instance of a backend, using the `ML` class as an example, use the `get_instance()` method:
+
+```python
+>>> backend = ML.get_instance()
+```
+
+To centralize the creation of `Backend` instances based on specific Django settings(e.g. dev vs. production environments), create `BackendFactory` class. This should follow the Factory Design Pattern.
+
+```python
+# Factory to instantiate the Backend based on Django Settings
+CLASS BackendFactory:
+ METHOD create_backend(self, backend=None) -> Backend
+ IF backend:
+ return backend
+ ELSE:
+ # Create an Adapter instance based on Django settings
+ IF DjangoSettings is 'SomeSetting':
+ backend = GCS.get_instance() # Use of Singleton pattern
+ ELSE IF DjangoSettings is 'AnotherSetting':
+ backend = ML.get_instance()
+ ELSE
+ RAISE ValueError
+ # Return the created Backend instance
+ RETURN backend
+```
+The `BackendFactory`'s `create_backend` method optionally allows a `Backend` instance to be injected into the factory instead of relying solely on Django settings. This is particularly useful if we want to explicitly specify the backend to use.
+
+### Creating Adapter that accepts any Backend
+
+The **`Adapter`** class can be initialized with a `Backend` instance(optional) which provides a `make_request` method that forwards requests to the chosen `Backend`, while adhering to its specific `request` and `response` formats.
+
+```python
+CLASS Adapter:
+
+ METHOD __init__(self, backend(Optional) defaults None)
+ # Initialize the Backend with BackendFactory
+ backend_factory = BackendFactory()
+ SET backend = backend_factory.create_backend(backend)
+
+ METHOD request(self):
+ # something
+ return self.backend.request()
+
+ METHOD response(self):
+ # something
+ return self.backend.response()
+```
+
+With this `Adapter` class in place, we can create Adapter that are able interact with any backend we need.
+
+```python
+CLASS Recommendation INHERITS ADAPTER:
+ METHOD generateEmbeddings(self, request) -> Boolean
+ # [ Implementation ]
+
+ METHOD getRecommendation(self, request) -> Array
+ # [ Implementation ]
+
+CLASS Transcription INHERITS ADAPTER:
+ METHOD generateCaption(self, request) -> Array
+ # [ Implementation ]
+
+CLASS OtherAdapter INHERITS ADAPTER:
+ METHOD someOperation(self, request) -> Any
+ # Operation that any backend wants
+```
+
+Below is a sample use case, using the `ML` backend as an example:
+
+```python
+>>> backend = ML.get_instance()
+>>> adapter = Transcription(backend)
+```
+
+To access specific methods within the adapter:
+
+```python
+>>> adapter.generateCaption(...)
+```
+
+### Resources
+
+[OOP Design patterns](https://refactoring.guru/design-patterns/catalog)
diff --git a/contentcuration/automation/utils/appnexus/__init__.py b/contentcuration/automation/utils/appnexus/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contentcuration/automation/utils/appnexus/base.py b/contentcuration/automation/utils/appnexus/base.py
new file mode 100644
index 0000000000..ab9e6d5096
--- /dev/null
+++ b/contentcuration/automation/utils/appnexus/base.py
@@ -0,0 +1,62 @@
+from abc import ABC
+from abc import abstractmethod
+from builtins import NotImplementedError
+
+
+class BackendRequest(object):
+ """ Class that should be inherited by specific backend for its requests"""
+ pass
+
+
+class BackendResponse(object):
+ """ Class that should be inherited by specific backend for its responses"""
+ pass
+
+
+class Backend(ABC):
+ """ An abstract base class for backend interfaces that also implements the singleton pattern """
+ _instance = None
+
+ def __new__(class_, *args, **kwargs):
+ if not isinstance(class_._instance, class_):
+ class_._instance = object.__new__(class_, *args, **kwargs)
+ return class_._instance
+
+ @abstractmethod
+ def connect(self) -> None:
+ """ Establishes a connection to the backend service. """
+ pass
+
+ @abstractmethod
+ def make_request(self, request) -> BackendResponse:
+ """ Make a request based on "request" """
+ pass
+
+ @classmethod
+ def get_instance(cls) -> 'Backend':
+ """ Returns existing instance, if not then create one. """
+ return cls._instance if cls._instance else cls._create_instance()
+
+ @classmethod
+ def _create_instance(cls) -> 'Backend':
+ """ Returns the instance after creating it. """
+ raise NotImplementedError("Subclasses should implement the creation of instance")
+
+
+class BackendFactory(ABC):
+ @abstractmethod
+ def create_backend(self) -> Backend:
+ """ Create a Backend instance from the given backend. """
+ pass
+
+
+class Adapter:
+ """
+ Base class for adapters that interact with a backend interface.
+
+ This class should be inherited by adapter classes that facilitate
+ interaction with different backend implementations.
+ """
+
+ def __init__(self, backend: Backend) -> None:
+ self.backend = backend
diff --git a/contentcuration/automation/views.py b/contentcuration/automation/views.py
new file mode 100644
index 0000000000..fd0e044955
--- /dev/null
+++ b/contentcuration/automation/views.py
@@ -0,0 +1,3 @@
+# from django.shortcuts import render
+
+# Create your views here.
diff --git a/contentcuration/contentcuration/api.py b/contentcuration/contentcuration/api.py
index 33c9692cbc..b297ffaba6 100644
--- a/contentcuration/contentcuration/api.py
+++ b/contentcuration/contentcuration/api.py
@@ -10,9 +10,6 @@
from django.core.files.storage import default_storage
import contentcuration.models as models
-from contentcuration.utils.garbage_collect import get_deleted_chefs_root
-from contentcuration.viewsets.sync.constants import CHANNEL
-from contentcuration.viewsets.sync.utils import generate_update_event
def write_file_to_storage(fobj, check_valid=False, name=None):
@@ -68,33 +65,3 @@ def get_hash(fobj):
md5.update(chunk)
fobj.seek(0)
return md5.hexdigest()
-
-
-def activate_channel(channel, user):
- user.check_channel_space(channel)
-
- if channel.previous_tree and channel.previous_tree != channel.main_tree:
- # IMPORTANT: Do not remove this block, MPTT updating the deleted chefs block could hang the server
- with models.ContentNode.objects.disable_mptt_updates():
- garbage_node = get_deleted_chefs_root()
- channel.previous_tree.parent = garbage_node
- channel.previous_tree.title = "Previous tree for channel {}".format(channel.pk)
- channel.previous_tree.save()
-
- channel.previous_tree = channel.main_tree
- channel.main_tree = channel.staging_tree
- channel.staging_tree = None
- channel.save()
-
- user.staged_files.all().delete()
- user.set_space_used()
-
- models.Change.create_change(generate_update_event(
- channel.id,
- CHANNEL,
- {
- "root_id": channel.main_tree.id,
- "staging_root_id": None
- },
- channel_id=channel.id,
- ), applied=True, created_by_id=user.id)
diff --git a/contentcuration/contentcuration/constants/channel_history.py b/contentcuration/contentcuration/constants/channel_history.py
index 28de05e035..790b4dfd51 100644
--- a/contentcuration/contentcuration/constants/channel_history.py
+++ b/contentcuration/contentcuration/constants/channel_history.py
@@ -1,13 +1,11 @@
-from django.utils.translation import ugettext_lazy as _
-
CREATION = "creation"
PUBLICATION = "publication"
DELETION = "deletion"
RECOVERY = "recovery"
choices = (
- (CREATION, _("Creation")),
- (PUBLICATION, _("Publication")),
- (DELETION, _("Deletion")),
- (RECOVERY, _("Deletion recovery")),
+ (CREATION, "Creation"),
+ (PUBLICATION, "Publication"),
+ (DELETION, "Deletion"),
+ (RECOVERY, "Deletion recovery"),
)
diff --git a/contentcuration/contentcuration/constants/locking.py b/contentcuration/contentcuration/constants/locking.py
new file mode 100644
index 0000000000..6b53fbd081
--- /dev/null
+++ b/contentcuration/contentcuration/constants/locking.py
@@ -0,0 +1,5 @@
+"""
+Constants for locking behaviors, like advisory locking in Postgres, and mutexes
+"""
+TREE_LOCK = 1001
+TASK_LOCK = 1002
diff --git a/contentcuration/contentcuration/constants/user_history.py b/contentcuration/contentcuration/constants/user_history.py
new file mode 100644
index 0000000000..9adc9b56c6
--- /dev/null
+++ b/contentcuration/contentcuration/constants/user_history.py
@@ -0,0 +1,9 @@
+DELETION = "soft-deletion"
+RECOVERY = "soft-recovery"
+RELATED_DATA_HARD_DELETION = "related-data-hard-deletion"
+
+choices = (
+ (DELETION, "User soft deletion"),
+ (RECOVERY, "User soft deletion recovery"),
+ (RELATED_DATA_HARD_DELETION, "User related data hard deletion"),
+)
diff --git a/contentcuration/contentcuration/db/advisory_lock.py b/contentcuration/contentcuration/db/advisory_lock.py
index 61d53a379f..f1d71995ed 100644
--- a/contentcuration/contentcuration/db/advisory_lock.py
+++ b/contentcuration/contentcuration/db/advisory_lock.py
@@ -6,11 +6,36 @@
logging = logger.getLogger(__name__)
+# signed limits are 2**32 or 2**64, so one less power of 2
+# to become unsigned limits (half above 0, half below 0)
+INT_32BIT = 2**31
+INT_64BIT = 2**63
+
class AdvisoryLockBusy(RuntimeError):
pass
+def _prepare_keys(keys):
+ """
+ Ensures that integers do not exceed postgres constraints:
+ - signed 64bit allowed with single key
+ - signed 32bit allowed with two keys
+ :param keys: A list of unsigned integers
+ :return: A list of signed integers
+ """
+ limit = INT_64BIT if len(keys) == 1 else INT_32BIT
+ new_keys = []
+ for key in keys:
+ # if key is over the limit, convert to negative int since key should be unsigned int
+ if key >= limit:
+ key = limit - key
+ if key < -limit or key >= limit:
+ raise OverflowError(f"Advisory lock key '{key}' is too large")
+ new_keys.append(key)
+ return new_keys
+
+
@contextmanager
def execute_lock(key1, key2=None, unlock=False, session=False, shared=False, wait=True):
"""
@@ -32,6 +57,7 @@ def execute_lock(key1, key2=None, unlock=False, session=False, shared=False, wai
keys = [key1]
if key2 is not None:
keys.append(key2)
+ keys = _prepare_keys(keys)
query = "SELECT pg{_try}_advisory_{xact_}{lock}{_shared}({keys}) AS lock;".format(
_try="" if wait else "_try",
@@ -41,11 +67,11 @@ def execute_lock(key1, key2=None, unlock=False, session=False, shared=False, wai
keys=", ".join(["%s" for i in range(0, 2 if key2 is not None else 1)])
)
- log_query = "'{}' with params {}".format(query, keys)
- logging.debug("Acquiring advisory lock: {}".format(query, log_query))
+ log_query = f"'{query}' with params {keys}"
+ logging.debug(f"Acquiring advisory lock: {log_query}")
with connection.cursor() as c:
c.execute(query, keys)
- logging.debug("Acquired advisory lock: {}".format(query, log_query))
+ logging.debug(f"Acquired advisory lock: {log_query}")
yield c
diff --git a/contentcuration/contentcuration/db/models/manager.py b/contentcuration/contentcuration/db/models/manager.py
index 3556fe8e70..db1e3a77bf 100644
--- a/contentcuration/contentcuration/db/models/manager.py
+++ b/contentcuration/contentcuration/db/models/manager.py
@@ -12,6 +12,7 @@
from mptt.managers import TreeManager
from mptt.signals import node_moved
+from contentcuration.constants.locking import TREE_LOCK
from contentcuration.db.advisory_lock import advisory_lock
from contentcuration.db.models.query import CustomTreeQuerySet
from contentcuration.utils.cache import ResourceSizeCache
@@ -32,7 +33,6 @@
# The exact optimum batch size is probably highly dependent on tree
# topology also, so these rudimentary tests are likely insufficient
BATCH_SIZE = 100
-TREE_LOCK = 1001
class CustomManager(Manager.from_queryset(CTEQuerySet)):
@@ -47,14 +47,33 @@ def log_lock_time_spent(timespent):
logging.debug("Spent {} seconds inside an mptt lock".format(timespent))
-def execute_queryset_without_results(queryset):
- query = queryset.query
- compiler = query.get_compiler(queryset.db)
- sql, params = compiler.as_sql()
- if not sql:
- return
- cursor = compiler.connection.cursor()
- cursor.execute(sql, params)
+# Fields that are allowed to be overridden on copies coming from a source that the user
+# does not have edit rights to.
+ALLOWED_OVERRIDES = {
+ "node_id",
+ "title",
+ "description",
+ "aggregator",
+ "provider",
+ "language_id",
+ "grade_levels",
+ "resource_types",
+ "learning_activities",
+ "accessibility_labels",
+ "categories",
+ "learner_needs",
+ "role",
+ "extra_fields",
+ "suggested_duration",
+}
+
+EDIT_ALLOWED_OVERRIDES = ALLOWED_OVERRIDES.union({
+ "license_id",
+ "license_description",
+ "extra_fields",
+ "copyright_holder",
+ "author",
+})
class CustomContentNodeTreeManager(TreeManager.from_queryset(CustomTreeQuerySet)):
@@ -272,7 +291,10 @@ def _clone_node(
copy.update(self.get_source_attributes(source))
if isinstance(mods, dict):
- copy.update(mods)
+ allowed_keys = EDIT_ALLOWED_OVERRIDES if can_edit_source_channel else ALLOWED_OVERRIDES
+ for key, value in mods.items():
+ if key in copy and key in allowed_keys:
+ copy[key] = value
# There might be some legacy nodes that don't have these, so ensure they are added
if (
@@ -465,6 +487,7 @@ def _copy_tags(self, source_copy_id_map):
tag_id_map[tag.id] = new_tag.id
tags_to_create.append(new_tag)
+ # TODO: Can cleanup the above and change the below to use ignore_conflicts=True
ContentTag.objects.bulk_create(tags_to_create)
mappings_to_create = [
@@ -477,7 +500,10 @@ def _copy_tags(self, source_copy_id_map):
for mapping in node_tags_mappings
]
- self.model.tags.through.objects.bulk_create(mappings_to_create)
+ # In the case that we are copying a node that is in the weird state of having a tag
+ # that is duplicated (with a channel tag and a null channel tag) this can cause an error
+ # so we ignore conflicts here to ignore the duplicate tags.
+ self.model.tags.through.objects.bulk_create(mappings_to_create, ignore_conflicts=True)
def _copy_assessment_items(self, source_copy_id_map):
from contentcuration.models import File
diff --git a/contentcuration/contentcuration/decorators.py b/contentcuration/contentcuration/decorators.py
index e8a2dd5a0d..9c51e83b7a 100644
--- a/contentcuration/contentcuration/decorators.py
+++ b/contentcuration/contentcuration/decorators.py
@@ -76,6 +76,10 @@ class DelayUserStorageCalculation(ContextDecorator):
def is_active(self):
return self.depth > 0
+ def add(self, user_id):
+ if user_id not in self.queue:
+ self.queue.append(user_id)
+
def __enter__(self):
self.depth += 1
diff --git a/contentcuration/contentcuration/dev_settings.py b/contentcuration/contentcuration/dev_settings.py
index d81d23a993..439bdef8af 100644
--- a/contentcuration/contentcuration/dev_settings.py
+++ b/contentcuration/contentcuration/dev_settings.py
@@ -5,4 +5,4 @@
ROOT_URLCONF = "contentcuration.dev_urls"
-INSTALLED_APPS += ("drf_yasg",)
+INSTALLED_APPS += ("drf_yasg", "automation")
diff --git a/contentcuration/contentcuration/forms.py b/contentcuration/contentcuration/forms.py
index 973916431e..d9dc781f61 100644
--- a/contentcuration/contentcuration/forms.py
+++ b/contentcuration/contentcuration/forms.py
@@ -7,6 +7,7 @@
from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.forms import UserCreationForm
from django.core import signing
+from django.db.models import Q
from django.template.loader import render_to_string
from contentcuration.models import User
@@ -45,7 +46,7 @@ class RegistrationForm(UserCreationForm, ExtraFormMixin):
def clean_email(self):
email = self.cleaned_data['email'].strip().lower()
- if User.objects.filter(email__iexact=email, is_active=True).exists():
+ if User.objects.filter(Q(is_active=True) | Q(deleted=True), email__iexact=email).exists():
raise UserWarning
return email
diff --git a/contentcuration/contentcuration/frontend/accounts/components/MessageLayout.vue b/contentcuration/contentcuration/frontend/accounts/components/MessageLayout.vue
index 0a89c4ba5d..869ccf3a88 100644
--- a/contentcuration/contentcuration/frontend/accounts/components/MessageLayout.vue
+++ b/contentcuration/contentcuration/frontend/accounts/components/MessageLayout.vue
@@ -13,7 +13,10 @@
-
+
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/Create.vue b/contentcuration/contentcuration/frontend/accounts/pages/Create.vue
index 361ffce01d..b211cd5634 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/Create.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/Create.vue
@@ -15,7 +15,7 @@
{{ $tr('createAnAccountTitle') }}
-
+
{{ registrationFailed ? $tr('registrationFailed') : $tr('errorsMessage') }}
@@ -131,42 +131,45 @@
/>
-
-
+
+
+
+
+
+ {{ $tr('contactMessage') }}
+
+
-
- {{ $tr('contactMessage') }}
-
-
- {{ $tr('finishButton') }}
-
+
@@ -238,12 +241,19 @@
passwordConfirmRules() {
return [value => (this.form.password1 === value ? true : this.$tr('passwordMatchMessage'))];
},
- tosRules() {
+ tosAndPolicyRules() {
return [value => (value ? true : this.$tr('ToSRequiredMessage'))];
},
- policyRules() {
- return [value => (value ? true : this.$tr('privacyPolicyRequiredMessage'))];
+ acceptedAgreement: {
+ get() {
+ return this.form.accepted_tos && this.form.accepted_policy;
+ },
+ set(accepted) {
+ this.form.accepted_tos = accepted;
+ this.form.accepted_policy = accepted;
+ },
},
+
usageOptions() {
return [
{
@@ -350,7 +360,7 @@
},
clean() {
return data => {
- let cleanedData = { ...data, policies: {} };
+ const cleanedData = { ...data, policies: {} };
Object.keys(cleanedData).forEach(key => {
// Trim text fields
if (key === 'source') {
@@ -413,10 +423,9 @@
showOtherField(id) {
return id === uses.OTHER && this.form.uses.includes(id);
},
-
submit() {
if (this.$refs.form.validate()) {
- let cleanedData = this.clean(this.form);
+ const cleanedData = this.clean(this.form);
return this.register(cleanedData)
.then(() => {
this.$router.push({ name: 'ActivationSent' });
@@ -439,6 +448,7 @@
return Promise.resolve();
},
},
+
$trs: {
backToLoginButton: 'Sign in',
createAnAccountTitle: 'Create an account',
@@ -447,7 +457,6 @@
registrationFailed: 'There was an error registering your account. Please try again',
registrationFailedOffline:
'You seem to be offline. Please connect to the internet to create an account.',
-
// Basic information strings
basicInformationHeader: 'Basic information',
firstNameLabel: 'First name',
@@ -492,15 +501,13 @@
otherSourcePlaceholder: 'Please describe',
// Privacy policy + terms of service
- viewToSLink: 'View terms of service',
- ToSCheck: 'I have read and agree to the terms of service',
- ToSRequiredMessage: 'Please accept our terms of service',
+ viewToSLink: 'View Terms of Service',
+ ToSRequiredMessage: 'Please accept our terms of service and policy',
- viewPrivacyPolicyLink: 'View privacy policy',
- privacyPolicyCheck: 'I have read and agree to the privacy policy',
- privacyPolicyRequiredMessage: 'Please accept our privacy policy',
+ viewPrivacyPolicyLink: 'View Privacy Policy',
contactMessage: 'Questions or concerns? Please email us at content@learningequality.org',
finishButton: 'Finish',
+ agreement: 'I have read and agree to terms of service and the privacy policy',
},
};
@@ -521,6 +528,11 @@
}
}
+ .policy-checkbox /deep/ .v-messages {
+ min-height: 0;
+ margin-left: 40px;
+ }
+
iframe {
width: 100%;
min-height: 400px;
@@ -529,4 +541,23 @@
border: 0;
}
+ .span-spacing {
+ display: flex;
+ margin-left: 40px;
+ }
+
+ .span-spacing span {
+ margin-left: 2px;
+ font-size: 16px;
+ }
+
+ .span-spacing-email {
+ margin-left: 3px;
+ font-size: 16px;
+ }
+
+ .align-items {
+ display: block;
+ }
+
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/Main.vue b/contentcuration/contentcuration/frontend/accounts/pages/Main.vue
index c15a96fca1..49833f60e2 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/Main.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/Main.vue
@@ -6,12 +6,12 @@
justify-center
class="main pt-5"
>
-
-
-
-
+
{{ $tr('kolibriStudio') }}
-
-
+
+
-
-
-
+
+
+
-
+
-
- {{ $tr('signInButton') }}
-
-
- {{ $tr('createAccountButton') }}
-
+
+
-
+
@@ -90,7 +123,6 @@
import PolicyModals from 'shared/views/policies/PolicyModals';
import { policies } from 'shared/constants';
import LanguageSwitcherList from 'shared/languageSwitcher/LanguageSwitcherList';
- import OfflineText from 'shared/views/OfflineText';
export default {
name: 'Main',
@@ -100,7 +132,6 @@
LanguageSwitcherList,
PasswordField,
PolicyModals,
- OfflineText,
},
data() {
return {
@@ -132,7 +163,7 @@
submit() {
if (this.$refs.form.validate()) {
this.busy = true;
- let credentials = {
+ const credentials = {
username: this.username,
password: this.password,
};
@@ -180,6 +211,7 @@
.main {
overflow: auto;
+ /* stylelint-disable-next-line custom-property-pattern */
background-color: var(--v-backgroundColor-base);
}
@@ -191,10 +223,8 @@
content: 'ā¢';
}
- .corner {
- position: absolute;
- top: 1em;
- left: 1em;
+ .w-100 {
+ width: 100%;
}
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js
index dc9f24df06..f2a201b560 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js
+++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js
@@ -34,7 +34,7 @@ const defaultData = {
const register = jest.fn();
function makeWrapper(formData) {
- let wrapper = mount(Create, {
+ const wrapper = mount(Create, {
router,
computed: {
getPolicyAcceptedData() {
@@ -62,7 +62,6 @@ function makeWrapper(formData) {
});
return wrapper;
}
-
function makeFailedPromise(statusCode) {
return () => {
return new Promise((resolve, reject) => {
@@ -81,13 +80,13 @@ describe('create', () => {
});
it('should trigger submit method when form is submitted', () => {
const submit = jest.fn();
- let wrapper = makeWrapper();
+ const wrapper = makeWrapper();
wrapper.setMethods({ submit });
wrapper.find({ ref: 'form' }).trigger('submit');
expect(submit).toHaveBeenCalled();
});
it('should call register with form data', () => {
- let wrapper = makeWrapper();
+ const wrapper = makeWrapper();
wrapper.find({ ref: 'form' }).trigger('submit');
expect(register.mock.calls[0][0]).toEqual({
...defaultData,
@@ -98,12 +97,12 @@ describe('create', () => {
});
it('should automatically fill the email if provided in the query param', () => {
router.push({ name: 'Create', query: { email: 'newtest@test.com' } });
- let wrapper = mount(Create, { router, stubs: ['PolicyModals'], mocks: connectionStateMocks });
+ const wrapper = mount(Create, { router, stubs: ['PolicyModals'], mocks: connectionStateMocks });
expect(wrapper.vm.form.email).toBe('newtest@test.com');
});
describe('validation', () => {
it('should call register if form is valid', () => {
- let wrapper = makeWrapper();
+ const wrapper = makeWrapper();
wrapper.vm.submit();
expect(register).toHaveBeenCalled();
});
@@ -122,26 +121,26 @@ describe('create', () => {
};
Object.keys(form).forEach(field => {
- let wrapper = makeWrapper({ [field]: form[field] });
+ const wrapper = makeWrapper({ [field]: form[field] });
wrapper.vm.submit();
expect(register).not.toHaveBeenCalled();
});
});
it('should fail if password1 and password2 do not match', () => {
- let wrapper = makeWrapper({ password1: 'some other password' });
+ const wrapper = makeWrapper({ password1: 'some other password' });
wrapper.vm.submit();
expect(register).not.toHaveBeenCalled();
});
it('should fail if uses field is set to fields that require more input that is not provided', () => {
[uses.STORING, uses.OTHER].forEach(use => {
- let wrapper = makeWrapper({ uses: [use] });
+ const wrapper = makeWrapper({ uses: [use] });
wrapper.vm.submit();
expect(register).not.toHaveBeenCalled();
});
});
it('should fail if source field is set to an option that requires more input that is not provided', () => {
[sources.ORGANIZATION, sources.CONFERENCE, sources.OTHER].forEach(source => {
- let wrapper = makeWrapper({ source });
+ const wrapper = makeWrapper({ source });
wrapper.vm.submit();
expect(register).not.toHaveBeenCalled();
});
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/main.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/main.spec.js
index dc57ccf210..2525a83d57 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/main.spec.js
+++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/main.spec.js
@@ -5,7 +5,7 @@ import Main from '../Main';
const login = jest.fn();
function makeWrapper() {
- let wrapper = mount(Main, {
+ const wrapper = mount(Main, {
router,
stubs: ['GlobalSnackbar', 'PolicyModals'],
mocks: {
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/accountDeleted/AccountDeleted.vue b/contentcuration/contentcuration/frontend/accounts/pages/accountDeleted/AccountDeleted.vue
index f881737065..032cfc506a 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/accountDeleted/AccountDeleted.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/accountDeleted/AccountDeleted.vue
@@ -5,7 +5,7 @@
>
- {{ $tr('continueToSignIn') }}
+ {{ $tr('backToLogin') }}
@@ -24,7 +24,7 @@
},
$trs: {
accountDeletedTitle: 'Account successfully deleted',
- continueToSignIn: 'Continue to sign-in page',
+ backToLogin: 'Continue to sign-in page',
},
};
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/AccountCreated.vue b/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/AccountCreated.vue
index b55bbdf418..14e106c232 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/AccountCreated.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/AccountCreated.vue
@@ -5,7 +5,7 @@
>
- {{ $tr('continueToSignIn') }}
+ {{ $tr('backToLogin') }}
@@ -24,7 +24,7 @@
},
$trs: {
accountCreatedTitle: 'Account successfully created',
- continueToSignIn: 'Continue to sign-in',
+ backToLogin: 'Continue to sign-in page',
},
};
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/AccountNotActivated.vue b/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/AccountNotActivated.vue
index a0db43bca2..ccbec003d1 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/AccountNotActivated.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/AccountNotActivated.vue
@@ -4,9 +4,12 @@
:header="$tr('title')"
:text="$tr('text')"
>
-
- {{ $tr('requestNewLink') }}
-
+
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/ActivationExpired.vue b/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/ActivationExpired.vue
index cdf2db7a43..9dd1dad18c 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/ActivationExpired.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/ActivationExpired.vue
@@ -4,9 +4,12 @@
:header="$tr('activationExpiredTitle')"
:text="$tr('activationExpiredText')"
>
-
- {{ $tr('requestNewLink') }}
-
+
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/RequestNewActivationLink.vue b/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/RequestNewActivationLink.vue
index df889b3ecb..afb06cdaca 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/RequestNewActivationLink.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/activateAccount/RequestNewActivationLink.vue
@@ -11,9 +11,12 @@
>
-
- {{ $tr('submitButton') }}
-
+
@@ -64,3 +67,11 @@
};
+
+
\ No newline at end of file
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ForgotPassword.vue b/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ForgotPassword.vue
index 13910a9bad..7349cf97ff 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ForgotPassword.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ForgotPassword.vue
@@ -7,9 +7,12 @@
-
- {{ $tr('submitButton') }}
-
+
@@ -65,3 +68,11 @@
};
+
+
\ No newline at end of file
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ResetLinkExpired.vue b/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ResetLinkExpired.vue
index 4f0c8fc057..0249fd9fda 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ResetLinkExpired.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ResetLinkExpired.vue
@@ -4,9 +4,12 @@
:header="$tr('resetExpiredTitle')"
:text="$tr('resetExpiredText')"
>
-
- {{ $tr('requestNewLink') }}
-
+
diff --git a/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ResetPassword.vue b/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ResetPassword.vue
index b3866f5ccd..2fd3ceddd3 100644
--- a/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ResetPassword.vue
+++ b/contentcuration/contentcuration/frontend/accounts/pages/resetPassword/ResetPassword.vue
@@ -16,9 +16,12 @@
:label="$tr('passwordConfirmLabel')"
:additionalRules="passwordConfirmRules"
/>
-
- {{ $tr('submitButton') }}
-
+
@@ -55,7 +58,7 @@
submit() {
this.error = false;
if (this.$refs.form.validate()) {
- let payload = {
+ const payload = {
...this.$route.query,
new_password1: this.new_password1,
new_password2: this.new_password2,
@@ -84,3 +87,11 @@
};
+
+
diff --git a/contentcuration/contentcuration/frontend/accounts/vuex/index.js b/contentcuration/contentcuration/frontend/accounts/vuex/index.js
index bc95d1ed29..f64df2778a 100644
--- a/contentcuration/contentcuration/frontend/accounts/vuex/index.js
+++ b/contentcuration/contentcuration/frontend/accounts/vuex/index.js
@@ -17,7 +17,7 @@ export default {
return client.post(window.Urls.auth_password_reset(), { email });
},
setPassword(context, { uidb64, token, new_password1, new_password2 }) {
- let data = {
+ const data = {
new_password1,
new_password2,
};
diff --git a/contentcuration/contentcuration/frontend/administration/mixins.js b/contentcuration/contentcuration/frontend/administration/mixins.js
index 529c75ffae..c27e933ed5 100644
--- a/contentcuration/contentcuration/frontend/administration/mixins.js
+++ b/contentcuration/contentcuration/frontend/administration/mixins.js
@@ -24,7 +24,7 @@ export function generateFilterMixin(filterMap) {
return this.$route.query.keywords;
},
set(value) {
- let params = { ...this.$route.query, page: 1 };
+ const params = { ...this.$route.query, page: 1 };
if (value) {
params.keywords = value;
} else {
@@ -37,7 +37,7 @@ export function generateFilterMixin(filterMap) {
get() {
// Return filter where all param conditions are met
const filterKeys = intersection(Object.keys(this.$route.query), paramKeys);
- let key = findKey(filterMap, value => {
+ const key = findKey(filterMap, value => {
return filterKeys.every(field => {
return value.params[field] === _getBooleanVal(this.$route.query[field]);
});
@@ -115,7 +115,7 @@ export const tableMixin = {
computed: {
pagination: {
get() {
- let params = {
+ const params = {
rowsPerPage: Number(this.$route.query.page_size) || 25,
page: Number(this.$route.query.page) || 1,
};
@@ -160,7 +160,7 @@ export const tableMixin = {
...this.$route.query,
};
if (params.sortBy) {
- params.ordering = (params.descending ? '-' : '') + params.sortBy;
+ params.ordering = (String(params.descending) === 'true' ? '-' : '') + params.sortBy;
delete params.sortBy;
delete params.descending;
}
diff --git a/contentcuration/contentcuration/frontend/administration/pages/AdministrationIndex.vue b/contentcuration/contentcuration/frontend/administration/pages/AdministrationIndex.vue
index 7ed794230f..10f2b67efb 100644
--- a/contentcuration/contentcuration/frontend/administration/pages/AdministrationIndex.vue
+++ b/contentcuration/contentcuration/frontend/administration/pages/AdministrationIndex.vue
@@ -128,6 +128,7 @@
.v-icon:not(.v-icon--is-component) {
font-size: 16pt !important;
+ /* stylelint-disable-next-line custom-property-pattern */
color: var(--v-darkGrey-darken1) !important;
opacity: 1 !important;
transform: none !important;
@@ -159,6 +160,7 @@
}
tr:hover td {
+ /* stylelint-disable-next-line custom-property-pattern */
background-color: var(--v-greyBackground-base) !important;
}
diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelActionsDropdown.vue b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelActionsDropdown.vue
index d71841492f..491706777c 100644
--- a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelActionsDropdown.vue
+++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelActionsDropdown.vue
@@ -153,7 +153,7 @@
return {
name: RouteNames.USERS,
query: {
- keywords: `${this.name} ${this.channel.id}`,
+ keywords: `${this.channel.id}`,
},
};
},
diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelDetails.vue b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelDetails.vue
index a7029e2710..b867ee36e8 100644
--- a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelDetails.vue
+++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelDetails.vue
@@ -19,7 +19,7 @@
-
+
This channel has been deleted
@@ -102,6 +102,9 @@
channel() {
return this.getChannel(this.channelId);
},
+ isDeleted() {
+ return this.channel && Boolean(this.channel?.deleted);
+ },
channelWithDetails() {
if (!this.channel || !this.details) {
return {};
diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelItem.vue b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelItem.vue
index 3e975714af..d87e1d4d3f 100644
--- a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelItem.vue
+++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelItem.vue
@@ -176,8 +176,8 @@
import ClipboardChip from '../../components/ClipboardChip';
import { RouteNames } from '../../constants';
import ChannelActionsDropdown from './ChannelActionsDropdown';
- import Checkbox from 'shared/views/form/Checkbox';
import { fileSizeMixin } from 'shared/mixins';
+ import Checkbox from 'shared/views/form/Checkbox';
export default {
name: 'ChannelItem',
@@ -232,7 +232,7 @@
return {
name: RouteNames.USERS,
query: {
- keywords: `${this.channel.name} ${this.channelId}`,
+ keywords: `${this.channelId}`,
},
};
},
diff --git a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue
index 76bfc4d4e2..b4185d8a85 100644
--- a/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue
+++ b/contentcuration/contentcuration/frontend/administration/pages/Channels/ChannelTable.vue
@@ -100,8 +100,8 @@
diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/progress/ProgressModal.vue b/contentcuration/contentcuration/frontend/channelEdit/views/progress/ProgressModal.vue
index f5ce76de6b..a124e0cc6e 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/views/progress/ProgressModal.vue
+++ b/contentcuration/contentcuration/frontend/channelEdit/views/progress/ProgressModal.vue
@@ -1,14 +1,21 @@
-
+
{{ $tr('syncHeader') }} {{ progressPercent }}
-
+
error
{{ $tr('syncError') }}
@@ -17,15 +24,25 @@
v-else-if="currentPublishTaskError"
class="red--text"
>
-
+
error
{{ $tr('defaultErrorText') }}
-
+
{{ $tr('publishHeader') }} {{ progressPercent }}
-
+
{{ lastPublished ?
$tr('lastPublished', { last_published: $formatRelative(lastPublished, now) }) :
$tr('unpublishedText')
@@ -37,7 +54,7 @@
diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/progress/__tests__/progressModal.spec.js b/contentcuration/contentcuration/frontend/channelEdit/views/progress/__tests__/progressModal.spec.js
index 61df49ad6e..e3a285f206 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/views/progress/__tests__/progressModal.spec.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/views/progress/__tests__/progressModal.spec.js
@@ -165,7 +165,7 @@ describe('ProgressModal', () => {
});
it('should display syncing message', () => {
- expect(wrapper.text()).toContain('Syncing channel');
+ expect(wrapper.text()).toContain('Syncing resources');
});
it('should display progress', () => {
diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/sync/SyncResourcesModal.vue b/contentcuration/contentcuration/frontend/channelEdit/views/sync/SyncResourcesModal.vue
index 055f8ccc34..e403c02256 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/views/sync/SyncResourcesModal.vue
+++ b/contentcuration/contentcuration/frontend/channelEdit/views/sync/SyncResourcesModal.vue
@@ -8,15 +8,22 @@
:submitDisabled="!continueAllowed"
:submitText="$tr('continueButtonLabel')"
:cancelText="$tr('cancelButtonLabel')"
+ :size="525"
@cancel="syncModal = false"
@submit="handleContinue"
>
-
- {{ $tr('syncModalExplainer') }}
+
+ {{ $tr('syncModalExplainer') }}
+
+ {{ $tr('syncModalSelectAttributes') }}
+
-
+
{{ $tr('syncFilesTitle') }}
@@ -26,17 +33,23 @@
-
+
-
- {{ $tr('syncTagsTitle') }}
- {{ $tr('syncTagsExplainer') }}
+
+ {{ $tr('syncResourceDetailsTitle') }}
+ {{ $tr('syncResourceDetailsExplainer') }}
-
+
{{ $tr('syncTitlesAndDescriptionsTitle') }}
@@ -46,7 +59,10 @@
-
+
{{ $tr('syncExercisesTitle') }}
@@ -63,26 +79,44 @@
:title="$tr('confirmSyncModalTitle')"
:submitText="$tr('syncButtonLabel')"
:cancelText="$tr('backButtonLabel')"
+ :size="525"
@cancel="handleBack"
@submit="handleSync"
>
- {{ $tr('confirmSyncModalExplainer') }}
+
+ {{ $tr('confirmSyncModalExplainer') }}
+
-
-
+
+
{{ $tr('syncFilesTitle') }}
-
- {{ $tr('syncTagsTitle') }}
+
+ {{ $tr('syncResourceDetailsTitle') }}
-
+
{{ $tr('syncTitlesAndDescriptionsTitle') }}
-
+
{{ $tr('syncExercisesTitle') }}
+
+ {{ $tr('confirmSyncModalWarningExplainer') }}
+
@@ -121,7 +155,7 @@
confirmSyncModal: false, // the should show second step
// user choices about which kind of resources to sync
syncFiles: false,
- syncTags: false,
+ syncResourceDetails: false,
syncTitlesAndDescriptions: false,
syncExercises: false,
};
@@ -141,7 +175,10 @@
continueAllowed() {
// allow CONTINUE button to appear only if something will be synced
return (
- this.syncFiles || this.syncTags || this.syncTitlesAndDescriptions || this.syncExercises
+ this.syncFiles ||
+ this.syncResourceDetails ||
+ this.syncTitlesAndDescriptions ||
+ this.syncExercises
);
},
},
@@ -161,8 +198,8 @@
},
handleSync() {
Channel.sync(this.channelId, {
- attributes: this.syncTitlesAndDescriptions,
- tags: this.syncTags,
+ titles_and_descriptions: this.syncTitlesAndDescriptions,
+ resource_details: this.syncResourceDetails,
files: this.syncFiles,
assessment_items: this.syncExercises,
})
@@ -171,7 +208,7 @@
this.$emit('syncing');
})
.catch(() => {
- // add a way for the progress modal to provide feedback
+ // add a way for the progress modal to provide feedback titles_and_descriptions
// since the available error message doesn't make sense here,
// for now we will just have the operation be reported complete
// see ProgressModal nothingToSync for more info
@@ -183,21 +220,26 @@
$trs: {
// Sync modal (Step 1 of 3)
syncModalTitle: 'Sync resources',
- syncModalExplainer: 'Sync and update your resources with their original source.',
+ syncModalExplainer:
+ 'Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files.',
+ syncModalSelectAttributes: 'Select what you would like to sync:',
syncFilesTitle: 'Files',
- syncFilesExplainer: 'Update all file information',
- syncTagsTitle: 'Tags',
- syncTagsExplainer: 'Update all tags',
+ syncFilesExplainer: 'Update all files, including: thumbnails, subtitles, and captions',
+ syncResourceDetailsTitle: 'Resource details',
+ syncResourceDetailsExplainer:
+ 'Update information about the resource: learning activity, level, requirements, category, tags, audience, and source',
syncTitlesAndDescriptionsTitle: 'Titles and descriptions',
syncTitlesAndDescriptionsExplainer: 'Update resource titles and descriptions',
syncExercisesTitle: 'Assessment details',
- syncExercisesExplainer: 'Update questions, answers, and hints',
+ syncExercisesExplainer: 'Update questions, answers, and hints in exercises and quizzes',
cancelButtonLabel: 'Cancel',
continueButtonLabel: 'Continue',
//
// Confirm sync (Step 2 of 3)
confirmSyncModalTitle: 'Confirm sync',
confirmSyncModalExplainer: 'You are about to sync and update the following:',
+ confirmSyncModalWarningExplainer:
+ 'Warning: this will overwrite any changes you have made to copied or imported resources.',
backButtonLabel: 'Back',
syncButtonLabel: 'Sync',
//
@@ -218,9 +260,16 @@
padding: 0;
}
- .v-list__tile {
+ /deep/ .v-list__tile {
+ align-items: flex-start;
height: unset;
- min-height: 72px;
+ padding: 8px 0;
+ }
+
+ .v-list__tile__action {
+ min-width: 42px;
+ height: unset;
+ padding: 2px 0 0;
}
.v-list__tile__sub-title {
@@ -228,4 +277,10 @@
white-space: unset;
}
+ .no-bullets {
+ padding: 0;
+ margin: 0;
+ list-style-type: none;
+ }
+
diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue b/contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue
index cf3364a20c..1212ab9fff 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue
+++ b/contentcuration/contentcuration/frontend/channelEdit/views/trash/TrashModal.vue
@@ -241,7 +241,7 @@
this.toggleSelectAll(false);
},
deleteNodes() {
- let text = this.$tr('deleteSuccessMessage');
+ const text = this.$tr('deleteSuccessMessage');
this.deleteContentNodes(this.selected).then(() => {
this.showConfirmationDialog = false;
this.reset();
diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/trash/__tests__/trashModal.spec.js b/contentcuration/contentcuration/frontend/channelEdit/views/trash/__tests__/trashModal.spec.js
index 602913bb3c..63130d7bd1 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/views/trash/__tests__/trashModal.spec.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/views/trash/__tests__/trashModal.spec.js
@@ -77,7 +77,7 @@ describe('trashModal', () => {
expect(wrapper.find('[data-test="loading"]').exists()).toBe(true);
});
it('should show empty text if there are no items', () => {
- let emptyWrapper = makeWrapper([]);
+ const emptyWrapper = makeWrapper([]);
emptyWrapper.setData({ loading: false });
expect(emptyWrapper.find('[data-test="empty"]').exists()).toBe(true);
});
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/actions.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/actions.js
index b1cdd58c6a..8452cc0641 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/actions.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/assessmentItem/actions.js
@@ -41,16 +41,20 @@ export function addAssessmentItem(context, assessmentItem) {
hints: JSON.stringify(assessmentItem.hints || []),
};
- return db.transaction('rw', [TABLE_NAMES.CONTENTNODE, TABLE_NAMES.ASSESSMENTITEM], () => {
- return AssessmentItem.put(stringifiedAssessmentItem).then(([contentnode, assessment_id]) => {
- context.commit('UPDATE_ASSESSMENTITEM', {
- ...assessmentItem,
- contentnode,
- assessment_id,
+ return db.transaction(
+ 'rw',
+ [TABLE_NAMES.CONTENTNODE, TABLE_NAMES.ASSESSMENTITEM, TABLE_NAMES.CHANGES_TABLE],
+ () => {
+ return AssessmentItem.add(stringifiedAssessmentItem).then(([contentnode, assessment_id]) => {
+ context.commit('UPDATE_ASSESSMENTITEM', {
+ ...assessmentItem,
+ contentnode,
+ assessment_id,
+ });
+ return updateNodeComplete(contentnode, context);
});
- return updateNodeComplete(contentnode, context);
- });
- });
+ }
+ );
}
export function updateAssessmentItem(context, assessmentItem) {
return updateAssessmentItems(context, [assessmentItem]);
@@ -65,38 +69,46 @@ export function updateAssessmentItems(context, assessmentItems) {
context.commit('UPDATE_ASSESSMENTITEM', assessmentItem);
});
- return db.transaction('rw', [TABLE_NAMES.CONTENTNODE, TABLE_NAMES.ASSESSMENTITEM], () => {
- return Promise.all(
- assessmentItems.map(assessmentItem => {
- // API accepts answers and hints as strings
- let stringifiedAssessmentItem = {
- ...assessmentItem,
- };
- if (assessmentItem.answers) {
- stringifiedAssessmentItem.answers = JSON.stringify(assessmentItem.answers);
- }
- if (assessmentItem.hints) {
- stringifiedAssessmentItem.hints = JSON.stringify(assessmentItem.hints);
- }
- return AssessmentItem.update(
- [assessmentItem.contentnode, assessmentItem.assessment_id],
- stringifiedAssessmentItem
- ).then(() => {
- updateNodeComplete(assessmentItem.contentnode, context);
- });
- })
- );
- });
+ return db.transaction(
+ 'rw',
+ [TABLE_NAMES.CONTENTNODE, TABLE_NAMES.ASSESSMENTITEM, TABLE_NAMES.CHANGES_TABLE],
+ () => {
+ return Promise.all(
+ assessmentItems.map(assessmentItem => {
+ // API accepts answers and hints as strings
+ const stringifiedAssessmentItem = {
+ ...assessmentItem,
+ };
+ if (assessmentItem.answers) {
+ stringifiedAssessmentItem.answers = JSON.stringify(assessmentItem.answers);
+ }
+ if (assessmentItem.hints) {
+ stringifiedAssessmentItem.hints = JSON.stringify(assessmentItem.hints);
+ }
+ return AssessmentItem.update(
+ [assessmentItem.contentnode, assessmentItem.assessment_id],
+ stringifiedAssessmentItem
+ ).then(() => {
+ updateNodeComplete(assessmentItem.contentnode, context);
+ });
+ })
+ );
+ }
+ );
}
export function deleteAssessmentItem(context, assessmentItem) {
- return db.transaction('rw', [TABLE_NAMES.CONTENTNODE, TABLE_NAMES.ASSESSMENTITEM], () => {
- return AssessmentItem.delete([assessmentItem.contentnode, assessmentItem.assessment_id]).then(
- () => {
- context.commit('DELETE_ASSESSMENTITEM', assessmentItem);
- const contentnode = assessmentItem.contentnode;
- return updateNodeComplete(contentnode, context);
- }
- );
- });
+ return db.transaction(
+ 'rw',
+ [TABLE_NAMES.CONTENTNODE, TABLE_NAMES.ASSESSMENTITEM, TABLE_NAMES.CHANGES_TABLE],
+ () => {
+ return AssessmentItem.delete([assessmentItem.contentnode, assessmentItem.assessment_id]).then(
+ () => {
+ context.commit('DELETE_ASSESSMENTITEM', assessmentItem);
+ const contentnode = assessmentItem.contentnode;
+ return updateNodeComplete(contentnode, context);
+ }
+ );
+ }
+ );
}
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/actions.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/actions.js
index 2358d4ded4..d6327f65fe 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/actions.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/actions.js
@@ -1,5 +1,6 @@
import get from 'lodash/get';
import partition from 'lodash/partition';
+import chunk from 'lodash/chunk';
import uniq from 'lodash/uniq';
import uniqBy from 'lodash/uniqBy';
import defer from 'lodash/defer';
@@ -83,12 +84,20 @@ export function loadClipboardNodes(context, { parent }) {
const legacyNodeIds = legacyNodes.map(n => n.id);
return Promise.all([
- context.dispatch(
- 'contentNode/loadContentNodes',
- { '[node_id+channel_id]__in': nodeIdChannelIdPairs },
- { root }
+ // To avoid error code 414 URI Too Long errors, we chunk the pairs
+ // Given URI limit is 2000 chars:
+ // base URL at 100 chars + each pair at 70 chars = max 27 pairs
+ ...chunk(nodeIdChannelIdPairs, 25).map(chunkPairs =>
+ context.dispatch(
+ 'contentNode/loadContentNodes',
+ { '[node_id+channel_id]__in': chunkPairs },
+ { root }
+ )
+ ),
+ // Chunk legacy nodes, double the size since not pairs
+ ...chunk(legacyNodeIds, 50).map(legacyChunk =>
+ context.dispatch('contentNode/loadContentNodes', { id__in: legacyChunk }, { root })
),
- context.dispatch('contentNode/loadContentNodes', { id__in: legacyNodeIds }, { root }),
]).then(() => {
return context.dispatch('addClipboardNodes', {
nodes: clipboardNodes,
@@ -463,7 +472,7 @@ export function moveClipboardNodes(context, { legacyTrees, newTrees, target }) {
);
}
if (newTrees.length) {
- for (let copyNode of newTrees) {
+ for (const copyNode of newTrees) {
promises.push(
context.dispatch(
'contentNode/copyContentNode',
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/getters.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/getters.js
index 3fe6ae14ba..4ed5ba31ee 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/getters.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/getters.js
@@ -443,8 +443,8 @@ export function getCopyTrees(state, getters, rootState, rootGetters) {
// can now switch mode to just return a mask of unselected node_ids
if (!(selectionState & SelectionFlags.ALL_DESCENDANTS) && !ignoreSelection) {
// Some of the children are not selected, so get the node_ids that aren't selected
- for (let child of children) {
- for (let key of recurseForUnselectedIds(child.id)) {
+ for (const child of children) {
+ for (const key of recurseForUnselectedIds(child.id)) {
update.extra_fields.excluded_descendants[key] = true;
}
}
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/mutations.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/mutations.js
index db23f6ee15..92070dd930 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/mutations.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/clipboard/mutations.js
@@ -17,7 +17,7 @@ export function ADD_CLIPBOARD_NODE(state, clipboardNode) {
}
export function ADD_CLIPBOARD_NODES(state, clipboardNodes) {
- for (let clipboardNode of clipboardNodes) {
+ for (const clipboardNode of clipboardNodes) {
ADD_CLIPBOARD_NODE(state, clipboardNode);
}
}
@@ -31,7 +31,7 @@ export function SET_PREVIEW_NODE(state, id) {
}
export function SET_PRELOAD_NODES(state, preloadNodes) {
- for (let parent in preloadNodes) {
+ for (const parent in preloadNodes) {
Vue.set(state.preloadNodes, parent, preloadNodes[parent]);
}
}
@@ -41,7 +41,7 @@ export function REMOVE_PRELOAD_NODES(state, parent) {
}
export function RESET_PRELOAD_NODES(state) {
- for (let key in state.preloadNodes) {
+ for (const key in state.preloadNodes) {
Vue.delete(state.preloadNodes, key);
}
}
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/__tests__/actions.spec.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/__tests__/actions.spec.js
index 0ebffd904c..8f0767beeb 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/__tests__/actions.spec.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/__tests__/actions.spec.js
@@ -4,6 +4,7 @@ import assessmentItem from '../../assessmentItem/index';
import file from 'shared/vuex/file';
import { ContentNode } from 'shared/data/resources';
import storeFactory from 'shared/vuex/baseStore';
+import { mockChannelScope, resetMockChannelScope } from 'shared/utils/testing';
jest.mock('../../currentChannel/index');
@@ -14,9 +15,18 @@ const parentId = '000000000000000000000000000000000000';
describe('contentNode actions', () => {
let store;
let id;
- const contentNodeDatum = { title: 'test', parent: parentId, lft: 1, tags: {} };
- beforeEach(() => {
- return ContentNode._put(contentNodeDatum).then(newId => {
+ const contentNodeDatum = {
+ title: 'test',
+ parent: parentId,
+ lft: 1,
+ tags: {},
+ total_count: 0,
+ resource_count: 0,
+ coach_count: 0,
+ };
+ beforeEach(async () => {
+ await mockChannelScope('test-123');
+ return ContentNode._add(contentNodeDatum).then(newId => {
id = newId;
contentNodeDatum.id = newId;
jest
@@ -25,7 +35,7 @@ describe('contentNode actions', () => {
jest
.spyOn(ContentNode, 'fetchModel')
.mockImplementation(() => Promise.resolve(contentNodeDatum));
- return ContentNode._put({ title: 'notatest', parent: newId, lft: 2 }).then(() => {
+ return ContentNode._add({ title: 'notatest', parent: newId, lft: 2 }).then(() => {
store = storeFactory({
modules: {
assessmentItem,
@@ -38,7 +48,8 @@ describe('contentNode actions', () => {
});
});
});
- afterEach(() => {
+ afterEach(async () => {
+ await resetMockChannelScope();
jest.restoreAllMocks();
return ContentNode.table.toCollection().delete();
});
@@ -62,6 +73,8 @@ describe('contentNode actions', () => {
expect(Object.values(store.state.contentNode.contentNodesMap)).toEqual([
{
...contentNodeDatum,
+ resource_count: 1,
+ total_count: 1,
},
]);
});
@@ -81,6 +94,8 @@ describe('contentNode actions', () => {
thumbnail_encoding: {},
...contentNodeDatum,
tags: [],
+ resource_count: 1,
+ total_count: 1,
});
});
});
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/actions.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/actions.js
index 436271a187..6ceeeafcfc 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/actions.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/actions.js
@@ -2,12 +2,19 @@ import flatMap from 'lodash/flatMap';
import uniq from 'lodash/uniq';
import { NEW_OBJECT, NOVALUE } from 'shared/constants';
import client from 'shared/client';
-import { RELATIVE_TREE_POSITIONS, CHANGES_TABLE, TABLE_NAMES } from 'shared/data/constants';
+import {
+ RELATIVE_TREE_POSITIONS,
+ CHANGES_TABLE,
+ TABLE_NAMES,
+ COPYING_STATUS,
+ COPYING_STATUS_VALUES,
+} from 'shared/data/constants';
import { ContentNode } from 'shared/data/resources';
import { ContentKindsNames } from 'shared/leUtils/ContentKinds';
import { findLicense } from 'shared/utils/helpers';
import { RolesNames } from 'shared/leUtils/Roles';
import { isNodeComplete } from 'shared/utils/validation';
+import * as publicApi from 'shared/data/public';
import db from 'shared/data/db';
@@ -34,6 +41,21 @@ export function loadContentNode(context, id) {
});
}
+/**
+ * @param context
+ * @param {string} id
+ * @param {string} nodeId - Note: this is `node_id` not `id`
+ * @param {string} rootId
+ * @param {string} parent - The `id` not `node_id` of the parent
+ * @return {Promise<{}>}
+ */
+export async function loadPublicContentNode(context, { id, nodeId, rootId, parent }) {
+ const publicNode = await publicApi.getContentNode(nodeId);
+ const localNode = publicApi.convertContentNodeResponse(id, rootId, parent, publicNode);
+ context.commit('ADD_CONTENTNODE', localNode);
+ return localNode;
+}
+
export function loadContentNodeByNodeId(context, nodeId) {
const channelId = context.rootState.currentChannel.currentChannelId;
return loadContentNodes(context, { '[node_id+channel_id]__in': [[nodeId, channelId]] })
@@ -47,8 +69,12 @@ export function loadContentNodeByNodeId(context, nodeId) {
});
}
-export function loadChildren(context, { parent }) {
- return loadContentNodes(context, { parent });
+export function loadChildren(context, { parent, published = null }) {
+ const params = { parent };
+ if (published !== null) {
+ params.published = published;
+ }
+ return loadContentNodes(context, params);
}
export function loadAncestors(context, { id }) {
@@ -191,7 +217,7 @@ export function createContentNode(context, { parent, kind, ...payload }) {
assessmentItems: [],
files: [],
});
- return ContentNode.put(contentNodeData).then(id => {
+ return ContentNode.add(contentNodeData).then(id => {
context.commit('ADD_CONTENTNODE', {
id,
...contentNodeData,
@@ -221,8 +247,12 @@ function generateContentNodeData({
learning_activities = NOVALUE,
categories = NOVALUE,
suggested_duration = NOVALUE,
+ [COPYING_STATUS]: copy_status_value = NOVALUE,
} = {}) {
const contentNodeData = {};
+ if (copy_status_value !== NOVALUE) {
+ contentNodeData[COPYING_STATUS] = copy_status_value;
+ }
if (title !== NOVALUE) {
contentNodeData.title = title;
}
@@ -353,7 +383,7 @@ export function addTags(context, { ids, tags }) {
return Promise.all(
ids.map(id => {
const updates = {};
- for (let tag of tags) {
+ for (const tag of tags) {
context.commit('ADD_TAG', { id, tag });
updates[`tags.${tag}`] = true;
}
@@ -366,7 +396,7 @@ export function removeTags(context, { ids, tags }) {
return Promise.all(
ids.map(id => {
const updates = {};
- for (let tag of tags) {
+ for (const tag of tags) {
context.commit('REMOVE_TAG', { id, tag });
updates[`tags.${tag}`] = undefined;
}
@@ -389,13 +419,29 @@ export function deleteContentNodes(context, contentNodeIds) {
);
}
+export function waitForCopyingStatus(context, { contentNodeId, startingRev }) {
+ return ContentNode.waitForCopying(contentNodeId, startingRev).catch(e => {
+ context.dispatch('updateContentNode', {
+ id: contentNodeId,
+ [COPYING_STATUS]: COPYING_STATUS_VALUES.FAILED,
+ });
+ return Promise.reject(e);
+ });
+}
+
export function copyContentNode(
context,
- { id, target, position = RELATIVE_TREE_POSITIONS.LAST_CHILD, excluded_descendants = null } = {}
+ {
+ id,
+ target,
+ position = RELATIVE_TREE_POSITIONS.LAST_CHILD,
+ excluded_descendants = null,
+ sourceNode = null,
+ } = {}
) {
// First, this will parse the tree and create the copy the local tree nodes,
// with a `source_id` of the source node then create the content node copies
- return ContentNode.copy(id, target, position, excluded_descendants).then(node => {
+ return ContentNode.copy(id, target, position, excluded_descendants, sourceNode).then(node => {
context.commit('ADD_CONTENTNODE', node);
return node;
});
@@ -403,10 +449,16 @@ export function copyContentNode(
export function copyContentNodes(
context,
- { id__in, target, position = RELATIVE_TREE_POSITIONS.LAST_CHILD }
+ { id__in, target, position = RELATIVE_TREE_POSITIONS.LAST_CHILD, sourceNodes = null }
) {
return Promise.all(
- id__in.map(id => context.dispatch('copyContentNode', { id, target, position }))
+ id__in.map(id => {
+ let sourceNode = null;
+ if (sourceNodes) {
+ sourceNode = sourceNodes.find(n => n.id === id);
+ }
+ return context.dispatch('copyContentNode', { id, target, position, sourceNode });
+ })
);
}
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js
index 53754a0da1..ba594da164 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/getters.js
@@ -7,6 +7,7 @@ import { parseNode } from './utils';
import { getNodeDetailsErrors, getNodeFilesErrors } from 'shared/utils/validation';
import { ContentKindsNames } from 'shared/leUtils/ContentKinds';
import { NEW_OBJECT } from 'shared/constants';
+import { COPYING_STATUS, COPYING_STATUS_VALUES } from 'shared/data/constants';
function sorted(nodes) {
return sortBy(nodes, ['lft']);
@@ -48,6 +49,31 @@ export function hasChildren(state, getters) {
};
}
+/**
+ * Whether the contentnode's interactivity should be disabled or not while copying?
+ * When the contentnode is copying or the copying has failed, we need
+ * to keep interactivity disabled. Hence, the FAILED condition is also there.
+ */
+export function isNodeInCopyingState(state, getters) {
+ return function(contentNodeId) {
+ const contentNode = getters.getContentNode(contentNodeId);
+ return (
+ contentNode[COPYING_STATUS] === COPYING_STATUS_VALUES.COPYING ||
+ contentNode[COPYING_STATUS] === COPYING_STATUS_VALUES.FAILED
+ );
+ };
+}
+
+/**
+ * Whether the contentnode's copying has errored or not?
+ */
+export function hasNodeCopyingErrored(state, getters) {
+ return function(contentNodeId) {
+ const contentNode = getters.getContentNode(contentNodeId);
+ return contentNode[COPYING_STATUS] === COPYING_STATUS_VALUES.FAILED;
+ };
+}
+
export function countContentNodeDescendants(state, getters) {
return function(contentNodeId) {
return getters.getContentNodeDescendants(contentNodeId).length;
@@ -91,7 +117,7 @@ export function getContentNodeChildren(state, getters) {
export function getContentNodeAncestors(state, getters) {
return function(id, includeSelf = false) {
- let node = getters.getContentNode(id);
+ const node = getters.getContentNode(id);
if (!node || !node.parent) {
return [node].filter(Boolean);
@@ -129,6 +155,13 @@ export function getContentNodeDetailsAreValid(state) {
};
}
+export function getNodeDetailsErrorsList(state) {
+ return function(contentNodeId) {
+ const contentNode = state.contentNodesMap[contentNodeId];
+ return getNodeDetailsErrors(contentNode);
+ };
+}
+
export function getContentNodeFilesAreValid(state, getters, rootState, rootGetters) {
return function(contentNodeId) {
const contentNode = state.contentNodesMap[contentNodeId];
@@ -139,7 +172,7 @@ export function getContentNodeFilesAreValid(state, getters, rootState, rootGette
return true;
}
if (contentNode && contentNode.kind !== ContentKindsNames.TOPIC) {
- let files = rootGetters['file/getContentNodeFiles'](contentNode.id);
+ const files = rootGetters['file/getContentNodeFiles'](contentNode.id);
if (files.length) {
// Don't count errors before files have loaded
return !getNodeFilesErrors(files).length;
@@ -250,7 +283,7 @@ function findNodeInMap(map, rootNodeId, nodeId) {
visitedNodes.add(targetId);
const nextSteps = map[targetId];
if (nextSteps) {
- for (let nextStep in nextSteps) {
+ for (const nextStep in nextSteps) {
if (nextStep === nodeId) {
return true;
} else {
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/index.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/index.js
index 34fb785ed9..5fa6d1faba 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/index.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/index.js
@@ -6,7 +6,7 @@ import { TABLE_NAMES, CHANGE_TYPES } from 'shared/data';
export default {
namespaced: true,
state: () => {
- let expandedNodes = {};
+ const expandedNodes = {};
// TODO: test performance before adding this in to avoid loading a lot of data at once
// if (window.sessionStorage) {
// expandedNodes = JSON.parse(window.sessionStorage.getItem('expandedNodes') || '{}');
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/mutations.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/mutations.js
index 8ad76a3572..18e6cb9272 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/mutations.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/mutations.js
@@ -9,7 +9,7 @@ export function ADD_CONTENTNODE(state, contentNode) {
}
export function ADD_CONTENTNODES(state, contentNodes = []) {
- for (let contentNode of contentNodes) {
+ for (const contentNode of contentNodes) {
ADD_CONTENTNODE(state, contentNode);
}
}
@@ -114,7 +114,7 @@ export function ADD_PREVIOUS_STEP(state, { target_node, prerequisite }) {
* ]
*/
export function SAVE_NEXT_STEPS(state, { mappings = [] } = {}) {
- for (let mapping of mappings) {
+ for (const mapping of mappings) {
ADD_PREVIOUS_STEP(state, mapping);
}
}
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/utils.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/utils.js
index bc212b92ec..634e17b75a 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/utils.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/contentNode/utils.js
@@ -1,13 +1,16 @@
+import isString from 'lodash/isString';
import find from 'lodash/find';
import { ContentKindsNames } from 'shared/leUtils/ContentKinds';
import { RolesNames } from 'shared/leUtils/Roles';
export function parseNode(node, children) {
- const thumbnail_encoding = JSON.parse(node.thumbnail_encoding || '{}');
+ const thumbnail_encoding = isString(node.thumbnail_encoding)
+ ? JSON.parse(node.thumbnail_encoding || '{}')
+ : node.thumbnail_encoding || {};
const tags = Object.keys(node.tags || {});
const aggregateValues = {};
if (node.kind === ContentKindsNames.TOPIC) {
- for (let child of children) {
+ for (const child of children) {
aggregateValues['error_count'] =
(aggregateValues['error_count'] || 0) +
(child['error_count'] || Number(!child['complete']));
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/__tests__/module.spec.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/__tests__/module.spec.js
index 5e2373ecfd..f43043649a 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/__tests__/module.spec.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/__tests__/module.spec.js
@@ -23,7 +23,7 @@ describe('currentChannel store', () => {
});
});
it('publishChannel action should post to publish_channel endpoint', () => {
- let notes = 'version notes';
+ const notes = 'version notes';
const spy = jest.spyOn(Channel, 'publish').mockImplementation(() => Promise.resolve());
return store.dispatch('currentChannel/publishChannel', notes).then(() => {
expect(spy.mock.calls[0][0]).toBe(store.state.currentChannel.currentChannelId);
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/actions.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/actions.js
index 1036f23a5b..9dca09a0a2 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/actions.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/actions.js
@@ -48,10 +48,7 @@ export function reloadCurrentChannelStagingDiff(context) {
}
export function deployCurrentChannel(context) {
- let payload = {
- channel_id: context.state.currentChannelId,
- };
- return client.post(window.Urls.activate_channel(), payload);
+ return Channel.deploy(context.state.currentChannelId);
}
export function publishChannel(context, version_notes) {
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/mutations.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/mutations.js
index c9d72e1caf..856900dcb5 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/mutations.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/mutations.js
@@ -29,8 +29,8 @@ export function SAVE_CURRENT_CHANNEL_STAGING_DIFF(state, payload) {
fields.forEach(fieldName => {
const field = payload.find(item => item.field === fieldName);
- let live = field.original;
- let staged = field.changed;
+ const live = field.original;
+ const staged = field.changed;
stagingDiff[fieldName] = { live, staged };
});
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/importFromChannels/actions.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/importFromChannels/actions.js
index 21cee26437..84003a33fe 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/importFromChannels/actions.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/importFromChannels/actions.js
@@ -1,17 +1,75 @@
+import partition from 'lodash/partition';
+import { ImportSearchPageSize } from '../../constants';
import client from 'shared/client';
+import urls from 'shared/urls';
import { NOVALUE, ChannelListTypes } from 'shared/constants';
import { Channel, SavedSearch } from 'shared/data/resources';
-export function fetchResourceSearchResults(context, params) {
+export async function fetchResourceSearchResults(context, params) {
params = { ...params };
delete params['last'];
- params.page_size = params.page_size || 25;
+ params.page_size = params.page_size || ImportSearchPageSize;
params.channel_list = params.channel_list || ChannelListTypes.PUBLIC;
- return client.get(window.Urls.search_list(), { params }).then(response => {
- context.commit('contentNode/ADD_CONTENTNODES', response.data.results, { root: true });
- return response.data;
- });
+
+ const response = await client.get(urls.search_list(), { params });
+
+ // Split nodes into public and private so we can call the separate apis
+ const [publicNodes, privateNodes] = partition(response.data.results, node => node.public);
+
+ const privatePromise = privateNodes.length
+ ? context.dispatch(
+ 'contentNode/loadContentNodes',
+ {
+ id__in: privateNodes.map(node => node.id),
+ },
+ { root: true }
+ )
+ : Promise.resolve([]);
+
+ const [privateNodesLoaded, publicNodesLoaded] = await Promise.all([
+ // the loadContentNodes action already loads the nodes into vuex
+ privatePromise,
+ Promise.all(
+ // The public API is cached, so we can hopefully call it multiple times without
+ // worrying too much about performance
+ publicNodes.map(node => {
+ return context
+ .dispatch(
+ 'contentNode/loadPublicContentNode',
+ {
+ id: node.id,
+ nodeId: node.node_id,
+ rootId: node.root_id,
+ parent: node.parent,
+ },
+ { root: true }
+ )
+ .catch(() => null);
+ })
+ ).then(nodes => nodes.filter(Boolean)),
+ ]);
+
+ // In case we failed to obtain data for all nodes, filter out the ones we didn't get
+ const results = response.data.results
+ .map(node => {
+ return (
+ privateNodesLoaded.find(n => n.id === node.id) ||
+ publicNodesLoaded.find(n => n.id === node.id)
+ );
+ })
+ .filter(Boolean);
+ // This won't work across multiple pages, if we fail to load some nodes, but that should be rare
+ const countDiff = response.data.results.length - results.length;
+ const count = response.data.count - countDiff;
+ const pageDiff = Math.floor(countDiff / params.page_size);
+
+ return {
+ count,
+ page: response.data.page,
+ results,
+ total_pages: response.data.total_pages - pageDiff,
+ };
}
export function loadChannels(context, params) {
@@ -38,7 +96,7 @@ export function createSearch({ commit, rootState }, params) {
saved_by: rootState.session.currentUser.id,
created: new Date(),
};
- return SavedSearch.put(data).then(id => {
+ return SavedSearch.add(data).then(id => {
commit('UPDATE_SAVEDSEARCH', {
id,
...data,
diff --git a/contentcuration/contentcuration/frontend/channelEdit/vuex/task/index.js b/contentcuration/contentcuration/frontend/channelEdit/vuex/task/index.js
index 17d049cad9..ce5e0815d0 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/vuex/task/index.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/vuex/task/index.js
@@ -13,7 +13,7 @@ export default {
actions: {
initState(store) {
return Task.where().then(tasks => {
- for (let task of tasks) {
+ for (const task of tasks) {
store.commit('ADD_ASYNC_TASK', task);
}
});
diff --git a/contentcuration/contentcuration/frontend/channelList/router.js b/contentcuration/contentcuration/frontend/channelList/router.js
index 3d391c92df..9f1c31aab4 100644
--- a/contentcuration/contentcuration/frontend/channelList/router.js
+++ b/contentcuration/contentcuration/frontend/channelList/router.js
@@ -59,7 +59,7 @@ const router = new VueRouter({
},
{
name: RouteNames.CATALOG_DETAILS,
- path: '/public/:channelId',
+ path: '/public/:channelId/details',
component: ChannelDetailsModal,
props: true,
},
diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue
index 564fcfae2f..17fa852e0d 100644
--- a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue
+++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue
@@ -207,8 +207,8 @@
// Reset selection mode if a filter is changed (ignore page)
const ignoreDefaults = { page: 0 };
- let toQuery = { ...to.query, ...ignoreDefaults };
- let fromQuery = { ...this.previousQuery, ...ignoreDefaults };
+ const toQuery = { ...to.query, ...ignoreDefaults };
+ const fromQuery = { ...this.previousQuery, ...ignoreDefaults };
if (!isEqual(toQuery, fromQuery)) {
this.setSelection(false);
}
@@ -223,7 +223,7 @@
...mapActions('channelList', ['searchCatalog']),
loadCatalog() {
this.loading = true;
- let params = {
+ const params = {
...this.$route.query,
};
return this.searchCatalog(params)
diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelItem.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelItem.vue
index e70d1eea8b..6fa7803c83 100644
--- a/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelItem.vue
+++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelItem.vue
@@ -88,28 +88,28 @@
-
-
-
+
-
-
- edit
-
+
+
+
{{ $tr('editChannel') }}
-
- content_copy
-
+
+
+
{{ $tr('copyToken') }}
-
+
launch
-
+
{{ $tr('goToWebsite') }}
-
+
launch
-
+
{{ $tr('viewContent') }}
-
- delete
-
+
+
+
{{ $tr('deleteChannel') }}
@@ -232,7 +241,6 @@
import ChannelTokenModal from 'shared/views/channel/ChannelTokenModal';
import Thumbnail from 'shared/views/files/Thumbnail';
import Languages from 'shared/leUtils/Languages';
- import IconButton from 'shared/views/IconButton';
export default {
name: 'ChannelItem',
@@ -240,7 +248,6 @@
ChannelStar,
ChannelTokenModal,
Thumbnail,
- IconButton,
},
props: {
channelId: {
@@ -402,10 +409,12 @@
cursor: pointer;
&:hover:not(.hideHighlight) {
+ /* stylelint-disable-next-line custom-property-pattern */
background-color: var(--v-greyBackground-base);
}
&.added {
+ /* stylelint-disable-next-line custom-property-pattern */
background-color: var(--v-greenHighlightBackground-base);
}
}
diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelStar.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelStar.vue
index 3e94d907d2..fd9e3a82b4 100644
--- a/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelStar.vue
+++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelStar.vue
@@ -2,10 +2,10 @@
-
@@ -16,13 +16,10 @@
-
diff --git a/contentcuration/contentcuration/frontend/shared/views/ContentNodeIcon.vue b/contentcuration/contentcuration/frontend/shared/views/ContentNodeIcon.vue
index 874b084810..770c3225cf 100644
--- a/contentcuration/contentcuration/frontend/shared/views/ContentNodeIcon.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/ContentNodeIcon.vue
@@ -84,6 +84,8 @@
return this.$tr('exercise');
case 'document':
return this.$tr('document');
+ case 'h5p':
+ return this.$tr('html5');
case 'html5':
return this.$tr('html5');
default:
diff --git a/contentcuration/contentcuration/frontend/shared/views/CopyToken.vue b/contentcuration/contentcuration/frontend/shared/views/CopyToken.vue
index 7206190618..d4535ef5af 100644
--- a/contentcuration/contentcuration/frontend/shared/views/CopyToken.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/CopyToken.vue
@@ -56,7 +56,7 @@
navigator.clipboard
.writeText(this.displayToken)
.then(() => {
- let text = this.successText || this.$tr('copiedTokenId');
+ const text = this.successText || this.$tr('copiedTokenId');
this.$store.dispatch('showSnackbar', { text });
this.$analytics.trackEvent('copy_token');
this.$emit('copied');
diff --git a/contentcuration/contentcuration/frontend/shared/views/ExpandableList.vue b/contentcuration/contentcuration/frontend/shared/views/ExpandableList.vue
index 2bb20e3a2e..d35f824cce 100644
--- a/contentcuration/contentcuration/frontend/shared/views/ExpandableList.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/ExpandableList.vue
@@ -116,7 +116,7 @@
return this.expanded ? this.items && this.items.length : this.max;
},
toggleText() {
- let moreCount = this.items.length - this.max;
+ const moreCount = this.items.length - this.max;
return this.isExpanded
? this.$tr('less')
: this.$tr('more', { more: this.$formatNumber(moreCount) });
diff --git a/contentcuration/contentcuration/frontend/shared/views/FullscreenModal.vue b/contentcuration/contentcuration/frontend/shared/views/FullscreenModal.vue
index 14a4e3ee91..2173e500d1 100644
--- a/contentcuration/contentcuration/frontend/shared/views/FullscreenModal.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/FullscreenModal.vue
@@ -100,6 +100,9 @@
this.hideHTMLScroll(!!val);
},
},
+ beforeDestroy() {
+ this.hideHTMLScroll(false); // Ensure scroll is restored when the component is destroyed
+ },
mounted() {
this.hideHTMLScroll(true);
this.$refs.dialog.initDetach();
diff --git a/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue b/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue
index c2af3b154e..a3256dbb67 100644
--- a/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue
@@ -29,7 +29,7 @@
- {{ languageText(item) }}
+ {{ languageText(item) }}
{{ languageText(item) }}
@@ -106,7 +106,8 @@
},
methods: {
languageText(item) {
- return this.$tr('languageItemText', { language: item.native_name, code: item.id });
+ const firstNativeName = item.native_name.split(',')[0].trim();
+ return this.$tr('languageItemText', { language: firstNativeName, code: item.id });
},
},
$trs: {
diff --git a/contentcuration/contentcuration/frontend/shared/views/MainNavigationDrawer.vue b/contentcuration/contentcuration/frontend/shared/views/MainNavigationDrawer.vue
index c5065c49ac..0c6b6ff4b7 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MainNavigationDrawer.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/MainNavigationDrawer.vue
@@ -9,50 +9,73 @@
:right="$isRTL"
>
-
- clear
+
+
Kolibri Studio
-
+
- home
+
{{ $tr('channelsLink') }}
-
+
- people
+
{{ $tr('administrationLink') }}
-
+
- settings
+
{{ $tr('settingsLink') }}
-
+
- language
+
-
+
- open_in_new
+
{{ $tr('helpLink') }}
@@ -60,7 +83,10 @@
- exit_to_app
+
{{ $tr('logoutLink') }}
@@ -73,12 +99,14 @@
:text="$tr('copyright', { year: new Date().getFullYear() })"
href="https://learningequality.org/"
target="_blank"
+ :tabindex="handleclickTab"
/>
@@ -123,6 +151,13 @@
...mapState({
user: state => state.session.currentUser,
}),
+ handleclickTab() {
+ if (this.value) {
+ return 0;
+ } else {
+ return -1;
+ }
+ },
drawer: {
get() {
return this.value;
@@ -149,6 +184,10 @@
trackClick(label) {
this.$analytics.trackClick('general', `User dropdown - ${label}`);
},
+ openLanguageModal() {
+ this.drawer = false;
+ this.showLanguageModal = true;
+ },
},
$trs: {
channelsLink: 'Channels',
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue
index 722c44f4f0..c8f671838f 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue
@@ -57,25 +57,19 @@
import '../mathquill/mathquill.js';
import 'codemirror/lib/codemirror.css';
import '@toast-ui/editor/dist/toastui-editor.css';
- import * as Showdown from 'showdown';
import Editor from '@toast-ui/editor';
- import debounce from 'lodash/debounce';
- import { stripHtml } from 'string-strip-html';
import imageUpload, { paramsToImageFieldHTML } from '../plugins/image-upload';
import formulas from '../plugins/formulas';
import minimize from '../plugins/minimize';
- import formulaHtmlToMd from '../plugins/formulas/formula-html-to-md';
import formulaMdToHtml from '../plugins/formulas/formula-md-to-html';
- import imagesHtmlToMd from '../plugins/image-upload/image-html-to-md';
import imagesMdToHtml from '../plugins/image-upload/image-md-to-html';
import { CLASS_MATH_FIELD_ACTIVE } from '../constants';
import { registerMarkdownFormulaField } from '../plugins/formulas/MarkdownFormulaField';
import { registerMarkdownImageField } from '../plugins/image-upload/MarkdownImageField';
- import { clearNodeFormat, getExtensionMenuPosition } from './utils';
- import keyHandlers from './keyHandlers';
+ import { clearNodeFormat, generateCustomConverter, getExtensionMenuPosition } from './utils';
import FormulasMenu from './FormulasMenu/FormulasMenu';
import ImagesMenu from './ImagesMenu/ImagesMenu';
import ClickOutside from 'shared/directives/click-outside';
@@ -83,8 +77,6 @@
registerMarkdownFormulaField();
registerMarkdownImageField();
- const wrapWithSpaces = html => ` ${html} `;
-
const AnalyticsActionMap = {
Bold: 'Bold',
Italic: 'Italicize',
@@ -164,7 +156,6 @@
markdown(newMd, previousMd) {
if (newMd !== previousMd && newMd !== this.editor.getMarkdown()) {
this.editor.setMarkdown(newMd);
- this.updateCustomNodeSpacers();
this.initImageFields();
}
},
@@ -172,36 +163,7 @@
mounted() {
this.mathQuill = MathQuill.getInterface(2);
- // This is currently the only way of inheriting and adjusting
- // default TUI's convertor methods
- // see https://github.com/nhn/tui.editor/issues/615
- const tmpEditor = new Editor({
- el: this.$refs.editor,
- });
- const showdown = new Showdown.Converter();
- const Convertor = tmpEditor.convertor.constructor;
- class CustomConvertor extends Convertor {
- toMarkdown(content) {
- content = showdown.makeMarkdown(content);
- content = imagesHtmlToMd(content);
- content = formulaHtmlToMd(content);
- content = content.replaceAll(' ', ' ');
-
- // TUI.editor sprinkles in extra ` ` tags that Kolibri renders literally
- // any copy pasted rich text that renders as HTML but does not get converted
- // will linger here, so remove it as Kolibri will render it literally also.
- content = stripHtml(content).result;
- return content;
- }
- toHTML(content) {
- // Kolibri and showdown assume double newlines for a single line break,
- // wheras TUI.editor prefers single newline characters.
- content = content.replaceAll('\n\n', '\n');
- content = super.toHTML(content);
- return content;
- }
- }
- tmpEditor.remove();
+ const CustomConvertor = generateCustomConverter(this.$refs.editor);
const createBoldButton = () => {
{
@@ -271,8 +233,8 @@
// https://github.com/nhn/tui.editor/blob/master/apps/editor/docs/custom-html-renderer.md
customHTMLRenderer: {
text(node) {
- let content = formulaMdToHtml(node.literal);
- content = imagesMdToHtml(content);
+ let content = formulaMdToHtml(node.literal, true);
+ content = imagesMdToHtml(content, true);
return {
type: 'html',
content,
@@ -306,35 +268,6 @@
this.keyDownEventListener = this.$el.addEventListener('keydown', this.onKeyDown, true);
this.clickEventListener = this.$el.addEventListener('click', this.onClick);
this.editImageEventListener = this.$el.addEventListener('editImage', this.handleEditImage);
-
- // Make sure all custom nodes have spacers around them.
- // Note: this is debounced because it's called every keystroke
- const editorEl = this.$refs.editor;
- this.updateCustomNodeSpacers = debounce(() => {
- editorEl.querySelectorAll('span[is]').forEach(el => {
- el.editing = true;
- const hasLeftwardSpace = el => {
- return (
- el.previousSibling &&
- el.previousSibling.textContent &&
- /\s$/.test(el.previousSibling.textContent)
- );
- };
- const hasRightwardSpace = el => {
- return (
- el.nextSibling && el.nextSibling.textContent && /^\s/.test(el.nextSibling.textContent)
- );
- };
- if (!hasLeftwardSpace(el)) {
- el.insertAdjacentText('beforebegin', '\xa0');
- }
- if (!hasRightwardSpace(el)) {
- el.insertAdjacentText('afterend', '\xa0');
- }
- });
- }, 150);
-
- this.updateCustomNodeSpacers();
},
activated() {
this.editor.focus();
@@ -358,15 +291,9 @@
* a recommended solution here https://github.com/neilj/Squire/issues/107
*/
onKeyDown(event) {
- const squire = this.editor.getSquire();
-
// Apply squire selection workarounds
this.fixSquireSelectionOnKeyDown(event);
- if (event.key in keyHandlers) {
- keyHandlers[event.key](squire);
- }
-
// ESC should close menus if any are open
// or close the editor if none are open
if (event.key === 'Escape') {
@@ -413,8 +340,6 @@
event.preventDefault();
event.stopPropagation();
}
-
- this.updateCustomNodeSpacers();
},
onPaste(event) {
const fragment = clearNodeFormat({
@@ -507,7 +432,7 @@
const getRightwardElement = selection => getElementAtRelativeOffset(selection, 1);
const getCharacterAtRelativeOffset = (selection, relativeOffset) => {
- let { element, offset } = squire.getSelectionInfoByOffset(
+ const { element, offset } = squire.getSelectionInfoByOffset(
selection.startContainer,
selection.startOffset + relativeOffset
);
@@ -529,27 +454,31 @@
/\s$/.test(getCharacterAtRelativeOffset(selection, 0));
const moveCursor = (selection, amount) => {
- let { element, offset } = squire.getSelectionInfoByOffset(
- selection.startContainer,
- selection.startOffset + amount
- );
- if (amount > 0) {
- selection.setStart(element, offset);
- } else {
- selection.setEnd(element, offset);
- }
+ const element = getElementAtRelativeOffset(selection, amount);
+ selection.setStart(element, 0);
+ selection.setEnd(element, 0);
return selection;
};
- // make sure Squire doesn't delete rightward custom nodes when 'backspace' is pressed
- if (event.key !== 'ArrowRight' && event.key !== 'Delete') {
- if (isCustomNode(getRightwardElement(selection))) {
+ const rightwardElement = getRightwardElement(selection);
+ const leftwardElement = getLeftwardElement(selection);
+
+ if (event.key === 'ArrowRight') {
+ if (isCustomNode(rightwardElement)) {
+ squire.setSelection(moveCursor(selection, 1));
+ } else if (spacerAndCustomElementAreRightward(selection)) {
+ squire.setSelection(moveCursor(selection, 2));
+ }
+ }
+ if (event.key === 'ArrowLeft') {
+ if (isCustomNode(leftwardElement)) {
squire.setSelection(moveCursor(selection, -1));
+ } else if (spacerAndCustomElementAreLeftward(selection)) {
+ squire.setSelection(moveCursor(selection, -2));
}
}
// make sure Squire doesn't get stuck with a broken cursor position when deleting
// elements with `contenteditable="false"` in FireFox
- let leftwardElement = getLeftwardElement(selection);
if (event.key === 'Backspace') {
if (selection.startContainer.tagName === 'DIV') {
// This happens normally when deleting from the beginning of an empty line...
@@ -558,9 +487,9 @@
// element has `contenteditable="false"` (which is necessary on FireFox for
// a different reason). As a result, Squire.js gets stuck. The trick here is
// to fix its selection so it knows what to delete.
- let fixedStartContainer =
+ const fixedStartContainer =
selection.startContainer.childNodes[selection.startOffset - 1];
- let fixedEndContainer = selection.endContainer.childNodes[selection.endOffset - 1];
+ const fixedEndContainer = selection.endContainer.childNodes[selection.endOffset - 1];
if (fixedStartContainer && fixedEndContainer) {
selection.setStart(fixedStartContainer, 0);
selection.setEnd(fixedEndContainer, 1);
@@ -593,7 +522,7 @@
}
// if any part of a custom node is in the selection, include the whole thing
if (isCustomNode(selection.startContainer)) {
- let previousSibling = selection.startContainer.previousSibling;
+ const previousSibling = selection.startContainer.previousSibling;
selection.setStart(previousSibling, previousSibling.length - 1);
squire.setSelection(selection);
}
@@ -782,16 +711,15 @@
formulaEl.setAttribute('is', 'markdown-formula-field');
formulaEl.setAttribute('editing', true);
formulaEl.innerHTML = formula;
- let formulaHTML = formulaEl.outerHTML;
+ const formulaHTML = formulaEl.outerHTML;
const activeMathFieldEl = this.findActiveMathField();
if (activeMathFieldEl !== null) {
// setting `outerHTML` is the preferred way to reset a custom node
activeMathFieldEl.outerHTML = formulaHTML;
} else {
- let squire = this.editor.getSquire();
+ const squire = this.editor.getSquire();
squire.insertHTML(formulaHTML);
- this.updateCustomNodeSpacers();
}
},
resetFormulasMenu() {
@@ -809,10 +737,10 @@
/* IMAGE MENU */
handleEditImage(event) {
- let { editorField, editEvent, image } = event.detail;
+ const { editorField, editEvent, image } = event.detail;
this.activeImageField = editorField;
- let editorEl = this.$el;
- let position = getExtensionMenuPosition({
+ const editorEl = this.$el;
+ const position = getExtensionMenuPosition({
editorEl,
targetX: editEvent.clientX,
targetY: editEvent.clientY,
@@ -876,8 +804,7 @@
const mdImageEl = template.content.firstElementChild;
mdImageEl.setAttribute('editing', true);
- // insert non-breaking spaces to allow users to write text before and after
- this.editor.getSquire().insertHTML(wrapWithSpaces(mdImageEl.outerHTML));
+ this.editor.getSquire().insertHTML(mdImageEl.outerHTML);
this.initImageFields();
}
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/keyHandlers.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/keyHandlers.js
deleted file mode 100644
index 7cb2b93597..0000000000
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/keyHandlers.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import { CLASS_MATH_FIELD, KEY_ARROW_RIGHT, KEY_ARROW_LEFT, KEY_BACKSPACE } from '../constants';
-
-/**
- * When arrow right pressed and next element is a math field
- * then move cursor to a first position after the math field
- *
- * @param {Object} squire Squire instance
- */
-const onArrowRight = squire => {
- const selection = squire.getSelection();
-
- if (
- selection &&
- selection.startContainer.nextSibling &&
- selection.startOffset === selection.startContainer.length &&
- selection.startContainer.nextSibling.classList.contains(CLASS_MATH_FIELD)
- ) {
- const rangeAfterMathField = new Range();
- rangeAfterMathField.setStartAfter(selection.startContainer.nextSibling);
-
- squire.setSelection(rangeAfterMathField);
- }
-};
-
-/**
- * When arrow left pressed and previous element is a math field
- * then move cursor to a last position before the math field
- *
- * @param {Object} squire Squire instance
- */
-const onArrowLeft = squire => {
- const selection = squire.getSelection();
-
- if (
- selection &&
- selection.startContainer.previousSibling &&
- selection.startOffset === 1 &&
- selection.startContainer.previousSibling.classList.contains(CLASS_MATH_FIELD)
- ) {
- const rangeBeforeMathField = new Range();
- rangeBeforeMathField.setStartBefore(selection.startContainer.previousSibling);
-
- squire.setSelection(rangeBeforeMathField);
- }
-};
-
-/**
- * When backspace pressed and previous element is a math field
- * then remove the math field
- *
- * @param {Object} squire Squire instance
- */
-const onBackspace = squire => {
- const selection = squire.getSelection();
-
- if (
- selection &&
- selection.startContainer.previousSibling &&
- selection.startOffset === 1 &&
- selection.startContainer.previousSibling.classList.contains(CLASS_MATH_FIELD)
- ) {
- const mathFieldRange = new Range();
- mathFieldRange.setStartBefore(selection.startContainer.previousSibling);
- mathFieldRange.setEndBefore(selection.startContainer);
-
- mathFieldRange.deleteContents();
- }
-};
-
-export default {
- [KEY_ARROW_RIGHT]: onArrowRight,
- [KEY_ARROW_LEFT]: onArrowLeft,
- [KEY_BACKSPACE]: onBackspace,
-};
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/utils.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/utils.js
index 5894845273..f5ce62e98e 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/utils.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/utils.js
@@ -1,3 +1,10 @@
+import * as Showdown from 'showdown';
+import Editor from '@toast-ui/editor';
+import { stripHtml } from 'string-strip-html';
+
+import imagesHtmlToMd from '../plugins/image-upload/image-html-to-md';
+import formulaHtmlToMd from '../plugins/formulas/formula-html-to-md';
+
/**
* Clear DOM node by keeping only its text content.
*
@@ -74,3 +81,37 @@ export const getExtensionMenuPosition = ({ editorEl, targetX, targetY }) => {
right: menuRight,
};
};
+
+export const generateCustomConverter = el => {
+ // This is currently the only way of inheriting and adjusting
+ // default TUI's convertor methods
+ // see https://github.com/nhn/tui.editor/issues/615
+ const tmpEditor = new Editor({ el });
+ const showdown = new Showdown.Converter();
+ const Convertor = tmpEditor.convertor.constructor;
+ class CustomConvertor extends Convertor {
+ toMarkdown(content) {
+ content = showdown.makeMarkdown(content);
+ content = imagesHtmlToMd(content);
+ content = formulaHtmlToMd(content);
+ // TUI.editor sprinkles in extra ` ` tags that Kolibri renders literally
+ // When showdown has already added linebreaks to render these in markdown
+ // so we just remove these here.
+ content = content.replaceAll(' ', '');
+
+ // any copy pasted rich text that renders as HTML but does not get converted
+ // will linger here, so remove it as Kolibri will render it literally also.
+ content = stripHtml(content).result;
+ return content;
+ }
+ toHTML(content) {
+ // Kolibri and showdown assume double newlines for a single line break,
+ // wheras TUI.editor prefers single newline characters.
+ content = content.replaceAll('\n\n', '\n');
+ content = super.toHTML(content);
+ return content;
+ }
+ }
+ tmpEditor.remove();
+ return CustomConvertor;
+};
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer.vue b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer.vue
index cd1b1c49bc..2769e88cd5 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer.vue
@@ -68,7 +68,7 @@
methods: {
initStaticMathFields() {
const mathFieldEls = this.$el.getElementsByClassName(CLASS_MATH_FIELD);
- for (let mathFieldEl of mathFieldEls) {
+ for (const mathFieldEl of mathFieldEls) {
this.mathQuill.StaticMath(mathFieldEl);
}
},
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/plugins/formulas/markdownFormulaField.spec.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/plugins/formulas/markdownFormulaField.spec.js
index 806b1d3e8d..0c8a16bd49 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/plugins/formulas/markdownFormulaField.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/plugins/formulas/markdownFormulaField.spec.js
@@ -27,9 +27,9 @@ describe('MarkdownFormulaField custom element', () => {
it('renders some MathQuill markup in a shadowRoot', async done => {
await window.customElements.whenDefined('markdown-formula-field');
- let shadowRoot = formulaEl.shadowRoot;
+ const shadowRoot = formulaEl.shadowRoot;
expect(shadowRoot).toBeTruthy();
- let varEls = shadowRoot.querySelectorAll('var');
+ const varEls = shadowRoot.querySelectorAll('var');
expect(varEls[0].innerHTML).toBe('x');
expect(varEls[1].innerHTML).toBe('y');
done();
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/utils.spec.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/utils.spec.js
index d201e81b33..1a55e0b179 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/utils.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/utils.spec.js
@@ -1,4 +1,9 @@
-import { clearNodeFormat } from '../MarkdownEditor/utils';
+/**
+ * @jest-environment jest-environment-jsdom-sixteen
+ */
+// Jsdom@^16 is required to test toast UI, as it relies on the Range API.
+
+import { clearNodeFormat, generateCustomConverter } from '../MarkdownEditor/utils';
const htmlStringToFragment = htmlString => {
const template = document.createElement('template');
@@ -56,3 +61,17 @@ describe('clearNodeFormat', () => {
);
});
});
+
+describe('markdown conversion', () => {
+ it('converts image tags to markdown without escaping them', () => {
+ const el = document.createElement('div');
+ const CustomConvertor = generateCustomConverter(el);
+ const converter = new CustomConvertor();
+ const html =
+ '![](${ā£ CONTENTSTORAGE}/bc1c5a86e1e46f20a6b4ee2c1bb6d6ff.png =485.453125x394) ';
+
+ expect(converter.toMarkdown(html)).toBe(
+ '![](${ā£ CONTENTSTORAGE}/bc1c5a86e1e46f20a6b4ee2c1bb6d6ff.png =485.453125x394)'
+ );
+ });
+});
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-html-to-md.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-html-to-md.js
index 1d170c9d0a..1fb6abd25b 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-html-to-md.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-html-to-md.js
@@ -31,10 +31,10 @@ export default html => {
const mathFieldsEls = doc.querySelectorAll('span[is="markdown-formula-field"]');
for (const mathFieldEl of mathFieldsEls) {
- let formula = mathFieldEl.innerHTML;
+ const formula = mathFieldEl.innerHTML;
mathFieldEl.replaceWith('$$' + formula.trim() + '$$');
}
- let newHtml = doc.body.innerHTML;
+ const newHtml = doc.body.innerHTML;
return newHtml;
};
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js
index f99fcd2f09..61669e5eac 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js
@@ -12,6 +12,10 @@
*
*/
-export default markdown => {
- return markdown.replace(/\$\$(.*?)\$\$/g, '$1 ');
+export default (markdown, editing) => {
+ const editAttr = editing ? ' editing="true"' : '';
+ return markdown.replace(
+ /\$\$(.*?)\$\$/g,
+ `$1 `
+ );
};
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/MarkdownImageField.vue b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/MarkdownImageField.vue
index d5308e83dc..a58aad5705 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/MarkdownImageField.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/MarkdownImageField.vue
@@ -136,8 +136,12 @@
);
},
handleRemove() {
- this.$destroy();
- this.$el.parentNode.removeChild(this.$el);
+ this.editorField.dispatchEvent(
+ new CustomEvent('remove', {
+ bubbles: true,
+ cancelable: true,
+ })
+ );
},
handleResize() {
this.resizing = true;
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/image-md-to-html.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/image-md-to-html.js
index c452c5a82a..1f61c121d1 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/image-md-to-html.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/image-md-to-html.js
@@ -14,6 +14,6 @@ import { IMAGE_REGEX, imageMdToImageFieldHTML } from './index';
// convert markdown images to image editor field custom elements
-export default markdown => {
- return markdown.replace(IMAGE_REGEX, imageMd => imageMdToImageFieldHTML(imageMd));
+export default (markdown, editing) => {
+ return markdown.replace(IMAGE_REGEX, imageMd => imageMdToImageFieldHTML(imageMd, editing));
};
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/index.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/index.js
index b4b13a7201..0e33f894c4 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/index.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/index.js
@@ -30,8 +30,10 @@ export const paramsToImageMd = ({ src, alt, width, height }) => {
}
};
-export const imageMdToImageFieldHTML = imageMd =>
- `${imageMd} `;
+export const imageMdToImageFieldHTML = (imageMd, editing) => {
+ const editAttr = editing ? ' editing="true"' : '';
+ return `${imageMd} `;
+};
export const paramsToImageFieldHTML = params => imageMdToImageFieldHTML(paramsToImageMd(params));
export default imageUploadExtension;
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/style.less b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/style.less
index 78e77f5820..11392b3ec7 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/style.less
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/style.less
@@ -8,6 +8,7 @@
img {
max-width: 100%;
height: auto;
+ /* stylelint-disable-next-line custom-property-pattern */
border: 1px solid var(--v-greyBorder-base);
object-fit: cover;
}
@@ -48,6 +49,7 @@ img {
top: 4px;
right: 4px;
user-select: none;
+ /* stylelint-disable-next-line custom-property-pattern */
background-color: var(--v-backgroundColor--base);
opacity: 0;
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/registerCustomMarkdownField.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/registerCustomMarkdownField.js
index 250ae5b30f..3074a8d543 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/registerCustomMarkdownField.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/registerCustomMarkdownField.js
@@ -2,6 +2,54 @@ import Vue from 'vue';
import vueCustomElement from 'vue-custom-element';
import { v4 as uuidv4 } from 'uuid';
+const leftwardSpaceRegex = /\s$/;
+
+const leftwardDoubleSpaceRegex = /\s\s$/;
+
+const hasLeftwardSpace = el => {
+ return (
+ // Has a previous sibling
+ el.previousSibling &&
+ // Which has text content
+ el.previousSibling.textContent &&
+ // The text content has white space right before this element
+ leftwardSpaceRegex.test(el.previousSibling.textContent) &&
+ // And either this sibling doesn't have a previous sibling
+ (!el.previousSibling.previousSibling ||
+ // Or it doesn't have a hasAttribute function
+ typeof el.previousSibling.previousSibling.hasAttribute !== 'function' ||
+ // Or the previous sibling is not another custom field
+ !el.previousSibling.previousSibling.hasAttribute('is') ||
+ // Or the previous sibling has two white spaces, one for each
+ // of the custom fields on either side.
+ leftwardDoubleSpaceRegex.test(el.previousSibling.textContent))
+ );
+};
+
+const rightwardSpaceRegex = /^\s/;
+
+const rightwardDoubleSpaceRegex = /^\s\s/;
+
+const hasRightwardSpace = el => {
+ return (
+ // Has a next sibling
+ el.nextSibling &&
+ // Which has text content
+ el.nextSibling.textContent &&
+ // The text content has white space right after this element
+ rightwardSpaceRegex.test(el.nextSibling.textContent) &&
+ // And either this sibling doesn't have a next sibling
+ (!el.nextSibling.nextSibling ||
+ // Or it doesn't have a hasAttribute function
+ typeof el.nextSibling.nextSibling.hasAttribute !== 'function' ||
+ // Or the next sibling is not another custom field
+ !el.nextSibling.nextSibling.hasAttribute('is') ||
+ // Or the next sibling has two white spaces, one for each
+ // of the custom fields on either side.
+ rightwardDoubleSpaceRegex.test(el.nextSibling.textContent))
+ );
+};
+
export default VueComponent => {
const dashed = camel => camel.replace(/([a-zA-Z])(?=[A-Z])/g, '$1-').toLowerCase();
const name = dashed(VueComponent.name);
@@ -15,18 +63,18 @@ export default VueComponent => {
vueInstanceCreatedCallback() {
// by default, `contenteditable` will be false
this.setAttribute('contenteditable', Boolean(VueComponent.contentEditable));
-
+ const id = `markdown-field-${uuidv4()}`;
// a hack to prevent squire from merging custom element spans
// see here: https://github.com/nhn/tui.editor/blob/master/libs/squire/source/Node.js#L92-L101
- this.classList.add(`markdown-field-${uuidv4()}`);
+ this.classList.add(id);
// pass innerHTML of host element as the `markdown` property
this.observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
const checkIfElementNode = x => x.nodeType === document.ELEMENT_NODE;
const checkIfTextNode = x => x.nodeType === document.TEXT_NODE;
- let elementNodesAdded = [...mutation.addedNodes].filter(checkIfElementNode);
- let textNodesRemoved = [...mutation.removedNodes].filter(checkIfTextNode);
+ const elementNodesAdded = [...mutation.addedNodes].filter(checkIfElementNode);
+ const textNodesRemoved = [...mutation.removedNodes].filter(checkIfTextNode);
// Prevent TUI.editor from adding unwanted DOM elements to the custom element
// This is necessary so that style modifiers don't wrap markdown in or tags.
@@ -35,14 +83,38 @@ export default VueComponent => {
this.innerHTML = textNodesRemoved.map(n => n.nodeValue).join();
} else {
// otherwise, pass the innerHTML to inner Vue component as `markdown` prop
- this.getVueInstance().markdown = this.innerHTML;
+ this.markdown = this.innerHTML;
}
});
});
this.observer.observe(this, { characterData: true, childList: true });
+ this.addEventListener('remove', () => {
+ if (hasLeftwardSpace(this)) {
+ this.previousSibling.textContent = this.previousSibling.textContent.replace(
+ leftwardSpaceRegex,
+ ''
+ );
+ }
+ if (hasRightwardSpace(this)) {
+ this.nextSibling.textContent = this.nextSibling.textContent.replace(
+ rightwardSpaceRegex,
+ ''
+ );
+ }
+ if (this.parentNode) {
+ this.parentNode.removeChild(this);
+ }
+ });
+
+ if (!hasLeftwardSpace(this)) {
+ this.insertAdjacentText('beforebegin', '\xa0');
+ }
+ if (!hasRightwardSpace(this)) {
+ this.insertAdjacentText('afterend', '\xa0');
+ }
// initialize the `markdown` property
- this.getVueInstance().$root.markdown = this.innerHTML;
+ this.markdown = this.innerHTML;
},
shadowCss: VueComponent.shadowCSS,
shadow: true,
diff --git a/contentcuration/contentcuration/frontend/shared/views/ResizableNavigationDrawer.vue b/contentcuration/contentcuration/frontend/shared/views/ResizableNavigationDrawer.vue
index c8fc608b05..28801fefda 100644
--- a/contentcuration/contentcuration/frontend/shared/views/ResizableNavigationDrawer.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/ResizableNavigationDrawer.vue
@@ -37,6 +37,10 @@
type: Number,
default: 100,
},
+ defaultWidth: {
+ type: Number,
+ default: 150,
+ },
maxWidth: {
type: Number,
default: window.innerWidth - 100,
@@ -68,7 +72,7 @@
const localStorageName = this.localName + '-drawer-width';
return {
dragging: false,
- width: parseFloat(localStorage[localStorageName]) || this.minWidth,
+ width: parseFloat(localStorage[localStorageName]) || this.defaultWidth || this.minWidth,
localStorageName,
};
},
@@ -113,7 +117,7 @@
this.throttledUpdateWidth(e.clientX);
},
updateWidth(clientX) {
- let offset = this.isRight ? window.innerWidth - clientX : clientX;
+ const offset = this.isRight ? window.innerWidth - clientX : clientX;
this.width = localStorage[this.localStorageName] = Math.min(
Math.max(this.minWidth, offset),
this.maxWidth
diff --git a/contentcuration/contentcuration/frontend/shared/views/ToolBar.vue b/contentcuration/contentcuration/frontend/shared/views/ToolBar.vue
index fe5cbc3b35..e1f20efce7 100644
--- a/contentcuration/contentcuration/frontend/shared/views/ToolBar.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/ToolBar.vue
@@ -2,7 +2,9 @@
-
+
+
+
diff --git a/contentcuration/contentcuration/frontend/shared/views/__tests__/contentNodeIcon.spec.js b/contentcuration/contentcuration/frontend/shared/views/__tests__/contentNodeIcon.spec.js
index d353221ec1..0c18f65fd4 100644
--- a/contentcuration/contentcuration/frontend/shared/views/__tests__/contentNodeIcon.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/__tests__/contentNodeIcon.spec.js
@@ -24,6 +24,7 @@ describe('ContentNodeIcon', () => {
{ value: 'exercise', icon: 'assignment' },
{ value: 'document', icon: 'class' },
{ value: 'html5', icon: 'widgets' },
+ { value: 'zim', icon: 'widgets' },
];
it.each(testIcons)('should display the correct icon $value', kind => {
const wrapper = makeWrapper(kind.value);
diff --git a/contentcuration/contentcuration/frontend/shared/views/__tests__/copyToken.spec.js b/contentcuration/contentcuration/frontend/shared/views/__tests__/copyToken.spec.js
index 0137778e95..46620f4904 100644
--- a/contentcuration/contentcuration/frontend/shared/views/__tests__/copyToken.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/__tests__/copyToken.spec.js
@@ -15,7 +15,7 @@ describe('copyToken', () => {
wrapper = makeWrapper();
});
it('text should be populated on load', () => {
- let token = wrapper.find({ name: 'v-text-field' });
+ const token = wrapper.find({ name: 'v-text-field' });
expect(token.props().value).toEqual('testt-oken');
expect(wrapper.vm.copyStatus === 'IDLE');
});
diff --git a/contentcuration/contentcuration/frontend/shared/views/__tests__/messageDialog.spec.js b/contentcuration/contentcuration/frontend/shared/views/__tests__/messageDialog.spec.js
index 763d61ebd1..e86a1b2c1b 100644
--- a/contentcuration/contentcuration/frontend/shared/views/__tests__/messageDialog.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/__tests__/messageDialog.spec.js
@@ -13,11 +13,11 @@ function makeWrapper(options) {
describe('messageDialog', () => {
it('header and text should render properly from props', () => {
- let wrapper = makeWrapper();
+ const wrapper = makeWrapper();
expect(wrapper.find('[data-test="text"]').text()).toEqual('text');
});
it('slots should render content correctly', () => {
- let wrapper = makeWrapper({
+ const wrapper = makeWrapper({
slots: {
default: ['new text'],
buttons: ['buttons'],
@@ -27,7 +27,7 @@ describe('messageDialog', () => {
expect(wrapper.find('[data-test="buttons"]').text()).toContain('buttons');
});
it('close should emit an input event to close the modal', () => {
- let wrapper = makeWrapper();
+ const wrapper = makeWrapper();
wrapper.vm.close();
expect(wrapper.emitted('input')[0][0]).toBe(false);
});
diff --git a/contentcuration/contentcuration/frontend/shared/views/__tests__/resizableNavigationDrawer.spec.js b/contentcuration/contentcuration/frontend/shared/views/__tests__/resizableNavigationDrawer.spec.js
index b1782953a0..04a5e6a67e 100644
--- a/contentcuration/contentcuration/frontend/shared/views/__tests__/resizableNavigationDrawer.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/__tests__/resizableNavigationDrawer.spec.js
@@ -7,7 +7,7 @@ function makeWrapper(props = {}) {
describe('resizableNavigationDrawer', () => {
it('slot should render content', () => {
- let wrapper = makeWrapper({
+ const wrapper = makeWrapper({
slots: {
default: 'test content',
},
diff --git a/contentcuration/contentcuration/frontend/shared/views/__tests__/toggleText.spec.js b/contentcuration/contentcuration/frontend/shared/views/__tests__/toggleText.spec.js
index dd156a35f2..7ad98767b3 100644
--- a/contentcuration/contentcuration/frontend/shared/views/__tests__/toggleText.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/__tests__/toggleText.spec.js
@@ -18,9 +18,9 @@ describe('toggleText', () => {
expect(splitWrapper.vm.overflowText).toBeFalsy();
});
it('clicking the toggle button should collapse/expand text', () => {
- let splitWrapper = makeWrapper(5);
- let toggler = splitWrapper.find('.toggler');
- let overflow = splitWrapper.find('[data-test="overflow"]');
+ const splitWrapper = makeWrapper(5);
+ const toggler = splitWrapper.find('.toggler');
+ const overflow = splitWrapper.find('[data-test="overflow"]');
expect(overflow.isVisible()).toBe(false);
toggler.trigger('click');
expect(overflow.isVisible()).toBe(true);
diff --git a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelDetailsModal.vue b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelDetailsModal.vue
index f73779ba50..a67c060203 100644
--- a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelDetailsModal.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelDetailsModal.vue
@@ -5,7 +5,7 @@
color="black"
>
- {{ channel.name }}
+ {{ channel ? channel.name : '' }}
diff --git a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelModal.vue b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelModal.vue
index b76c6782b2..50f66d3679 100644
--- a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelModal.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelModal.vue
@@ -72,7 +72,7 @@
v-model="contentDefaults"
/>
-
+
{{ isNew ? $tr('createButton') : $tr('saveChangesButton' ) }}
@@ -109,8 +109,8 @@
import Vue from 'vue';
import { mapActions, mapGetters, mapMutations, mapState } from 'vuex';
- import ChannelSharing from './ChannelSharing';
import ChannelThumbnail from './ChannelThumbnail';
+ import ChannelSharing from './ChannelSharing';
import { NEW_OBJECT, ErrorTypes } from 'shared/constants';
import MessageDialog from 'shared/views/MessageDialog';
import LanguageDropdown from 'shared/views/LanguageDropdown';
@@ -153,6 +153,7 @@
showUnsavedDialog: false,
diffTracker: {},
dialog: true,
+ isDisable: false,
};
},
computed: {
@@ -287,21 +288,25 @@
...mapActions('channel', ['updateChannel', 'loadChannel', 'commitChannel']),
...mapMutations('channel', ['REMOVE_CHANNEL']),
saveChannel() {
+ this.isDisable = true;
if (this.$refs.detailsform.validate()) {
this.changed = false;
if (this.isNew) {
return this.commitChannel({ id: this.channelId, ...this.diffTracker }).then(() => {
// TODO: Make sure channel gets created before navigating to channel
window.location = window.Urls.channel(this.channelId);
+ this.isDisable = false;
});
} else {
return this.updateChannel({ id: this.channelId, ...this.diffTracker }).then(() => {
this.$store.dispatch('showSnackbarSimple', this.$tr('changesSaved'));
this.header = this.channel.name;
+ this.isDisable = false;
});
}
} else if (this.$refs.detailsform.$el.scrollIntoView) {
this.$refs.detailsform.$el.scrollIntoView({ behavior: 'smooth' });
+ this.isDisable = false;
}
},
updateTitleForPage() {
@@ -321,7 +326,7 @@
this.dialog = value;
},
setChannel(data) {
- for (let key in data) {
+ for (const key in data) {
Vue.set(this.diffTracker, key, data[key]);
}
this.changed = true;
diff --git a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSharingTable.vue b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSharingTable.vue
index 2fbecc4b78..5c9353af24 100644
--- a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSharingTable.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSharingTable.vue
@@ -172,7 +172,7 @@
user: state => state.session.currentUser,
}),
users() {
- let users = this.getChannelUsers(this.channelId, this.mode);
+ const users = this.getChannelUsers(this.channelId, this.mode);
// Make sure current user is at the top of the list
if (users.find(u => u.id === this.user.id)) {
@@ -215,7 +215,7 @@
'removeViewer',
]),
getUserText(user) {
- let nameParams = {
+ const nameParams = {
first_name: user.first_name,
last_name: user.last_name,
};
diff --git a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelThumbnail.vue b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelThumbnail.vue
index a6a0b448ca..4a9a59f395 100644
--- a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelThumbnail.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelThumbnail.vue
@@ -331,6 +331,7 @@
.thumbnail {
padding: 28% 0;
+ /* stylelint-disable-next-line custom-property-pattern */
border-color: var(--v-greyBorder-base) !important;
border-style: solid;
border-width: 1px;
diff --git a/contentcuration/contentcuration/frontend/shared/views/channel/__tests__/channelModal.spec.js b/contentcuration/contentcuration/frontend/shared/views/channel/__tests__/channelModal.spec.js
index 40c66e9949..c23e85d48c 100644
--- a/contentcuration/contentcuration/frontend/shared/views/channel/__tests__/channelModal.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/channel/__tests__/channelModal.spec.js
@@ -18,7 +18,7 @@ const router = new VueRouter({
const store = storeFactory();
const channelId = '11111111111111111111111111111111';
-let tab = 'share';
+const tab = 'share';
function makeWrapper() {
router
@@ -62,7 +62,7 @@ describe('channelModal', () => {
wrapper = makeWrapper();
});
it('clicking close should call cancelChanges', () => {
- let cancelChanges = jest.fn();
+ const cancelChanges = jest.fn();
wrapper.setMethods({ cancelChanges });
wrapper.find('[data-test="close"]').trigger('click');
expect(cancelChanges).toHaveBeenCalled();
diff --git a/contentcuration/contentcuration/frontend/shared/views/details/Details.vue b/contentcuration/contentcuration/frontend/shared/views/details/Details.vue
index 4beb67f2d4..b2f0d0bda3 100644
--- a/contentcuration/contentcuration/frontend/shared/views/details/Details.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/details/Details.vue
@@ -3,50 +3,50 @@
- {{ isChannel ? details.name : details.title }}
+ {{ isChannel ? _details.name : _details.title }}
- {{ details.description }}
+ {{ _details.description }}
-
+
{{
- details.primary_token.slice(0, 5) + '-' + details.primary_token.slice(5)
+ _details.primary_token.slice(0, 5) + '-' + _details.primary_token.slice(5)
}}
- {{ publishedDate }}
+ {{ publishedDate }}
{{ $tr('unpublishedText') }}
-
- {{ details.version }}
+
+ {{ _details.version }}
{{ defaultText }}
@@ -62,7 +62,7 @@
/>
- {{ $formatNumber(details.resource_count) }}
+ {{ $formatNumber(_details.resource_count) }}
+
+
+
+ {{ defaultText }}
+
+
+ {{ level }}
+
+
+ {{ levelsPrintable }}
+
+
+
+
+
+
+ {{ defaultText }}
+
+
+ {{ category }}
+
+
+ {{ categoriesPrintable }}
+
+
+
-
+
{{ $tr('coachHeading') }}
-
+
{{ $tr('assessmentsIncludedText') }}
-
+
{{ defaultText }}
@@ -101,7 +137,7 @@
@@ -126,7 +162,7 @@
@@ -136,7 +172,7 @@
@@ -150,7 +186,7 @@
@@ -163,7 +199,7 @@
@@ -176,7 +212,7 @@
@@ -186,7 +222,7 @@
-
+
{{ translateConstant(license) }}
@@ -203,7 +239,7 @@
@@ -251,7 +287,7 @@
+ import cloneDeep from 'lodash/cloneDeep';
+ import defaultsDeep from 'lodash/defaultsDeep';
+ import camelCase from 'lodash/camelCase';
import orderBy from 'lodash/orderBy';
- import DetailsRow from './DetailsRow';
import { SCALE_TEXT, SCALE, CHANNEL_SIZE_DIVISOR } from './constants';
+ import DetailsRow from './DetailsRow';
+ import { CategoriesLookup, LevelsLookup } from 'shared/constants';
import {
fileSizeMixin,
constantsTranslationMixin,
printingMixin,
titleMixin,
+ metadataTranslationMixin,
} from 'shared/mixins';
import LoadingText from 'shared/views/LoadingText';
import ExpandableList from 'shared/views/ExpandableList';
@@ -289,6 +330,39 @@
import Thumbnail from 'shared/views/files/Thumbnail';
import CopyToken from 'shared/views/CopyToken';
+ const DEFAULT_DETAILS = {
+ name: '',
+ title: '',
+ description: '',
+ thumbnail_url: null,
+ thumbnail_src: null,
+ thumbnail_encoding: null,
+ published: false,
+ version: null,
+ primary_token: null,
+ language: null,
+ last_update: null,
+ created: null,
+ last_published: null,
+ resource_count: 0,
+ resource_size: 0,
+ includes: { coach_content: 0, exercises: 0 },
+ kind_count: [],
+ languages: [],
+ accessible_languages: [],
+ licenses: [],
+ tags: [],
+ copyright_holders: [],
+ authors: [],
+ aggregators: [],
+ providers: [],
+ sample_pathway: [],
+ original_channels: [],
+ sample_nodes: [],
+ levels: [],
+ categories: [],
+ };
+
export default {
name: 'Details',
components: {
@@ -299,7 +373,13 @@
DetailsRow,
Thumbnail,
},
- mixins: [fileSizeMixin, constantsTranslationMixin, printingMixin, titleMixin],
+ mixins: [
+ fileSizeMixin,
+ constantsTranslationMixin,
+ printingMixin,
+ titleMixin,
+ metadataTranslationMixin,
+ ],
props: {
// Object matching that returned by the channel details and
// node details API endpoints, see backend for details of the
@@ -319,13 +399,19 @@
},
},
computed: {
+ _details() {
+ const details = cloneDeep(this.details);
+ defaultsDeep(details, DEFAULT_DETAILS);
+ details.published = Boolean(details.last_published);
+ return details;
+ },
defaultText() {
// Making this a computed property so it's easier to update
return '---';
},
publishedDate() {
if (this.isChannel) {
- return this.$formatDate(this.details.last_published, {
+ return this.$formatDate(this._details.last_published, {
year: 'numeric',
month: 'long',
day: 'numeric',
@@ -337,7 +423,7 @@
return window.libraryMode;
},
sizeText() {
- let size = (this.details && this.details.resource_size) || 0;
+ const size = this._details.resource_size;
const sizeIndex = Math.max(
1,
Math.min(Math.ceil(Math.log(size / CHANNEL_SIZE_DIVISOR) / Math.log(2)), 10)
@@ -348,10 +434,10 @@
});
},
kindCount() {
- return orderBy(this.details.kind_count, ['count', 'kind_id'], ['desc', 'asc']);
+ return orderBy(this._details.kind_count, ['count', 'kind_id'], ['desc', 'asc']);
},
createdDate() {
- return this.$formatDate(this.details.created, {
+ return this.$formatDate(this._details.created, {
year: 'numeric',
month: 'long',
day: 'numeric',
@@ -361,26 +447,54 @@
return Object.keys(this.details).length;
},
sortedTags() {
- return orderBy(this.details.tags, ['count'], ['desc']);
+ return orderBy(this._details.tags, ['count'], ['desc']);
},
includesPrintable() {
const includes = [];
- if (this.details.includes.coach_content) {
+ if (this._details.includes.coach_content) {
includes.push(this.$tr('coachHeading'));
}
- if (this.details.includes.exercises) {
+ if (this._details.includes.exercises) {
includes.push(this.$tr('assessmentsIncludedText'));
}
return includes.length ? includes.join(', ') : this.defaultText;
},
licensesPrintable() {
- return this.details.licenses.map(this.translateConstant).join(', ');
+ return this._details.licenses.map(this.translateConstant).join(', ');
},
tagPrintable() {
return this.sortedTags.map(tag => tag.tag_name).join(', ');
},
+ levels() {
+ return this._details.levels.map(level => {
+ level = LevelsLookup[level];
+ let translationKey;
+ if (level === 'PROFESSIONAL') {
+ translationKey = 'specializedProfessionalTraining';
+ } else if (level === 'WORK_SKILLS') {
+ translationKey = 'allLevelsWorkSkills';
+ } else if (level === 'BASIC_SKILLS') {
+ translationKey = 'allLevelsBasicSkills';
+ } else {
+ translationKey = camelCase(level);
+ }
+ return this.translateMetadataString(translationKey);
+ });
+ },
+ levelsPrintable() {
+ return this.levels.join(', ');
+ },
+ categories() {
+ return this._details.categories.map(category => {
+ category = CategoriesLookup[category];
+ return this.translateMetadataString(camelCase(category));
+ });
+ },
+ categoriesPrintable() {
+ return this.categories.join(', ');
+ },
},
methods: {
channelUrl(channel) {
@@ -397,6 +511,8 @@
tagsHeading: 'Common tags',
creationHeading: 'Created on',
containsHeading: 'Contains',
+ levelsHeading: 'Levels',
+ categoriesHeading: 'Categories',
languagesHeading: 'Languages',
subtitlesHeading: 'Captions and subtitles',
authorsLabel: 'Authors',
diff --git a/contentcuration/contentcuration/frontend/shared/views/errors/AppError.vue b/contentcuration/contentcuration/frontend/shared/views/errors/AppError.vue
index 362a461a02..409359f11e 100644
--- a/contentcuration/contentcuration/frontend/shared/views/errors/AppError.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/errors/AppError.vue
@@ -1,4 +1,4 @@
-
+
+
+
diff --git a/contentcuration/contentcuration/frontend/shared/views/policies/PoliciesModal.vue b/contentcuration/contentcuration/frontend/shared/views/policies/PoliciesModal.vue
index 3d19d882f4..4f8de84787 100644
--- a/contentcuration/contentcuration/frontend/shared/views/policies/PoliciesModal.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/policies/PoliciesModal.vue
@@ -20,15 +20,13 @@
data-test="accept-checkbox"
@change="togglePolicyAccepted"
/>
-
- {{ $tr('checkboxValidationErrorMessage') }}
-
@@ -87,7 +85,6 @@
data() {
return {
policyAccepted: false,
- showError: false,
};
},
computed: {
@@ -104,7 +101,6 @@
this.$emit('close');
},
validate() {
- this.showError = !this.policyAccepted;
return this.policyAccepted;
},
onPolicyAccept() {
@@ -117,7 +113,6 @@
lastUpdated: 'Last updated {date}',
closeButton: 'Close',
continueButton: 'Continue',
- checkboxValidationErrorMessage: 'Field is required',
checkboxText: 'I have agreed to the above terms',
},
};
diff --git a/contentcuration/contentcuration/frontend/shared/views/policies/__tests__/policiesModal.spec.js b/contentcuration/contentcuration/frontend/shared/views/policies/__tests__/policiesModal.spec.js
index 7dadb02723..41f29a1890 100644
--- a/contentcuration/contentcuration/frontend/shared/views/policies/__tests__/policiesModal.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/policies/__tests__/policiesModal.spec.js
@@ -89,16 +89,10 @@ describe('PoliciesModal', () => {
});
describe('when accept policy checkbox is not checked', () => {
- it('clicking continue button should display validation error', () => {
- wrapper.find('[data-test="continue-button"]').trigger('click');
-
- expect(wrapper.text()).toContain('Field is required');
- });
-
- it("clicking continue button shouldn't emit accept event", () => {
- wrapper.find('[data-test="continue-button"]').trigger('click');
-
- expect(wrapper.emitted().accept).toBeFalsy();
+ it('disable continue button', () => {
+ expect(wrapper.find('[data-test="continue-button"]').attributes().disabled).toEqual(
+ 'disabled'
+ );
});
});
diff --git a/contentcuration/contentcuration/frontend/shared/vuetify/icons.js b/contentcuration/contentcuration/frontend/shared/vuetify/icons.js
index f02d90a78d..fa0ce43e61 100644
--- a/contentcuration/contentcuration/frontend/shared/vuetify/icons.js
+++ b/contentcuration/contentcuration/frontend/shared/vuetify/icons.js
@@ -5,8 +5,8 @@ import CollapseAllIcon from '../views/icons/CollapseAllIcon';
import IndicatorIcon from '../views/icons/IndicatorIcon';
import LightBulbIcon from '../views/icons/LightBulbIcon';
import ViewOnlyIcon from '../views/icons/ViewOnlyIcon';
-import { ContentKindsNames } from 'shared/leUtils/ContentKinds';
import Icon from 'shared/views/Icon';
+import { ContentKindsNames } from 'shared/leUtils/ContentKinds';
Vue.component(Icon.name, Icon);
@@ -20,6 +20,7 @@ export const CONTENT_KIND_ICONS = {
[ContentKindsNames.EXERCISE]: 'assignment',
[ContentKindsNames.DOCUMENT]: 'class',
[ContentKindsNames.HTML5]: 'widgets',
+ [ContentKindsNames.ZIM]: 'widgets',
};
export function getContentKindIcon(kind, isEmpty = false) {
@@ -71,7 +72,7 @@ export default function icons(additional = {}) {
// Update icons to use our custom `Icon` component which adds a layer between implementation
// within Vuetify and our code, and the underlying `VIcon` component
- let vuetifyUpdatedIcons = Object.entries(iconMap)
+ const vuetifyUpdatedIcons = Object.entries(iconMap)
.map(([name, mdName]) => {
return {
[name]: {
diff --git a/contentcuration/contentcuration/frontend/shared/vuetify/theme.js b/contentcuration/contentcuration/frontend/shared/vuetify/theme.js
index 6ea2a7ca5c..add22c47ef 100644
--- a/contentcuration/contentcuration/frontend/shared/vuetify/theme.js
+++ b/contentcuration/contentcuration/frontend/shared/vuetify/theme.js
@@ -20,7 +20,9 @@ export default function theme() {
audio: '#f06292',
document: '#ff3d00',
exercise: '#4db6ac',
+ h5p: '#ff8f00',
html5: '#ff8f00',
+ zim: '#ff8f00',
slideshow: '#4ece90',
channelHighlightDefault: colors.grey.lighten3,
draggableDropZone: '#dddddd',
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/baseStore.js b/contentcuration/contentcuration/frontend/shared/vuex/baseStore.js
index 745e0de13f..a12991c35c 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/baseStore.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/baseStore.js
@@ -24,7 +24,8 @@ Vue.use(Vuex);
function parseListeners(moduleName, listeners, namespaced = false) {
const parsedListeners = [];
- for (let [tableName, tableListeners] of Object.entries(listeners)) {
+ for (const [tableName, tableListeners] of Object.entries(listeners)) {
+ /* eslint-disable-next-line prefer-const */
for (let [changeType, listener] of Object.entries(tableListeners)) {
if (!(listener instanceof Listener)) {
listener = commitListener(listener);
@@ -57,7 +58,7 @@ export default function storeFactory({
};
const parsedListeners = parseListeners(null, listeners);
- for (let [moduleName, module] of Object.entries(modules)) {
+ for (const [moduleName, module] of Object.entries(modules)) {
if (module.listeners) {
parsedListeners.push(...parseListeners(moduleName, module.listeners, module.namespaced));
delete module.listeners;
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/channel/__tests__/module.spec.js b/contentcuration/contentcuration/frontend/shared/vuex/channel/__tests__/module.spec.js
index 0f09ad4d7d..182dd79fd6 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/channel/__tests__/module.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/channel/__tests__/module.spec.js
@@ -8,6 +8,7 @@ import {
ViewerM2M,
EditorM2M,
User,
+ injectVuexStore,
} from 'shared/data/resources';
import { SharingPermissions } from 'shared/constants';
import storeFactory from 'shared/vuex/baseStore';
@@ -22,18 +23,20 @@ describe('channel actions', () => {
let id;
const channelDatum = { name: 'test', deleted: false, edit: true };
beforeEach(() => {
- return Channel.put(channelDatum).then(newId => {
+ store = storeFactory({
+ modules: {
+ channel,
+ },
+ });
+ injectVuexStore(store);
+ store.state.session.currentUser.id = userId;
+ return Channel.add(channelDatum).then(newId => {
id = newId;
channelDatum.id = id;
- store = storeFactory({
- modules: {
- channel,
- },
- });
- store.state.session.currentUser.id = userId;
});
});
afterEach(() => {
+ injectVuexStore();
return Channel.table.toCollection().delete();
});
describe('loadChannelList action', () => {
@@ -178,14 +181,14 @@ describe('channel actions', () => {
});
});
describe('bookmarkChannel action', () => {
- it('should call Bookmark.put when creating a bookmark', () => {
- const putSpy = jest.spyOn(Bookmark, 'put');
+ it('should call Bookmark.add when creating a bookmark', () => {
+ const addSpy = jest.spyOn(Bookmark, 'add');
store.commit('channel/ADD_CHANNEL', {
id,
name: 'test',
});
return store.dispatch('channel/bookmarkChannel', { id, bookmark: true }).then(() => {
- expect(putSpy).toHaveBeenCalledWith({ channel: id });
+ expect(addSpy).toHaveBeenCalledWith({ channel: id });
});
});
it('should call Bookmark.delete when removing a bookmark', () => {
@@ -268,7 +271,14 @@ describe('Channel sharing vuex', () => {
jest
.spyOn(ChannelUser, 'fetchCollection')
.mockImplementation(() => Promise.resolve([testUser]));
- return Channel.put(channelDatum).then(newId => {
+ store = storeFactory({
+ modules: {
+ channel,
+ },
+ });
+ injectVuexStore(store);
+ store.state.session.currentUser.id = userId;
+ return Channel.add(channelDatum).then(newId => {
channelId = newId;
const user = {
...testUser,
@@ -276,15 +286,9 @@ describe('Channel sharing vuex', () => {
const invitations = makeInvitations(channelId);
testInvitations = invitations;
- return User.put(user).then(() => {
- return ViewerM2M.put({ user: user.id, channel: channelDatum.id }).then(() => {
+ return User.add(user).then(() => {
+ return ViewerM2M.add({ user: user.id, channel: channelDatum.id }).then(() => {
return Invitation.table.bulkPut(invitations).then(() => {
- store = storeFactory({
- modules: {
- channel,
- },
- });
- store.state.session.currentUser.id = userId;
store.commit('channel/ADD_CHANNEL', { id: channelId, ...channelDatum });
store.commit('channel/SET_USERS_TO_CHANNEL', { channelId, users: [user] });
store.commit('channel/ADD_INVITATIONS', invitations);
@@ -295,6 +299,7 @@ describe('Channel sharing vuex', () => {
});
afterEach(() => {
jest.restoreAllMocks();
+ injectVuexStore();
return Promise.all([
Channel.table.toCollection().delete(),
ViewerM2M.table.toCollection().delete(),
@@ -380,9 +385,11 @@ describe('Channel sharing vuex', () => {
id: 'choosy-invitation',
email: 'choosy-collaborator@test.com',
declined: true,
+ channel: 'some-other-channel',
+ user: 'some-other-user',
};
- Invitation.put(declinedInvitation).then(() => {
+ Invitation.add(declinedInvitation).then(() => {
store.dispatch('channel/loadChannelUsers', channelId).then(() => {
expect(Object.keys(store.state.channel.invitationsMap)).not.toContain(
'choosy-invitation'
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/channel/actions.js b/contentcuration/contentcuration/frontend/shared/vuex/channel/actions.js
index 67470e6cd9..dede896b49 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/channel/actions.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/channel/actions.js
@@ -1,12 +1,11 @@
import pickBy from 'lodash/pickBy';
import { NOVALUE } from 'shared/constants';
-import { IGNORED_SOURCE } from 'shared/data/constants';
import { Bookmark, Channel, Invitation, ChannelUser } from 'shared/data/resources';
import client from 'shared/client';
export async function loadBookmarks(context) {
const bookmarks = await Bookmark.where();
- for (let bookmark of bookmarks) {
+ for (const bookmark of bookmarks) {
context.commit('SET_BOOKMARK', bookmark);
}
return bookmarks;
@@ -199,7 +198,7 @@ export function updateChannel(
export function bookmarkChannel(context, { id, bookmark }) {
if (bookmark) {
- return Bookmark.put({ channel: id }).then(() => {
+ return Bookmark.add({ channel: id }).then(() => {
context.commit('SET_BOOKMARK', { channel: id });
});
} else {
@@ -259,12 +258,12 @@ export function loadChannelUsers(context, channelId) {
}
export async function sendInvitation(context, { channelId, email, shareMode }) {
- let postedInvitation = await client.post(window.Urls.send_invitation_email(), {
+ const postedInvitation = await client.post(window.Urls.send_invitation_email(), {
user_email: email,
share_mode: shareMode,
channel_id: channelId,
});
- await Invitation.transaction({ mode: 'rw', source: IGNORED_SOURCE }, () => {
+ await Invitation.transaction({ mode: 'rw' }, () => {
return Invitation.table.put(postedInvitation.data);
});
context.commit('ADD_INVITATION', postedInvitation.data);
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/channel/mutations.js b/contentcuration/contentcuration/frontend/shared/vuex/channel/mutations.js
index 6ff4c501e0..2bef394c9a 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/channel/mutations.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/channel/mutations.js
@@ -81,7 +81,7 @@ export function DELETE_INVITATION(state, invitationId) {
}
export function SET_USERS_TO_CHANNEL(state, { channelId, users = [] } = {}) {
- for (let user of users) {
+ for (const user of users) {
const canEdit = user.can_edit;
const canView = user.can_view;
delete user.can_edit;
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/connectionPlugin/connectionModule.js b/contentcuration/contentcuration/frontend/shared/vuex/connectionPlugin/connectionModule.js
index 1383d07e13..0ceb330c34 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/connectionPlugin/connectionModule.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/connectionPlugin/connectionModule.js
@@ -36,9 +36,9 @@ export default {
if (state.polling) return; // polling has already been initiated
// used https://github.com/Aupajo/backoff-calculator to tune this
- let maximumPollingDelay = 30 * 60; // 30 minutes
- let initialPollingDelay = 1; // 1 second
- let delaySeconds = i => Math.min(i ** 2 + initialPollingDelay, maximumPollingDelay);
+ const maximumPollingDelay = 30 * 60; // 30 minutes
+ const initialPollingDelay = 1; // 1 second
+ const delaySeconds = i => Math.min(i ** 2 + initialPollingDelay, maximumPollingDelay);
const stealth = window.Urls.stealth();
const pollingClient = axios.create();
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/actions.js b/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/actions.js
index 46857ae1bb..de09ded7ab 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/actions.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/actions.js
@@ -162,7 +162,7 @@ export function clearDraggableDropped(context, identity) {
const { key } = new DraggableIdentityHelper(identity);
// If this identity maps to another key, use that
- let targetKey = isString(context.state.draggableContainerDrops[key])
+ const targetKey = isString(context.state.draggableContainerDrops[key])
? context.state.draggableContainerDrops[key]
: key;
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/mutations.js b/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/mutations.js
index fb4a63b005..df7d977276 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/mutations.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/mutations.js
@@ -42,13 +42,13 @@ export function RESET_DRAGGABLE_DIRECTION(state) {
}
export function ADD_DRAGGABLE_CONTAINER_DROPS(state, data) {
- for (let key in data) {
+ for (const key in data) {
Vue.set(state.draggableContainerDrops, key, data[key]);
}
}
export function REMOVE_DRAGGABLE_CONTAINER_DROPS(state, keys) {
- for (let key of keys) {
+ for (const key of keys) {
Vue.delete(state.draggableContainerDrops, key);
}
}
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/submodule/actions.js b/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/submodule/actions.js
index 0e2dbddd5d..64f46c2185 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/submodule/actions.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/draggablePlugin/module/submodule/actions.js
@@ -34,7 +34,7 @@ export function updateHoverDraggable(context, { id, minX, maxX, minY, maxY }) {
return;
}
- let { clientX, clientY } = context.rootState.draggable;
+ const { clientX, clientY } = context.rootState.draggable;
let section = DraggableFlags.NONE;
// Determine the quadrant of the element's bounds that the user is dragging over
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/file/__tests__/module.spec.js b/contentcuration/contentcuration/frontend/shared/vuex/file/__tests__/module.spec.js
index d9b74e6823..3f38ceabd8 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/file/__tests__/module.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/file/__tests__/module.spec.js
@@ -1,6 +1,9 @@
+import JSZip from 'jszip';
+import { getH5PMetadata } from '../utils';
import storeFactory from 'shared/vuex/baseStore';
-import { File } from 'shared/data/resources';
+import { File, injectVuexStore } from 'shared/data/resources';
import client from 'shared/client';
+import { mockChannelScope, resetMockChannelScope } from 'shared/utils/testing';
jest.mock('shared/vuex/connectionPlugin');
@@ -18,21 +21,46 @@ const testFile = {
const userId = 'some user';
+function get_metadata_file(data) {
+ const manifest = {
+ h5p: '1.0',
+ mainLibrary: 'content',
+ libraries: [
+ {
+ machineName: 'content',
+ majorVersion: 1,
+ minorVersion: 0,
+ },
+ ],
+ content: {
+ library: 'content',
+ },
+ ...data,
+ };
+ const manifestBlob = new Blob([JSON.stringify(manifest, null, 2)], { type: 'application/json' });
+ const manifestFile = new global.File([manifestBlob], 'h5p.json', { type: 'application/json' });
+ return manifestFile;
+}
+
describe('file store', () => {
let store;
let id;
- beforeEach(() => {
+ beforeEach(async () => {
+ await mockChannelScope('test-123');
jest.spyOn(File, 'fetchCollection').mockImplementation(() => Promise.resolve([testFile]));
jest.spyOn(File, 'fetchModel').mockImplementation(() => Promise.resolve(testFile));
- return File.put(testFile).then(newId => {
+ store = storeFactory();
+ injectVuexStore(store);
+ store.state.session.currentUser.id = userId;
+ return File.add(testFile).then(newId => {
id = newId;
- store = storeFactory();
store.commit('file/ADD_FILE', { id, ...testFile });
- store.state.session.currentUser.id = userId;
});
});
- afterEach(() => {
+ afterEach(async () => {
+ await resetMockChannelScope();
jest.restoreAllMocks();
+ injectVuexStore();
return File.table.toCollection().delete();
});
describe('file getters', () => {
@@ -42,14 +70,14 @@ describe('file store', () => {
expect(file.preset.id).toBe('document');
});
it('contentNodesTotalSize', () => {
- let file = {
+ const file = {
id: 'test',
preset: 'document_thumbnail',
file_size: 100,
checksum: 'checksum-1',
contentnode,
};
- let file2 = {
+ const file2 = {
id: 'test2',
preset: 'epub',
file_size: 100,
@@ -105,7 +133,7 @@ describe('file store', () => {
});
describe('upload actions', () => {
it('uploadFileToStorage should call client.put with upload url', () => {
- let payload = {
+ const payload = {
id: 'file-id',
file: { id: 'hello' },
url: 'test_url',
@@ -117,5 +145,97 @@ describe('file store', () => {
});
});
});
+ describe('H5P content file extract metadata', () => {
+ it('getH5PMetadata should check for h5p.json file', () => {
+ const zip = new JSZip();
+ return zip.generateAsync({ type: 'blob' }).then(async function(h5pBlob) {
+ await expect(getH5PMetadata(h5pBlob)).rejects.toThrow(
+ 'h5p.json not found in the H5P file.'
+ );
+ });
+ });
+ it('getH5PMetadata should extract metadata from h5p.json', async () => {
+ const manifestFile = get_metadata_file({ title: 'Test file' });
+ const zip = new JSZip();
+ zip.file('h5p.json', manifestFile);
+ await zip.generateAsync({ type: 'blob' }).then(async function(h5pBlob) {
+ await expect(getH5PMetadata(h5pBlob)).resolves.toEqual({
+ title: 'Test file',
+ });
+ });
+ });
+ it('getH5PMetadata should not extract und language', async () => {
+ const manifestFile = get_metadata_file({ title: 'Test file', language: 'und' });
+ const zip = new JSZip();
+ zip.file('h5p.json', manifestFile);
+ await zip.generateAsync({ type: 'blob' }).then(async function(h5pBlob) {
+ await expect(getH5PMetadata(h5pBlob)).resolves.toEqual({
+ title: 'Test file',
+ });
+ });
+ });
+ it.each([
+ ['CC BY', 1],
+ ['CC BY-SA', 2],
+ ['CC BY-ND', 3],
+ ['CC BY-NC', 4],
+ ['CC BY-NC-SA', 5],
+ ['CC BY-NC-ND', 6],
+ ['CC0 1.0', 8],
+ ])('getH5PMetadata should parse CC license %s', async (licenseName, licenseId) => {
+ const manifestFile = get_metadata_file({ title: 'Test file', license: licenseName });
+ const zip = new JSZip();
+ zip.file('h5p.json', manifestFile);
+ await zip.generateAsync({ type: 'blob' }).then(async function(h5pBlob) {
+ await expect(getH5PMetadata(h5pBlob)).resolves.toEqual({
+ title: 'Test file',
+ license: licenseId,
+ });
+ });
+ });
+ it.each([
+ [{ role: 'Author', name: 'Testing' }, 'author'],
+ [{ role: 'Editor', name: 'Testing' }, 'aggregator'],
+ [{ role: 'Licensee', name: 'Testing' }, 'copyright_holder'],
+ [{ role: 'Originator', name: 'Testing' }, 'provider'],
+ ])('getH5PMetadata should parse CC license %s', async (authorObj, field) => {
+ const manifestFile = get_metadata_file({ title: 'Test file', authors: [authorObj] });
+ const zip = new JSZip();
+ zip.file('h5p.json', manifestFile);
+ await zip.generateAsync({ type: 'blob' }).then(async function(h5pBlob) {
+ await expect(getH5PMetadata(h5pBlob)).resolves.toEqual({
+ title: 'Test file',
+ [field]: authorObj.name,
+ });
+ });
+ });
+ it('getH5PMetadata should not extract Firstname Surname author', async () => {
+ const manifestFile = get_metadata_file({
+ title: 'Test file',
+ authors: [{ name: 'Firstname Surname', role: 'Author' }],
+ });
+ const zip = new JSZip();
+ zip.file('h5p.json', manifestFile);
+ await zip.generateAsync({ type: 'blob' }).then(async function(h5pBlob) {
+ await expect(getH5PMetadata(h5pBlob)).resolves.toEqual({
+ title: 'Test file',
+ });
+ });
+ });
+ it('getH5PMetadata should exract metadata from h5p.json', async () => {
+ const manifestFile = get_metadata_file({
+ title: 'Test file',
+ language: 'en',
+ });
+ const zip = new JSZip();
+ zip.file('h5p.json', manifestFile);
+ await zip.generateAsync({ type: 'blob' }).then(async function(h5pBlob) {
+ await expect(getH5PMetadata(h5pBlob)).resolves.toEqual({
+ title: 'Test file',
+ language: 'en',
+ });
+ });
+ });
+ });
});
});
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/file/actions.js b/contentcuration/contentcuration/frontend/shared/vuex/file/actions.js
index 1a63391c67..778ebec9fa 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/file/actions.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/file/actions.js
@@ -89,7 +89,7 @@ export function updateFile(context, { id, ...payload }) {
if (fileData.contentnode) {
const presetObj = FormatPresetsMap.get(fileData.preset);
const files = context.getters.getContentNodeFiles(fileData.contentnode);
- for (let f of files) {
+ for (const f of files) {
if (
f.preset.id === presetObj.id &&
(!presetObj.multi_language || f.language.id === fileData.language) &&
@@ -164,90 +164,104 @@ export function uploadFileToStorage(
});
}
+/**
+ * @return {Promise<{uploadPromise: Promise, fileObject: Object}>}
+ */
export function uploadFile(context, { file, preset = null } = {}) {
- return new Promise((resolve, reject) => {
- // 1. Get the checksum of the file
- Promise.all([getHash(file), extractMetadata(file, preset)])
- .then(([checksum, metadata]) => {
- const file_format = file.name
- .split('.')
- .pop()
- .toLowerCase();
- // 2. Get the upload url
- File.uploadUrl({
+ const file_format = file.name
+ .split('.')
+ .pop()
+ .toLowerCase();
+ const hashPromise = getHash(file).catch(() => Promise.reject(fileErrors.CHECKSUM_HASH_FAILED));
+ let checksum,
+ metadata = {};
+
+ return Promise.all([hashPromise, extractMetadata(file, preset)])
+ .then(([fileChecksum, fileMetadata]) => {
+ checksum = fileChecksum;
+ metadata = fileMetadata;
+
+ // 2. Get the upload url
+ return File.uploadUrl({
+ checksum,
+ size: file.size,
+ type: file.type,
+ name: file.name,
+ file_format,
+ ...metadata,
+ }).catch(error => {
+ let errorType = fileErrors.UPLOAD_FAILED;
+ if (error.response && error.response.status === 412) {
+ errorType = fileErrors.NO_STORAGE;
+ }
+ return Promise.reject(errorType);
+ }); // End get upload url
+ })
+ .then(data => {
+ data.file.metadata = metadata;
+ const fileObject = {
+ ...data.file,
+ loaded: 0,
+ total: file.size,
+ };
+ context.commit('ADD_FILE', fileObject);
+
+ // Asynchronously generate file preview
+ setTimeout(() => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onloadend = () => {
+ if (reader.result) {
+ context.commit('ADD_FILE', {
+ id: data.file.id,
+ previewSrc: reader.result,
+ });
+ }
+ };
+ }, 0);
+
+ // 3. Upload file
+ const uploadPromise = context
+ .dispatch('uploadFileToStorage', {
+ id: fileObject.id,
checksum,
- size: file.size,
- type: file.type,
- name: file.name,
+ file,
file_format,
- ...metadata,
+ url: data['uploadURL'],
+ contentType: data['mimetype'],
+ mightSkip: data['might_skip'],
})
- .then(data => {
- const fileObject = {
- ...data.file,
- loaded: 0,
- total: file.size,
- };
- context.commit('ADD_FILE', fileObject);
- // 3. Upload file
- const promise = context
- .dispatch('uploadFileToStorage', {
- id: fileObject.id,
- checksum,
- file,
- file_format,
- url: data['uploadURL'],
- contentType: data['mimetype'],
- mightSkip: data['might_skip'],
- })
- .catch(() => {
- context.commit('ADD_FILE', {
- id: fileObject.id,
- loaded: 0,
- error: fileErrors.UPLOAD_FAILED,
- });
- return fileErrors.UPLOAD_FAILED;
- }); // End upload file
- // Resolve with a summary of the uploaded file
- // and a promise that can be chained from for file
- // upload completion
- resolve({ fileObject, promise });
- // Asynchronously generate file preview
- const reader = new FileReader();
- reader.readAsDataURL(file);
- reader.onloadend = () => {
- if (reader.result) {
- context.commit('ADD_FILE', {
- id: data.file.id,
- previewSrc: reader.result,
- });
- }
- };
- })
- .catch(error => {
- let errorType = fileErrors.UPLOAD_FAILED;
- if (error.response && error.response.status === 412) {
- errorType = fileErrors.NO_STORAGE;
- }
- const fileObject = {
- checksum,
- loaded: 0,
- total: file.size,
- file_size: file.size,
- original_filename: file.name,
- file_format,
- preset: metadata.preset,
- error: errorType,
- };
- context.commit('ADD_FILE', fileObject);
- // Resolve with a summary of the uploaded file
- resolve(fileObject);
- }); // End get upload url
- })
- .catch(() => {
- reject(fileErrors.CHECKSUM_HASH_FAILED);
- }); // End get hash
- });
+ .then(() => fileObject)
+ .catch(() => {
+ // Update vuex with failure
+ context.commit('ADD_FILE', {
+ id: fileObject.id,
+ loaded: 0,
+ error: fileErrors.UPLOAD_FAILED,
+ });
+ return Promise.reject(fileErrors.UPLOAD_FAILED);
+ });
+ // End upload file
+ return { fileObject, uploadPromise };
+ })
+ .catch(error => {
+ // If error isn't one of defined error constants, raise it
+ if (!Object.values(fileErrors).includes(error)) {
+ throw error;
+ }
+ const fileObject = {
+ checksum,
+ loaded: 0,
+ total: file.size,
+ file_size: file.size,
+ original_filename: file.name,
+ file_format,
+ preset: metadata.preset,
+ error,
+ };
+ context.commit('ADD_FILE', fileObject);
+ return { fileObject, uploadPromise: Promise.reject(error) };
+ });
}
export function getAudioData(context, url) {
@@ -255,7 +269,7 @@ export function getAudioData(context, url) {
client
.get(url, { responseType: 'arraybuffer' })
.then(response => {
- let audioContext = new AudioContext();
+ const audioContext = new AudioContext();
audioContext
.decodeAudioData(response.data, buffer => {
resolve(buffer.getChannelData(0));
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/file/utils.js b/contentcuration/contentcuration/frontend/shared/vuex/file/utils.js
index 151b2ab449..12e59b943c 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/file/utils.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/file/utils.js
@@ -1,5 +1,8 @@
import SparkMD5 from 'spark-md5';
+import JSZip from 'jszip';
import { FormatPresetsList, FormatPresetsNames } from 'shared/leUtils/FormatPresets';
+import { LicensesList } from 'shared/leUtils/Licenses';
+import LanguagesMap from 'shared/leUtils/Languages';
const BLOB_SLICE = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
const CHUNK_SIZE = 2097152;
@@ -7,15 +10,17 @@ const MEDIA_PRESETS = [
FormatPresetsNames.AUDIO,
FormatPresetsNames.HIGH_RES_VIDEO,
FormatPresetsNames.LOW_RES_VIDEO,
+ FormatPresetsNames.H5P,
];
const VIDEO_PRESETS = [FormatPresetsNames.HIGH_RES_VIDEO, FormatPresetsNames.LOW_RES_VIDEO];
+const H5P_PRESETS = [FormatPresetsNames.H5P];
export function getHash(file) {
return new Promise((resolve, reject) => {
- let fileReader = new FileReader();
- let spark = new SparkMD5.ArrayBuffer();
+ const fileReader = new FileReader();
+ const spark = new SparkMD5.ArrayBuffer();
let currentChunk = 0;
- let chunks = Math.ceil(file.size / CHUNK_SIZE);
+ const chunks = Math.ceil(file.size / CHUNK_SIZE);
fileReader.onload = function(e) {
spark.append(e.target.result);
currentChunk++;
@@ -61,6 +66,62 @@ export function storageUrl(checksum, file_format) {
return `/content/storage/${checksum[0]}/${checksum[1]}/${checksum}.${file_format}`;
}
+const AuthorFieldMappings = {
+ Author: 'author',
+ Editor: 'aggregator',
+ Licensee: 'copyright_holder',
+ Originator: 'provider',
+};
+
+export async function getH5PMetadata(fileInput) {
+ const zip = new JSZip();
+ const metadata = {};
+ return zip
+ .loadAsync(fileInput)
+ .then(function(zip) {
+ const h5pJson = zip.file('h5p.json');
+ if (h5pJson) {
+ return h5pJson.async('text');
+ } else {
+ throw new Error('h5p.json not found in the H5P file.');
+ }
+ })
+ .then(function(h5pContent) {
+ const data = JSON.parse(h5pContent);
+ if (Object.prototype.hasOwnProperty.call(data, 'title')) {
+ metadata.title = data['title'];
+ }
+ if (
+ Object.prototype.hasOwnProperty.call(data, 'language') &&
+ LanguagesMap.has(data['language']) &&
+ data['language'] !== 'und'
+ ) {
+ metadata.language = data['language'];
+ }
+ if (Object.prototype.hasOwnProperty.call(data, 'authors')) {
+ for (const author of data['authors']) {
+ // Ignore obvious placedholders created by online H5P editor tools
+ if (author.role && author.name !== 'Firstname Surname') {
+ if (AuthorFieldMappings[author.role]) {
+ metadata[AuthorFieldMappings[author.role]] = author.name;
+ }
+ }
+ }
+ }
+ if (Object.prototype.hasOwnProperty.call(data, 'license')) {
+ const license = LicensesList.find(license => license.license_name === data['license']);
+ if (license) {
+ metadata.license = license.id;
+ } else if (data['license'] == 'CC0 1.0') {
+ // Special case for CC0 1.0
+ // this is the hard coded license id for CC0 1.0
+ metadata.license = 8;
+ }
+ }
+ return metadata;
+ });
+}
+
/**
* @param {{name: String, preset: String}} file
* @param {String|null} preset
@@ -85,24 +146,33 @@ export function extractMetadata(file, preset = null) {
return Promise.resolve(metadata);
}
+ const isH5P = H5P_PRESETS.includes(metadata.preset);
+
// Extract additional media metadata
const isVideo = VIDEO_PRESETS.includes(metadata.preset);
return new Promise(resolve => {
- const mediaElement = document.createElement(isVideo ? 'video' : 'audio');
- // Add a listener to read the metadata once it has loaded.
- mediaElement.addEventListener('loadedmetadata', () => {
- metadata.duration = Math.floor(mediaElement.duration);
- // Override preset based off video resolution
- if (isVideo) {
- metadata.preset =
- mediaElement.videoHeight >= 720
- ? FormatPresetsNames.HIGH_RES_VIDEO
- : FormatPresetsNames.LOW_RES_VIDEO;
- }
+ if (isH5P) {
+ getH5PMetadata(file).then(data => {
+ Object.assign(metadata, data);
+ });
resolve(metadata);
- });
- // Set the src url on the media element
- mediaElement.src = URL.createObjectURL(file);
+ } else {
+ const mediaElement = document.createElement(isVideo ? 'video' : 'audio');
+ // Add a listener to read the metadata once it has loaded.
+ mediaElement.addEventListener('loadedmetadata', () => {
+ metadata.duration = Math.floor(mediaElement.duration);
+ // Override preset based off video resolution
+ if (isVideo) {
+ metadata.preset =
+ mediaElement.videoHeight >= 720
+ ? FormatPresetsNames.HIGH_RES_VIDEO
+ : FormatPresetsNames.LOW_RES_VIDEO;
+ }
+ resolve(metadata);
+ });
+ // Set the src url on the media element
+ mediaElement.src = URL.createObjectURL(file);
+ }
});
}
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/indexedDBPlugin/index.js b/contentcuration/contentcuration/frontend/shared/vuex/indexedDBPlugin/index.js
index 52a1475260..f8afa7445b 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/indexedDBPlugin/index.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/indexedDBPlugin/index.js
@@ -114,11 +114,15 @@ export default function IndexedDBPlugin(db, listeners = []) {
if (change.type === CHANGE_TYPES.UPDATED) {
obj = change.mods;
}
+ // omit the last fetched attribute used only in resource layer
+ const mods = omit(obj, [LAST_FETCHED]);
+ if (Object.keys(mods).length === 0 && change.type === CHANGE_TYPES.UPDATED) {
+ return;
+ }
events.emit(getEventName(change.table, change.type), {
// we ensure we invoke the listeners with an object that has the PK
[db[change.table].schema.primKey.keyPath]: change.key,
- // spread the object, omitting the last fetched attribute used only in resource layer
- ...omit(obj, [LAST_FETCHED]),
+ ...mods,
});
});
});
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/policies/index.js b/contentcuration/contentcuration/frontend/shared/vuex/policies/index.js
index ccd5955cc7..2a2e30baa3 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/policies/index.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/policies/index.js
@@ -89,7 +89,7 @@ export const mutations = {
state.selectedPolicy = policy;
},
SET_POLICIES(state, policies) {
- for (let policy in policies) {
+ for (const policy in policies) {
Vue.set(state.policies, policy, policies[policy]);
}
},
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/session/index.js b/contentcuration/contentcuration/frontend/shared/vuex/session/index.js
index d89a56e6d5..b0a2b10a05 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/session/index.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/session/index.js
@@ -6,6 +6,7 @@ import { Session, User } from 'shared/data/resources';
import { forceServerSync } from 'shared/data/serverSync';
import translator from 'shared/translator';
import { applyMods } from 'shared/data/applyRemoteChanges';
+import { FeatureFlagKeys } from 'shared/constants';
function langCode(language) {
// Turns a Django language name (en-gb) into an ISO language code (en-GB)
@@ -94,6 +95,12 @@ export default {
return getters.isAdmin || Boolean(getters.featureFlags[flag]);
};
},
+ isAIFeatureEnabled(state, getters) {
+ if (getters.loggedIn) {
+ return getters.hasFeatureEnabled(FeatureFlagKeys.ai_feature);
+ }
+ return false;
+ },
},
actions: {
saveSession(context, currentUser) {
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/session/index.spec.js b/contentcuration/contentcuration/frontend/shared/vuex/session/index.spec.js
new file mode 100644
index 0000000000..ef7e7308f3
--- /dev/null
+++ b/contentcuration/contentcuration/frontend/shared/vuex/session/index.spec.js
@@ -0,0 +1,84 @@
+import vuexSessionModule from './index.js';
+import { FeatureFlagKeys } from 'shared/constants';
+
+describe('session module feature flag related getters', () => {
+ let state;
+ beforeEach(() => {
+ state = {
+ currentUser: {
+ feature_flags: {
+ true_flag: true,
+ false_flag: false,
+ },
+ },
+ };
+ state.currentUser.feature_flags[FeatureFlagKeys.ai_feature] = true;
+ });
+
+ describe('featureFlags', () => {
+ let getters;
+ beforeEach(() => {
+ getters = {
+ featureFlags: vuexSessionModule.getters.featureFlags,
+ };
+ });
+ it('should return feature flags from current user', () => {
+ const result = getters.featureFlags(state);
+ expect(result).toEqual(state.currentUser.feature_flags);
+ });
+
+ it('should return empty object if no feature flags set', () => {
+ state.currentUser = {};
+ const result = getters.featureFlags(state);
+ expect(result).toEqual({});
+ });
+ });
+
+ describe('hasFeatureEnabled', () => {
+ let getters;
+ beforeEach(() => {
+ getters = {
+ featureFlags: state.currentUser.feature_flags,
+ hasFeatureEnabled: vuexSessionModule.getters.hasFeatureEnabled,
+ };
+ });
+ it('for admin user returns true even when the flag value is false', () => {
+ getters.isAdmin = true;
+ expect(getters.hasFeatureEnabled(state, getters)('true_flag')).toBe(true);
+ expect(getters.hasFeatureEnabled(state, getters)('false_flag')).toBe(true);
+ });
+
+ it('returns flag value for non-admin user', () => {
+ getters.isAdmin = false;
+ expect(getters.hasFeatureEnabled(state, getters)('true_flag')).toBe(true);
+ expect(getters.hasFeatureEnabled(state, getters)('false_flag')).toBe(false);
+ });
+ });
+
+ describe('isAIFeatureEnabled', () => {
+ let getters;
+ beforeEach(() => {
+ getters = {
+ loggedIn: true,
+ hasFeatureEnabled: vuexSessionModule.getters.hasFeatureEnabled(state, {
+ featureFlags: vuexSessionModule.getters.featureFlags(state),
+ isAdmin: false,
+ }),
+ isAIFeatureEnabled: vuexSessionModule.getters.isAIFeatureEnabled,
+ };
+ });
+ it('should return false if not logged in', () => {
+ getters.loggedIn = false;
+ expect(getters.isAIFeatureEnabled(state, getters)).toBe(false);
+ });
+
+ it('should return true if logged in and ai feature flag is true', () => {
+ expect(getters.isAIFeatureEnabled(state, getters)).toBe(true);
+ });
+
+ it('should return false if logged in and ai feature flag is false', () => {
+ state.currentUser.feature_flags[FeatureFlagKeys.ai_feature] = false;
+ expect(getters.isAIFeatureEnabled(state, getters)).toBe(false);
+ });
+ });
+});
diff --git a/contentcuration/contentcuration/frontend/shared/vuex/syncProgressPlugin/index.js b/contentcuration/contentcuration/frontend/shared/vuex/syncProgressPlugin/index.js
index 60a2324d2e..0943920c24 100644
--- a/contentcuration/contentcuration/frontend/shared/vuex/syncProgressPlugin/index.js
+++ b/contentcuration/contentcuration/frontend/shared/vuex/syncProgressPlugin/index.js
@@ -1,3 +1,4 @@
+import { liveQuery } from 'dexie';
import syncProgressModule from './syncProgressModule';
import db from 'shared/data/db';
import { CHANGES_TABLE } from 'shared/data/constants';
@@ -5,18 +6,23 @@ import { CHANGES_TABLE } from 'shared/data/constants';
const SyncProgressPlugin = store => {
store.registerModule('syncProgress', syncProgressModule);
- db.on('changes', function(changes) {
- const changesTableUpdated = changes.some(change => change.table === CHANGES_TABLE);
- if (!changesTableUpdated) {
- return;
- }
+ store.listenForIndexedDBChanges = () => {
+ const observable = liveQuery(() => {
+ return db[CHANGES_TABLE].toCollection()
+ .filter(c => !c.synced)
+ .first(Boolean);
+ });
- db[CHANGES_TABLE].toCollection()
- .filter(c => !c.synced)
- .limit(1)
- .count()
- .then(count => store.commit('SET_UNSAVED_CHANGES', count > 0));
- });
+ const subscription = observable.subscribe({
+ next(result) {
+ store.commit('SET_UNSAVED_CHANGES', result);
+ },
+ error() {
+ subscription.unsubscribe();
+ },
+ });
+ store.stopListeningForIndexedDBChanges = subscription.unsubscribe;
+ };
};
export default SyncProgressPlugin;
diff --git a/contentcuration/contentcuration/management/commands/count_public_resources.py b/contentcuration/contentcuration/management/commands/count_public_resources.py
index a9edd48bca..40b717b608 100644
--- a/contentcuration/contentcuration/management/commands/count_public_resources.py
+++ b/contentcuration/contentcuration/management/commands/count_public_resources.py
@@ -5,7 +5,6 @@
from contentcuration.models import Channel
from contentcuration.models import ContentNode
-logging.basicConfig()
logger = logging.getLogger('command')
@@ -15,7 +14,7 @@ def handle(self, *args, **options):
public_tree_ids = Channel.objects.filter(public=True, deleted=False).values_list('main_tree__tree_id', flat=True)
count = ContentNode.objects.filter(tree_id__in=public_tree_ids) \
.exclude(kind_id='topic') \
- .values_list('content_id', flat=True) \
+ .values('content_id', 'language_id') \
.distinct() \
.count()
logger.info("{} unique resources".format(count))
diff --git a/contentcuration/contentcuration/management/commands/fix_duplicate_assessment_items.py b/contentcuration/contentcuration/management/commands/fix_duplicate_assessment_items.py
index 2aa3bf0f28..96c86e3fa5 100644
--- a/contentcuration/contentcuration/management/commands/fix_duplicate_assessment_items.py
+++ b/contentcuration/contentcuration/management/commands/fix_duplicate_assessment_items.py
@@ -2,14 +2,12 @@
import time
import uuid
-import progressbar
from django.core.management.base import BaseCommand
from django.db.models import Count
from django.db.models import F
from contentcuration.models import ContentNode
-logmodule.basicConfig()
logging = logmodule.getLogger(__name__)
@@ -27,7 +25,6 @@ def handle(self, *args, **options):
logging.info("Fixing {} nodes...".format(total))
- bar = progressbar.ProgressBar(max_value=total)
for i, node in enumerate(nodes):
# Go through each node's assessment items
for item in node.assessment_items.all():
@@ -51,6 +48,6 @@ def handle(self, *args, **options):
new_id = uuid.uuid4().hex
item.assessment_id = new_id
item.save()
- bar.update(i)
+ logging.info("Fixed assessment items for {} node(s)".format(i + 1))
logging.info("Finished in {}".format(time.time() - start))
diff --git a/contentcuration/contentcuration/management/commands/fix_exercise_complete.py b/contentcuration/contentcuration/management/commands/fix_exercise_complete.py
index 74b3438302..f9ed6e903f 100644
--- a/contentcuration/contentcuration/management/commands/fix_exercise_complete.py
+++ b/contentcuration/contentcuration/management/commands/fix_exercise_complete.py
@@ -9,7 +9,6 @@
from contentcuration.models import ContentNode
from contentcuration.models import License
-logmodule.basicConfig(level=logmodule.INFO)
logging = logmodule.getLogger('command')
diff --git a/contentcuration/contentcuration/management/commands/garbage_collect.py b/contentcuration/contentcuration/management/commands/garbage_collect.py
index f31db7ad5c..732a494aec 100644
--- a/contentcuration/contentcuration/management/commands/garbage_collect.py
+++ b/contentcuration/contentcuration/management/commands/garbage_collect.py
@@ -11,11 +11,11 @@
from contentcuration.utils.garbage_collect import clean_up_contentnodes
from contentcuration.utils.garbage_collect import clean_up_deleted_chefs
from contentcuration.utils.garbage_collect import clean_up_feature_flags
+from contentcuration.utils.garbage_collect import clean_up_soft_deleted_users
from contentcuration.utils.garbage_collect import clean_up_stale_files
from contentcuration.utils.garbage_collect import clean_up_tasks
-logmodule.basicConfig(level=logmodule.INFO)
logging = logmodule.getLogger('command')
@@ -26,15 +26,23 @@ def handle(self, *args, **options):
Actual logic for garbage collection.
"""
- # clean up contentnodes, files and file objects on storage that are associated
- # with the orphan tree
+ # Clean up users that are soft deleted and are older than ACCOUNT_DELETION_BUFFER (90 days).
+ # Also clean contentnodes, files and file objects on storage that are associated
+ # with the orphan tree.
+ logging.info("Cleaning up soft deleted users older than ACCOUNT_DELETION_BUFFER (90 days)")
+ clean_up_soft_deleted_users()
+
logging.info("Cleaning up contentnodes from the orphan tree")
clean_up_contentnodes()
+
logging.info("Cleaning up deleted chef nodes")
clean_up_deleted_chefs()
+
logging.info("Cleaning up feature flags")
clean_up_feature_flags()
+
logging.info("Cleaning up stale file objects")
clean_up_stale_files()
+
logging.info("Cleaning up tasks")
clean_up_tasks()
diff --git a/contentcuration/contentcuration/management/commands/mark_incomplete.py b/contentcuration/contentcuration/management/commands/mark_incomplete.py
index 0e058b4e95..056634d7d8 100644
--- a/contentcuration/contentcuration/management/commands/mark_incomplete.py
+++ b/contentcuration/contentcuration/management/commands/mark_incomplete.py
@@ -13,7 +13,6 @@
from contentcuration.models import File
from contentcuration.models import License
-logmodule.basicConfig(level=logmodule.INFO)
logging = logmodule.getLogger('command')
diff --git a/contentcuration/contentcuration/management/commands/reconcile_change_tasks.py b/contentcuration/contentcuration/management/commands/reconcile_change_tasks.py
new file mode 100644
index 0000000000..4aa2f9f261
--- /dev/null
+++ b/contentcuration/contentcuration/management/commands/reconcile_change_tasks.py
@@ -0,0 +1,43 @@
+import logging
+
+from django.core.management.base import BaseCommand
+
+from contentcuration.celery import app
+from contentcuration.models import Change
+from contentcuration.models import User
+
+logger = logging.getLogger('command')
+
+
+class Command(BaseCommand):
+ """
+ Reconciles that unready tasks are marked as reserved or active according to celery control
+ """
+
+ def handle(self, *args, **options):
+ from contentcuration.tasks import apply_channel_changes_task
+ from contentcuration.tasks import apply_user_changes_task
+
+ active_task_ids = [task['id'] for task in app.get_active_and_reserved_tasks()]
+
+ channel_changes = Change.objects.filter(channel_id__isnull=False, applied=False, errored=False) \
+ .order_by('channel_id', 'created_by_id') \
+ .values('channel_id', 'created_by_id') \
+ .distinct()
+ for channel_change in channel_changes:
+ apply_channel_changes_task.revoke(exclude_task_ids=active_task_ids, channel_id=channel_change['channel_id'])
+ apply_channel_changes_task.fetch_or_enqueue(
+ User.objects.get(pk=channel_change['created_by_id']),
+ channel_id=channel_change['channel_id']
+ )
+
+ user_changes = Change.objects.filter(channel_id__isnull=True, user_id__isnull=False, applied=False, errored=False) \
+ .order_by('user_id', 'created_by_id') \
+ .values('user_id', 'created_by_id') \
+ .distinct()
+ for user_change in user_changes:
+ apply_user_changes_task.revoke(exclude_task_ids=active_task_ids, user_id=user_change['user_id'])
+ apply_user_changes_task.fetch_or_enqueue(
+ User.objects.get(pk=user_change['created_by_id']),
+ user_id=user_change['user_id']
+ )
diff --git a/contentcuration/contentcuration/management/commands/reconcile_publishing_status.py b/contentcuration/contentcuration/management/commands/reconcile_publishing_status.py
new file mode 100644
index 0000000000..ce97abf7a5
--- /dev/null
+++ b/contentcuration/contentcuration/management/commands/reconcile_publishing_status.py
@@ -0,0 +1,36 @@
+import logging
+
+from django.core.management.base import BaseCommand
+
+from contentcuration.celery import app
+from contentcuration.models import Channel
+
+logging.basicConfig()
+logger = logging.getLogger("command")
+
+
+class Command(BaseCommand):
+ """
+ Reconciles publishing status of channels.
+ If there's no active task for a publishing channel then we reset its publishing status
+ to False.
+ """
+
+ def handle(self, *args, **options):
+ from contentcuration.tasks import apply_channel_changes_task
+
+ # Channels that are in `publishing` state.
+ publishing_channels = list(Channel.objects.filter(deleted=False, main_tree__publishing=True).values_list("id", flat=True))
+
+ # channel_ids of tasks that are currently being run by the celery workers.
+ active_channel_tasks = [task["kwargs"].get("channel_id") for task in app.get_active_tasks()
+ if task["name"] == apply_channel_changes_task.name]
+
+ # If channel is in publishing state and doesnot have any active task,
+ # that means the worker has crashed. So, we reset the publishing state to False.
+ for channel_id in publishing_channels:
+ if channel_id not in active_channel_tasks:
+ channel = Channel.objects.get(pk=channel_id)
+ channel.main_tree.publishing = False
+ channel.main_tree.save()
+ logger.info(f"Resetted publishing status to False for channel {channel.id}.")
diff --git a/contentcuration/contentcuration/management/commands/restore_channel.py b/contentcuration/contentcuration/management/commands/restore_channel.py
index 4088232c4b..efaeb3ee7c 100644
--- a/contentcuration/contentcuration/management/commands/restore_channel.py
+++ b/contentcuration/contentcuration/management/commands/restore_channel.py
@@ -4,7 +4,6 @@
from contentcuration.utils.import_tools import import_channel
-logging.basicConfig()
logger = logging.getLogger('command')
diff --git a/contentcuration/contentcuration/management/commands/set_content_mimetypes.py b/contentcuration/contentcuration/management/commands/set_content_mimetypes.py
index 72c5b57d8c..732d64f8d6 100755
--- a/contentcuration/contentcuration/management/commands/set_content_mimetypes.py
+++ b/contentcuration/contentcuration/management/commands/set_content_mimetypes.py
@@ -11,7 +11,6 @@
import concurrent.futures
import os
-import progressbar
from django.core.files.storage import default_storage
from django.core.management.base import BaseCommand
@@ -26,15 +25,11 @@ def handle(self, *args, **kwargs):
futures = []
with concurrent.futures.ThreadPoolExecutor() as e:
print("Scheduling all metadata update jobs...")
- progbar = progressbar.ProgressBar()
- for blob in progbar(blobs):
+ for blob in blobs:
future = e.submit(self._update_metadata, blob)
futures.append(future)
print("Waiting for all jobs to finish...")
- progbar = progressbar.ProgressBar(max_value=len(futures))
- for _ in progbar(concurrent.futures.as_completed(futures)):
- pass
def _determine_cache_control(self, name):
_, ext = os.path.splitext(name)
diff --git a/contentcuration/contentcuration/management/commands/set_default_learning_activities.py b/contentcuration/contentcuration/management/commands/set_default_learning_activities.py
index 9d958227e0..b6202477fe 100644
--- a/contentcuration/contentcuration/management/commands/set_default_learning_activities.py
+++ b/contentcuration/contentcuration/management/commands/set_default_learning_activities.py
@@ -6,7 +6,6 @@
from contentcuration.constants.contentnode import kind_activity_map
from contentcuration.models import ContentNode
-logmodule.basicConfig(level=logmodule.INFO)
logging = logmodule.getLogger('command')
diff --git a/contentcuration/contentcuration/management/commands/set_file_duration.py b/contentcuration/contentcuration/management/commands/set_file_duration.py
index 1e828dac05..77446c9853 100644
--- a/contentcuration/contentcuration/management/commands/set_file_duration.py
+++ b/contentcuration/contentcuration/management/commands/set_file_duration.py
@@ -7,7 +7,6 @@
from contentcuration.models import File
from contentcuration.models import MEDIA_PRESETS
-logmodule.basicConfig(level=logmodule.INFO)
logging = logmodule.getLogger('command')
diff --git a/contentcuration/contentcuration/management/commands/set_orm_based_has_captions.py b/contentcuration/contentcuration/management/commands/set_orm_based_has_captions.py
index edbcbbcd40..38865f6b89 100644
--- a/contentcuration/contentcuration/management/commands/set_orm_based_has_captions.py
+++ b/contentcuration/contentcuration/management/commands/set_orm_based_has_captions.py
@@ -11,7 +11,6 @@
from contentcuration.models import ContentNode
from contentcuration.models import File
-logmodule.basicConfig(level=logmodule.INFO)
logging = logmodule.getLogger('command')
@@ -23,13 +22,13 @@ class Command(BaseCommand):
def handle(self, *args, **options):
start = time.time()
- logging.info("Setting 'has captions' for video kinds")
+ logging.info("Setting 'has captions' for audio kinds")
has_captions_subquery = Exists(File.objects.filter(contentnode=OuterRef("id"), language=OuterRef("language"), preset_id=format_presets.VIDEO_SUBTITLE))
- # Only try to update video nodes which have not had any accessibility labels set on them
+ # Only try to update audio nodes which have not had any accessibility labels set on them
# this will allow this management command to be rerun and resume from where it left off
# and also prevent stomping previous edits to the accessibility_labels field.
- updateable_nodes = ContentNode.objects.filter(has_captions_subquery, kind=content_kinds.VIDEO, accessibility_labels__isnull=True)
+ updateable_nodes = ContentNode.objects.filter(has_captions_subquery, kind=content_kinds.AUDIO, accessibility_labels__isnull=True)
updateable_node_slice = updateable_nodes.values_list("id", flat=True)[0:CHUNKSIZE]
diff --git a/contentcuration/contentcuration/management/commands/set_storage_used.py b/contentcuration/contentcuration/management/commands/set_storage_used.py
index 33ba1c3ff9..906ac580e7 100644
--- a/contentcuration/contentcuration/management/commands/set_storage_used.py
+++ b/contentcuration/contentcuration/management/commands/set_storage_used.py
@@ -1,16 +1,19 @@
-import progressbar
+import logging
+
from django.core.management.base import BaseCommand
from contentcuration.models import User
+logger = logging.getLogger(__name__)
+
+
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("--force", action="store_true", dest="force", default=False)
def handle(self, *args, **options):
users = User.objects.all() if options["force"] else User.objects.filter(disk_space_used=0)
- bar = progressbar.ProgressBar(max_value=users.count())
for index, user in enumerate(users):
user.set_space_used()
- bar.update(index)
+ logger.info("Updated storage used for {} user(s)".format(index + 1))
diff --git a/contentcuration/contentcuration/management/commands/setup.py b/contentcuration/contentcuration/management/commands/setup.py
index 5a41ed9e7b..3284349ebe 100644
--- a/contentcuration/contentcuration/management/commands/setup.py
+++ b/contentcuration/contentcuration/management/commands/setup.py
@@ -26,7 +26,6 @@
from contentcuration.utils.publish import publish_channel
from contentcuration.utils.storage_common import is_gcs_backend
-logmodule.basicConfig()
logging = logmodule.getLogger(__name__)
DESCRIPTION = """
diff --git a/contentcuration/contentcuration/middleware/locale.py b/contentcuration/contentcuration/middleware/locale.py
new file mode 100644
index 0000000000..965312c0fa
--- /dev/null
+++ b/contentcuration/contentcuration/middleware/locale.py
@@ -0,0 +1,27 @@
+from django.conf import settings
+from django.middleware.locale import LocaleMiddleware
+from django.utils import translation
+
+LOCALE_EXEMPT = "_locale_exempt"
+
+
+def locale_exempt(view):
+ setattr(view, LOCALE_EXEMPT, True)
+ return view
+
+
+class KolibriStudioLocaleMiddleware(LocaleMiddleware):
+ def _is_exempt(self, obj):
+ return hasattr(obj, LOCALE_EXEMPT)
+
+ def process_view(self, request, callback, callback_args, callback_kwargs):
+ if self._is_exempt(callback):
+ setattr(request, LOCALE_EXEMPT, True)
+ translation.activate(settings.LANGUAGE_CODE)
+ request.LANGUAGE_CODE = translation.get_language()
+ return None
+
+ def process_response(self, request, response):
+ if self._is_exempt(request):
+ return response
+ return super(KolibriStudioLocaleMiddleware, self).process_response(request, response)
diff --git a/contentcuration/contentcuration/middleware/session.py b/contentcuration/contentcuration/middleware/session.py
new file mode 100644
index 0000000000..35fb81a367
--- /dev/null
+++ b/contentcuration/contentcuration/middleware/session.py
@@ -0,0 +1,23 @@
+from django.contrib.sessions.middleware import SessionMiddleware
+
+SESSION_EXEMPT = "_session_exempt"
+
+
+def session_exempt(view):
+ setattr(view, SESSION_EXEMPT, True)
+ return view
+
+
+class KolibriStudioSessionMiddleware(SessionMiddleware):
+ def _is_exempt(self, obj):
+ return hasattr(obj, SESSION_EXEMPT)
+
+ def process_view(self, request, callback, callback_args, callback_kwargs):
+ if self._is_exempt(callback):
+ setattr(request, SESSION_EXEMPT, True)
+ return None
+
+ def process_response(self, request, response):
+ if self._is_exempt(request):
+ return response
+ return super(KolibriStudioSessionMiddleware, self).process_response(request, response)
diff --git a/contentcuration/contentcuration/migrations/0141_add_task_signature.py b/contentcuration/contentcuration/migrations/0141_add_task_signature.py
new file mode 100644
index 0000000000..4e182e8fa1
--- /dev/null
+++ b/contentcuration/contentcuration/migrations/0141_add_task_signature.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.2.14 on 2022-12-09 16:09
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ replaces = [('django_celery_results', '0140_delete_task'),]
+
+ def __init__(self, name, app_label):
+ super(Migration, self).__init__(name, 'django_celery_results')
+
+ dependencies = [
+ ('contentcuration', '0140_delete_task'),
+ ('django_celery_results', '0011_taskresult_periodic_task_name'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='taskresult',
+ name='signature',
+ field=models.CharField(max_length=32, null=True),
+ ),
+ migrations.AddIndex(
+ model_name='taskresult',
+ index=models.Index(condition=models.Q(('status__in', frozenset(['STARTED', 'REJECTED', 'RETRY', 'RECEIVED', 'PENDING']))), fields=['signature'], name='task_result_signature_idx'),
+ ),
+ ]
diff --git a/contentcuration/contentcuration/migrations/0142_remove_file_file_media_duration_int.py b/contentcuration/contentcuration/migrations/0142_remove_file_file_media_duration_int.py
new file mode 100644
index 0000000000..e497fbd398
--- /dev/null
+++ b/contentcuration/contentcuration/migrations/0142_remove_file_file_media_duration_int.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.2.18 on 2023-04-26 18:55
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('contentcuration', '0141_add_task_signature'),
+ ]
+
+ operations = [
+ migrations.RemoveConstraint(
+ model_name='file',
+ name='file_media_duration_int',
+ ),
+ ]
diff --git a/contentcuration/contentcuration/migrations/0143_file_file_media_duration_int.py b/contentcuration/contentcuration/migrations/0143_file_file_media_duration_int.py
new file mode 100644
index 0000000000..3a7dbae1a0
--- /dev/null
+++ b/contentcuration/contentcuration/migrations/0143_file_file_media_duration_int.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.18 on 2023-05-03 15:28
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('contentcuration', '0142_remove_file_file_media_duration_int'),
+ ]
+
+ operations = [
+ migrations.AddConstraint(
+ model_name='file',
+ constraint=models.CheckConstraint(check=models.Q(models.Q(('duration__gt', 0), ('preset__in', ['audio', 'audio_dependency', 'high_res_video', 'low_res_video', 'video_dependency'])), ('duration__isnull', True), _connector='OR'), name='file_media_duration_int'),
+ ),
+ ]
diff --git a/contentcuration/contentcuration/migrations/0144_soft_delete_user.py b/contentcuration/contentcuration/migrations/0144_soft_delete_user.py
new file mode 100644
index 0000000000..a04040df69
--- /dev/null
+++ b/contentcuration/contentcuration/migrations/0144_soft_delete_user.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.2.14 on 2022-10-22 18:30
+import django.db.models.deletion
+import django.utils.timezone
+from django.conf import settings
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('contentcuration', '0143_file_file_media_duration_int'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='user',
+ name='deleted',
+ field=models.BooleanField(db_index=True, default=False),
+ ),
+ migrations.CreateModel(
+ name='UserHistory',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('action', models.CharField(choices=[('soft-deletion', 'User soft deletion'), ('soft-recovery',
+ 'User soft deletion recovery'), ('related-data-hard-deletion', 'User related data hard deletion')], max_length=32)),
+ ('performed_at', models.DateTimeField(default=django.utils.timezone.now)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='history', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/contentcuration/contentcuration/migrations/0145_custom_task_metadata.py b/contentcuration/contentcuration/migrations/0145_custom_task_metadata.py
new file mode 100644
index 0000000000..64287039f0
--- /dev/null
+++ b/contentcuration/contentcuration/migrations/0145_custom_task_metadata.py
@@ -0,0 +1,48 @@
+# Generated by Django 3.2.19 on 2023-09-14 05:08
+import django.core.validators
+import django.db.models.deletion
+from celery import states
+from django.conf import settings
+from django.db import migrations
+from django.db import models
+
+def transfer_data(apps, schema_editor):
+ CustomTaskMetadata = apps.get_model('contentcuration', 'CustomTaskMetadata')
+ TaskResult = apps.get_model('django_celery_results', 'taskresult')
+
+ old_task_results = TaskResult.objects.filter(status__in=states.UNREADY_STATES)
+
+ for old_task_result in old_task_results:
+ CustomTaskMetadata.objects.create(
+ task_id=old_task_result.task_id,
+ user=old_task_result.user,
+ channel_id=old_task_result.channel_id,
+ progress=old_task_result.progress,
+ signature=old_task_result.signature,
+ )
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('contentcuration', '0144_soft_delete_user'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CustomTaskMetadata',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('task_id', models.CharField(max_length=255, unique=True)),
+ ('channel_id', models.UUIDField(blank=True, db_index=True, null=True)),
+ ('progress', models.IntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
+ ('signature', models.CharField(max_length=32, null=True)),
+ ('date_created', models.DateTimeField(auto_now_add=True, help_text='Datetime field when the custom_metadata for task was created in UTC', verbose_name='Created DateTime')),
+ ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.AddIndex(
+ model_name='customtaskmetadata',
+ index=models.Index(fields=['signature'], name='task_result_signature'),
+ ),
+ migrations.RunPython(transfer_data),
+ ]
diff --git a/contentcuration/contentcuration/migrations/0146_drop_taskresult_fields.py b/contentcuration/contentcuration/migrations/0146_drop_taskresult_fields.py
new file mode 100644
index 0000000000..5ecc6cb98f
--- /dev/null
+++ b/contentcuration/contentcuration/migrations/0146_drop_taskresult_fields.py
@@ -0,0 +1,37 @@
+# Generated by Django 3.2.19 on 2023-09-14 10:42
+from django.db import migrations
+
+class Migration(migrations.Migration):
+
+ replaces = [('django_celery_results', '0145_custom_task_metadata'),]
+
+ def __init__(self, name, app_label):
+ super(Migration, self).__init__(name, 'django_celery_results')
+
+ dependencies = [
+ ('contentcuration', '0145_custom_task_metadata'),
+ ('contentcuration', '0141_add_task_signature'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='taskresult',
+ name='channel_id',
+ ),
+ migrations.RemoveField(
+ model_name='taskresult',
+ name='progress',
+ ),
+ migrations.RemoveField(
+ model_name='taskresult',
+ name='user',
+ ),
+ migrations.RemoveField(
+ model_name='taskresult',
+ name='signature',
+ ),
+ migrations.RemoveIndex(
+ model_name='taskresult',
+ name='task_result_signature_idx',
+ ),
+ ]
diff --git a/contentcuration/contentcuration/migrations/0147_alter_formatpreset_id.py b/contentcuration/contentcuration/migrations/0147_alter_formatpreset_id.py
new file mode 100644
index 0000000000..ac3faa8904
--- /dev/null
+++ b/contentcuration/contentcuration/migrations/0147_alter_formatpreset_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.24 on 2024-03-22 23:30
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('contentcuration', '0146_drop_taskresult_fields'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='formatpreset',
+ name='id',
+ field=models.CharField(choices=[('high_res_video', 'High Resolution'), ('low_res_video', 'Low Resolution'), ('video_thumbnail', 'Thumbnail'), ('video_subtitle', 'Subtitle'), ('video_dependency', 'Video (dependency)'), ('audio', 'Audio'), ('audio_thumbnail', 'Thumbnail'), ('audio_dependency', 'audio (dependency)'), ('document', 'Document'), ('epub', 'ePub Document'), ('document_thumbnail', 'Thumbnail'), ('exercise', 'Exercise'), ('exercise_thumbnail', 'Thumbnail'), ('exercise_image', 'Exercise Image'), ('exercise_graphie', 'Exercise Graphie'), ('channel_thumbnail', 'Channel Thumbnail'), ('topic_thumbnail', 'Thumbnail'), ('html5_zip', 'HTML5 Zip'), ('html5_dependency', 'HTML5 Dependency (Zip format)'), ('html5_thumbnail', 'HTML5 Thumbnail'), ('h5p', 'H5P Zip'), ('h5p_thumbnail', 'H5P Thumbnail'), ('zim', 'Zim'), ('zim_thumbnail', 'Zim Thumbnail'), ('qti', 'QTI Zip'), ('qti_thumbnail', 'QTI Thumbnail'), ('slideshow_image', 'Slideshow Image'), ('slideshow_thumbnail', 'Slideshow Thumbnail'), ('slideshow_manifest', 'Slideshow Manifest'), ('imscp_zip', 'IMSCP Zip')], max_length=150, primary_key=True, serialize=False),
+ ),
+ ]
diff --git a/contentcuration/contentcuration/models.py b/contentcuration/contentcuration/models.py
index eb19955c7f..58b109f155 100644
--- a/contentcuration/contentcuration/models.py
+++ b/contentcuration/contentcuration/models.py
@@ -45,7 +45,6 @@
from django.dispatch import receiver
from django.utils import timezone
from django.utils.translation import gettext as _
-from django_celery_results.models import TaskResult
from django_cte import With
from le_utils import proquint
from le_utils.constants import content_kinds
@@ -66,6 +65,7 @@
from contentcuration.constants import channel_history
from contentcuration.constants import completion_criteria
+from contentcuration.constants import user_history
from contentcuration.constants.contentnode import kind_activity_map
from contentcuration.db.models.expressions import Array
from contentcuration.db.models.functions import ArrayRemove
@@ -200,6 +200,8 @@ class User(AbstractBaseUser, PermissionsMixin):
policies = JSONField(default=dict, null=True)
feature_flags = JSONField(default=dict, null=True)
+ deleted = models.BooleanField(default=False, db_index=True)
+
_field_updates = FieldTracker(fields=[
# Field to watch for changes
"disk_space",
@@ -213,27 +215,67 @@ def __unicode__(self):
return self.email
def delete(self):
+ """
+ Soft deletes the user account.
+ """
+ self.deleted = True
+ # Deactivate the user to disallow authentication and also
+ # to let the user verify the email again after recovery.
+ self.is_active = False
+ self.save()
+ self.history.create(user_id=self.pk, action=user_history.DELETION)
+
+ def recover(self):
+ """
+ Use this method when we want to recover a user.
+ """
+ self.deleted = False
+ self.save()
+ self.history.create(user_id=self.pk, action=user_history.RECOVERY)
+
+ def hard_delete_user_related_data(self):
+ """
+ Hard delete all user related data. But keeps the user record itself intact.
+
+ User related data that gets hard deleted are:
+ - sole editor non-public channels.
+ - sole editor non-public channelsets.
+ - sole editor non-public channels' content nodes and its underlying files that are not
+ used by any other channel.
+ - all user invitations.
+ """
from contentcuration.viewsets.common import SQCount
- # Remove any invitations associated to this account
+
+ # Hard delete invitations associated to this account.
self.sent_to.all().delete()
+ self.sent_by.all().delete()
- # Delete channels associated with this user (if user is the only editor)
- user_query = (
+ editable_channels_user_query = (
User.objects.filter(editable_channels__id=OuterRef('id'))
.values_list('id', flat=True)
.distinct()
)
- self.editable_channels.annotate(num_editors=SQCount(user_query, field="id")).filter(num_editors=1).delete()
+ non_public_channels_sole_editor = self.editable_channels.annotate(num_editors=SQCount(
+ editable_channels_user_query, field="id")).filter(num_editors=1, public=False)
- # Delete channel collections associated with this user (if user is the only editor)
+ # Point sole editor non-public channels' contentnodes to orphan tree to let
+ # our garbage collection delete the nodes and underlying files.
+ ContentNode._annotate_channel_id(ContentNode.objects).filter(channel_id__in=list(
+ non_public_channels_sole_editor.values_list("id", flat=True))).update(parent_id=settings.ORPHANAGE_ROOT_ID)
+
+ # Hard delete non-public channels associated with this user (if user is the only editor).
+ non_public_channels_sole_editor.delete()
+
+ # Hard delete non-public channel collections associated with this user (if user is the only editor).
user_query = (
User.objects.filter(channel_sets__id=OuterRef('id'))
.values_list('id', flat=True)
.distinct()
)
- self.channel_sets.annotate(num_editors=SQCount(user_query, field="id")).filter(num_editors=1).delete()
+ self.channel_sets.annotate(num_editors=SQCount(user_query, field="id")).filter(num_editors=1, public=False).delete()
- super(User, self).delete()
+ # Create history!
+ self.history.create(user_id=self.pk, action=user_history.RELATED_DATA_HARD_DELETION)
def can_edit(self, channel_id):
return Channel.filter_edit_queryset(Channel.objects.all(), self).filter(pk=channel_id).exists()
@@ -405,18 +447,23 @@ def filter_edit_queryset(cls, queryset, user):
return queryset.filter(pk=user.pk)
@classmethod
- def get_for_email(cls, email, **filters):
+ def get_for_email(cls, email, deleted=False, **filters):
"""
Returns the appropriate User record given an email, ordered by:
- those with is_active=True first, which there should only ever be one
- otherwise by ID DESC so most recent inactive shoud be returned
+ Filters out deleted User records by default. To include both deleted and
+ undeleted user records pass None to the deleted argument.
+
:param email: A string of the user's email
:param filters: Additional filters to filter the User queryset
:return: User or None
"""
- return User.objects.filter(email__iexact=email.strip(), **filters)\
- .order_by("-is_active", "-id").first()
+ user_qs = User.objects.filter(email__iexact=email.strip())
+ if deleted is not None:
+ user_qs = user_qs.filter(deleted=deleted)
+ return user_qs.filter(**filters).order_by("-is_active", "-id").first()
class UUIDField(models.CharField):
@@ -1038,6 +1085,16 @@ class Meta:
]
+class UserHistory(models.Model):
+ """
+ Model that stores the user's action history.
+ """
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False, related_name="history", on_delete=models.CASCADE)
+ action = models.CharField(max_length=32, choices=user_history.choices)
+
+ performed_at = models.DateTimeField(default=timezone.now)
+
+
class ChannelSet(models.Model):
# NOTE: this is referred to as "channel collections" on the front-end, but we need to call it
# something else as there is already a ChannelCollection model on the front-end
@@ -1465,13 +1522,12 @@ def get_details(self, channel_id=None):
from contentcuration.viewsets.common import SQCount
from contentcuration.viewsets.common import SQRelatedArrayAgg
from contentcuration.viewsets.common import SQSum
+ from contentcuration.viewsets.common import SQJSONBKeyArrayAgg
node = ContentNode.objects.filter(pk=self.id, tree_id=self.tree_id).order_by()
descendants = (
self.get_descendants()
- .prefetch_related("children", "files", "tags")
- .select_related("license", "language")
.values("id")
)
@@ -1490,17 +1546,19 @@ def get_details(self, channel_id=None):
"resource_size": 0,
"includes": {"coach_content": 0, "exercises": 0},
"kind_count": [],
- "languages": "",
- "accessible_languages": "",
- "licenses": "",
+ "languages": [],
+ "accessible_languages": [],
+ "licenses": [],
"tags": [],
- "copyright_holders": "",
- "authors": "",
- "aggregators": "",
- "providers": "",
+ "copyright_holders": [],
+ "authors": [],
+ "aggregators": [],
+ "providers": [],
"sample_pathway": [],
"original_channels": [],
"sample_nodes": [],
+ "levels": [],
+ "categories": [],
}
# Set cache with latest data
@@ -1595,6 +1653,14 @@ def get_details(self, channel_id=None):
exercises=SQCount(
resources.filter(kind_id=content_kinds.EXERCISE), field="id"
),
+ levels=SQJSONBKeyArrayAgg(
+ descendants.exclude(grade_levels__isnull=True),
+ field="grade_levels",
+ ),
+ all_categories=SQJSONBKeyArrayAgg(
+ descendants.exclude(categories__isnull=True),
+ field="categories",
+ ),
)
# Get sample pathway by getting longest path
@@ -1684,6 +1750,8 @@ def get_details(self, channel_id=None):
"tags_list",
"kind_count",
"exercises",
+ "levels",
+ "all_categories",
)
.first()
)
@@ -1713,6 +1781,8 @@ def get_details(self, channel_id=None):
"aggregators": list(filter(bool, node["aggregators"])),
"providers": list(filter(bool, node["providers"])),
"copyright_holders": list(filter(bool, node["copyright_holders"])),
+ "levels": node.get("levels") or [],
+ "categories": node.get("all_categories") or [],
}
# Set cache with latest data
@@ -1756,12 +1826,15 @@ def mark_complete(self): # noqa C901
if self.kind_id == content_kinds.EXERCISE:
# Check to see if the exercise has at least one assessment item that has:
if not self.assessment_items.filter(
- # A non-blank question
- ~Q(question='')
- # Non-blank answers
- & ~Q(answers='[]')
- # With either an input question or one answer marked as correct
- & (Q(type=exercises.INPUT_QUESTION) | Q(answers__iregex=r'"correct":\s*true'))
+ # Item with non-blank raw data
+ ~Q(raw_data="") | (
+ # A non-blank question
+ ~Q(question='')
+ # Non-blank answers
+ & ~Q(answers='[]')
+ # With either an input question or one answer marked as correct
+ & (Q(type=exercises.INPUT_QUESTION) | Q(answers__iregex=r'"correct":\s*true'))
+ )
).exists():
errors.append("No questions with question text and complete answers")
# Check that it has a mastery model set
@@ -1778,6 +1851,16 @@ def mark_complete(self): # noqa C901
self.complete = not errors
return errors
+ def make_content_id_unique(self):
+ """
+ If self is NOT an original contentnode (in other words, a copied contentnode)
+ and a contentnode with same content_id exists then we update self's content_id.
+ """
+ is_node_original = self.original_source_node_id is None or self.original_source_node_id == self.node_id
+ node_same_content_id = ContentNode.objects.exclude(pk=self.pk).filter(content_id=self.content_id)
+ if (not is_node_original) and node_same_content_id.exists():
+ ContentNode.objects.filter(pk=self.pk).update(content_id=uuid.uuid4().hex)
+
def on_create(self):
self.changed = True
self.recalculate_editors_storage()
@@ -1886,6 +1969,9 @@ def copy_to(
def copy(self):
return self.copy_to()
+ def is_publishable(self):
+ return self.complete and self.get_descendants(include_self=True).exclude(kind_id=content_kinds.TOPIC).exists()
+
class Meta:
verbose_name = "Topic"
verbose_name_plural = "Topics"
@@ -2052,6 +2138,28 @@ def filter_view_queryset(cls, queryset, user):
return queryset.filter(Q(view=True) | Q(edit=True) | Q(public=True))
+ def on_create(self):
+ """
+ When an exercise is added to a contentnode, update its content_id
+ if it's a copied contentnode.
+ """
+ self.contentnode.make_content_id_unique()
+
+ def on_update(self):
+ """
+ When an exercise is updated of a contentnode, update its content_id
+ if it's a copied contentnode.
+ """
+ self.contentnode.make_content_id_unique()
+
+ def delete(self, *args, **kwargs):
+ """
+ When an exercise is deleted from a contentnode, update its content_id
+ if it's a copied contentnode.
+ """
+ self.contentnode.make_content_id_unique()
+ return super(AssessmentItem, self).delete(*args, **kwargs)
+
class SlideshowSlide(models.Model):
contentnode = models.ForeignKey('ContentNode', related_name="slideshow_slides", blank=True, null=True,
@@ -2072,7 +2180,13 @@ class StagedFile(models.Model):
FILE_DISTINCT_INDEX_NAME = "file_checksum_file_size_idx"
FILE_MODIFIED_DESC_INDEX_NAME = "file_modified_desc_idx"
FILE_DURATION_CONSTRAINT = "file_media_duration_int"
-MEDIA_PRESETS = [format_presets.AUDIO, format_presets.VIDEO_HIGH_RES, format_presets.VIDEO_LOW_RES]
+MEDIA_PRESETS = [
+ format_presets.AUDIO,
+ format_presets.AUDIO_DEPENDENCY,
+ format_presets.VIDEO_HIGH_RES,
+ format_presets.VIDEO_LOW_RES,
+ format_presets.VIDEO_DEPENDENCY,
+]
class File(models.Model):
@@ -2169,10 +2283,19 @@ def filename(self):
return os.path.basename(self.file_on_disk.name)
+ def update_contentnode_content_id(self):
+ """
+ If the file is attached to a contentnode and is not a thumbnail
+ then update that contentnode's content_id if it's a copied contentnode.
+ """
+ if self.contentnode and self.preset.thumbnail is False:
+ self.contentnode.make_content_id_unique()
+
def on_update(self):
# since modified was added later as a nullable field to File, we don't use a default but
# instead we'll just make sure it's always updated through our serializers
self.modified = timezone.now()
+ self.update_contentnode_content_id()
def save(self, set_by_file_on_disk=True, *args, **kwargs):
"""
@@ -2215,7 +2338,12 @@ class Meta:
models.Index(fields=["-modified"], name=FILE_MODIFIED_DESC_INDEX_NAME),
]
constraints = [
- models.CheckConstraint(check=(Q(preset__in=MEDIA_PRESETS, duration__gt=0) | Q(duration__isnull=True)), name=FILE_DURATION_CONSTRAINT)
+ # enforces that duration is null when not a media preset, but the duration may be null for media presets
+ # but if not-null, should be greater than 0
+ models.CheckConstraint(
+ check=(Q(preset__in=MEDIA_PRESETS, duration__gt=0) | Q(duration__isnull=True)),
+ name=FILE_DURATION_CONSTRAINT
+ )
]
@@ -2436,39 +2564,29 @@ def serialize_to_change_dict(self):
return self.serialize(self)
-class TaskResultCustom(object):
- """
- Custom fields to add to django_celery_results's TaskResult model
- """
+class CustomTaskMetadata(models.Model):
+ # Task_id for reference
+ task_id = models.CharField(
+ max_length=255, # Adjust the max_length as needed
+ unique=True,
+ )
# user shouldn't be null, but in order to append the field, this needs to be allowed
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="tasks", on_delete=models.CASCADE, null=True)
channel_id = DjangoUUIDField(db_index=True, null=True, blank=True)
progress = models.IntegerField(null=True, blank=True, validators=[MinValueValidator(0), MaxValueValidator(100)])
+ # a hash of the task name and kwargs for identifying repeat tasks
+ signature = models.CharField(null=True, blank=False, max_length=32)
+ date_created = models.DateTimeField(
+ auto_now_add=True,
+ verbose_name=_('Created DateTime'),
+ help_text=_('Datetime field when the custom_metadata for task was created in UTC')
+ )
- super_as_dict = TaskResult.as_dict
-
- def as_dict(self):
- """
- :return: A dictionary representation
- """
- super_dict = self.super_as_dict()
- super_dict.update(
- user_id=self.user_id,
- channel_id=self.channel_id,
- progress=self.progress,
- )
- return super_dict
-
- @classmethod
- def contribute_to_class(cls, model_class=TaskResult):
- """
- Adds fields to model, by default TaskResult
- :param model_class: TaskResult model
- """
- for field in dir(cls):
- if not field.startswith("_"):
- model_class.add_to_class(field, getattr(cls, field))
-
-
-# trigger class contributions immediately
-TaskResultCustom.contribute_to_class()
+ class Meta:
+ indexes = [
+ # add index that matches query usage for signature
+ models.Index(
+ fields=['signature'],
+ name='task_result_signature',
+ ),
+ ]
diff --git a/contentcuration/contentcuration/not_production_settings.py b/contentcuration/contentcuration/not_production_settings.py
index 44c5da8bc5..e98410433d 100644
--- a/contentcuration/contentcuration/not_production_settings.py
+++ b/contentcuration/contentcuration/not_production_settings.py
@@ -1,5 +1,3 @@
-import logging
-
from .settings import * # noqa
ALLOWED_HOSTS = ["studio.local", "192.168.31.9", "127.0.0.1", "*"]
@@ -10,7 +8,6 @@
POSTMARK_TEST_MODE = True
SITE_ID = 2
-logging.basicConfig(level="INFO")
# Allow the debug() context processor to add variables to template context.
# Include here the IPs from which a local dev server might be accessed. See
diff --git a/contentcuration/contentcuration/settings.py b/contentcuration/contentcuration/settings.py
index bb540d3527..4dbb5feaa3 100644
--- a/contentcuration/contentcuration/settings.py
+++ b/contentcuration/contentcuration/settings.py
@@ -88,6 +88,7 @@
'mathfilters',
'django.contrib.postgres',
'django_celery_results',
+ 'kolibri_public',
'channels',
)
@@ -124,8 +125,8 @@
MIDDLEWARE = (
# 'django.middleware.cache.UpdateCacheMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.locale.LocaleMiddleware',
+ 'contentcuration.middleware.session.KolibriStudioSessionMiddleware',
+ 'contentcuration.middleware.locale.KolibriStudioLocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.common.BrokenLinkEmailsMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@@ -341,8 +342,10 @@ def gettext(s):
HELP_EMAIL = 'content@learningequality.org'
DEFAULT_FROM_EMAIL = 'Kolibri Studio '
POLICY_EMAIL = 'legal@learningequality.org'
-ACCOUNT_DELETION_BUFFER = 5 # Used to determine how many days a user
-# has to undo accidentally deleting account
+
+# Used to determine how many days a user
+# has to undo accidentally deleting account.
+ACCOUNT_DELETION_BUFFER = 90
DEFAULT_LICENSE = 1
diff --git a/contentcuration/contentcuration/static/feature_flags.json b/contentcuration/contentcuration/static/feature_flags.json
index 605261b96f..c5567d2451 100644
--- a/contentcuration/contentcuration/static/feature_flags.json
+++ b/contentcuration/contentcuration/static/feature_flags.json
@@ -8,6 +8,11 @@
"title": "Test development feature",
"description": "This no-op feature flag is excluded from non-dev environments",
"$env": "development"
+ },
+ "ai_feature":{
+ "type": "boolean",
+ "title":"Test AI feature",
+ "description": "Allow user access to AI features"
}
},
"examples": [
diff --git a/contentcuration/contentcuration/static/img/kolibri_placeholder.png b/contentcuration/contentcuration/static/img/kolibri_placeholder.png
new file mode 100644
index 0000000000..c12008541e
Binary files /dev/null and b/contentcuration/contentcuration/static/img/kolibri_placeholder.png differ
diff --git a/contentcuration/contentcuration/tests/helpers.py b/contentcuration/contentcuration/tests/helpers.py
index cf1d54130e..73371135be 100644
--- a/contentcuration/contentcuration/tests/helpers.py
+++ b/contentcuration/contentcuration/tests/helpers.py
@@ -2,10 +2,11 @@
from importlib import import_module
import mock
+from celery import states
+from django_celery_results.models import TaskResult
from search.models import ContentNodeFullTextSearch
from contentcuration.models import ContentNode
-from contentcuration.models import TaskResult
def clear_tasks(except_task_id=None):
@@ -22,6 +23,7 @@ def clear_tasks(except_task_id=None):
qs = qs.exclude(task_id=except_task_id)
for task_id in qs.values_list("task_id", flat=True):
app.control.revoke(task_id, terminate=True)
+ qs.update(status=states.REVOKED)
def mock_class_instance(target):
diff --git a/contentcuration/contentcuration/tests/test_asynctask.py b/contentcuration/contentcuration/tests/test_asynctask.py
index e09219f98d..4496680f9c 100644
--- a/contentcuration/contentcuration/tests/test_asynctask.py
+++ b/contentcuration/contentcuration/tests/test_asynctask.py
@@ -1,20 +1,20 @@
from __future__ import absolute_import
-import json
import threading
+import time
import uuid
+import pytest
from celery import states
from celery.result import allow_join_result
from celery.utils.log import get_task_logger
from django.core.management import call_command
from django.test import TransactionTestCase
+from django_celery_results.models import TaskResult
from . import testdata
from .helpers import clear_tasks
from contentcuration.celery import app
-from contentcuration.models import TaskResult
-
logger = get_task_logger(__name__)
@@ -36,6 +36,22 @@ def test_task(self, **kwargs):
return 42
+# Create a Task that takes a bit longer to be executed
+@app.task(bind=True, name="test_task_delayed")
+def test_task_delayed(self, delay_seconds=100, **kwargs):
+ """
+ This is a mock task that takes a bit longer to execute
+ so that revoke function can be called succesfully without test being SUCCESS before hand,
+ to be used ONLY for unit-testing various pieces of the
+ async task API.
+ :return: The number 42
+ """
+ time.sleep(delay_seconds)
+ logger.info("Request ID = {}".format(self.request.id))
+ assert TaskResult.objects.filter(task_id=self.request.id).count() == 1
+ return 42
+
+
@app.task(name="error_test_task")
def error_test_task(**kwargs):
"""
@@ -88,11 +104,8 @@ def _celery_task_worker():
])
-def _celery_progress_monitor(thread_event):
- def _on_iteration(receiver):
- if thread_event.is_set():
- receiver.should_stop = True
- app.events.monitor_progress(on_iteration=_on_iteration)
+def _return_celery_task_object(task_id):
+ return TaskResult.objects.get(task_id=task_id)
class AsyncTaskTestCase(TransactionTestCase):
@@ -108,11 +121,6 @@ class AsyncTaskTestCase(TransactionTestCase):
@classmethod
def setUpClass(cls):
super(AsyncTaskTestCase, cls).setUpClass()
- # start progress monitor in separate thread
- cls.monitor_thread_event = threading.Event()
- cls.monitor_thread = threading.Thread(target=_celery_progress_monitor, args=(cls.monitor_thread_event,))
- cls.monitor_thread.start()
-
# start celery worker in separate thread
cls.worker_thread = threading.Thread(target=_celery_task_worker)
cls.worker_thread.start()
@@ -122,8 +130,6 @@ def tearDownClass(cls):
super(AsyncTaskTestCase, cls).tearDownClass()
# tell the work thread to stop through the celery control API
if cls.worker_thread:
- cls.monitor_thread_event.set()
- cls.monitor_thread.join(5)
app.control.shutdown()
cls.worker_thread.join(5)
@@ -143,21 +149,23 @@ def _wait_for(self, async_result, timeout=30):
with allow_join_result():
return async_result.get(timeout=timeout)
+ def test_app_count_queued_tasks(self):
+ self.assertIsInstance(app.count_queued_tasks(), int)
+
def test_asynctask_reports_success(self):
"""
Tests that when an async task is created and completed, the Task object has a status of 'SUCCESS' and
contains the return value of the task.
"""
async_result = test_task.enqueue(self.user)
-
result = self._wait_for(async_result)
task_result = async_result.get_model()
+ celery_task_result = TaskResult.objects.get(task_id=task_result.task_id)
self.assertEqual(task_result.user, self.user)
-
self.assertEqual(result, 42)
task_result.refresh_from_db()
self.assertEqual(async_result.result, 42)
- self.assertEqual(task_result.task_name, "test_task")
+ self.assertEqual(celery_task_result.task_name, "test_task")
self.assertEqual(async_result.status, states.SUCCESS)
self.assertEqual(TaskResult.objects.get(task_id=async_result.id).result, "42")
self.assertEqual(TaskResult.objects.get(task_id=async_result.id).status, states.SUCCESS)
@@ -174,11 +182,12 @@ def test_asynctask_reports_error(self):
self._wait_for(async_result)
task_result = async_result.get_model()
- self.assertEqual(task_result.status, states.FAILURE)
- self.assertIsNotNone(task_result.traceback)
+ celery_task_result = _return_celery_task_object(task_result.task_id)
+ self.assertEqual(celery_task_result.status, states.FAILURE)
+ self.assertIsNotNone(celery_task_result.traceback)
self.assertIn(
- "I'm sorry Dave, I'm afraid I can't do that.", task_result.result
+ "I'm sorry Dave, I'm afraid I can't do that.", celery_task_result.result
)
def test_only_create_async_task_creates_task_entry(self):
@@ -191,22 +200,43 @@ def test_only_create_async_task_creates_task_entry(self):
self.assertEquals(result, 42)
self.assertEquals(TaskResult.objects.filter(task_id=async_result.task_id).count(), 0)
+ @pytest.mark.skip(reason="This test is flaky on Github Actions")
def test_fetch_or_enqueue_task(self):
- expected_task = TaskResult.objects.create(
- task_id=uuid.uuid4().hex,
- task_name=test_task.name,
- status=states.PENDING,
- user=self.user,
- task_kwargs=json.dumps({
- "is_test": True
- }),
- )
-
+ expected_task = test_task.enqueue(self.user, is_test=True)
async_result = test_task.fetch_or_enqueue(self.user, is_test=True)
self.assertEqual(expected_task.task_id, async_result.task_id)
+ @pytest.mark.skip(reason="This test is flaky on Github Actions")
+ def test_fetch_or_enqueue_task__channel_id(self):
+ channel_id = uuid.uuid4()
+ expected_task = test_task.enqueue(self.user, channel_id=channel_id)
+ async_result = test_task.fetch_or_enqueue(self.user, channel_id=channel_id)
+ self.assertEqual(expected_task.task_id, async_result.task_id)
+
+ @pytest.mark.skip(reason="This test is flaky on Github Actions")
+ def test_fetch_or_enqueue_task__channel_id__hex(self):
+ channel_id = uuid.uuid4()
+ expected_task = test_task.enqueue(self.user, channel_id=channel_id.hex)
+ async_result = test_task.fetch_or_enqueue(self.user, channel_id=channel_id.hex)
+ self.assertEqual(expected_task.task_id, async_result.task_id)
+
+ @pytest.mark.skip(reason="This test is flaky on Github Actions")
+ def test_fetch_or_enqueue_task__channel_id__hex_then_uuid(self):
+ channel_id = uuid.uuid4()
+ expected_task = test_task.enqueue(self.user, channel_id=channel_id.hex)
+ async_result = test_task.fetch_or_enqueue(self.user, channel_id=channel_id)
+ self.assertEqual(expected_task.task_id, async_result.task_id)
+
+ @pytest.mark.skip(reason="This test is flaky on Github Actions")
+ def test_fetch_or_enqueue_task__channel_id__uuid_then_hex(self):
+ channel_id = uuid.uuid4()
+ expected_task = test_task.enqueue(self.user, channel_id=channel_id)
+ async_result = test_task.fetch_or_enqueue(self.user, channel_id=channel_id.hex)
+ self.assertEqual(expected_task.task_id, async_result.task_id)
+
def test_requeue_task(self):
- existing_task_ids = requeue_test_task.find_ids()
+ signature = requeue_test_task.generate_signature({})
+ existing_task_ids = requeue_test_task.find_ids(signature)
self.assertEqual(len(existing_task_ids), 0)
first_async_result = requeue_test_task.enqueue(self.user, requeue=True)
@@ -220,3 +250,20 @@ def test_requeue_task(self):
second_result = self._wait_for(second_async_result)
self.assertIsNone(second_result)
self.assertTrue(second_async_result.successful())
+
+ def test_revoke_task(self):
+ channel_id = uuid.uuid4()
+ async_result = test_task_delayed.enqueue(self.user, channel_id=channel_id)
+ # A bit delay to let the task object be saved async,
+ # This delay is relatively small and hopefully wont cause any issues in the real time
+ time.sleep(0.5)
+ test_task_delayed.revoke(channel_id=channel_id)
+
+ # this should raise an exception, even though revoked, because the task is in ready state but not success
+ with self.assertRaises(Exception):
+ self._wait_for(async_result)
+
+ try:
+ TaskResult.objects.get(task_id=async_result.task_id, status=states.REVOKED)
+ except TaskResult.DoesNotExist:
+ self.fail('Missing revoked task result')
diff --git a/contentcuration/contentcuration/tests/test_contentnodes.py b/contentcuration/contentcuration/tests/test_contentnodes.py
index 25569f8988..57496362cf 100644
--- a/contentcuration/contentcuration/tests/test_contentnodes.py
+++ b/contentcuration/contentcuration/tests/test_contentnodes.py
@@ -78,7 +78,7 @@ def _check_files_for_object(source, copy):
def _check_tags_for_node(source, copy):
- source_tags = source.tags.all().order_by("tag_name")
+ source_tags = source.tags.all().order_by("tag_name").distinct("tag_name")
copy_tags = copy.tags.all().order_by("tag_name")
assert len(source_tags) == len(copy_tags)
for source_tag, copy_tag in zip(source_tags, copy_tags):
@@ -184,10 +184,15 @@ def test_get_node_details(self):
assert details["resource_count"] > 0
assert details["resource_size"] > 0
assert len(details["kind_count"]) > 0
- assert len(details["authors"]) == len([author for author in details["authors"] if author])
- assert len(details["aggregators"]) == len([aggregator for aggregator in details["aggregators"] if aggregator])
- assert len(details["providers"]) == len([provider for provider in details["providers"] if provider])
- assert len(details["copyright_holders"]) == len([holder for holder in details["copyright_holders"] if holder])
+
+ # assert format of list fields, including that they do not contain invalid data
+ list_fields = [
+ "kind_count", "languages", "accessible_languages", "licenses", "tags", "original_channels",
+ "authors", "aggregators", "providers", "copyright_holders"
+ ]
+ for field in list_fields:
+ self.assertIsInstance(details.get(field), list, f"Field '{field}' isn't a list")
+ self.assertEqual(len(details[field]), len([value for value in details[field] if value]), f"List field '{field}' has falsy values")
class NodeOperationsTestCase(StudioTestCase):
@@ -410,6 +415,40 @@ def test_duplicate_nodes_with_tags(self):
num_test_tags_before, ContentTag.objects.filter(tag_name="test").count()
)
+ def test_duplicate_nodes_with_duplicate_tags(self):
+ """
+ Ensures that when we copy nodes with duplicated tags they get copied
+ """
+ new_channel = testdata.channel()
+
+ tree = TreeBuilder(tags=True)
+ self.channel.main_tree = tree.root
+ self.channel.save()
+
+ # Add a legacy tag with a set channel to test the tag copying behaviour.
+ legacy_tag = ContentTag.objects.create(tag_name="test", channel=self.channel)
+ # Add an identical tag without a set channel to make sure it gets reused.
+ identical_tag = ContentTag.objects.create(tag_name="test")
+
+ num_test_tags_before = ContentTag.objects.filter(tag_name="test").count()
+
+ # Add both the legacy and the new style tag and ensure that it doesn't break.
+ self.channel.main_tree.get_children().first().tags.add(legacy_tag)
+ self.channel.main_tree.get_children().first().tags.add(identical_tag)
+
+ self.channel.main_tree.copy_to(new_channel.main_tree, batch_size=1000)
+
+ _check_node_copy(
+ self.channel.main_tree,
+ new_channel.main_tree.get_children().last(),
+ original_channel_id=self.channel.id,
+ channel=new_channel,
+ )
+
+ self.assertEqual(
+ num_test_tags_before, ContentTag.objects.filter(tag_name="test").count()
+ )
+
def test_duplicate_nodes_deep(self):
"""
Ensures that when we copy nodes in a deep way, a full copy happens
@@ -528,8 +567,7 @@ def test_duplicate_nodes_freeze_authoring_data_no_edit(self):
def test_duplicate_nodes_no_freeze_authoring_data_edit(self):
"""
- Ensures that when we copy nodes, we can exclude nodes from the descendant
- hierarchy
+ Ensures that when we copy nodes, we can modify fields if they are not frozen for editing
"""
new_channel = testdata.channel()
@@ -548,8 +586,7 @@ def test_duplicate_nodes_no_freeze_authoring_data_edit(self):
def test_duplicate_nodes_freeze_authoring_data_edit(self):
"""
- Ensures that when we copy nodes, we can exclude nodes from the descendant
- hierarchy
+ Ensures that when we copy nodes, we can't modify fields if they are frozen for editing
"""
new_channel = testdata.channel()
@@ -748,8 +785,8 @@ def test_sync_after_no_changes(self):
orig_video, cloned_video = self._setup_original_and_deriative_nodes()
sync_node(
cloned_video,
- sync_attributes=True,
- sync_tags=True,
+ sync_titles_and_descriptions=True,
+ sync_resource_details=True,
sync_files=True,
sync_assessment_items=True,
)
@@ -762,8 +799,8 @@ def test_sync_with_subs(self):
self._add_subs_to_video_node(orig_video, "en")
sync_node(
cloned_video,
- sync_attributes=True,
- sync_tags=True,
+ sync_titles_and_descriptions=True,
+ sync_resource_details=True,
sync_files=True,
sync_assessment_items=True,
)
@@ -776,8 +813,8 @@ def test_resync_after_more_subs_added(self):
self._add_subs_to_video_node(orig_video, "en")
sync_node(
cloned_video,
- sync_attributes=True,
- sync_tags=True,
+ sync_titles_and_descriptions=True,
+ sync_resource_details=True,
sync_files=True,
sync_assessment_items=True,
)
@@ -785,8 +822,8 @@ def test_resync_after_more_subs_added(self):
self._add_subs_to_video_node(orig_video, "zul")
sync_node(
cloned_video,
- sync_attributes=True,
- sync_tags=True,
+ sync_titles_and_descriptions=True,
+ sync_resource_details=True,
sync_files=True,
sync_assessment_items=True,
)
@@ -1061,6 +1098,15 @@ def test_create_exercise_valid_assessment_items(self):
new_obj.mark_complete()
self.assertTrue(new_obj.complete)
+ def test_create_exercise_valid_assessment_items_raw_data(self):
+ licenses = list(License.objects.filter(copyright_holder_required=False, is_custom=False).values_list("pk", flat=True))
+ channel = testdata.channel()
+ new_obj = ContentNode(title="yes", kind_id=content_kinds.EXERCISE, parent=channel.main_tree, license_id=licenses[0], extra_fields=self.new_extra_fields)
+ new_obj.save()
+ AssessmentItem.objects.create(contentnode=new_obj, raw_data="{\"question\": {}}")
+ new_obj.mark_complete()
+ self.assertTrue(new_obj.complete)
+
def test_create_exercise_no_extra_fields(self):
licenses = list(License.objects.filter(copyright_holder_required=False, is_custom=False).values_list("pk", flat=True))
channel = testdata.channel()
diff --git a/contentcuration/contentcuration/tests/test_decorators.py b/contentcuration/contentcuration/tests/test_decorators.py
index c3fb08e0eb..2c795716d7 100644
--- a/contentcuration/contentcuration/tests/test_decorators.py
+++ b/contentcuration/contentcuration/tests/test_decorators.py
@@ -2,17 +2,22 @@
from contentcuration.decorators import delay_user_storage_calculation
from contentcuration.tests.base import StudioTestCase
+from contentcuration.tests.base import testdata
from contentcuration.utils.user import calculate_user_storage
class DecoratorsTestCase(StudioTestCase):
+ def setUp(self):
+ super(DecoratorsTestCase, self).setUp()
+ self.user = testdata.user()
+
@mock.patch("contentcuration.utils.user.calculate_user_storage_task")
def test_delay_storage_calculation(self, mock_task):
@delay_user_storage_calculation
def do_test():
- calculate_user_storage(self.admin_user.id)
- calculate_user_storage(self.admin_user.id)
+ calculate_user_storage(self.user.id)
+ calculate_user_storage(self.user.id)
mock_task.fetch_or_enqueue.assert_not_called()
do_test()
- mock_task.fetch_or_enqueue.assert_called_once_with(self.admin_user, user_id=self.admin_user.id)
+ mock_task.fetch_or_enqueue.assert_called_once_with(self.user, user_id=self.user.id)
diff --git a/contentcuration/contentcuration/tests/test_exportchannel.py b/contentcuration/contentcuration/tests/test_exportchannel.py
index 0e2a2bf398..36e331c713 100644
--- a/contentcuration/contentcuration/tests/test_exportchannel.py
+++ b/contentcuration/contentcuration/tests/test_exportchannel.py
@@ -1,6 +1,5 @@
from __future__ import absolute_import
-import json
import os
import random
import string
@@ -10,6 +9,7 @@
from django.core.management import call_command
from django.db import connections
from kolibri_content import models as kolibri_models
+from kolibri_content.router import cleanup_content_database_connection
from kolibri_content.router import get_active_content_database
from kolibri_content.router import set_active_content_database
from le_utils.constants import exercises
@@ -27,7 +27,9 @@
from .testdata import create_studio_file
from .testdata import node as create_node
from .testdata import slideshow
+from .testdata import thumbnail_bytes
from contentcuration import models as cc
+from contentcuration.utils.publish import ChannelIncompleteError
from contentcuration.utils.publish import convert_channel_thumbnail
from contentcuration.utils.publish import create_content_database
from contentcuration.utils.publish import create_slideshow_manifest
@@ -39,9 +41,6 @@
pytestmark = pytest.mark.django_db
-thumbnail_bytes = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01\r\n-\xb4\x00\x00\x00\x00IEND\xaeB`\x82' # noqa E501
-
-
def description():
return "".join(random.sample(string.printable, 20))
@@ -120,6 +119,18 @@ def setUp(self):
new_exercise.complete = True
new_exercise.parent = current_exercise.parent
new_exercise.save()
+
+ bad_container = create_node({'kind_id': 'topic', 'title': 'Bad topic container', 'children': []})
+ bad_container.complete = True
+ bad_container.parent = self.content_channel.main_tree
+ bad_container.save()
+
+ # exercise without mastery model, but marked as complete
+ broken_exercise = create_node({'kind_id': 'exercise', 'title': 'Bad mastery test', 'extra_fields': {}})
+ broken_exercise.complete = True
+ broken_exercise.parent = bad_container
+ broken_exercise.save()
+
thumbnail_data = create_studio_file(thumbnail_bytes, preset="exercise_thumbnail", ext="png")
file_obj = thumbnail_data["db_file"]
file_obj.contentnode = new_exercise
@@ -207,8 +218,7 @@ def setUp(self):
def tearDown(self):
# Clean up datbase connection after the test
- connections[self.tempdb].close()
- del connections.databases[self.tempdb]
+ cleanup_content_database_connection(self.tempdb)
super(ExportChannelTestCase, self).tearDown()
set_active_content_database(None)
if os.path.exists(self.tempdb):
@@ -250,6 +260,9 @@ def test_contentnode_incomplete_not_published(self):
for node in incomplete_nodes:
assert kolibri_nodes.filter(pk=node.node_id).count() == 0
+ # bad exercise node should not be published (technically incomplete)
+ assert kolibri_models.ContentNode.objects.filter(title='Bad mastery test').count() == 0
+
def test_tags_greater_than_30_excluded(self):
tag_node = kolibri_models.ContentNode.objects.filter(title='kolibri tag test').first()
published_tags = tag_node.tags.all()
@@ -265,6 +278,14 @@ def test_duration_override_on_completion_criteria_time(self):
assert completion_criteria_node.duration == 20
assert non_completion_criteria_node.duration == 100
+ def test_completion_criteria_set(self):
+ completion_criteria_node = kolibri_models.ContentNode.objects.filter(title='Completion criteria test').first()
+
+ self.assertEqual(completion_criteria_node.options["completion_criteria"], {
+ "model": "time",
+ "threshold": 20
+ })
+
def test_contentnode_channel_id_data(self):
channel = kolibri_models.ChannelMetadata.objects.first()
nodes = kolibri_models.ContentNode.objects.all()
@@ -296,8 +317,8 @@ def test_channel_icon_encoding(self):
def test_assessment_metadata(self):
for i, exercise in enumerate(kolibri_models.ContentNode.objects.filter(kind="exercise")):
asm = exercise.assessmentmetadata.first()
- self.assertTrue(isinstance(json.loads(asm.assessment_item_ids), list))
- mastery = json.loads(asm.mastery_model)
+ self.assertTrue(isinstance(asm.assessment_item_ids, list))
+ mastery = asm.mastery_model
self.assertTrue(isinstance(mastery, dict))
self.assertEqual(mastery["type"], exercises.DO_ALL if i == 0 else exercises.M_OF_N)
self.assertEqual(mastery["m"], 3 if i == 0 else 1)
@@ -392,6 +413,12 @@ def test_publish_no_modify_exercise_extra_fields(self):
"n": 2,
"mastery_model": exercises.M_OF_N,
})
+ published_exercise = kolibri_models.ContentNode.objects.get(title="Mastery test")
+ self.assertEqual(published_exercise.options["completion_criteria"]["threshold"], {
+ "m": 1,
+ "n": 2,
+ "mastery_model": exercises.M_OF_N,
+ })
def test_publish_no_modify_legacy_exercise_extra_fields(self):
current_exercise = cc.ContentNode.objects.get(title="Legacy Mastery test")
@@ -403,6 +430,29 @@ def test_publish_no_modify_legacy_exercise_extra_fields(self):
})
+class EmptyChannelTestCase(StudioTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super(EmptyChannelTestCase, cls).setUpClass()
+ cls.patch_copy_db = patch('contentcuration.utils.publish.save_export_database')
+ cls.patch_copy_db.start()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(EmptyChannelTestCase, cls).tearDownClass()
+ cls.patch_copy_db.stop()
+
+ def test_publish_empty_channel(self):
+ content_channel = channel()
+ set_channel_icon_encoding(content_channel)
+ content_channel.main_tree.complete = True
+ content_channel.main_tree.save()
+ content_channel.main_tree.get_descendants().exclude(kind_id="topic").delete()
+ with self.assertRaises(ChannelIncompleteError):
+ create_content_database(content_channel, True, self.admin_user.id, True)
+
+
class ChannelExportUtilityFunctionTestCase(StudioTestCase):
@classmethod
def setUpClass(cls):
diff --git a/contentcuration/contentcuration/tests/test_models.py b/contentcuration/contentcuration/tests/test_models.py
index 3c0caa9967..d53be0176b 100644
--- a/contentcuration/contentcuration/tests/test_models.py
+++ b/contentcuration/contentcuration/tests/test_models.py
@@ -5,15 +5,18 @@
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
+from django.db.models import Q
from django.db.utils import IntegrityError
from django.utils import timezone
from le_utils.constants import content_kinds
from le_utils.constants import format_presets
from contentcuration.constants import channel_history
+from contentcuration.constants import user_history
from contentcuration.models import AssessmentItem
from contentcuration.models import Channel
from contentcuration.models import ChannelHistory
+from contentcuration.models import ChannelSet
from contentcuration.models import ContentNode
from contentcuration.models import CONTENTNODE_TREE_ID_CACHE_KEY
from contentcuration.models import File
@@ -22,6 +25,7 @@
from contentcuration.models import Invitation
from contentcuration.models import object_storage_name
from contentcuration.models import User
+from contentcuration.models import UserHistory
from contentcuration.tests import testdata
from contentcuration.tests.base import StudioTestCase
@@ -439,6 +443,31 @@ def test_filter_by_pk__tree_id_updated_on_move(self):
self.assertEqual(after_move_sourcenode.tree_id, testchannel.trash_tree.tree_id)
self.assertEqual(tree_id_from_cache, testchannel.trash_tree.tree_id)
+ def test_make_content_id_unique(self):
+ channel_original = testdata.channel()
+ channel_importer = testdata.channel()
+
+ # Import a node from a channel.
+ original_node = channel_original.main_tree.get_descendants().first()
+ copied_node = original_node.copy_to(target=channel_importer.main_tree)
+
+ original_node.refresh_from_db()
+ copied_node.refresh_from_db()
+
+ original_node_old_content_id = original_node.content_id
+ copied_node_old_content_id = copied_node.content_id
+
+ original_node.make_content_id_unique()
+ copied_node.make_content_id_unique()
+
+ original_node.refresh_from_db()
+ copied_node.refresh_from_db()
+
+ # Assert that original node's content_id doesn't change.
+ self.assertEqual(original_node_old_content_id, original_node.content_id)
+ # Assert copied node's content_id changes.
+ self.assertNotEqual(copied_node_old_content_id, copied_node.content_id)
+
class AssessmentItemTestCase(PermissionQuerysetTestCase):
@property
@@ -778,6 +807,51 @@ def _create_user(self, email, password='password', is_active=True):
user.save()
return user
+ def _setup_user_related_data(self):
+ user_a = self._create_user("a@tester.com")
+ user_b = self._create_user("b@tester.com")
+
+ # Create a sole editor non-public channel.
+ sole_editor_channel = Channel.objects.create(name="sole-editor")
+ sole_editor_channel.editors.add(user_a)
+
+ # Create sole-editor channel nodes.
+ for i in range(0, 3):
+ testdata.node({
+ "title": "sole-editor-channel-node",
+ "kind_id": "video",
+ }, parent=sole_editor_channel.main_tree)
+
+ # Create a sole editor public channel.
+ public_channel = testdata.channel("public")
+ public_channel.editors.add(user_a)
+ public_channel.public = True
+ public_channel.save()
+
+ # Create a shared channel.
+ shared_channel = testdata.channel("shared-channel")
+ shared_channel.editors.add(user_a)
+ shared_channel.editors.add(user_b)
+
+ # Invitations.
+ Invitation.objects.create(sender_id=user_a.id, invited_id=user_b.id)
+ Invitation.objects.create(sender_id=user_b.id, invited_id=user_a.id)
+
+ # Channel sets.
+ channel_set = ChannelSet.objects.create(name="sole-editor")
+ channel_set.editors.add(user_a)
+
+ channel_set = ChannelSet.objects.create(name="public")
+ channel_set.editors.add(user_a)
+ channel_set.public = True
+ channel_set.save()
+
+ channel_set = ChannelSet.objects.create(name="shared-channelset")
+ channel_set.editors.add(user_a)
+ channel_set.editors.add(user_b)
+
+ return user_a
+
def test_unique_lower_email(self):
self._create_user("tester@tester.com")
with self.assertRaises(IntegrityError):
@@ -787,6 +861,7 @@ def test_get_for_email(self):
user1 = self._create_user("tester@tester.com", is_active=False)
user2 = self._create_user("tester@Tester.com", is_active=False)
user3 = self._create_user("Tester@Tester.com", is_active=True)
+ user4 = self._create_user("testing@test.com", is_active=True)
# active should be returned first
self.assertEqual(user3, User.get_for_email("tester@tester.com"))
@@ -801,6 +876,63 @@ def test_get_for_email(self):
# ensure nothing found doesn't error
self.assertIsNone(User.get_for_email("tester@tester.com"))
+ # ensure we don't return soft-deleted users
+ user4.delete()
+ self.assertIsNone(User.get_for_email("testing@test.com"))
+
+ def test_delete(self):
+ user = self._create_user("tester@tester.com")
+ user.delete()
+
+ # Sets deleted?
+ self.assertEqual(user.deleted, True)
+ # Sets is_active to False?
+ self.assertEqual(user.is_active, False)
+ # Creates user history?
+ user_delete_history = UserHistory.objects.filter(user_id=user.id, action=user_history.DELETION).first()
+ self.assertIsNotNone(user_delete_history)
+
+ def test_recover(self):
+ user = self._create_user("tester@tester.com")
+ user.delete()
+ user.recover()
+
+ # Sets deleted to False?
+ self.assertEqual(user.deleted, False)
+ # Keeps is_active to False?
+ self.assertEqual(user.is_active, False)
+ # Creates user history?
+ user_recover_history = UserHistory.objects.filter(user_id=user.id, action=user_history.RECOVERY).first()
+ self.assertIsNotNone(user_recover_history)
+
+ def test_hard_delete_user_related_data(self):
+ user = self._setup_user_related_data()
+ user.hard_delete_user_related_data()
+
+ # Deletes sole-editor channels.
+ self.assertFalse(Channel.objects.filter(name="sole-editor").exists())
+ # Preserves shared channels.
+ self.assertTrue(Channel.objects.filter(name="shared-channel").exists())
+ # Preserves public channels.
+ self.assertTrue(Channel.objects.filter(name="public").exists())
+
+ # Deletes all user related invitations.
+ self.assertFalse(Invitation.objects.filter(Q(sender_id=user.id) | Q(invited_id=user.id)).exists())
+
+ # Deletes sole-editor channelsets.
+ self.assertFalse(ChannelSet.objects.filter(name="sole-editor").exists())
+ # Preserves shared channelsets.
+ self.assertTrue(ChannelSet.objects.filter(name="shared-channelset").exists())
+ # Preserves public channelsets.
+ self.assertTrue(ChannelSet.objects.filter(name="public").exists())
+
+ # All contentnodes of sole-editor channel points to ORPHANGE ROOT NODE?
+ self.assertFalse(ContentNode.objects.filter(~Q(parent_id=settings.ORPHANAGE_ROOT_ID)
+ & Q(title="sole-editor-channel-node")).exists())
+ # Creates user history?
+ user_hard_delete_history = UserHistory.objects.filter(user_id=user.id, action=user_history.RELATED_DATA_HARD_DELETION).first()
+ self.assertIsNotNone(user_hard_delete_history)
+
class ChannelHistoryTestCase(StudioTestCase):
def setUp(self):
diff --git a/contentcuration/contentcuration/tests/test_settings.py b/contentcuration/contentcuration/tests/test_settings.py
index c216f088a2..25bbc71a54 100644
--- a/contentcuration/contentcuration/tests/test_settings.py
+++ b/contentcuration/contentcuration/tests/test_settings.py
@@ -1,5 +1,6 @@
import json
+import mock
from django.urls import reverse_lazy
from .base import BaseAPITestCase
@@ -10,7 +11,7 @@
class SettingsTestCase(BaseAPITestCase):
def test_username_change(self):
- data = json.dumps({"first_name": "New firstname", "last_name": "New lastname",})
+ data = json.dumps({"first_name": "New firstname", "last_name": "New lastname", })
request = self.create_post_request(
reverse_lazy("update_user_full_name"),
data=data,
@@ -40,13 +41,19 @@ def test_delete_account_invalid(self):
self.assertTrue(User.objects.filter(email=self.user.email).exists())
def test_delete_account(self):
- # TODO: send_email causes connection errors
- data = json.dumps({"email": self.user.email})
- self.create_post_request(
- reverse_lazy("delete_user_account"),
- data=data,
- content_type="application/json",
- )
- # response = DeleteAccountView.as_view()(request)
- # self.assertEqual(response.status_code, 200)
- # self.assertFalse(User.objects.filter(email=self.user.email).exists())
+ with mock.patch("contentcuration.views.users.djangologout") as djangologout:
+ self.user.delete = mock.Mock()
+ data = json.dumps({"email": self.user.email})
+ request = self.create_post_request(
+ reverse_lazy("delete_user_account"),
+ data=data,
+ content_type="application/json",
+ )
+ response = DeleteAccountView.as_view()(request)
+
+ # Ensure successful response.
+ self.assertEqual(response.status_code, 200)
+ # Ensure user's delete method is called.
+ self.user.delete.assert_called_once()
+ # Ensure we logout the user.
+ djangologout.assert_called_once_with(request)
diff --git a/contentcuration/contentcuration/tests/test_sync.py b/contentcuration/contentcuration/tests/test_sync.py
index 5948ea0d06..b03d34b4c6 100644
--- a/contentcuration/contentcuration/tests/test_sync.py
+++ b/contentcuration/contentcuration/tests/test_sync.py
@@ -1,14 +1,33 @@
from __future__ import absolute_import
+import uuid
+
+from django.urls import reverse
from le_utils.constants import content_kinds
+from le_utils.constants import file_formats
+from le_utils.constants import format_presets
+from le_utils.constants.labels import accessibility_categories
+from le_utils.constants.labels import learning_activities
+from le_utils.constants.labels import levels
+from le_utils.constants.labels import needs
+from le_utils.constants.labels import resource_type
+from le_utils.constants.labels import subjects
from .base import StudioTestCase
from .testdata import create_temp_file
from contentcuration.models import AssessmentItem
from contentcuration.models import Channel
from contentcuration.models import ContentTag
+from contentcuration.models import File
+from contentcuration.tests import testdata
+from contentcuration.tests.base import StudioAPITestCase
+from contentcuration.tests.viewsets.base import generate_create_event
+from contentcuration.tests.viewsets.base import generate_update_event
+from contentcuration.tests.viewsets.base import SyncTestMixin
from contentcuration.utils.publish import mark_all_nodes_as_published
from contentcuration.utils.sync import sync_channel
+from contentcuration.viewsets.sync.constants import ASSESSMENTITEM
+from contentcuration.viewsets.sync.constants import FILE
class SyncTestCase(StudioTestCase):
@@ -62,8 +81,8 @@ def test_sync_channel_noop(self):
sync_channel(
self.derivative_channel,
- sync_attributes=True,
- sync_tags=True,
+ sync_titles_and_descriptions=True,
+ sync_resource_details=True,
sync_files=True,
sync_assessment_items=True,
)
@@ -106,6 +125,31 @@ def test_sync_files_add(self):
)
self.assertTrue(self.derivative_channel.has_changes())
+ def test_sync_files_remove(self):
+ """
+ Tests whether sync_files remove additional files from the copied node or not.
+ """
+ video_node = (self.channel.main_tree.get_descendants()
+ .filter(kind_id=content_kinds.VIDEO)
+ .first()
+ )
+ video_node_copy = self.derivative_channel.main_tree.get_descendants().get(
+ source_node_id=video_node.node_id
+ )
+
+ self.assertEqual(video_node.files.count(), video_node_copy.files.count())
+
+ self._add_temp_file_to_content_node(video_node_copy)
+
+ self.assertNotEqual(video_node.files.count(), video_node_copy.files.count())
+
+ sync_channel(channel=self.derivative_channel, sync_files=True)
+
+ self.assertEqual(video_node.files.count(), video_node_copy.files.count())
+
+ for file in File.objects.filter(contentnode=video_node.id):
+ self.assertTrue(video_node_copy.files.filter(checksum=file.checksum).exists())
+
def test_sync_assessment_item_add(self):
"""
Test that calling sync_assessment_items successfully syncs a file added to the original node to
@@ -189,7 +233,7 @@ def test_sync_tags_add(self):
target_child.tags.count(), contentnode.tags.count()
)
- sync_channel(self.derivative_channel, sync_tags=True)
+ sync_channel(self.derivative_channel, sync_resource_details=True)
self.derivative_channel.main_tree.refresh_from_db()
self.assertEqual(
@@ -239,7 +283,7 @@ def test_sync_tags_add_multiple_tags(self):
target_child.tags.count(), contentnode.tags.count()
)
try:
- sync_channel(self.derivative_channel, sync_tags=True)
+ sync_channel(self.derivative_channel, sync_resource_details=True)
except Exception as e:
self.fail("Could not run sync_channel without raising exception: {}".format(e))
self.derivative_channel.main_tree.refresh_from_db()
@@ -256,3 +300,211 @@ def test_sync_tags_add_multiple_tags(self):
)
self.assertTrue(self.derivative_channel.has_changes())
+
+ def test_sync_channel_titles_and_descriptions(self):
+ """
+ Test that calling sync channel will update titles and descriptions.
+ """
+
+ self.assertFalse(self.channel.has_changes())
+ self.assertFalse(self.derivative_channel.has_changes())
+
+ labels = {
+ "title": "Some channel title",
+ "description": "Some channel description",
+ }
+
+ contentnode = (
+ self.channel.main_tree.get_descendants()
+ .exclude(kind_id=content_kinds.TOPIC)
+ .first()
+ )
+
+ target_child = self.derivative_channel.main_tree.get_descendants().get(
+ source_node_id=contentnode.node_id
+ )
+
+ self.assertIsNotNone(target_child)
+
+ for key, value in labels.items():
+ setattr(contentnode, key, value)
+ contentnode.save()
+
+ sync_channel(
+ self.derivative_channel,
+ sync_titles_and_descriptions=True,
+ sync_resource_details=False,
+ sync_files=False,
+ sync_assessment_items=False,
+ )
+
+ self.assertTrue(self.channel.has_changes())
+ self.assertTrue(self.derivative_channel.has_changes())
+
+ target_child.refresh_from_db()
+
+ for key, value in labels.items():
+ self.assertEqual(getattr(target_child, key), value)
+
+ def test_sync_channel_other_metadata_labels(self):
+ """
+ Test that calling sync channel will also bring in other metadata label updates.
+ """
+
+ self.assertFalse(self.channel.has_changes())
+ self.assertFalse(self.derivative_channel.has_changes())
+
+ labels = {
+ "categories": subjects.MATHEMATICS,
+ "learner_needs": needs.PRIOR_KNOWLEDGE,
+ "accessibility_labels": accessibility_categories.CAPTIONS_SUBTITLES,
+ "grade_levels": levels.LOWER_SECONDARY,
+ "resource_types": resource_type.LESSON_PLAN,
+ "learning_activities": learning_activities.LISTEN,
+ }
+
+ contentnode = (
+ self.channel.main_tree.get_descendants()
+ .exclude(kind_id=content_kinds.TOPIC)
+ .first()
+ )
+
+ target_child = self.derivative_channel.main_tree.get_descendants().get(
+ source_node_id=contentnode.node_id
+ )
+
+ self.assertIsNotNone(target_child)
+
+ for key, value in labels.items():
+ setattr(contentnode, key, {value: True})
+ contentnode.save()
+
+ sync_channel(
+ self.derivative_channel,
+ sync_titles_and_descriptions=False,
+ sync_resource_details=True,
+ sync_files=False,
+ sync_assessment_items=False,
+ )
+
+ self.assertTrue(self.channel.has_changes())
+ self.assertTrue(self.derivative_channel.has_changes())
+
+ target_child.refresh_from_db()
+
+ for key, value in labels.items():
+ self.assertEqual(getattr(target_child, key), {value: True})
+
+
+class ContentIDTestCase(SyncTestMixin, StudioAPITestCase):
+ def setUp(self):
+ super(ContentIDTestCase, self).setUp()
+ self.channel = testdata.channel()
+ self.user = testdata.user()
+ self.channel.editors.add(self.user)
+ self.client.force_authenticate(user=self.user)
+
+ def _get_assessmentitem_metadata(self, assessment_id=None, contentnode_id=None):
+ return {
+ "assessment_id": assessment_id or uuid.uuid4().hex,
+ "contentnode_id": contentnode_id or self.channel.main_tree.get_descendants()
+ .filter(kind_id=content_kinds.EXERCISE)
+ .first()
+ .id,
+ }
+
+ def _get_file_metadata(self):
+ return {
+ "size": 2500,
+ "checksum": uuid.uuid4().hex,
+ "name": "le_studio_file",
+ "file_format": file_formats.MP3,
+ "preset": format_presets.AUDIO,
+ }
+
+ def _upload_file_to_contentnode(self, file_metadata=None, contentnode_id=None):
+ """
+ This method mimics the frontend file upload process which is a two-step
+ process for the backend.
+ First, file's upload URL is fetched and then that file's ORM object is updated
+ to point to the contentnode.
+ """
+ file = file_metadata or self._get_file_metadata()
+ self.client.post(reverse("file-upload-url"), file, format="json",)
+ file_from_db = File.objects.get(checksum=file["checksum"])
+ self.sync_changes(
+ [generate_update_event(
+ file_from_db.id,
+ FILE,
+ {
+ "contentnode": contentnode_id or self.channel.main_tree.get_descendants().first().id
+ },
+ channel_id=self.channel.id)],)
+ file_from_db.refresh_from_db()
+ return file_from_db
+
+ def _create_assessmentitem(self, assessmentitem, channel_id):
+ self.sync_changes(
+ [
+ generate_create_event(
+ [assessmentitem["contentnode_id"], assessmentitem["assessment_id"]],
+ ASSESSMENTITEM,
+ assessmentitem,
+ channel_id=channel_id,
+ )
+ ],
+ )
+
+ def test_content_id__becomes_equal_on_channel_sync_assessment_item(self):
+ # Make a copy of an existing assessmentitem contentnode.
+ assessmentitem_node = self.channel.main_tree.get_descendants().filter(kind_id=content_kinds.EXERCISE).first()
+ assessmentitem_node_copy = assessmentitem_node.copy_to(target=self.channel.main_tree)
+
+ # Create a new assessmentitem.
+ self._create_assessmentitem(
+ assessmentitem=self._get_assessmentitem_metadata(contentnode_id=assessmentitem_node_copy.id),
+ channel_id=self.channel.id
+ )
+
+ # Assert after creating a new assessmentitem on copied node, it's content_id is changed.
+ assessmentitem_node.refresh_from_db()
+ assessmentitem_node_copy.refresh_from_db()
+ self.assertNotEqual(assessmentitem_node.content_id, assessmentitem_node_copy.content_id)
+
+ # Syncs channel.
+ self.channel.main_tree.refresh_from_db()
+ self.channel.save()
+ sync_channel(
+ self.channel,
+ sync_assessment_items=True,
+ )
+
+ # Now after syncing the original and copied node should have same content_id.
+ assessmentitem_node.refresh_from_db()
+ assessmentitem_node_copy.refresh_from_db()
+ self.assertEqual(assessmentitem_node.content_id, assessmentitem_node_copy.content_id)
+
+ def test_content_id__becomes_equal_on_channel_sync_file(self):
+ file = self._upload_file_to_contentnode()
+ file_contentnode_copy = file.contentnode.copy_to(target=self.channel.main_tree)
+
+ # Upload a new file to the copied contentnode.
+ self._upload_file_to_contentnode(contentnode_id=file_contentnode_copy.id)
+
+ # Assert after new file upload, content_id changes.
+ file.contentnode.refresh_from_db()
+ file_contentnode_copy.refresh_from_db()
+ self.assertNotEqual(file.contentnode.content_id, file_contentnode_copy.content_id)
+
+ # Syncs channel.
+ self.channel.main_tree.refresh_from_db()
+ self.channel.save()
+ sync_channel(
+ self.channel,
+ sync_files=True,
+ )
+
+ # Assert that after channel syncing, content_id becomes equal.
+ file.contentnode.refresh_from_db()
+ file_contentnode_copy.refresh_from_db()
+ self.assertEqual(file.contentnode.content_id, file_contentnode_copy.content_id)
diff --git a/contentcuration/contentcuration/tests/test_user.py b/contentcuration/contentcuration/tests/test_user.py
index f9995fb48c..9fda1ceefe 100644
--- a/contentcuration/contentcuration/tests/test_user.py
+++ b/contentcuration/contentcuration/tests/test_user.py
@@ -15,7 +15,6 @@
from .base import BaseAPITestCase
from .testdata import fileobj_video
-from contentcuration.models import Channel
from contentcuration.models import DEFAULT_CONTENT_DEFAULTS
from contentcuration.models import Invitation
from contentcuration.models import User
@@ -161,15 +160,3 @@ def test_user_csv_export(self):
self.assertIn(videos[index - 1].original_filename, row)
self.assertIn(_format_size(videos[index - 1].file_size), row)
self.assertEqual(index, len(videos))
-
- def test_account_deletion(self):
- self.user.delete()
- self.assertFalse(Channel.objects.filter(pk=self.channel.pk).exists())
-
- def test_account_deletion_shared_channels_preserved(self):
- # Deleting a user account shouldn't delete shared channels
- newuser = self.create_user()
- self.channel.editors.add(newuser)
- self.channel.save()
- self.user.delete()
- self.assertTrue(Channel.objects.filter(pk=self.channel.pk).exists())
diff --git a/contentcuration/contentcuration/tests/testdata.py b/contentcuration/contentcuration/tests/testdata.py
index 6d91292154..3592c9df17 100644
--- a/contentcuration/contentcuration/tests/testdata.py
+++ b/contentcuration/contentcuration/tests/testdata.py
@@ -22,6 +22,9 @@
pytestmark = pytest.mark.django_db
+thumbnail_bytes = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01\r\n-\xb4\x00\x00\x00\x00IEND\xaeB`\x82' # noqa E501
+
+
def video():
"""
Create a video content kind entry.
diff --git a/contentcuration/contentcuration/tests/utils/test_automation_manager.py b/contentcuration/contentcuration/tests/utils/test_automation_manager.py
new file mode 100644
index 0000000000..a01eaaa228
--- /dev/null
+++ b/contentcuration/contentcuration/tests/utils/test_automation_manager.py
@@ -0,0 +1,40 @@
+import unittest
+from unittest.mock import MagicMock
+
+from contentcuration.utils.automation_manager import AutomationManager
+
+
+class AutomationManagerTestCase(unittest.TestCase):
+ def setUp(self):
+ self.automation_manager = AutomationManager()
+
+ def test_creation(self):
+ # Check if an instance of AutomationManager is created successfully
+ self.assertIsInstance(self.automation_manager, AutomationManager)
+
+ def test_generate_embedding(self):
+ text = "Some text that needs to be embedded"
+ # Mock the generate_embedding method of RecommendationsAdapter
+ # as the implementation is yet to be done
+ self.automation_manager.recommendations_backend_adapter.generate_embedding = MagicMock(return_value=[0.1, 0.2, 0.3])
+ embedding_vector = self.automation_manager.generate_embedding(text)
+ self.assertIsNotNone(embedding_vector)
+
+ def test_embedding_exists(self):
+ embedding_vector = [0.1, 0.2, 0.3]
+ # Currently no solid implementation exists for this
+ # So the embadding_exists function returns true anyways
+ exists = self.automation_manager.embedding_exists(embedding_vector)
+ self.assertTrue(exists)
+
+ def test_load_recommendations(self):
+ embedding_vector = [0.1, 0.2, 0.3]
+ self.automation_manager.recommendations_backend_adapter.get_recommendations = MagicMock(return_value=["item1", "item2"])
+ recommendations = self.automation_manager.load_recommendations(embedding_vector)
+ self.assertIsInstance(recommendations, list)
+
+ def test_cache_embeddings(self):
+ embeddings_list = [[0.1, 0.2, 0.3]]
+ # Currently the function returns true anyways
+ success = self.automation_manager.cache_embeddings(embeddings_list)
+ self.assertTrue(success)
diff --git a/contentcuration/contentcuration/tests/utils/test_cloud_storage.py b/contentcuration/contentcuration/tests/utils/test_cloud_storage.py
new file mode 100644
index 0000000000..3aade0d72a
--- /dev/null
+++ b/contentcuration/contentcuration/tests/utils/test_cloud_storage.py
@@ -0,0 +1,10 @@
+from django.test import TestCase
+
+from contentcuration.utils.cloud_storage import CloudStorage
+
+
+class CloudStorageTestCase(TestCase):
+ def test_backend_initialization(self):
+ cloud_storage_instance = CloudStorage()
+ self.assertIsNotNone(cloud_storage_instance)
+ self.assertIsInstance(cloud_storage_instance.get_instance(), CloudStorage)
diff --git a/contentcuration/contentcuration/tests/utils/test_garbage_collect.py b/contentcuration/contentcuration/tests/utils/test_garbage_collect.py
index 6746fa43a6..f67daf8c28 100644
--- a/contentcuration/contentcuration/tests/utils/test_garbage_collect.py
+++ b/contentcuration/contentcuration/tests/utils/test_garbage_collect.py
@@ -11,21 +11,24 @@
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.urls import reverse_lazy
+from django_celery_results.models import TaskResult
from le_utils.constants import content_kinds
from le_utils.constants import file_formats
from le_utils.constants import format_presets
from contentcuration import models as cc
-from contentcuration.api import activate_channel
+from contentcuration.constants import user_history
from contentcuration.models import ContentNode
from contentcuration.models import File
-from contentcuration.models import TaskResult
+from contentcuration.models import UserHistory
from contentcuration.tests.base import BaseAPITestCase
from contentcuration.tests.base import StudioTestCase
from contentcuration.tests.testdata import tree
+from contentcuration.utils.db_tools import create_user
from contentcuration.utils.garbage_collect import clean_up_contentnodes
from contentcuration.utils.garbage_collect import clean_up_deleted_chefs
from contentcuration.utils.garbage_collect import clean_up_feature_flags
+from contentcuration.utils.garbage_collect import clean_up_soft_deleted_users
from contentcuration.utils.garbage_collect import clean_up_stale_files
from contentcuration.utils.garbage_collect import clean_up_tasks
from contentcuration.utils.garbage_collect import get_deleted_chefs_root
@@ -142,35 +145,6 @@ def test_old_staging_tree(self):
self.assertFalse(cc.ContentNode.objects.filter(parent=garbage_node).exists())
self.assertFalse(cc.ContentNode.objects.filter(pk=child_pk).exists())
- def test_activate_channel(self):
- previous_tree = self.channel.previous_tree
- tree(parent=previous_tree)
- garbage_node = get_deleted_chefs_root()
-
- # Previous tree shouldn't be in garbage tree until activate_channel is called
- self.assertFalse(
- garbage_node.get_descendants().filter(pk=previous_tree.pk).exists()
- )
- activate_channel(self.channel, self.user)
- garbage_node.refresh_from_db()
- previous_tree.refresh_from_db()
- self.channel.refresh_from_db()
-
- # We can't use MPTT methods on the deleted chefs tree because we are not running the sort code
- # for performance reasons, so just do a parent test instead.
- self.assertTrue(previous_tree.parent == garbage_node)
-
- # New previous tree should not be in garbage tree
- self.assertFalse(self.channel.previous_tree.parent)
- self.assertNotEqual(garbage_node.tree_id, self.channel.previous_tree.tree_id)
-
- child_pk = previous_tree.children.first().pk
-
- clean_up_deleted_chefs()
-
- self.assertFalse(cc.ContentNode.objects.filter(parent=garbage_node).exists())
- self.assertFalse(cc.ContentNode.objects.filter(pk=child_pk).exists())
-
THREE_MONTHS_AGO = datetime.now() - timedelta(days=93)
@@ -192,6 +166,40 @@ def _create_expired_contentnode(creation_date=THREE_MONTHS_AGO):
return c
+def _create_deleted_user_in_past(deletion_datetime, email="test@test.com"):
+ user = create_user(email, "password", "test", "test")
+ user.delete()
+
+ user_latest_delete_history = UserHistory.objects.filter(user_id=user.id, action=user_history.DELETION).order_by("-performed_at").first()
+ user_latest_delete_history.performed_at = deletion_datetime
+ user_latest_delete_history.save()
+ return user
+
+
+class CleanUpSoftDeletedExpiredUsersTestCase(StudioTestCase):
+ def test_cleanup__all_expired_soft_deleted_users(self):
+ expired_users = []
+ for i in range(0, 5):
+ expired_users.append(_create_deleted_user_in_past(deletion_datetime=THREE_MONTHS_AGO, email=f"test-{i}@test.com"))
+
+ clean_up_soft_deleted_users()
+
+ for user in expired_users:
+ assert UserHistory.objects.filter(user_id=user.id, action=user_history.RELATED_DATA_HARD_DELETION).exists() is True
+
+ def test_no_cleanup__unexpired_soft_deleted_users(self):
+ two_months_ago = datetime.now() - timedelta(days=63)
+ user = _create_deleted_user_in_past(deletion_datetime=two_months_ago)
+ clean_up_soft_deleted_users()
+ assert UserHistory.objects.filter(user_id=user.id, action=user_history.RELATED_DATA_HARD_DELETION).exists() is False
+
+ def test_no_cleanup__undeleted_users(self):
+ user = create_user("test@test.com", "password", "test", "test")
+ clean_up_soft_deleted_users()
+ assert user.deleted is False
+ assert UserHistory.objects.filter(user_id=user.id, action=user_history.RELATED_DATA_HARD_DELETION).exists() is False
+
+
class CleanUpContentNodesTestCase(StudioTestCase):
def test_delete_all_contentnodes_in_orphanage_tree(self):
@@ -376,10 +384,9 @@ def test_clean_up(self):
class CleanupTaskTestCase(StudioTestCase):
def setUp(self):
- user = self.admin_user
- self.pruned_task = TaskResult.objects.create(task_id=uuid.uuid4().hex, status=states.SUCCESS, task_name="pruned_task", user_id=user.id)
- self.failed_task = TaskResult.objects.create(task_id=uuid.uuid4().hex, status=states.FAILURE, task_name="failed_task", user_id=user.id)
- self.recent_task = TaskResult.objects.create(task_id=uuid.uuid4().hex, status=states.SUCCESS, task_name="recent_task", user_id=user.id)
+ self.pruned_task = TaskResult.objects.create(task_id=uuid.uuid4().hex, status=states.SUCCESS, task_name="pruned_task")
+ self.failed_task = TaskResult.objects.create(task_id=uuid.uuid4().hex, status=states.FAILURE, task_name="failed_task")
+ self.recent_task = TaskResult.objects.create(task_id=uuid.uuid4().hex, status=states.SUCCESS, task_name="recent_task")
# `date_done` uses `auto_now`, so manually set it
done = datetime.now() - timedelta(days=8)
diff --git a/contentcuration/contentcuration/tests/utils/test_recommendations.py b/contentcuration/contentcuration/tests/utils/test_recommendations.py
new file mode 100644
index 0000000000..d410651de1
--- /dev/null
+++ b/contentcuration/contentcuration/tests/utils/test_recommendations.py
@@ -0,0 +1,10 @@
+from django.test import TestCase
+
+from contentcuration.utils.recommendations import Recommendations
+
+
+class RecommendationsTestCase(TestCase):
+ def test_backend_initialization(self):
+ recomendations = Recommendations()
+ self.assertIsNotNone(recomendations)
+ self.assertIsInstance(recomendations.get_instance(), Recommendations)
diff --git a/contentcuration/contentcuration/tests/test_node_views.py b/contentcuration/contentcuration/tests/views/test_nodes.py
similarity index 51%
rename from contentcuration/contentcuration/tests/test_node_views.py
rename to contentcuration/contentcuration/tests/views/test_nodes.py
index 922675c721..2367263275 100644
--- a/contentcuration/contentcuration/tests/test_node_views.py
+++ b/contentcuration/contentcuration/tests/views/test_nodes.py
@@ -1,22 +1,39 @@
-from __future__ import absolute_import
-
import datetime
import json
import pytz
from django.conf import settings
from django.core.cache import cache
+from django.urls import reverse
+from mock import Mock
from mock import patch
-from rest_framework.reverse import reverse
-from .base import BaseAPITestCase
-from .testdata import tree
-from contentcuration.models import Channel
+from contentcuration.tasks import generatenodediff_task
+from contentcuration.tests.base import BaseAPITestCase
+
+
+class NodesViewsTestCase(BaseAPITestCase):
+ def test_get_node_diff__missing_contentnode(self):
+ response = self.get(reverse("get_node_diff", kwargs=dict(updated_id="abc123", original_id="def456")))
+ self.assertEqual(response.status_code, 404)
+
+ def test_get_node_diff__no_task_processing(self):
+ pk = self.channel.main_tree.pk
+ response = self.get(reverse("get_node_diff", kwargs=dict(updated_id=pk, original_id=pk)))
+ self.assertEqual(response.status_code, 404)
+
+ @patch.object(generatenodediff_task, 'find_incomplete_ids')
+ def test_get_node_diff__task_processing(self, mock_find_incomplete_ids):
+ qs = Mock(spec="django.db.models.query.QuerySet")
+ mock_find_incomplete_ids.return_value = qs()
+ mock_find_incomplete_ids.return_value.exists.return_value = True
+ pk = self.channel.main_tree.pk
+ response = self.get(reverse("get_node_diff", kwargs=dict(updated_id=pk, original_id=pk)))
+ self.assertEqual(response.status_code, 302)
-class NodeViewsUtilityTestCase(BaseAPITestCase):
def test_get_channel_details(self):
- url = reverse('get_channel_details', [self.channel.id])
+ url = reverse('get_channel_details', kwargs={"channel_id": self.channel.id})
response = self.get(url)
details = json.loads(response.content)
@@ -33,13 +50,13 @@ def test_get_channel_details_cached(self):
cache.set(cache_key, json.dumps(data))
with patch("contentcuration.views.nodes.getnodedetails_task") as task_mock:
- url = reverse('get_channel_details', [self.channel.id])
+ url = reverse('get_channel_details', kwargs={"channel_id": self.channel.id})
self.get(url)
# Check that the outdated cache prompts an asynchronous cache update
task_mock.enqueue.assert_called_once_with(self.user, node_id=self.channel.main_tree.id)
-class GetTopicDetailsEndpointTestCase(BaseAPITestCase):
+class ChannelDetailsEndpointTestCase(BaseAPITestCase):
def test_200_post(self):
response = self.get(
reverse("get_channel_details", kwargs={"channel_id": self.channel.id})
@@ -47,10 +64,8 @@ def test_200_post(self):
self.assertEqual(response.status_code, 200)
def test_404_no_permission(self):
- new_channel = Channel.objects.create()
- new_channel.main_tree = tree()
- new_channel.save()
+ self.channel.editors.remove(self.user)
response = self.get(
- reverse("get_channel_details", kwargs={"channel_id": new_channel.id}),
+ reverse("get_channel_details", kwargs={"channel_id": self.channel.id}),
)
self.assertEqual(response.status_code, 404)
diff --git a/contentcuration/contentcuration/tests/views/test_users.py b/contentcuration/contentcuration/tests/views/test_users.py
index 0c03d5f626..e79e0cdbf9 100644
--- a/contentcuration/contentcuration/tests/views/test_users.py
+++ b/contentcuration/contentcuration/tests/views/test_users.py
@@ -98,6 +98,14 @@ def test_login__whitespace(self):
self.assertIsInstance(redirect, HttpResponseRedirectBase)
self.assertIn("channels", redirect['Location'])
+ def test_after_delete__no_login(self):
+ with mock.patch("contentcuration.views.users.djangologin") as djangologin:
+ self.user.delete()
+ response = login(self.request)
+
+ self.assertIsInstance(response, HttpResponseForbidden)
+ djangologin.assert_not_called()
+
class UserRegistrationViewTestCase(StudioAPITestCase):
def setUp(self):
@@ -121,6 +129,12 @@ def test_post__no_duplicate_registration(self):
response = self.view.post(self.request)
self.assertIsInstance(response, HttpResponseForbidden)
+ def test_after_delete__no_registration(self):
+ user = testdata.user(email="tester@tester.com")
+ user.delete()
+ response = self.view.post(self.request)
+ self.assertIsInstance(response, HttpResponseForbidden)
+
class UserActivationViewTestCase(StudioAPITestCase):
def setUp(self):
diff --git a/contentcuration/contentcuration/tests/views/test_views_base.py b/contentcuration/contentcuration/tests/views/test_views_base.py
index 201058d289..8bf4b80726 100644
--- a/contentcuration/contentcuration/tests/views/test_views_base.py
+++ b/contentcuration/contentcuration/tests/views/test_views_base.py
@@ -2,69 +2,54 @@
"""
Tests for contentcuration.views.internal functions.
"""
+import uuid
+
from django.urls import reverse_lazy
+from django_celery_results.models import TaskResult
from ..base import BaseAPITestCase
-from contentcuration.models import Channel
-from contentcuration.models import TaskResult
+from contentcuration.models import CustomTaskMetadata
+from contentcuration.tests import testdata
from contentcuration.utils.db_tools import TreeBuilder
-class APIActivateChannelEndpointTestCase(BaseAPITestCase):
- def test_200_post(self):
- main_tree = TreeBuilder(user=self.user)
- staging_tree = TreeBuilder(user=self.user)
- self.channel.main_tree = main_tree.root
- self.channel.staging_tree = staging_tree.root
- self.channel.save()
- response = self.post(
- reverse_lazy("activate_channel"), {"channel_id": self.channel.id}
- )
- self.assertEqual(response.status_code, 200)
-
- def test_404_no_permission(self):
- new_channel = Channel.objects.create()
- staging_tree = TreeBuilder(user=self.user, levels=1)
- new_channel.staging_tree = staging_tree.root
- new_channel.save()
- response = self.post(
- reverse_lazy("activate_channel"), {"channel_id": new_channel.id}
- )
- self.assertEqual(response.status_code, 404)
-
- def test_200_no_change_in_space(self):
- main_tree = TreeBuilder(user=self.user)
- staging_tree = TreeBuilder(user=self.user)
- self.channel.main_tree = main_tree.root
- self.channel.staging_tree = staging_tree.root
- self.channel.save()
- self.user.disk_space = self.user.get_space_used(active_files=self.user.get_user_active_files())
- self.user.save()
- response = self.post(
- reverse_lazy("activate_channel"), {"channel_id": self.channel.id}
- )
- self.assertEqual(response.status_code, 200)
-
-
class PublishingStatusEndpointTestCase(BaseAPITestCase):
def test_200_get(self):
self.user.is_admin = True
self.user.save()
main_tree = TreeBuilder(user=self.user)
self.channel.main_tree = main_tree.root
+ channel_2 = testdata.channel()
self.channel.save()
+ channel_2.save()
self.channel.mark_publishing(self.user)
+ channel_2.mark_publishing(self.user)
+ task_id = uuid.uuid4().hex
+ task_id_2 = uuid.uuid4().hex
task = TaskResult.objects.create(
+ task_id=task_id,
task_name="export-channel",
- channel_id=self.channel.id,
- user_id=self.user.id,
status="QUEUED",
)
+ task_2 = TaskResult.objects.create(
+ task_id=task_id_2,
+ task_name="export-channel",
+ status="QUEUED",
+ )
+ CustomTaskMetadata(task_id=task_id, user=self.user, channel_id=self.channel.id).save()
+ CustomTaskMetadata(task_id=task_id_2, user=self.user, channel_id=channel_2.id).save()
response = self.get(
reverse_lazy("publishing_status"),
)
+
+ expected_channel_ids = [self.channel.id, channel_2.id]
+ expected_channel_ids.sort()
+
self.assertEqual(response.status_code, 200)
- self.assertEqual(1, len(response.data))
- self.assertEqual(self.channel.id, response.data[0]["channel_id"])
- self.assertEqual(task.task_id, response.data[0]["task_id"])
- self.assertIn("performed", response.data[0])
+ self.assertEqual(2, len(response.data))
+
+ for i, item in enumerate(response.data):
+ self.assertEqual(expected_channel_ids[i], item["channel_id"])
+ expected_task_id = task.task_id if item["channel_id"] == self.channel.id else task_2.task_id
+ self.assertEqual(expected_task_id, item["task_id"])
+ self.assertIn("performed", item)
diff --git a/contentcuration/contentcuration/tests/views/test_views_internal.py b/contentcuration/contentcuration/tests/views/test_views_internal.py
index 999cc9a7b6..3a23654ee6 100644
--- a/contentcuration/contentcuration/tests/views/test_views_internal.py
+++ b/contentcuration/contentcuration/tests/views/test_views_internal.py
@@ -8,6 +8,7 @@
from django.db import connections
from django.urls import reverse_lazy
+from le_utils.constants import content_kinds
from le_utils.constants import format_presets
from le_utils.constants.labels.accessibility_categories import ACCESSIBILITYCATEGORIESLIST
from le_utils.constants.labels.learning_activities import LEARNINGACTIVITIESLIST
@@ -27,8 +28,11 @@
from ..testdata import fileobj_exercise_graphie
from ..testdata import fileobj_exercise_image
from ..testdata import fileobj_video
+from ..testdata import thumbnail_bytes
from ..testdata import user
from contentcuration import ricecooker_versions as rc
+from contentcuration.db.models.manager import ALLOWED_OVERRIDES
+from contentcuration.db.models.manager import EDIT_ALLOWED_OVERRIDES
from contentcuration.models import Channel
from contentcuration.models import ContentNode
from contentcuration.views import internal
@@ -227,6 +231,33 @@ def test_tag_greater_than_30_chars_excluded(self):
self.assertEqual(response.status_code, 400, response.content)
+ def test_add_nodes__not_a_topic(self):
+ resource_node = self._make_node_data()
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ resource_node,
+ ],
+ }
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+ # should succeed
+ self.assertEqual(response.status_code, 200, response.content)
+ resource_node_id = next(iter(response.json().get('root_ids').values()))
+
+ invalid_child = self._make_node_data()
+ test_data = {
+ "root_id": resource_node_id,
+ "content_data": [
+ invalid_child,
+ ],
+ }
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
def test_invalid_metadata_label_excluded(self):
invalid_metadata_labels = self._make_node_data()
invalid_metadata_labels["title"] = "invalid_metadata_labels"
@@ -613,23 +644,6 @@ def test_404_no_permission(self):
self.assertEqual(response.status_code, 404)
-class APIActivateChannelEndpointTestCase(BaseAPITestCase):
- def test_200_post(self):
- self.channel.staging_tree = self.channel.main_tree
- self.channel.save()
- response = self.post(
- reverse_lazy("activate_channel_internal"), {"channel_id": self.channel.id}
- )
- self.assertEqual(response.status_code, 200)
-
- def test_404_no_permission(self):
- new_channel = Channel.objects.create()
- response = self.post(
- reverse_lazy("activate_channel_internal"), {"channel_id": new_channel.id}
- )
- self.assertEqual(response.status_code, 404)
-
-
class CheckUserIsEditorEndpointTestCase(BaseAPITestCase):
def test_200_post(self):
response = self.post(
@@ -801,3 +815,362 @@ def test_associates_extra_fields_with_root_node(self):
self.fail("Channel was not created")
self.assertEqual(c.chef_tree.extra_fields["modality"], "CUSTOM_NAVIGATION")
+
+
+class ApiAddRemoteNodesToTreeTestCase(StudioTestCase):
+ """
+ Tests for contentcuration.views.internal.api_add_nodes_to_tree function.
+ For adding nodes that already exist on Studio to a cheffed tree.
+ """
+
+ def _make_node_data(self):
+ random_data = mixer.blend(SampleContentNodeDataSchema)
+ fileobj = self.fileobj
+ return {
+ "source_channel_id": self.source_channel.id,
+ "source_node_id": self.source_video.node_id,
+ "source_content_id": self.source_video.content_id,
+ "title": random_data.title,
+ "language": "en",
+ "description": random_data.description,
+ "node_id": random_data.node_id,
+ "content_id": random_data.content_id,
+ "source_domain": random_data.source_domain,
+ "source_id": random_data.source_id,
+ "author": random_data.author,
+ "tags": ["oer", "edtech"],
+ "files": [
+ {
+ "size": fileobj.file_size,
+ "preset": fileobj.preset_id,
+ "filename": fileobj.filename(),
+ "original_filename": fileobj.original_filename,
+ "language": fileobj.language,
+ "source_url": fileobj.source_url,
+ }
+ ],
+ "kind": "document",
+ "license": "CC BY",
+ "license_description": "This is a fake license",
+ "copyright_holder": random_data.copyright_holder,
+ "questions": [],
+ "extra_fields": "{}",
+ "role": "learner",
+ }
+
+ def setUp(self):
+ super(ApiAddRemoteNodesToTreeTestCase, self).setUp()
+ self.source_channel = channel()
+ self.source_video = self.source_channel.main_tree.get_descendants().filter(kind_id=content_kinds.VIDEO).first()
+
+ # first setup a test channel...
+ self.channel = channel()
+ self.root_node = self.channel.main_tree
+
+ temp_file_dict = create_studio_file(thumbnail_bytes, preset=format_presets.VIDEO_THUMBNAIL, ext='jpg')
+
+ # File used for every node
+ self.fileobj = temp_file_dict["db_file"]
+
+ # Valid node
+ self.valid_node = self._make_node_data()
+ self.title = self.valid_node["title"]
+
+ valid_metadata_labels = self._make_node_data()
+ valid_metadata_labels["title"] = "valid_metadata_labels"
+ for label, values in METADATA.items():
+ valid_metadata_labels[label] = [values[0]]
+
+ self.sample_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ self.valid_node,
+ valid_metadata_labels,
+ ],
+ }
+
+ def test_404_no_permission(self):
+ client = APIClient()
+ client.force_authenticate(user())
+ response = client.post(
+ reverse_lazy("api_add_nodes_to_tree"), self.sample_data, format="json"
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_returns_200_status_code(self):
+ """
+ Check that we return 200 if passed in a valid JSON.
+ """
+ self.resp = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=self.sample_data, format="json"
+ )
+
+ # check that we returned 200 with that POST request
+ assert self.resp.status_code == 200, "Got a request error: {}".format(
+ self.resp.content
+ )
+
+ def test_creates_nodes(self):
+ """
+ Test that it creates a node with the given title and parent.
+ """
+ self.resp = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=self.sample_data, format="json"
+ )
+
+ # make sure a node with our given self.title exists, with the given parent.
+ assert ContentNode.get_nodes_with_title(
+ title=self.title, limit_to_children_of=self.root_node.id
+ ).exists()
+
+ def test_associates_file_with_created_node(self):
+ """
+ Check that the file we created beforehand is now associated
+ with the node we just created through add_nodes.
+ """
+ self.resp = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=self.sample_data, format="json"
+ )
+
+ c = ContentNode.objects.get(title=self.title)
+
+ # check that the file we associated has the same checksum
+ f = c.files.get(checksum=self.fileobj.checksum)
+ assert f
+
+ # check that we can read the file and it's equivalent to
+ # our original file object
+ assert f.file_on_disk.read() == self.fileobj.file_on_disk.read()
+
+ def test_metadata_properly_created(self):
+ self.resp = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=self.sample_data, format="json"
+ )
+
+ node = ContentNode.objects.get(title="valid_metadata_labels")
+ for label, values in METADATA.items():
+ self.assertEqual(getattr(node, label), {
+ values[0]: True
+ })
+
+ def test_metadata_properly_screened_viewer(self):
+ self.root_node.get_descendants().delete()
+ new_user = user()
+ new_channel = channel()
+ new_channel.editors.add(new_user)
+ self.source_channel.viewers.add(new_user)
+ self.sample_data = {
+ "root_id": new_channel.main_tree.id,
+ "content_data": [
+ self.valid_node,
+ ],
+ }
+
+ client = APIClient()
+ client.force_authenticate(new_user)
+ client.post(
+ reverse_lazy("api_add_nodes_to_tree"), self.sample_data, format="json"
+ )
+
+ # Ensure that we do not allow disallowed mods to the copied files
+ node = ContentNode.objects.get(title=self.title)
+ for key, value in self.valid_node.items():
+ if key not in METADATA:
+ if hasattr(node, key):
+ # These will be matching even though we don't overwrite them.
+ if key in ALLOWED_OVERRIDES or key in {"source_channel_id", "source_node_id"}:
+ self.assertEqual(getattr(node, key), value, key)
+ else:
+ self.assertNotEqual(getattr(node, key), value, key)
+
+ def test_metadata_properly_screened_editor(self):
+ self.resp = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=self.sample_data, format="json"
+ )
+
+ # Ensure that we do not allow disallowed mods to the copied files
+ node = ContentNode.objects.get(title=self.title)
+ for key, value in self.valid_node.items():
+ if key not in METADATA:
+ if hasattr(node, key):
+ # These will be matching even though we don't overwrite them.
+ if key in EDIT_ALLOWED_OVERRIDES or key in {"source_channel_id", "source_node_id"}:
+ self.assertEqual(getattr(node, key), value, key)
+ else:
+ self.assertNotEqual(getattr(node, key), value, key)
+
+ def test_tag_greater_than_30_chars_excluded(self):
+ # Node with tag greater than 30 characters
+ invalid_tag_length = self._make_node_data()
+ invalid_tag_length["title"] = "invalid_tag_length"
+ invalid_tag_length["tags"] = ["kolibri studio, kolibri studio!"]
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ invalid_tag_length,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_invalid_metadata_label_excluded(self):
+ invalid_metadata_labels = self._make_node_data()
+ invalid_metadata_labels["title"] = "invalid_metadata_labels"
+ invalid_metadata_labels["categories"] = ["not a label!"]
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ invalid_metadata_labels,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_topic_excluded(self):
+ topic_data = self._make_node_data()
+ topic_data["source_node_id"] = self.root_node.node_id
+ topic_data["source_content_id"] = self.root_node.content_id
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ topic_data,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_null_source_channel_id(self):
+ null_source_channel_id = self._make_node_data()
+ null_source_channel_id["source_channel_id"] = None
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ null_source_channel_id,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_invalid_source_channel_id(self):
+ invalid_source_channel_id = self._make_node_data()
+ invalid_source_channel_id["source_channel_id"] = "not a channel id"
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ invalid_source_channel_id,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_null_node_id_and_content_id(self):
+ null_node_id_and_content_id = self._make_node_data()
+ null_node_id_and_content_id["source_node_id"] = None
+ null_node_id_and_content_id["source_content_id"] = None
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ null_node_id_and_content_id,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_invalid_node_id_and_content_id(self):
+ invalid_node_id_and_content_id = self._make_node_data()
+ invalid_node_id_and_content_id["source_node_id"] = "not valid"
+ invalid_node_id_and_content_id["source_content_id"] = "def not valid"
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ invalid_node_id_and_content_id,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_no_node_id(self):
+ no_node_id = self._make_node_data()
+ del no_node_id["source_node_id"]
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ no_node_id,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 200, response.content)
+
+ def test_no_content_id(self):
+ no_content_id = self._make_node_data()
+ del no_content_id["source_content_id"]
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ no_content_id,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 200, response.content)
+
+ def test_no_files(self):
+ no_files = self._make_node_data()
+ del no_files["files"]
+
+ test_data = {
+ "root_id": self.root_node.id,
+ "content_data": [
+ no_files,
+ ],
+ }
+
+ response = self.admin_client().post(
+ reverse_lazy("api_add_nodes_to_tree"), data=test_data, format="json"
+ )
+
+ self.assertEqual(response.status_code, 200, response.content)
diff --git a/contentcuration/contentcuration/tests/viewsets/base.py b/contentcuration/contentcuration/tests/viewsets/base.py
index 5eca0415fe..f02ff3c4d1 100644
--- a/contentcuration/contentcuration/tests/viewsets/base.py
+++ b/contentcuration/contentcuration/tests/viewsets/base.py
@@ -5,9 +5,13 @@
from contentcuration.celery import app
from contentcuration.models import Change
from contentcuration.tests.helpers import clear_tasks
+from contentcuration.viewsets.sync.constants import CHANNEL
+from contentcuration.viewsets.sync.constants import SYNCED
+from contentcuration.viewsets.sync.utils import _generate_event as base_generate_event
from contentcuration.viewsets.sync.utils import generate_copy_event as base_generate_copy_event
from contentcuration.viewsets.sync.utils import generate_create_event as base_generate_create_event
from contentcuration.viewsets.sync.utils import generate_delete_event as base_generate_delete_event
+from contentcuration.viewsets.sync.utils import generate_deploy_event as base_generate_deploy_event
from contentcuration.viewsets.sync.utils import generate_update_event as base_generate_update_event
@@ -35,6 +39,22 @@ def generate_update_event(*args, **kwargs):
return event
+def generate_sync_channel_event(channel_id, titles_and_descriptions, resource_details, files, assessment_items):
+ event = base_generate_event(key=channel_id, table=CHANNEL, event_type=SYNCED, channel_id=channel_id, user_id=None)
+ event["rev"] = random.randint(1, 10000000)
+ event["titles_and_descriptions"] = titles_and_descriptions
+ event["resource_details"] = resource_details
+ event["files"] = files
+ event["assessment_items"] = assessment_items
+ return event
+
+
+def generate_deploy_channel_event(channel_id, user_id):
+ event = base_generate_deploy_event(channel_id, user_id=user_id)
+ event["rev"] = random.randint(1, 10000000)
+ return event
+
+
class SyncTestMixin(object):
celery_task_always_eager = None
diff --git a/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py b/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py
index e6370e7a49..dd80e09291 100644
--- a/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py
+++ b/contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py
@@ -1,5 +1,6 @@
from __future__ import absolute_import
+import json
import uuid
from django.urls import reverse
@@ -21,6 +22,7 @@ class SyncTestCase(SyncTestMixin, StudioAPITestCase):
@property
def assessmentitem_metadata(self):
return {
+
"assessment_id": uuid.uuid4().hex,
"contentnode": self.channel.main_tree.get_descendants()
.filter(kind_id=content_kinds.EXERCISE)
@@ -105,11 +107,14 @@ def test_create_assessmentitem_with_file_answers(self):
image_file = testdata.fileobj_exercise_image()
image_file.uploaded_by = self.user
image_file.save()
- answers = "![alt_text](${}/{}.{})".format(
+ answer = "![alt_text](${}/{}.{})".format(
exercises.IMG_PLACEHOLDER, image_file.checksum, image_file.file_format_id
)
- assessmentitem["answers"] = answers
+ answers = [{'answer': answer, 'correct': False, 'order': 1}]
+
+ assessmentitem["answers"] = json.dumps(answers)
+
response = self.sync_changes(
[
generate_create_event(
@@ -139,11 +144,16 @@ def test_create_assessmentitem_with_file_hints(self):
image_file = testdata.fileobj_exercise_image()
image_file.uploaded_by = self.user
image_file.save()
- hints = "![alt_text](${}/{}.{})".format(
+ hint = "![alt_text](${}/{}.{})".format(
exercises.IMG_PLACEHOLDER, image_file.checksum, image_file.file_format_id
)
+ hints = [
+ {"hint": hint, "order": 1},
+ ]
+ hints = json.dumps(hints)
assessmentitem["hints"] = hints
+
response = self.sync_changes(
[
generate_create_event(
@@ -154,6 +164,7 @@ def test_create_assessmentitem_with_file_hints(self):
)
],
)
+
self.assertEqual(response.status_code, 200, response.content)
try:
ai = models.AssessmentItem.objects.get(
@@ -182,7 +193,7 @@ def test_create_assessmentitem_with_file_no_permission(self):
ASSESSMENTITEM,
assessmentitem,
channel_id=self.channel.id,
- )
+ ),
],
)
self.assertEqual(response.status_code, 200, response.content)
@@ -262,12 +273,12 @@ def test_attempt_update_missing_assessmentitem(self):
response = self.sync_changes(
[
generate_update_event([
- self.channel.main_tree.get_descendants()
+ self.channel.main_tree.get_descendants()
.filter(kind_id=content_kinds.EXERCISE)
.first()
.id,
- uuid.uuid4().hex
- ],
+ uuid.uuid4().hex
+ ],
ASSESSMENTITEM,
{"question": "but why is it missing in the first place?"},
channel_id=self.channel.id,
@@ -498,6 +509,103 @@ def test_delete_assessmentitems(self):
except models.AssessmentItem.DoesNotExist:
pass
+ def test_valid_hints_assessmentitem(self):
+ self.client.force_authenticate(user=self.user)
+ assessmentitem = self.assessmentitem_metadata
+ assessmentitem["hints"] = json.dumps([{'hint': 'asdasdwdqasd', 'order': 1}, {'hint': 'testing the hint', 'order': 2}])
+ response = self.sync_changes(
+ [
+ generate_create_event(
+ [assessmentitem["contentnode"], assessmentitem["assessment_id"]],
+ ASSESSMENTITEM,
+ assessmentitem,
+ channel_id=self.channel.id,
+ ),
+ ],
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ try:
+ models.AssessmentItem.objects.get(
+ assessment_id=assessmentitem["assessment_id"]
+ )
+ except models.AssessmentItem.DoesNotExist:
+ self.fail("AssessmentItem was not created")
+
+ def test_invalid_hints_assessmentitem(self):
+
+ self.client.force_authenticate(user=self.user)
+ assessmentitem = self.assessmentitem_metadata
+ assessmentitem["hints"] = json.dumps("test invalid string for hints")
+ response = self.sync_changes(
+ [
+ generate_create_event(
+ [assessmentitem["contentnode"], assessmentitem["assessment_id"]],
+ ASSESSMENTITEM,
+ assessmentitem,
+ channel_id=self.channel.id,
+ ),
+ ],
+ )
+
+ self.assertEqual(response.json()["errors"][0]["table"], "assessmentitem")
+ self.assertEqual(response.json()["errors"][0]["errors"]["hints"][0], "JSON Data Invalid for hints")
+ self.assertEqual(len(response.json()["errors"]), 1)
+
+ with self.assertRaises(models.AssessmentItem.DoesNotExist, msg="AssessmentItem was created"):
+ models.AssessmentItem.objects.get(
+ assessment_id=assessmentitem["assessment_id"]
+ )
+
+ def test_valid_answers_assessmentitem(self):
+ self.client.force_authenticate(user=self.user)
+ assessmentitem = self.assessmentitem_metadata
+ assessmentitem["answers"] = json.dumps([{'answer': 'test answer 1 :)', 'correct': False, 'order': 1},
+ {'answer': 'test answer 2 :)', 'correct': False, 'order': 2},
+ {'answer': 'test answer 3 :)', 'correct': True, 'order': 3}
+ ])
+ response = self.sync_changes(
+ [
+ generate_create_event(
+ [assessmentitem["contentnode"], assessmentitem["assessment_id"]],
+ ASSESSMENTITEM,
+ assessmentitem,
+ channel_id=self.channel.id,
+ ),
+ ],
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ try:
+ models.AssessmentItem.objects.get(
+ assessment_id=assessmentitem["assessment_id"]
+ )
+ except models.AssessmentItem.DoesNotExist:
+ self.fail("AssessmentItem was not created")
+
+ def test_invalid_answers_assessmentitem(self):
+
+ self.client.force_authenticate(user=self.user)
+ assessmentitem = self.assessmentitem_metadata
+ assessmentitem["answers"] = json.dumps("test invalid string for answers")
+ response = self.sync_changes(
+ [
+ generate_create_event(
+ [assessmentitem["contentnode"], assessmentitem["assessment_id"]],
+ ASSESSMENTITEM,
+ assessmentitem,
+ channel_id=self.channel.id,
+ ),
+ ],
+ )
+
+ self.assertEqual(response.json()["errors"][0]["table"], "assessmentitem")
+ self.assertEqual(response.json()["errors"][0]["errors"]["answers"][0], "JSON Data Invalid for answers")
+ self.assertEqual(len(response.json()["errors"]), 1)
+
+ with self.assertRaises(models.AssessmentItem.DoesNotExist, msg="AssessmentItem was created"):
+ models.AssessmentItem.objects.get(
+ assessment_id=assessmentitem["assessment_id"]
+ )
+
class CRUDTestCase(StudioAPITestCase):
@property
@@ -736,3 +844,155 @@ def test_delete_assessmentitem(self):
self.fail("AssessmentItem was not deleted")
except models.AssessmentItem.DoesNotExist:
pass
+
+
+class ContentIDTestCase(SyncTestMixin, StudioAPITestCase):
+ def setUp(self):
+ super(ContentIDTestCase, self).setUp()
+ self.channel = testdata.channel()
+ self.user = testdata.user()
+ self.channel.editors.add(self.user)
+ self.client.force_authenticate(user=self.user)
+
+ def _get_assessmentitem_metadata(self, assessment_id=None, contentnode_id=None):
+ return {
+ "assessment_id": assessment_id or uuid.uuid4().hex,
+ "contentnode_id": contentnode_id or self.channel.main_tree.get_descendants()
+ .filter(kind_id=content_kinds.EXERCISE)
+ .first()
+ .id,
+ }
+
+ def _create_assessmentitem(self, assessmentitem):
+ self.sync_changes(
+ [
+ generate_create_event(
+ [assessmentitem["contentnode_id"], assessmentitem["assessment_id"]],
+ ASSESSMENTITEM,
+ assessmentitem,
+ channel_id=self.channel.id,
+ )
+ ],
+ )
+
+ def _update_assessmentitem(self, assessmentitem, update_dict):
+ self.sync_changes(
+ [
+ generate_update_event(
+ [assessmentitem["contentnode_id"], assessmentitem["assessment_id"]],
+ ASSESSMENTITEM,
+ update_dict,
+ channel_id=self.channel.id,
+ )
+ ],
+ )
+
+ def _delete_assessmentitem(self, assessmentitem):
+ self.sync_changes(
+ [
+ generate_delete_event(
+ [assessmentitem["contentnode_id"], assessmentitem["assessment_id"]],
+ ASSESSMENTITEM,
+ channel_id=self.channel.id,
+ )
+ ],
+ )
+
+ def test_content_id__same_on_copy(self):
+ # Make a copy of an existing assessmentitem contentnode.
+ assessmentitem_node = self.channel.main_tree.get_descendants().filter(kind_id=content_kinds.EXERCISE).first()
+ assessmentitem_node_copy = assessmentitem_node.copy_to(target=self.channel.main_tree)
+
+ # Assert after copying content_id is same.
+ assessmentitem_node.refresh_from_db()
+ assessmentitem_node_copy.refresh_from_db()
+ self.assertEqual(assessmentitem_node.content_id, assessmentitem_node_copy.content_id)
+
+ def test_content_id__changes_on_new_assessmentitem(self):
+ # Make a copy of an existing assessmentitem contentnode.
+ assessmentitem_node = self.channel.main_tree.get_descendants().filter(kind_id=content_kinds.EXERCISE).first()
+ assessmentitem_node_copy = assessmentitem_node.copy_to(target=self.channel.main_tree)
+
+ # Create a new assessmentitem.
+ self._create_assessmentitem(self._get_assessmentitem_metadata(contentnode_id=assessmentitem_node_copy.id))
+
+ # Assert after creating a new assessmentitem on copied node, it's content_id should change.
+ assessmentitem_node.refresh_from_db()
+ assessmentitem_node_copy.refresh_from_db()
+ self.assertNotEqual(assessmentitem_node.content_id, assessmentitem_node_copy.content_id)
+
+ def test_content_id__changes_on_deleting_assessmentitem(self):
+ # Make a copy of an existing assessmentitem contentnode.
+ assessmentitem_node = self.channel.main_tree.get_descendants().filter(kind_id=content_kinds.EXERCISE).first()
+ assessmentitem_node_copy = assessmentitem_node.copy_to(target=self.channel.main_tree)
+
+ # Delete an already present assessmentitem from copied contentnode.
+ assessmentitem_from_db = models.AssessmentItem.objects.filter(contentnode=assessmentitem_node_copy.id).first()
+ self._delete_assessmentitem(self._get_assessmentitem_metadata(assessmentitem_from_db.assessment_id, assessmentitem_node_copy.id))
+
+ # Assert after deleting assessmentitem on copied node, it's content_id should change.
+ assessmentitem_node.refresh_from_db()
+ assessmentitem_node_copy.refresh_from_db()
+ self.assertNotEqual(assessmentitem_node.content_id, assessmentitem_node_copy.content_id)
+
+ def test_content_id__changes_on_updating_assessmentitem(self):
+ # Make a copy of an existing assessmentitem contentnode.
+ assessmentitem_node = self.channel.main_tree.get_descendants().filter(kind_id=content_kinds.EXERCISE).first()
+ assessmentitem_node_copy = assessmentitem_node.copy_to(target=self.channel.main_tree)
+
+ # Update an already present assessmentitem from copied contentnode.
+ assessmentitem_from_db = models.AssessmentItem.objects.filter(contentnode=assessmentitem_node_copy.id).first()
+ self._update_assessmentitem(self._get_assessmentitem_metadata(assessmentitem_from_db.assessment_id, assessmentitem_node_copy.id),
+ {"question": "New Question!"})
+
+ # Assert after updating assessmentitem on copied node, it's content_id should change.
+ assessmentitem_node.refresh_from_db()
+ assessmentitem_node_copy.refresh_from_db()
+ self.assertNotEqual(assessmentitem_node.content_id, assessmentitem_node_copy.content_id)
+
+ def test_content_id__doesnot_changes_of_original_node(self):
+ # Make a copy of an existing assessmentitem contentnode.
+ assessmentitem_node = self.channel.main_tree.get_descendants().filter(kind_id=content_kinds.EXERCISE).first()
+ assessmentitem_node.copy_to(target=self.channel.main_tree)
+
+ content_id_before_updates = assessmentitem_node.content_id
+
+ # Create, update and delete assessmentitems from original contentnode.
+ assessmentitem_from_db = models.AssessmentItem.objects.filter(contentnode=assessmentitem_node.id).first()
+ self._update_assessmentitem(self._get_assessmentitem_metadata(assessmentitem_from_db.assessment_id, assessmentitem_node.id),
+ {"question": "New Question!"})
+ self._delete_assessmentitem(self._get_assessmentitem_metadata(assessmentitem_from_db.assessment_id, assessmentitem_node.id))
+ self._create_assessmentitem(self._get_assessmentitem_metadata(contentnode_id=assessmentitem_node.id))
+
+ # Assert content_id before and after updates remain same.
+ assessmentitem_node.refresh_from_db()
+ content_id_after_updates = assessmentitem_node.content_id
+ self.assertEqual(content_id_before_updates, content_id_after_updates)
+
+ def test_content_id__doesnot_changes_if_already_unique(self):
+ # Make a copy of an existing assessmentitem contentnode.
+ assessmentitem_node = self.channel.main_tree.get_descendants().filter(kind_id=content_kinds.EXERCISE).first()
+ assessmentitem_node_copy = assessmentitem_node.copy_to(target=self.channel.main_tree)
+
+ # Create, update and delete assessmentitems of copied contentnode.
+ assessmentitem_from_db = models.AssessmentItem.objects.filter(contentnode=assessmentitem_node_copy.id).first()
+ self._update_assessmentitem(self._get_assessmentitem_metadata(assessmentitem_from_db.assessment_id, assessmentitem_node_copy.id),
+ {"question": "New Question!"})
+ self._delete_assessmentitem(self._get_assessmentitem_metadata(assessmentitem_from_db.assessment_id, assessmentitem_node_copy.id))
+ self._create_assessmentitem(self._get_assessmentitem_metadata(contentnode_id=assessmentitem_node_copy.id))
+
+ assessmentitem_node_copy.refresh_from_db()
+ content_id_after_first_update = assessmentitem_node_copy.content_id
+
+ # Once again, let us create, update and delete assessmentitems of copied contentnode.
+ assessmentitem_from_db = models.AssessmentItem.objects.filter(contentnode=assessmentitem_node_copy.id).first()
+ self._update_assessmentitem(self._get_assessmentitem_metadata(assessmentitem_from_db.assessment_id, assessmentitem_node_copy.id),
+ {"question": "New Question!"})
+ self._delete_assessmentitem(self._get_assessmentitem_metadata(assessmentitem_from_db.assessment_id, assessmentitem_node_copy.id))
+ self._create_assessmentitem(self._get_assessmentitem_metadata(contentnode_id=assessmentitem_node_copy.id))
+
+ assessmentitem_node_copy.refresh_from_db()
+ content_id_after_second_update = assessmentitem_node_copy.content_id
+
+ # Assert after first and second updates of assessmentitem content_id remains same.
+ self.assertEqual(content_id_after_first_update, content_id_after_second_update)
diff --git a/contentcuration/contentcuration/tests/viewsets/test_channel.py b/contentcuration/contentcuration/tests/viewsets/test_channel.py
index 63b0940ae4..9c49551ba8 100644
--- a/contentcuration/contentcuration/tests/viewsets/test_channel.py
+++ b/contentcuration/contentcuration/tests/viewsets/test_channel.py
@@ -2,13 +2,18 @@
import uuid
+import mock
from django.urls import reverse
+from le_utils.constants import content_kinds
from contentcuration import models
+from contentcuration import models as cc
from contentcuration.tests import testdata
from contentcuration.tests.base import StudioAPITestCase
from contentcuration.tests.viewsets.base import generate_create_event
from contentcuration.tests.viewsets.base import generate_delete_event
+from contentcuration.tests.viewsets.base import generate_deploy_channel_event
+from contentcuration.tests.viewsets.base import generate_sync_channel_event
from contentcuration.tests.viewsets.base import generate_update_event
from contentcuration.tests.viewsets.base import SyncTestMixin
from contentcuration.viewsets.sync.constants import CHANNEL
@@ -273,6 +278,99 @@ def test_cannot_delete_some_channels(self):
self.assertTrue(models.Channel.objects.get(id=channel1.id).deleted)
self.assertFalse(models.Channel.objects.get(id=channel2.id).deleted)
+ @mock.patch("contentcuration.viewsets.channel.sync_channel")
+ def test_sync_channel_called_correctly(self, sync_channel_mock):
+ user = testdata.user()
+ channel = testdata.channel()
+ channel.editors.add(user)
+ channel_node = channel.main_tree.get_descendants().first()
+ channel_node.copy_to(target=channel.main_tree)
+
+ self.client.force_authenticate(user=user)
+ for i in range(1, 5):
+ sync_channel_mock.reset_mock()
+ args = [channel.id, False, False, False, False]
+ args[i] = True
+
+ response = self.sync_changes(
+ [
+ generate_sync_channel_event(*args)
+ ]
+ )
+
+ self.assertEqual(response.status_code, 200)
+ sync_channel_mock.assert_called_once()
+ self.assertEqual(sync_channel_mock.call_args.args[i], True)
+
+ def test_deploy_channel_event(self):
+ channel = testdata.channel()
+ user = testdata.user()
+ channel.editors.add(user)
+ self.client.force_authenticate(
+ user
+ ) # This will skip all authentication checks
+ channel.main_tree.refresh_from_db()
+
+ channel.staging_tree = cc.ContentNode(
+ kind_id=content_kinds.TOPIC, title="test", node_id="aaa"
+ )
+ channel.staging_tree.save()
+ channel.previous_tree = cc.ContentNode(
+ kind_id=content_kinds.TOPIC, title="test", node_id="bbb"
+ )
+ channel.previous_tree.save()
+ channel.chef_tree = cc.ContentNode(
+ kind_id=content_kinds.TOPIC, title="test", node_id="ccc"
+ )
+ channel.chef_tree.save()
+ channel.save()
+
+ self.contentnode = cc.ContentNode.objects.create(kind_id="video")
+
+ response = self.sync_changes(
+ [
+ generate_deploy_channel_event(channel.id, user.id)
+ ]
+ )
+
+ self.assertEqual(response.status_code, 200)
+ modified_channel = models.Channel.objects.get(id=channel.id)
+ self.assertEqual(modified_channel.main_tree, channel.staging_tree)
+ self.assertEqual(modified_channel.staging_tree, None)
+ self.assertEqual(modified_channel.previous_tree, channel.main_tree)
+
+ def test_deploy_with_staging_tree_None(self):
+ channel = testdata.channel()
+ user = testdata.user()
+ channel.editors.add(user)
+ self.client.force_authenticate(
+ user
+ ) # This will skip all authentication checks
+ channel.main_tree.refresh_from_db()
+
+ channel.staging_tree = None
+ channel.previous_tree = cc.ContentNode(
+ kind_id=content_kinds.TOPIC, title="test", node_id="bbb"
+ )
+ channel.previous_tree.save()
+ channel.chef_tree = cc.ContentNode(
+ kind_id=content_kinds.TOPIC, title="test", node_id="ccc"
+ )
+ channel.chef_tree.save()
+ channel.save()
+
+ self.contentnode = cc.ContentNode.objects.create(kind_id="video")
+ response = self.sync_changes(
+ [
+ generate_deploy_channel_event(channel.id, user.id)
+ ]
+ )
+ # Should raise validation error as staging tree was set to NONE
+ self.assertEqual(len(response.json()["errors"]), 1, response.content)
+ modified_channel = models.Channel.objects.get(id=channel.id)
+ self.assertNotEqual(modified_channel.main_tree, channel.staging_tree)
+ self.assertNotEqual(modified_channel.previous_tree, channel.main_tree)
+
class CRUDTestCase(StudioAPITestCase):
@property
diff --git a/contentcuration/contentcuration/tests/viewsets/test_file.py b/contentcuration/contentcuration/tests/viewsets/test_file.py
index 0fa5f146a6..2cd0740e59 100644
--- a/contentcuration/contentcuration/tests/viewsets/test_file.py
+++ b/contentcuration/contentcuration/tests/viewsets/test_file.py
@@ -443,3 +443,155 @@ def test_upload_url(self):
self.assertEqual(response.status_code, 200)
file = models.File.objects.get(checksum=self.file["checksum"])
self.assertEqual(10, file.duration)
+
+ def test_upload_url_doesnot_sets_contentnode(self):
+ self.client.force_authenticate(user=self.user)
+ response = self.client.post(reverse("file-upload-url"), self.file, format="json",)
+ file = models.File.objects.get(checksum=self.file["checksum"])
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(file.contentnode, None)
+
+ def test_duration_zero(self):
+ self.file["duration"] = 0
+
+ self.client.force_authenticate(user=self.user)
+ response = self.client.post(
+ reverse("file-upload-url"), self.file, format="json",
+ )
+
+ self.assertEqual(response.status_code, 400)
+
+
+class ContentIDTestCase(SyncTestMixin, StudioAPITestCase):
+ def setUp(self):
+ super(ContentIDTestCase, self).setUp()
+ self.channel = testdata.channel()
+ self.user = testdata.user()
+ self.channel.editors.add(self.user)
+ self.client.force_authenticate(user=self.user)
+
+ def _get_file_metadata(self):
+ return {
+ "size": 2500,
+ "checksum": uuid.uuid4().hex,
+ "name": "le_studio_file",
+ "file_format": file_formats.MP3,
+ "preset": format_presets.AUDIO,
+ }
+
+ def _upload_file_to_contentnode(self, file_metadata=None, contentnode_id=None):
+ """
+ This method mimics the frontend file upload process which is a two-step
+ process for the backend.
+ First, file's upload URL is fetched and then that file's ORM object is updated
+ to point to the contentnode.
+ """
+ file = file_metadata or self._get_file_metadata()
+ self.client.post(reverse("file-upload-url"), file, format="json",)
+ file_from_db = models.File.objects.get(checksum=file["checksum"])
+ self.sync_changes(
+ [generate_update_event(
+ file_from_db.id,
+ FILE,
+ {
+ "contentnode": contentnode_id or self.channel.main_tree.get_descendants().first().id
+ },
+ channel_id=self.channel.id)],)
+ file_from_db.refresh_from_db()
+ return file_from_db
+
+ def _delete_file_from_contentnode(self, file_from_db):
+ self.sync_changes(
+ [
+ generate_delete_event(file_from_db.id, FILE, channel_id=self.channel.id),
+ ],
+ )
+
+ def test_content_id__same_on_copy_file_node(self):
+ file = self._upload_file_to_contentnode()
+ file_contentnode_copy = file.contentnode.copy_to(target=self.channel.main_tree)
+
+ # Assert content_id same after copying.
+ file.contentnode.refresh_from_db()
+ file_contentnode_copy.refresh_from_db()
+ self.assertEqual(file.contentnode.content_id, file_contentnode_copy.content_id)
+
+ def test_content_id__changes_on_upload_file_to_node(self):
+ file = self._upload_file_to_contentnode()
+ file_contentnode_copy = file.contentnode.copy_to(target=self.channel.main_tree)
+
+ # Upload a new file to the copied contentnode.
+ self._upload_file_to_contentnode(contentnode_id=file_contentnode_copy.id)
+
+ # Assert after new file upload, content_id changes.
+ file.contentnode.refresh_from_db()
+ file_contentnode_copy.refresh_from_db()
+ self.assertNotEqual(file.contentnode.content_id, file_contentnode_copy.content_id)
+
+ def test_content_id__changes_on_delete_file_from_node(self):
+ file = self._upload_file_to_contentnode()
+ file_contentnode_copy = file.contentnode.copy_to(target=self.channel.main_tree)
+
+ # Delete file from the copied contentnode.
+ self._delete_file_from_contentnode(file_from_db=file_contentnode_copy.files.first())
+
+ # Assert after deleting file, content_id changes.
+ file.contentnode.refresh_from_db()
+ file_contentnode_copy.refresh_from_db()
+ self.assertNotEqual(file.contentnode.content_id, file_contentnode_copy.content_id)
+
+ def test_content_id__doesnot_changes_on_update_original_file_node(self):
+ file = self._upload_file_to_contentnode()
+ file.contentnode.copy_to(target=self.channel.main_tree)
+
+ # Upload and delete file from the original contentnode.
+ content_id_before_updates = file.contentnode.content_id
+ self._upload_file_to_contentnode(contentnode_id=file.contentnode.id)
+ self._delete_file_from_contentnode(file_from_db=file)
+
+ # Assert after changes to original contentnode, content_id remains same.
+ file.contentnode.refresh_from_db()
+ content_id_after_updates = file.contentnode.content_id
+ self.assertEqual(content_id_before_updates, content_id_after_updates)
+
+ def test_content_id__doesnot_update_if_unique(self):
+ file = self._upload_file_to_contentnode()
+ file_contentnode_copy = file.contentnode.copy_to(target=self.channel.main_tree)
+
+ # Upload a new file to the copied contentnode.
+ self._upload_file_to_contentnode(contentnode_id=file_contentnode_copy.id)
+ file_contentnode_copy.refresh_from_db()
+ content_id_after_first_update = file_contentnode_copy.content_id
+
+ # Upload another new file to the copied contentnode. At this point,
+ # the content_id of copied node is already unique so it should not be updated.
+ self._upload_file_to_contentnode(contentnode_id=file_contentnode_copy.id)
+ file_contentnode_copy.refresh_from_db()
+ content_id_after_second_update = file_contentnode_copy.content_id
+
+ self.assertEqual(content_id_after_first_update, content_id_after_second_update)
+
+ def test_content_id__thumbnails_dont_update_content_id(self):
+ file = self._upload_file_to_contentnode()
+ file_contentnode_copy = file.contentnode.copy_to(target=self.channel.main_tree)
+
+ thumbnail_file_meta_1 = self._get_file_metadata()
+ thumbnail_file_meta_2 = self._get_file_metadata()
+ thumbnail_file_meta_1.update({"preset": format_presets.AUDIO_THUMBNAIL, "file_format": file_formats.JPEG, })
+ thumbnail_file_meta_2.update({"preset": format_presets.AUDIO_THUMBNAIL, "file_format": file_formats.JPEG, })
+
+ # Upload thumbnail to original contentnode and copied contentnode.
+ # content_id should remain same for both these nodes.
+ original_node_content_id_before_upload = file.contentnode.content_id
+ copied_node_content_id_before_upload = file_contentnode_copy.content_id
+ self._upload_file_to_contentnode(file_metadata=thumbnail_file_meta_1, contentnode_id=file.contentnode.id)
+ self._upload_file_to_contentnode(file_metadata=thumbnail_file_meta_2, contentnode_id=file_contentnode_copy.id)
+
+ # Assert content_id is same after uploading thumbnails to nodes.
+ file.contentnode.refresh_from_db()
+ file_contentnode_copy.refresh_from_db()
+ original_node_content_id_after_upload = file.contentnode.content_id
+ copied_node_content_id_after_upload = file_contentnode_copy.content_id
+
+ self.assertEqual(original_node_content_id_before_upload, original_node_content_id_after_upload)
+ self.assertEqual(copied_node_content_id_before_upload, copied_node_content_id_after_upload)
diff --git a/contentcuration/contentcuration/urls.py b/contentcuration/contentcuration/urls.py
index 4f0e5358e6..bb03f3876e 100644
--- a/contentcuration/contentcuration/urls.py
+++ b/contentcuration/contentcuration/urls.py
@@ -20,13 +20,13 @@
from django.urls import path
from django.urls import re_path
from django.views.generic.base import RedirectView
+from kolibri_public.urls import urlpatterns as kolibri_public_urls
from rest_framework import routers
import contentcuration.views.admin as admin_views
import contentcuration.views.base as views
import contentcuration.views.internal as internal_views
import contentcuration.views.nodes as node_views
-import contentcuration.views.public as public_views
import contentcuration.views.settings as settings_views
import contentcuration.views.users as registration_views
import contentcuration.views.zip as zip_views
@@ -71,13 +71,14 @@ def get_redirect_url(self, *args, **kwargs):
urlpatterns = [
re_path(r'^api/', include(router.urls)),
re_path(r'^serviceWorker.js$', pwa.ServiceWorkerView.as_view(), name="service_worker"),
- re_path(r'^api/activate_channel$', views.activate_channel_endpoint, name='activate_channel'),
re_path(r'^healthz$', views.health, name='health'),
re_path(r'^stealthz$', views.stealth, name='stealth'),
re_path(r'^api/search/', include('search.urls'), name='search'),
re_path(r'^api/probers/get_prober_channel', views.get_prober_channel, name='get_prober_channel'),
re_path(r'^api/probers/publishing_status', views.publishing_status, name='publishing_status'),
re_path(r'^api/probers/celery_worker_status', views.celery_worker_status, name='celery_worker_status'),
+ re_path(r'^api/probers/task_queue_status', views.task_queue_status, name='task_queue_status'),
+ re_path(r'^api/probers/unapplied_changes_status', views.unapplied_changes_status, name='unapplied_changes_status'),
re_path(r'^api/sync/$', SyncView.as_view(), name="sync"),
]
@@ -89,12 +90,7 @@ def get_redirect_url(self, *args, **kwargs):
# Add public api endpoints
-urlpatterns += [
- re_path(r'^api/public/channel/(?P[^/]+)', public_views.get_channel_name_by_id, name='get_channel_name_by_id'),
- re_path(r'^api/public/(?P[^/]+)/channels$', public_views.get_public_channel_list, name='get_public_channel_list'),
- re_path(r'^api/public/(?P[^/]+)/channels/lookup/(?P[^/]+)', public_views.get_public_channel_lookup, name='get_public_channel_lookup'),
- re_path(r'^api/public/info', public_views.InfoViewSet.as_view({'get': 'list'}), name='info'),
-]
+urlpatterns += kolibri_public_urls
# Add node api enpoints
urlpatterns += [
@@ -127,7 +123,6 @@ def get_redirect_url(self, *args, **kwargs):
re_path(r'^api/internal/file_diff$', internal_views.file_diff, name="file_diff"),
re_path(r'^api/internal/file_upload$', internal_views.api_file_upload, name="api_file_upload"),
re_path(r'^api/internal/publish_channel$', internal_views.api_publish_channel, name="api_publish_channel"),
- re_path(r'^api/internal/activate_channel_internal$', internal_views.activate_channel_internal, name='activate_channel_internal'),
re_path(r'^api/internal/check_user_is_editor$', internal_views.check_user_is_editor, name='check_user_is_editor'),
re_path(r'^api/internal/get_tree_data$', internal_views.get_tree_data, name='get_tree_data'),
re_path(r'^api/internal/get_node_tree_data$', internal_views.get_node_tree_data, name='get_node_tree_data'),
@@ -145,10 +140,6 @@ def get_redirect_url(self, *args, **kwargs):
urlpatterns += [re_path(r'^jsreverse/$', django_js_reverse_views.urls_js, name='js_reverse')]
# I18N Endpoints
-js_info_dict = {
- 'packages': ('your.app.package',),
-}
-
urlpatterns += [
re_path(r'^i18n/', include('django.conf.urls.i18n')),
]
@@ -171,7 +162,8 @@ def get_redirect_url(self, *args, **kwargs):
re_path(r'^activate/(?P[-:\w]+)/$', registration_views.UserActivationView.as_view(), name='registration_activate'),
re_path(r'^api/send_invitation_email/$', registration_views.send_invitation_email, name='send_invitation_email'),
re_path(r'^new/accept_invitation/(?P[^/]+)/', registration_views.new_user_redirect, name="accept_invitation_and_registration"),
- re_path(r'^api/deferred_user_data/$', registration_views.deferred_user_data, name="deferred_user_data"),
+ re_path(r'^api/deferred_user_space_by_kind/$', registration_views.deferred_user_space_by_kind, name="deferred_user_space_by_kind"),
+ re_path(r'^api/deferred_user_api_token/$', registration_views.deferred_user_api_token, name="deferred_user_api_token"),
re_path(r'^settings/$', settings_views.settings, name='settings'),
re_path(r'^administration/', admin_views.administration, name='administration'),
re_path(r'^manifest.webmanifest$', pwa.ManifestView.as_view(), name="manifest"),
diff --git a/contentcuration/contentcuration/utils/automation_manager.py b/contentcuration/contentcuration/utils/automation_manager.py
new file mode 100644
index 0000000000..c609064fe7
--- /dev/null
+++ b/contentcuration/contentcuration/utils/automation_manager.py
@@ -0,0 +1,52 @@
+from contentcuration.utils.recommendations import RecommendationsAdapter
+from contentcuration.utils.recommendations import RecommendationsBackendFactory
+
+
+class AutomationManager:
+ def __init__(self):
+ self.recommendations_backend_factory = RecommendationsBackendFactory()
+ self.recommendations_backend_instance = self.recommendations_backend_factory.create_backend()
+ self.recommendations_backend_adapter = RecommendationsAdapter(self.recommendations_backend_instance)
+
+ def generate_embedding(self, text):
+ """
+ Generate an embedding vector for the given text.
+ Args:
+ text (str): The text for which to generate an embedding.
+ Returns:
+ Vector: The generated embedding vector.
+ """
+ embedding_vector = self.recommendations_backend_adapter.generate_embedding(text=text)
+ return embedding_vector
+
+ def embedding_exists(self, embedding):
+ """
+ Check if the given embedding vector exists.
+ Args:
+ embedding (Vector): The embedding vector to check.
+ Returns:
+ bool: True if the embedding exists, False otherwise.
+ """
+ return self.recommendations_backend_adapter.embedding_exists(embedding=embedding)
+
+ def load_recommendations(self, embedding):
+ """
+ Load recommendations based on the given embedding vector.
+ Args:
+ embedding (Vector): The embedding vector to use for recommendations.
+ Returns:
+ list: A list of recommended items.
+ """
+ # Need to extract the recommendation list from the ResponseObject and change the return statement
+ self.recommendations_backend_adapter.get_recommendations(embedding=embedding)
+ return []
+
+ def cache_embeddings(self, embeddings):
+ """
+ Cache a list of embedding vectors.
+ Args:
+ embeddings (list): A list of embedding vectors to cache.
+ Returns:
+ bool: True if caching was successful, False otherwise.
+ """
+ return self.recommendations_backend_adapter.cache_embeddings(embeddings)
diff --git a/contentcuration/contentcuration/utils/celery/app.py b/contentcuration/contentcuration/utils/celery/app.py
index 5870947bad..7eec641dc9 100644
--- a/contentcuration/contentcuration/utils/celery/app.py
+++ b/contentcuration/contentcuration/utils/celery/app.py
@@ -1,9 +1,11 @@
import base64
import json
+import redis.exceptions
from celery import Celery
from contentcuration.utils.celery.tasks import CeleryTask
+from contentcuration.utils.sentry import report_exception
class CeleryApp(Celery):
@@ -45,6 +47,45 @@ def get_queued_tasks(self, queue_name="celery"):
return decoded_tasks
+ def count_queued_tasks(self, queue_name="celery"):
+ """
+ :param queue_name: The queue name, defaults to the default "celery" queue
+ :return: int
+ """
+ count = 0
+ try:
+ with self.pool.acquire(block=True) as conn:
+ count = conn.default_channel.client.llen(queue_name)
+ except redis.exceptions.RedisError as e:
+ # log these so we can get visibility into the reliability of the redis connection
+ report_exception(e)
+ pass
+ return count
+
+ def get_active_and_reserved_tasks(self):
+ """
+ Iterate over active and reserved tasks
+ :return: A list of dictionaries
+ """
+ active = self.control.inspect().active() or {}
+ for _, tasks in active.items():
+ for task in tasks:
+ yield task
+ reserved = self.control.inspect().reserved() or {}
+ for _, tasks in reserved.items():
+ for task in tasks:
+ yield task
+
+ def get_active_tasks(self):
+ """
+ Iterate over active tasks
+ :return: A list of dictionaries
+ """
+ active = self.control.inspect().active() or {}
+ for _, tasks in active.items():
+ for task in tasks:
+ yield task
+
def decode_result(self, result, status=None):
"""
Decodes the celery result, like the raw result from the database, using celery tools
diff --git a/contentcuration/contentcuration/utils/celery/tasks.py b/contentcuration/contentcuration/utils/celery/tasks.py
index 3eb83d4180..df0f282304 100644
--- a/contentcuration/contentcuration/utils/celery/tasks.py
+++ b/contentcuration/contentcuration/utils/celery/tasks.py
@@ -1,11 +1,18 @@
+import contextlib
+import hashlib
import logging
import math
import uuid
+import zlib
+from collections import OrderedDict
from celery import states
from celery.app.task import Task
from celery.result import AsyncResult
+from django.db import transaction
+from contentcuration.constants.locking import TASK_LOCK
+from contentcuration.db.advisory_lock import advisory_lock
from contentcuration.utils.sentry import report_exception
@@ -61,9 +68,29 @@ def task_progress(self):
def get_task_model(ref, task_id):
"""
Returns the task model for a task, will create one if not found
- :rtype: contentcuration.models.TaskResult
+ :rtype: contentcuration.models.CustomTaskMetadata
"""
- return ref.backend.TaskModel.objects.get_task(task_id)
+ from contentcuration.models import CustomTaskMetadata
+ try:
+ return CustomTaskMetadata.objects.get(task_id=task_id)
+ except CustomTaskMetadata.DoesNotExist:
+ return None
+
+
+def generate_task_signature(task_name, task_kwargs=None, channel_id=None):
+ """
+ :type task_name: str
+ :param task_kwargs: the celery encoded/serialized form of the task_kwargs dict
+ :type task_kwargs: str|None
+ :type channel_id: str|None
+ :return: A hex string, md5
+ :rtype: str
+ """
+ md5 = hashlib.md5()
+ md5.update(task_name.encode('utf-8'))
+ md5.update((task_kwargs or '').encode('utf-8'))
+ md5.update((channel_id or '').encode('utf-8'))
+ return md5.hexdigest()
class CeleryTask(Task):
@@ -81,10 +108,8 @@ def my_task(self):
track_started = True
send_events = True
- # ensure our tasks are restarted if they're interrupted
- acks_late = True
- acks_on_failure_or_timeout = True
- reject_on_worker_lost = True
+ # Tasks are acknowledged just before they start executing
+ acks_late = False
@property
def TaskModel(self):
@@ -107,35 +132,61 @@ def shadow_name(self, *args, **kwargs):
"""
return super(CeleryTask, self).shadow_name(*args, **kwargs)
- def find_ids(self, channel_id=None, **kwargs):
+ def _prepare_kwargs(self, kwargs):
"""
- :param channel_id:
- :param kwargs: Keyword arguments sent to the task, which will be matched against
- :return: A TaskResult queryset
- :rtype: django.db.models.query.QuerySet
+ Prepares kwargs, converting UUID to their hex value
"""
- task_qs = self.TaskModel.objects.filter(task_name=self.name)
+ return OrderedDict(
+ (key, value.hex if isinstance(value, uuid.UUID) else value)
+ for key, value in kwargs.items()
+ )
- # add channel filter since we have dedicated field
- if channel_id:
- task_qs = task_qs.filter(channel_id=channel_id)
- else:
- task_qs = task_qs.filter(channel_id__isnull=True)
+ def generate_signature(self, kwargs):
+ """
+ :param kwargs: A dictionary of task kwargs
+ :return: An hex string representing an md5 hash of task metadata
+ """
+ prepared_kwargs = self._prepare_kwargs(kwargs)
+ return generate_task_signature(
+ self.name,
+ task_kwargs=self.backend.encode(prepared_kwargs),
+ channel_id=prepared_kwargs.get('channel_id')
+ )
- # search for task args in values
- for value in kwargs.values():
- task_qs = task_qs.filter(task_kwargs__contains=self.backend.encode(value))
+ @contextlib.contextmanager
+ def _lock_signature(self, signature):
+ """
+ Opens a transaction and creates an advisory lock for its duration, based off a crc32 hash to convert
+ the signature into an integer which postgres' lock function require
+ :param signature: An hex string representing an md5 hash of task metadata
+ """
+ with transaction.atomic():
+ # compute crc32 to turn signature into integer
+ key2 = zlib.crc32(signature.encode('utf-8'))
+ advisory_lock(TASK_LOCK, key2=key2)
+ yield
- return task_qs.values_list("task_id", flat=True)
+ def find_ids(self, signature):
+ """
+ :param signature: An hex string representing an md5 hash of task metadata
+ :return: A CustomTaskMetadata queryset
+ :rtype: django.db.models.query.QuerySet
+ """
+ from contentcuration.models import CustomTaskMetadata
+ return CustomTaskMetadata.objects.filter(signature=signature)\
+ .values_list("task_id", flat=True)
- def find_incomplete_ids(self, channel_id=None, **kwargs):
+ def find_incomplete_ids(self, signature):
"""
- :param channel_id:
- :param kwargs:
+ :param signature: An hex string representing an md5 hash of task metadata
:return: A TaskResult queryset
:rtype: django.db.models.query.QuerySet
"""
- return self.find_ids(channel_id=channel_id, **kwargs).exclude(status__in=states.READY_STATES)
+ from django_celery_results.models import TaskResult
+ # Get the filtered task_ids from CustomTaskMetadata model
+ filtered_task_ids = self.find_ids(signature)
+ task_objects_ids = TaskResult.objects.filter(task_id__in=filtered_task_ids, status__in=states.UNREADY_STATES).values_list("task_id", flat=True)
+ return task_objects_ids
def fetch(self, task_id):
"""
@@ -146,24 +197,9 @@ def fetch(self, task_id):
"""
return self.AsyncResult(task_id)
- def fetch_match(self, task_id, **kwargs):
- """
- Gets the result object for a task, assuming it was called async, and ensures it was called with kwargs
- :param task_id: The hex task ID
- :param kwargs: The kwargs the task was called with, which must match when fetching
- :return: A CeleryAsyncResult
- :rtype: CeleryAsyncResult
- """
- async_result = self.fetch(task_id)
- # the task kwargs are serialized in the DB so just ensure that args actually match
- if async_result.kwargs == kwargs:
- return async_result
- return None
-
def enqueue(self, user, **kwargs):
"""
- Enqueues the task called with `kwargs`, and requires the user who wants to enqueue it. If `channel_id` is
- passed to the function, that will be set on the TaskResult model as well.
+ Enqueues the task called with `kwargs`, and requires the user who wants to enqueue it.
:param user: User object of the user performing the operation
:param kwargs: Keyword arguments for task `apply_async`
@@ -171,33 +207,43 @@ def enqueue(self, user, **kwargs):
:rtype: CeleryAsyncResult
"""
from contentcuration.models import User
+ from contentcuration.models import CustomTaskMetadata
if user is None or not isinstance(user, User):
raise TypeError("All tasks must be assigned to a user.")
+ signature = kwargs.pop('signature', None)
+ if signature is None:
+ signature = self.generate_signature(kwargs)
+
task_id = uuid.uuid4().hex
- channel_id = kwargs.get("channel_id")
+ prepared_kwargs = self._prepare_kwargs(kwargs)
+ channel_id = prepared_kwargs.get("channel_id")
+ custom_task_result = CustomTaskMetadata(
+ task_id=task_id,
+ user=user,
+ signature=signature,
+ channel_id=channel_id
+ )
+ custom_task_result.save()
- logging.info(f"Enqueuing task:id {self.name}:{task_id} for user:channel {user.pk}:{channel_id} | {kwargs}")
+ logging.info(f"Enqueuing task:id {self.name}:{task_id} for user:channel {user.pk}:{channel_id} | {signature}")
# returns a CeleryAsyncResult
async_result = self.apply_async(
task_id=task_id,
- kwargs=kwargs,
+ task_name=self.name,
+ kwargs=prepared_kwargs,
)
+
# ensure the result is saved to the backend (database)
self.backend.add_pending_result(async_result)
- # after calling apply, we should have task result model, so get it and set our custom fields
- task_result = get_task_model(self, task_id)
- task_result.user = user
- task_result.channel_id = channel_id
- task_result.save()
return async_result
def fetch_or_enqueue(self, user, **kwargs):
"""
Fetches an existing incomplete task or enqueues one if not found, called with `kwargs`, and requires the user
- who wants to enqueue it. If `channel_id` is passed to the function, that will be set on the TaskResult model
+ who wants to enqueue it. If `channel_id` is passed to the function, that will be set on the CustomTaskMetadata model
:param user: User object of the user performing the operation
:param kwargs: Keyword arguments for task `apply_async`
@@ -206,15 +252,24 @@ def fetch_or_enqueue(self, user, **kwargs):
"""
# if we're eagerly executing the task (synchronously), then we shouldn't check for an existing task because
# implementations probably aren't prepared to rely on an existing asynchronous task
- if not self.app.conf.task_always_eager:
- task_ids = self.find_incomplete_ids(**kwargs).order_by("date_created")[:1]
+ if self.app.conf.task_always_eager:
+ return self.enqueue(user, **kwargs)
+
+ signature = self.generate_signature(kwargs)
+
+ # create an advisory lock to obtain exclusive control on preventing task duplicates
+ with self._lock_signature(signature):
+ # order by most recently created
+ task_ids = self.find_incomplete_ids(signature).order_by("-date_created")[:1]
if task_ids:
- async_result = self.fetch_match(task_ids[0], **kwargs)
- if async_result:
- logging.info(f"Fetched matching task {self.name} for user {user.pk} with id {async_result.id} | {kwargs}")
+ async_result = self.fetch(task_ids[0])
+ # double check
+ if async_result and async_result.status not in states.READY_STATES:
+ logging.info(f"Fetched matching task {self.name} for user {user.pk} with id {async_result.id} | {signature}")
return async_result
- logging.info(f"Didn't fetch matching task {self.name} for user {user.pk} | {kwargs}")
- return self.enqueue(user, **kwargs)
+ logging.info(f"Didn't fetch matching task {self.name} for user {user.pk} | {signature}")
+ kwargs.update(signature=signature)
+ return self.enqueue(user, **kwargs)
def requeue(self, **kwargs):
"""
@@ -223,38 +278,68 @@ def requeue(self, **kwargs):
:return: The celery async result
:rtype: CeleryAsyncResult
"""
+ from contentcuration.models import CustomTaskMetadata
request = self.request
if request is None:
raise NotImplementedError("This method should only be called within the execution of a task")
- task_result = get_task_model(self, request.id)
task_kwargs = request.kwargs.copy()
task_kwargs.update(kwargs)
- logging.info(f"Re-queuing task {self.name} for user {task_result.user.pk} from {request.id} | {task_kwargs}")
- return self.enqueue(task_result.user, **task_kwargs)
+ signature = self.generate_signature(kwargs)
+ custom_task_metadata = CustomTaskMetadata.objects.get(task_id=request.id)
+ logging.info(f"Re-queuing task {self.name} for user {custom_task_metadata.user.pk} from {request.id} | {signature}")
+ return self.enqueue(custom_task_metadata.user, signature=signature, **task_kwargs)
+
+ def revoke(self, exclude_task_ids=None, **kwargs):
+ """
+ Revokes and terminates all unready tasks matching the kwargs
+ :param exclude_task_ids: Any task ids to exclude from this behavior
+ :param kwargs: Task keyword arguments that will be used to match against tasks
+ :return: The number of tasks revoked
+ """
+ from django_celery_results.models import TaskResult
+ signature = self.generate_signature(kwargs)
+ task_ids = self.find_incomplete_ids(signature)
+
+ if exclude_task_ids is not None:
+ task_ids = task_ids.exclude(task_id__in=exclude_task_ids)
+ count = 0
+ for task_id in task_ids:
+ logging.info(f"Revoking task {task_id}")
+ self.app.control.revoke(task_id, terminate=True)
+ count += 1
+ # be sure the database backend has these marked appropriately
+ TaskResult.objects.filter(task_id__in=task_ids).update(status=states.REVOKED)
+ return count
class CeleryAsyncResult(AsyncResult):
"""
Custom result class which has access to task data stored in the backend
-
- The properties access additional properties in the same manner as super properties,
- and our custom properties are added to the meta via TaskResultCustom.as_dict()
+ We access those from the CustomTaskMetadata model.
"""
+
+ _cached_model = None
+
def get_model(self):
"""
- :return: The TaskResult model object
- :rtype: contentcuration.models.TaskResult
+ :return: The CustomTaskMetadatamodel object
+ :rtype: contentcuration.models.CustomTaskMetadata
"""
- return get_task_model(self, self.task_id)
+ if self._cached_model is None:
+ self._cached_model = get_task_model(self, self.task_id)
+ return self._cached_model
@property
def user_id(self):
- return self._get_task_meta().get('user_id')
+ if self.get_model():
+ return self.get_model().user_id
@property
def channel_id(self):
- return self._get_task_meta().get('channel_id')
+ if self.get_model():
+ return self.get_model().channel_id
@property
def progress(self):
- return self._get_task_meta().get('progress')
+ if self.get_model():
+ return self.get_model().progress
diff --git a/contentcuration/contentcuration/utils/cloud_storage.py b/contentcuration/contentcuration/utils/cloud_storage.py
new file mode 100644
index 0000000000..bf60b51bb3
--- /dev/null
+++ b/contentcuration/contentcuration/utils/cloud_storage.py
@@ -0,0 +1,40 @@
+from automation.utils.appnexus.base import Backend
+from automation.utils.appnexus.base import BackendFactory
+from automation.utils.appnexus.base import BackendRequest
+from automation.utils.appnexus.base import BackendResponse
+
+
+class CloudStorageBackendRequest(BackendRequest):
+ pass
+
+
+class CloudStorageRequest(CloudStorageBackendRequest):
+ def __init__(self) -> None:
+ super().__init__()
+
+
+class CloudStorageBackendResponse(BackendResponse):
+ pass
+
+
+class CloudStorageResponse(CloudStorageBackendResponse):
+ def __init__(self) -> None:
+ pass
+
+
+class CloudStorageBackendFactory(BackendFactory):
+ def create_backend(self) -> Backend:
+ return super().create_backend()
+
+
+class CloudStorage(Backend):
+
+ def connect(self) -> None:
+ return super().connect()
+
+ def make_request(self, request) -> CloudStorageResponse:
+ return super().make_request(request)
+
+ @classmethod
+ def _create_instance(cls) -> 'CloudStorage':
+ return cls()
diff --git a/contentcuration/contentcuration/utils/files.py b/contentcuration/contentcuration/utils/files.py
index 217b6b3c3a..1d1125293a 100644
--- a/contentcuration/contentcuration/utils/files.py
+++ b/contentcuration/contentcuration/utils/files.py
@@ -117,7 +117,7 @@ def get_thumbnail_encoding(filename, dimension=THUMBNAIL_WIDTH):
# aspect ratio. So a square image will remain square rather
# than being distorted to a 16:9 aspect ratio. This removes
# the need to make any changes like cropping the image.
- image.thumbnail(thumbnail_size, Image.ANTIALIAS)
+ image.thumbnail(thumbnail_size, Image.LANCZOS)
image.save(outbuffer, image_format)
return "data:image/{};base64,{}".format(ext[1:], base64.b64encode(outbuffer.getvalue()).decode('utf-8'))
diff --git a/contentcuration/contentcuration/utils/garbage_collect.py b/contentcuration/contentcuration/utils/garbage_collect.py
index 3343013b7c..805a114231 100755
--- a/contentcuration/contentcuration/utils/garbage_collect.py
+++ b/contentcuration/contentcuration/utils/garbage_collect.py
@@ -7,19 +7,26 @@
from celery import states
from django.conf import settings
+from django.core.files.storage import default_storage
+from django.db.models import Subquery
from django.db.models.expressions import CombinedExpression
+from django.db.models.expressions import Exists
from django.db.models.expressions import F
+from django.db.models.expressions import OuterRef
from django.db.models.expressions import Value
from django.db.models.signals import post_delete
from django.utils.timezone import now
+from django_celery_results.models import TaskResult
from le_utils.constants import content_kinds
from contentcuration.constants import feature_flags
+from contentcuration.constants import user_history
from contentcuration.db.models.functions import JSONObjectKeys
from contentcuration.models import ContentNode
+from contentcuration.models import CustomTaskMetadata
from contentcuration.models import File
-from contentcuration.models import TaskResult
from contentcuration.models import User
+from contentcuration.models import UserHistory
class DisablePostDeleteSignal(object):
@@ -42,6 +49,48 @@ def get_deleted_chefs_root():
return deleted_chefs_node
+def _clean_up_files(contentnode_ids):
+ """
+ Clean up the files (both in the DB and in object storage)
+ associated with the `contentnode_ids` iterable that are
+ not pointed by any other contentnode.
+ """
+ files = File.objects.filter(contentnode__in=contentnode_ids)
+ files_on_storage = files.values_list("file_on_disk", flat=True)
+
+ for disk_file_path in files_on_storage:
+ is_other_node_pointing = Exists(File.objects.filter(file_on_disk=disk_file_path).exclude(contentnode__in=contentnode_ids))
+ if not is_other_node_pointing:
+ default_storage.delete(disk_file_path)
+
+ # use _raw_delete for much fast file deletions
+ files._raw_delete(files.db)
+
+
+def clean_up_soft_deleted_users():
+ """
+ Hard deletes user related data for soft deleted users that are older than ACCOUNT_DELETION_BUFFER.
+
+ Note: User record itself is not hard deleted.
+
+ User related data that gets hard deleted are:
+ - sole editor non-public channels.
+ - sole editor non-public channelsets.
+ - sole editor non-public channels' content nodes and its underlying files that are not
+ used by any other channel.
+ - all user invitations.
+ """
+ account_deletion_buffer_delta = now() - datetime.timedelta(days=settings.ACCOUNT_DELETION_BUFFER)
+ user_latest_deletion_time_subquery = Subquery(UserHistory.objects.filter(user_id=OuterRef(
+ "id"), action=user_history.DELETION).values("performed_at").order_by("-performed_at")[:1])
+ users_to_delete = User.objects.annotate(latest_deletion_time=user_latest_deletion_time_subquery).filter(
+ deleted=True, latest_deletion_time__lt=account_deletion_buffer_delta)
+
+ for user in users_to_delete:
+ user.hard_delete_user_related_data()
+ logging.info("Hard deleted user related data for user {}.".format(user.email))
+
+
def clean_up_deleted_chefs():
"""
Clean up all deleted chefs attached to the deleted chefs tree, including all
@@ -81,7 +130,7 @@ def clean_up_contentnodes(delete_older_than=settings.ORPHAN_DATE_CLEAN_UP_THRESH
# delete all files first
with DisablePostDeleteSignal():
- clean_up_files(nodes_to_clean_up)
+ _clean_up_files(nodes_to_clean_up)
# Use _raw_delete for fast bulk deletions
try:
@@ -92,32 +141,6 @@ def clean_up_contentnodes(delete_older_than=settings.ORPHAN_DATE_CLEAN_UP_THRESH
pass
-def clean_up_files(contentnode_ids):
- """
- Clean up the files (both in the DB and in object storage)
- associated with the contentnode_ids given in the `contentnode_ids`
- iterable.
- """
-
- # get all file objects associated with these contentnodes
- files = File.objects.filter(contentnode__in=contentnode_ids)
- # get all their associated real files in object storage
- files_on_storage = files.values_list("file_on_disk")
- for f in files_on_storage:
- # values_list returns each set of items in a tuple, even
- # if there's only one item in there. Extract the file_on_disk
- # string value from inside that singleton tuple
- f[0]
- # NOTE (aron):call the storage's delete method on each file, one by one
- # disabled for now until we implement logic to not delete files
- # that are referenced by non-orphan nodes
- # storage.delete(file_path)
-
- # finally, remove the entries from object storage
- # use _raw_delete for much fast file deletions
- files._raw_delete(files.db)
-
-
def clean_up_feature_flags():
"""
Removes lingering feature flag settings in User records that aren't currently present in the
@@ -142,8 +165,10 @@ def clean_up_tasks():
"""
with DisablePostDeleteSignal():
date_cutoff = now() - datetime.timedelta(days=7)
- count, _ = TaskResult.objects.filter(date_done__lt=date_cutoff, status__in=states.READY_STATES).delete()
+ tasks_to_delete = TaskResult.objects.filter(date_done__lt=date_cutoff, status__in=states.READY_STATES)
+ CustomTaskMetadata.objects.filter(task_id__in=tasks_to_delete.values_list("task_id", flat=True)).delete()
+ count, _ = tasks_to_delete.delete()
logging.info("Deleted {} completed task(s) from the task table".format(count))
diff --git a/contentcuration/contentcuration/utils/nodes.py b/contentcuration/contentcuration/utils/nodes.py
index 2a1c2dad1a..61491305d1 100644
--- a/contentcuration/contentcuration/utils/nodes.py
+++ b/contentcuration/contentcuration/utils/nodes.py
@@ -83,6 +83,12 @@ def map_files_to_node(user, node, data): # noqa: C901
duration=file_data.get("duration"),
)
resource_obj.file_on_disk.name = file_path
+
+ if kind_preset and kind_preset.thumbnail:
+ # If this is a thumbnail, delete any other thumbnails that are
+ # already attached to the file.
+ node.files.filter(preset=kind_preset).delete()
+
resource_obj.save()
# Handle thumbnail
diff --git a/contentcuration/contentcuration/utils/pagination.py b/contentcuration/contentcuration/utils/pagination.py
index 0c67b560fe..d8da8699cc 100644
--- a/contentcuration/contentcuration/utils/pagination.py
+++ b/contentcuration/contentcuration/utils/pagination.py
@@ -1,4 +1,7 @@
import hashlib
+from base64 import b64encode
+from collections import OrderedDict
+from urllib.parse import urlencode
from django.core.cache import cache
from django.core.exceptions import EmptyResultSet
@@ -7,6 +10,8 @@
from django.core.paginator import Paginator
from django.db.models import QuerySet
from django.utils.functional import cached_property
+from rest_framework.pagination import _reverse_ordering
+from rest_framework.pagination import CursorPagination
from rest_framework.pagination import NotFound
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
@@ -129,3 +134,114 @@ def get_paginated_response_schema(self, schema):
class CachedListPagination(ValuesViewsetPageNumberPagination):
django_paginator_class = CachedValuesViewsetPaginator
+
+
+class ValuesViewsetCursorPagination(CursorPagination):
+ def paginate_queryset(self, queryset, request, view=None):
+ pks_queryset = super(ValuesViewsetCursorPagination, self).paginate_queryset(
+ queryset, request, view=view
+ )
+ if pks_queryset is None:
+ return None
+ self.request = request
+ if self.cursor is None:
+ reverse = False
+ else:
+ _, reverse, _ = self.cursor
+ ordering = _reverse_ordering(self.ordering) if reverse else self.ordering
+ return queryset.filter(pk__in=[obj.pk for obj in pks_queryset]).order_by(
+ *ordering
+ )
+
+ def get_more(self): # noqa C901
+ """
+ Vendored and modified from
+ https://github.com/encode/django-rest-framework/blob/6ea95b6ad1bc0d4a4234a267b1ba32701878c6bb/rest_framework/pagination.py#L694
+ """
+ if not self.has_next:
+ return None
+
+ if (
+ self.page
+ and self.cursor
+ and self.cursor.reverse
+ and self.cursor.offset != 0
+ ):
+ # If we're reversing direction and we have an offset cursor
+ # then we cannot use the first position we find as a marker.
+ compare = self._get_position_from_instance(self.page[-1], self.ordering)
+ else:
+ compare = self.next_position
+ offset = 0
+
+ has_item_with_unique_position = False
+ for item in reversed(self.page):
+ position = self._get_position_from_instance(item, self.ordering)
+ if position != compare:
+ # The item in this position and the item following it
+ # have different positions. We can use this position as
+ # our marker.
+ has_item_with_unique_position = True
+ break
+
+ # The item in this position has the same position as the item
+ # following it, we can't use it as a marker position, so increment
+ # the offset and keep seeking to the previous item.
+ compare = position
+ offset += 1
+
+ if self.page and not has_item_with_unique_position:
+ # There were no unique positions in the page.
+ if not self.has_previous:
+ # We are on the first page.
+ # Our cursor will have an offset equal to the page size,
+ # but no position to filter against yet.
+ offset = self.page_size
+ position = None
+ elif self.cursor.reverse:
+ # The change in direction will introduce a paging artifact,
+ # where we end up skipping forward a few extra items.
+ offset = 0
+ position = self.previous_position
+ else:
+ # Use the position from the existing cursor and increment
+ # it's offset by the page size.
+ offset = self.cursor.offset + self.page_size
+ position = self.previous_position
+
+ if not self.page:
+ position = self.next_position
+
+ tokens = {}
+ if offset != 0:
+ tokens["o"] = str(offset)
+ if position is not None:
+ tokens["p"] = position
+
+ querystring = urlencode(tokens, doseq=True)
+ encoded = b64encode(querystring.encode("ascii")).decode("ascii")
+ params = self.request.query_params.copy()
+ params.update(
+ {
+ self.cursor_query_param: encoded,
+ }
+ )
+ return params
+
+ def get_paginated_response(self, data):
+ return Response(OrderedDict([("more", self.get_more()), ("results", data)]))
+
+ def get_paginated_response_schema(self, schema):
+ return {
+ "type": "object",
+ "properties": {
+ "more": {
+ "type": "object",
+ "nullable": True,
+ "example": {
+ "cursor": "asdadshjashjadh",
+ },
+ },
+ "results": schema,
+ },
+ }
diff --git a/contentcuration/contentcuration/utils/publish.py b/contentcuration/contentcuration/utils/publish.py
index 93bee0bf40..5bfddf4b7b 100644
--- a/contentcuration/contentcuration/utils/publish.py
+++ b/contentcuration/contentcuration/utils/publish.py
@@ -11,6 +11,7 @@
import uuid
import zipfile
from builtins import str
+from copy import deepcopy
from itertools import chain
from django.conf import settings
@@ -31,8 +32,10 @@
from django.utils.translation import gettext_lazy as _
from django.utils.translation import override
from kolibri_content import models as kolibrimodels
+from kolibri_content.base_models import MAX_TAG_LENGTH
from kolibri_content.router import get_active_content_database
from kolibri_content.router import using_content_database
+from kolibri_public.utils.mapper import ChannelMapper
from le_utils.constants import completion_criteria
from le_utils.constants import content_kinds
from le_utils.constants import exercises
@@ -71,6 +74,10 @@ class NoNodesChangedError(Exception):
pass
+class ChannelIncompleteError(Exception):
+ pass
+
+
class SlowPublishError(Exception):
"""
Used to track slow Publishing operations. We don't raise this error,
@@ -139,12 +146,15 @@ def create_content_database(channel, force, user_id, force_exercises, progress_t
progress_tracker=progress_tracker,
)
tree_mapper.map_nodes()
- map_channel_to_kolibri_channel(channel)
+ kolibri_channel = map_channel_to_kolibri_channel(channel)
# It should be at this percent already, but just in case.
if progress_tracker:
progress_tracker.track(90)
map_prerequisites(channel.main_tree)
save_export_database(channel.pk)
+ if channel.public:
+ mapper = ChannelMapper(kolibri_channel)
+ mapper.run()
return tempdb
@@ -189,8 +199,8 @@ def __init__(
force_exercises=False,
progress_tracker=None,
):
- if not root_node.complete:
- raise ValueError("Attempted to publish a channel with an incomplete root node")
+ if not root_node.is_publishable():
+ raise ChannelIncompleteError("Attempted to publish a channel with an incomplete root node or no resources")
self.root_node = root_node
task_percent_total = 80.0
@@ -214,7 +224,18 @@ def recurse_nodes(self, node, inherited_fields): # noqa C901
logging.debug("Mapping node with id {id}".format(id=node.pk))
# Only process nodes that are either non-topics or have non-topic descendants
- if node.get_descendants(include_self=True).exclude(kind_id=content_kinds.TOPIC).exists() and node.complete:
+ if node.is_publishable():
+ # early validation to make sure we don't have any exercises without mastery models
+ # which should be unlikely when the node is complete, but just in case
+ if node.kind_id == content_kinds.EXERCISE:
+ try:
+ # migrates and extracts the mastery model from the exercise
+ _, mastery_model = parse_assessment_metadata(node)
+ if not mastery_model:
+ raise ValueError("Exercise does not have a mastery model")
+ except Exception as e:
+ logging.warning("Unable to parse exercise {id} mastery model: {error}".format(id=node.pk, error=str(e)))
+ return
metadata = {}
@@ -236,12 +257,12 @@ def recurse_nodes(self, node, inherited_fields): # noqa C901
kolibrinode = create_bare_contentnode(node, self.default_language, self.channel_id, self.channel_name, metadata)
- if node.kind.kind == content_kinds.EXERCISE:
+ if node.kind_id == content_kinds.EXERCISE:
exercise_data = process_assessment_metadata(node, kolibrinode)
if self.force_exercises or node.changed or not \
node.files.filter(preset_id=format_presets.EXERCISE).exists():
create_perseus_exercise(node, kolibrinode, exercise_data, user_id=self.user_id)
- elif node.kind.kind == content_kinds.SLIDESHOW:
+ elif node.kind_id == content_kinds.SLIDESHOW:
create_slideshow_manifest(node, user_id=self.user_id)
elif node.kind_id == content_kinds.TOPIC:
for child in node.children.all():
@@ -345,7 +366,7 @@ def create_bare_contentnode(ccnode, default_language, channel_id, channel_name,
'license_description': kolibri_license.license_description if kolibri_license is not None else None,
'coach_content': ccnode.role_visibility == roles.COACH,
'duration': duration,
- 'options': json.dumps(options),
+ 'options': options,
# Fields for metadata labels
"grade_levels": ",".join(grade_levels.keys()) if grade_levels else None,
"resource_types": ",".join(resource_types.keys()) if resource_types else None,
@@ -480,18 +501,23 @@ def create_perseus_exercise(ccnode, kolibrinode, exercise_data, user_id=None):
temppath and os.unlink(temppath)
-def process_assessment_metadata(ccnode, kolibrinode):
- # Get mastery model information, set to default if none provided
- assessment_items = ccnode.assessment_items.all().order_by('order')
+def parse_assessment_metadata(ccnode):
extra_fields = ccnode.extra_fields
if isinstance(extra_fields, basestring):
extra_fields = json.loads(extra_fields)
extra_fields = migrate_extra_fields(extra_fields) or {}
randomize = extra_fields.get('randomize') if extra_fields.get('randomize') is not None else True
+ return randomize, extra_fields.get('options').get('completion_criteria').get('threshold')
+
+
+def process_assessment_metadata(ccnode, kolibrinode):
+ # Get mastery model information, set to default if none provided
+ assessment_items = ccnode.assessment_items.all().order_by('order')
assessment_item_ids = [a.assessment_id for a in assessment_items]
- exercise_data = extra_fields.get('options').get('completion_criteria').get('threshold')
+ randomize, mastery_criteria = parse_assessment_metadata(ccnode)
+ exercise_data = deepcopy(mastery_criteria)
exercise_data_type = exercise_data.get('mastery_model', "")
mastery_model = {'type': exercise_data_type or exercises.M_OF_N}
@@ -522,9 +548,9 @@ def process_assessment_metadata(ccnode, kolibrinode):
kolibrimodels.AssessmentMetaData.objects.create(
id=uuid.uuid4(),
contentnode=kolibrinode,
- assessment_item_ids=json.dumps(assessment_item_ids),
+ assessment_item_ids=assessment_item_ids,
number_of_assessments=assessment_items.count(),
- mastery_model=json.dumps(mastery_model),
+ mastery_model=mastery_model,
randomize=randomize,
is_manipulable=ccnode.kind_id == content_kinds.EXERCISE,
)
@@ -756,7 +782,7 @@ def map_tags_to_node(kolibrinode, ccnode):
for tag in ccnode.tags.all():
t, _new = kolibrimodels.ContentTag.objects.get_or_create(pk=tag.pk, tag_name=tag.tag_name)
- if len(t.tag_name) <= 30:
+ if len(t.tag_name) <= MAX_TAG_LENGTH:
tags_to_add.append(t)
kolibrinode.tags.set(tags_to_add)
@@ -869,10 +895,7 @@ def sync_contentnode_and_channel_tsvectors(channel_id):
# Insert newly created nodes.
# "set_contentnode_tsvectors" command is defined in "search/management/commands" directory.
- call_command("set_contentnode_tsvectors",
- "--channel-id={}".format(channel_id),
- "--tree-id={}".format(channel["main_tree__tree_id"]),
- "--complete")
+ call_command("set_contentnode_tsvectors", "--channel-id={}".format(channel_id))
@delay_user_storage_calculation
diff --git a/contentcuration/contentcuration/utils/recommendations.py b/contentcuration/contentcuration/utils/recommendations.py
new file mode 100644
index 0000000000..8fd551da15
--- /dev/null
+++ b/contentcuration/contentcuration/utils/recommendations.py
@@ -0,0 +1,84 @@
+from typing import Union
+
+from automation.utils.appnexus.base import Adapter
+from automation.utils.appnexus.base import Backend
+from automation.utils.appnexus.base import BackendFactory
+from automation.utils.appnexus.base import BackendRequest
+from automation.utils.appnexus.base import BackendResponse
+
+
+class RecommendationsBackendRequest(BackendRequest):
+ pass
+
+
+class RecommedationsRequest(RecommendationsBackendRequest):
+ def __init__(self) -> None:
+ super().__init__()
+
+
+class EmbeddingsRequest(RecommendationsBackendRequest):
+ def __init__(self) -> None:
+ super().__init__()
+
+
+class RecommendationsBackendResponse(BackendResponse):
+ pass
+
+
+class RecommendationsResponse(RecommendationsBackendResponse):
+ def __init__(self) -> None:
+ pass
+
+
+class EmbeddingsResponse(RecommendationsBackendResponse):
+ def __init__(self) -> None:
+ pass
+
+
+class RecommendationsBackendFactory(BackendFactory):
+ def create_backend(self) -> Backend:
+ # Return backend based on some setting.
+ return super().create_backend()
+
+
+class RecommendationsAdapter(Adapter):
+
+ def generate_embedding(self, text) -> EmbeddingsResponse:
+ request = EmbeddingsRequest()
+ return self.backend.make_request(request)
+
+ def embedding_exists(self, embedding) -> bool:
+ # Need to implement the logic to check if the embeddigns exist
+ # Return True if the embedding exists, or False otherwise
+ return True
+
+ def cache_embeddings(self, embeddings_list) -> bool:
+ for embedding in embeddings_list:
+ try:
+ # Attempt to cache the embedding
+ # Write the caching logic
+ # A conrner case to look at here is if one of the embedding fails to get cached
+ # we need to handel it so that only the once that were not succesfull
+ # are attempted to cache again
+ pass
+ except Exception as e:
+ print(e)
+ return False
+ return True
+
+ def get_recommendations(self, embedding) -> RecommendationsResponse:
+ request = RecommedationsRequest(embedding)
+ return self.backend.make_request(request)
+
+
+class Recommendations(Backend):
+
+ def connect(self) -> None:
+ return super().connect()
+
+ def make_request(self, request) -> Union[EmbeddingsResponse, RecommendationsResponse]:
+ return super().make_request(request)
+
+ @classmethod
+ def _create_instance(cls) -> 'Recommendations':
+ return cls()
diff --git a/contentcuration/contentcuration/utils/secretmanagement.py b/contentcuration/contentcuration/utils/secretmanagement.py
index 912c803681..c17b8c00b1 100644
--- a/contentcuration/contentcuration/utils/secretmanagement.py
+++ b/contentcuration/contentcuration/utils/secretmanagement.py
@@ -2,8 +2,10 @@
import logging
import os
-from google.cloud import kms_v1
+import six
+from google.cloud import kms
from google.cloud.storage import Client
+from google_crc32c import value as _crc32c
ENV_VARS = "ENV_VARS"
KMS_GCS = 2
@@ -71,10 +73,22 @@ def decrypt_secret(ciphertext, project_id, loc, env, secret_name):
"""
Decrypt the ciphertext by using the GCloud KMS keys for that secret.
"""
- kms_client = kms_v1.KeyManagementServiceClient()
- key_path = kms_client.crypto_key_path_path(project_id, loc, env, secret_name)
+ kms_client = kms.KeyManagementServiceClient()
+ key_path = kms_client.crypto_key_path(project_id, loc, env, secret_name)
+
+ # Optional, but recommended: compute ciphertext's CRC32C.
+ # See crc32c() function defined below.
+ ciphertext_crc32c = crc32c(ciphertext)
+
+ response = kms_client.decrypt(
+ request={'name': key_path, 'ciphertext': ciphertext, 'ciphertext_crc32c': ciphertext_crc32c})
+
+ # Optional, but recommended: perform integrity verification on decrypt_response.
+ # For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
+ # https://cloud.google.com/kms/docs/data-integrity-guidelines
+ if not response.plaintext_crc32c == crc32c(response.plaintext):
+ raise Exception('The response received from the server was corrupted in-transit.')
- response = kms_client.decrypt(key_path, ciphertext)
return response.plaintext
@@ -103,3 +117,14 @@ def get_encrypted_secret(secret_name, project_id, env):
)
return ret
+
+
+def crc32c(data):
+ """
+ Calculates the CRC32C checksum of the provided data.
+ Args:
+ data: the bytes over which the checksum should be calculated.
+ Returns:
+ An int representing the CRC32C checksum of the provided bytes.
+ """
+ return _crc32c(six.ensure_binary(data))
diff --git a/contentcuration/contentcuration/utils/sync.py b/contentcuration/contentcuration/utils/sync.py
index c42be1d99d..5b3664002a 100644
--- a/contentcuration/contentcuration/utils/sync.py
+++ b/contentcuration/contentcuration/utils/sync.py
@@ -4,7 +4,6 @@
import logging
from django.db.models import Q
-from django_bulk_update.helper import bulk_update
from le_utils.constants import content_kinds
from le_utils.constants import format_presets
@@ -15,8 +14,8 @@
def sync_channel(
channel,
- sync_attributes=False,
- sync_tags=False,
+ sync_titles_and_descriptions=False,
+ sync_resource_details=False,
sync_files=False,
sync_assessment_items=False,
progress_tracker=None,
@@ -37,8 +36,8 @@ def sync_channel(
for node in nodes_to_sync:
node = sync_node(
node,
- sync_attributes=sync_attributes,
- sync_tags=sync_tags,
+ sync_titles_and_descriptions=sync_titles_and_descriptions,
+ sync_resource_details=sync_resource_details,
sync_files=sync_files,
sync_assessment_items=sync_assessment_items,
)
@@ -50,8 +49,8 @@ def sync_channel(
def sync_node(
node,
- sync_attributes=False,
- sync_tags=False,
+ sync_titles_and_descriptions=False,
+ sync_resource_details=False,
sync_files=False,
sync_assessment_items=False,
):
@@ -62,26 +61,39 @@ def sync_node(
node.title, original_node.get_channel().name
)
)
- if sync_attributes: # Sync node metadata
- sync_node_data(node, original_node)
- if sync_tags: # Sync node tags
+ if sync_titles_and_descriptions:
+ fields = [
+ "title",
+ "description",
+ ]
+ sync_node_data(node, original_node, fields)
+ if sync_resource_details:
+ fields = [
+ "license_id",
+ "copyright_holder",
+ "author",
+ "extra_fields",
+ "categories",
+ "learner_needs",
+ "accessibility_labels",
+ "grade_levels",
+ "resource_types",
+ "learning_activities",
+ ]
+ sync_node_data(node, original_node, fields)
sync_node_tags(node, original_node)
- if sync_files: # Sync node files
+ if sync_files:
sync_node_files(node, original_node)
if (
sync_assessment_items and node.kind_id == content_kinds.EXERCISE
- ): # Sync node exercises
+ ):
sync_node_assessment_items(node, original_node)
return node
-def sync_node_data(node, original):
- node.title = original.title
- node.description = original.description
- node.license_id = original.license_id
- node.copyright_holder = original.copyright_holder
- node.author = original.author
- node.extra_fields = original.extra_fields
+def sync_node_data(node, original, fields):
+ for field in fields:
+ setattr(node, field, getattr(original, field))
# Set changed if anything has changed
node.on_update()
@@ -106,47 +118,60 @@ def sync_node_tags(node, original):
node.changed = True
-def sync_node_files(node, original):
+def sync_node_files(node, original): # noqa C901
"""
Sync all files in ``node`` from the files in ``original`` node.
"""
- node_files = {}
-
- for file in node.files.all():
- if file.preset_id == format_presets.VIDEO_SUBTITLE:
- file_key = "{}:{}".format(file.preset_id, file.language_id)
- else:
- file_key = file.preset_id
- node_files[file_key] = file
+ is_node_uploaded_file = False
source_files = {}
+ # 1. Build a hashmap of all original node files.
for file in original.files.all():
if file.preset_id == format_presets.VIDEO_SUBTITLE:
file_key = "{}:{}".format(file.preset_id, file.language_id)
else:
file_key = file.preset_id
source_files[file_key] = file
-
+ # If node has any non-thumbnail file then it means the node
+ # is an uploaded file.
+ if file.preset.thumbnail is False:
+ is_node_uploaded_file = True
+
+ # 2. Iterate through the copied node files. If the copied node file and
+ # source file are same then we remove it from source_files hashmap.
+ # Else we mark that file for deletion.
files_to_delete = []
+ for file in node.files.all():
+ if file.preset_id == format_presets.VIDEO_SUBTITLE:
+ file_key = "{}:{}".format(file.preset_id, file.language_id)
+ else:
+ file_key = file.preset_id
+ source_file = source_files.get(file_key)
+ if source_file and source_file.checksum == file.checksum:
+ del source_files[file_key]
+ else:
+ files_to_delete.append(file.id)
+
+ # 3. Mark all files present in source_files hashmap for creation.
+ # Files that are not in copied node but in source node
+ # will be present in source_files hashmap.
files_to_create = []
- # B. Add all files that are in original
- for file_key, source_file in source_files.items():
- # 1. Look for old file with matching preset (and language if subs file)
- node_file = node_files.get(file_key)
- if not node_file or node_file.checksum != source_file.checksum:
- if node_file:
- files_to_delete.append(node_file.id)
- source_file.id = None
- source_file.contentnode_id = node.id
- files_to_create.append(source_file)
- node.changed = True
+ for source_file in source_files.values():
+ source_file.id = None
+ source_file.contentnode_id = node.id
+ files_to_create.append(source_file)
if files_to_delete:
File.objects.filter(id__in=files_to_delete).delete()
+ node.changed = True
if files_to_create:
File.objects.bulk_create(files_to_create)
+ node.changed = True
+
+ if node.changed and is_node_uploaded_file:
+ node.content_id = original.content_id
assessment_item_fields = (
@@ -208,7 +233,7 @@ def sync_node_assessment_items(node, original): # noqa C901
node.changed = True
if ai_to_update:
- bulk_update(ai_to_update, update_fields=assessment_item_fields)
+ AssessmentItem.objects.bulk_update(ai_to_update, list(assessment_item_fields))
node.changed = True
if files_to_delete:
@@ -218,3 +243,8 @@ def sync_node_assessment_items(node, original): # noqa C901
if files_to_create:
File.objects.bulk_create(files_to_create)
node.changed = True
+
+ # Now, node and its original have same content so
+ # let us equalize its content_id.
+ if node.changed:
+ node.content_id = original.content_id
diff --git a/contentcuration/contentcuration/utils/transcription.py b/contentcuration/contentcuration/utils/transcription.py
new file mode 100644
index 0000000000..105b1b0608
--- /dev/null
+++ b/contentcuration/contentcuration/utils/transcription.py
@@ -0,0 +1,44 @@
+from automation.utils.appnexus.base import Adapter
+from automation.utils.appnexus.base import Backend
+from automation.utils.appnexus.base import BackendFactory
+from automation.utils.appnexus.base import BackendRequest
+from automation.utils.appnexus.base import BackendResponse
+
+
+class WhisperRequest(BackendRequest):
+ def __init__(self) -> None:
+ super().__init__()
+
+class WhisperResponse(BackendResponse):
+ def __init__(self) -> None:
+ super().__init__()
+
+
+class Whisper(Backend):
+ def connect(self) -> None:
+ raise NotImplementedError("The 'connect' method is not implemented for the 'Whisper' backend.")
+
+ def make_request(self, request: WhisperRequest) -> WhisperResponse:
+ # Implement production backend here.
+ pass
+
+ @classmethod
+ def _create_instance(cls) -> 'Whisper':
+ return cls()
+
+class LocalWhisper(Backend):
+ def make_request(self, request: WhisperRequest) -> WhisperResponse:
+ # Implement your local backend here.
+ pass
+
+
+class WhisperBackendFactory(BackendFactory):
+ def create_backend(self) -> Backend:
+ # Return backend based on some setting.
+ return super().create_backend()
+
+
+class WhisperAdapter(Adapter):
+ def transcribe(self, caption_file_id: str) -> WhisperResponse:
+ request = WhisperRequest()
+ return self.backend.make_request(request)
diff --git a/contentcuration/contentcuration/utils/user.py b/contentcuration/contentcuration/utils/user.py
index 673b0634c8..aeeffbf5b5 100644
--- a/contentcuration/contentcuration/utils/user.py
+++ b/contentcuration/contentcuration/utils/user.py
@@ -9,13 +9,14 @@ def calculate_user_storage(user_id):
from contentcuration.decorators import delay_user_storage_calculation
if delay_user_storage_calculation.is_active:
- delay_user_storage_calculation.queue.append(user_id)
+ delay_user_storage_calculation.add(user_id)
return
try:
if user_id is None:
raise User.DoesNotExist
user = User.objects.get(pk=user_id)
- calculate_user_storage_task.fetch_or_enqueue(user, user_id=user_id)
+ if not user.is_admin:
+ calculate_user_storage_task.fetch_or_enqueue(user, user_id=user_id)
except User.DoesNotExist:
logging.error("Tried to calculate user storage for user with id {} but they do not exist".format(user_id))
diff --git a/contentcuration/contentcuration/views/base.py b/contentcuration/contentcuration/views/base.py
index 9a7339481c..2975bd1ed0 100644
--- a/contentcuration/contentcuration/views/base.py
+++ b/contentcuration/contentcuration/views/base.py
@@ -7,12 +7,14 @@
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.db.models import Count
+from django.db.models import Exists
from django.db.models import IntegerField
from django.db.models import OuterRef
from django.db.models import Subquery
from django.db.models import UUIDField
from django.db.models.functions import Cast
from django.http import HttpResponse
+from django.http import HttpResponseBadRequest
from django.http import HttpResponseForbidden
from django.http import HttpResponseNotFound
from django.shortcuts import redirect
@@ -27,6 +29,7 @@
from django.utils.translation import LANGUAGE_SESSION_KEY
from django.views.decorators.http import require_POST
from django.views.i18n import LANGUAGE_QUERY_PARAMETER
+from django_celery_results.models import TaskResult
from rest_framework.authentication import BasicAuthentication
from rest_framework.authentication import SessionAuthentication
from rest_framework.authentication import TokenAuthentication
@@ -39,7 +42,6 @@
from .json_dump import json_for_parse_from_data
from .json_dump import json_for_parse_from_serializer
-from contentcuration.api import activate_channel
from contentcuration.constants import channel_history
from contentcuration.decorators import browser_is_supported
from contentcuration.models import Change
@@ -47,10 +49,10 @@
from contentcuration.models import ChannelHistory
from contentcuration.models import ChannelSet
from contentcuration.models import ContentKind
+from contentcuration.models import CustomTaskMetadata
from contentcuration.models import DEFAULT_USER_PREFERENCES
from contentcuration.models import Language
from contentcuration.models import License
-from contentcuration.models import TaskResult
from contentcuration.serializers import SimplifiedChannelProbeCheckSerializer
from contentcuration.utils.messages import get_messages
from contentcuration.viewsets.channelset import PublicChannelSetSerializer
@@ -132,17 +134,16 @@ def get_prober_channel(request):
return Response(SimplifiedChannelProbeCheckSerializer(channel).data)
-
@api_view(["GET"])
@authentication_classes((TokenAuthentication, SessionAuthentication))
@permission_classes((IsAuthenticated,))
def publishing_status(request):
if not request.user.is_admin:
return HttpResponseForbidden()
-
+ associated_custom_task_metadata_ids = CustomTaskMetadata.objects.filter(channel_id=Cast(OuterRef(OuterRef("channel_id")), UUIDField())).values_list("task_id",flat=True)
associated_tasks = TaskResult.objects.filter(
task_name="export-channel",
- channel_id=Cast(OuterRef("channel_id"), UUIDField()),
+ task_id__in=Subquery(associated_custom_task_metadata_ids),
)
channel_publish_status = (
ChannelHistory.objects
@@ -173,6 +174,39 @@ def celery_worker_status(request):
return Response(app.control.inspect().ping() or {})
+@api_view(["GET"])
+@authentication_classes((TokenAuthentication, SessionAuthentication))
+@permission_classes((IsAuthenticated,))
+def task_queue_status(request):
+ if not request.user.is_admin:
+ return HttpResponseForbidden()
+
+ from contentcuration.celery import app
+
+ return Response({
+ 'queued_task_count': app.count_queued_tasks(),
+ })
+
+
+@api_view(["GET"])
+@authentication_classes((TokenAuthentication, SessionAuthentication))
+@permission_classes((IsAuthenticated,))
+def unapplied_changes_status(request):
+ if not request.user.is_admin:
+ return HttpResponseForbidden()
+
+ from contentcuration.celery import app
+
+ active_task_count = 0
+ for _ in app.get_active_and_reserved_tasks():
+ active_task_count += 1
+
+ return Response({
+ 'active_task_count': active_task_count,
+ 'unapplied_changes_count': Change.objects.filter(applied=False, errored=False).count(),
+ })
+
+
""" END HEALTH CHECKS """
@@ -325,23 +359,6 @@ class SQCountDistinct(Subquery):
output_field = IntegerField()
-@api_view(['POST'])
-@authentication_classes((SessionAuthentication,))
-@permission_classes((IsAuthenticated,))
-def activate_channel_endpoint(request):
- data = request.data
- try:
- channel = Channel.filter_edit_queryset(Channel.objects.all(), request.user).get(pk=data["channel_id"])
- except Channel.DoesNotExist:
- return HttpResponseNotFound("Channel not found")
- try:
- activate_channel(channel, request.user)
- except PermissionDenied as e:
- return HttpResponseForbidden(str(e))
-
- return HttpResponse(json.dumps({"success": True}))
-
-
@require_POST
# flake8: noqa: C901
def set_language(request):
diff --git a/contentcuration/contentcuration/views/internal.py b/contentcuration/contentcuration/views/internal.py
index 754c8fb740..5767928719 100644
--- a/contentcuration/contentcuration/views/internal.py
+++ b/contentcuration/contentcuration/views/internal.py
@@ -34,9 +34,9 @@
from sentry_sdk import capture_exception
from contentcuration import ricecooker_versions as rc
-from contentcuration.api import activate_channel
from contentcuration.api import write_file_to_storage
from contentcuration.constants import completion_criteria
+from contentcuration.decorators import delay_user_storage_calculation
from contentcuration.models import AssessmentItem
from contentcuration.models import Change
from contentcuration.models import Channel
@@ -54,7 +54,6 @@
from contentcuration.utils.nodes import map_files_to_node
from contentcuration.utils.nodes import map_files_to_slideshow_slide_item
from contentcuration.utils.sentry import report_exception
-from contentcuration.utils.tracing import trace
from contentcuration.viewsets.sync.constants import CHANNEL
from contentcuration.viewsets.sync.utils import generate_publish_event
from contentcuration.viewsets.sync.utils import generate_update_event
@@ -340,23 +339,6 @@ def api_publish_channel(request):
return HttpResponseServerError(content=str(e), reason=str(e))
-@api_view(['POST'])
-@authentication_classes((TokenAuthentication,))
-@permission_classes((IsAuthenticated,))
-def activate_channel_internal(request):
- try:
- data = json.loads(request.body)
- channel_id = data['channel_id']
- channel = Channel.get_editable(request.user, channel_id)
- activate_channel(channel, request.user)
- return Response({"success": True})
- except Channel.DoesNotExist:
- return HttpResponseNotFound("No channel matching: {}".format(channel_id))
- except Exception as e:
- handle_server_error(e, request)
- return HttpResponseServerError(content=str(e), reason=str(e))
-
-
@api_view(["POST"])
@authentication_classes((TokenAuthentication, SessionAuthentication,))
@permission_classes((IsAuthenticated,))
@@ -565,12 +547,99 @@ def __init__(self, node, errors):
super(IncompleteNodeError, self).__init__(self.message)
-@trace
-def convert_data_to_nodes(user, content_data, parent_node):
+def add_tags(node, node_data):
+ tags = []
+ channel = node.get_channel()
+ if "tags" in node_data:
+ tag_data = node_data["tags"]
+ if tag_data is not None:
+ for tag in tag_data:
+ if len(tag) > 30:
+ raise ValidationError("tag is greater than 30 characters")
+ else:
+ tags.append(
+ ContentTag.objects.get_or_create(tag_name=tag, channel=channel)[0]
+ )
+
+ if len(tags) > 0:
+ node.tags.add(*tags)
+
+
+def validate_metadata_labels(node_data):
+ metadata_labels = {}
+
+ for label, valid_values in METADATA.items():
+ if label in node_data:
+ if type(node_data[label]) is not list:
+ raise NodeValidationError("{} must pass a list of values".format(label))
+ metadata_labels[label] = {}
+ for value in node_data[label]:
+ if value not in valid_values:
+ raise NodeValidationError("{} is not a valid value for {}".format(value, label))
+ metadata_labels[label][value] = True
+
+ return metadata_labels
+
+
+def handle_remote_node(user, node_data, parent_node):
+ if node_data["source_channel_id"] is None:
+ raise NodeValidationError("source_channel_id was None")
+ source_channel_id = node_data["source_channel_id"]
+
+ source_node_id = node_data.get("source_node_id")
+
+ source_content_id = node_data.get("source_content_id")
+
+ if not source_node_id and not source_content_id:
+ raise NodeValidationError("Both source_node_id and source_content_id are None")
+
+ try:
+ channel = Channel.filter_view_queryset(Channel.objects.all(), user).get(id=source_channel_id)
+ except Channel.DoesNotExist:
+ raise NodeValidationError("source_channel_id does not exist")
+
+ contentnode = None
+
+ channel_resource_nodes = channel.main_tree.get_descendants().exclude(kind=content_kinds.TOPIC)
+
+ if source_node_id:
+ try:
+ contentnode = channel_resource_nodes.get(node_id=source_node_id)
+ except ContentNode.DoesNotExist:
+ pass
+
+ if contentnode is None and source_content_id:
+ contentnode = channel_resource_nodes.filter(content_id=source_content_id).first()
+
+ if contentnode is None:
+ raise NodeValidationError(
+ "Unable to find node with node_id {} and/or content_id {} from channel {}".format(
+ source_node_id, source_content_id, source_channel_id
+ )
+ )
+
+ metadata_labels = validate_metadata_labels(node_data)
+
+ # Validate any metadata labels then overwrite the source data so that we are
+ # setting validated and appropriate mapped metadata.
+ node_data.update(metadata_labels)
+
+ can_edit_source_channel = ContentNode.filter_edit_queryset(
+ ContentNode.filter_by_pk(pk=contentnode.id), user=user
+ ).exists()
+
+ return contentnode.copy_to(target=parent_node, mods=node_data, can_edit_source_channel=can_edit_source_channel)
+
+
+@delay_user_storage_calculation
+def convert_data_to_nodes(user, content_data, parent_node): # noqa: C901
""" Parse dict and create nodes accordingly """
try:
root_mapping = {}
parent_node = ContentNode.objects.get(pk=parent_node)
+ if parent_node.kind_id != content_kinds.TOPIC:
+ raise NodeValidationError("Parent node must be a topic/folder | actual={}".format(parent_node.kind_id))
+
sort_order = parent_node.children.count() + 1
existing_node_ids = ContentNode.objects.filter(
parent_id=parent_node.pk
@@ -579,32 +648,40 @@ def convert_data_to_nodes(user, content_data, parent_node):
for node_data in content_data:
# Check if node id is already in the tree to avoid duplicates
if node_data["node_id"] not in existing_node_ids:
- # Create the node
- new_node = create_node(node_data, parent_node, sort_order)
+ if "source_channel_id" in node_data:
+ new_node = handle_remote_node(user, node_data, parent_node)
- # Create files associated with node
- map_files_to_node(user, new_node, node_data["files"])
+ map_files_to_node(user, new_node, node_data.get("files", []))
- # Create questions associated exercise nodes
- create_exercises(user, new_node, node_data["questions"])
- sort_order += 1
+ add_tags(new_node, node_data)
- # Create Slideshow slides (if slideshow kind)
- if node_data["kind"] == "slideshow":
- extra_fields_unicode = node_data["extra_fields"]
+ else:
+ # Create the node
+ new_node = create_node(node_data, parent_node, sort_order)
- # Extra Fields comes as type - convert it to a dict and get slideshow_data
- extra_fields_json = extra_fields_unicode.encode(
- "ascii", "ignore"
- )
- extra_fields = json.loads(extra_fields_json)
+ # Create files associated with node
+ map_files_to_node(user, new_node, node_data["files"])
- slides = create_slides(
- user, new_node, extra_fields.get("slideshow_data")
- )
- map_files_to_slideshow_slide_item(
- user, new_node, slides, node_data["files"]
- )
+ # Create questions associated exercise nodes
+ create_exercises(user, new_node, node_data["questions"])
+ sort_order += 1
+
+ # Create Slideshow slides (if slideshow kind)
+ if node_data["kind"] == "slideshow":
+ extra_fields_unicode = node_data["extra_fields"]
+
+ # Extra Fields comes as type - convert it to a dict and get slideshow_data
+ extra_fields_json = extra_fields_unicode.encode(
+ "ascii", "ignore"
+ )
+ extra_fields = json.loads(extra_fields_json)
+
+ slides = create_slides(
+ user, new_node, extra_fields.get("slideshow_data")
+ )
+ map_files_to_slideshow_slide_item(
+ user, new_node, slides, node_data["files"]
+ )
# Wait until after files have been set on the node to check for node completeness
# as some node kinds are counted as incomplete if they lack a default file.
@@ -662,17 +739,7 @@ def create_node(node_data, parent_node, sort_order): # noqa: C901
license_description = node_data.get('license_description', "")
copyright_holder = node_data.get('copyright_holder', "")
- metadata_labels = {}
-
- for label, valid_values in METADATA.items():
- if label in node_data:
- if type(node_data[label]) is not list:
- raise NodeValidationError("{} must pass a list of values".format(label))
- metadata_labels[label] = {}
- for value in node_data[label]:
- if value not in valid_values:
- raise NodeValidationError("{} is not a valid value for {}".format(value, label))
- metadata_labels[label][value] = True
+ metadata_labels = validate_metadata_labels(node_data)
node = ContentNode.objects.create(
title=title,
@@ -703,21 +770,7 @@ def create_node(node_data, parent_node, sort_order): # noqa: C901
**metadata_labels
)
- tags = []
- channel = node.get_channel()
- if "tags" in node_data:
- tag_data = node_data["tags"]
- if tag_data is not None:
- for tag in tag_data:
- if len(tag) > 30:
- raise ValidationError("tag is greater than 30 characters")
- else:
- tags.append(
- ContentTag.objects.get_or_create(tag_name=tag, channel=channel)[0]
- )
-
- if len(tags) > 0:
- node.tags.set(tags)
+ add_tags(node, node_data)
return node
diff --git a/contentcuration/contentcuration/views/nodes.py b/contentcuration/contentcuration/views/nodes.py
index 0d83b29dd4..a1e924c0c7 100644
--- a/contentcuration/contentcuration/views/nodes.py
+++ b/contentcuration/contentcuration/views/nodes.py
@@ -98,8 +98,9 @@ def get_node_diff(request, updated_id, original_id):
if data:
return Response(data)
+ signature = generatenodediff_task.generate_signature(dict(updated_id=updated_id, original_id=original_id))
# See if there's already a staging task in progress
- if generatenodediff_task.find_incomplete_ids(updated_id=updated_id, original_id=original_id).exists():
+ if generatenodediff_task.find_incomplete_ids(signature).exists():
return Response('Diff is being generated', status=status.HTTP_302_FOUND)
except ContentNode.DoesNotExist:
pass
diff --git a/contentcuration/contentcuration/views/pwa.py b/contentcuration/contentcuration/views/pwa.py
index 8c7028804c..a150527d1b 100644
--- a/contentcuration/contentcuration/views/pwa.py
+++ b/contentcuration/contentcuration/views/pwa.py
@@ -1,7 +1,9 @@
import os
+from django.conf import settings
from django.contrib.staticfiles import finders
from django.contrib.staticfiles.storage import staticfiles_storage
+from django.http import Http404
from django.views.generic.base import TemplateView
@@ -12,17 +14,27 @@ class ServiceWorkerView(TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Code to access the compiled serivce worker in developer mode
- # if getattr(settings, "DEBUG", False):
- # import requests
-
- # request = requests.get("http://127.0.0.1:4000/dist/serviceWorker.js")
- # content = request.content
- # else:
- path = staticfiles_storage.path("studio/serviceWorker.js")
- if not os.path.exists(path):
- path = finders.find("studio/serviceWorker.js")
- with open(path) as f:
- content = f.read()
+ content = None
+ if getattr(settings, "DEBUG", False):
+ import requests
+ try:
+ request = requests.get("http://127.0.0.1:4000/dist/serviceWorker.js")
+ content = request.content.decode("utf-8")
+ except requests.exceptions.RequestException:
+ pass
+
+ if content is None:
+ path = staticfiles_storage.path("studio/serviceWorker.js")
+ if not os.path.exists(path):
+ path = finders.find("studio/serviceWorker.js")
+ try:
+ with open(path) as f:
+ content = f.read()
+ except (FileNotFoundError, IOError):
+ pass
+
+ if content is None:
+ raise Http404("Service worker not found")
context["webpack_service_worker"] = content
return context
diff --git a/contentcuration/contentcuration/views/settings.py b/contentcuration/contentcuration/views/settings.py
index f41b6ac926..b34f311ece 100644
--- a/contentcuration/contentcuration/views/settings.py
+++ b/contentcuration/contentcuration/views/settings.py
@@ -34,6 +34,7 @@
from contentcuration.utils.csv_writer import generate_user_csv_filename
from contentcuration.utils.messages import get_messages
from contentcuration.views.base import current_user_for_context
+from contentcuration.views.users import logout
from contentcuration.viewsets.channel import SettingsChannelSerializer
ISSUE_UPDATE_DATE = datetime(2018, 10, 29)
@@ -165,6 +166,7 @@ def form_valid(self, form):
os.unlink(csv_path)
self.request.user.delete()
+ logout(self.request)
class StorageSettingsView(PostFormMixin, FormView):
@@ -180,8 +182,9 @@ def form_valid(self, form):
"channels": channels,
},
)
+
send_mail(
- "Kolibri Studio storage request",
+ f"Kolibri Studio storage request from {self.request.user}",
message,
ccsettings.DEFAULT_FROM_EMAIL,
[ccsettings.SPACE_REQUEST_EMAIL, self.request.user.email],
diff --git a/contentcuration/contentcuration/views/users.py b/contentcuration/contentcuration/views/users.py
index 77a45afecd..53ec959043 100644
--- a/contentcuration/contentcuration/views/users.py
+++ b/contentcuration/contentcuration/views/users.py
@@ -106,10 +106,19 @@ def send_invitation_email(request):
@api_view(["GET"])
@authentication_classes((SessionAuthentication,))
@permission_classes((IsAuthenticated,))
-def deferred_user_data(request):
+def deferred_user_space_by_kind(request):
return Response(
{
"space_used_by_kind": request.user.get_space_used_by_kind(),
+ }
+ )
+
+@api_view(["GET"])
+@authentication_classes((SessionAuthentication,))
+@permission_classes((IsAuthenticated,))
+def deferred_user_api_token(request):
+ return Response(
+ {
"api_token": request.user.get_token(),
}
)
diff --git a/contentcuration/contentcuration/viewsets/assessmentitem.py b/contentcuration/contentcuration/viewsets/assessmentitem.py
index 978208b5f1..06996edd90 100644
--- a/contentcuration/contentcuration/viewsets/assessmentitem.py
+++ b/contentcuration/contentcuration/viewsets/assessmentitem.py
@@ -1,8 +1,9 @@
+import json
import re
from django.db import transaction
-from django_bulk_update.helper import bulk_update
from le_utils.constants import exercises
+from rest_framework import serializers
from rest_framework.permissions import IsAuthenticated
from rest_framework.serializers import ValidationError
@@ -42,12 +43,14 @@ def get_filenames_from_assessment(assessment_item):
# Get unique checksums in the assessment item text fields markdown
# Coerce to a string, for Python 2, as the stored data is in unicode, and otherwise
# the unicode char in the placeholder will not match
+ answers = json.loads(assessment_item.answers)
+ hints = json.loads(assessment_item.hints)
return set(
exercise_image_filename_regex.findall(
str(
assessment_item.question
- + assessment_item.answers
- + assessment_item.hints
+ + str([a["answer"] for a in answers])
+ + str([h["hint"] for h in hints])
)
)
)
@@ -74,6 +77,8 @@ def update(self, queryset, all_validated_data):
class AssessmentItemSerializer(BulkModelSerializer):
# This is set as editable=False on the model so by default DRF does not allow us
# to set it.
+ hints = serializers.CharField(required=False)
+ answers = serializers.CharField(required=False)
assessment_id = UUIDRegexField()
contentnode = UserFilteredPrimaryKeyRelatedField(
queryset=ContentNode.objects.all(), required=False
@@ -98,6 +103,24 @@ class Meta:
# Use the contentnode and assessment_id as the lookup field for updates
update_lookup_field = ("contentnode", "assessment_id")
+ def validate_answers(self, value):
+ answers = json.loads(value)
+ for answer in answers:
+ if not type(answer) is dict:
+ raise ValidationError('JSON Data Invalid for answers')
+ if not all(k in answer for k in ('answer', 'correct', 'order')):
+ raise ValidationError('Incorrect field in answers')
+ return value
+
+ def validate_hints(self, value):
+ hints = json.loads(value)
+ for hint in hints:
+ if not type(hint) is dict:
+ raise ValidationError('JSON Data Invalid for hints')
+ if not all(k in hint for k in ('hint', 'order')):
+ raise ValidationError('Incorrect field in hints')
+ return value
+
def set_files(self, all_objects, all_validated_data=None): # noqa C901
files_to_delete = []
files_to_update = {}
@@ -108,8 +131,7 @@ def set_files(self, all_objects, all_validated_data=None): # noqa C901
# If this is an update operation, check the validated data for which items
# have had these fields modified.
md_fields_modified = {
- self.id_value_lookup(ai) for ai in all_validated_data
- if "question" in ai or "hints" in ai or "answers" in ai
+ self.id_value_lookup(ai) for ai in all_validated_data if "question" in ai or "hints" in ai or "answers" in ai
}
else:
# If this is a create operation, just check if these fields are not null.
@@ -170,7 +192,7 @@ def set_files(self, all_objects, all_validated_data=None): # noqa C901
raise ValidationError(
"Attempted to set files to an assessment item that do not have a file on the server"
)
- bulk_update(source_files)
+ File.objects.bulk_update(source_files, ['assessment_item'])
def create(self, validated_data):
with transaction.atomic():
diff --git a/contentcuration/contentcuration/viewsets/base.py b/contentcuration/contentcuration/viewsets/base.py
index df3c90e031..51b907cc09 100644
--- a/contentcuration/contentcuration/viewsets/base.py
+++ b/contentcuration/contentcuration/viewsets/base.py
@@ -8,9 +8,10 @@
from channels.layers import get_channel_layer
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
+from django.db.utils import IntegrityError
from django.http import Http404
from django.http.request import HttpRequest
-from django_bulk_update.helper import bulk_update
+from django_celery_results.models import TaskResult
from django_filters.constants import EMPTY_VALUES
from django_filters.rest_framework import DjangoFilterBackend
from django_filters.rest_framework import FilterSet
@@ -28,10 +29,11 @@
from rest_framework.status import HTTP_204_NO_CONTENT
from rest_framework.utils import html
from rest_framework.utils import model_meta
-from rest_framework.viewsets import ReadOnlyModelViewSet
+from rest_framework.viewsets import GenericViewSet
from contentcuration.models import Change
-from contentcuration.models import TaskResult
+from contentcuration.models import CustomTaskMetadata
+from contentcuration.utils.celery.tasks import generate_task_signature
from contentcuration.utils.celery.tasks import ProgressTracker
from contentcuration.viewsets.common import MissingRequiredParamsException
from contentcuration.viewsets.sync.constants import TASK_ID
@@ -328,7 +330,8 @@ def update(self, queryset, all_validated_data):
self.missing_keys = set(all_validated_data_by_id.keys())\
.difference(updated_keys)
- bulk_update(updated_objects, update_fields=properties_to_update)
+ if len(properties_to_update) > 0:
+ self.child.Meta.model.objects.bulk_update(updated_objects, list(properties_to_update))
return updated_objects
@@ -438,13 +441,57 @@ def remove_invalid_fields(self, queryset, fields, view, request):
return ordering
-class ReadOnlyValuesViewset(SimpleReprMixin, ReadOnlyModelViewSet):
+class RequiredFilterSet(FilterSet):
+
+ def __init__(self, required=False, **kwargs):
+ self._required = required
+ super().__init__(**kwargs)
+
+ @property
+ def qs(self):
+ if self._required:
+ has_filtering_queries = False
+ if self.form.is_valid():
+ for name, filter_ in self.filters.items():
+ value = self.form.cleaned_data.get(name)
+
+ if value not in EMPTY_VALUES:
+ has_filtering_queries = True
+ break
+ if not has_filtering_queries and self.request.method == "GET":
+ raise MissingRequiredParamsException("No valid filter parameters supplied")
+ return super(FilterSet, self).qs
+
+
+class RequiredFiltersFilterBackend(DjangoFilterBackend):
+ """
+ Override the default filter backend to conditionalize initialization
+ if we are using a RequiredFilterSet
+ """
+ def get_filterset(self, request, queryset, view):
+ filterset_class = self.get_filterset_class(view, queryset)
+ if filterset_class is None:
+ return None
+
+ kwargs = self.get_filterset_kwargs(request, queryset, view)
+
+ if issubclass(filterset_class, RequiredFilterSet):
+ action_handler = getattr(view, view.action)
+ # Either this is a list action, or it's a decorated action that
+ # had its detail attribute explicitly set to False.
+ if view.action == "list" or not getattr(action_handler, "detail", True):
+ kwargs["required"] = True
+
+ return filterset_class(**kwargs)
+
+
+class BaseValuesViewset(SimpleReprMixin, GenericViewSet):
"""
A viewset that uses a values call to get all model/queryset data in
a single database query, rather than delegating serialization to a
DRF ModelSerializer.
"""
- filter_backends = (DjangoFilterBackend, ValuesViewsetOrderingFilter)
+ filter_backends = (RequiredFiltersFilterBackend, ValuesViewsetOrderingFilter)
# A tuple of values to get from the queryset
values = None
@@ -456,7 +503,7 @@ class ReadOnlyValuesViewset(SimpleReprMixin, ReadOnlyModelViewSet):
field_map = {}
def __init__(self, *args, **kwargs):
- super(ReadOnlyValuesViewset, self).__init__(*args, **kwargs)
+ super(BaseValuesViewset, self).__init__(*args, **kwargs)
if not isinstance(self.values, tuple):
raise TypeError("values must be defined as a tuple")
self._values = tuple(self.values)
@@ -530,7 +577,7 @@ def get_serializer_class(self):
return Serializer
def get_queryset(self):
- queryset = super(ReadOnlyValuesViewset, self).get_queryset()
+ queryset = super(BaseValuesViewset, self).get_queryset()
if hasattr(queryset.model, "filter_view_queryset"):
return queryset.model.filter_view_queryset(queryset, self.request.user)
return queryset
@@ -540,7 +587,7 @@ def get_edit_queryset(self):
Return a filtered copy of the queryset to only the objects
that a user is able to edit, rather than view.
"""
- queryset = super(ReadOnlyValuesViewset, self).get_queryset()
+ queryset = super(BaseValuesViewset, self).get_queryset()
if hasattr(queryset.model, "filter_edit_queryset"):
return queryset.model.filter_edit_queryset(queryset, self.request.user)
return self.get_queryset()
@@ -598,13 +645,25 @@ def _map_fields(self, item):
def consolidate(self, items, queryset):
return items
- def _cast_queryset_to_values(self, queryset):
+ def serialize(self, queryset):
queryset = self.annotate_queryset(queryset)
- return queryset.values(*self._values)
+ values_queryset = queryset.values(*self._values)
+ return self.consolidate(
+ list(map(self._map_fields, values_queryset or [])), queryset
+ )
+
+ def serialize_object(self, **filter_kwargs):
+ try:
+ filter_kwargs = filter_kwargs or self._get_lookup_filter()
+ queryset = self.get_queryset().filter(**filter_kwargs)
+ return self.serialize(self.filter_queryset(queryset))[0]
+ except (IndexError, ValueError, TypeError):
+ raise Http404(
+ "No %s matches the given query." % queryset.model._meta.object_name
+ )
- def serialize(self, queryset):
- return self.consolidate(list(map(self._map_fields, queryset or [])), queryset)
+class ListModelMixin(object):
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
@@ -613,29 +672,21 @@ def list(self, request, *args, **kwargs):
if page_queryset is not None:
queryset = page_queryset
- queryset = self._cast_queryset_to_values(queryset)
-
if page_queryset is not None:
return self.get_paginated_response(self.serialize(queryset))
return Response(self.serialize(queryset))
- def serialize_object(self, **filter_kwargs):
- queryset = self.get_queryset()
- try:
- filter_kwargs = filter_kwargs or self._get_lookup_filter()
- return self.serialize(
- self._cast_queryset_to_values(queryset.filter(**filter_kwargs))
- )[0]
- except IndexError:
- raise Http404(
- "No %s matches the given query." % queryset.model._meta.object_name
- )
+class RetrieveModelMixin(object):
def retrieve(self, request, *args, **kwargs):
return Response(self.serialize_object())
+class ReadOnlyValuesViewset(BaseValuesViewset, RetrieveModelMixin, ListModelMixin):
+ pass
+
+
class CreateModelMixin(object):
def _map_create_change(self, change):
return dict(
@@ -667,7 +718,12 @@ def create_from_changes(self, changes):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
- self.perform_create(serializer)
+
+ try:
+ self.perform_create(serializer)
+
+ except IntegrityError as e:
+ return Response({"error": str(e)}, status=409)
instance = serializer.instance
return Response(self.serialize_object(pk=instance.pk), status=HTTP_201_CREATED)
@@ -881,35 +937,25 @@ def delete_from_changes(self, changes):
return errors
-class RequiredFilterSet(FilterSet):
- @property
- def qs(self):
- has_filtering_queries = False
- if self.form.is_valid():
- for name, filter_ in self.filters.items():
- value = self.form.cleaned_data.get(name)
-
- if value not in EMPTY_VALUES:
- has_filtering_queries = True
- break
- if not has_filtering_queries:
- raise MissingRequiredParamsException("No valid filter parameters supplied")
- return super(FilterSet, self).qs
-
-
@contextmanager
def create_change_tracker(pk, table, channel_id, user, task_name):
+ task_kwargs = json.dumps({'pk': pk, 'table': table})
+
# Clean up any previous tasks specific to this in case there were failures.
- meta = json.dumps(dict(pk=pk, table=table))
- TaskResult.objects.filter(channel_id=channel_id, task_name=task_name, meta=meta).delete()
+ signature = generate_task_signature(task_name, task_kwargs=task_kwargs, channel_id=channel_id)
+
+ task_id_to_delete = CustomTaskMetadata.objects.filter(channel_id=channel_id, signature=signature)
+ if task_id_to_delete:
+ TaskResult.objects.filter(task_id=task_id_to_delete, task_name=task_name).delete()
+
progress = 0
status = states.STARTED
+ task_id = uuid.uuid4().hex
+
channel_layer = get_channel_layer()
room_group_name = channel_id
- task_id = uuid.uuid4().hex
-
async_to_sync(channel_layer.group_send)(
str(room_group_name),
{
@@ -954,7 +1000,7 @@ def update_progress(progress=None):
try:
yield tracker
- except Exception as e:
+ except Exception:
status = states.FAILURE
traceback_str = traceback.format_exc()
async_to_sync(channel_layer.group_send)(
@@ -967,12 +1013,13 @@ def update_progress(progress=None):
'task_id': task_id,
'task_name': task_name,
'traceback': traceback_str,
- 'progress': progress,
+ 'progress': 100,
'channel_id': channel_id,
'status': status,
}
}
)
+ raise
finally:
if status == states.STARTED:
progress = 100
diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py
index a6375cb6c1..07ffd42371 100644
--- a/contentcuration/contentcuration/viewsets/channel.py
+++ b/contentcuration/contentcuration/viewsets/channel.py
@@ -28,6 +28,7 @@
from search.models import ContentNodeFullTextSearch
from search.utils import get_fts_search_query
+import contentcuration.models as models
from contentcuration.decorators import cache_no_user_data
from contentcuration.models import Change
from contentcuration.models import Channel
@@ -36,8 +37,10 @@
from contentcuration.models import generate_storage_url
from contentcuration.models import SecretToken
from contentcuration.models import User
+from contentcuration.utils.garbage_collect import get_deleted_chefs_root
from contentcuration.utils.pagination import CachedListPagination
from contentcuration.utils.pagination import ValuesViewsetPageNumberPagination
+from contentcuration.utils.publish import ChannelIncompleteError
from contentcuration.utils.publish import publish_channel
from contentcuration.utils.sync import sync_channel
from contentcuration.viewsets.base import BulkListSerializer
@@ -377,9 +380,18 @@ def format_demo_server_url(item):
def _unpublished_changes_query(channel):
+ """
+ :param channel: Either an `OuterRef` or `Channel` object
+ :type channel: Channel|OuterRef
+ :return: QuerySet for unpublished changes
+ """
+ # double wrap the channel if it's an outer ref so that we can match the outermost channel
+ # to optimize query performance
+ channel_ref = OuterRef(channel) if isinstance(channel, OuterRef) else channel
+
return Change.objects.filter(
server_rev__gt=Coalesce(Change.objects.filter(
- channel=OuterRef("channel"),
+ channel=channel_ref,
change_type=PUBLISHED,
errored=False
).values("server_rev").order_by("-server_rev")[:1], Value(0)),
@@ -491,6 +503,13 @@ def publish(self, pk, version_notes="", language=None):
"unpublished_changes": bool(_unpublished_changes_query(channel.id).exists())
}, channel_id=channel.id
), created_by_id=self.request.user.id, applied=True)
+ except ChannelIncompleteError:
+ Change.create_changes([
+ generate_update_event(
+ channel.id, CHANNEL, {"publishing": False}, channel_id=channel.id
+ ),
+ ], created_by_id=self.request.user.id, applied=True)
+ raise ValidationError("Channel is not ready to be published")
except Exception:
Change.create_changes([
generate_update_event(
@@ -502,12 +521,12 @@ def publish(self, pk, version_notes="", language=None):
def sync_from_changes(self, changes):
errors = []
for sync in changes:
- # Publish change will have key, attributes, tags, files, and assessment_items.
+ # Publish change will have key, titles_and_descriptions, resource_details, files, and assessment_items.
try:
self.sync(
sync["key"],
- attributes=sync.get("attributes"),
- tags=sync.get("tags"),
+ titles_and_descriptions=sync.get("titles_and_descriptions"),
+ resource_details=sync.get("resource_details"),
files=sync.get("files"),
assessment_items=sync.get("assessment_items")
)
@@ -517,7 +536,7 @@ def sync_from_changes(self, changes):
errors.append(sync)
return errors
- def sync(self, pk, attributes=False, tags=False, files=False, assessment_items=False):
+ def sync(self, pk, titles_and_descriptions=False, resource_details=False, files=False, assessment_items=False):
logging.debug("Entering the sync channel endpoint")
channel = self.get_edit_queryset().get(pk=pk)
@@ -541,13 +560,59 @@ def sync(self, pk, attributes=False, tags=False, files=False, assessment_items=F
with create_change_tracker(pk, CHANNEL, channel.id, self.request.user, "sync-channel") as progress_tracker:
sync_channel(
channel,
- attributes,
- tags,
+ titles_and_descriptions,
+ resource_details,
files,
- tags,
+ assessment_items,
progress_tracker=progress_tracker,
)
+ def deploy_from_changes(self, changes):
+ errors = []
+ for deploy in changes:
+ try:
+ self.deploy(self.request.user, deploy["key"])
+ except Exception as e:
+ log_sync_exception(e, user=self.request.user, change=deploy)
+ deploy["errors"] = [str(e)]
+ errors.append(deploy)
+ return errors
+
+ def deploy(self, user, pk):
+
+ channel = self.get_edit_queryset().get(pk=pk)
+
+ if channel.staging_tree is None:
+ raise ValidationError("Cannot deploy a channel without staging tree")
+
+ user.check_channel_space(channel)
+
+ if channel.previous_tree and channel.previous_tree != channel.main_tree:
+ # IMPORTANT: Do not remove this block, MPTT updating the deleted chefs block could hang the server
+ with models.ContentNode.objects.disable_mptt_updates():
+ garbage_node = get_deleted_chefs_root()
+ channel.previous_tree.parent = garbage_node
+ channel.previous_tree.title = "Previous tree for channel {}".format(channel.pk)
+ channel.previous_tree.save()
+
+ channel.previous_tree = channel.main_tree
+ channel.main_tree = channel.staging_tree
+ channel.staging_tree = None
+ channel.save()
+
+ user.staged_files.all().delete()
+ user.set_space_used()
+
+ models.Change.create_change(generate_update_event(
+ channel.id,
+ CHANNEL,
+ {
+ "root_id": channel.main_tree.id,
+ "staging_root_id": None
+ },
+ channel_id=channel.id,
+ ), applied=True, created_by_id=user.id)
+
@method_decorator(
cache_page(
diff --git a/contentcuration/contentcuration/viewsets/channelset.py b/contentcuration/contentcuration/viewsets/channelset.py
index 543907dd2e..7ad1fa6723 100644
--- a/contentcuration/contentcuration/viewsets/channelset.py
+++ b/contentcuration/contentcuration/viewsets/channelset.py
@@ -78,7 +78,8 @@ def get_queryset(self):
queryset = super(ChannelSetViewSet, self).get_queryset()
user_id = not self.request.user.is_anonymous and self.request.user.id
edit = Exists(User.channel_sets.through.objects.filter(user_id=user_id, channelset_id=OuterRef("id")))
- return queryset.annotate(edit=edit)
+ queryset = queryset.annotate(edit=edit).filter(edit=True)
+ return queryset
def annotate_queryset(self, queryset):
return queryset.annotate(
diff --git a/contentcuration/contentcuration/viewsets/common.py b/contentcuration/contentcuration/viewsets/common.py
index 274553508a..7298c99d70 100644
--- a/contentcuration/contentcuration/viewsets/common.py
+++ b/contentcuration/contentcuration/viewsets/common.py
@@ -95,6 +95,18 @@ class SQRelatedArrayAgg(SQArrayAgg):
template = "(SELECT ARRAY_AGG(%(fieldname)s::text) FROM (%(subquery)s) AS %(field)s__sum)"
+class SQJSONBKeyArrayAgg(AggregateSubquery):
+ """
+ An aggregate subquery to get all the distinct keys of a JSON field that contains maps to store
+ e.g. metadata labels.
+ """
+ # Include ALIAS at the end to support Postgres
+ template = (
+ "(SELECT ARRAY_AGG(f) FROM (SELECT DISTINCT jsonb_object_keys(%(field)s) AS f FROM (%(subquery)s) AS x) AS %(field)s__sum)"
+ )
+ output_field = ArrayField(CharField())
+
+
dot_path_regex = re.compile(r"^([^.]+)\.(.+)$")
diff --git a/contentcuration/contentcuration/viewsets/contentnode.py b/contentcuration/contentcuration/viewsets/contentnode.py
index ca636c95a4..49a81b84b4 100644
--- a/contentcuration/contentcuration/viewsets/contentnode.py
+++ b/contentcuration/contentcuration/viewsets/contentnode.py
@@ -67,11 +67,12 @@
from contentcuration.viewsets.common import UserFilteredPrimaryKeyRelatedField
from contentcuration.viewsets.common import UUIDInFilter
from contentcuration.viewsets.sync.constants import CONTENTNODE
-from contentcuration.viewsets.sync.constants import COPYING_FLAG
+from contentcuration.viewsets.sync.constants import COPYING_STATUS
+from contentcuration.viewsets.sync.constants import COPYING_STATUS_VALUES
from contentcuration.viewsets.sync.constants import CREATED
from contentcuration.viewsets.sync.constants import DELETED
from contentcuration.viewsets.sync.utils import generate_update_event
-
+from contentcuration.viewsets.sync.utils import log_sync_exception
channel_query = Channel.objects.filter(main_tree__tree_id=OuterRef("tree_id"))
@@ -908,10 +909,15 @@ def copy_from_changes(self, changes):
for copy in changes:
# Copy change will have key, must also have other attributes, defined in `copy`
# Just pass as keyword arguments here to let copy do the validation
- copy_errors = self.copy(copy["key"], **copy)
- if copy_errors:
- copy.update({"errors": copy_errors})
+ try:
+ self.copy(copy["key"], **copy)
+ except Exception as e:
+ log_sync_exception(e, user=self.request.user, change=copy)
+ copy["errors"] = [str(e)]
errors.append(copy)
+ failed_copy_node = self.get_queryset().filter(pk=copy["key"]).first()
+ if failed_copy_node is not None:
+ failed_copy_node.delete()
return errors
def copy(
@@ -924,30 +930,24 @@ def copy(
excluded_descendants=None,
**kwargs
):
- try:
- target, position = self.validate_targeting_args(target, position)
- except ValidationError as e:
- return str(e)
+ target, position = self.validate_targeting_args(target, position)
try:
source = self.get_queryset().get(pk=from_key)
except ContentNode.DoesNotExist:
- error = ValidationError("Copy source node does not exist")
- return str(error)
+ raise ValidationError("Copy source node does not exist")
# Affected channel for the copy is the target's channel
channel_id = target.channel_id
if ContentNode.filter_by_pk(pk=pk).exists():
- error = ValidationError("Copy pk already exists")
- return str(error)
+ raise ValidationError("Copy pk already exists")
can_edit_source_channel = ContentNode.filter_edit_queryset(
ContentNode.filter_by_pk(pk=source.id), user=self.request.user
).exists()
with create_change_tracker(pk, CONTENTNODE, channel_id, self.request.user, "copy_nodes") as progress_tracker:
-
new_node = source.copy_to(
target,
position,
@@ -962,15 +962,13 @@ def copy(
generate_update_event(
pk,
CONTENTNODE,
- {COPYING_FLAG: False, "node_id": new_node.node_id},
+ {COPYING_STATUS: COPYING_STATUS_VALUES.SUCCESS, "node_id": new_node.node_id},
channel_id=channel_id
),
applied=True,
created_by_id=self.request.user.id,
)
- return None
-
def perform_create(self, serializer, change=None):
instance = serializer.save()
diff --git a/contentcuration/contentcuration/viewsets/file.py b/contentcuration/contentcuration/viewsets/file.py
index 860645a146..a4131dce0e 100644
--- a/contentcuration/contentcuration/viewsets/file.py
+++ b/contentcuration/contentcuration/viewsets/file.py
@@ -114,18 +114,23 @@ class FileViewSet(BulkDeleteMixin, BulkUpdateMixin, ReadOnlyValuesViewset):
def delete_from_changes(self, changes):
try:
- # reset channel resource size cache
+ # Reset channel resource size cache.
keys = [change["key"] for change in changes]
- queryset = self.filter_queryset_from_keys(
+ files_qs = self.filter_queryset_from_keys(
self.get_edit_queryset(), keys
).order_by()
- # find all root nodes for files, and reset the cache modified date
+ # Find all root nodes for files, and reset the cache modified date.
root_nodes = ContentNode.objects.filter(
parent__isnull=True,
- tree_id__in=queryset.values_list('contentnode__tree_id', flat=True).distinct(),
+ tree_id__in=files_qs.values_list('contentnode__tree_id', flat=True).distinct(),
)
for root_node in root_nodes:
ResourceSizeCache(root_node).reset_modified(None)
+
+ # Update file's contentnode content_id.
+ for file in files_qs:
+ file.update_contentnode_content_id()
+
except Exception as e:
report_exception(e)
@@ -149,6 +154,8 @@ def upload_url(self, request):
if not isinstance(duration, (int, float)):
return HttpResponseBadRequest(reason="File duration must be a number")
duration = math.floor(duration)
+ if duration <= 0:
+ return HttpResponseBadRequest(reason="File duration is equal to or less than 0")
try:
request.user.check_space(float(size), checksum)
diff --git a/contentcuration/contentcuration/viewsets/sync/base.py b/contentcuration/contentcuration/viewsets/sync/base.py
index 455a97e4aa..f11a8f4729 100644
--- a/contentcuration/contentcuration/viewsets/sync/base.py
+++ b/contentcuration/contentcuration/viewsets/sync/base.py
@@ -22,6 +22,7 @@
from contentcuration.viewsets.sync.constants import COPIED
from contentcuration.viewsets.sync.constants import CREATED
from contentcuration.viewsets.sync.constants import DELETED
+from contentcuration.viewsets.sync.constants import DEPLOYED
from contentcuration.viewsets.sync.constants import EDITOR_M2M
from contentcuration.viewsets.sync.constants import FILE
from contentcuration.viewsets.sync.constants import INVITATION
@@ -92,6 +93,7 @@ def get_change_type(obj):
COPIED: "copy_from_changes",
PUBLISHED: "publish_from_changes",
SYNCED: "sync_from_changes",
+ DEPLOYED: "deploy_from_changes",
}
diff --git a/contentcuration/contentcuration/viewsets/sync/constants.py b/contentcuration/contentcuration/viewsets/sync/constants.py
index 6e553b6ccd..ace4ebaf7b 100644
--- a/contentcuration/contentcuration/viewsets/sync/constants.py
+++ b/contentcuration/contentcuration/viewsets/sync/constants.py
@@ -6,6 +6,7 @@
COPIED = 5
PUBLISHED = 6
SYNCED = 7
+DEPLOYED = 8
ALL_CHANGES = set([
@@ -16,6 +17,7 @@
COPIED,
PUBLISHED,
SYNCED,
+ DEPLOYED,
])
# Client-side table constants
@@ -53,7 +55,15 @@
)
-# Key to use for whether a node is currently copying
-COPYING_FLAG = "__COPYING"
+# Enum for copying states
+class COPYING_STATUS_VALUES:
+ COPYING = "COPYING"
+ FAILED = "FAILED"
+ SUCCESS = "SUCCESS"
+
+
+# Key to track copying status
+COPYING_STATUS = "__COPYING_STATUS"
+
# Key for tracking id of async task that is relevant to this indexedDB row
TASK_ID = "__TASK_ID"
diff --git a/contentcuration/contentcuration/viewsets/sync/utils.py b/contentcuration/contentcuration/viewsets/sync/utils.py
index 1d3718b5e2..f86b9685d8 100644
--- a/contentcuration/contentcuration/viewsets/sync/utils.py
+++ b/contentcuration/contentcuration/viewsets/sync/utils.py
@@ -1,11 +1,14 @@
import logging
+from django.conf import settings
+
from contentcuration.utils.sentry import report_exception
from contentcuration.viewsets.sync.constants import ALL_TABLES
from contentcuration.viewsets.sync.constants import CHANNEL
from contentcuration.viewsets.sync.constants import COPIED
from contentcuration.viewsets.sync.constants import CREATED
from contentcuration.viewsets.sync.constants import DELETED
+from contentcuration.viewsets.sync.constants import DEPLOYED
from contentcuration.viewsets.sync.constants import MOVED
from contentcuration.viewsets.sync.constants import PUBLISHED
from contentcuration.viewsets.sync.constants import UPDATED
@@ -74,6 +77,11 @@ def generate_publish_event(
return event
+def generate_deploy_event(key, user_id):
+ event = _generate_event(key, CHANNEL, DEPLOYED, channel_id=key, user_id=user_id)
+ return event
+
+
def log_sync_exception(e, user=None, change=None, changes=None):
# Capture exception and report, but allow sync
# to complete properly.
@@ -86,7 +94,9 @@ def log_sync_exception(e, user=None, change=None, changes=None):
elif changes is not None:
contexts["changes"] = changes
- report_exception(e, user=user, contexts=contexts)
+ # in production, we'll get duplicates in Sentry if we log the exception here.
+ if settings.DEBUG:
+ # make sure we leave a record in the logs just in case.
+ logging.exception(e)
- # make sure we leave a record in the logs just in case.
- logging.error(e)
+ report_exception(e, user=user, contexts=contexts)
diff --git a/contentcuration/contentcuration/wsgi.py b/contentcuration/contentcuration/wsgi.py
index 9fa3a86379..94e4dac5a8 100644
--- a/contentcuration/contentcuration/wsgi.py
+++ b/contentcuration/contentcuration/wsgi.py
@@ -13,7 +13,7 @@
# Attach newrelic APM
try:
import newrelic.agent
-
+ newrelic.agent.disable_browser_autorum()
newrelic.agent.initialize()
except ImportError:
pass
diff --git a/contentcuration/kolibri_content/base_models.py b/contentcuration/kolibri_content/base_models.py
new file mode 100644
index 0000000000..0fbe59c710
--- /dev/null
+++ b/contentcuration/kolibri_content/base_models.py
@@ -0,0 +1,190 @@
+"""
+This file is an exact copy of the base_models.py file from Kolibri.
+The only modifications are to vendor imports from Morango and Kolibri
+for custom field definitions. These have been placed in kolibri_content.fields.
+
+In addition, any foreign key fields have on_delete definitions added for Django 3 compatibility.
+https://github.com/learningequality/kolibri/blob/0f6bb6781a4453cd9fdc836d52b65dd69e395b20/kolibri/core/content/base_models.py#L68
+"""
+from __future__ import print_function
+
+from django.db import models
+from kolibri_content.fields import DateTimeTzField
+from kolibri_content.fields import JSONField
+from kolibri_content.fields import UUIDField
+from le_utils.constants import content_kinds
+from le_utils.constants import file_formats
+from le_utils.constants import format_presets
+from le_utils.constants.languages import LANGUAGE_DIRECTIONS
+from mptt.models import MPTTModel
+from mptt.models import TreeForeignKey
+
+
+MAX_TAG_LENGTH = 30
+
+
+class ContentTag(models.Model):
+ id = UUIDField(primary_key=True)
+ tag_name = models.CharField(max_length=MAX_TAG_LENGTH, blank=True)
+
+ class Meta:
+ abstract = True
+
+
+class ContentNode(MPTTModel):
+ """
+ The primary object type in a content database. Defines the properties that are shared
+ across all content types.
+
+ It represents videos, exercises, audio, documents, and other 'content items' that
+ exist as nodes in content channels.
+ """
+
+ id = UUIDField(primary_key=True)
+ parent = TreeForeignKey(
+ "self", null=True, blank=True, related_name="children", db_index=True, on_delete=models.CASCADE
+ )
+ license_name = models.CharField(max_length=50, null=True, blank=True)
+ license_description = models.TextField(null=True, blank=True)
+ has_prerequisite = models.ManyToManyField(
+ "self", related_name="prerequisite_for", symmetrical=False, blank=True
+ )
+ related = models.ManyToManyField("self", symmetrical=True, blank=True)
+ tags = models.ManyToManyField(
+ "ContentTag", symmetrical=False, related_name="tagged_content", blank=True
+ )
+ title = models.CharField(max_length=200)
+ coach_content = models.BooleanField(default=False)
+
+ # the content_id is used for tracking a user's interaction with a piece of
+ # content, in the face of possibly many copies of that content. When a user
+ # interacts with a piece of content, all substantially similar pieces of
+ # content should be marked as such as well. We track these "substantially
+ # similar" types of content by having them have the same content_id.
+ content_id = UUIDField(db_index=True)
+ channel_id = UUIDField(db_index=True)
+
+ description = models.TextField(blank=True, null=True)
+ sort_order = models.FloatField(blank=True, null=True)
+ license_owner = models.CharField(max_length=200, blank=True)
+ author = models.CharField(max_length=200, blank=True)
+ kind = models.CharField(max_length=200, choices=content_kinds.choices, blank=True)
+ available = models.BooleanField(default=False)
+ lang = models.ForeignKey("Language", blank=True, null=True, on_delete=models.CASCADE)
+
+ # A JSON Dictionary of properties to configure loading, rendering, etc. the file
+ options = JSONField(default={}, blank=True, null=True)
+
+ # Fields for metadata labels
+ grade_levels = models.TextField(blank=True, null=True)
+ resource_types = models.TextField(blank=True, null=True)
+ learning_activities = models.TextField(blank=True, null=True)
+ accessibility_labels = models.TextField(blank=True, null=True)
+ categories = models.TextField(blank=True, null=True)
+ learner_needs = models.TextField(blank=True, null=True)
+
+ # The (suggested) duration of a resource, in seconds.
+ duration = models.PositiveIntegerField(null=True, blank=True)
+
+ class Meta:
+ abstract = True
+
+
+class Language(models.Model):
+ id = models.CharField(max_length=14, primary_key=True)
+ lang_code = models.CharField(max_length=3, db_index=True)
+ lang_subcode = models.CharField(max_length=10, db_index=True, blank=True, null=True)
+ # Localized name
+ lang_name = models.CharField(max_length=100, blank=True, null=True)
+ lang_direction = models.CharField(
+ max_length=3, choices=LANGUAGE_DIRECTIONS, default=LANGUAGE_DIRECTIONS[0][0]
+ )
+
+ class Meta:
+ abstract = True
+
+
+class File(models.Model):
+ """
+ The second to bottom layer of the contentDB schema, defines the basic building brick for content.
+ Things it can represent are, for example, mp4, avi, mov, html, css, jpeg, pdf, mp3...
+ """
+
+ id = UUIDField(primary_key=True)
+ # The foreign key mapping happens here as many File objects can map onto a single local file
+ local_file = models.ForeignKey("LocalFile", related_name="files", on_delete=models.CASCADE)
+ contentnode = models.ForeignKey("ContentNode", related_name="files", on_delete=models.CASCADE)
+ preset = models.CharField(
+ max_length=150, choices=format_presets.choices, blank=True
+ )
+ lang = models.ForeignKey("Language", blank=True, null=True, on_delete=models.CASCADE)
+ supplementary = models.BooleanField(default=False)
+ thumbnail = models.BooleanField(default=False)
+ priority = models.IntegerField(blank=True, null=True, db_index=True)
+
+ class Meta:
+ abstract = True
+
+
+class LocalFile(models.Model):
+ """
+ The bottom layer of the contentDB schema, defines the local state of files on the device storage.
+ """
+
+ # ID should be the checksum of the file
+ id = models.CharField(max_length=32, primary_key=True)
+ extension = models.CharField(
+ max_length=40, choices=file_formats.choices, blank=True
+ )
+ available = models.BooleanField(default=False)
+ file_size = models.IntegerField(blank=True, null=True)
+
+ class Meta:
+ abstract = True
+
+
+class AssessmentMetaData(models.Model):
+ """
+ A model to describe additional metadata that characterizes assessment behaviour in Kolibri.
+ This model contains additional fields that are only revelant to content nodes that probe a
+ user's state of knowledge and allow them to practice to Mastery.
+ ContentNodes with this metadata may also be able to be used within quizzes and exams.
+ """
+
+ id = UUIDField(primary_key=True)
+ contentnode = models.ForeignKey("ContentNode", related_name="assessmentmetadata", on_delete=models.CASCADE)
+ # A JSON blob containing a serialized list of ids for questions that the assessment can present.
+ assessment_item_ids = JSONField(default=[])
+ # Length of the above assessment_item_ids for a convenience lookup.
+ number_of_assessments = models.IntegerField()
+ # A JSON blob describing the mastery model that is used to set this assessment as mastered.
+ mastery_model = JSONField(default={})
+ # Should the questions listed in assessment_item_ids be presented in a random order?
+ randomize = models.BooleanField(default=False)
+ # Is this assessment compatible with being previewed and answer filled for display in coach reports
+ # and use in summative and formative tests?
+ is_manipulable = models.BooleanField(default=False)
+
+ class Meta:
+ abstract = True
+
+
+class ChannelMetadata(models.Model):
+ """
+ Holds metadata about all existing content databases that exist locally.
+ """
+
+ id = UUIDField(primary_key=True)
+ name = models.CharField(max_length=200)
+ description = models.CharField(max_length=400, blank=True)
+ tagline = models.CharField(max_length=150, blank=True, null=True)
+ author = models.CharField(max_length=400, blank=True)
+ version = models.IntegerField(default=0)
+ thumbnail = models.TextField(blank=True)
+ last_updated = DateTimeTzField(null=True)
+ # Minimum version of Kolibri that this content database is compatible with
+ min_schema_version = models.CharField(max_length=50)
+ root = models.ForeignKey("ContentNode", on_delete=models.CASCADE)
+
+ class Meta:
+ abstract = True
diff --git a/contentcuration/kolibri_content/constants/schema_versions.py b/contentcuration/kolibri_content/constants/schema_versions.py
new file mode 100644
index 0000000000..01e7ac1f88
--- /dev/null
+++ b/contentcuration/kolibri_content/constants/schema_versions.py
@@ -0,0 +1,47 @@
+"""
+This is a direct copy of the constants file of the same name in kolibri.core.content
+https://github.com/learningequality/kolibri/blob/c7417e1d558a1e1e52ac8423927d61a0e44da576/kolibri/core/content/constants/schema_versions.py
+"""
+
+V020BETA1 = "v0.2.0-beta1"
+
+V040BETA3 = "v0.4.0-beta3"
+
+NO_VERSION = "unversioned"
+
+VERSION_1 = "1"
+
+VERSION_2 = "2"
+
+VERSION_3 = "3"
+
+VERSION_4 = "4"
+
+VERSION_5 = "5"
+
+# List of the content db schema versions, ordered from most recent to least recent.
+# When a new schema version is generated, it should be added here, at the top of the list.
+CONTENT_DB_SCHEMA_VERSIONS = [
+ VERSION_5,
+ VERSION_4,
+ VERSION_3,
+ VERSION_2,
+ VERSION_1,
+ NO_VERSION,
+ V040BETA3,
+ V020BETA1,
+]
+
+# The latest compatible exported schema version for this version of Kolibri
+CONTENT_SCHEMA_VERSION = VERSION_5
+
+# The version name for the current content schema,
+# which may have schema modifications not present in the export schema
+CURRENT_SCHEMA_VERSION = "current"
+
+# The minimum content schema version that we are able to provide import metadata for
+# from the Kolibri content app database tables.
+# This should be updated if we make a change to the content schema that it would be
+# exceptionally difficult for us to backfill, such as deleting a model field that
+# we cannot meaningfully infer the content of from other metadata.
+MIN_CONTENT_SCHEMA_VERSION = VERSION_5
diff --git a/contentcuration/kolibri_content/fields.py b/contentcuration/kolibri_content/fields.py
new file mode 100644
index 0000000000..5220359026
--- /dev/null
+++ b/contentcuration/kolibri_content/fields.py
@@ -0,0 +1,185 @@
+import datetime
+import json
+import re
+import uuid
+
+import pytz
+from django.db import models
+from django.db.backends.utils import typecast_timestamp
+from django.db.models.fields import Field
+from django.utils import timezone
+from jsonfield import JSONField as JSONFieldBase
+
+
+date_time_format = "%Y-%m-%d %H:%M:%S.%f"
+tz_format = "({tz})"
+tz_regex = re.compile(r"\(([^\)]+)\)")
+db_storage_string = "{date_time_string}{tz_string}"
+
+
+def parse_timezonestamp(value):
+ if tz_regex.search(value):
+ tz = pytz.timezone(tz_regex.search(value).groups()[0])
+ else:
+ tz = timezone.get_current_timezone()
+ utc_value = tz_regex.sub("", value)
+ value = typecast_timestamp(utc_value)
+ if value.tzinfo is None:
+ # Naive datetime, make aware
+ value = timezone.make_aware(value, pytz.utc)
+ return value.astimezone(tz)
+
+
+def create_timezonestamp(value):
+ if value.tzinfo and hasattr(value.tzinfo, "zone"):
+ # We have a pytz timezone, we can work with this
+ tz = value.tzinfo.zone
+ elif value.tzinfo:
+ # Got some timezone data, but it's not a pytz timezone
+ # Let's just assume someone used dateutil parser on a UTC
+ # ISO format timestamp
+ # Fixes https://github.com/learningequality/kolibri/issues/1824
+ tz = pytz.utc
+ value = value.astimezone(tz)
+ else:
+ tz = timezone.get_current_timezone().zone
+ value = timezone.make_aware(value, timezone.get_current_timezone())
+ date_time_string = value.astimezone(pytz.utc).strftime(date_time_format)
+ tz_string = tz_format.format(tz=tz)
+ value = db_storage_string.format(
+ date_time_string=date_time_string, tz_string=tz_string
+ )
+ return value
+
+
+class DateTimeTzField(Field):
+ """
+ A field that stores datetime information as a char in this format:
+
+ %Y-%m-%d %H:%M:%S.%f()
+
+ It reads a timezone aware datetime object, and extracts the timezone zone information
+ then parses the datetime into the format above with the timezone information appended.
+
+ As this is ISO formatted, alphabetic sorting should still allow for proper queries
+ against this in the database. Mostly engineered for SQLite usage.
+ Vendored from Kolibri:
+ https://github.com/learningequality/kolibri/blob/0f6bb6781a4453cd9fdc836d52b65dd69e395b20/kolibri/core/fields.py#L54
+ """
+
+ def db_type(self, connection):
+ return "varchar"
+
+ def from_db_value(self, value, expression, connection):
+ if value is None:
+ return value
+ return parse_timezonestamp(value)
+
+ def to_python(self, value):
+ if isinstance(value, datetime.datetime):
+ return value
+
+ if value is None:
+ return value
+
+ return parse_timezonestamp(value)
+
+ def get_prep_value(self, value):
+ # Casts datetimes into the format expected by the backend
+ if value is None:
+ return value
+ if isinstance(value, str):
+ value = parse_timezonestamp(value)
+ return create_timezonestamp(value)
+
+ def get_db_prep_value(self, value, connection, prepared=False):
+ if not prepared:
+ value = self.get_prep_value(value)
+ return value
+
+ def value_from_object_json_compatible(self, obj):
+ if self.value_from_object(obj):
+ return create_timezonestamp(self.value_from_object(obj))
+
+
+class JSONField(JSONFieldBase):
+ """
+ This may have been corrected in the 3.0 release of JSONField, but we copy the Kolibri
+ implementation here to be sure:
+ https://github.com/learningequality/kolibri/blob/0f6bb6781a4453cd9fdc836d52b65dd69e395b20/kolibri/core/fields.py#L102
+ """
+ def from_db_value(self, value, expression, connection):
+ if isinstance(value, str):
+ try:
+ return json.loads(value, **self.load_kwargs)
+ except ValueError:
+ pass
+
+ return value
+
+ def to_python(self, value):
+ if isinstance(value, str):
+ try:
+ return json.loads(value, **self.load_kwargs)
+ except ValueError:
+ pass
+
+ return value
+
+
+class UUIDField(models.CharField):
+ """
+ Adaptation of Django's UUIDField, but with 32-char hex representation as Python representation rather than a UUID instance.
+ Vendored from Morango:
+ https://github.com/learningequality/morango/blob/fd882f6f6dfa9a786ee678b9f99039a5199a6ed6/morango/models/fields/uuids.py#L12
+ """
+
+ def __init__(self, *args, **kwargs):
+ kwargs["max_length"] = 32
+ super(UUIDField, self).__init__(*args, **kwargs)
+
+ def prepare_value(self, value):
+ if isinstance(value, uuid.UUID):
+ return value.hex
+ return value
+
+ def deconstruct(self):
+ name, path, args, kwargs = super(UUIDField, self).deconstruct()
+ del kwargs["max_length"]
+ return name, path, args, kwargs
+
+ def get_internal_type(self):
+ return "UUIDField"
+
+ def get_db_prep_value(self, value, connection, prepared=False):
+ if value is None:
+ return None
+ if not isinstance(value, uuid.UUID):
+ try:
+ value = uuid.UUID(value)
+ except AttributeError:
+ raise TypeError(self.error_messages["invalid"] % {"value": value})
+ return value.hex
+
+ def from_db_value(self, value, expression, connection):
+ return self.to_python(value)
+
+ def to_python(self, value):
+ if isinstance(value, uuid.UUID):
+ return value.hex
+ return value
+
+ def get_default(self):
+ """
+ Returns the default value for this field.
+ """
+ if self.has_default():
+ if callable(self.default):
+ default = self.default()
+ if isinstance(default, uuid.UUID):
+ return default.hex
+ return default
+ if isinstance(self.default, uuid.UUID):
+ return self.default.hex
+ return self.default
+ return None
diff --git a/contentcuration/kolibri_content/migrations/0019_auto_20230207_0116.py b/contentcuration/kolibri_content/migrations/0019_auto_20230207_0116.py
new file mode 100644
index 0000000000..55cada5291
--- /dev/null
+++ b/contentcuration/kolibri_content/migrations/0019_auto_20230207_0116.py
@@ -0,0 +1,72 @@
+# Generated by Django 3.2.14 on 2023-02-07 01:16
+import django.db.models.deletion
+import kolibri_content.fields
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('content', '0018_auto_20220224_2031'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='contentnode',
+ options={},
+ ),
+ migrations.AlterModelOptions(
+ name='file',
+ options={},
+ ),
+ migrations.AlterField(
+ model_name='assessmentmetadata',
+ name='assessment_item_ids',
+ field=kolibri_content.fields.JSONField(default=[]),
+ ),
+ migrations.AlterField(
+ model_name='assessmentmetadata',
+ name='mastery_model',
+ field=kolibri_content.fields.JSONField(default={}),
+ ),
+ migrations.AlterField(
+ model_name='contentnode',
+ name='coach_content',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AlterField(
+ model_name='contentnode',
+ name='description',
+ field=models.TextField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name='contentnode',
+ name='duration',
+ field=models.PositiveIntegerField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name='contentnode',
+ name='lang',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='content.language'),
+ ),
+ migrations.AlterField(
+ model_name='contentnode',
+ name='license_description',
+ field=models.TextField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name='contentnode',
+ name='options',
+ field=kolibri_content.fields.JSONField(blank=True, default={}, null=True),
+ ),
+ migrations.AlterField(
+ model_name='file',
+ name='lang',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='content.language'),
+ ),
+ migrations.AlterIndexTogether(
+ name='contentnode',
+ index_together=set(),
+ ),
+ ]
diff --git a/contentcuration/kolibri_content/migrations/0020_alter_file_preset.py b/contentcuration/kolibri_content/migrations/0020_alter_file_preset.py
new file mode 100644
index 0000000000..08f714d583
--- /dev/null
+++ b/contentcuration/kolibri_content/migrations/0020_alter_file_preset.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.24 on 2024-03-22 23:30
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('content', '0019_auto_20230207_0116'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='file',
+ name='preset',
+ field=models.CharField(blank=True, choices=[('high_res_video', 'High Resolution'), ('low_res_video', 'Low Resolution'), ('video_thumbnail', 'Thumbnail'), ('video_subtitle', 'Subtitle'), ('video_dependency', 'Video (dependency)'), ('audio', 'Audio'), ('audio_thumbnail', 'Thumbnail'), ('audio_dependency', 'audio (dependency)'), ('document', 'Document'), ('epub', 'ePub Document'), ('document_thumbnail', 'Thumbnail'), ('exercise', 'Exercise'), ('exercise_thumbnail', 'Thumbnail'), ('exercise_image', 'Exercise Image'), ('exercise_graphie', 'Exercise Graphie'), ('channel_thumbnail', 'Channel Thumbnail'), ('topic_thumbnail', 'Thumbnail'), ('html5_zip', 'HTML5 Zip'), ('html5_dependency', 'HTML5 Dependency (Zip format)'), ('html5_thumbnail', 'HTML5 Thumbnail'), ('h5p', 'H5P Zip'), ('h5p_thumbnail', 'H5P Thumbnail'), ('zim', 'Zim'), ('zim_thumbnail', 'Zim Thumbnail'), ('qti', 'QTI Zip'), ('qti_thumbnail', 'QTI Thumbnail'), ('slideshow_image', 'Slideshow Image'), ('slideshow_thumbnail', 'Slideshow Thumbnail'), ('slideshow_manifest', 'Slideshow Manifest'), ('imscp_zip', 'IMSCP Zip')], max_length=150),
+ ),
+ ]
diff --git a/contentcuration/kolibri_content/models.py b/contentcuration/kolibri_content/models.py
index 763f3b5995..1ceab27b64 100644
--- a/contentcuration/kolibri_content/models.py
+++ b/contentcuration/kolibri_content/models.py
@@ -1,14 +1,14 @@
+"""
+These models are defined by combining the base_models from Kolibri
+and then manually adding the fields and models from legacy_models
+in order to create a completely backwards compatible export database.
+"""
from __future__ import unicode_literals
-import uuid
-
from django.db import models
-from le_utils.constants import content_kinds
+from kolibri_content import base_models
+from kolibri_content.fields import UUIDField
from le_utils.constants import file_formats
-from le_utils.constants import format_presets
-from le_utils.constants.languages import LANGUAGE_DIRECTIONS
-from mptt.models import MPTTModel
-from mptt.models import TreeForeignKey
class License(models.Model):
@@ -16,283 +16,52 @@ class License(models.Model):
license_description = models.CharField(max_length=400, null=True, blank=True)
-class UUIDField(models.CharField):
- """
- Adaptation of Django's UUIDField, but with 32-char hex representation as Python representation rather than a UUID instance.
- """
-
- def __init__(self, *args, **kwargs):
- kwargs["max_length"] = 32
- super(UUIDField, self).__init__(*args, **kwargs)
-
- def prepare_value(self, value):
- if isinstance(value, uuid.UUID):
- return value.hex
- return value
-
- def deconstruct(self):
- name, path, args, kwargs = super(UUIDField, self).deconstruct()
- del kwargs["max_length"]
- return name, path, args, kwargs
-
- def get_internal_type(self):
- return "UUIDField"
-
- def get_db_prep_value(self, value, connection, prepared=False):
- if value is None:
- return None
- if not isinstance(value, uuid.UUID):
- try:
- value = uuid.UUID(value)
- except AttributeError:
- raise TypeError(self.error_messages["invalid"] % {"value": value})
- return value.hex
-
- def from_db_value(self, value, expression, connection):
- return self.to_python(value)
-
- def to_python(self, value):
- if isinstance(value, uuid.UUID):
- return value.hex
- return value
-
-
-class ContentTag(models.Model):
- id = UUIDField(primary_key=True)
- tag_name = models.CharField(max_length=30, blank=True)
-
- def __str__(self):
- return self.tag_name
-
-
-class ContentNode(MPTTModel):
- """
- The top layer of the contentDB schema, defines the most common properties that are shared across all different contents.
- Things it can represent are, for example, video, exercise, audio or document...
- """
-
- id = UUIDField(primary_key=True)
- parent = TreeForeignKey(
- "self",
- null=True,
- blank=True,
- related_name="children",
- db_index=True,
- on_delete=models.CASCADE,
- )
- license_name = models.CharField(max_length=50, null=True, blank=True)
- license_description = models.CharField(max_length=400, null=True, blank=True)
- has_prerequisite = models.ManyToManyField(
- "self", related_name="prerequisite_for", symmetrical=False, blank=True
- )
- related = models.ManyToManyField("self", symmetrical=True, blank=True)
- tags = models.ManyToManyField(
- ContentTag, symmetrical=False, related_name="tagged_content", blank=True
- )
-
- title = models.CharField(max_length=200)
-
- # the content_id is used for tracking a user's interaction with a piece of
- # content, in the face of possibly many copies of that content. When a user
- # interacts with a piece of content, all substantially similar pieces of
- # content should be marked as such as well. We track these "substantially
- # similar" types of content by having them have the same content_id.
- content_id = UUIDField(db_index=True)
- channel_id = UUIDField(db_index=True)
+class ContentTag(base_models.ContentTag):
+ pass
- description = models.CharField(max_length=400, blank=True, null=True)
- sort_order = models.FloatField(blank=True, null=True)
- license_owner = models.CharField(max_length=200, blank=True)
- author = models.CharField(max_length=200, blank=True)
- kind = models.CharField(max_length=200, choices=content_kinds.choices, blank=True)
- available = models.BooleanField(default=False)
- stemmed_metaphone = models.CharField(
- max_length=1800, blank=True
- ) # for fuzzy search in title and description
- lang = models.ForeignKey(
- "Language", blank=True, null=True, on_delete=models.SET_NULL
- )
- coach_content = models.BooleanField(default=False, db_index=True)
+class ContentNode(base_models.ContentNode):
# Added legacy fields
license = models.ForeignKey(
"License", null=True, blank=True, on_delete=models.SET_NULL
)
-
- # A JSON Dictionary of properties to configure loading, rendering, etc. the file
- options = models.TextField(default="{}")
-
- # Fields for metadata labels
- grade_levels = models.TextField(blank=True, null=True)
- resource_types = models.TextField(blank=True, null=True)
- learning_activities = models.TextField(blank=True, null=True)
- accessibility_labels = models.TextField(blank=True, null=True)
- categories = models.TextField(blank=True, null=True)
- learner_needs = models.TextField(blank=True, null=True)
-
- # If media, the duration in seconds
- duration = models.IntegerField(null=True, blank=True)
-
- class Meta:
- ordering = ("lft",)
- index_together = [
- ["level", "channel_id", "kind"],
- ["level", "channel_id", "available"],
- ]
-
- def __str__(self):
- return self.title
-
- def get_descendant_content_ids(self):
- """
- Retrieve a queryset of content_ids for non-topic content nodes that are
- descendants of this node.
- """
- return (
- ContentNode.objects.filter(lft__gte=self.lft, lft__lte=self.rght)
- .exclude(kind=content_kinds.TOPIC)
- .values_list("content_id", flat=True)
- )
-
-
-class Language(models.Model):
- id = models.CharField(max_length=14, primary_key=True)
- lang_code = models.CharField(max_length=3, db_index=True)
- lang_subcode = models.CharField(max_length=10, db_index=True, blank=True, null=True)
- # Localized name
- lang_name = models.CharField(max_length=100, blank=True, null=True)
- lang_direction = models.CharField(
- max_length=3, choices=LANGUAGE_DIRECTIONS, default=LANGUAGE_DIRECTIONS[0][0]
- )
-
- def __str__(self):
- return self.lang_name or ""
+ stemmed_metaphone = models.CharField(
+ max_length=1800, blank=True
+ ) # for fuzzy search in title and description
+ # For compatibility with Kolibri versions prior to v0.9.2 we could include these
+ # description = models.CharField(max_length=400, blank=True, null=True)
+ # license_description = models.CharField(max_length=400, blank=True, null=True)
+ # However, as SQLite only has a single TEXT column type for strings
+ # the distinction here is unnecessary, and exports of a TextField type will
+ # function just as well.
-class File(models.Model):
- """
- The second to bottom layer of the contentDB schema, defines the basic building brick for content.
- Things it can represent are, for example, mp4, avi, mov, html, css, jpeg, pdf, mp3...
- """
+class Language(base_models.Language):
+ pass
- id = UUIDField(primary_key=True)
- # The foreign key mapping happens here as many File objects can map onto a single local file
- local_file = models.ForeignKey(
- "LocalFile", related_name="files", on_delete=models.CASCADE
- )
- available = models.BooleanField(default=False)
- contentnode = models.ForeignKey(
- ContentNode, related_name="files", on_delete=models.CASCADE
- )
- preset = models.CharField(
- max_length=150, choices=format_presets.choices, blank=True
- )
- lang = models.ForeignKey(Language, blank=True, null=True, on_delete=models.SET_NULL)
- supplementary = models.BooleanField(default=False)
- thumbnail = models.BooleanField(default=False)
- priority = models.IntegerField(blank=True, null=True, db_index=True)
+class File(base_models.File):
# Added legacy fields
extension = models.CharField(
max_length=40, choices=file_formats.choices, blank=True
)
file_size = models.IntegerField(blank=True, null=True)
checksum = models.CharField(max_length=400, blank=True)
-
- class Meta:
- ordering = ["priority"]
-
- class Admin:
- pass
-
- def get_extension(self):
- return self.local_file.extension
-
- def get_file_size(self):
- return self.local_file.file_size
-
- def get_storage_url(self):
- return self.local_file.get_storage_url()
-
-
-class LocalFileManager(models.Manager):
- def get_orphan_files(self):
- return self.filter(files__isnull=True)
-
- def delete_orphan_file_objects(self):
- return self.get_orphan_files().delete()
-
-
-class LocalFile(models.Model):
- """
- The bottom layer of the contentDB schema, defines the local state of files on the device storage.
- """
-
- # ID should be the checksum of the file
- id = models.CharField(max_length=32, primary_key=True)
- extension = models.CharField(
- max_length=40, choices=file_formats.choices, blank=True
- )
available = models.BooleanField(default=False)
- file_size = models.IntegerField(blank=True, null=True)
-
- objects = LocalFileManager()
- class Admin:
- pass
+class LocalFile(base_models.LocalFile):
+ pass
-class AssessmentMetaData(models.Model):
- """
- A model to describe additional metadata that characterizes assessment behaviour in Kolibri.
- This model contains additional fields that are only revelant to content nodes that probe a
- user's state of knowledge and allow them to practice to Mastery.
- ContentNodes with this metadata may also be able to be used within quizzes and exams.
- """
-
- id = UUIDField(primary_key=True)
- contentnode = models.ForeignKey(
- ContentNode, related_name="assessmentmetadata", on_delete=models.CASCADE
- )
- # A JSON blob containing a serialized list of ids for questions that the assessment can present.
- assessment_item_ids = models.TextField(default="[]")
- # Length of the above assessment_item_ids for a convenience lookup.
- number_of_assessments = models.IntegerField()
- # A JSON blob describing the mastery model that is used to set this assessment as mastered.
- mastery_model = models.TextField(default="{}")
- # Should the questions listed in assessment_item_ids be presented in a random order?
- randomize = models.BooleanField(default=False)
- # Is this assessment compatible with being previewed and answer filled for display in coach reports
- # and use in summative and formative tests?
- is_manipulable = models.BooleanField(default=False)
+class AssessmentMetaData(base_models.AssessmentMetaData):
+ pass
-class ChannelMetadata(models.Model):
- """
- Holds metadata about all existing content databases that exist locally.
- """
- id = UUIDField(primary_key=True)
- name = models.CharField(max_length=200)
- description = models.CharField(max_length=400, blank=True)
- tagline = models.CharField(max_length=150, blank=True, null=True)
- author = models.CharField(max_length=400, blank=True)
- version = models.IntegerField(default=0)
- thumbnail = models.TextField(blank=True)
+class ChannelMetadata(base_models.ChannelMetadata):
+ # This has previously been rendered blank by setting as a CharField
+ # in SQLite, datetime fields are stored as CharFields anyway,
+ # so for exported channel databases this makes no difference.
last_updated = models.CharField(null=True, max_length=200)
- # Minimum version of Kolibri that this content database is compatible with
- min_schema_version = models.CharField(max_length=50)
- root = models.ForeignKey(ContentNode, on_delete=models.CASCADE)
-
# Added legacy fields
root_pk = UUIDField()
-
- class Admin:
- pass
-
- def __str__(self):
- return self.name
-
- def delete_content_tree_and_files(self):
- # Use Django ORM to ensure cascading delete:
- self.root.delete()
diff --git a/contentcuration/kolibri_content/router.py b/contentcuration/kolibri_content/router.py
index b7b2e69b70..9a991a9df1 100644
--- a/contentcuration/kolibri_content/router.py
+++ b/contentcuration/kolibri_content/router.py
@@ -72,6 +72,16 @@ def get_content_database_connection(alias=None):
return connections[alias].connection
+def cleanup_content_database_connection(alias):
+ try:
+ connection = connections[alias]
+ connection.close()
+ del connections.databases[alias]
+ except (ConnectionDoesNotExist, KeyError):
+ # Already cleaned up, nothing to do here!
+ pass
+
+
class ContentDBRouter(object):
"""A router that decides what content database to read from based on a thread-local variable."""
@@ -158,6 +168,7 @@ def __enter__(self):
def __exit__(self, exc_type, exc_value, traceback):
set_active_content_database(self.previous_alias)
+ cleanup_content_database_connection(self.alias)
def __call__(self, querying_func):
# allow using the context manager as a decorator
diff --git a/contentcuration/kolibri_public/__init__.py b/contentcuration/kolibri_public/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contentcuration/kolibri_public/admin.py b/contentcuration/kolibri_public/admin.py
new file mode 100644
index 0000000000..5d28852b15
--- /dev/null
+++ b/contentcuration/kolibri_public/admin.py
@@ -0,0 +1,2 @@
+# from django.contrib import admin
+# Register your models here.
diff --git a/contentcuration/kolibri_public/apps.py b/contentcuration/kolibri_public/apps.py
new file mode 100644
index 0000000000..ce4d9066cf
--- /dev/null
+++ b/contentcuration/kolibri_public/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class KolibriPublicConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'kolibri_public'
diff --git a/contentcuration/kolibri_public/constants/stopwords-all.json b/contentcuration/kolibri_public/constants/stopwords-all.json
new file mode 100644
index 0000000000..56b4e430d0
--- /dev/null
+++ b/contentcuration/kolibri_public/constants/stopwords-all.json
@@ -0,0 +1 @@
+{"af":["'n","aan","af","al","as","baie","by","daar","dag","dat","die","dit","een","ek","en","gaan","gesĆŖ","haar","het","hom","hulle","hy","in","is","jou","jy","kan","kom","ma","maar","met","my","na","nie","om","ons","op","saam","sal","se","sien","so","sy","te","toe","uit","van","vir","was","wat","Å"],"ar":["Ų","Ų¢Ų¶","Ų¢Ł
ŁŁŁ","Ų¢Ł","Ų¢ŁŲ§Ł","Ų¢Ł","Ų£","Ų£ŲØ","Ų£Ų¬Ł","Ų£Ų¬Ł
Ų¹","Ų£Ų®","Ų£Ų®Ų°","Ų£ŲµŲØŲ","Ų£Ų¶ŲŁ","Ų£ŁŲØŁ","Ų£ŁŁ","Ų£ŁŲ«Ų±","Ų£ŁŲ§","Ų£Ł
","Ų£Ł
Ų§","Ų£Ł
Ų§Ł
Ł","Ų£Ł
Ų§Ł
ŁŁ","Ų£Ł
Ų³Ł","Ų£Ł
ŁŲ§","Ų£Ł","Ų£ŁŲ§","Ų£ŁŲŖ","Ų£ŁŲŖŁ
","Ų£ŁŲŖŁ
Ų§","Ų£ŁŲŖŁ","Ų£ŁŲŖŁ","Ų£ŁŲ“Ų£","Ų£ŁŁŁ","Ų£Ł","Ų£ŁŲ“Ł","Ų£ŁŁŲ¦Ł","Ų£ŁŁŲ¦ŁŁ
","Ų£ŁŁŲ§Ų”","Ų£ŁŁŲ§ŁŁ","Ų£ŁŁŁŁ","Ų£Ł","Ų£ŁŲ§","Ų£ŁŁ","Ų£ŁŁŁ
Ų§","Ų£ŁŁ","Ų£ŁŁŁŁ","Ų£ŁŁŁŁŁ","Ų£ŁŁŁŁ","Ų„Ų°","Ų„Ų°Ų§","Ų„Ų°Ų§Ł","Ų„Ų°Ł
Ų§","Ų„Ų°Ł","Ų„ŁŁ","Ų„ŁŁŁŁ
","Ų„ŁŁŁŁ
Ų§","Ų„ŁŁŁŁŁ","Ų„ŁŁŁŁ","Ų„ŁŁŁŁŁŁ","Ų„ŁŁŲ§","Ų„Ł
ŁŲ§","Ų„Ł","Ų„ŁŁŁ
Ų§","Ų„Ł","Ų„ŁŲ§Ł","Ų„ŁŲ§ŁŁ
","Ų„ŁŲ§ŁŁ
Ų§","Ų„ŁŲ§ŁŁ","Ų„ŁŲ§ŁŲ§","Ų„ŁŲ§Ł","Ų„ŁŲ§ŁŲ§","Ų„ŁŲ§ŁŁ
","Ų„ŁŲ§ŁŁ
Ų§","Ų„ŁŲ§ŁŁ","Ų„ŁŲ§Ł","Ų„ŁŁŁ","Ų„ŁŁŁŁ","Ų§","Ų§ŲØŲŖŲÆŲ£","Ų§Ų«Ų±","Ų§Ų¬Ł","Ų§ŲŲÆ","Ų§Ų®Ų±Ł","Ų§Ų®ŁŁŁŁ","Ų§Ų°Ų§","Ų§Ų±ŲØŲ¹Ų©","Ų§Ų±ŲŖŲÆŁ","Ų§Ų³ŲŖŲŲ§Ł","Ų§Ų·Ų§Ų±","Ų§Ų¹Ų§ŲÆŲ©","Ų§Ų¹ŁŁŲŖ","Ų§Ł","Ų§ŁŲ«Ų±","Ų§ŁŲÆ","Ų§ŁŲ£ŁŲ§Ų”","Ų§ŁŲ£ŁŁ","Ų§ŁŲ§","Ų§ŁŲ§Ų®ŁŲ±Ų©","Ų§ŁŲ§Ł","Ų§ŁŲ§ŁŁ","Ų§ŁŲ§ŁŁŁ","Ų§ŁŲŖŁ","Ų§ŁŲŖŁ","Ų§ŁŲ«Ų§ŁŁ","Ų§ŁŲ«Ų§ŁŁŲ©","Ų§ŁŲ°Ų§ŲŖŁ","Ų§ŁŲ°Ł","Ų§ŁŲ°Ł","Ų§ŁŲ°ŁŁ","Ų§ŁŲ³Ų§ŲØŁ","Ų§ŁŁ","Ų§ŁŁŲ§Ų¦Ł","Ų§ŁŁŲ§ŲŖŁ","Ų§ŁŁŲŖŲ§Ł","Ų§ŁŁŲŖŁŲ§","Ų§ŁŁŲŖŁŁ","Ų§ŁŁŲ°Ų§Ł","Ų§ŁŁŲ°ŁŁ","Ų§ŁŁŁŲ§ŲŖŁ","Ų§ŁŁ
Ų§Ų¶Ł","Ų§ŁŁ
ŁŲØŁ","Ų§ŁŁŁŲŖ","Ų§ŁŁ","Ų§ŁŁŁŁ
","Ų§Ł
Ų§","Ų§Ł
Ų§Ł
","Ų§Ł
Ų³","Ų§Ł","Ų§ŁŲØŲ±Ł","Ų§ŁŁŁŲØ","Ų§ŁŁ","Ų§ŁŁŲ§","Ų§Ł","Ų§ŁŁ","Ų§Ł","Ų§ŁŲ§Ų±","Ų§ŁŲ§Ł
","Ų§ŁŲ¶Ų§","ŲØ","ŲØŲ§ŲŖ","ŲØŲ§Ų³Ł
","ŲØŲ§Ł","ŲØŲ®Ł","ŲØŲ±Ų³","ŲØŲ³ŲØŲØ","ŲØŲ³Ł","ŲØŲ“ŁŁ","ŲØŲ¶Ų¹","ŲØŲ·Ų¢Ł","ŲØŲ¹ŲÆ","ŲØŲ¹Ų¶","ŲØŁ","ŲØŁŁ
","ŲØŁŁ
Ų§","ŲØŁŁ","ŲØŁ","ŲØŁŁ","ŲØŁ
Ų§","ŲØŁ
Ų§Ų°Ų§","ŲØŁ
Ł","ŲØŁ","ŲØŁŲ§","ŲØŁ","ŲØŁŲ§","ŲØŁ","ŲØŁŲÆ","ŲØŁŁ","ŲØŁŲ³Ł","ŲØŁŁŁŁŁ","ŲØŁŲ¦ŁŲ³Ł","ŲŖŲ§ŁŁ","ŲŖŲ§ŁŁŁ","ŲŖŲØŲÆŁŁ","ŲŖŲ¬Ų§Ł","ŲŖŲŁŁŁ","ŲŖŁŁŲ§Ų”","ŲŖŁŁ","ŲŖŁŁŁ
","ŲŖŁŁŁ
Ų§","ŲŖŁ
","ŲŖŁŁŁ","ŲŖŁŁŁŁŁ","ŲŖŁŁ","ŲŖŁŁ","Ų«ŁŲ§Ų«Ų©","Ų«Ł
","Ų«Ł
Ł","Ų«Ł
ŁŲ©","Ų«ŁŁ
ŁŁ","Ų¬Ų¹Ł","Ų¬ŁŁ","Ų¬Ł
ŁŲ¹","Ų¬ŁŲ±","ŲŲ§Ų±","ŲŲ§Ų“Ų§","ŲŲ§ŁŁŲ§","ŲŲ§Ł","ŲŲŖŁ","ŲŲ±Ł","ŲŲ³ŲØ","ŲŁ
","ŲŁŲ§ŁŁ","ŲŁŁ","ŲŁŲ«","ŲŁŲ«Ł
Ų§","ŲŁŁ","ŲŁŁŁ","ŲŁŲØŁŁŲ°ŁŲ§","ŲŁŲŖŁŁŁ","ŲŁŲ°Ų§Ų±Ł","Ų®ŁŲ§","Ų®ŁŲ§Ł","ŲÆŁŁ","ŲÆŁŁŁ","Ų°Ų§","Ų°Ų§ŲŖ","Ų°Ų§Ł","Ų°Ų§ŁŁ","Ų°Ų§ŁŁ","Ų°ŁŁ","Ų°ŁŁŁ
","Ų°ŁŁŁ
Ų§","Ų°ŁŁŁ","Ų°Ł","Ų°ŁŲ§","Ų°ŁŲ§ŲŖŲ§","Ų°ŁŲ§ŲŖŁ","Ų°ŁŲŖ","Ų°ŁŁŁ","Ų°ŁŁŁŁŁ","Ų°ŁŁ","Ų°ŁŁ","Ų±Ų§Ų","Ų±Ų¬Ų¹","Ų±ŁŁŲÆŁ","Ų±ŁŲ«","Ų±ŁŲØŁŁ","Ų²ŁŲ§Ų±Ų©","Ų³ŲØŲŲ§Ł","Ų³Ų±Ų¹Ų§Ł","Ų³ŁŲ©","Ų³ŁŁŲ§ŲŖ","Ų³ŁŁ","Ų³ŁŁ","Ų³ŁŲ§Ų”Ł","Ų³ŁŲ§Ų”ŁŁ
ŁŲ§","Ų“ŲØŁ","Ų“Ų®ŲµŲ§","Ų“Ų±Ų¹","Ų“ŁŲŖŁŁŲ§ŁŁ","ŲµŲ§Ų±","ŲµŲØŲ§Ų","ŲµŁŲ±","ŲµŁŁ","ŲµŁŁ","Ų¶ŲÆ","Ų¶Ł
Ł","Ų·Ų§Ł","Ų·Ų§ŁŁ
Ų§","Ų·ŁŁ","Ų·ŁŁ","ŲøŁŁ","Ų¹Ų§ŲÆ","Ų¹Ų§Ł
","Ų¹Ų§Ł
Ų§","Ų¹Ų§Ł
Ų©","Ų¹ŲÆŲ§","Ų¹ŲÆŲ©","Ų¹ŲÆŲÆ","Ų¹ŲÆŁ
","Ų¹Ų³Ł","Ų¹Ų“Ų±","Ų¹Ų“Ų±Ų©","Ų¹ŁŁ","Ų¹ŁŁ","Ų¹ŁŁŁ","Ų¹ŁŁŁ","Ų¹ŁŁŁŲ§","Ų¹ŁŁŁ","Ų¹Ł","Ų¹ŁŲÆ","Ų¹ŁŲÆŁ
Ų§","Ų¹ŁŲ¶","Ų¹ŁŁ","Ų¹ŁŲÆŁŲ³Ł","Ų¹ŁŁ
ŁŁŲ§","ŲŗŲÆŲ§","ŲŗŁŲ±","Ł","Ł","ŁŲ§Ł","ŁŁŲ§Ł","ŁŁ","ŁŁ","ŁŁ","ŁŁŁ
","ŁŁŁ
Ų§","ŁŁŁ","ŁŁŁŲ§","ŁŲ§Ł","ŁŲ§Ł
","ŁŲØŁ","ŁŲÆ","ŁŲ·Ł","ŁŁŁ
Ų§","ŁŁŲ©","ŁŲ£ŁŁŁ
Ų§","ŁŲ£ŁŁ","ŁŲ£ŁŁ","ŁŲ£ŁŁŁ","ŁŲ§ŲÆ","ŁŲ§Ł","ŁŲ§ŁŲŖ","ŁŲ°Ų§","ŁŲ°ŁŁ","ŁŲ±ŲØ","ŁŁ","ŁŁŲ§","ŁŁŲ§ŁŁ
Ų§","ŁŁŲŖŲ§","ŁŁŁ
","ŁŁŁŁŁ
Ų§","ŁŁŁŁŁ
Ų§","ŁŁŁŁ
Ų§","ŁŁŁŁŲ§","ŁŁ
","ŁŁ
Ų§","ŁŁ","ŁŁŲŖ","ŁŁŁ","ŁŁŁŁ
Ų§","ŁŁŲ£ŁŁŁŁ","ŁŁŲ®","ŁŲ¦Ł","ŁŲ§","ŁŲ§ŲŖ","ŁŲ§Ų³ŁŁ
Ų§","ŁŲÆŁ","ŁŲÆŁ","ŁŲ¹Ł
Ų±","ŁŁŲ§Ų”","ŁŁ","ŁŁŁ
","ŁŁŁ
Ų§","ŁŁŁ","ŁŁŁŁŁŁ
Ų§","ŁŁŁ","ŁŁŁŁŲ§","ŁŁŲ§Ł
Ł
","ŁŁ
","ŁŁ
Ų§","ŁŁ
ŁŲ§","ŁŁ","ŁŁŲ§","ŁŁ","ŁŁŲ§","ŁŁ","ŁŁŁŲ§ŁŲ©","ŁŁŁŲ§","ŁŁŁ
Ų§","ŁŁ","ŁŁŲ³ŁŲŖŁ","ŁŁŲ³ŁŲŖŁ","ŁŁŲ³ŁŲŖŁŁ
","ŁŁŲ³ŁŲŖŁŁ
ŁŲ§","ŁŁŲ³ŁŲŖŁŁŁŁ","ŁŁŲ³ŁŲŖŁ","ŁŁŲ³ŁŁŁ","ŁŁŲ¹ŁŁŁŁ","ŁŁŁŁŁŁŁ","ŁŁŁŁŲŖŁ","ŁŁŁŁŲ³Ł","ŁŁŁŁŲ³ŁŲ§","ŁŁŁŁŲ³ŁŲŖŁŲ§","ŁŁŁŁŲ³ŁŲŖŁ","ŁŁŁŁŲ³ŁŁŲ§","ŁŁŁŲ³ŁŁŁŲ§","Ł
Ų§","Ł
Ų§Ų§ŁŁŁ","Ł
Ų§ŲØŲ±Ų","Ł
Ų§ŲÆŲ§Ł
","Ł
Ų§Ų°Ų§","Ł
Ų§Ų²Ų§Ł","Ł
Ų§ŁŲŖŲ¦","Ł
Ų§ŁŁ","Ł
ŲŖŁ","Ł
Ų«Ł","Ł
Ų°","Ł
Ų³Ų§Ų”","Ł
Ų¹","Ł
Ų¹Ų§Ų°","Ł
ŁŲ§ŲØŁ","Ł
ŁŲ§ŁŁŁ
","Ł
ŁŲ§ŁŁŁ
Ų§","Ł
ŁŲ§ŁŁŁŁ","Ł
ŁŲ§ŁŁŁ","Ł
ŁŁŲ§Ų±","Ł
ŁŁŁŁ","Ł
Ł
Ų§","Ł
Ł
Ł","Ł
Ł","Ł
ŁŲ°","Ł
ŁŁŲ§","Ł
Ł","Ł
ŁŁ
Ų§","Ł
ŁŁŁ","Ł
ŁŁ","ŁŲŁ","ŁŲŁ","ŁŲ¹Ł
","ŁŁŲ³","ŁŁŲ³Ł","ŁŁŲ§ŁŲ©","ŁŁŲ®Ł","ŁŁŲ¹ŁŁ
ŁŲ§","ŁŁŲ¹ŁŁ
Ł","ŁŲ§","ŁŲ§Ų¤Ł
","ŁŲ§ŁŁ","ŁŲ§ŁŁŲ§","ŁŲØŁ","ŁŲ°Ų§","ŁŲ°Ł","ŁŁŲ°Ų§","ŁŁ","ŁŁŁ
ŁŁ","ŁŁŁŲ§","ŁŁ
","ŁŁ
Ų§","ŁŁ","ŁŁŲ§","ŁŁŲ§Ł","ŁŁŲ§ŁŁ","ŁŁ","ŁŁ","ŁŁŲ§","ŁŁŲŖ","ŁŁŁŲ§","ŁŁŲ¤ŁŲ§Ų”","ŁŁŲ§ŲŖŲ§ŁŁ","ŁŁŲ§ŲŖŁŁŁŁŁ","ŁŁŲ§ŲŖŁŁ","ŁŁŲ§ŲŖŁŁ","ŁŁŲ¬Ł","ŁŁŲ°Ų§","ŁŁŲ°Ų§ŁŁ","ŁŁŲ°ŁŁŁŁŁ","ŁŁŲ°ŁŁ","ŁŁŲ°ŁŁ","ŁŁŁŁŁŁŲ§ŲŖŁ","Ł","Ł6","ŁŲ§","ŁŲ§ŲŲÆ","ŁŲ§Ų¶Ų§Ł","ŁŲ§Ų¶Ų§ŁŲŖ","ŁŲ§ŁŲÆ","ŁŲ§Ł","ŁŲ§ŁŲ§Ł","ŁŲ§ŁŲ¶Ų","ŁŲ±Ų§Ų”ŁŁ","ŁŁŁ","ŁŁŲ§Ł","ŁŁŲ§ŁŲŖ","ŁŁŲÆ","ŁŁŁ","ŁŁŲ§Ł","ŁŁŲ§ŁŲŖ","ŁŁŲ§","ŁŁŁ
","ŁŁ
Ł","ŁŁŁ","ŁŁŁ","ŁŁŁŲ£ŁŁ","ŁŁŁŁ","ŁŁŲ“ŁŁŁŲ§ŁŁŁ","ŁŁŁŁ","ŁŁ
ŁŁ","ŁŁŁ
","ŁŲ£ŁŁŲ§Ł"],"hy":["Õ”ÕµÕ¤","Õ”ÕµÕ¬","Õ”ÕµÕ¶","Õ”ÕµÕ½","Õ¤ÕøÖ","Õ¤ÕøÖÖ","Õ„Õ“","Õ„Õ¶","Õ„Õ¶Ö","Õ„Õ½","Õ„Ö","Õ§","Õ§Õ«","Õ§Õ«Õ¶","Õ§Õ«Õ¶Ö","Õ§Õ«Ö","Õ§Õ«Ö","Õ§Ö","ÕØÕ½Õæ","Õ©","Õ«","Õ«Õ¶","Õ«Õ½ÕÆ","Õ«Ö","ÕÆÕ”Õ“","Õ°Õ”Õ“Õ”Ö","Õ°Õ„Õæ","Õ°Õ„ÕæÕø","Õ“Õ„Õ¶Ö","Õ“Õ„Õ»","Õ“Õ«","Õ¶","Õ¶Õ”","Õ¶Õ”Ö","Õ¶ÖÕ”","Õ¶ÖÕ”Õ¶Ö","ÕøÖ","ÕøÖÕØ","ÕøÖÕøÕ¶Ö","ÕøÖÕŗÕ„Õ½","ÕøÖ","ÕøÖÕ“","ÕŗÕ«ÕæÕ«","Õ¾ÖÕ”","Ö"],"eu":["al","anitz","arabera","asko","baina","bat","batean","batek","bati","batzuei","batzuek","batzuetan","batzuk","bera","beraiek","berau","berauek","bere","berori","beroriek","beste","bezala","da","dago","dira","ditu","du","dute","edo","egin","ere","eta","eurak","ez","gainera","gu","gutxi","guzti","haiei","haiek","haietan","hainbeste","hala","han","handik","hango","hara","hari","hark","hartan","hau","hauei","hauek","hauetan","hemen","hemendik","hemengo","hi","hona","honek","honela","honetan","honi","hor","hori","horiei","horiek","horietan","horko","horra","horrek","horrela","horretan","horri","hortik","hura","izan","ni","noiz","nola","non","nondik","nongo","nor","nora","ze","zein","zen","zenbait","zenbat","zer","zergatik","ziren","zituen","zu","zuek","zuen","zuten"],"bn":["ą¦
ą¦¤ą¦ą¦¬","ą¦
ą¦„ą¦","ą¦
ą¦„ą¦¬ą¦¾","ą¦
ą¦Øą§ą¦Æą¦¾ą¦Æą¦¼ą§","ą¦
ą¦Øą§ą¦","ą¦
ą¦Øą§ą¦ą§","ą¦
ą¦Øą§ą¦ą§ą¦","ą¦
ą¦Øą§ą¦¤ą¦¤","ą¦
ą¦Øą§ą¦Æ","ą¦
ą¦¬ą¦§ą¦æ","ą¦
ą¦¬ą¦¶ą§ą¦Æ","ą¦
ą¦°ą§ą¦„ą¦¾ą¦¤","ą¦ą¦","ą¦ą¦ą¦¾ą¦®ą§","ą¦ą¦ą§","ą¦ą¦ą§ą¦","ą¦ą¦ą§","ą¦ą¦","ą¦ą¦¦ą§ą¦Æą¦ą¦¾ą¦ą§","ą¦ą¦Ŗą¦Øą¦¾ą¦°","ą¦ą¦Ŗą¦Øą¦æ","ą¦ą¦¬ą¦¾ą¦°","ą¦ą¦®ą¦°ą¦¾","ą¦ą¦®ą¦¾ą¦ą§","ą¦ą¦®ą¦¾ą¦¦ą§ą¦°","ą¦ą¦®ą¦¾ą¦°","ą¦ą¦®ą¦æ","ą¦ą¦°","ą¦ą¦°ą¦","ą¦","ą¦ą¦¤ą§ą¦Æą¦¾ą¦¦ą¦æ","ą¦ą¦¹ą¦¾","ą¦ą¦ą¦æą¦¤","ą¦ą¦¤ą§ą¦¤ą¦°","ą¦ą¦Øą¦æ","ą¦ą¦Ŗą¦°","ą¦ą¦Ŗą¦°ą§","ą¦","ą¦ą¦ą¦¦ą§ą¦°","ą¦ą¦ą¦°ą¦¾","ą¦ą¦","ą¦ą¦ą¦","ą¦ą¦ą¦ą¦æ","ą¦ą¦ą¦¬ą¦¾ą¦°","ą¦ą¦ą§","ą¦ą¦ą§","ą¦ą¦ą¦Ø","ą¦ą¦ą¦Øą¦","ą¦ą¦ą¦¾ą¦Øą§","ą¦ą¦ą¦¾ą¦Øą§ą¦","ą¦ą¦ą¦¾","ą¦ą¦ą¦¾ą¦","ą¦ą¦ą¦æ","ą¦ą¦¤","ą¦ą¦¤ą¦ą¦¾ą¦","ą¦ą¦¤ą§","ą¦ą¦¦ą§ą¦°","ą¦ą¦¬","ą¦ą¦¬ą¦","ą¦ą¦¬ą¦¾ą¦°","ą¦ą¦®ą¦Ø","ą¦ą¦®ą¦Øą¦ą§","ą¦ą¦®ą¦Øą¦æ","ą¦ą¦°","ą¦ą¦°ą¦¾","ą¦ą¦²","ą¦ą¦ø","ą¦ą¦øą§","ą¦","ą¦","ą¦ą¦ą¦¦ą§ą¦°","ą¦ą¦ą¦°","ą¦ą¦ą¦°ą¦¾","ą¦ą¦","ą¦ą¦ą§","ą¦ą¦ą¦¾ą¦Øą§","ą¦ą¦¦ą§ą¦°","ą¦ą¦°","ą¦ą¦°ą¦¾","ą¦ą¦ą¦Øą¦","ą¦ą¦¤","ą¦ą¦¬ą§","ą¦ą¦®ą¦Øą§","ą¦ą¦Æą¦¼ą§ą¦","ą¦ą¦Æą¦¼ą§ą¦ą¦ą¦æ","ą¦ą¦°ą¦ą§","ą¦ą¦°ą¦ą§ą¦Ø","ą¦ą¦°ą¦¤ą§","ą¦ą¦°ą¦¬ą§","ą¦ą¦°ą¦¬ą§ą¦Ø","ą¦ą¦°ą¦²ą§","ą¦ą¦°ą¦²ą§ą¦Ø","ą¦ą¦°ą¦¾","ą¦ą¦°ą¦¾ą¦","ą¦ą¦°ą¦¾ą¦Æą¦¼","ą¦ą¦°ą¦¾ą¦°","ą¦ą¦°ą¦æ","ą¦ą¦°ą¦æą¦¤ą§","ą¦ą¦°ą¦æą¦Æą¦¼ą¦¾","ą¦ą¦°ą¦æą¦Æą¦¼ą§","ą¦ą¦°ą§","ą¦ą¦°ą§ą¦","ą¦ą¦°ą§ą¦ą¦æą¦²ą§ą¦Ø","ą¦ą¦°ą§ą¦ą§","ą¦ą¦°ą§ą¦ą§ą¦Ø","ą¦ą¦°ą§ą¦Ø","ą¦ą¦¾ą¦ą¦ą§","ą¦ą¦¾ą¦","ą¦ą¦¾ą¦ą§","ą¦ą¦¾ą¦","ą¦ą¦¾ą¦ą§","ą¦ą¦¾ą¦°ą¦","ą¦ą¦¾ą¦°ą¦£","ą¦ą¦æ","ą¦ą¦æą¦ą¦¬ą¦¾","ą¦ą¦æą¦ą§","ą¦ą¦æą¦ą§ą¦","ą¦ą¦æą¦Øą§ą¦¤ą§","ą¦ą§","ą¦ą§","ą¦ą§ą¦","ą¦ą§ą¦ą¦","ą¦ą§ą¦ą¦¾","ą¦ą§ą¦Ø","ą¦ą§ą¦ą¦æ","ą¦ą§ą¦Ø","ą¦ą§ą¦Øą¦","ą¦ą§ą¦Øą§","ą¦ą§ą¦·ą§ą¦¤ą§ą¦°ą§","ą¦ą§ą§ą¦","ą¦ą§ą¦¬","ą¦ą¦æą¦Æą¦¼ą§","ą¦ą¦æą¦Æą¦¼ą§ą¦ą§","ą¦ą¦æą§ą§","ą¦ą§ą¦²ą¦æ","ą¦ą§ą¦ą§","ą¦ą§ą¦²","ą¦ą§ą¦²ą§","ą¦ą§ą¦ą¦¾","ą¦ą¦²ą§","ą¦ą¦¾ą¦Ø","ą¦ą¦¾ą¦Æą¦¼","ą¦ą¦¾ą¦°","ą¦ą¦¾ą¦²ą§","ą¦ą§ą¦Æą¦¼ą§","ą¦ą§ą¦·ą§ą¦ą¦¾","ą¦ą¦¾ą¦”ą¦¼ą¦¾","ą¦ą¦¾ą¦”ą¦¼ą¦¾ą¦","ą¦ą¦æą¦²","ą¦ą¦æą¦²ą§ą¦Ø","ą¦ą¦Ø","ą¦ą¦Øą¦ą§","ą¦ą¦Øą§ą¦°","ą¦ą¦Øą§ą¦Æ","ą¦ą¦Øą§ą¦Æą¦ą¦ą§","ą¦ą¦¾ą¦Øą¦¤ą§","ą¦ą¦¾ą¦Øą¦¾","ą¦ą¦¾ą¦Øą¦¾ą¦Øą§","ą¦ą¦¾ą¦Øą¦¾ą¦Æą¦¼","ą¦ą¦¾ą¦Øą¦æą¦Æą¦¼ą§","ą¦ą¦¾ą¦Øą¦æą¦Æą¦¼ą§ą¦ą§","ą¦ą§","ą¦ą§ą¦Øą¦ą¦Ø","ą¦ą¦æ","ą¦ ą¦æą¦","ą¦¤ą¦ą¦Ø","ą¦¤ą¦¤","ą¦¤ą¦„ą¦¾","ą¦¤ą¦¬ą§","ą¦¤ą¦¬ą§","ą¦¤ą¦¾","ą¦¤ą¦¾ą¦ą¦ą§","ą¦¤ą¦¾ą¦ą¦¦ą§ą¦°","ą¦¤ą¦¾ą¦ą¦°","ą¦¤ą¦¾ą¦ą¦°ą¦¾","ą¦¤ą¦¾ą¦ą¦¾ą¦¹ą¦¾ą¦°ą¦¾","ą¦¤ą¦¾ą¦","ą¦¤ą¦¾ą¦","ą¦¤ą¦¾ą¦ą§","ą¦¤ą¦¾ą¦¤ą§","ą¦¤ą¦¾ą¦¦ą§ą¦°","ą¦¤ą¦¾ą¦°","ą¦¤ą¦¾ą¦°ą¦Ŗą¦°","ą¦¤ą¦¾ą¦°ą¦¾","ą¦¤ą¦¾ą¦°ą§","ą¦¤ą¦¾ą¦¹ą¦²ą§","ą¦¤ą¦¾ą¦¹ą¦¾","ą¦¤ą¦¾ą¦¹ą¦¾ą¦¤ą§","ą¦¤ą¦¾ą¦¹ą¦¾ą¦°","ą¦¤ą¦æą¦Øą¦","ą¦¤ą¦æą¦Øą¦æ","ą¦¤ą¦æą¦Øą¦æą¦","ą¦¤ą§ą¦®ą¦æ","ą¦¤ą§ą¦²ą§","ą¦¤ą§ą¦®ą¦Ø","ą¦¤ą§","ą¦¤ą§ą¦®ą¦¾ą¦°","ą¦„ą¦¾ą¦ą¦¬ą§","ą¦„ą¦¾ą¦ą¦¬ą§ą¦Ø","ą¦„ą¦¾ą¦ą¦¾","ą¦„ą¦¾ą¦ą¦¾ą¦Æą¦¼","ą¦„ą¦¾ą¦ą§","ą¦„ą¦¾ą¦ą§ą¦Ø","ą¦„ą§ą¦ą§","ą¦„ą§ą¦ą§ą¦","ą¦„ą§ą¦ą§ą¦","ą¦¦ą¦æą¦ą§","ą¦¦ą¦æą¦¤ą§","ą¦¦ą¦æą¦Ø","ą¦¦ą¦æą¦Æą¦¼ą§","ą¦¦ą¦æą¦Æą¦¼ą§ą¦ą§","ą¦¦ą¦æą¦Æą¦¼ą§ą¦ą§ą¦Ø","ą¦¦ą¦æą¦²ą§ą¦Ø","ą¦¦ą§","ą¦¦ą§ą¦","ą¦¦ą§ą¦ą¦æ","ą¦¦ą§ą¦ą§","ą¦¦ą§ą¦ą¦Æą¦¼ą¦¾","ą¦¦ą§ą¦ą¦Æą¦¼ą¦¾ą¦°","ą¦¦ą§ą¦ą§ą¦¾","ą¦¦ą§ą¦ą¦¤ą§","ą¦¦ą§ą¦ą¦¾","ą¦¦ą§ą¦ą§","ą¦¦ą§ą¦Ø","ą¦¦ą§ą¦Æą¦¼","ą¦¦ą§ą¦¬ą¦¾ą¦°ą¦¾","ą¦§ą¦°ą¦¾","ą¦§ą¦°ą§","ą¦§ą¦¾ą¦®ą¦¾ą¦°","ą¦Øą¦¤ą§ą¦Ø","ą¦Øą¦Æą¦¼","ą¦Øą¦¾","ą¦Øą¦¾ą¦","ą¦Øą¦¾ą¦ą¦æ","ą¦Øą¦¾ą¦ą¦¾ą¦¦","ą¦Øą¦¾ą¦Øą¦¾","ą¦Øą¦æą¦ą§","ą¦Øą¦æą¦ą§ą¦","ą¦Øą¦æą¦ą§ą¦¦ą§ą¦°","ą¦Øą¦æą¦ą§ą¦°","ą¦Øą¦æą¦¤ą§","ą¦Øą¦æą¦Æą¦¼ą§","ą¦Øą¦æą§ą§","ą¦Øą§ą¦","ą¦Øą§ą¦ą¦Æą¦¼ą¦¾","ą¦Øą§ą¦ą¦Æą¦¼ą¦¾ą¦°","ą¦Øą§ą¦ą§ą¦¾","ą¦Øą§","ą¦Ŗą¦ą§ą¦·ą§","ą¦Ŗą¦°","ą¦Ŗą¦°ą§","ą¦Ŗą¦°ą§ą¦","ą¦Ŗą¦°ą§ą¦","ą¦Ŗą¦°ą§ą¦Æą¦Øą§ą¦¤","ą¦Ŗą¦¾ą¦ą¦Æą¦¼ą¦¾","ą¦Ŗą¦¾ą¦","ą¦Ŗą¦¾ą¦°ą¦æ","ą¦Ŗą¦¾ą¦°ą§","ą¦Ŗą¦¾ą¦°ą§ą¦Ø","ą¦Ŗą¦æ","ą¦Ŗą§ą¦Æą¦¼ą§","ą¦Ŗą§ą§ą§ą¦°ą§","ą¦Ŗą§ą¦°ą¦¤ą¦æ","ą¦Ŗą§ą¦°ą¦„ą¦®","ą¦Ŗą§ą¦°ą¦ą§ą¦¤ą¦æ","ą¦Ŗą§ą¦°ą¦Æą¦Øą§ą¦¤","ą¦Ŗą§ą¦°ą¦¾ą¦„ą¦®ą¦æą¦","ą¦Ŗą§ą¦°ą¦¾ą¦Æą¦¼","ą¦Ŗą§ą¦°ą¦¾ą§","ą¦«ą¦²ą§","ą¦«ą¦æą¦°ą§","ą¦«ą§ą¦°","ą¦¬ą¦ą§ą¦¤ą¦¬ą§ą¦Æ","ą¦¬ą¦¦ą¦²ą§","ą¦¬ą¦Ø","ą¦¬ą¦°ą¦","ą¦¬ą¦²ą¦¤ą§","ą¦¬ą¦²ą¦²","ą¦¬ą¦²ą¦²ą§ą¦Ø","ą¦¬ą¦²ą¦¾","ą¦¬ą¦²ą§","ą¦¬ą¦²ą§ą¦ą§ą¦Ø","ą¦¬ą¦²ą§ą¦Ø","ą¦¬ą¦øą§","ą¦¬ą¦¹ą§","ą¦¬ą¦¾","ą¦¬ą¦¾ą¦¦ą§","ą¦¬ą¦¾ą¦°","ą¦¬ą¦æ","ą¦¬ą¦æą¦Øą¦¾","ą¦¬ą¦æą¦ą¦æą¦Øą§ą¦Ø","ą¦¬ą¦æą¦¶ą§ą¦·","ą¦¬ą¦æą¦·ą¦Æą¦¼ą¦ą¦æ","ą¦¬ą§ą¦¶","ą¦¬ą§ą¦¶ą¦æ","ą¦¬ą§ą¦Æą¦¬ą¦¹ą¦¾ą¦°","ą¦¬ą§ą¦Æą¦¾ą¦Ŗą¦¾ą¦°ą§","ą¦ą¦¾ą¦¬ą§","ą¦ą¦¾ą¦¬ą§ą¦","ą¦®ą¦¤ą§","ą¦®ą¦¤ą§ą¦","ą¦®ą¦§ą§ą¦Æą¦ą¦¾ą¦ą§","ą¦®ą¦§ą§ą¦Æą§","ą¦®ą¦§ą§ą¦Æą§ą¦","ą¦®ą¦§ą§ą¦Æą§ą¦","ą¦®ą¦Øą§","ą¦®ą¦¾ą¦¤ą§ą¦°","ą¦®ą¦¾ą¦§ą§ą¦Æą¦®ą§","ą¦®ą§ą¦","ą¦®ą§ą¦ą§ą¦","ą¦Æą¦ą¦Ø","ą¦Æą¦¤","ą¦Æą¦¤ą¦ą¦¾","ą¦Æą¦„ą§ą¦·ą§ą¦","ą¦Æą¦¦ą¦æ","ą¦Æą¦¦ą¦æą¦","ą¦Æą¦¾","ą¦Æą¦¾ą¦ą¦°","ą¦Æą¦¾ą¦ą¦°ą¦¾","ą¦Æą¦¾ą¦ą¦Æą¦¼ą¦¾","ą¦Æą¦¾ą¦ą¦Æą¦¼ą¦¾ą¦°","ą¦Æą¦¾ą¦ą§ą¦¾","ą¦Æą¦¾ą¦ą§","ą¦Æą¦¾ą¦ą§ą¦ą§","ą¦Æą¦¾ą¦¤ą§","ą¦Æą¦¾ą¦¦ą§ą¦°","ą¦Æą¦¾ą¦Ø","ą¦Æą¦¾ą¦¬ą§","ą¦Æą¦¾ą¦Æą¦¼","ą¦Æą¦¾ą¦°","ą¦Æą¦¾ą¦°ą¦¾","ą¦Æą¦æą¦Øą¦æ","ą¦Æą§","ą¦Æą§ą¦ą¦¾ą¦Øą§","ą¦Æą§ą¦¤ą§","ą¦Æą§ą¦Ø","ą¦Æą§ą¦®ą¦Ø","ą¦°","ą¦°ą¦ą¦®","ą¦°ą¦Æą¦¼ą§ą¦ą§","ą¦°ą¦¾ą¦ą¦¾","ą¦°ą§ą¦ą§","ą¦²ą¦ą§ą¦·","ą¦¶ą§ą¦§ą§","ą¦¶ą§ą¦°ą§","ą¦øą¦ą§ą¦ą§","ą¦øą¦ą§ą¦ą§ą¦","ą¦øą¦¬","ą¦øą¦¬ą¦¾ą¦°","ą¦øą¦®ą¦øą§ą¦¤","ą¦øą¦®ą§ą¦Ŗą§ą¦°ą¦¤ą¦æ","ą¦øą¦¹","ą¦øą¦¹ą¦æą¦¤","ą¦øą¦¾ą¦§ą¦¾ą¦°ą¦£","ą¦øą¦¾ą¦®ą¦Øą§","ą¦øą¦æ","ą¦øą§ą¦¤ą¦°ą¦¾ą¦","ą¦øą§","ą¦øą§ą¦","ą¦øą§ą¦ą¦¾ą¦Ø","ą¦øą§ą¦ą¦¾ą¦Øą§","ą¦øą§ą¦ą¦¾","ą¦øą§ą¦ą¦¾ą¦","ą¦øą§ą¦ą¦¾ą¦","ą¦øą§ą¦ą¦æ","ą¦øą§ą¦Ŗą¦·ą§ą¦","ą¦øą§ą¦¬ą¦Æą¦¼ą¦","ą¦¹ą¦ą¦¤ą§","ą¦¹ą¦ą¦¬ą§","ą¦¹ą¦ą¦Æą¦¼ą¦¾","ą¦¹ą¦ą¦Æą¦¼ą¦¾","ą¦¹ą¦ą¦Æą¦¼ą¦¾ą¦Æą¦¼","ą¦¹ą¦ą¦Æą¦¼ą¦¾ą¦°","ą¦¹ą¦ą§ą¦ą§","ą¦¹ą¦¤","ą¦¹ą¦¤ą§","ą¦¹ą¦¤ą§ą¦","ą¦¹ą¦Ø","ą¦¹ą¦¬ą§","ą¦¹ą¦¬ą§ą¦Ø","ą¦¹ą¦Æą¦¼","ą¦¹ą¦Æą¦¼ą¦¤ą§","ą¦¹ą¦Æą¦¼ą¦Øą¦æ","ą¦¹ą¦Æą¦¼ą§","ą¦¹ą¦Æą¦¼ą§ą¦","ą¦¹ą¦Æą¦¼ą§ą¦ą¦æą¦²","ą¦¹ą¦Æą¦¼ą§ą¦ą§","ą¦¹ą¦Æą¦¼ą§ą¦ą§ą¦Ø","ą¦¹ą¦²","ą¦¹ą¦²ą§","ą¦¹ą¦²ą§ą¦","ą¦¹ą¦²ą§ą¦","ą¦¹ą¦²ą§","ą¦¹ą¦¾ą¦ą¦¾ą¦°","ą¦¹ą¦æą¦øą¦¾ą¦¬ą§","ą¦¹ą§ą¦²ą§","ą¦¹ą§ą¦","ą¦¹ą§"],"br":["'blam","'d","'m","'r","'ta","'vat","'z","'zo","a","a:","aba","abalamour","abaoe","ac'hane","ac'hanoc'h","ac'hanomp","ac'hanon","ac'hanout","adal","adalek","adarre","ae","aec'h","aed","aemp","aen","aent","aes","afe","afec'h","afed","afemp","afen","afent","afes","ag","ah","aimp","aint","aio","aiou","aje","ajec'h","ajed","ajemp","ajen","ajent","ajes","al","alato","alies","aliesaƱ","alkent","all","allas","allo","allĆ“","am","amaƱ","amzer","an","anezhaƱ","anezhe","anezhi","anezho","anvet","aon","aotren","ar","arall","araok","araoki","araozaƱ","araozo","araozoc'h","araozomp","araozon","araozor","araozout","arbenn","arre","atalek","atav","az","azalek","azirazaƱ","azirazi","azirazo","azirazoc'h","azirazomp","azirazon","azirazor","azirazout","b:","ba","ba'l","ba'n","ba'r","bad","bah","bal","ban","bar","bastaƱ","befe","bell","benaos","benn","bennag","bennak","bennozh","bep","bepred","berr","berzh","bet","betek","betra","bev","bevet","bez","bezaƱ","beze","bezent","bezet","bezh","bezit","bezomp","bihan","bije","biou","biskoazh","blam","bo","boa","bominapl","boudoudom","bouez","boull","boum","bout","bras","brasaƱ","brav","bravo","bremaƱ","bres","brokenn","bronn","brrr","brutal","buhezek","c'h:","c'haout","c'he","c'hem","c'herz","c'heƱver","c'hichen","c'hiz","c'hoazh","c'horre","c'houde","c'houst","c'hreiz","c'hwec'h","c'hwec'hvet","c'hwezek","c'hwi","ch:","chaous","chik","chit","chom","chut","d'","d'al","d'an","d'ar","d'az","d'e","d'he","d'ho","d'hol","d'hon","d'hor","d'o","d'ober","d'ul","d'un","d'ur","d:","da","dak","daka","dal","dalbezh","dalc'hmat","dalit","damdost","damheƱvel","damm","dan","danvez","dao","daol","daonet","daou","daoust","daouzek","daouzekvet","darn","dastrewiƱ","dav","davedoc'h","davedomp","davedon","davedor","davedout","davet","davetaƱ","davete","daveti","daveto","defe","dehou","dek","dekvet","den","deoc'h","deomp","deor","derc'hel","deus","dez","deze","dezhaƱ","dezhe","dezhi","dezho","di","diabarzh","diagent","diar","diaraok","diavaez","dibaoe","dibaot","dibar","dic'halaƱ","didiac'h","dienn","difer","diganeoc'h","diganeomp","diganeor","diganimp","diganin","diganit","digant","digantaƱ","digante","diganti","diganto","digemmesk","diget","digor","digoret","dija","dije","dimp","din","dinaou","dindan","dindanaƱ","dindani","dindano","dindanoc'h","dindanomp","dindanon","dindanor","dindanout","dioutaƱ","dioute","diouti","diouto","diouzh","diouzhin","diouzhit","diouzhoc'h","diouzhomp","diouzhor","dirak","dirazaƱ","dirazi","dirazo","dirazoc'h","dirazomp","dirazon","dirazor","dirazout","disheƱvel","dispar","distank","dister","disteraƱ","disterig","distro","dit","divaez","diwar","diwezhat","diwezhaƱ","do","doa","doare","dont","dost","doue","douetus","douez","doug","draou","draoƱ","dre","drede","dreist","dreistaƱ","dreisti","dreisto","dreistoc'h","dreistomp","dreiston","dreistor","dreistout","drek","dreƱv","dring","dro","du","e","e:","eas","ebet","ec'h","edo","edoc'h","edod","edomp","edon","edont","edos","eer","eeun","efed","egedoc'h","egedomp","egedon","egedor","egedout","eget","egetaƱ","egete","egeti","egeto","eh","eil","eilvet","eizh","eizhvet","ejoc'h","ejod","ejomp","ejont","ejout","el","em","emaint","emaoc'h","emaomp","emaon","emaout","emaƱ","eme","emeur","emezaƱ","emezi","emezo","emezoc'h","emezomp","emezon","emezout","emporzhiaƱ","en","end","endan","endra","enep","ennaƱ","enni","enno","ennoc'h","ennomp","ennon","ennor","ennout","enta","eo","eomp","eont","eor","eot","er","erbet","erfin","esa","esae","espar","estlamm","estraƱj","eta","etre","etreoc'h","etrezo","etrezoc'h","etrezomp","etrezor","euh","eur","eus","evel","evelato","eveldoc'h","eveldomp","eveldon","eveldor","eveldout","evelkent","eveltaƱ","evelte","evelti","evelto","evidoc'h","evidomp","evidon","evidor","evidout","evit","evitaƱ","evite","eviti","evito","ez","eƱ","f:","fac'h","fall","fed","feiz","fenn","fezh","fin","finsalvet","foei","fouilhezaƱ","g:","gallout","ganeoc'h","ganeomp","ganin","ganit","gant","gantaƱ","ganti","ganto","gaout","gast","gein","gellout","genndost","gentaƱ","ger","gerz","get","geƱver","gichen","gin","giz","glan","gloev","goll","gorre","goude","gouez","gouezit","gouezomp","goulz","gounnar","gour","goust","gouze","gouzout","gra","grak","grec'h","greiz","grenn","greomp","grit","groƱs","gutez","gwall","gwashoc'h","gwazh","gwech","gwechall","gwechoĆ¹","gwell","gwezh","gwezhall","gwezharall","gwezhoĆ¹","gwig","gwirionez","gwitibunan","gĆŖr","h:","ha","hag","han","hanter","hanterc'hantad","hanterkantved","harz","haƱ","haƱval","he","hebioĆ¹","hec'h","hei","hein","hem","hemaƱ","hen","hend","henhont","henn","hennezh","hent","hep","hervez","hervezaƱ","hervezi","hervezo","hervezoc'h","hervezomp","hervezon","hervezor","hervezout","heul","heuliaƱ","hevelep","heverk","heƱvel","heƱvelat","heƱvelaƱ","heƱveliƱ","heƱveloc'h","heƱvelout","hi","hilh","hini","hirie","hirio","hiziv","hiziviken","ho","hoaliƱ","hoc'h","hogen","hogos","hogozik","hol","holl","holĆ ","homaƱ","hon","honhont","honnezh","hont","hop","hopala","hor","hou","houp","hudu","hue","hui","hum","hurrah","i","i:","in","int","is","ispisial","isurzhiet","it","ivez","izelaƱ","j:","just","k:","kae","kaer","kalon","kalz","kant","kaout","kar","kazi","keid","kein","keit","kel","kellies","keloĆ¹","kement","ken","kenkent","kenkoulz","kenment","kent","kentaƱ","kentizh","kentoc'h","kentre","ker","kerkent","kerz","kerzh","ket","keta","keƱver","keƱverel","keƱverius","kichen","kichenik","kit","kiz","klak","klek","klik","komprenet","komz","kont","korf","korre","koulskoude","koulz","koust","krak","krampouezh","krec'h","kreiz","kuit","kwir","l:","la","laez","laoskel","laouen","lavar","lavaret","lavarout","lec'h","lein","leizh","lerc'h","leun","leuskel","lew","lies","liesaƱ","lod","lusk","lĆ¢r","lĆ¢rout","m:","ma","ma'z","mac'h","mac'hat","mac'haƱ","mac'hoc'h","mad","maez","maksimal","mann","mar","mard","marg","marzh","mat","maƱ","me","memes","memestra","merkapl","mersi","mes","mesk","met","meur","mil","minimal","moan","moaniaat","mod","mont","mout","mui","muiaƱ","muioc'h","n","n'","n:","na","nag","naontek","naturel","nav","navet","ne","nebeudig","nebeut","nebeutaƱ","nebeutoc'h","neketa","nemedoc'h","nemedomp","nemedon","nemedor","nemedout","nemet","nemetaƱ","nemete","nemeti","nemeto","nemeur","neoac'h","nepell","nerzh","nes","neseser","netra","neubeudoĆ¹","neuhe","neuze","nevez","newazh","nez","ni","nikun","niverus","nul","o","o:","oa","oac'h","oad","oamp","oan","oant","oar","oas","ober","oc'h","oc'ho","oc'hola","oc'hpenn","oh","ohe","ollĆ©","olole","olĆ©","omp","on","ordin","ordinal","ouejoc'h","ouejod","ouejomp","ouejont","ouejout","ouek","ouezas","ouezi","ouezimp","ouezin","ouezint","ouezis","ouezo","ouezoc'h","ouezor","ouf","oufe","oufec'h","oufed","oufemp","oufen","oufent","oufes","ouie","ouiec'h","ouied","ouiemp","ouien","ouient","ouies","ouije","ouijec'h","ouijed","ouijemp","ouijen","ouijent","ouijes","out","outaƱ","outi","outo","ouzer","ouzh","ouzhin","ouzhit","ouzhoc'h","ouzhomp","ouzhor","ouzhpenn","ouzhpennik","ouzoc'h","ouzomp","ouzon","ouzont","ouzout","p'","p:","pa","pad","padal","paf","pan","panevedeoc'h","panevedo","panevedomp","panevedon","panevedout","panevet","panevetaƱ","paneveti","pas","paseet","pe","peadra","peder","pedervet","pedervetvet","pefe","pegeit","pegement","pegen","pegiz","pegoulz","pehini","pelec'h","pell","pemod","pemp","pempved","pemzek","penaos","penn","peogwir","peotramant","pep","perak","perc'hennaƱ","pergen","permetiƱ","peseurt","pet","petiaoul","petoare","petra","peur","peurgetket","peurheƱvel","peurliesaƱ","peurvuiaƱ","peus","peustost","peuz","pevar","pevare","pevarevet","pevarzek","pez","peze","pezh","pff","pfft","pfut","picher","pif","pife","pign","pije","pikol","pitiaoul","piv","plaouf","plok","plouf","po","poa","poelladus","pof","pok","posupl","pouah","pourc'henn","prest","prestik","prim","prin","provostapl","pst","pu","pur","r:","ra","rae","raec'h","raed","raemp","raen","raent","raes","rafe","rafec'h","rafed","rafemp","rafen","rafent","rafes","rag","raimp","raint","raio","raje","rajec'h","rajed","rajemp","rajen","rajent","rajes","rak","ral","ran","rankout","raok","razh","re","reas","reer","regennoĆ¹","reiƱ","rejoc'h","rejod","rejomp","rejont","rejout","rener","rentaƱ","reoc'h","reomp","reont","reor","reot","resis","ret","reve","rez","ri","rik","rin","ris","rit","rouez","s:","sac'h","sant","sav","saƱset","se","sed","seitek","seizh","seizhvet","sell","sellit","ser","setu","seul","seurt","siwazh","skignaƱ","skoaz","skouer","sort","souden","souvitaƱ","soƱj","speriaƱ","spririƱ","stad","stlabezaƱ","stop","stranaƱ","strewiƱ","strishaat","stumm","sujed","surtoud","t:","ta","taer","tailh","tak","tal","talvoudegezh","tamm","tanav","taol","te","techet","teir","teirvet","telt","teltenn","teus","teut","teuteu","ti","tik","toa","tok","tost","tostig","toud","touesk","touez","toull","tra","trantenn","traoƱ","trawalc'h","tre","trede","tregont","tremenet","tri","trivet","triwec'h","trizek","tro","trugarez","trumm","tsoin","tsouin","tu","tud","u:","ugent","uhel","uhelaƱ","ul","un","unan","unanez","unanig","unnek","unnekvet","ur","urzh","us","v:","va","vale","van","vare","vat","vefe","vefec'h","vefed","vefemp","vefen","vefent","vefes","vesk","vete","vez","vezan","vezaƱ","veze","vezec'h","vezed","vezemp","vezen","vezent","vezer","vezes","vezez","vezit","vezomp","vezont","vi","vihan","vihanaƱ","vije","vijec'h","vijed","vijemp","vijen","vijent","vijes","viken","vimp","vin","vint","vior","viot","virviken","viskoazh","vlan","vlaou","vo","vod","voe","voec'h","voed","voemp","voen","voent","voes","vont","vostapl","vrac'h","vrasaƱ","vremaƱ","w:","walc'h","war","warnaƱ","warni","warno","warnoc'h","warnomp","warnon","warnor","warnout","wazh","wech","wechoĆ¹","well","y:","you","youadenn","youc'hadenn","youc'hou","z:","za","zan","zaw","zeu","zi","ziar","zigarez","ziget","zindan","zioc'h","ziouzh","zirak","zivout","ziwar","ziwezhaƱ","zo","zoken","zokenoc'h","zouesk","zouez","zro","zu"],"bg":["Š°","Š°Š²ŃŠµŠ½ŃŠøŃŠµŠ½","Š°Š·","Š°ŠŗŠ¾","Š°Š»Š°","Š±Šµ","Š±ŠµŠ·","Š±ŠµŃŠµ","Š±Šø","Š±ŠøŠ²Ń","Š±ŠøŠ²ŃŠ°","Š±ŠøŠ²ŃŠ¾","Š±ŠøŠ»","Š±ŠøŠ»Š°","Š±ŠøŠ»Šø","Š±ŠøŠ»Š¾","Š±Š»Š°Š³Š¾Š“Š°ŃŃ","Š±Š»ŠøŠ·Š¾","Š±ŃŠ“Š°Ń","Š±ŃŠ“Šµ","Š±ŃŃ
Š°","Š²","Š²Š°Ń","Š²Š°Ń","Š²Š°ŃŠ°","Š²ŠµŃŠ¾ŃŃŠ½Š¾","Š²ŠµŃŠµ","Š²Š·ŠµŠ¼Š°","Š²Šø","Š²ŠøŠµ","Š²ŠøŠ½Š°Š³Šø","Š²Š½ŠøŠ¼Š°Š²Š°","Š²ŃŠµŠ¼Šµ","Š²ŃŠµ","Š²ŃŠµŠŗŠø","Š²ŃŠøŃŠŗŠø","Š²ŃŠøŃŠŗŠ¾","Š²ŃŃŠŗŠ°","Š²ŃŠ²","Š²ŃŠæŃŠµŠŗŠø","Š²ŃŃŃ
Ń","Š³","Š³Šø","Š³Š»Š°Š²ŠµŠ½","Š³Š»Š°Š²Š½Š°","Š³Š»Š°Š²Š½Š¾","Š³Š»Š°Ń","Š³Š¾","Š³Š¾Š“ŠøŠ½Š°","Š³Š¾Š“ŠøŠ½Šø","Š³Š¾Š“ŠøŃŠµŠ½","Š“","Š“Š°","Š“Š°Š»Šø","Š“Š²Š°","Š“Š²Š°Š¼Š°","Š“Š²Š°Š¼Š°ŃŠ°","Š“Š²Šµ","Š“Š²ŠµŃŠµ","Š“ŠµŠ½","Š“Š½ŠµŃ","Š“Š½Šø","Š“Š¾","Š“Š¾Š±ŃŠ°","Š“Š¾Š±ŃŠµ","Š“Š¾Š±ŃŠ¾","Š“Š¾Š±ŃŃ","Š“Š¾ŠŗŠ°ŃŠ¾","Š“Š¾ŠŗŠ¾Š³Š°","Š“Š¾ŃŠø","Š“Š¾ŃŠµŠ³Š°","Š“Š¾ŃŃŠ°","Š“ŃŃŠ³","Š“ŃŃŠ³Š°","Š“ŃŃŠ³Šø","Šµ","ŠµŠ²ŃŠøŠ½","ŠµŠ“Š²Š°","ŠµŠ“ŠøŠ½","ŠµŠ“Š½Š°","ŠµŠ“Š½Š°ŠŗŠ²Š°","ŠµŠ“Š½Š°ŠŗŠ²Šø","ŠµŠ“Š½Š°ŠŗŃŠ²","ŠµŠ“Š½Š¾","ŠµŠŗŠøŠæ","ŠµŃŠ¾","Š¶ŠøŠ²Š¾Ń","Š·Š°","Š·Š°Š±Š°Š²ŃŠ¼","Š·Š°Š“","Š·Š°ŠµŠ“Š½Š¾","Š·Š°ŃŠ°Š“Šø","Š·Š°ŃŠµŠ³Š°","Š·Š°ŃŠæŠ°Š»","Š·Š°ŃŠ¾Š²Š°","Š·Š°ŃŠ¾","Š·Š°ŃŠ¾ŃŠ¾","Šø","ŠøŠ·","ŠøŠ»Šø","ŠøŠ¼","ŠøŠ¼Š°","ŠøŠ¼Š°Ń","ŠøŃŠŗŠ°","Š¹","ŠŗŠ°Š·Š°","ŠŗŠ°Šŗ","ŠŗŠ°ŠŗŠ²Š°","ŠŗŠ°ŠŗŠ²Š¾","ŠŗŠ°ŠŗŃŠ¾","ŠŗŠ°ŠŗŃŠ²","ŠŗŠ°ŃŠ¾","ŠŗŠ¾Š³Š°","ŠŗŠ¾Š³Š°ŃŠ¾","ŠŗŠ¾ŠµŃŠ¾","ŠŗŠ¾ŠøŃŠ¾","ŠŗŠ¾Š¹","ŠŗŠ¾Š¹ŃŠ¾","ŠŗŠ¾Š»ŠŗŠ¾","ŠŗŠ¾ŃŃŠ¾","ŠŗŃŠ“Šµ","ŠŗŃŠ“ŠµŃŠ¾","ŠŗŃŠ¼","Š»ŠµŃŠµŠ½","Š»ŠµŃŠ½Š¾","Š»Šø","Š»Š¾Ń","Š¼","Š¼Š°Š¹","Š¼Š°Š»ŠŗŠ¾","Š¼Šµ","Š¼ŠµŠ¶Š“Ń","Š¼ŠµŠŗ","Š¼ŠµŠ½","Š¼ŠµŃŠµŃ","Š¼Šø","Š¼Š½Š¾Š³Š¾","Š¼Š½Š¾Š·ŠøŠ½Š°","Š¼Š¾Š³Š°","Š¼Š¾Š³Š°Ń","Š¼Š¾Š¶Šµ","Š¼Š¾ŠŗŃŃ","Š¼Š¾Š»Ń","Š¼Š¾Š¼ŠµŠ½ŃŠ°","Š¼Ń","Š½","Š½Š°","Š½Š°Š“","Š½Š°Š·Š°Š“","Š½Š°Š¹","Š½Š°ŠæŃŠ°Š²Šø","Š½Š°ŠæŃŠµŠ“","Š½Š°ŠæŃŠøŠ¼ŠµŃ","Š½Š°Ń","Š½Šµ","Š½ŠµŠ³Š¾","Š½ŠµŃŠ¾","Š½ŠµŃ","Š½Šø","Š½ŠøŠµ","Š½ŠøŠŗŠ¾Š¹","Š½ŠøŃŠ¾","Š½ŠøŃŠ¾","Š½Š¾","Š½Š¾Š²","Š½Š¾Š²Š°","Š½Š¾Š²Šø","Š½Š¾Š²ŠøŠ½Š°","Š½ŃŠŗŠ¾Šø","Š½ŃŠŗŠ¾Š¹","Š½ŃŠŗŠ¾Š»ŠŗŠ¾","Š½ŃŠ¼Š°","Š¾Š±Š°ŃŠµ","Š¾ŠŗŠ¾Š»Š¾","Š¾ŃŠ²ŠµŠ½","Š¾ŃŠ¾Š±ŠµŠ½Š¾","Š¾Ń","Š¾ŃŠ³Š¾ŃŠµ","Š¾ŃŠ½Š¾Š²Š¾","Š¾ŃŠµ","ŠæŠ°Šŗ","ŠæŠ¾","ŠæŠ¾Š²ŠµŃŠµ","ŠæŠ¾Š²ŠµŃŠµŃŠ¾","ŠæŠ¾Š“","ŠæŠ¾Š½Šµ","ŠæŠ¾ŃŠ°Š“Šø","ŠæŠ¾ŃŠ»Šµ","ŠæŠ¾ŃŃŠø","ŠæŃŠ°Š²Šø","ŠæŃŠµŠ“","ŠæŃŠµŠ“Šø","ŠæŃŠµŠ·","ŠæŃŠø","ŠæŃŠŗ","ŠæŃŃŠ²Š°ŃŠ°","ŠæŃŃŠ²Šø","ŠæŃŃŠ²Š¾","ŠæŃŃŠø","ŃŠ°Š²ŠµŠ½","ŃŠ°Š²Š½Š°","Ń","ŃŠ°","ŃŠ°Š¼","ŃŠ°Š¼Š¾","ŃŠµ","ŃŠµŠ³Š°","ŃŠø","ŃŠøŠ½","ŃŠŗŠ¾ŃŠ¾","ŃŠ»ŠµŠ“","ŃŠ»ŠµŠ“Š²Š°Ń","ŃŠ¼Šµ","ŃŠ¼ŃŃ
","ŃŠæŠ¾ŃŠµŠ“","ŃŃŠµŠ“","ŃŃŠµŃŃ","ŃŃŠµ","ŃŃŠ¼","ŃŃŃ","ŃŃŃŠ¾","Ń","Ń.Š½.","ŃŠ°Š·Šø","ŃŠ°ŠŗŠ°","ŃŠ°ŠŗŠøŠ²Š°","ŃŠ°ŠŗŃŠ²","ŃŠ°Š¼","ŃŠ²Š¾Š¹","ŃŠµ","ŃŠµŠ·Šø","ŃŠø","ŃŠ¾","ŃŠ¾Š²Š°","ŃŠ¾Š³Š°Š²Š°","ŃŠ¾Š·Šø","ŃŠ¾Š¹","ŃŠ¾Š»ŠŗŠ¾Š²Š°","ŃŠ¾ŃŠ½Š¾","ŃŃŠø","ŃŃŃŠ±Š²Š°","ŃŃŠŗ","ŃŃŠ¹","ŃŃ","ŃŃŃ
","Ń","ŃŃŃŠµ","Ń
Š°ŃŠµŃŠ²Š°","Ń
ŠøŠ»ŃŠ“Šø","Ń","ŃŠ°ŃŠ°","ŃŠµ","ŃŠµŃŃŠ¾","ŃŃŠµŠ·","ŃŠµ","ŃŠ¾Š¼","ŃŠ¼ŃŃŠŗ","Ń","ŃŠŗ"],"ca":["a","abans","acĆ","ah","aixĆ","aixĆ²","al","aleshores","algun","alguna","algunes","alguns","alhora","allĆ ","allĆ","allĆ²","als","altra","altre","altres","amb","ambdues","ambdĆ³s","anar","ans","apa","aquell","aquella","aquelles","aquells","aquest","aquesta","aquestes","aquests","aquĆ","baix","bastant","bĆ©","cada","cadascuna","cadascunes","cadascuns","cadascĆŗ","com","consegueixo","conseguim","conseguir","consigueix","consigueixen","consigueixes","contra","d'un","d'una","d'unes","d'uns","dalt","de","del","dels","des","des de","desprĆ©s","dins","dintre","donat","doncs","durant","e","eh","el","elles","ells","els","em","en","encara","ens","entre","era","erem","eren","eres","es","esta","estan","estat","estava","estaven","estem","esteu","estic","estĆ ","estĆ vem","estĆ veu","et","etc","ets","fa","faig","fan","fas","fem","fer","feu","fi","fins","fora","gairebĆ©","ha","han","has","haver","havia","he","hem","heu","hi","ho","i","igual","iguals","inclĆ²s","ja","jo","l'hi","la","les","li","li'n","llarg","llavors","m'he","ma","mal","malgrat","mateix","mateixa","mateixes","mateixos","me","mentre","meu","meus","meva","meves","mode","molt","molta","moltes","molts","mon","mons","mĆ©s","n'he","n'hi","ne","ni","no","nogensmenys","nomĆ©s","nosaltres","nostra","nostre","nostres","o","oh","oi","on","pas","pel","pels","per","per que","perquĆØ","perĆ²","poc","poca","pocs","podem","poden","poder","podeu","poques","potser","primer","propi","puc","qual","quals","quan","quant","que","quelcom","qui","quin","quina","quines","quins","quĆØ","s'ha","s'han","sa","sabem","saben","saber","sabeu","sap","saps","semblant","semblants","sense","ser","ses","seu","seus","seva","seves","si","sobre","sobretot","soc","solament","sols","som","son","sons","sota","sou","sĆ³c","sĆ³n","t'ha","t'han","t'he","ta","tal","tambĆ©","tampoc","tan","tant","tanta","tantes","te","tene","tenim","tenir","teniu","teu","teus","teva","teves","tinc","ton","tons","tot","tota","totes","tots","un","una","unes","uns","us","va","vaig","vam","van","vas","veu","vosaltres","vostra","vostre","vostres","Ć©rem","Ć©reu","Ć©s","Ć©ssent","Ćŗltim","Ćŗs"],"zh":["ć","ć","ć","ć","ć","ć","äø","äøäøŖ","äøäŗ","äøä½","äøå","äøå","äøę¹é¢","äøę¦","äøę„","äøę ·","äøē§","äøč¬","äøč½¬ē¼","äø","äøäø","äø","äø","äøäø","äø","äø","äøä»
","äøä½","äøå
","äøå","äøåŖ","äøå¤ä¹","äøå¦","äøå¦Ø","äøå°½","äøå°½ē¶","äøå¾","äøę","äøę","äøę","äøę","äøę","äøęÆ","äøęÆ","äøē¶","äøē¹","äøē¬","äøē®”","äøč³äŗ","äøč„","äøč®ŗ","äøčæ","äøé®","äø","äøå
¶","äøå
¶čÆ“","äøå¦","äøę¤åę¶","äø","äøäøčÆ“","äøčÆ“","äø¤č
","äøŖ","äøŖå«","äø","äø“","äøŗ","äøŗäŗ","äøŗä»ä¹","äøŗä½","äøŗę¢","äøŗę¤","äøŗē","ä¹","ä¹č³","ä¹č³äŗ","ä¹","ä¹","ä¹äø","ä¹ę仄","ä¹ē±»","ä¹ä¹","ä¹","ä¹","ä¹","ä¹","ä¹å„½","ä¹ē½¢","äŗ","äŗ","äŗę„","äŗ","äŗęÆ","äŗęÆä¹","äŗäŗ","äŗå°","äŗ","äŗ","äŗ¦","äŗŗ","äŗŗ们","äŗŗ家","ä»","ä»ä¹","ä»ä¹ę ·","ä»","ä»äŗ","ä»","ä»ę§","ä»","ä»ę¤","ä»č","ä»","ä»äŗŗ","ä»ä»¬","ä»ä»¬ä»¬","仄","仄äø","仄äøŗ","仄ä¾æ","仄å
","仄å","仄ę
","仄ę","仄ę„","仄č³","仄č³äŗ","仄č“","们","ä»»","ä»»ä½","ä»»å","ä¼","ä¼¼ē","ä½","ä½å”","ä½ęÆ","ä½","ä½ä»„","ä½åµ","ä½å¤","ä½ę¶","ä½å¤","ä½äøŗ","ä½ ","ä½ ä»¬","ä½æ","ä½æå¾","ä¾å¦","ä¾","ä¾ę®","ä¾ē
§","ä¾æäŗ","äæŗ","äæŗ们","å","åä½æ","åę","åē¶","åč„","å","åå„ē¶","åä½æ","åå¦","åč„","å","å","åæ","å
äøå
","å
","å
ęÆ","å
Øä½","å
ØéØ","å
«","å
","å
®","å
±","å
³äŗ","å
³äŗå
·ä½å°čÆ“","å
¶","å
¶äø","å
¶äø","å
¶äŗ","å
¶ä»","å
¶ä½","å
¶å®","å
¶ę¬”","å
·ä½å°čÆ“","å
·ä½čÆ“ę„","å
¼ä¹","å
","å","åå
¶ę¬”","åå","åę","åč
","åč
čÆ“","åčÆ“","å","å²","åµäø","å ","å ę¶","å”","å”ęÆ","å","åå","åŗäŗ","åŗę„","å","åå«","å","åē","å«","å«äŗŗ","å«å¤","å«ęÆ","å«ē","å«ē®”","å«čÆ“","å°","åå","åę¤","åč
","å ä¹","å 仄","åŗ","å³","å³ä»¤","å³ä½æ","å³ä¾æ","å³å¦","å³ę","å³č„","å“","å»","å","åå","å","åå
¶","åč³","åä¹","åč","åčæę„","åčæę„čÆ“","åå°","å¦","å¦äøę¹é¢","å¦å¤","å¦ę","åŖ","åŖå½","åŖę","åŖęÆ","åŖę","åŖę¶","åŖč¦","åŖé","å«","å®å","åÆ","åÆ仄","åÆęÆ","åÆč§","å","åäøŖ","åä½","åē§","åčŖ","å","åę¶","å","åč
","å","åä½æ","åē","å","å","å¦å","å§","å§å","å«","å±","å","å","å","å","å","åå¼","å¢","åµ","åµåµ","åø","å¼å§","å","å","å","å¦","å§","å±","å±ä»¬","å³","å","å","åå","å","å","åå","åå","å","å","å¦","å©","åŖ","åŖäøŖ","åŖäŗ","åŖåæ","åŖ天","åŖ幓","åŖę","åŖę ·","åŖč¾¹","åŖé","å¼","å¼å·","å","åÆę","å","å","å„","å¦","åŖč¾¾","å·å½","å","å","åå·","å½","å”","å”å”","å¬","åÆ","å³","å","åē»","å","å","å»","åæ","åæåæ","å","å ","å äøŗ","å äŗ","å ę¤","å ē","å č","åŗē¶","åØ","åØäø","åØäŗ","å°","åŗäŗ","å¤åØ","å¤","å¤ä¹","å¤å°","大","大家","儹","儹们","儽","å¦","å¦äø","å¦äøęčæ°","å¦äø","å¦ä½","å¦å
¶","å¦å","å¦ęÆ","å¦ę","å¦ę¤","å¦č„","å§č","å°ę","å°ē„","å®","å®åÆ","å®ęæ","å®čÆ","å®","å®ä»¬","åƹ","åƹäŗ","åƹå¾
","åƹę¹","åƹęÆ","å°","å°","å°","å°å","å°å°","å°äø","å°±","å°±ęÆ","å°±ęÆäŗ","å°±ęÆčÆ“","å°±ē®","å°±č¦","å°½","å°½ē®”","å°½ē®”å¦ę¤","å²ä½","å·±","å·²","å·²ē£","å·“","å·“å·“","幓","并","并äø","åŗ¶ä¹","åŗ¶å ","å¼å¤","å¼å§","å½","å½é½","å½","å½å°","å½ē¶","å½ē","å½¼","å½¼ę¶","å½¼ę¤","å¾","å¾
","å¾","å¾","å¾äŗ","ę","ęä¹","ęä¹å","ęä¹ę ·","ęå„","ęę ·","ę»ä¹","ę»ēę„ē","ę»ēę„čÆ“","ę»ēčÆ“ę„","ę»ččØä¹","ę°ę°ēøå","ęØ","ęå
¶","ę
¢čÆ“","ę","ę们","ę","ęå","ęęÆ","ęę°","ęč
","ęŖč³","ę","ę仄","ęåØ","ęå¹ø","ęę","ę","ęč½","ę","ęä»","ę","ęę","ęæ","ę","ęē
§","ę¢å„čÆčÆ“","ę¢čØä¹","ę®","ę®ę¤","ę„ē","ę
","ę
ę¤","ę
č","ęäŗŗ","ę ","ę å®","ę č®ŗ","ę¢","ę¢å¾","ę¢ęÆ","ę¢ē¶","ę„","ę¶","ę¶å","ęÆ","ęÆ仄","ęÆē","ę“","ę¾","ęæ","ęæ代","ę","ę","ę","ęäŗ","ęå
³","ęå","ęę¶","ęē","ę","ę","ęē","ę¬","ę¬äŗŗ","ę¬å°","ę¬ē","ę¬čŗ«","ę„","ę„ē","ę„čŖ","ę„čÆ“","ęäŗ","ęē¶","ęē","ę","ęäøŖ","ęäŗ","ęę","ę ¹ę®","ꬤ","ę£å¼","ę£å¦","ę£å·§","ę£ęÆ","ę¤","ę¤å°","ę¤å¤","ę¤å¤","ę¤ę¶","ę¤ę¬”","ę¤é“","ęÆå®","ęÆ","ęÆå½","ęÆ","ęÆå","ęÆå¦","ęÆę¹","ę²”å„ä½","ę²æ","ę²æē","ę¼«čÆ“","ē¹","ē","ē¶å","ē¶å","ē¶č","ē
§","ē
§ē","ē¹äø","ē¹čŖ","ēäø","ēä¹","ēę","ēč","ēč³","ēč³äŗ","ēØ","ēØę„","ē±","ē±äŗ","ē±ęÆ","ē±ę¤","ē±ę¤åÆč§","ē","ēē”®","ēčÆ","ē“å°","ēøåƹččØ","ēå¾","ē","ēØē¼","ē","ēå¢","ē£","ē£ä¹","ē£å","ē¦»","ē§","ē§°","ē«č","ē¬¬","ē","ēå°","ēē","ē®čØä¹","ē®”","ē±»å¦","ē“§ę„ē","ēŗµ","ēŗµä»¤","ēŗµä½æ","ēŗµē¶","ē»","ē»čæ","ē»ę","ē»","ē»§ä¹","ē»§å","ē»§č","ē»¼äøęčæ°","ē½¢äŗ","č
","č","čäø","čåµ","čå","čå¤","čå·²","čęÆ","ččØ","č½","č½å¦","č
¾","čŖ","čŖäøŖåæ","čŖä»","čŖååæ","čŖå","čŖ家","čŖå·±","čŖę","čŖčŗ«","č³","č³äŗ","č³ä»","č³č„","č“","č¬ē","č„","č„夫","č„ęÆ","č„ę","č„é","č«äøē¶","č«å¦","č«č„","č½","č½å","č½ē¶","č½čÆ“","č¢«","č¦","č¦äø","č¦äøęÆ","č¦äøē¶","č¦ä¹","č¦ęÆ","č¬å»","č¬å¦","让","č®øå¤","č®ŗ","č®¾ä½æ","č®¾ę","č®¾č„","čÆå¦","čÆē¶","čÆ„","čÆ“","čÆ“ę„","čÆ·","čÆø","čÆøä½","čÆøå¦","č°","č°äŗŗ","č°ę","č°ē„","č“¼ę»","čµä»„","赶","čµ·","čµ·č§","č¶","č¶ē","č¶ęÆ","č·","č·","č¾","č¾ä¹","č¾¹","čæ","čæ","čæęÆ","čæę","čæč¦","čæ","čæäøę„","čæäøŖ","čæä¹","čæä¹äŗ","čæä¹ę ·","čæä¹ē¹åæ","čæäŗ","čæä¼åæ","čæåæ","čæå°±ęÆčÆ“","čæę¶","čæę ·","čæꬔ","čæč¬","čæč¾¹","čæé","čæč","čæ","čæå","éę„","éčæ","éµå¾Ŗ","éµē
§","é£","é£äøŖ","é£ä¹","é£ä¹äŗ","é£ä¹ę ·","é£äŗ","é£ä¼åæ","é£åæ","é£ę¶","é£ę ·","é£č¬","é£č¾¹","é£é","é½","éäŗŗ","é“äŗ","éåƹ","éæ","é¤","é¤äŗ","é¤å¤","é¤å¼","é¤ę¤ä¹å¤","é¤é","é","éå","éę¶","éē","é¾éčÆ“","é¶","é","éä½","éå¾","éē¹","éē¬","é ","é”ŗ","é”ŗē","é¦å
","ļøæ","ļ¼","ļ¼","ļ¼","ļ¼
","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼ ","ļ¼»","ļ¼½","ļ½","ļ½","ļ½","ļ½","ļæ„"],"hr":["a","ako","ali","bi","bih","bila","bili","bilo","bio","bismo","biste","biti","bumo","da","do","duž","ga","hoÄe","hoÄemo","hoÄete","hoÄeÅ”","hoÄu","i","iako","ih","ili","iz","ja","je","jedna","jedne","jedno","jer","jesam","jesi","jesmo","jest","jeste","jesu","jim","joj","joÅ”","ju","kada","kako","kao","koja","koje","koji","kojima","koju","kroz","li","me","mene","meni","mi","mimo","moj","moja","moje","mu","na","nad","nakon","nam","nama","nas","naÅ”","naÅ”a","naÅ”e","naÅ”eg","ne","nego","neka","neki","nekog","neku","nema","netko","neÄe","neÄemo","neÄete","neÄeÅ”","neÄu","neÅ”to","ni","nije","nikoga","nikoje","nikoju","nisam","nisi","nismo","niste","nisu","njega","njegov","njegova","njegovo","njemu","njezin","njezina","njezino","njih","njihov","njihova","njihovo","njim","njima","njoj","nju","no","o","od","odmah","on","ona","oni","ono","ova","pa","pak","po","pod","pored","prije","s","sa","sam","samo","se","sebe","sebi","si","smo","ste","su","sve","svi","svog","svoj","svoja","svoje","svom","ta","tada","taj","tako","te","tebe","tebi","ti","to","toj","tome","tu","tvoj","tvoja","tvoje","u","uz","vam","vama","vas","vaÅ”","vaÅ”a","vaÅ”e","veÄ","vi","vrlo","za","zar","Äe","Äemo","Äete","ÄeÅ”","Äu","Å”to"],"cs":["a","aby","ahoj","aj","ale","anebo","ani","aniž","ano","asi","aspoÅ","atd","atp","az","aÄkoli","až","bez","beze","blĆzko","bohužel","brzo","bude","budem","budeme","budes","budete","budeÅ”","budou","budu","by","byl","byla","byli","bylo","byly","bys","byt","bĆ½t","bÄhem","chce","chceme","chcete","chceÅ”","chci","chtĆt","chtÄjĆ","chut'","chuti","ci","clanek","clanku","clanky","co","coz","což","cz","daleko","dalsi","dalÅ”Ć","den","deset","design","devatenĆ”ct","devÄt","dnes","do","dobrĆ½","docela","dva","dvacet","dvanĆ”ct","dvÄ","dĆ”l","dĆ”le","dÄkovat","dÄkujeme","dÄkuji","email","ho","hodnÄ","i","jak","jakmile","jako","jakož","jde","je","jeden","jedenĆ”ct","jedna","jedno","jednou","jedou","jeho","jehož","jej","jeji","jejich","jejĆ","jelikož","jemu","jen","jenom","jenž","jeste","jestli","jestliže","jeÅ”tÄ","jež","ji","jich","jimi","jinak","jine","jinĆ©","jiz","již","jsem","jses","jseÅ”","jsi","jsme","jsou","jste","jĆ”","jĆ","jĆm","jĆž","jÅ”te","k","kam","každĆ½","kde","kdo","kdy","kdyz","když","ke","kolik","kromÄ","ktera","ktere","kteri","kterou","ktery","kterĆ”","kterĆ©","kterĆ½","kteÅi","kteÅĆ","ku","kvÅÆli","ma","majĆ","mate","me","mezi","mi","mit","mne","mnou","mnÄ","moc","mohl","mohou","moje","moji","možnĆ”","muj","musĆ","muze","my","mĆ”","mĆ”lo","mĆ”m","mĆ”me","mĆ”te","mĆ”Å”","mĆ©","mĆ","mĆt","mÄ","mÅÆj","mÅÆže","na","nad","nade","nam","napiste","napiÅ”te","naproti","nas","nasi","naÄež","naÅ”e","naÅ”i","ne","nebo","nebyl","nebyla","nebyli","nebyly","nechÅ„","nedÄlajĆ","nedÄlĆ”","nedÄlĆ”m","nedÄlĆ”me","nedÄlĆ”te","nedÄlĆ”Å”","neg","nejsi","nejsou","nemajĆ","nemĆ”me","nemĆ”te","nemÄl","neni","nenĆ","nestaÄĆ","nevadĆ","nez","než","nic","nich","nimi","nove","novy","novĆ©","novĆ½","nula","nĆ”","nĆ”m","nĆ”mi","nĆ”s","nĆ”Å”","nĆ","nĆm","nÄ","nÄco","nÄjak","nÄkde","nÄkdo","nÄmu","nÄmuž","o","od","ode","on","ona","oni","ono","ony","osm","osmnĆ”ct","pak","patnĆ”ct","po","pod","podle","pokud","potom","pouze","pozdÄ","poÅĆ”d","prave","pravĆ©","pred","pres","pri","pro","proc","prostÄ","prosĆm","proti","proto","protoze","protože","proÄ","prvni","prvnĆ","prĆ”ve","pta","pÄt","pÅed","pÅede","pÅes","pÅese","pÅi","pÅiÄemž","re","rovnÄ","s","se","sedm","sedmnĆ”ct","si","sice","skoro","smĆ","smÄjĆ","snad","spolu","sta","sto","strana","stĆ©","sve","svych","svym","svymi","svĆ©","svĆ½ch","svĆ½m","svĆ½mi","svÅÆj","ta","tady","tak","take","takhle","taky","takze","takĆ©","takže","tam","tamhle","tamhleto","tamto","tato","te","tebe","tebou","ted'","tedy","tema","ten","tento","teto","ti","tim","timto","tipy","tisĆc","tisĆce","to","tobÄ","tohle","toho","tohoto","tom","tomto","tomu","tomuto","toto","troÅ”ku","tu","tuto","tvoje","tvĆ”","tvĆ©","tvÅÆj","ty","tyto","tĆ©ma","tĆ©to","tĆm","tĆmto","tÄ","tÄm","tÄma","tÄmu","tÅeba","tÅi","tÅinĆ”ct","u","urÄitÄ","uz","už","v","vam","vas","vase","vaÅ”e","vaÅ”i","ve","vedle","veÄer","vice","vlastnÄ","vsak","vy","vĆ”m","vĆ”mi","vĆ”s","vĆ”Å”","vĆce","vÅ”ak","vÅ”echen","vÅ”echno","vÅ”ichni","vÅÆbec","vždy","z","za","zatĆmco","zaÄ","zda","zde","ze","zpet","zpravy","zprĆ”vy","zpÄt","Äau","Äi","ÄlĆ”nek","ÄlĆ”nku","ÄlĆ”nky","ÄtrnĆ”ct","ÄtyÅi","Å”est","Å”estnĆ”ct","že"],"da":["ad","af","aldrig","alle","alt","anden","andet","andre","at","bare","begge","blev","blive","bliver","da","de","dem","den","denne","der","deres","det","dette","dig","din","dine","disse","dit","dog","du","efter","ej","eller","en","end","ene","eneste","enhver","er","et","far","fem","fik","fire","flere","fleste","for","fordi","forrige","fra","fĆ„","fĆ„r","fĆør","god","godt","ham","han","hans","har","havde","have","hej","helt","hende","hendes","her","hos","hun","hvad","hvem","hver","hvilken","hvis","hvor","hvordan","hvorfor","hvornĆ„r","i","ikke","ind","ingen","intet","ja","jeg","jer","jeres","jo","kan","kom","komme","kommer","kun","kunne","lad","lav","lidt","lige","lille","man","mand","mange","med","meget","men","mens","mere","mig","min","mine","mit","mod","mĆ„","ned","nej","ni","nogen","noget","nogle","nu","ny","nyt","nĆ„r","nƦr","nƦste","nƦsten","og","ogsĆ„","okay","om","op","os","otte","over","pĆ„","se","seks","selv","ser","ses","sig","sige","sin","sine","sit","skal","skulle","som","stor","store","syv","sĆ„","sĆ„dan","tag","tage","thi","ti","til","to","tre","ud","under","var","ved","vi","vil","ville","vor","vores","vƦre","vƦret"],"nl":["aan","aangaande","aangezien","achte","achter","achterna","af","afgelopen","al","aldaar","aldus","alhoewel","alias","alle","allebei","alleen","alles","als","alsnog","altijd","altoos","ander","andere","anders","anderszins","beetje","behalve","behoudens","beide","beiden","ben","beneden","bent","bepaald","betreffende","bij","bijna","bijv","binnen","binnenin","blijkbaar","blijken","boven","bovenal","bovendien","bovengenoemd","bovenstaand","bovenvermeld","buiten","bv","daar","daardoor","daarheen","daarin","daarna","daarnet","daarom","daarop","daaruit","daarvanlangs","dan","dat","de","deden","deed","der","derde","derhalve","dertig","deze","dhr","die","dikwijls","dit","doch","doe","doen","doet","door","doorgaand","drie","duizend","dus","echter","een","eens","eer","eerdat","eerder","eerlang","eerst","eerste","eigen","eigenlijk","elk","elke","en","enig","enige","enigszins","enkel","er","erdoor","erg","ergens","etc","etcetera","even","eveneens","evenwel","gauw","ge","gedurende","geen","gehad","gekund","geleden","gelijk","gemoeten","gemogen","genoeg","geweest","gewoon","gewoonweg","haar","haarzelf","had","hadden","hare","heb","hebben","hebt","hedden","heeft","heel","hem","hemzelf","hen","het","hetzelfde","hier","hierbeneden","hierboven","hierin","hierna","hierom","hij","hijzelf","hoe","hoewel","honderd","hun","hunne","ieder","iedere","iedereen","iemand","iets","ik","ikzelf","in","inderdaad","inmiddels","intussen","inzake","is","ja","je","jezelf","jij","jijzelf","jou","jouw","jouwe","juist","jullie","kan","klaar","kon","konden","krachtens","kun","kunnen","kunt","laatst","later","liever","lijken","lijkt","maak","maakt","maakte","maakten","maar","mag","maken","me","meer","meest","meestal","men","met","mevr","mezelf","mij","mijn","mijnent","mijner","mijzelf","minder","miss","misschien","missen","mits","mocht","mochten","moest","moesten","moet","moeten","mogen","mr","mrs","mw","na","naar","nadat","nam","namelijk","nee","neem","negen","nemen","nergens","net","niemand","niet","niets","niks","noch","nochtans","nog","nogal","nooit","nu","nv","of","ofschoon","om","omdat","omhoog","omlaag","omstreeks","omtrent","omver","ondanks","onder","ondertussen","ongeveer","ons","onszelf","onze","onzeker","ooit","ook","op","opnieuw","opzij","over","overal","overeind","overige","overigens","paar","pas","per","precies","recent","redelijk","reeds","rond","rondom","samen","sedert","sinds","sindsdien","slechts","sommige","spoedig","steeds","tamelijk","te","tegen","tegenover","tenzij","terwijl","thans","tien","tiende","tijdens","tja","toch","toe","toen","toenmaals","toenmalig","tot","totdat","tussen","twee","tweede","u","uit","uitgezonderd","uw","vaak","vaakwat","van","vanaf","vandaan","vanuit","vanwege","veel","veeleer","veertig","verder","verscheidene","verschillende","vervolgens","via","vier","vierde","vijf","vijfde","vijftig","vol","volgend","volgens","voor","vooraf","vooral","vooralsnog","voorbij","voordat","voordezen","voordien","voorheen","voorop","voorts","vooruit","vrij","vroeg","waar","waarom","waarschijnlijk","wanneer","want","waren","was","wat","we","wederom","weer","weg","wegens","weinig","wel","weldra","welk","welke","werd","werden","werder","wezen","whatever","wie","wiens","wier","wij","wijzelf","wil","wilden","willen","word","worden","wordt","zal","ze","zei","zeker","zelf","zelfde","zelfs","zes","zeven","zich","zichzelf","zij","zijn","zijne","zijzelf","zo","zoals","zodat","zodra","zonder","zou","zouden","zowat","zulk","zulke","zullen","zult"],"en":["'ll","'tis","'twas","'ve","10","39","a","a's","able","ableabout","about","above","abroad","abst","accordance","according","accordingly","across","act","actually","ad","added","adj","adopted","ae","af","affected","affecting","affects","after","afterwards","ag","again","against","ago","ah","ahead","ai","ain't","aint","al","all","allow","allows","almost","alone","along","alongside","already","also","although","always","am","amid","amidst","among","amongst","amoungst","amount","an","and","announce","another","any","anybody","anyhow","anymore","anyone","anything","anyway","anyways","anywhere","ao","apart","apparently","appear","appreciate","appropriate","approximately","aq","ar","are","area","areas","aren","aren't","arent","arise","around","arpa","as","aside","ask","asked","asking","asks","associated","at","au","auth","available","aw","away","awfully","az","b","ba","back","backed","backing","backs","backward","backwards","bb","bd","be","became","because","become","becomes","becoming","been","before","beforehand","began","begin","beginning","beginnings","begins","behind","being","beings","believe","below","beside","besides","best","better","between","beyond","bf","bg","bh","bi","big","bill","billion","biol","bj","bm","bn","bo","both","bottom","br","brief","briefly","bs","bt","but","buy","bv","bw","by","bz","c","c'mon","c's","ca","call","came","can","can't","cannot","cant","caption","case","cases","cause","causes","cc","cd","certain","certainly","cf","cg","ch","changes","ci","ck","cl","clear","clearly","click","cm","cmon","cn","co","co.","com","come","comes","computer","con","concerning","consequently","consider","considering","contain","containing","contains","copy","corresponding","could","could've","couldn","couldn't","couldnt","course","cr","cry","cs","cu","currently","cv","cx","cy","cz","d","dare","daren't","darent","date","de","dear","definitely","describe","described","despite","detail","did","didn","didn't","didnt","differ","different","differently","directly","dj","dk","dm","do","does","doesn","doesn't","doesnt","doing","don","don't","done","dont","doubtful","down","downed","downing","downs","downwards","due","during","dz","e","each","early","ec","ed","edu","ee","effect","eg","eh","eight","eighty","either","eleven","else","elsewhere","empty","end","ended","ending","ends","enough","entirely","er","es","especially","et","et-al","etc","even","evenly","ever","evermore","every","everybody","everyone","everything","everywhere","ex","exactly","example","except","f","face","faces","fact","facts","fairly","far","farther","felt","few","fewer","ff","fi","fifteen","fifth","fifty","fify","fill","find","finds","fire","first","five","fix","fj","fk","fm","fo","followed","following","follows","for","forever","former","formerly","forth","forty","forward","found","four","fr","free","from","front","full","fully","further","furthered","furthering","furthermore","furthers","fx","g","ga","gave","gb","gd","ge","general","generally","get","gets","getting","gf","gg","gh","gi","give","given","gives","giving","gl","gm","gmt","gn","go","goes","going","gone","good","goods","got","gotten","gov","gp","gq","gr","great","greater","greatest","greetings","group","grouped","grouping","groups","gs","gt","gu","gw","gy","h","had","hadn't","hadnt","half","happens","hardly","has","hasn","hasn't","hasnt","have","haven","haven't","havent","having","he","he'd","he'll","he's","hed","hell","hello","help","hence","her","here","here's","hereafter","hereby","herein","heres","hereupon","hers","herself","herseā","hes","hi","hid","high","higher","highest","him","himself","himseā","his","hither","hk","hm","hn","home","homepage","hopefully","how","how'd","how'll","how's","howbeit","however","hr","ht","htm","html","http","hu","hundred","i","i'd","i'll","i'm","i've","i.e.","id","ie","if","ignored","ii","il","ill","im","immediate","immediately","importance","important","in","inasmuch","inc","inc.","indeed","index","indicate","indicated","indicates","information","inner","inside","insofar","instead","int","interest","interested","interesting","interests","into","invention","inward","io","iq","ir","is","isn","isn't","isnt","it","it'd","it'll","it's","itd","itll","its","itself","itseā","ive","j","je","jm","jo","join","jp","just","k","ke","keep","keeps","kept","keys","kg","kh","ki","kind","km","kn","knew","know","known","knows","kp","kr","kw","ky","kz","l","la","large","largely","last","lately","later","latest","latter","latterly","lb","lc","least","length","less","lest","let","let's","lets","li","like","liked","likely","likewise","line","little","lk","ll","long","longer","longest","look","looking","looks","low","lower","lr","ls","lt","ltd","lu","lv","ly","m","ma","made","mainly","make","makes","making","man","many","may","maybe","mayn't","maynt","mc","md","me","mean","means","meantime","meanwhile","member","members","men","merely","mg","mh","microsoft","might","might've","mightn't","mightnt","mil","mill","million","mine","minus","miss","mk","ml","mm","mn","mo","more","moreover","most","mostly","move","mp","mq","mr","mrs","ms","msie","mt","mu","much","mug","must","must've","mustn't","mustnt","mv","mw","mx","my","myself","myseā","mz","n","na","name","namely","nay","nc","nd","ne","near","nearly","necessarily","necessary","need","needed","needing","needn't","neednt","needs","neither","net","netscape","never","neverf","neverless","nevertheless","new","newer","newest","next","nf","ng","ni","nine","ninety","nl","no","no-one","nobody","non","none","nonetheless","noone","nor","normally","nos","not","noted","nothing","notwithstanding","novel","now","nowhere","np","nr","nu","null","number","numbers","nz","o","obtain","obtained","obviously","of","off","often","oh","ok","okay","old","older","oldest","om","omitted","on","once","one","one's","ones","only","onto","open","opened","opening","opens","opposite","or","ord","order","ordered","ordering","orders","org","other","others","otherwise","ought","oughtn't","oughtnt","our","ours","ourselves","out","outside","over","overall","owing","own","p","pa","page","pages","part","parted","particular","particularly","parting","parts","past","pe","per","perhaps","pf","pg","ph","pk","pl","place","placed","places","please","plus","pm","pmid","pn","point","pointed","pointing","points","poorly","possible","possibly","potentially","pp","pr","predominantly","present","presented","presenting","presents","presumably","previously","primarily","probably","problem","problems","promptly","proud","provided","provides","pt","put","puts","pw","py","q","qa","que","quickly","quite","qv","r","ran","rather","rd","re","readily","really","reasonably","recent","recently","ref","refs","regarding","regardless","regards","related","relatively","research","reserved","respectively","resulted","resulting","results","right","ring","ro","room","rooms","round","ru","run","rw","s","sa","said","same","saw","say","saying","says","sb","sc","sd","se","sec","second","secondly","seconds","section","see","seeing","seem","seemed","seeming","seems","seen","sees","self","selves","sensible","sent","serious","seriously","seven","seventy","several","sg","sh","shall","shan't","shant","she","she'd","she'll","she's","shed","shell","shes","should","should've","shouldn","shouldn't","shouldnt","show","showed","showing","shown","showns","shows","si","side","sides","significant","significantly","similar","similarly","since","sincere","site","six","sixty","sj","sk","sl","slightly","sm","small","smaller","smallest","sn","so","some","somebody","someday","somehow","someone","somethan","something","sometime","sometimes","somewhat","somewhere","soon","sorry","specifically","specified","specify","specifying","sr","st","state","states","still","stop","strongly","su","sub","substantially","successfully","such","sufficiently","suggest","sup","sure","sv","sy","system","sz","t","t's","take","taken","taking","tc","td","tell","ten","tends","test","text","tf","tg","th","than","thank","thanks","thanx","that","that'll","that's","that've","thatll","thats","thatve","the","their","theirs","them","themselves","then","thence","there","there'd","there'll","there're","there's","there've","thereafter","thereby","thered","therefore","therein","therell","thereof","therere","theres","thereto","thereupon","thereve","these","they","they'd","they'll","they're","they've","theyd","theyll","theyre","theyve","thick","thin","thing","things","think","thinks","third","thirty","this","thorough","thoroughly","those","thou","though","thoughh","thought","thoughts","thousand","three","throug","through","throughout","thru","thus","til","till","tip","tis","tj","tk","tm","tn","to","today","together","too","took","top","toward","towards","tp","tr","tried","tries","trillion","truly","try","trying","ts","tt","turn","turned","turning","turns","tv","tw","twas","twelve","twenty","twice","two","tz","u","ua","ug","uk","um","un","under","underneath","undoing","unfortunately","unless","unlike","unlikely","until","unto","up","upon","ups","upwards","us","use","used","useful","usefully","usefulness","uses","using","usually","uucp","uy","uz","v","va","value","various","vc","ve","versus","very","vg","vi","via","viz","vn","vol","vols","vs","vu","w","want","wanted","wanting","wants","was","wasn","wasn't","wasnt","way","ways","we","we'd","we'll","we're","we've","web","webpage","website","wed","welcome","well","wells","went","were","weren","weren't","werent","weve","wf","what","what'd","what'll","what's","what've","whatever","whatll","whats","whatve","when","when'd","when'll","when's","whence","whenever","where","where'd","where'll","where's","whereafter","whereas","whereby","wherein","wheres","whereupon","wherever","whether","which","whichever","while","whilst","whim","whither","who","who'd","who'll","who's","whod","whoever","whole","wholl","whom","whomever","whos","whose","why","why'd","why'll","why's","widely","width","will","willing","wish","with","within","without","won","won't","wonder","wont","words","work","worked","working","works","world","would","would've","wouldn","wouldn't","wouldnt","ws","www","x","y","ye","year","years","yes","yet","you","you'd","you'll","you're","you've","youd","youll","young","younger","youngest","your","youre","yours","yourself","yourselves","youve","yt","yu","z","za","zero","zm","zr"],"eo":["adiaÅ","ajn","al","ankoraÅ","antaÅ","aÅ","bonan","bonvole","bonvolu","bv","ci","cia","cian","cin","d-ro","da","de","dek","deka","do","doktor'","doktoro","du","dua","dum","eble","ekz","ekzemple","en","estas","estis","estos","estu","estus","eÄ","f-no","feliÄan","for","fraÅlino","ha","havas","havis","havos","havu","havus","he","ho","hu","ili","ilia","ilian","ilin","inter","io","ion","iu","iujn","iun","ja","jam","je","jes","k","kaj","ke","kio","kion","kiu","kiujn","kiun","kvankam","kvar","kvara","kvazaÅ","kvin","kvina","la","li","lia","lian","lin","malantaÅ","male","malgraÅ","mem","mi","mia","mian","min","minus","naÅ","naÅa","ne","nek","nenio","nenion","neniu","neniun","nepre","ni","nia","nian","nin","nu","nun","nur","ok","oka","oni","onia","onian","onin","plej","pli","plu","plus","por","post","preter","s-no","s-ro","se","sed","sep","sepa","ses","sesa","si","sia","sian","sin","sinjor'","sinjorino","sinjoro","sub","super","supren","sur","tamen","tio","tion","tiu","tiujn","tiun","tra","tri","tria","tuj","tute","unu","unua","ve","verÅajne","vi","via","vian","vin","Äi","Äio","Äion","Äiu","Äiujn","Äiun","Äu","Äi","Äia","Äian","Äin","Äis","ĵus","Åi","Åia","Åin"],"et":["aga","ei","et","ja","jah","kas","kui","kƵik","ma","me","mida","midagi","mind","minu","mis","mu","mul","mulle","nad","nii","oled","olen","oli","oma","on","pole","sa","seda","see","selle","siin","siis","ta","te","Ƥra"],"fi":["aiemmin","aika","aikaa","aikaan","aikaisemmin","aikaisin","aikajen","aikana","aikoina","aikoo","aikovat","aina","ainakaan","ainakin","ainoa","ainoat","aiomme","aion","aiotte","aist","aivan","ajan","alas","alemmas","alkuisin","alkuun","alla","alle","aloitamme","aloitan","aloitat","aloitatte","aloitattivat","aloitettava","aloitettevaksi","aloitettu","aloitimme","aloitin","aloitit","aloititte","aloittaa","aloittamatta","aloitti","aloittivat","alta","aluksi","alussa","alusta","annettavaksi","annetteva","annettu","ansiosta","antaa","antamatta","antoi","aoua","apu","asia","asiaa","asian","asiasta","asiat","asioiden","asioihin","asioita","asti","avuksi","avulla","avun","avutta","edelle","edelleen","edellƤ","edeltƤ","edemmƤs","edes","edessƤ","edestƤ","ehkƤ","ei","eikƤ","eilen","eivƤt","eli","ellei","elleivƤt","ellemme","ellen","ellet","ellette","emme","en","enemmƤn","eniten","ennen","ensi","ensimmƤinen","ensimmƤiseksi","ensimmƤisen","ensimmƤisenƤ","ensimmƤiset","ensimmƤisiksi","ensimmƤisinƤ","ensimmƤisiƤ","ensimmƤistƤ","ensin","entinen","entisen","entisiƤ","entisten","entistƤ","enƤƤ","eri","erittƤin","erityisesti","erƤiden","erƤs","erƤƤt","esi","esiin","esillƤ","esimerkiksi","et","eteen","etenkin","etessa","ette","ettei","ettƤ","haikki","halua","haluaa","haluamatta","haluamme","haluan","haluat","haluatte","haluavat","halunnut","halusi","halusimme","halusin","halusit","halusitte","halusivat","halutessa","haluton","he","hei","heidƤn","heidƤt","heihin","heille","heillƤ","heiltƤ","heissƤ","heistƤ","heitƤ","helposti","heti","hetkellƤ","hieman","hitaasti","hoikein","huolimatta","huomenna","hyvien","hyviin","hyviksi","hyville","hyviltƤ","hyvin","hyvinƤ","hyvissƤ","hyvistƤ","hyviƤ","hyvƤ","hyvƤt","hyvƤƤ","hƤn","hƤneen","hƤnelle","hƤnellƤ","hƤneltƤ","hƤnen","hƤnessƤ","hƤnestƤ","hƤnet","hƤntƤ","ihan","ilman","ilmeisesti","itse","itsensƤ","itseƤƤn","ja","jo","johon","joiden","joihin","joiksi","joilla","joille","joilta","joina","joissa","joista","joita","joka","jokainen","jokin","joko","joksi","joku","jolla","jolle","jolloin","jolta","jompikumpi","jona","jonka","jonkin","jonne","joo","jopa","jos","joskus","jossa","josta","jota","jotain","joten","jotenkin","jotenkuten","jotka","jotta","jouduimme","jouduin","jouduit","jouduitte","joudumme","joudun","joudutte","joukkoon","joukossa","joukosta","joutua","joutui","joutuivat","joutumaan","joutuu","joutuvat","juuri","jƤlkeen","jƤlleen","jƤƤ","kahdeksan","kahdeksannen","kahdella","kahdelle","kahdelta","kahden","kahdessa","kahdesta","kahta","kahteen","kai","kaiken","kaikille","kaikilta","kaikkea","kaikki","kaikkia","kaikkiaan","kaikkialla","kaikkialle","kaikkialta","kaikkien","kaikkin","kaksi","kannalta","kannattaa","kanssa","kanssaan","kanssamme","kanssani","kanssanne","kanssasi","kauan","kauemmas","kaukana","kautta","kehen","keiden","keihin","keiksi","keille","keillƤ","keiltƤ","keinƤ","keissƤ","keistƤ","keitten","keittƤ","keitƤ","keneen","keneksi","kenelle","kenellƤ","keneltƤ","kenen","kenenƤ","kenessƤ","kenestƤ","kenet","kenettƤ","kennessƤstƤ","kenties","kerran","kerta","kertaa","keskellƤ","kesken","keskimƤƤrin","ketkƤ","ketƤ","kiitos","kohti","koko","kokonaan","kolmas","kolme","kolmen","kolmesti","koska","koskaan","kovin","kuin","kuinka","kuinkan","kuitenkaan","kuitenkin","kuka","kukaan","kukin","kukka","kumpainen","kumpainenkaan","kumpi","kumpikaan","kumpikin","kun","kuten","kuuden","kuusi","kuutta","kylliksi","kyllƤ","kymmenen","kyse","liian","liki","lisƤksi","lisƤƤ","lla","luo","luona","lƤhekkƤin","lƤhelle","lƤhellƤ","lƤheltƤ","lƤhemmƤs","lƤhes","lƤhinnƤ","lƤhtien","lƤpi","mahdollisimman","mahdollista","me","meidƤn","meidƤt","meihin","meille","meillƤ","meiltƤ","meissƤ","meistƤ","meitƤ","melkein","melko","menee","meneet","menemme","menen","menet","menette","menevƤt","meni","menimme","menin","menit","menivƤt","mennessƤ","mennyt","menossa","mihin","mikin","miksi","mikƤ","mikƤli","mikƤƤn","mille","milloin","milloinkan","millƤ","miltƤ","minkƤ","minne","minua","minulla","minulle","minulta","minun","minussa","minusta","minut","minuun","minƤ","missƤ","mistƤ","miten","mitkƤ","mitƤ","mitƤƤn","moi","molemmat","mones","monesti","monet","moni","moniaalla","moniaalle","moniaalta","monta","muassa","muiden","muita","muka","mukaan","mukaansa","mukana","mutta","muu","muualla","muualle","muualta","muuanne","muulloin","muun","muut","muuta","muutama","muutaman","muuten","myƶhemmin","myƶs","myƶskin","myƶskƤƤn","myƶtƤ","ne","neljƤ","neljƤn","neljƤƤ","niiden","niihin","niiksi","niille","niillƤ","niiltƤ","niin","niinƤ","niissƤ","niistƤ","niitƤ","noiden","noihin","noiksi","noilla","noille","noilta","noin","noina","noissa","noista","noita","nopeammin","nopeasti","nopeiten","nro","nuo","nyt","nƤiden","nƤihin","nƤiksi","nƤille","nƤillƤ","nƤiltƤ","nƤin","nƤinƤ","nƤissƤ","nƤissƤhin","nƤissƤlle","nƤissƤltƤ","nƤissƤstƤ","nƤistƤ","nƤitƤ","nƤmƤ","ohi","oikea","oikealla","oikein","ole","olemme","olen","olet","olette","oleva","olevan","olevat","oli","olimme","olin","olisi","olisimme","olisin","olisit","olisitte","olisivat","olit","olitte","olivat","olla","olleet","olli","ollut","oma","omaa","omaan","omaksi","omalle","omalta","oman","omassa","omat","omia","omien","omiin","omiksi","omille","omilta","omissa","omista","on","onkin","onko","ovat","paikoittain","paitsi","pakosti","paljon","paremmin","parempi","parhaillaan","parhaiten","perusteella","perƤti","pian","pieneen","pieneksi","pienelle","pienellƤ","pieneltƤ","pienempi","pienestƤ","pieni","pienin","poikki","puolesta","puolestaan","pƤƤlle","runsaasti","saakka","sadam","sama","samaa","samaan","samalla","samallalta","samallassa","samallasta","saman","samat","samoin","sata","sataa","satojen","se","seitsemƤn","sekƤ","sen","seuraavat","siellƤ","sieltƤ","siihen","siinƤ","siis","siitƤ","sijaan","siksi","sille","silloin","sillƤ","silti","siltƤ","sinne","sinua","sinulla","sinulle","sinulta","sinun","sinussa","sinusta","sinut","sinuun","sinƤ","sisƤkkƤin","sisƤllƤ","siten","sitten","sitƤ","ssa","sta","suoraan","suuntaan","suuren","suuret","suuri","suuria","suurin","suurten","taa","taas","taemmas","tahansa","tai","takaa","takaisin","takana","takia","tallƤ","tapauksessa","tarpeeksi","tavalla","tavoitteena","te","teidƤn","teidƤt","teihin","teille","teillƤ","teiltƤ","teissƤ","teistƤ","teitƤ","tietysti","todella","toinen","toisaalla","toisaalle","toisaalta","toiseen","toiseksi","toisella","toiselle","toiselta","toisemme","toisen","toisensa","toisessa","toisesta","toista","toistaiseksi","toki","tosin","tuhannen","tuhat","tule","tulee","tulemme","tulen","tulet","tulette","tulevat","tulimme","tulin","tulisi","tulisimme","tulisin","tulisit","tulisitte","tulisivat","tulit","tulitte","tulivat","tulla","tulleet","tullut","tuntuu","tuo","tuohon","tuoksi","tuolla","tuolle","tuolloin","tuolta","tuon","tuona","tuonne","tuossa","tuosta","tuota","tuotƤ","tuskin","tykƶ","tƤhƤn","tƤksi","tƤlle","tƤllƤ","tƤllƶin","tƤltƤ","tƤmƤ","tƤmƤn","tƤnne","tƤnƤ","tƤnƤƤn","tƤssƤ","tƤstƤ","tƤten","tƤtƤ","tƤysin","tƤytyvƤt","tƤytyy","tƤƤllƤ","tƤƤltƤ","ulkopuolella","usea","useasti","useimmiten","usein","useita","uudeksi","uudelleen","uuden","uudet","uusi","uusia","uusien","uusinta","uuteen","uutta","vaan","vahemmƤn","vai","vaiheessa","vaikea","vaikean","vaikeat","vaikeilla","vaikeille","vaikeilta","vaikeissa","vaikeista","vaikka","vain","varmasti","varsin","varsinkin","varten","vasen","vasenmalla","vasta","vastaan","vastakkain","vastan","verran","vielƤ","vierekkƤin","vieressƤ","vieri","viiden","viime","viimeinen","viimeisen","viimeksi","viisi","voi","voidaan","voimme","voin","voisi","voit","voitte","voivat","vuoden","vuoksi","vuosi","vuosien","vuosina","vuotta","vƤhemmƤn","vƤhintƤƤn","vƤhiten","vƤhƤn","vƤlillƤ","yhdeksƤn","yhden","yhdessƤ","yhteen","yhteensƤ","yhteydessƤ","yhteyteen","yhtƤ","yhtƤƤlle","yhtƤƤllƤ","yhtƤƤltƤ","yhtƤƤn","yhƤ","yksi","yksin","yksittƤin","yleensƤ","ylemmƤs","yli","ylƶs","ympƤri","Ƥlkƶƶn","ƤlƤ"],"fr":["a","abord","absolument","afin","ah","ai","aie","aient","aies","ailleurs","ainsi","ait","allaient","allo","allons","allĆ“","alors","anterieur","anterieure","anterieures","apres","aprĆØs","as","assez","attendu","au","aucun","aucune","aucuns","aujourd","aujourd'hui","aupres","auquel","aura","aurai","auraient","aurais","aurait","auras","aurez","auriez","aurions","aurons","auront","aussi","autant","autre","autrefois","autrement","autres","autrui","aux","auxquelles","auxquels","avaient","avais","avait","avant","avec","avez","aviez","avions","avoir","avons","ayant","ayez","ayons","b","bah","bas","basee","bat","beau","beaucoup","bien","bigre","bon","boum","bravo","brrr","c","car","ce","ceci","cela","celle","celle-ci","celle-lĆ ","celles","celles-ci","celles-lĆ ","celui","celui-ci","celui-lĆ ","celĆ ","cent","cependant","certain","certaine","certaines","certains","certes","ces","cet","cette","ceux","ceux-ci","ceux-lĆ ","chacun","chacune","chaque","cher","chers","chez","chiche","chut","chĆØre","chĆØres","ci","cinq","cinquantaine","cinquante","cinquantiĆØme","cinquiĆØme","clac","clic","combien","comme","comment","comparable","comparables","compris","concernant","contre","couic","crac","d","da","dans","de","debout","dedans","dehors","deja","delĆ ","depuis","dernier","derniere","derriere","derriĆØre","des","desormais","desquelles","desquels","dessous","dessus","deux","deuxiĆØme","deuxiĆØmement","devant","devers","devra","devrait","different","differentes","differents","diffĆ©rent","diffĆ©rente","diffĆ©rentes","diffĆ©rents","dire","directe","directement","dit","dite","dits","divers","diverse","diverses","dix","dix-huit","dix-neuf","dix-sept","dixiĆØme","doit","doivent","donc","dont","dos","douze","douziĆØme","dring","droite","du","duquel","durant","dĆØs","dĆ©but","dĆ©sormais","e","effet","egale","egalement","egales","eh","elle","elle-mĆŖme","elles","elles-mĆŖmes","en","encore","enfin","entre","envers","environ","es","essai","est","et","etant","etc","etre","eu","eue","eues","euh","eurent","eus","eusse","eussent","eusses","eussiez","eussions","eut","eux","eux-mĆŖmes","exactement","exceptĆ©","extenso","exterieur","eĆ»mes","eĆ»t","eĆ»tes","f","fais","faisaient","faisant","fait","faites","faƧon","feront","fi","flac","floc","fois","font","force","furent","fus","fusse","fussent","fusses","fussiez","fussions","fut","fĆ»mes","fĆ»t","fĆ»tes","g","gens","h","ha","haut","hein","hem","hep","hi","ho","holĆ ","hop","hormis","hors","hou","houp","hue","hui","huit","huitiĆØme","hum","hurrah","hĆ©","hĆ©las","i","ici","il","ils","importe","j","je","jusqu","jusque","juste","k","l","la","laisser","laquelle","las","le","lequel","les","lesquelles","lesquels","leur","leurs","longtemps","lors","lorsque","lui","lui-meme","lui-mĆŖme","lĆ ","lĆØs","m","ma","maint","maintenant","mais","malgre","malgrĆ©","maximale","me","meme","memes","merci","mes","mien","mienne","miennes","miens","mille","mince","mine","minimale","moi","moi-meme","moi-mĆŖme","moindres","moins","mon","mot","moyennant","multiple","multiples","mĆŖme","mĆŖmes","n","na","naturel","naturelle","naturelles","ne","neanmoins","necessaire","necessairement","neuf","neuviĆØme","ni","nombreuses","nombreux","nommĆ©s","non","nos","notamment","notre","nous","nous-mĆŖmes","nouveau","nouveaux","nul","nĆ©anmoins","nĆ“tre","nĆ“tres","o","oh","ohĆ©","ollĆ©","olĆ©","on","ont","onze","onziĆØme","ore","ou","ouf","ouias","oust","ouste","outre","ouvert","ouverte","ouverts","o|","oĆ¹","p","paf","pan","par","parce","parfois","parle","parlent","parler","parmi","parole","parseme","partant","particulier","particuliĆØre","particuliĆØrement","pas","passĆ©","pendant","pense","permet","personne","personnes","peu","peut","peuvent","peux","pff","pfft","pfut","pif","pire","piĆØce","plein","plouf","plupart","plus","plusieurs","plutĆ“t","possessif","possessifs","possible","possibles","pouah","pour","pourquoi","pourrais","pourrait","pouvait","prealable","precisement","premier","premiĆØre","premiĆØrement","pres","probable","probante","procedant","proche","prĆØs","psitt","pu","puis","puisque","pur","pure","q","qu","quand","quant","quant-Ć -soi","quanta","quarante","quatorze","quatre","quatre-vingt","quatriĆØme","quatriĆØmement","que","quel","quelconque","quelle","quelles","quelqu'un","quelque","quelques","quels","qui","quiconque","quinze","quoi","quoique","r","rare","rarement","rares","relative","relativement","remarquable","rend","rendre","restant","reste","restent","restrictif","retour","revoici","revoilĆ ","rien","s","sa","sacrebleu","sait","sans","sapristi","sauf","se","sein","seize","selon","semblable","semblaient","semble","semblent","sent","sept","septiĆØme","sera","serai","seraient","serais","serait","seras","serez","seriez","serions","serons","seront","ses","seul","seule","seulement","si","sien","sienne","siennes","siens","sinon","six","sixiĆØme","soi","soi-mĆŖme","soient","sois","soit","soixante","sommes","son","sont","sous","souvent","soyez","soyons","specifique","specifiques","speculatif","stop","strictement","subtiles","suffisant","suffisante","suffit","suis","suit","suivant","suivante","suivantes","suivants","suivre","sujet","superpose","sur","surtout","t","ta","tac","tandis","tant","tardive","te","tel","telle","tellement","telles","tels","tenant","tend","tenir","tente","tes","tic","tien","tienne","tiennes","tiens","toc","toi","toi-mĆŖme","ton","touchant","toujours","tous","tout","toute","toutefois","toutes","treize","trente","tres","trois","troisiĆØme","troisiĆØmement","trop","trĆØs","tsoin","tsouin","tu","tĆ©","u","un","une","unes","uniformement","unique","uniques","uns","v","va","vais","valeur","vas","vers","via","vif","vifs","vingt","vivat","vive","vives","vlan","voici","voie","voient","voilĆ ","voire","vont","vos","votre","vous","vous-mĆŖmes","vu","vĆ©","vĆ“tre","vĆ“tres","w","x","y","z","zut","Ć ","Ć¢","Ƨa","ĆØs","Ć©taient","Ć©tais","Ć©tait","Ć©tant","Ć©tat","Ć©tiez","Ć©tions","Ć©tĆ©","Ć©tĆ©e","Ć©tĆ©es","Ć©tĆ©s","ĆŖtes","ĆŖtre","Ć“"],"gl":["a","alĆ","ao","aos","aquel","aquela","aquelas","aqueles","aquilo","aquĆ","as","asĆ","aĆnda","ben","cando","che","co","coa","coas","comigo","con","connosco","contigo","convosco","cos","cun","cunha","cunhas","cuns","da","dalgunha","dalgunhas","dalgĆŗn","dalgĆŗns","das","de","del","dela","delas","deles","desde","deste","do","dos","dun","dunha","dunhas","duns","e","el","ela","elas","eles","en","era","eran","esa","esas","ese","eses","esta","estaba","estar","este","estes","estiven","estou","estĆ”","estĆ”n","eu","facer","foi","foron","fun","habĆa","hai","iso","isto","la","las","lle","lles","lo","los","mais","me","meu","meus","min","miƱa","miƱas","moi","na","nas","neste","nin","no","non","nos","nosa","nosas","noso","nosos","nun","nunha","nunhas","nuns","nĆ³s","o","os","ou","para","pero","pode","pois","pola","polas","polo","polos","por","que","se","senĆ³n","ser","seu","seus","sexa","sido","sobre","sĆŗa","sĆŗas","tamĆ©n","tan","te","ten","ter","teu","teus","teƱen","teƱo","ti","tido","tiven","tiƱa","tĆŗa","tĆŗas","un","unha","unhas","uns","vos","vosa","vosas","voso","vosos","vĆ³s","Ć”","Ć©","Ć³","Ć³s"],"de":["a","ab","aber","ach","acht","achte","achten","achter","achtes","ag","alle","allein","allem","allen","aller","allerdings","alles","allgemeinen","als","also","am","an","ander","andere","anderem","anderen","anderer","anderes","anderm","andern","anderr","anders","au","auch","auf","aus","ausser","ausserdem","auĆer","auĆerdem","b","bald","bei","beide","beiden","beim","beispiel","bekannt","bereits","besonders","besser","besten","bin","bis","bisher","bist","c","d","d.h","da","dabei","dadurch","dafĆ¼r","dagegen","daher","dahin","dahinter","damals","damit","danach","daneben","dank","dann","daran","darauf","daraus","darf","darfst","darin","darum","darunter","darĆ¼ber","das","dasein","daselbst","dass","dasselbe","davon","davor","dazu","dazwischen","daĆ","dein","deine","deinem","deinen","deiner","deines","dem","dementsprechend","demgegenĆ¼ber","demgemƤss","demgemƤĆ","demselben","demzufolge","den","denen","denn","denselben","der","deren","derer","derjenige","derjenigen","dermassen","dermaĆen","derselbe","derselben","des","deshalb","desselben","dessen","deswegen","dich","die","diejenige","diejenigen","dies","diese","dieselbe","dieselben","diesem","diesen","dieser","dieses","dir","doch","dort","drei","drin","dritte","dritten","dritter","drittes","du","durch","durchaus","durfte","durften","dĆ¼rfen","dĆ¼rft","e","eben","ebenso","ehrlich","ei","ei,","eigen","eigene","eigenen","eigener","eigenes","ein","einander","eine","einem","einen","einer","eines","einig","einige","einigem","einigen","einiger","einiges","einmal","eins","elf","en","ende","endlich","entweder","er","ernst","erst","erste","ersten","erster","erstes","es","etwa","etwas","euch","euer","eure","eurem","euren","eurer","eures","f","folgende","frĆ¼her","fĆ¼nf","fĆ¼nfte","fĆ¼nften","fĆ¼nfter","fĆ¼nftes","fĆ¼r","g","gab","ganz","ganze","ganzen","ganzer","ganzes","gar","gedurft","gegen","gegenĆ¼ber","gehabt","gehen","geht","gekannt","gekonnt","gemacht","gemocht","gemusst","genug","gerade","gern","gesagt","geschweige","gewesen","gewollt","geworden","gibt","ging","gleich","gott","gross","grosse","grossen","grosser","grosses","groĆ","groĆe","groĆen","groĆer","groĆes","gut","gute","guter","gutes","h","hab","habe","haben","habt","hast","hat","hatte","hatten","hattest","hattet","heisst","her","heute","hier","hin","hinter","hoch","hƤtte","hƤtten","i","ich","ihm","ihn","ihnen","ihr","ihre","ihrem","ihren","ihrer","ihres","im","immer","in","indem","infolgedessen","ins","irgend","ist","j","ja","jahr","jahre","jahren","je","jede","jedem","jeden","jeder","jedermann","jedermanns","jedes","jedoch","jemand","jemandem","jemanden","jene","jenem","jenen","jener","jenes","jetzt","k","kam","kann","kannst","kaum","kein","keine","keinem","keinen","keiner","keines","kleine","kleinen","kleiner","kleines","kommen","kommt","konnte","konnten","kurz","kƶnnen","kƶnnt","kƶnnte","l","lang","lange","leicht","leide","lieber","los","m","machen","macht","machte","mag","magst","mahn","mal","man","manche","manchem","manchen","mancher","manches","mann","mehr","mein","meine","meinem","meinen","meiner","meines","mensch","menschen","mich","mir","mit","mittel","mochte","mochten","morgen","muss","musst","musste","mussten","muĆ","muĆt","mƶchte","mƶgen","mƶglich","mƶgt","mĆ¼ssen","mĆ¼sst","mĆ¼Ćt","n","na","nach","nachdem","nahm","natĆ¼rlich","neben","nein","neue","neuen","neun","neunte","neunten","neunter","neuntes","nicht","nichts","nie","niemand","niemandem","niemanden","noch","nun","nur","o","ob","oben","oder","offen","oft","ohne","ordnung","p","q","r","recht","rechte","rechten","rechter","rechtes","richtig","rund","s","sa","sache","sagt","sagte","sah","satt","schlecht","schluss","schon","sechs","sechste","sechsten","sechster","sechstes","sehr","sei","seid","seien","sein","seine","seinem","seinen","seiner","seines","seit","seitdem","selbst","sich","sie","sieben","siebente","siebenten","siebenter","siebentes","sind","so","solang","solche","solchem","solchen","solcher","solches","soll","sollen","sollst","sollt","sollte","sollten","sondern","sonst","soweit","sowie","spƤter","startseite","statt","steht","suche","t","tag","tage","tagen","tat","teil","tel","tritt","trotzdem","tun","u","uhr","um","und","uns","unse","unsem","unsen","unser","unsere","unserer","unses","unter","v","vergangenen","viel","viele","vielem","vielen","vielleicht","vier","vierte","vierten","vierter","viertes","vom","von","vor","w","wahr","wann","war","waren","warst","wart","warum","was","weg","wegen","weil","weit","weiter","weitere","weiteren","weiteres","welche","welchem","welchen","welcher","welches","wem","wen","wenig","wenige","weniger","weniges","wenigstens","wenn","wer","werde","werden","werdet","weshalb","wessen","wie","wieder","wieso","will","willst","wir","wird","wirklich","wirst","wissen","wo","woher","wohin","wohl","wollen","wollt","wollte","wollten","worden","wurde","wurden","wƤhrend","wƤhrenddem","wƤhrenddessen","wƤre","wĆ¼rde","wĆ¼rden","x","y","z","z.b","zehn","zehnte","zehnten","zehnter","zehntes","zeit","zu","zuerst","zugleich","zum","zunƤchst","zur","zurĆ¼ck","zusammen","zwanzig","zwar","zwei","zweite","zweiten","zweiter","zweites","zwischen","zwƶlf","Ć¼ber","Ć¼berhaupt","Ć¼brigens"],"el":["ĪĪ½Ī±","ĪĪ½Ī±Ī½","ĪĪ½Ī±Ļ","Ī±Ī¹","Ī±ĪŗĪæĪ¼Ī±","Ī±ĪŗĪæĪ¼Ī·","Ī±ĪŗĻĪ¹Ī²ĻĻ","Ī±Ī»Ī·ĪøĪµĪ¹Ī±","Ī±Ī»Ī·ĪøĪ¹Ī½Ī±","Ī±Ī»Ī»Ī±","Ī±Ī»Ī»Ī±ĻĪæĻ
","Ī±Ī»Ī»ĪµĻ","Ī±Ī»Ī»Ī·","Ī±Ī»Ī»Ī·Ī½","Ī±Ī»Ī»Ī·Ļ","Ī±Ī»Ī»Ī¹ĻĻ","Ī±Ī»Ī»Ī¹ĻĻĪ¹ĪŗĪ±","Ī±Ī»Ī»Īæ","Ī±Ī»Ī»ĪæĪ¹","Ī±Ī»Ī»ĪæĪ¹ĻĻ","Ī±Ī»Ī»ĪæĪ¹ĻĻĪ¹ĪŗĪ±","Ī±Ī»Ī»ĪæĪ½","Ī±Ī»Ī»ĪæĻ","Ī±Ī»Ī»ĪæĻĪµ","Ī±Ī»Ī»ĪæĻ
","Ī±Ī»Ī»ĪæĻ
Ļ","Ī±Ī»Ī»ĻĪ½","Ī±Ī¼Ī±","Ī±Ī¼ĪµĻĪ±","Ī±Ī¼ĪµĻĻĻ","Ī±Ī½","Ī±Ī½Ī±","Ī±Ī½Ī±Ī¼ĪµĻĪ±","Ī±Ī½Ī±Ī¼ĪµĻĪ±Ī¾Ļ
","Ī±Ī½ĪµĻ
","Ī±Ī½ĻĪ¹","Ī±Ī½ĻĪ¹ĻĪµĻĪ±","Ī±Ī½ĻĪ¹Ļ","Ī±Ī½Ļ","Ī±Ī½ĻĻĪµĻĻ","Ī±Ī¾Ī±ĻĪ½Ī±","Ī±Ļ","Ī±ĻĪµĪ½Ī±Ī½ĻĪ¹","Ī±ĻĪæ","Ī±ĻĪæĻĪµ","Ī±ĻĻ","Ī±ĻĪ±","Ī±ĻĪ±Ī³Īµ","Ī±ĻĪ³Ī±","Ī±ĻĪ³ĪæĻĪµĻĪæ","Ī±ĻĪ¹ĻĻĪµĻĪ±","Ī±ĻĪŗĪµĻĪ±","Ī±ĻĻĪ¹ĪŗĪ±","Ī±Ļ","Ī±Ļ
ĻĪ¹Īæ","Ī±Ļ
ĻĪ±","Ī±Ļ
ĻĪµĻ","Ī±Ļ
ĻĪµĻ","Ī±Ļ
ĻĪ·","Ī±Ļ
ĻĪ·Ī½","Ī±Ļ
ĻĪ·Ļ","Ī±Ļ
ĻĪæ","Ī±Ļ
ĻĪæĪ¹","Ī±Ļ
ĻĪæĪ½","Ī±Ļ
ĻĪæĻ","Ī±Ļ
ĻĪæĻ","Ī±Ļ
ĻĪæĻ
","Ī±Ļ
ĻĪæĻ
Ļ","Ī±Ļ
ĻĪæĻ
Ļ","Ī±Ļ
ĻĻĪ½","Ī±ĻĪæĻĪæĻ
","Ī±ĻĪæĻ
","Ī±į¼±","Ī±į¼³","Ī±į¼µ","Ī±į½ĻĻĻ","Ī±į½Ļį½øĻ","Ī±į½","Ī±āĪ¹Ī±ĪŗĪæĻĪ±","Ī²ĪµĪ²Ī±Ī¹Ī±","Ī²ĪµĪ²Ī±Ī¹ĪæĻĪ±ĻĪ±","Ī³Ī¬Ļ","Ī³Ī±","Ī³Ī±^","Ī³Īµ","Ī³Ī¹","Ī³Ī¹Ī±","Ī³Īæįæ¦Ī½","Ī³ĻĪ·Ī³ĪæĻĪ±","Ī³Ļ
ĻĻ","Ī³į½°Ļ","Ī“'","Ī“Ī","Ī“Ī®","Ī“Ī±ĪÆ","Ī“Ī±ĪÆĻ","Ī“Ī±į½¶","Ī“Ī±į½¶Ļ","Ī“Īµ","Ī“ĪµĪ½","Ī“Ī¹","Ī“Ī¹'","Ī“Ī¹Ī¬","Ī“Ī¹Ī±","Ī“Ī¹į½°","Ī“į½²","Ī“į½“","Ī“ā","ĪµĪ±Ī½","ĪµĪ±Ļ
ĻĪæ","ĪµĪ±Ļ
ĻĪæĪ½","ĪµĪ±Ļ
ĻĪæĻ
","ĪµĪ±Ļ
ĻĪæĻ
Ļ","ĪµĪ±Ļ
ĻĻĪ½","ĪµĪ³ĪŗĪ±Ī¹ĻĪ±","ĪµĪ³ĪŗĪ±Ī¹ĻĻĻ","ĪµĪ³Ļ","ĪµĪ¹ĪøĪµ","ĪµĪ¹Ī¼Ī±Ī¹","ĪµĪ¹Ī¼Ī±ĻĻĪµ","ĪµĪ¹Ī½Ī±Ī¹","ĪµĪ¹Ļ","ĪµĪ¹ĻĪ±Ī¹","ĪµĪ¹ĻĪ±ĻĻĪµ","ĪµĪ¹ĻĻĪµ","ĪµĪ¹ĻĪµ","ĪµĪ¹ĻĪ±","ĪµĪ¹ĻĪ±Ī¼Īµ","ĪµĪ¹ĻĪ±Ī½","ĪµĪ¹ĻĪ±ĻĪµ","ĪµĪ¹ĻĪµ","ĪµĪ¹ĻĪµĻ","ĪµĪ¹āĪµĪ¼Ī·","ĪµĪŗ","ĪµĪŗĪ±ĻĻĪ±","ĪµĪŗĪ±ĻĻĪµĻ","ĪµĪŗĪ±ĻĻĪ·","ĪµĪŗĪ±ĻĻĪ·Ī½","ĪµĪŗĪ±ĻĻĪ·Ļ","ĪµĪŗĪ±ĻĻĪæ","ĪµĪŗĪ±ĻĻĪæĪ¹","ĪµĪŗĪ±ĻĻĪæĪ½","ĪµĪŗĪ±ĻĻĪæĻ","ĪµĪŗĪ±ĻĻĪæĻ
","ĪµĪŗĪ±ĻĻĪæĻ
Ļ","ĪµĪŗĪ±ĻĻĻĪ½","ĪµĪŗĪµĪ¹","ĪµĪŗĪµĪ¹Ī½Ī±","ĪµĪŗĪµĪ¹Ī½ĪµĻ","ĪµĪŗĪµĪ¹Ī½ĪµĻ","ĪµĪŗĪµĪ¹Ī½Ī·","ĪµĪŗĪµĪ¹Ī½Ī·Ī½","ĪµĪŗĪµĪ¹Ī½Ī·Ļ","ĪµĪŗĪµĪ¹Ī½Īæ","ĪµĪŗĪµĪ¹Ī½ĪæĪ¹","ĪµĪŗĪµĪ¹Ī½ĪæĪ½","ĪµĪŗĪµĪ¹Ī½ĪæĻ","ĪµĪŗĪµĪ¹Ī½ĪæĻ","ĪµĪŗĪµĪ¹Ī½ĪæĻ
","ĪµĪŗĪµĪ¹Ī½ĪæĻ
Ļ","ĪµĪŗĪµĪ¹Ī½ĪæĻ
Ļ","ĪµĪŗĪµĪ¹Ī½ĻĪ½","ĪµĪŗĻĪæĻ","ĪµĪ¼Ī±Ļ","ĪµĪ¼ĪµĪ¹Ļ","ĪµĪ¼ĪµĪ½Ī±","ĪµĪ¼ĻĻĪæĻ","ĪµĪ½","ĪµĪ½Ī±","ĪµĪ½Ī±Ī½","ĪµĪ½Ī±Ļ","ĪµĪ½ĪæĻ","ĪµĪ½ĻĪµĪ»ĻĻ","ĪµĪ½ĻĪæĻ","ĪµĪ½ĻĻĪ¼ĪµĻĪ±Ī¾Ļ
","ĪµĪ½Ļ","ĪµĪ½ĻĻ","ĪµĪ¾","ĪµĪ¾Ī±ĻĪ½Ī±","ĪµĪ¾Ī·Ļ","ĪµĪ¾Ī¹ĻĪæĻ
","ĪµĪ¾Ļ","ĪµĻ","ĪµĻĪÆ","ĪµĻĪ±Ī½Ļ","ĪµĻĪµĪ¹ĻĪ±","ĪµĻĪµĪ¹āĪ·","ĪµĻĪ¹","ĪµĻĪ¹ĻĪ·Ļ","ĪµĻĪæĪ¼ĪµĪ½ĻĻ","ĪµĻĪ±Ļ","ĪµĻĪµĪ¹Ļ","ĪµĻĪµĪ½Ī±","ĪµĻĻĻ","ĪµĻĻ
","ĪµĻĪµĻĪ±","ĪµĻĪµĻĪ±Ī¹","ĪµĻĪµĻĪ±Ļ","ĪµĻĪµĻĪµĻ","ĪµĻĪµĻĪ·","ĪµĻĪµĻĪ·Ļ","ĪµĻĪµĻĪæ","ĪµĻĪµĻĪæĪ¹","ĪµĻĪµĻĪæĪ½","ĪµĻĪµĻĪæĻ","ĪµĻĪµĻĪæĻ
","ĪµĻĪµĻĪæĻ
Ļ","ĪµĻĪµĻĻĪ½","ĪµĻĪæĻ
ĻĪ±","ĪµĻĪæĻ
ĻĪµĻ","ĪµĻĪæĻ
ĻĪ·","ĪµĻĪæĻ
ĻĪ·Ī½","ĪµĻĪæĻ
ĻĪ·Ļ","ĪµĻĪæĻ
ĻĪæ","ĪµĻĪæĻ
ĻĪæĪ¹","ĪµĻĪæĻ
ĻĪæĪ½","ĪµĻĪæĻ
ĻĪæĻ","ĪµĻĪæĻ
ĻĪæĻ
","ĪµĻĪæĻ
ĻĪæĻ
Ļ","ĪµĻĪæĻ
ĻĻĪ½","ĪµĻĻĪ¹","ĪµĻ
Ī³Īµ","ĪµĻ
ĪøĻ
Ļ","ĪµĻ
ĻĻ
ĻĻĻ","ĪµĻĪµĪ¾Ī·Ļ","ĪµĻĪµĪ¹","ĪµĻĪµĪ¹Ļ","ĪµĻĪµĻĪµ","ĪµĻĪøĪµĻ","ĪµĻĪæĪ¼Īµ","ĪµĻĪæĻ
Ī¼Īµ","ĪµĻĪæĻ
Ī½","ĪµĻĻĪµĻ","ĪµĻĻ","ĪµĻĻ","Īµį¼°","Īµį¼°Ī¼ĪÆ","Īµį¼°Ī¼į½¶","Īµį¼°Ļ","Īµį¼°Ļ","Īµį¼“","Īµį¼“Ī¼Ī¹","Īµį¼“ĻĪµ","ĪµāĻ","Ī·","Ī·Ī¼Ī±ĻĻĪ±Ī½","Ī·Ī¼Ī±ĻĻĪµ","Ī·Ī¼ĪæĻ
Ī½","Ī·ĻĪ±ĻĻĪ±Ī½","Ī·ĻĪ±ĻĻĪµ","Ī·ĻĪæĻ
Ī½","Ī·ĻĪ±Ī½","Ī·ĻĪ±Ī½Īµ","Ī·ĻĪæĪ¹","Ī·ĻĻĪæĪ½","Ī·āĪ·","ĪøĪ±","Ī¹","Ī¹Ī¹","Ī¹Ī¹Ī¹","Ī¹ĻĪ±Ī¼Īµ","Ī¹ĻĪ¹Ī±","Ī¹ĻĻĻ","Ī¹ĻĻĻ","Ī¹āĪ¹Ī±","Ī¹āĪ¹Ī±Ī½","Ī¹āĪ¹Ī±Ļ","Ī¹āĪ¹ĪµĻ","Ī¹āĪ¹Īæ","Ī¹āĪ¹ĪæĪ¹","Ī¹āĪ¹ĪæĪ½","Ī¹āĪ¹ĪæĻ","Ī¹āĪ¹ĪæĻ
","Ī¹āĪ¹ĪæĻ
Ļ","Ī¹āĪ¹ĻĪ½","Ī¹āĪ¹ĻĻ","Īŗ","ĪŗĪ±ĪÆ","ĪŗĪ±ĪÆĻĪæĪ¹","ĪŗĪ±Īø","ĪŗĪ±ĪøĪµ","ĪŗĪ±ĪøĪµĪ¼Ī¹Ī±","ĪŗĪ±ĪøĪµĪ¼Ī¹Ī±Ļ","ĪŗĪ±ĪøĪµĪ½Ī±","ĪŗĪ±ĪøĪµĪ½Ī±Ļ","ĪŗĪ±ĪøĪµĪ½ĪæĻ","ĪŗĪ±ĪøĪµĻĪ¹","ĪŗĪ±ĪøĪæĪ»ĪæĻ
","ĪŗĪ±ĪøĻĻ","ĪŗĪ±Ī¹","ĪŗĪ±ĪŗĪ±","ĪŗĪ±ĪŗĻĻ","ĪŗĪ±Ī»Ī±","ĪŗĪ±Ī»ĻĻ","ĪŗĪ±Ī¼Ī¹Ī±","ĪŗĪ±Ī¼Ī¹Ī±Ī½","ĪŗĪ±Ī¼Ī¹Ī±Ļ","ĪŗĪ±Ī¼ĻĪæĻĪ±","ĪŗĪ±Ī¼ĻĪæĻĪµĻ","ĪŗĪ±Ī¼ĻĪæĻĪ·","ĪŗĪ±Ī¼ĻĪæĻĪ·Ī½","ĪŗĪ±Ī¼ĻĪæĻĪ·Ļ","ĪŗĪ±Ī¼ĻĪæĻĪæ","ĪŗĪ±Ī¼ĻĪæĻĪæĪ¹","ĪŗĪ±Ī¼ĻĪæĻĪæĪ½","ĪŗĪ±Ī¼ĻĪæĻĪæĻ","ĪŗĪ±Ī¼ĻĪæĻĪæĻ
","ĪŗĪ±Ī¼ĻĪæĻĪæĻ
Ļ","ĪŗĪ±Ī¼ĻĪæĻĻĪ½","ĪŗĪ±Ī½ĪµĪ¹Ļ","ĪŗĪ±Ī½ĪµĪ½","ĪŗĪ±Ī½ĪµĪ½Ī±","ĪŗĪ±Ī½ĪµĪ½Ī±Ī½","ĪŗĪ±Ī½ĪµĪ½Ī±Ļ","ĪŗĪ±Ī½ĪµĪ½ĪæĻ","ĪŗĪ±ĻĪæĪ¹Ī±","ĪŗĪ±ĻĪæĪ¹Ī±Ī½","ĪŗĪ±ĻĪæĪ¹Ī±Ļ","ĪŗĪ±ĻĪæĪ¹ĪµĻ","ĪŗĪ±ĻĪæĪ¹Īæ","ĪŗĪ±ĻĪæĪ¹ĪæĪ¹","ĪŗĪ±ĻĪæĪ¹ĪæĪ½","ĪŗĪ±ĻĪæĪ¹ĪæĻ","ĪŗĪ±ĻĪæĪ¹ĪæĻ
","ĪŗĪ±ĻĪæĪ¹ĪæĻ
Ļ","ĪŗĪ±ĻĪæĪ¹ĻĪ½","ĪŗĪ±ĻĪæĻĪµ","ĪŗĪ±ĻĪæĻ
","ĪŗĪ±ĻĻĻ","ĪŗĪ±Ļ","ĪŗĪ±ĻĪ¬","ĪŗĪ±ĻĪ±","ĪŗĪ±ĻĪ¹","ĪŗĪ±ĻĪ¹ĻĪ¹","ĪŗĪ±ĻĪæĻĪ¹Ī½","ĪŗĪ±ĻĻ","ĪŗĪ±Ļį½°","ĪŗĪ±į½¶","ĪŗĪ¹","ĪŗĪ¹ĪæĪ»Ī±Ļ","ĪŗĪ»Ļ","ĪŗĪæĪ½ĻĪ±","ĪŗĻĪ»","ĪŗĻ
ĻĪ¹ĻĻ","Īŗį¼Ī½","Īŗį¼Ī½","Ī»Ī¹Ī³Ī±ĪŗĪ¹","Ī»Ī¹Ī³Īæ","Ī»Ī¹Ī³ĻĻĪµĻĪæ","Ī»ĪæĪ³Ļ","Ī»ĪæĪ¹ĻĪ±","Ī»ĪæĪ¹ĻĪæĪ½","Ī¼ĪĪ½","Ī¼ĪĻĪ±","Ī¼Ī®","Ī¼Ī®ĻĪµ","Ī¼ĪÆĪ±","Ī¼Ī±","Ī¼Ī±Ī¶Ī¹","Ī¼Ī±ĪŗĪ±ĻĪ¹","Ī¼Ī±ĪŗĻĻ
Ī±","Ī¼Ī±Ī»Ī¹ĻĻĪ±","Ī¼Ī±Ī»Ī»ĪæĪ½","Ī¼Ī±Ļ","Ī¼Īµ","Ī¼ĪµĪø","Ī¼ĪµĪøĪ±Ļ
ĻĪ¹Īæ","Ī¼ĪµĪ¹ĪæĪ½","Ī¼ĪµĪ»ĪµĪ¹","Ī¼ĪµĪ»Ī»ĪµĻĪ±Ī¹","Ī¼ĪµĪ¼Ī¹Ī±Ļ","Ī¼ĪµĪ½","Ī¼ĪµĻĪ¹ĪŗĪ±","Ī¼ĪµĻĪ¹ĪŗĪµĻ","Ī¼ĪµĻĪ¹ĪŗĪæĪ¹","Ī¼ĪµĻĪ¹ĪŗĪæĻ
Ļ","Ī¼ĪµĻĪ¹ĪŗĻĪ½","Ī¼ĪµĻĪ±","Ī¼ĪµĻ","Ī¼ĪµĻĪ¬","Ī¼ĪµĻĪ±","Ī¼ĪµĻĪ±Ī¾Ļ
","Ī¼ĪµĻį½°","Ī¼ĪµĻĻĪ¹","Ī¼Ī·","Ī¼Ī·Ī½","Ī¼Ī·ĻĻĻ","Ī¼Ī·ĻĪµ","Ī¼Ī·āĪµ","Ī¼Ī¹Ī¬","Ī¼Ī¹Ī±","Ī¼Ī¹Ī±Ī½","Ī¼Ī¹Ī±Ļ","Ī¼ĪæĪ»Ī¹Ļ","Ī¼ĪæĪ»ĪæĪ½ĪæĻĪ¹","Ī¼ĪæĪ½Ī±ĻĪ±","Ī¼ĪæĪ½ĪµĻ","Ī¼ĪæĪ½Ī·","Ī¼ĪæĪ½Ī·Ī½","Ī¼ĪæĪ½Ī·Ļ","Ī¼ĪæĪ½Īæ","Ī¼ĪæĪ½ĪæĪ¹","Ī¼ĪæĪ½ĪæĪ¼Ī¹Ī±Ļ","Ī¼ĪæĪ½ĪæĻ","Ī¼ĪæĪ½ĪæĻ
","Ī¼ĪæĪ½ĪæĻ
Ļ","Ī¼ĪæĪ½ĻĪ½","Ī¼ĪæĻ
","Ī¼ĻĪæĻĪµĪ¹","Ī¼ĻĪæĻĪæĻ
Ī½","Ī¼ĻĻĪ±Ī²Īæ","Ī¼ĻĻĪæĻ","Ī¼į¼Ī½","Ī¼į½²Ī½","Ī¼į½“","Ī¼į½“Ī½","Ī½Ī±","Ī½Ī±Ī¹","Ī½ĻĻĪ¹Ļ","Ī¾Ī±Ī½Ī±","Ī¾Ī±ĻĪ½Ī¹ĪŗĪ±","Īæ","ĪæĪ¹","ĪæĪ»Ī±","ĪæĪ»ĪµĻ","ĪæĪ»Ī·","ĪæĪ»Ī·Ī½","ĪæĪ»Ī·Ļ","ĪæĪ»Īæ","ĪæĪ»ĪæĪ³Ļ
ĻĪ±","ĪæĪ»ĪæĪ¹","ĪæĪ»ĪæĪ½","ĪæĪ»ĪæĪ½ĪµĪ½","ĪæĪ»ĪæĻ","ĪæĪ»ĪæĻĪµĪ»Ī±","ĪæĪ»ĪæĻ
","ĪæĪ»ĪæĻ
Ļ","ĪæĪ»ĻĪ½","ĪæĪ»ĻĻ","ĪæĪ»ĻĻāĪ¹ĪæĪ»ĪæĻ
","ĪæĪ¼ĻĻ","ĪæĪ¼ĻĻ","ĪæĻĪæĪ¹Ī±","ĪæĻĪæĪ¹Ī±Ī½","ĪæĻĪæĪ¹Ī±Ī½āĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹Ī±Ļ","ĪæĻĪæĪ¹Ī±ĻāĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹Ī±āĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹ĪµĻ","ĪæĻĪæĪ¹ĪµĻāĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹Īæ","ĪæĻĪæĪ¹ĪæĪ¹","ĪæĻĪæĪ¹ĪæĪ½","ĪæĻĪæĪ¹ĪæĪ½āĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹ĪæĻ","ĪæĻĪæĪ¹ĪæĻāĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹ĪæĻ
","ĪæĻĪæĪ¹ĪæĻ
Ļ","ĪæĻĪæĪ¹ĪæĻ
ĻāĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹ĪæĻ
āĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹ĪæāĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹ĻĪ½","ĪæĻĪæĪ¹ĻĪ½āĪ·ĻĪæĻĪµ","ĪæĻĪæĪ¹āĪ·ĻĪæĻĪµ","ĪæĻĪæĻĪµ","ĪæĻĪæĻĪµāĪ·ĻĪæĻĪµ","ĪæĻĪæĻ
","ĪæĻĪæĻ
āĪ·ĻĪæĻĪµ","ĪæĻĻĻ","ĪæĻĻĻ","ĪæĻĪ¹ĻĪ¼ĪµĪ½Ī±","ĪæĻĪ¹ĻĪ¼ĪµĪ½ĪµĻ","ĪæĻĪ¹ĻĪ¼ĪµĪ½ĻĪ½","ĪæĻĪ¹ĻĪ¼ĪµĪ½ĻĻ","ĪæĻĪ±","ĪæĻĪ±āĪ·ĻĪæĻĪµ","ĪæĻĪµĻ","ĪæĻĪµĻāĪ·ĻĪæĻĪµ","ĪæĻĪ·","ĪæĻĪ·Ī½","ĪæĻĪ·Ī½āĪ·ĻĪæĻĪµ","ĪæĻĪ·Ļ","ĪæĻĪ·ĻāĪ·ĻĪæĻĪµ","ĪæĻĪ·āĪ·ĻĪæĻĪµ","ĪæĻĪæ","ĪæĻĪæĪ¹","ĪæĻĪæĪ¹āĪ·ĻĪæĻĪµ","ĪæĻĪæĪ½","ĪæĻĪæĪ½āĪ·ĻĪæĻĪµ","ĪæĻĪæĻ","ĪæĻĪæĻāĪ·ĻĪæĻĪµ","ĪæĻĪæĻ
","ĪæĻĪæĻ
Ļ","ĪæĻĪæĻ
ĻāĪ·ĻĪæĻĪµ","ĪæĻĪæĻ
āĪ·ĻĪæĻĪµ","ĪæĻĪæāĪ·ĻĪæĻĪµ","ĪæĻĻĪ½","ĪæĻĻĪ½āĪ·ĻĪæĻĪµ","ĪæĻĪ±Ī½","ĪæĻĪ¹","ĪæĻĪ¹āĪ·ĻĪæĻĪµ","ĪæĻĪæĻ
","ĪæĻ
","ĪæĻ
ĻĪµ","ĪæĻ
āĪµ","ĪæĻĪ¹","Īæį¼±","Īæį¼³","Īæį¼·Ļ","Īæį½","Īæį½Ī“","Īæį½Ī“Ī","Īæį½Ī“ĪµĪÆĻ","Īæį½Ī“Īµį½¶Ļ","Īæį½Ī“į½²","Īæį½Ī“į½²Ī½","Īæį½Īŗ","Īæį½Ļ","Īæį½Ļį½¶","Īæį½Ļ","Īæį½ĻĪµ","Īæį½ĻĻ","Īæį½ĻĻĻ","Īæį½ĻĻĻ","Īæį½Ī½","Īæį½","Īæį½ĻĪæĻ","Īæį½ĻĪæĻ","ĻĪ±Ī»Ī¹","ĻĪ±Ī½ĻĪæĻĪµ","ĻĪ±Ī½ĻĪæĻ
","ĻĪ±Ī½ĻĻĻ","ĻĪ±Ļ","ĻĪ±ĻĪ¬","ĻĪ±ĻĪ±","ĻĪ±Ļį½°","ĻĪµĻĪÆ","ĻĪµĻĪ±","ĻĪµĻĪ¹","ĻĪµĻĪ¹ĻĪæĻ
","ĻĪµĻĪ¹ĻĻĪæĻĪµĻĪæ","ĻĪµĻĻĪ¹","ĻĪµĻĻ
ĻĪ¹","ĻĪµĻį½¶","ĻĪ¹Ī±","ĻĪ¹ĪøĪ±Ī½ĪæĪ½","ĻĪ¹Īæ","ĻĪ¹ĻĻ","ĻĪ»Ī±Ī¹","ĻĪ»ĪµĪæĪ½","ĻĪ»Ī·Ī½","ĻĪæĪ¹Ī±","ĻĪæĪ¹Ī±Ī½","ĻĪæĪ¹Ī±Ļ","ĻĪæĪ¹ĪµĻ","ĻĪæĪ¹ĪµĻ","ĻĪæĪ¹Īæ","ĻĪæĪ¹ĪæĪ¹","ĻĪæĪ¹ĪæĪ½","ĻĪæĪ¹ĪæĻ","ĻĪæĪ¹ĪæĻ","ĻĪæĪ¹ĪæĻ
","ĻĪæĪ¹ĪæĻ
Ļ","ĻĪæĪ¹ĪæĻ
Ļ","ĻĪæĪ¹ĻĪ½","ĻĪæĪ»Ļ
","ĻĪæĻĪµĻ","ĻĪæĻĪ·","ĻĪæĻĪ·Ī½","ĻĪæĻĪ·Ļ","ĻĪæĻĪæĪ¹","ĻĪæĻĪæĻ","ĻĪæĻĪæĻ
Ļ","ĻĪæĻĪµ","ĻĪæĻ
","ĻĪæĻ
ĪøĪµ","ĻĪæĻ
ĪøĪµĪ½Ī±","ĻĪæįæ¦","ĻĻĪµĻĪµĪ¹","ĻĻĪ¹Ī½","ĻĻĪæ","ĻĻĪæĪŗĪµĪ¹Ī¼ĪµĪ½ĪæĻ
","ĻĻĪæĪŗĪµĪ¹ĻĪ±Ī¹","ĻĻĪæĻĪµĻĻĪ¹","ĻĻĪæĻ","ĻĻĪæĻ","ĻĻĪæĻĪæĻ
","ĻĻĪæĻĪøĪµĻ","ĻĻĪæĻĻĪµĻ","ĻĻĻĻĻ
ĻĪµĻĪ±","ĻĻĻĻ","ĻĻį½ø","ĻĻį½øĻ","ĻĻĻ","ĻĻĻ","ĻĪ±Ī½","ĻĪ±Ļ","ĻĪµ","ĻĪµĪ¹Ļ","ĻĪ·Ī¼ĪµĻĪ±","ĻĪ¹Ī³Ī±","ĻĪæĻ
","ĻĻĪ±","ĻĻĪ·","ĻĻĪ·Ī½","ĻĻĪ·Ļ","ĻĻĪ¹Ļ","ĻĻĪæ","ĻĻĪæĪ½","ĻĻĪæĻ
","ĻĻĪæĻ
Ļ","ĻĻĻĪ½","ĻĻ
Ī³ĻĻĪæĪ½ĻĻ","ĻĻ
Ī½","ĻĻ
Ī½Ī±Ī¼Ī±","ĻĻ
Ī½ĪµĻĻĻ","ĻĻ
Ī½Ī·ĪøĻĻ","ĻĻ
ĻĪ½Ī±","ĻĻ
ĻĪ½Ī±Ļ","ĻĻ
ĻĪ½ĪµĻ","ĻĻ
ĻĪ½Ī·","ĻĻ
ĻĪ½Ī·Ī½","ĻĻ
ĻĪ½Ī·Ļ","ĻĻ
ĻĪ½Īæ","ĻĻ
ĻĪ½ĪæĪ¹","ĻĻ
ĻĪ½ĪæĪ½","ĻĻ
ĻĪ½ĪæĻ","ĻĻ
ĻĪ½ĪæĻ
","ĻĻ
ĻĪ½ĪæĻ
Ļ","ĻĻ
ĻĪ½ĻĪ½","ĻĻ
ĻĪ½ĻĻ","ĻĻĪµāĪæĪ½","ĻĻĻĻĪ±","ĻĻĻ","ĻĻ","ĻĻĪ½","Ļį½øĻ","Ļį½ŗ","Ļį½ŗĪ½","ĻĪ¬","ĻĪ®Ī½","ĻĪÆ","ĻĪÆĻ","ĻĪÆĻ","ĻĪ±","ĻĪ±Ļ
ĻĪ±","ĻĪ±Ļ
ĻĪµĻ","ĻĪ±Ļ
ĻĪ·","ĻĪ±Ļ
ĻĪ·Ī½","ĻĪ±Ļ
ĻĪ·Ļ","ĻĪ±Ļ
ĻĪæ,ĻĪ±Ļ
ĻĪæĪ½","ĻĪ±Ļ
ĻĪæĻ","ĻĪ±Ļ
ĻĪæĻ
","ĻĪ±Ļ
ĻĻĪ½","ĻĪ±ĻĪ±","ĻĪ±ĻĪ±ĻĪµ","ĻĪ±įæĻ","ĻĪ±āĪµ","ĻĪµ","ĻĪµĪ»Ī¹ĪŗĪ±","ĻĪµĪ»Ī¹ĪŗĻĻ","ĻĪµĻ","ĻĪµĻĪæĪ¹Ī±","ĻĪµĻĪæĪ¹Ī±Ī½","ĻĪµĻĪæĪ¹Ī±Ļ","ĻĪµĻĪæĪ¹ĪµĻ","ĻĪµĻĪæĪ¹Īæ","ĻĪµĻĪæĪ¹ĪæĪ¹","ĻĪµĻĪæĪ¹ĪæĪ½","ĻĪµĻĪæĪ¹ĪæĻ","ĻĪµĻĪæĪ¹ĪæĻ
","ĻĪµĻĪæĪ¹ĪæĻ
Ļ","ĻĪµĻĪæĪ¹ĻĪ½","ĻĪ·","ĻĪ·Ī½","ĻĪ·Ļ","ĻĪ·Ļ","ĻĪ¹","ĻĪ¹Ī½Ī±","ĻĪ¹ĻĪæĻĪ±","ĻĪ¹ĻĪæĻĪµ","ĻĪ¹Ļ","ĻĪ¹Ļ","ĻĪæ","ĻĪæĪÆ","ĻĪæĪ¹","ĻĪæĪ¹Īæįæ¦ĻĪæĻ","ĻĪæĪ¹Īæįæ¦ĻĪæĻ","ĻĪæĪ½","ĻĪæĻ","ĻĪæĻĪ±","ĻĪæĻĪµĻ","ĻĪæĻĪ·","ĻĪæĻĪ·Ī½","ĻĪæĻĪ·Ļ","ĻĪæĻĪæ","ĻĪæĻĪæĪ¹","ĻĪæĻĪæĪ½","ĻĪæĻĪæĻ","ĻĪæĻĪæĻ
","ĻĪæĻĪæĻ
Ļ","ĻĪæĻĻĪ½","ĻĪæĻĪµ","ĻĪæĻ
","ĻĪæĻ
Ī»Ī±ĻĪ¹ĻĻĪæ","ĻĪæĻ
Ī»Ī±ĻĪ¹ĻĻĪæĪ½","ĻĪæĻ
Ļ","ĻĪæĻ
ĻĪ±","ĻĪæĻ
ĻĪµĻ","ĻĪæĻ
ĻĪ·","ĻĪæĻ
ĻĪ·Ī½","ĻĪæĻ
ĻĪ·Ļ","ĻĪæĻ
ĻĪæ","ĻĪæĻ
ĻĪæĪ¹","ĻĪæĻ
ĻĪæĪ¹Ļ","ĻĪæĻ
ĻĪæĪ½","ĻĪæĻ
ĻĪæĻ","ĻĪæĻ
ĻĪæĻ
","ĻĪæĻ
ĻĪæĻ
Ļ","ĻĪæĻ
ĻĻĪ½","ĻĪæĻĻ","ĻĪæį½ŗĻ","ĻĪæįæĻ","ĻĪæįæ¦","ĻĻ
ĻĪæĪ½","ĻĻĪ½","ĻĻĻĪ±","ĻĻ","ĻĻĪ½","ĻĻĻĪµ","Ļį½°","Ļį½°Ļ","Ļį½“Ī½","Ļį½ø","Ļį½øĪ½","ĻįæĻ","ĻįæĻ","Ļįæ","Ļįæ¶Ī½","Ļįæ·","Ļ
Ļ","Ļ
ĻĪµĻ","Ļ
ĻĪæ","Ļ
ĻĪæĻĪ·","Ļ
ĻĪæĻĪ¹Ī½","Ļ
ĻĻ","Ļ
ĻĻĪµĻĪ±","ĻĪµĻĪæĻ","ĻĪ±Ī¼Ī·Ī»Ī±","ĻĪøĪµĻ","ĻĻĪµĻ","ĻĻĻĪ¹Ļ","ĻĻĻĪ¹ĻĻĪ±","ĻĪ·Ī»Ī±","Ļ","ĻĻĪ±Ī¹Ī±","ĻĻ","ĻĻ","ĻĻĪ±Ī½","ĻĻĪæĻĪæĻ
","ĻĻĻĪæĻ
","ĻĻĻĪµ","ĻĻĻĪæĻĪæ","ĻĻ","į¼Ī»Ī»'","į¼Ī»Ī»Ī¬","į¼Ī»Ī»į½°","į¼Ī»Ī»ā","į¼Ļ","į¼ĻĻ","į¼Ļį½ø","į¼Ļ","į¼Ī½","į¼","į¼Ī»Ī»ĪæĻ","į¼Ī»Ī»ĪæĻ","į¼Ī½","į¼ĻĪ±","į¼
Ī¼Ī±","į¼Ī¬Ī½","į¼Ī³Ļ","į¼Ī³į½¼","į¼Īŗ","į¼Ī¼ĻĻ","į¼Ī¼į½øĻ","į¼Ī½","į¼Ī¾","į¼ĻĪÆ","į¼ĻĪµį½¶","į¼Ļį½¶","į¼ĻĻĪ¹","į¼Ļ","į¼į½°Ī½","į¼Ī±Ļ
ĻĪæįæ¦","į¼ĻĪ¹","į¼”","į¼¢","į¼£","į¼¤","į¼„","į¼§Ļ","į¼µĪ½Ī±","į½","į½","į½Ī½","į½Ļ","į½
","į½
Ī“Īµ","į½
ĪøĪµĪ½","į½
ĻĪµĻ","į½
Ļ","į½
Ļ","į½
ĻĻĪ¹Ļ","į½
ĻĻĪ¹Ļ","į½
ĻĪµ","į½
ĻĪ¹","į½Ī¼ĻĻ","į½Ļ","į½ĻĪĻ","į½ĻĻ","į½Ļį½²Ļ","į½Ļį½ø","į½”Ļ","į½”Ļ","į½„Ļ","į½„ĻĻĪµ","į½¦","į¾§","āĪ±","āĪµ","āĪµĪ¹Ī½Ī±","āĪµĪ½","āĪµĪ¾Ī¹Ī±","āĪ·ĪøĪµĪ½","āĪ·Ī»Ī±āĪ·","āĪ¹","āĪ¹Ī±","āĪ¹Ī±ĻĪŗĻĻ","āĪ¹ĪŗĪ±","āĪ¹ĪŗĪæ","āĪ¹ĪŗĪæĪ¹","āĪ¹ĪŗĪæĻ","āĪ¹ĪŗĪæĻ
","āĪ¹ĪŗĪæĻ
Ļ","āĪ¹ĪæĪ»ĪæĻ
","āĪ¹ĻĪ»Ī±","āĪ¹ĻĻĻ"],"gu":["ąŖ
ąŖąŖą«","ąŖ
ąŖąŖ¦ąŖ°","ąŖ
ąŖ„ąŖµąŖ¾","ąŖ
ąŖØą«","ąŖ
ąŖ®ąŖØą«","ąŖ
ąŖ®ąŖ¾ąŖ°ą«ąŖ","ąŖ
ąŖ®ą«","ąŖ
ąŖ¹ą«ąŖ","ąŖ","ąŖąŖąŖ³","ąŖąŖ„ą«","ąŖąŖØą«ąŖ","ąŖąŖØą«","ąŖąŖŖąŖ£ąŖØą«","ąŖąŖŖąŖ£ą«ąŖ","ąŖąŖŖąŖ£ą«","ąŖąŖŖą«","ąŖąŖ°","ąŖąŖµą«","ąŖąŖµą«","ąŖąŖŖąŖ°","ąŖąŖąŖ¾","ąŖąŖąŖą«","ąŖąŖą«ąŖ","ąŖ","ąŖąŖ","ąŖąŖØ","ąŖąŖØąŖ¾","ąŖąŖØąŖ¾ąŖ","ąŖąŖØą«","ąŖąŖØą«ąŖ","ąŖąŖØą«","ąŖąŖØą«","ąŖąŖ®","ąŖąŖµąŖ¾","ąŖąŖµąŖ¾ąŖ","ąŖąŖµą«","ąŖąŖµą«ąŖ","ąŖąŖµą«","ąŖąŖą«ąŖ","ąŖąŖąŖąŖ","ąŖąŖ","ąŖąŖÆą«ąŖ","ąŖąŖÆą«","ąŖąŖ°ąŖ¤ąŖ¾ąŖ","ąŖąŖ°ąŖµą«ąŖ","ąŖąŖ°ą«","ąŖąŖ°ą«ąŖ","ąŖąŖ°ą«ąŖ","ąŖąŖ°ą«","ąŖąŖ°ą«ąŖ²ą«ąŖ","ąŖąŖ°ą«ąŖÆąŖ¾","ąŖąŖ°ą«ąŖÆąŖ¾ąŖ","ąŖąŖ°ą«ąŖÆą«ąŖ","ąŖąŖ°ą«ąŖÆą«","ąŖąŖ¾ąŖąŖ","ąŖą«","ąŖą«ąŖąŖ²ą«ąŖ","ąŖą«ąŖ®","ąŖą«ąŖµą«","ąŖą«ąŖµą«ąŖ","ąŖą«ąŖ","ąŖą«ąŖąŖ","ąŖą«ąŖ£","ąŖą«ąŖ£ą«","ąŖą«ąŖØą«","ąŖą«ąŖÆąŖ¾ąŖ","ąŖą«ąŖÆąŖ¾ąŖ°ą«","ąŖą«ąŖ¬","ąŖąŖ","ąŖąŖÆąŖ¾","ąŖąŖÆąŖ¾ąŖ","ąŖąŖÆą«ąŖ","ąŖąŖÆą«","ąŖąŖ£ą«ąŖ","ąŖ","ąŖąŖ¤ąŖ¾ąŖ","ąŖą«ąŖ","ąŖą«ąŖ","ąŖą«","ąŖą«ąŖ","ąŖą«","ąŖ","ąŖąŖ¾ąŖÆ","ąŖą«","ąŖą«","ąŖą«ąŖąŖ²ą«ąŖ","ąŖą«ąŖØą«","ąŖą«ąŖ®","ąŖą«ąŖµą«","ąŖą«ąŖµą«ąŖ","ąŖą«ąŖµą«","ąŖą«","ąŖą«ąŖąŖ","ąŖą«ąŖÆąŖ¾ąŖ","ąŖą«ąŖÆąŖ¾ąŖ°ą«","ąŖąŖ¾ąŖą«ąŖ","ąŖ¤ąŖØą«","ąŖ¤ąŖ®ąŖØą«","ąŖ¤ąŖ®ąŖ¾ąŖ°ą«ąŖ","ąŖ¤ąŖ®ą«","ąŖ¤ąŖ¾","ąŖ¤ąŖ¾ąŖ°ąŖ¾ąŖ„ą«","ąŖ¤ąŖ¾ąŖ°ąŖ¾ąŖ®ąŖ¾ąŖ","ąŖ¤ąŖ¾ąŖ°ą«ąŖ","ąŖ¤ą«ąŖ","ąŖ¤ą«","ąŖ¤ą«ąŖ","ąŖ¤ą«ąŖ","ąŖ¤ą«ąŖ£ą«","ąŖ¤ą«ąŖ„ą«","ąŖ¤ą«ąŖØąŖ¾","ąŖ¤ą«ąŖØą«","ąŖ¤ą«ąŖØą«ąŖ","ąŖ¤ą«ąŖØą«","ąŖ¤ą«ąŖ®","ąŖ¤ą«ąŖ®ąŖØą«ąŖ","ąŖ¤ą«ąŖ®ąŖØą«","ąŖ¤ą«ąŖµą«","ąŖ¤ą«ąŖµą«ąŖ","ąŖ¤ą«","ąŖ¤ą«ąŖÆąŖ¾ąŖ","ąŖ¤ą«ąŖÆąŖ¾ąŖ°ą«","ąŖ„ąŖ","ąŖ„ąŖ","ąŖ„ąŖąŖ","ąŖ„ąŖ¤ąŖ¾","ąŖ„ąŖ¤ąŖ¾ąŖ","ąŖ„ąŖ¤ą«","ąŖ„ąŖ¤ą«ąŖ","ąŖ„ąŖ¤ą«","ąŖ„ąŖÆąŖ¾","ąŖ„ąŖÆąŖ¾ąŖ","ąŖ„ąŖÆą«ąŖ","ąŖ„ąŖÆą«ąŖ²ą«ąŖ","ąŖ„ąŖÆą«","ąŖ„ąŖµą«ąŖ","ąŖ„ąŖ¾ąŖąŖ","ąŖ„ąŖ¾ąŖ","ąŖ„ąŖ¾ąŖÆ","ąŖ„ą«","ąŖ„ą«ąŖ”ą«ąŖ","ąŖ¦ąŖ°ą«ąŖ","ąŖØ","ąŖØąŖ","ąŖØąŖ.","ąŖØąŖ„ą«","ąŖØąŖ¹ąŖæ","ąŖØąŖ¹ą«","ąŖØąŖ¹ą«ąŖ","ąŖØąŖ¾","ąŖØą«","ąŖØą«ąŖą«","ąŖØą«ąŖ","ąŖØą«","ąŖØą«","ąŖŖąŖą«","ąŖŖąŖ£","ąŖŖąŖ°","ąŖŖąŖ°ąŖąŖ¤ą«","ąŖŖąŖ¹ą«ąŖ²ąŖ¾ąŖ","ąŖŖąŖ¾ąŖąŖ³","ąŖŖąŖ¾ąŖøą«","ąŖŖą«ąŖ¤ąŖ¾ąŖØą«ąŖ","ąŖŖą«ąŖ°ąŖ¤ą«ąŖÆą«ąŖ","ąŖ«ąŖą«ąŖ¤","ąŖ«ąŖ°ą«","ąŖ«ąŖ°ą«ąŖ„ą«","ąŖ¬ąŖąŖØą«","ąŖ¬ąŖ§ąŖ¾","ąŖ¬ąŖ§ą«ąŖ","ąŖ¬ąŖØą«","ąŖ¬ąŖ¹ąŖ¾ąŖ°","ąŖ¬ąŖ¹ą«","ąŖ¬ąŖ¾ąŖ¦","ąŖ¬ą«","ąŖ®ąŖØą«","ąŖ®ąŖ¾","ąŖ®ąŖ¾ąŖ","ąŖ®ąŖ¾ąŖą«","ąŖ®ąŖ¾ąŖ¤ą«ąŖ°","ąŖ®ąŖ¾ąŖ°ą«ąŖ","ąŖ®ą«","ąŖ®ą«ąŖąŖµą«ąŖ","ąŖ®ą«ąŖą«","ąŖ®ą«ąŖą«ąŖÆąŖ¾","ąŖ®ą«ąŖą«ąŖÆąŖ¾ąŖ","ąŖ®ą«ąŖą«ąŖÆą«ąŖ","ąŖ®ą«ąŖ","ąŖ°ąŖ¹ą«","ąŖ°ąŖ¹ą«","ąŖ°ąŖ¹ą«ąŖµą«ąŖ","ąŖ°ąŖ¹ą«ąŖÆąŖ¾","ąŖ°ąŖ¹ą«ąŖÆąŖ¾ąŖ","ąŖ°ąŖ¹ą«ąŖÆą«","ąŖ°ą«ąŖ¤ą«","ąŖ°ą«.","ąŖ°ą«ąŖ¾","ąŖ²ą«ąŖ¤ąŖ¾","ąŖ²ą«ąŖ¤ą«ąŖ","ąŖ²ą«ąŖµąŖ¾","ąŖµąŖą«ąŖ°ą«","ąŖµąŖ§ą«","ąŖ¶ąŖą«","ąŖ¶ąŖ¾","ąŖ¶ą«ąŖ","ąŖøąŖ°ąŖą«ąŖ","ąŖøąŖ¾ąŖ®ą«","ąŖøą«ąŖ§ą«","ąŖ¹ąŖ¤ąŖ¾","ąŖ¹ąŖ¤ąŖ¾ąŖ","ąŖ¹ąŖ¤ą«","ąŖ¹ąŖ¤ą«ąŖ","ąŖ¹ąŖµą«","ąŖ¹ąŖ¶ą«","ąŖ¹ąŖ¶ą«","ąŖ¹ąŖ¾","ąŖ¹ą«ąŖ","ąŖ¹ą«","ąŖ¹ą«ąŖ","ąŖ¹ą«ąŖąŖ¶","ąŖ¹ą«ąŖąŖ¶ą«ąŖ","ąŖ¹ą«ąŖÆ","ąŖ¹ą«ąŖµąŖ¾"],"ha":["a","amma","ba","ban","ce","cikin","da","don","ga","in","ina","ita","ji","ka","ko","kuma","lokacin","ma","mai","na","ne","ni","sai","shi","su","suka","sun","ta","tafi","take","tana","wani","wannan","wata","ya","yake","yana","yi","za"],"he":["×××","××","××××","×××Ŗ×","×××Ŗ×","×××Ŗ×","×××Ŗ×","×××Ŗ×","×××Ŗ×","×××Ŗ× ×","××","×××Ø","×××Ø××Ŗ","×××Ø×","×××Ø×××","×××Ø××","×××Ø×Ŗ","××","××××","×××","×××","××פ×","×××Ŗ×","×××Ŗ×","×××Ŗ×","×××Ŗ×","×××Ŗ××","×××Ŗ××","×××Ŗ×","×××Ŗ×","×××Ŗ× ×","××","××","×××","×××","××","×× ×× ×","×× ×","××”","××£","×צ×","×ש×Ø","××Ŗ","××Ŗ×","××Ŗ××","××Ŗ××","××Ŗ×","××Ŗ×","×××××××××","×××צע","×××צע××Ŗ","××××","×××","×××","×××××","××ק××ש××","××Ø×","×ש×××","×שע×ש","××Ŗ××","××","××Ø×","×××","×××","×××","××××","×××Ŗ×","×××Ŗ×","××","××","×× ×","××”×××ש×××××","××Ø×","×××××","×××Ŗ","×××Ŗ","××","×××Ŗ","××××","××××","×××××","×××Ŗ×Ø×××","××××","×××××","××××××Ŗ","××××××","×××","××××","××××","×ש","×××","××ש×Ø","××××","××××","×××","××","××צ×","××","×××","××","×××","×××","××","×פ×","×ש","××","×××","××××××Ŗ××××Ŗ","×××","××××","××","×××××Ŗ","×××","×××","××","××","×××","×××","×××","××××","×××¢××","××ק××ש××","×××Ø××Ŗ","×× ×","××¢××Ø","××¢×××","×פ×××","××¤× ×","×××","×××××Ø×","××××××”×××","××××","×××פ×","××××","×××¢×","××××¢","××","×××××","×××","×××ׄ","××","××××","××××××","××××","××","×× ××","××”×××","××¢×","××¢×××","××¢×","×צ×","×ק××××","××Ŗ××Ŗ","××Ŗ×","× ××","× ××Ø","× ×","×¢×","×¢×","×¢×","×¢××","×¢×××","×¢××××","×¢××××","×¢×××","×¢×××","×¢××××","×¢××× ×","×¢×","עצ××","עצ×××","עצ×××","עצ××","עצ××","עצ××","עצ××","עצ×× ×","פ×","×Øק","ש××","ש×","ש××","ש×××","ש×××","ש××","ש××","ש××","ש×××","ש×××","ש×××","ש×× ×","ש×","×Ŗ×××","×Ŗ××Ŗ"],"hi":["ą¤
ą¤ą¤¦ą¤°","ą¤
ą¤¤","ą¤
ą¤¦ą¤æ","ą¤
ą¤Ŗ","ą¤
ą¤Ŗą¤Øą¤¾","ą¤
ą¤Ŗą¤Øą¤æ","ą¤
ą¤Ŗą¤Øą„","ą¤
ą¤Ŗą¤Øą„","ą¤
ą¤ą¤æ","ą¤
ą¤ą„","ą¤ą¤¦ą¤æ","ą¤ą¤Ŗ","ą¤ą¤ą¤¹ą¤æą¤","ą¤ą¤ą¤¹ą„ą¤","ą¤ą¤ą¤¹ą„ą¤","ą¤ą¤¤ą¤Æą¤¾ą¤¦ą¤æ","ą¤ą¤¤ą„ą¤Æą¤¾ą¤¦ą¤æ","ą¤ą¤Ø","ą¤ą¤Øą¤ą¤¾","ą¤ą¤Øą„ą¤¹ą„ą¤","ą¤ą¤Øą„ą¤¹ą„ą¤","ą¤ą¤Øą„ą¤¹ą„ą¤","ą¤ą¤ø","ą¤ą¤øą¤ą¤¾","ą¤ą¤øą¤ą¤æ","ą¤ą¤øą¤ą„","ą¤ą¤øą¤ą„","ą¤ą¤øą¤®ą„ą¤","ą¤ą¤øą¤æ","ą¤ą¤øą„","ą¤ą¤øą„","ą¤ą¤ą¤¹ą¤æą¤","ą¤ą¤ą¤¹ą„ą¤","ą¤ą¤ą¤¹ą„ą¤","ą¤ą¤Ø","ą¤ą¤Øą¤ą¤¾","ą¤ą¤Øą¤ą¤æ","ą¤ą¤Øą¤ą„","ą¤ą¤Øą¤ą„","ą¤ą¤Øą¤ą„","ą¤ą¤Øą„ą¤¹ą„ą¤","ą¤ą¤Øą„ą¤¹ą„ą¤","ą¤ą¤Øą„ą¤¹ą„ą¤","ą¤ą¤ø","ą¤ą¤øą¤ą„","ą¤ą¤øą¤æ","ą¤ą¤øą„","ą¤ą¤øą„","ą¤ą¤","ą¤ą¤µą¤","ą¤ą¤ø","ą¤ą¤øą„","ą¤ą¤øą„","ą¤ą¤°","ą¤ą¤°","ą¤ą¤","ą¤ą¤","ą¤ą¤°","ą¤ą¤°ą¤¤ą¤¾","ą¤ą¤°ą¤¤ą„","ą¤ą¤°ą¤Øą¤¾","ą¤ą¤°ą¤Øą„","ą¤ą¤°ą„ą¤","ą¤ą¤¹ą¤¤ą„","ą¤ą¤¹ą¤¾","ą¤ą¤¾","ą¤ą¤¾ą¤«ą¤æ","ą¤ą¤¾ą„ą„","ą¤ą¤æ","ą¤ą¤æą¤ą¤¹ą„ą¤","ą¤ą¤æą¤ą¤¹ą„ą¤","ą¤ą¤æą¤¤ą¤Øą¤¾","ą¤ą¤æą¤Øą„ą¤¹ą„ą¤","ą¤ą¤æą¤Øą„ą¤¹ą„ą¤","ą¤ą¤æą¤Æą¤¾","ą¤ą¤æą¤°","ą¤ą¤æą¤ø","ą¤ą¤æą¤øą¤æ","ą¤ą¤æą¤øą„","ą¤ą¤æą¤øą„","ą¤ą„","ą¤ą„ą¤","ą¤ą„ą¤²","ą¤ą„","ą¤ą„","ą¤ą„ą¤","ą¤ą„ą¤","ą¤ą„ą¤Ø","ą¤ą„ą¤Øą¤øą¤¾","ą¤ą„ą¤Ø","ą¤ą„ą¤Øą¤øą¤¾","ą¤ą¤Æą¤¾","ą¤ą¤°","ą¤ą¤¬","ą¤ą¤¹ą¤¾ą¤","ą¤ą¤¹ą¤¾ą¤","ą¤ą¤¾","ą¤ą¤æą¤ą¤¹ą„ą¤","ą¤ą¤æą¤ą¤¹ą„ą¤","ą¤ą¤æą¤¤ą¤Øą¤¾","ą¤ą¤æą¤§ą¤°","ą¤ą¤æą¤Ø","ą¤ą¤æą¤Øą„ą¤¹ą„ą¤","ą¤ą¤æą¤Øą„ą¤¹ą„ą¤","ą¤ą¤æą¤ø","ą¤ą¤æą¤øą„","ą¤ą„ą¤§ą¤°","ą¤ą„ą¤øą¤¾","ą¤ą„ą¤øą„","ą¤ą„ą¤øą¤¾","ą¤ą„ą¤øą„","ą¤ą„","ą¤¤ą¤","ą¤¤ą¤¬","ą¤¤ą¤°ą¤¹","ą¤¤ą¤æą¤ą¤¹ą„ą¤","ą¤¤ą¤æą¤ą¤¹ą„ą¤","ą¤¤ą¤æą¤Ø","ą¤¤ą¤æą¤Øą„ą¤¹ą„ą¤","ą¤¤ą¤æą¤Øą„ą¤¹ą„ą¤","ą¤¤ą¤æą¤ø","ą¤¤ą¤æą¤øą„","ą¤¤ą„","ą¤„ą¤¾","ą¤„ą¤æ","ą¤„ą„","ą¤„ą„","ą¤¦ą¤¬ą¤¾ą¤°ą¤¾","ą¤¦ą¤µą¤¾ą¤°ą¤¾","ą¤¦ą¤æą¤Æą¤¾","ą¤¦ą„ą¤øą¤°ą¤¾","ą¤¦ą„ą¤øą¤°ą„","ą¤¦ą„ą¤øą¤°ą„","ą¤¦ą„","ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾","ą¤Ø","ą¤Øą¤¹ą¤æą¤","ą¤Øą¤¹ą„ą¤","ą¤Øą¤¾","ą¤Øą¤æą¤ą„","ą¤Øą¤æą¤¹ą¤¾ą¤Æą¤¤","ą¤Øą„ą¤ą„","ą¤Øą„","ą¤Ŗą¤°","ą¤Ŗą¤¹ą¤²ą„","ą¤Ŗą„ą¤°ą¤¾","ą¤Ŗą„ą¤°ą¤¾","ą¤Ŗą„","ą¤«ą¤æą¤°","ą¤¬ą¤Øą¤æ","ą¤¬ą¤Øą„","ą¤¬ą¤¹ą¤æ","ą¤¬ą¤¹ą„","ą¤¬ą¤¹ą„ą¤¤","ą¤¬ą¤¾ą¤¦","ą¤¬ą¤¾ą¤²ą¤¾","ą¤¬ą¤æą¤²ą¤ą„ą¤²","ą¤ą¤æ","ą¤ą¤æą¤¤ą¤°","ą¤ą„","ą¤ą„ą¤¤ą¤°","ą¤®ą¤ą¤°","ą¤®ą¤¾ą¤Øą„","ą¤®ą„","ą¤®ą„ą¤","ą¤Æą¤¦ą¤æ","ą¤Æą¤¹","ą¤Æą¤¹ą¤¾ą¤","ą¤Æą¤¹ą¤¾ą¤","ą¤Æą¤¹ą¤æ","ą¤Æą¤¹ą„","ą¤Æą¤¾","ą¤Æą¤æą¤¹","ą¤Æą„","ą¤°ą¤ą„ą¤","ą¤°ą¤µą¤¾ą¤øą¤¾","ą¤°ą¤¹ą¤¾","ą¤°ą¤¹ą„","ą¤±ą„ą¤µą¤¾ą¤øą¤¾","ą¤²ą¤æą¤","ą¤²ą¤æą¤Æą„","ą¤²ą„ą¤ą¤æą¤Ø","ą¤µ","ą¤µą¤ą„ą¤°ą¤¹","ą¤µą¤°ą¤","ą¤µą¤°ą„ą¤","ą¤µą¤¹","ą¤µą¤¹ą¤¾ą¤","ą¤µą¤¹ą¤¾ą¤","ą¤µą¤¹ą¤æą¤","ą¤µą¤¹ą„ą¤","ą¤µą¤¾ą¤²ą„","ą¤µą„ą¤¹","ą¤µą„","ą¤µą„ą„ą¤°ą¤¹","ą¤øą¤ą¤","ą¤øą¤ą¤¤ą¤¾","ą¤øą¤ą¤¤ą„","ą¤øą¤¬ą¤øą„","ą¤øą¤ą¤æ","ą¤øą¤ą„","ą¤øą¤¾ą¤„","ą¤øą¤¾ą¤¬ą„ą¤¤","ą¤øą¤¾ą¤","ą¤øą¤¾ą¤°ą¤¾","ą¤øą„","ą¤øą„","ą¤¹ą¤æ","ą¤¹ą„","ą¤¹ą„ą¤
","ą¤¹ą„ą¤","ą¤¹ą„ą¤","ą¤¹ą„ą¤","ą¤¹ą„ą¤","ą¤¹ą„","ą¤¹ą„ą¤","ą¤¹ą„","ą¤¹ą„ą¤","ą¤¹ą„","ą¤¹ą„ą¤¤ą¤¾","ą¤¹ą„ą¤¤ą¤æ","ą¤¹ą„ą¤¤ą„","ą¤¹ą„ą¤¤ą„","ą¤¹ą„ą¤Øą¤¾","ą¤¹ą„ą¤Øą„"],"hu":["a","abba","abban","abbĆ³l","addig","ahhoz","ahogy","ahol","aki","akik","akkor","akĆ”r","alapjĆ”n","alatt","alatta","alattad","alattam","alattatok","alattuk","alattunk","alĆ”","alĆ”d","alĆ”juk","alĆ”m","alĆ”nk","alĆ”tok","alĆ³l","alĆ³la","alĆ³lad","alĆ³lam","alĆ³latok","alĆ³luk","alĆ³lunk","amely","amelybol","amelyek","amelyekben","amelyeket","amelyet","amelyik","amelynek","ami","amikor","amit","amolyan","amott","amĆg","annak","annĆ”l","arra","arrĆ³l","attĆ³l","az","aznap","azok","azokat","azokba","azokban","azokbĆ³l","azokhoz","azokig","azokkal","azokkĆ”","azoknak","azoknĆ”l","azokon","azokra","azokrĆ³l","azoktĆ³l","azokĆ©rt","azon","azonban","azonnal","azt","aztĆ”n","azutĆ”n","azzal","azzĆ”","azĆ©rt","bal","balra","ban","be","belĆ©","belĆ©d","belĆ©jĆ¼k","belĆ©m","belĆ©nk","belĆ©tek","belĆ¼l","belÅle","belÅled","belÅlem","belÅletek","belÅlĆ¼k","belÅlĆ¼nk","ben","benne","benned","bennem","bennetek","bennĆ¼k","bennĆ¼nk","bĆ”r","bĆ”rcsak","bĆ”rmilyen","bĆŗcsĆŗ","cikk","cikkek","cikkeket","csak","csakhogy","csupĆ”n","de","dehogy","e","ebbe","ebben","ebbÅl","eddig","egy","egyebek","egyebet","egyedĆ¼l","egyelÅre","egyes","egyet","egyetlen","egyik","egymĆ”s","egyre","egyszerre","egyĆ©b","egyĆ¼tt","egĆ©sz","egĆ©szen","ehhez","ekkor","el","eleinte","ellen","ellenes","elleni","ellenĆ©re","elmondta","elsƵ","elsÅ","elsÅk","elsÅsorban","elsÅt","elĆ©","elĆ©d","elĆ©g","elĆ©jĆ¼k","elĆ©m","elĆ©nk","elĆ©tek","elƵ","elƵszƶr","elƵtt","elÅ","elÅbb","elÅl","elÅle","elÅled","elÅlem","elÅletek","elÅlĆ¼k","elÅlĆ¼nk","elÅszƶr","elÅtt","elÅtte","elÅtted","elÅttem","elÅttetek","elÅttĆ¼k","elÅttĆ¼nk","elÅzÅ","emilyen","engem","ennek","ennyi","ennĆ©l","enyĆ©m","erre","errÅl","esetben","ettÅl","ez","ezek","ezekbe","ezekben","ezekbÅl","ezeken","ezeket","ezekhez","ezekig","ezekkel","ezekkĆ©","ezeknek","ezeknĆ©l","ezekre","ezekrÅl","ezektÅl","ezekĆ©rt","ezen","ezentĆŗl","ezer","ezret","ezt","ezutĆ”n","ezzel","ezzĆ©","ezĆ©rt","fel","fele","felek","felet","felett","felĆ©","fent","fenti","fĆ©l","fƶlĆ©","gyakran","ha","hallĆ³","hamar","hanem","harmadik","harmadikat","harminc","hat","hatodik","hatodikat","hatot","hatvan","helyett","hetedik","hetediket","hetet","hetven","hirtelen","hiszen","hiĆ”ba","hogy","hogyan","hol","holnap","holnapot","honnan","hova","hozzĆ”","hozzĆ”d","hozzĆ”juk","hozzĆ”m","hozzĆ”nk","hozzĆ”tok","hurrĆ”","huszadik","hĆ”ny","hĆ”nyszor","hĆ”rmat","hĆ”rom","hĆ”t","hĆ”tha","hĆ”tulsĆ³","hĆ©t","hĆŗsz","ide","ide-Š¾da","idĆ©n","igazĆ”n","igen","ill","ill.","illetve","ilyen","ilyenkor","immĆ”r","inkĆ”bb","is","ismĆ©t","ison","itt","jelenleg","jobban","jobbra","jĆ³","jĆ³l","jĆ³lesik","jĆ³val","jƶvÅre","kell","kellene","kellett","kelljen","keressĆ¼nk","keresztĆ¼l","ketten","kettÅ","kettÅt","kevĆ©s","ki","kiben","kibÅl","kicsit","kicsoda","kihez","kik","kikbe","kikben","kikbÅl","kiken","kiket","kikhez","kikkel","kikkĆ©","kiknek","kiknĆ©l","kikre","kikrÅl","kiktÅl","kikĆ©rt","kilenc","kilencedik","kilencediket","kilencet","kilencven","kin","kinek","kinĆ©l","kire","kirÅl","kit","kitÅl","kivel","kivĆ©","kiĆ©","kiĆ©rt","korĆ”bban","kĆ©pest","kĆ©rem","kĆ©rlek","kĆ©sz","kĆ©sÅ","kĆ©sÅbb","kĆ©sÅn","kĆ©t","kĆ©tszer","kĆvĆ¼l","kƶrĆ¼l","kƶszƶnhetÅen","kƶszƶnƶm","kƶzben","kƶzel","kƶzepesen","kƶzepĆ©n","kƶzĆ©","kƶzƶtt","kƶzĆ¼l","kĆ¼lƶn","kĆ¼lƶnben","kĆ¼lƶnbƶzÅ","kĆ¼lƶnbƶzÅbb","kĆ¼lƶnbƶzÅek","lassan","le","legalĆ”bb","legyen","lehet","lehetetlen","lehetett","lehetÅleg","lehetÅsĆ©g","lenne","lenni","lennĆ©k","lennĆ©nek","lesz","leszek","lesznek","leszĆ¼nk","lett","lettek","lettem","lettĆ¼nk","lĆ©vÅ","ma","maga","magad","magam","magatokat","magukat","magunkat","magĆ”t","mai","majd","majdnem","manapsĆ”g","meg","megcsinĆ”l","megcsinĆ”lnak","megint","megvan","mellett","mellette","melletted","mellettem","mellettetek","mellettĆ¼k","mellettĆ¼nk","mellĆ©","mellĆ©d","mellĆ©jĆ¼k","mellĆ©m","mellĆ©nk","mellĆ©tek","mellÅl","mellÅle","mellÅled","mellÅlem","mellÅletek","mellÅlĆ¼k","mellÅlĆ¼nk","mely","melyek","melyik","mennyi","mert","mi","miatt","miatta","miattad","miattam","miattatok","miattuk","miattunk","mibe","miben","mibÅl","mihez","mik","mikbe","mikben","mikbÅl","miken","miket","mikhez","mikkel","mikkĆ©","miknek","miknĆ©l","mikor","mikre","mikrÅl","miktÅl","mikĆ©rt","milyen","min","mind","mindegyik","mindegyiket","minden","mindenesetre","mindenki","mindent","mindenĆ¼tt","mindig","mindketten","minek","minket","mint","mintha","minĆ©l","mire","mirÅl","mit","mitÅl","mivel","mivĆ©","miĆ©rt","mondta","most","mostanĆ”ig","mĆ”r","mĆ”s","mĆ”sik","mĆ”sikat","mĆ”snap","mĆ”sodik","mĆ”sodszor","mĆ”sok","mĆ”sokat","mĆ”st","mĆ©g","mĆ©gis","mĆg","mƶgĆ©","mƶgĆ©d","mƶgĆ©jĆ¼k","mƶgĆ©m","mƶgĆ©nk","mƶgĆ©tek","mƶgƶtt","mƶgƶtte","mƶgƶtted","mƶgƶttem","mƶgƶttetek","mƶgƶttĆ¼k","mƶgƶttĆ¼nk","mƶgĆ¼l","mƶgĆ¼le","mƶgĆ¼led","mƶgĆ¼lem","mƶgĆ¼letek","mƶgĆ¼lĆ¼k","mƶgĆ¼lĆ¼nk","mĆŗltkor","mĆŗlva","na","nagy","nagyobb","nagyon","naponta","napot","ne","negyedik","negyediket","negyven","neked","nekem","neki","nekik","nektek","nekĆ¼nk","nem","nemcsak","nemrĆ©g","nincs","nyolc","nyolcadik","nyolcadikat","nyolcat","nyolcvan","nĆ”la","nĆ”lad","nĆ”lam","nĆ”latok","nĆ”luk","nĆ”lunk","nĆ©gy","nĆ©gyet","nĆ©ha","nĆ©hĆ”ny","nĆ©lkĆ¼l","o","oda","ok","olyan","onnan","ott","pedig","persze","pĆ”r","pĆ©ldĆ”ul","rajta","rajtad","rajtam","rajtatok","rajtuk","rajtunk","rendben","rosszul","rĆ”","rĆ”d","rĆ”juk","rĆ”m","rĆ”nk","rĆ”tok","rĆ©gen","rĆ©gĆ³ta","rĆ©szĆ©re","rĆ³la","rĆ³lad","rĆ³lam","rĆ³latok","rĆ³luk","rĆ³lunk","rƶgtƶn","s","sajĆ”t","se","sem","semmi","semmilyen","semmisĆ©g","senki","soha","sok","sokan","sokat","sokkal","sokszor","sokĆ”ig","sorĆ”n","stb.","szemben","szerbusz","szerint","szerinte","szerinted","szerintem","szerintetek","szerintĆ¼k","szerintĆ¼nk","szervusz","szinte","szĆ”mĆ”ra","szĆ”z","szĆ”zadik","szĆ”zat","szĆ©pen","szĆ©t","szĆves","szĆvesen","szĆveskedjĆ©k","sÅt","talĆ”n","tavaly","te","tegnap","tegnapelÅtt","tehĆ”t","tele","teljes","tessĆ©k","ti","tied","titeket","tizedik","tizediket","tizenegy","tizenegyedik","tizenhat","tizenhĆ”rom","tizenhĆ©t","tizenkettedik","tizenkettÅ","tizenkilenc","tizenkĆ©t","tizennyolc","tizennĆ©gy","tizenƶt","tizet","tovĆ”bb","tovĆ”bbi","tovĆ”bbĆ”","tĆ”vol","tĆ©ged","tĆ©nyleg","tĆz","tƶbb","tƶbbi","tƶbbszƶr","tĆŗl","tÅle","tÅled","tÅlem","tÅletek","tÅlĆ¼k","tÅlĆ¼nk","ugyanakkor","ugyanez","ugyanis","ugye","urak","uram","urat","utoljĆ”ra","utolsĆ³","utĆ”n","utĆ”na","vagy","vagyis","vagyok","vagytok","vagyunk","vajon","valahol","valaki","valakit","valamelyik","valami","valamint","valĆ³","van","vannak","vele","veled","velem","veletek","velĆ¼k","velĆ¼nk","vissza","viszlĆ”t","viszont","viszontlĆ”tĆ”sra","volna","volnĆ”nak","volnĆ©k","volt","voltak","voltam","voltunk","vĆ©gre","vĆ©gĆ©n","vĆ©gĆ¼l","Ć”ltal","Ć”ltalĆ”ban","Ć”m","Ć”t","Ć©ljen","Ć©n","Ć©ppen","Ć©rte","Ć©rted","Ć©rtem","Ć©rtetek","Ć©rtĆ¼k","Ć©rtĆ¼nk","Ć©s","Ć©v","Ć©vben","Ć©ve","Ć©vek","Ć©ves","Ć©vi","Ć©vvel","Ćgy","Ć³ta","Ƶ","Ƶk","Ƶket","ƶn","ƶnbe","ƶnben","ƶnbÅl","ƶnhƶz","ƶnnek","ƶnnel","ƶnnĆ©l","ƶnre","ƶnrÅl","ƶnt","ƶntÅl","ƶnĆ©rt","ƶnƶk","ƶnƶkbe","ƶnƶkben","ƶnƶkbÅl","ƶnƶket","ƶnƶkhƶz","ƶnƶkkel","ƶnƶknek","ƶnƶknĆ©l","ƶnƶkre","ƶnƶkrÅl","ƶnƶktÅl","ƶnƶkĆ©rt","ƶnƶkƶn","ƶnƶn","ƶssze","ƶt","ƶtven","ƶtƶdik","ƶtƶdiket","ƶtƶt","Ćŗgy","Ćŗgyis","Ćŗgynevezett","Ćŗj","Ćŗjabb","Ćŗjra","Ćŗr","Å","Åk","Åket","Åt"],"id":["ada","adalah","adanya","adapun","agak","agaknya","agar","akan","akankah","akhir","akhiri","akhirnya","aku","akulah","amat","amatlah","anda","andalah","antar","antara","antaranya","apa","apaan","apabila","apakah","apalagi","apatah","artinya","asal","asalkan","atas","atau","ataukah","ataupun","awal","awalnya","bagai","bagaikan","bagaimana","bagaimanakah","bagaimanapun","bagi","bagian","bahkan","bahwa","bahwasanya","baik","bakal","bakalan","balik","banyak","bapak","baru","bawah","beberapa","begini","beginian","beginikah","beginilah","begitu","begitukah","begitulah","begitupun","bekerja","belakang","belakangan","belum","belumlah","benar","benarkah","benarlah","berada","berakhir","berakhirlah","berakhirnya","berapa","berapakah","berapalah","berapapun","berarti","berawal","berbagai","berdatangan","beri","berikan","berikut","berikutnya","berjumlah","berkali-kali","berkata","berkehendak","berkeinginan","berkenaan","berlainan","berlalu","berlangsung","berlebihan","bermacam","bermacam-macam","bermaksud","bermula","bersama","bersama-sama","bersiap","bersiap-siap","bertanya","bertanya-tanya","berturut","berturut-turut","bertutur","berujar","berupa","besar","betul","betulkah","biasa","biasanya","bila","bilakah","bisa","bisakah","boleh","bolehkah","bolehlah","buat","bukan","bukankah","bukanlah","bukannya","bulan","bung","cara","caranya","cukup","cukupkah","cukuplah","cuma","dahulu","dalam","dan","dapat","dari","daripada","datang","dekat","demi","demikian","demikianlah","dengan","depan","di","dia","diakhiri","diakhirinya","dialah","diantara","diantaranya","diberi","diberikan","diberikannya","dibuat","dibuatnya","didapat","didatangkan","digunakan","diibaratkan","diibaratkannya","diingat","diingatkan","diinginkan","dijawab","dijelaskan","dijelaskannya","dikarenakan","dikatakan","dikatakannya","dikerjakan","diketahui","diketahuinya","dikira","dilakukan","dilalui","dilihat","dimaksud","dimaksudkan","dimaksudkannya","dimaksudnya","diminta","dimintai","dimisalkan","dimulai","dimulailah","dimulainya","dimungkinkan","dini","dipastikan","diperbuat","diperbuatnya","dipergunakan","diperkirakan","diperlihatkan","diperlukan","diperlukannya","dipersoalkan","dipertanyakan","dipunyai","diri","dirinya","disampaikan","disebut","disebutkan","disebutkannya","disini","disinilah","ditambahkan","ditandaskan","ditanya","ditanyai","ditanyakan","ditegaskan","ditujukan","ditunjuk","ditunjuki","ditunjukkan","ditunjukkannya","ditunjuknya","dituturkan","dituturkannya","diucapkan","diucapkannya","diungkapkan","dong","dua","dulu","empat","enggak","enggaknya","entah","entahlah","guna","gunakan","hal","hampir","hanya","hanyalah","hari","harus","haruslah","harusnya","hendak","hendaklah","hendaknya","hingga","ia","ialah","ibarat","ibaratkan","ibaratnya","ibu","ikut","ingat","ingat-ingat","ingin","inginkah","inginkan","ini","inikah","inilah","itu","itukah","itulah","jadi","jadilah","jadinya","jangan","jangankan","janganlah","jauh","jawab","jawaban","jawabnya","jelas","jelaskan","jelaslah","jelasnya","jika","jikalau","juga","jumlah","jumlahnya","justru","kala","kalau","kalaulah","kalaupun","kalian","kami","kamilah","kamu","kamulah","kan","kapan","kapankah","kapanpun","karena","karenanya","kasus","kata","katakan","katakanlah","katanya","ke","keadaan","kebetulan","kecil","kedua","keduanya","keinginan","kelamaan","kelihatan","kelihatannya","kelima","keluar","kembali","kemudian","kemungkinan","kemungkinannya","kenapa","kepada","kepadanya","kesampaian","keseluruhan","keseluruhannya","keterlaluan","ketika","khususnya","kini","kinilah","kira","kira-kira","kiranya","kita","kitalah","kok","kurang","lagi","lagian","lah","lain","lainnya","lalu","lama","lamanya","lanjut","lanjutnya","lebih","lewat","lima","luar","macam","maka","makanya","makin","malah","malahan","mampu","mampukah","mana","manakala","manalagi","masa","masalah","masalahnya","masih","masihkah","masing","masing-masing","mau","maupun","melainkan","melakukan","melalui","melihat","melihatnya","memang","memastikan","memberi","memberikan","membuat","memerlukan","memihak","meminta","memintakan","memisalkan","memperbuat","mempergunakan","memperkirakan","memperlihatkan","mempersiapkan","mempersoalkan","mempertanyakan","mempunyai","memulai","memungkinkan","menaiki","menambahkan","menandaskan","menanti","menanti-nanti","menantikan","menanya","menanyai","menanyakan","mendapat","mendapatkan","mendatang","mendatangi","mendatangkan","menegaskan","mengakhiri","mengapa","mengatakan","mengatakannya","mengenai","mengerjakan","mengetahui","menggunakan","menghendaki","mengibaratkan","mengibaratkannya","mengingat","mengingatkan","menginginkan","mengira","mengucapkan","mengucapkannya","mengungkapkan","menjadi","menjawab","menjelaskan","menuju","menunjuk","menunjuki","menunjukkan","menunjuknya","menurut","menuturkan","menyampaikan","menyangkut","menyatakan","menyebutkan","menyeluruh","menyiapkan","merasa","mereka","merekalah","merupakan","meski","meskipun","meyakini","meyakinkan","minta","mirip","misal","misalkan","misalnya","mula","mulai","mulailah","mulanya","mungkin","mungkinkah","nah","naik","namun","nanti","nantinya","nyaris","nyatanya","oleh","olehnya","pada","padahal","padanya","pak","paling","panjang","pantas","para","pasti","pastilah","penting","pentingnya","per","percuma","perlu","perlukah","perlunya","pernah","persoalan","pertama","pertama-tama","pertanyaan","pertanyakan","pihak","pihaknya","pukul","pula","pun","punya","rasa","rasanya","rata","rupanya","saat","saatnya","saja","sajalah","saling","sama","sama-sama","sambil","sampai","sampai-sampai","sampaikan","sana","sangat","sangatlah","satu","saya","sayalah","se","sebab","sebabnya","sebagai","sebagaimana","sebagainya","sebagian","sebaik","sebaik-baiknya","sebaiknya","sebaliknya","sebanyak","sebegini","sebegitu","sebelum","sebelumnya","sebenarnya","seberapa","sebesar","sebetulnya","sebisanya","sebuah","sebut","sebutlah","sebutnya","secara","secukupnya","sedang","sedangkan","sedemikian","sedikit","sedikitnya","seenaknya","segala","segalanya","segera","seharusnya","sehingga","seingat","sejak","sejauh","sejenak","sejumlah","sekadar","sekadarnya","sekali","sekali-kali","sekalian","sekaligus","sekalipun","sekarang","sekecil","seketika","sekiranya","sekitar","sekitarnya","sekurang-kurangnya","sekurangnya","sela","selagi","selain","selaku","selalu","selama","selama-lamanya","selamanya","selanjutnya","seluruh","seluruhnya","semacam","semakin","semampu","semampunya","semasa","semasih","semata","semata-mata","semaunya","sementara","semisal","semisalnya","sempat","semua","semuanya","semula","sendiri","sendirian","sendirinya","seolah","seolah-olah","seorang","sepanjang","sepantasnya","sepantasnyalah","seperlunya","seperti","sepertinya","sepihak","sering","seringnya","serta","serupa","sesaat","sesama","sesampai","sesegera","sesekali","seseorang","sesuatu","sesuatunya","sesudah","sesudahnya","setelah","setempat","setengah","seterusnya","setiap","setiba","setibanya","setidak-tidaknya","setidaknya","setinggi","seusai","sewaktu","siap","siapa","siapakah","siapapun","sini","sinilah","soal","soalnya","suatu","sudah","sudahkah","sudahlah","supaya","tadi","tadinya","tahu","tahun","tak","tambah","tambahnya","tampak","tampaknya","tandas","tandasnya","tanpa","tanya","tanyakan","tanyanya","tapi","tegas","tegasnya","telah","tempat","tengah","tentang","tentu","tentulah","tentunya","tepat","terakhir","terasa","terbanyak","terdahulu","terdapat","terdiri","terhadap","terhadapnya","teringat","teringat-ingat","terjadi","terjadilah","terjadinya","terkira","terlalu","terlebih","terlihat","termasuk","ternyata","tersampaikan","tersebut","tersebutlah","tertentu","tertuju","terus","terutama","tetap","tetapi","tiap","tiba","tiba-tiba","tidak","tidakkah","tidaklah","tiga","tinggi","toh","tunjuk","turut","tutur","tuturnya","ucap","ucapnya","ujar","ujarnya","umum","umumnya","ungkap","ungkapnya","untuk","usah","usai","waduh","wah","wahai","waktu","waktunya","walau","walaupun","wong","yaitu","yakin","yakni","yang"],"ga":["a","ach","ag","agus","an","aon","ar","arna","as","b'","ba","beirt","bhĆŗr","caoga","ceathair","ceathrar","chomh","chtĆ³","chuig","chun","cois","cĆ©ad","cĆŗig","cĆŗigear","d'","daichead","dar","de","deich","deichniĆŗr","den","dhĆ”","do","don","dtĆ","dĆ”","dĆ”r","dĆ³","faoi","faoin","faoina","faoinĆ”r","fara","fiche","gach","gan","go","gur","haon","hocht","i","iad","idir","in","ina","ins","inĆ”r","is","le","leis","lena","lenĆ”r","m'","mar","mo","mĆ©","na","nach","naoi","naonĆŗr","nĆ”","nĆ","nĆor","nĆ³","nĆ³cha","ocht","ochtar","os","roimh","sa","seacht","seachtar","seachtĆ³","seasca","seisear","siad","sibh","sinn","sna","sĆ©","sĆ","tar","thar","thĆŗ","triĆŗr","trĆ","trĆna","trĆnĆ”r","trĆocha","tĆŗ","um","Ć”r","Ć©","Ć©is","Ć","Ć³","Ć³n","Ć³na","Ć³nĆ”r"],"it":["a","abbastanza","abbia","abbiamo","abbiano","abbiate","accidenti","ad","adesso","affinchĆ©","agl","agli","ahime","ahimĆØ","ai","al","alcuna","alcuni","alcuno","all","alla","alle","allo","allora","altre","altri","altrimenti","altro","altrove","altrui","anche","ancora","anni","anno","ansa","anticipo","assai","attesa","attraverso","avanti","avemmo","avendo","avente","aver","avere","averlo","avesse","avessero","avessi","avessimo","aveste","avesti","avete","aveva","avevamo","avevano","avevate","avevi","avevo","avrai","avranno","avrebbe","avrebbero","avrei","avremmo","avremo","avreste","avresti","avrete","avrĆ ","avrĆ²","avuta","avute","avuti","avuto","basta","ben","bene","benissimo","brava","bravo","buono","c","caso","cento","certa","certe","certi","certo","che","chi","chicchessia","chiunque","ci","ciascuna","ciascuno","cima","cinque","cio","cioe","cioĆØ","circa","citta","cittĆ ","ciĆ²","co","codesta","codesti","codesto","cogli","coi","col","colei","coll","coloro","colui","come","cominci","comprare","comunque","con","concernente","conclusione","consecutivi","consecutivo","consiglio","contro","cortesia","cos","cosa","cosi","cosƬ","cui","d","da","dagl","dagli","dai","dal","dall","dalla","dalle","dallo","dappertutto","davanti","degl","degli","dei","del","dell","della","delle","dello","dentro","detto","deve","devo","di","dice","dietro","dire","dirimpetto","diventa","diventare","diventato","dopo","doppio","dov","dove","dovra","dovrĆ ","dovunque","due","dunque","durante","e","ebbe","ebbero","ebbi","ecc","ecco","ed","effettivamente","egli","ella","entrambi","eppure","era","erano","eravamo","eravate","eri","ero","esempio","esse","essendo","esser","essere","essi","ex","fa","faccia","facciamo","facciano","facciate","faccio","facemmo","facendo","facesse","facessero","facessi","facessimo","faceste","facesti","faceva","facevamo","facevano","facevate","facevi","facevo","fai","fanno","farai","faranno","fare","farebbe","farebbero","farei","faremmo","faremo","fareste","faresti","farete","farĆ ","farĆ²","fatto","favore","fece","fecero","feci","fin","finalmente","finche","fine","fino","forse","forza","fosse","fossero","fossi","fossimo","foste","fosti","fra","frattempo","fu","fui","fummo","fuori","furono","futuro","generale","gente","gia","giacche","giorni","giorno","giu","giĆ ","gli","gliela","gliele","glieli","glielo","gliene","grande","grazie","gruppo","ha","haha","hai","hanno","ho","i","ie","ieri","il","improvviso","in","inc","indietro","infatti","inoltre","insieme","intanto","intorno","invece","io","l","la","lasciato","lato","le","lei","li","lo","lontano","loro","lui","lungo","luogo","lĆ ","ma","macche","magari","maggior","mai","male","malgrado","malissimo","me","medesimo","mediante","meglio","meno","mentre","mesi","mezzo","mi","mia","mie","miei","mila","miliardi","milioni","minimi","mio","modo","molta","molti","moltissimo","molto","momento","mondo","ne","negl","negli","nei","nel","nell","nella","nelle","nello","nemmeno","neppure","nessun","nessuna","nessuno","niente","no","noi","nome","non","nondimeno","nonostante","nonsia","nostra","nostre","nostri","nostro","novanta","nove","nulla","nuovi","nuovo","o","od","oggi","ogni","ognuna","ognuno","oltre","oppure","ora","ore","osi","ossia","ottanta","otto","paese","parecchi","parecchie","parecchio","parte","partendo","peccato","peggio","per","perche","perchĆØ","perchĆ©","percio","perciĆ²","perfino","pero","persino","persone","perĆ²","piedi","pieno","piglia","piu","piuttosto","piĆ¹","po","pochissimo","poco","poi","poiche","possa","possedere","posteriore","posto","potrebbe","preferibilmente","presa","press","prima","primo","principalmente","probabilmente","promesso","proprio","puo","pure","purtroppo","puĆ²","qua","qualche","qualcosa","qualcuna","qualcuno","quale","quali","qualunque","quando","quanta","quante","quanti","quanto","quantunque","quarto","quasi","quattro","quel","quella","quelle","quelli","quello","quest","questa","queste","questi","questo","qui","quindi","quinto","realmente","recente","recentemente","registrazione","relativo","riecco","rispetto","salvo","sara","sarai","saranno","sarebbe","sarebbero","sarei","saremmo","saremo","sareste","saresti","sarete","sarĆ ","sarĆ²","scola","scopo","scorso","se","secondo","seguente","seguito","sei","sembra","sembrare","sembrato","sembrava","sembri","sempre","senza","sette","si","sia","siamo","siano","siate","siete","sig","solito","solo","soltanto","sono","sopra","soprattutto","sotto","spesso","sta","stai","stando","stanno","starai","staranno","starebbe","starebbero","starei","staremmo","staremo","stareste","staresti","starete","starĆ ","starĆ²","stata","state","stati","stato","stava","stavamo","stavano","stavate","stavi","stavo","stemmo","stessa","stesse","stessero","stessi","stessimo","stesso","steste","stesti","stette","stettero","stetti","stia","stiamo","stiano","stiate","sto","su","sua","subito","successivamente","successivo","sue","sugl","sugli","sui","sul","sull","sulla","sulle","sullo","suo","suoi","tale","tali","talvolta","tanto","te","tempo","terzo","th","ti","titolo","tra","tranne","tre","trenta","triplo","troppo","trovato","tu","tua","tue","tuo","tuoi","tutta","tuttavia","tutte","tutti","tutto","uguali","ulteriore","ultimo","un","una","uno","uomo","va","vai","vale","vari","varia","varie","vario","verso","vi","vicino","visto","vita","voi","volta","volte","vostra","vostre","vostri","vostro","ĆØ"],"ja":["ććć","ćć£","ćć®","ćć®ćć","ćć®äŗŗ","ćć","ććć¾ć","ćć","ćć","ć","ćć","ćć¾ć","ćć","ć","ćć”","ć","ć","ććć³","ćć","ććć¾ć","ć","ćć¤ć¦","ćć","ć","ć","ćć","ćć”ć","ććØ","ćć®","ćć","ććć","ć","ććć«","ć","ććć","ćć","ć","ć","ćć","ćć","ććć¦","ćć®","ćć®ä»","ćć®å¾","ćć","ćććć","ććć§","ć","ćć ć","ćć”","ćć","ćć","ć ","ć ć£","ć ć","ć¤","ć¦","ć§","ć§ć","ć§ćć","ć§ć","ć§ćÆ","ć§ć","ćØ","ćØćć","ćØćć£ć","ćØć","ćØćć","ćØćć¦","ćØćØćć«","ćØć","ćØå
±ć«","ć©ć","ć©ć®","ćŖ","ćŖć","ćŖć","ćŖćć£","ćŖćć","ćŖć","ćŖć£","ćŖć©","ćŖć«","ćŖć","ćŖć","ćŖć","ćŖć","ć«","ć«ććć¦","ć«ććć","ć«ć¤ćć¦","ć«ć¦","ć«ćć£ć¦","ć«ćć","ć«ćć","ć«åƾćć¦","ć«åƾćć","ć«é¢ćć","ć®","ć®ć§","ć®ćæ","ćÆ","ć°","ćø","ć»ć","ć»ćØćć©","ć»ć©","ć¾ć","ć¾ć","ć¾ććÆ","ć¾ć§","ć","ćć®","ćć®ć®","ć","ćć","ćć","ć","ćć","ććć","ć","ćć","ć","ć","ä½","åć³","å½¼","彼儳","ęć
","ē¹ć«","ē§","ē§é","č²“ę¹","č²“ę¹ę¹"],"ko":["!","\"","$","%","&","'","(",")","*","+",",","-",".","...","0","1","2","3","4","5","6","7","8","9",";","<","=",">","?","@","\\","^","_","`","|","~","Ā·","ā","āā","ā","ā","ā","ā","ā¦","ć","ć","ć","ć","ć","ć","ź°","ź°ź¹ģ¤ė”","ź°ė ¹","ź°","ź°ź°","ź°ģ","ź°ģ¢
","ź°ź³ ė§ķģė©“","ź°ė¤","ź°ģ“","ź°ģģ¹ģź³ ","ź±°ėģ","ź±°ė°","ź±°ģ","ź²","ź²ź³¼ ź°ģ“","ź²ė¤","ź²ė¤ź°","ź²ģ°ė¤","ź²Øģ°","ź²¬ģ§ģģ","ź²°ź³¼ģ ģ“ė„“ė¤","ź²°źµ","ź²°ė” ģ ė¼ ģ ģė¤","ź²øģ¬ź²øģ¬","ź³ ė ¤ķė©“","ź³ ė”","ź³§","ź³µėģ¼ė”","ź³¼","ź³¼ģ°","ź“ź³ź° ģė¤","ź“ź³ģģ“","ź“ė Øģ“ ģė¤","ź“ķģ¬","ź“ķ","ź“ķ“ģė","źµ¬","źµ¬ģ²“ģ ģ¼ė”","źµ¬ķ ķė¤","ź·ø","ź·øė¤","ź·øė","ź·øė","ź·øėė","ź·øėģ","ź·øė¬ė","ź·øė¬ė","ź·øė¬ėź¹","ź·øė¬ė©“","ź·øė¬ėÆė”","ź·øė¬ķģ¦","ź·øė° ź¹ėģ","ź·øė°ė°","ź·øė°ģ¦","ź·øė¼","ź·øė¼ģė ė¶źµ¬ķź³ ","ź·øė ź² ķØģ¼ė”ģØ","ź·øė ģ§","ź·øė ģ§ ģė¤ė©“","ź·øė ģ§ ģģ¼ė©“","ź·øė ģ§ė§","ź·øė ģ§ģģ¼ė©“","ź·øė¦¬ź³ ","ź·øė¦¬ķģ¬","ź·øė§ģ“ė¤","ź·øģ ė°ė„“ė","ź·øģģ","ź·øģ ","ź·øģ¤ģģ","ź·øģ¹ģ§ ģė¤","ź·¼ź±°ė”","ź·¼ź±°ķģ¬","źø°ėģ¬","źø°ģ ģ¼ė”","źø°ģ¤ģ¼ė”","źø°ķ","ź¹ėģ¼ė”","ź¹ģ
","ź¹ģ§","ź¹ģ§ ėÆøģ¹ė¤","ź¹ģ§ė","ź½ė¹","ėė","ė¼ģµ","ė","ėėØøģ§ė","ėØė¤","ėØģ§","ė","ėķ¬","ėķ¬ė¤","ė¤","ė·","ė
","ė
¼ķģ§ ģė¤","ėė¼ė¤","ėź° ģź² ėź°","ėźµ¬","ė¤ė„ø","ė¤ė„ø ė°©ė©“ģ¼ė”","ė¤ė§","ė¤ģÆ","ė¤ģ","ė¤ģ","ė¤ģ ė§ķģė©“","ė¤ģė§ķė©“","ė¤ģ","ė¤ģģ","ė¤ģģ¼ė”","ėØģ§","ėµė¤","ė¹ģ ","ė¹ģ„","ėė” ķė¤","ėķė©“","ėķģ¬","ėķ“ ė§ķģė©“","ėķ“ģ","ėź·ø","ėźµ¬ė","ėźµ°ė¤ė","ėė¼ė","ėė¶ģ“","ėģ±ė","ėģ±ģ“ė","ėė¬ķė¤","ėģ°©ķė¤","ėģģ","ėģ","ėė°ģģ¼","ėģ“ģ","ėė²ģ§øė”","ė","ė„ė„","ė¤ė°ė¼","ė¤ģ“ģ“","ė ź°ģ","ė¤","ė±","ė±ė±","ė©ė","ė°ė¼","ė°ė¼ģ","ė°ģ","ė°ģ§ģ§ ģė¤","ė±","ė","ėź° ėģ“","ėė¬øģ","ė","ėķ","ėė","ė¼ ķ“ė","ė ¹","ė”","ė” ģøķģ¬","ė”ė¶ķ°","ė”ģØ","ė„","ė„¼","ė§ģėė”","ė§ģ ","ė§ģ ė","ė§ģ¹","ė§ė” ķź³ ","ė§ ėŖ»ķė¤","ė§ģ½","ė§ģ½ģ","ė§ģ ģėė¤","ė§ģ“ ģėė¤","ė§ģ¼","ė§ķ¼","ė§ķģė©“","ė§ķ ź²ė ģź³ ","ė§¤","ė§¤ė²","ė©ģ°ź²ė¤","ėŖ","ėŖØ","ėŖØė","ė¬“ė µ","ė¬“ė¦ģ°ź³ ","ė¬“ģØ","ė¬“ģ","ė¬“ģėė¬øģ","ė¬¼ė” ","ė°","ė°ź¾øģ“ė§ķė©“","ė°ź¾øģ“ė§ķģė©“","ė°ź¾øģ“ģ ė§ķė©“","ė°ź¾øģ“ģ ķė¤ė©“","ė°źæ ė§ķė©“","ė°ė”","ė°ģź°ģ“","ė°ģ ģėė¤","ė°ėė”","ė°ėė” ė§ķģė©“","ė°ėģ","ė²źø","ė³“ėė°ģ","ė³“ė¤ė","ė³“ėė","ė³øėė”","ė“","ė“ė¼","ė¶ė„ģ ģ¬ėė¤","ė¶ķ°","ė¶źµ¬ķź³ ","ė¶ė¬øķź³ ","ė¶ė¶","ė¹ź±±ź±°ė¦¬ė¤","ė¹źµģ ","ė¹źøøģ ģė¤","ė¹ė”ģ","ė¹ė”","ė¹ģ·ķė¤","ė¹ģ¶ģ“ ė³“ģ","ė¹ķė©“","ėæė§ ģėė¼","ėæė§ģėė¼","ėæģ“ė¤","ģź±±","ģź±±ź±°ė¦¬ė¤","ģ¬","ģ¼","ģėģ ģ¼ė” ė§ķģė©“","ģź°ķėė”","ģ¤ė ¹","ģ¤ė§","ģ¤ģ¬","ģ
","ģģ","ģģø","ģØ","ģæ","ģµėź¹","ģµėė¤","ģź°","ģź°","ģģķģ¬","ģģ“ģ","ģķ¤ė¤","ģ¤ė”","ģ¬ģ§ģ“","ģ","ģė","ģėėė¤ė„¼ź°","ģėė¼ė©“","ģėė©“","ģėģė¤ė©“","ģėģ","ģė¬“ź±°ė","ģė¬“ė","ģģ¼","ģģøė¬","ģģ“","ģģ“ź³ ","ģģ“źµ¬","ģģ“ģ¼","ģģ“ģæ ","ģķ","ģķ","ģ ź·øė¬ė©“","ģźø° ģķģ¬","ģźø° ģķ“ģ","ģ ģ ģė¤","ģģģ“","ģ","ģģģ","ģģź²","ģ¼","ģ½ź°","ģģ","ģ“","ģ“źø°ģ¬ģ°Ø","ģ“ė","ģ“ė ė
ė","ģ“ėź²","ģ“ėź³³","ģ“ėė","ģ“ėģŖ½","ģ“ėķ“","ģ“ė","ģ“ė","ģ“ė ķ","ģ“ė¤","ģ“ė¤ź²","ģ“ė¤ź²ė¤","ģ“ė»ź²","ģ“ė»ķ“","ģ“ģ“","ģ“ģ§øģ","ģ“ģØė ","ģ“ģ©ģ ģė¤","ģ“ģ°","ģ“ģ°ėė ","ģ“ģ°ėģ“","ģ“ģ°ķė ģ§","ģ“ģ°ķģ¬","ģøģ ","ģøģ ź°","ģ¼ė§","ģ¼ė§ ģ ėė ź²","ģ¼ė§ź°","ģ¼ė§ė","ģ¼ė§ė ģ§","ģ¼ė§ė§ķ¼","ģ¼ė§ķ¼","ģģ","ģ","ģ ź°ģ","ģ ė¬ė ¤ ģė¤","ģ ėķ“","ģ ģė¤","ģ ķķė¤","ģź²","ģģ","ģ¬","ģ¬źø°","ģ¬ė","ģ¬ė¬ė¶","ģ¬ė³“ģģ¤","ģ¬ė¶","ģ¬ģÆ","ģ¬ģ ķ","ģ¬ģ°Ø","ģ°ź“ėė¤","ģ°ģ“ģ","ģ","ģģ°Ø","ģģ¬ė","ģ","ģė„¼ ė¤ė©“","ģė„¼ ė¤ģė©“","ģģ»Øė","ģķė©“","ģ¤","ģ¤ė”ģ§","ģ¤ė„“ė¤","ģ¤ģė§ģ","ģ¤ģ§","ģ¤ķø","ģ¤ķė ¤","ģ","ģ ź°ģ ģ¬ėė¤","ģė„“ė„“","ģģ","ģ","ģėķė©“","ģøģė","ģė§ķ¼","ģė§ķ ź²","ģė§ķź±ø","ģģ»Øė","ģ°ė„“ė„“","ģ°ė¦¬","ģ°ė¦¬ė¤","ģ°ģ ","ģ°ģ ģ¢
ķ©ķź²ź³¼ź°ģ“","ģ“ģ“","ģ","ģģģ ģģ ķė°ģź°ģ“","ģķģ¬","ģķ“ģ","ģģ","ģ”","ģ¼ė”","ģ¼ė” ģøķģ¬","ģ¼ė”ģ","ģ¼ė”ģØ","ģ","ģ","ģė¹","ģ","ģź±°ķģ¬","ģģ§ķģ¬","ģķ“","ģķ“ėė¤","ģķ“ģ","ģ“","ģ“ ėė¤","ģ“ ėė¬øģ","ģ“ ė°ģ","ģ“ ģøģ","ģ“ ģ ėģ","ģ“ź²","ģ“ź³³","ģ“ė","ģ“ė¼ė©“","ģ“ė","ģ“ė¬ģ“ė¬ķė¤","ģ“ė¬ķ","ģ“ė°","ģ“ė“ģ ėė”","ģ“ė ź² ė§ģ ź²","ģ“ė ź²ėė©“","ģ“ė ź²ė§ķģė©“","ģ“ė źµ¬ė","ģ“ė” ģøķģ¬","ģ“ė„“źø°ź¹ģ§","ģ“ė¦¬ķģ¬","ģ“ė§ķ¼","ģ“ė²","ģ“ė“","ģ“ģ","ģ“ģ“ģ","ģ“ģė¤","ģ“ģ ź°ė¤","ģ“ģ ź°ģ","ģ“ģ ė°ėė”","ģ“ģź°ė¤ė©“","ģ“ģøģė","ģ“ģ©ķģ¬","ģ“ģ ė§ģ¼ė”","ģ“ģ ","ģ“ģ§ė§","ģ“ģŖ½","ģ“ģ²źµ¬","ģ“ģ²ģ”","ģ“ģ²ģ¹ ","ģ“ģ²ķ","ģø ėÆķė¤","ģøģ ","ģ¼","ģ¼ź²ģ“ė¤","ģ¼ź³±","ģ¼ėØ","ģ¼ė","ģ¼ė°ģ ģ¼ė”","ģ¼ģ§ė¼ė","ģģ ķė¦¼ģė¤","ģ
ź°ķģ¬","ģ
ģ„ģģ","ģė°ė¼","ģė¤","ģ","ģźø°","ģźø°ģ§","ģė§ģ","ģģ ","ģ ź¹","ģ ģ","ģ ","ģ ź²","ģ ź²ė§ķ¼","ģ źø°","ģ ģŖ½","ģ ķ¬","ģ ė¶","ģ ģ","ģ ķ","ģ ģģ ė³“ģ","ģ ėģ ģ“ė„“ė¤","ģ ","ģ ź°źø°","ģ ģøķź³ ","ģ”°źø","ģ”°ģ°Ø","ģ”°ģ°Øė","ģ”øģ”ø","ģ¢","ģ¢ģ","ģ¢ģ¢","ģ£¼ė£©ģ£¼ė£©","ģ£¼ģ ķģ§ ģź³ ","ģ¤ģ ėŖ°ėė¤","ģ¤ģėŖØė„øė¤","ģ¤ģģ","ģ¤ģķė","ģ¦ģķģ¬","ģ¦","ģ¦ģ","ģ§ė ģ§","ģ§ė§","ģ§ė§ź³ ","ģ§ģ§ė”","ģŖ½ģ¼ė”","ģ°Øė¼ė¦¬","ģ°ø","ģ°øė","ģ²«ė²ģ§øė”","ģ³","ģ“ģ ģ¼ė”","ģ“ģ ģ¼ė” ė§ķė©“","ģ“ģ ģ¼ė” ė³“ė©“","ģ¹ ","ģ½øģ½ø","ģ¾
ģ¾
","ģæµ","ķė¤","ķģø","ķķ","ķ ķė¤","ķµķģ¬","ķ","ķ¤","ķķ","ķ","ķ","ķ½","ķė ","ķ","ķź²ė ź²ģ“ė¤","ķź²ķė¤","ķź² ėź°","ķź³ ģė¤","ķź³ ģģė¤","ķź³¤ķģė¤","ķźµ¬ė","ķźø° ėė¬øģ","ķźø° ģķģ¬","ķźø°ėķė°","ķźø°ė§ ķė©“","ķźø°ė³“ė¤ė","ķźø°ģ","ķė","ķėė","ķė ź¹ģ","ķė ķøģ“ ė«ė¤","ķėź²ė","ķėź²ė§ ėŖ»ķė¤","ķėź²ģ“ ė«ė¤","ķėė°","ķėė¼ė","ķėė¤","ķėė”ģķ¤ė¤","ķėė”ķė¤","ķė ģ§","ķė ¤ź³ ķė¤","ķė§ķ°ė©“","ķė©“ ķ ģė”","ķė©“ėė¤","ķė©“ģ","ķė¬¼ė©°","ķģ¬źø","ķģ¬ģ¼","ķģė§ģ","ķģ§ ģėė¤ė©“","ķģ§ ģėė”","ķģ§ė§","ķģ§ė§ė¼","ķģ§ė§","ķķ","ķ ź¹ėģ","ķ ģ“ģ ė","ķ ķ","ķė¤ė©“","ķė¤ė©“ ėŖ°ė¼ė","ķė°","ķė§ė","ķģ ģ“ģė¤","ķģ¼ ģ¼ė”ė","ķķėŖ©","ķ ė°ė¦ģ“ė¤","ķ ģź°ģ“ė¤","ķ ģ¤ ģė¤","ķ ģ§ź²½ģ“ė¤","ķ ķģ“ ģė¤","ķ ė","ķ ė§ķė¤","ķ ė§ģ ","ķ ėæ","ķ ģģė¤","ķ ģģģ“","ķ ģ¤ģė¤","ķ ģ§ė¼ė","ķ ģ§ģøģ ","ķØź»","ķ“ėėė¤","ķ“ėģ¢ė¤","ķ“ė“ģ","ķ“ģė ģėė¤","ķ“ģ¼ķė¤","ķ“ģ","ķģ“ģ","ķ„ķė¤","ķ„ķģ¬","ķ„ķ“ģ","ķ","ķź±±","ķķ","ķ","ķķ","ķė”ķė”","ķģģ¼ė” ģ°ģ¬","ķ¹ģ","ķ¹ģ","ķ¼ģ","ķØģ¬","ķģµ","ķ“","ķķ","ķ„","ķģ
ģ“","ļøæ","ļ¼","ļ¼","ļ¼","ļ¼
","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼","ļ¼ ","ļ¼»","ļ¼½","ļ½","ļ½","ļ½","ļ½","ļæ„"],"ku":["Ų¦ŪŁ
Ū","Ų¦ŪŁŪ","Ų¦ŪŁ
","Ų¦ŪŁ","Ų¦ŪŁŲ§Ł","Ų¦ŪŁŪŪ","ŲØŪ","ŲØŪ","ŲØŪŲ¬ŚÆŪ","ŲØŪ","ŲØŪŲØŪ","ŲØŪŲÆŪŁ
","ŲØŪŲ±ŲÆŪŁ
","ŲØŪŲ±ŁŪ","ŲØŪŲ±ŪŁŪ","ŲØŪŲ±ŪŁŪ","ŲØŪŁŲ§Ū","ŲØŪŁ¾ŪŪ","ŲŖŪ","ŲŖŪ","Ų¬ŚÆŪ","ŲÆŁŲ§Ū","ŲÆŁŁ","ŲÆŪ","ŲÆŪŚ©Ų§ŲŖ","ŲÆŪŚÆŪŚµ","Ų³ŪŲ±","ŁŪ","ŁŪ","ŁŪŲØŲ§ŲØŪŲŖ","ŁŪŲØŲ§ŲŖŪ","ŁŪŲØŲ§Ų±ŪŪ","ŁŪŲØŲ±ŪŲŖŪ","ŁŪŲØŁ","ŁŪŲØŪŲ±","ŁŪŲØŪŪŁŪ","ŁŪŲÆŪŁ
","ŁŪŲ±Ū","ŁŪŲ±ŪŚÆŲ§","ŁŪŲ±ŪŁŪ","ŁŪŲ³ŪŲ±","ŁŪŁŲ§ŪŪŁ","ŁŪŁŲ§Ł","ŁŪŁŪŁ","ŁŪŁ","ŁŪŁ¾ŪŁŲ§ŁŪ","ŁŪŚŪŲ±","ŁŪŚÆŪŚµ","Ł
Ł","ŁŲ§Ł","ŁŪŁŲ§Ł","ŁŪŲ±","ŁŪŲ±ŁŪŁŲ§","Ł","ŁŪŚ©","Ł¾Ų§Ų“","Ł¾Ū","Ł¾ŪŲ“","ŚŪŁŲÆ","Ś©Ų±ŲÆ","Ś©Ū","Ū"],"la":["a","ab","ac","ad","at","atque","aut","autem","cum","de","dum","e","erant","erat","est","et","etiam","ex","haec","hic","hoc","in","ita","me","nec","neque","non","per","qua","quae","quam","qui","quibus","quidem","quo","quod","re","rebus","rem","res","sed","si","sic","sunt","tamen","tandem","te","ut","vel"],"lt":["abi","abidvi","abiejose","abiejuose","abiejĆø","abiem","abigaliai","abipus","abu","abudu","ai","ana","anaiptol","anaisiais","anajai","anajam","anajame","anapus","anas","anasai","anasis","anei","aniedvi","anieji","aniesiems","anoji","anojo","anojoje","anokia","anoks","anosiomis","anosioms","anosios","anosiose","anot","ant","antai","anuodu","anuoju","anuosiuose","anuosius","anĆ ja","anĆ jĆ ","anĆ jĆ”","anĆ sias","anĆøjĆø","apie","aplink","ar","arba","argi","arti","aukĆ°ĆØiau","aĆ°","be","bei","beje","bemaĆ¾","bent","bet","betgi","beveik","dar","dargi","daugmaĆ¾","deja","dĆ«ka","dĆ«l","dĆ«lei","dĆ«lto","ech","et","gal","galbĆ»t","galgi","gan","gana","gi","greta","idant","iki","ir","irgi","it","itin","iĆ°","iĆ°ilgai","iĆ°vis","jaisiais","jajai","jajam","jajame","jei","jeigu","ji","jiedu","jiedvi","jieji","jiesiems","jinai","jis","jisai","jog","joji","jojo","jojoje","jokia","joks","josiomis","josioms","josios","josiose","judu","judvi","juk","jumis","jums","jumyse","juodu","juoju","juosiuose","juosius","jus","jĆ ja","jĆ jĆ ","jĆ sias","jĆ”jĆ”","jĆøjĆø","jĆ»s","jĆ»siĆ°kis","jĆ»siĆ°kĆ«","jĆ»sĆø","kad","kada","kadangi","kai","kaip","kaipgi","kas","katra","katras","katriedvi","katruodu","kaĆ¾in","kaĆ¾kas","kaĆ¾katra","kaĆ¾katras","kaĆ¾kokia","kaĆ¾koks","kaĆ¾kuri","kaĆ¾kuris","kiaurai","kiek","kiekvienas","kieno","kita","kitas","kitokia","kitoks","kodĆ«l","kokia","koks","kol","kolei","kone","kuomet","kur","kurgi","kuri","kuriedvi","kuris","kuriuodu","lai","lig","ligi","link","lyg","man","manaisiais","manajai","manajam","manajame","manas","manasai","manasis","mane","manieji","maniesiems","manim","manimi","maniĆ°kis","maniĆ°kĆ«","mano","manoji","manojo","manojoje","manosiomis","manosioms","manosios","manosiose","manuoju","manuosiuose","manuosius","manyje","manĆ ja","manĆ jĆ ","manĆ jĆ”","manĆ sias","manƦs","manĆøjĆø","mat","maĆ¾daug","maĆ¾ne","mes","mudu","mudvi","mumis","mums","mumyse","mus","mĆ»siĆ°kis","mĆ»siĆ°kĆ«","mĆ»sĆø","na","nagi","ne","nebe","nebent","negi","negu","nei","nejau","nejaugi","nekaip","nelyginant","nes","net","netgi","netoli","neva","nors","nuo","nĆ«","o","ogi","oi","paeiliui","pagal","pakeliui","palaipsniui","palei","pas","pasak","paskos","paskui","paskum","pat","pati","patiems","paties","pats","patys","patĆ”","paĆØiais","paĆØiam","paĆØiame","paĆØiu","paĆØiuose","paĆØius","paĆØiĆø","per","pernelyg","pirm","pirma","pirmiau","po","prie","prieĆ°","prieĆ°ais","pro","pusiau","rasi","rodos","sau","savaisiais","savajai","savajam","savajame","savas","savasai","savasis","save","savieji","saviesiems","savimi","saviĆ°kis","saviĆ°kĆ«","savo","savoji","savojo","savojoje","savosiomis","savosioms","savosios","savosiose","savuoju","savuosiuose","savuosius","savyje","savĆ ja","savĆ jĆ ","savĆ jĆ”","savĆ sias","savƦs","savĆøjĆø","skersai","skradĆ¾iai","staĆØiai","su","sulig","ta","tad","tai","taigi","taip","taipogi","taisiais","tajai","tajam","tajame","tamsta","tarp","tarsi","tartum","tarytum","tas","tasai","tau","tavaisiais","tavajai","tavajam","tavajame","tavas","tavasai","tavasis","tave","tavieji","taviesiems","tavimi","taviĆ°kis","taviĆ°kĆ«","tavo","tavoji","tavojo","tavojoje","tavosiomis","tavosioms","tavosios","tavosiose","tavuoju","tavuosiuose","tavuosius","tavyje","tavĆ ja","tavĆ jĆ ","tavĆ jĆ”","tavĆ sias","tavƦs","tavĆøjĆø","taĆØiau","te","tegu","tegul","tiedvi","tieji","ties","tiesiems","tiesiog","tik","tikriausiai","tiktai","toji","tojo","tojoje","tokia","toks","tol","tolei","toliau","tosiomis","tosioms","tosios","tosiose","tu","tuodu","tuoju","tuosiuose","tuosius","turbĆ»t","tĆ ja","tĆ jĆ ","tĆ jĆ”","tĆ sias","tĆøjĆø","tĆ»las","uĆ¾","uĆ¾tat","uĆ¾vis","va","vai","viduj","vidury","vien","vienas","vienokia","vienoks","vietoj","virĆ°","virĆ°uj","virĆ°um","vis","vis dĆ«lto","visa","visas","visgi","visokia","visoks","vos","vĆ«l","vĆ«lgi","ypaĆØ","Ć”","Ć”kypai","Ć”striĆ¾ai","Ć°alia","Ć°e","Ć°i","Ć°iaisiais","Ć°iajai","Ć°iajam","Ć°iajame","Ć°iapus","Ć°iedvi","Ć°ieji","Ć°iesiems","Ć°ioji","Ć°iojo","Ć°iojoje","Ć°iokia","Ć°ioks","Ć°iosiomis","Ć°iosioms","Ć°iosios","Ć°iosiose","Ć°is","Ć°isai","Ć°it","Ć°ita","Ć°itas","Ć°itiedvi","Ć°itokia","Ć°itoks","Ć°ituodu","Ć°iuodu","Ć°iuoju","Ć°iuosiuose","Ć°iuosius","Ć°iĆ ja","Ć°iĆ jĆ ","Ć°iĆ sias","Ć°iĆøjĆø","Ć°tai","Ć°Ć”jĆ”","Ć¾emiau"],"lv":["aiz","ap","apakÅ”","apakÅ”pus","ar","arÄ«","augÅ”pus","bet","bez","bija","biji","biju","bijÄm","bijÄt","bÅ«s","bÅ«si","bÅ«siet","bÅ«sim","bÅ«t","bÅ«Å”u","caur","diemžÄl","diezin","droÅ”i","dÄļ","esam","esat","esi","esmu","gan","gar","iekam","iekams","iekÄm","iekÄms","iekÅ”","iekÅ”pus","ik","ir","it","itin","iz","ja","jau","jeb","jebÅ”u","jel","jo","jÄ","ka","kamÄr","kaut","kolÄ«dz","kopÅ”","kÄ","kļuva","kļuvi","kļuvu","kļuvÄm","kļuvÄt","kļūs","kļūsi","kļūsiet","kļūsim","kļūst","kļūstam","kļūstat","kļūsti","kļūstu","kļūt","kļūŔu","labad","lai","lejpus","lÄ«dz","lÄ«dzko","ne","nebÅ«t","nedz","nekÄ","nevis","nezin","no","nu","nÄ","otrpus","pa","par","pat","pie","pirms","pret","priekÅ”","pÄr","pÄc","starp","tad","tak","tapi","taps","tapsi","tapsiet","tapsim","tapt","tapÄt","tapÅ”u","taÄu","te","tiec","tiek","tiekam","tiekat","tieku","tik","tika","tikai","tiki","tikko","tiklab","tiklÄ«dz","tiks","tiksiet","tiksim","tikt","tiku","tikvien","tikÄm","tikÄt","tikÅ”u","tomÄr","topat","turpretim","turpretÄ«","tÄ","tÄdÄļ","tÄlab","tÄpÄc","un","uz","vai","var","varat","varÄja","varÄji","varÄju","varÄjÄm","varÄjÄt","varÄs","varÄsi","varÄsiet","varÄsim","varÄt","varÄÅ”u","vien","virs","virspus","vis","viÅpus","zem","Ärpus","Å”aipus"],"ms":["abdul","abdullah","acara","ada","adalah","ahmad","air","akan","akhbar","akhir","aktiviti","alam","amat","amerika","anak","anggota","antara","antarabangsa","apa","apabila","april","as","asas","asean","asia","asing","atas","atau","australia","awal","awam","bagaimanapun","bagi","bahagian","bahan","baharu","bahawa","baik","bandar","bank","banyak","barangan","baru","baru-baru","bawah","beberapa","bekas","beliau","belum","berada","berakhir","berbanding","berdasarkan","berharap","berikutan","berjaya","berjumlah","berkaitan","berkata","berkenaan","berlaku","bermula","bernama","bernilai","bersama","berubah","besar","bhd","bidang","bilion","bn","boleh","bukan","bulan","bursa","cadangan","china","dagangan","dalam","dan","dana","dapat","dari","daripada","dasar","datang","datuk","demikian","dengan","depan","derivatives","dewan","di","diadakan","dibuka","dicatatkan","dijangka","diniagakan","dis","disember","ditutup","dolar","dr","dua","dunia","ekonomi","eksekutif","eksport","empat","enam","faedah","feb","global","hadapan","hanya","harga","hari","hasil","hingga","hubungan","ia","iaitu","ialah","indeks","india","indonesia","industri","ini","islam","isnin","isu","itu","jabatan","jalan","jan","jawatan","jawatankuasa","jepun","jika","jualan","juga","julai","jumaat","jumlah","jun","juta","kadar","kalangan","kali","kami","kata","katanya","kaunter","kawasan","ke","keadaan","kecil","kedua","kedua-dua","kedudukan","kekal","kementerian","kemudahan","kenaikan","kenyataan","kepada","kepentingan","keputusan","kerajaan","kerana","kereta","kerja","kerjasama","kes","keselamatan","keseluruhan","kesihatan","ketika","ketua","keuntungan","kewangan","khamis","kini","kira-kira","kita","klci","klibor","komposit","kontrak","kos","kuala","kuasa","kukuh","kumpulan","lagi","lain","langkah","laporan","lebih","lepas","lima","lot","luar","lumpur","mac","mahkamah","mahu","majlis","makanan","maklumat","malam","malaysia","mana","manakala","masa","masalah","masih","masing-masing","masyarakat","mata","media","mei","melalui","melihat","memandangkan","memastikan","membantu","membawa","memberi","memberikan","membolehkan","membuat","mempunyai","menambah","menarik","menawarkan","mencapai","mencatatkan","mendapat","mendapatkan","menerima","menerusi","mengadakan","mengambil","mengenai","menggalakkan","menggunakan","mengikut","mengumumkan","mengurangkan","meningkat","meningkatkan","menjadi","menjelang","menokok","menteri","menunjukkan","menurut","menyaksikan","menyediakan","mereka","merosot","merupakan","mesyuarat","minat","minggu","minyak","modal","mohd","mudah","mungkin","naik","najib","nasional","negara","negara-negara","negeri","niaga","nilai","nov","ogos","okt","oleh","operasi","orang","pada","pagi","paling","pameran","papan","para","paras","parlimen","parti","pasaran","pasukan","pegawai","pejabat","pekerja","pelabur","pelaburan","pelancongan","pelanggan","pelbagai","peluang","pembangunan","pemberita","pembinaan","pemimpin","pendapatan","pendidikan","penduduk","penerbangan","pengarah","pengeluaran","pengerusi","pengguna","pengurusan","peniaga","peningkatan","penting","peratus","perdagangan","perdana","peringkat","perjanjian","perkara","perkhidmatan","perladangan","perlu","permintaan","perniagaan","persekutuan","persidangan","pertama","pertubuhan","pertumbuhan","perusahaan","peserta","petang","pihak","pilihan","pinjaman","polis","politik","presiden","prestasi","produk","program","projek","proses","proton","pukul","pula","pusat","rabu","rakan","rakyat","ramai","rantau","raya","rendah","ringgit","rumah","sabah","sahaja","saham","sama","sarawak","satu","sawit","saya","sdn","sebagai","sebahagian","sebanyak","sebarang","sebelum","sebelumnya","sebuah","secara","sedang","segi","sehingga","sejak","sekarang","sektor","sekuriti","selain","selama","selasa","selatan","selepas","seluruh","semakin","semalam","semasa","sementara","semua","semula","sen","sendiri","seorang","sepanjang","seperti","sept","september","serantau","seri","serta","sesi","setiap","setiausaha","sidang","singapura","sini","sistem","sokongan","sri","sudah","sukan","suku","sumber","supaya","susut","syarikat","syed","tahap","tahun","tan","tanah","tanpa","tawaran","teknologi","telah","tempat","tempatan","tempoh","tenaga","tengah","tentang","terbaik","terbang","terbesar","terbuka","terdapat","terhadap","termasuk","tersebut","terus","tetapi","thailand","tiada","tidak","tiga","timbalan","timur","tindakan","tinggi","tun","tunai","turun","turut","umno","unit","untuk","untung","urus","usaha","utama","walaupun","wang","wanita","wilayah","yang"],"mr":["ą¤
ą¤§ą¤æą¤","ą¤
ą¤Øą„ą¤","ą¤
ą¤¶ą„","ą¤
ą¤øą¤²ą¤Æą¤¾ą¤ą„","ą¤
ą¤øą¤²ą„ą¤²ą„ą¤Æą¤¾","ą¤
ą¤øą¤¾","ą¤
ą¤øą„ą¤Ø","ą¤
ą¤øą„","ą¤ą¤","ą¤ą¤£ą¤æ","ą¤ą¤¤ą¤¾","ą¤ą¤Ŗą¤²ą„ą¤Æą¤¾","ą¤ą¤²ą¤¾","ą¤ą¤²ą„","ą¤ą¤²ą„","ą¤ą¤¹ą„","ą¤ą¤¹ą„ą¤¤","ą¤ą¤","ą¤ą¤ą¤¾","ą¤ą¤®ą„","ą¤ą¤°ą¤£ą¤Æą¤¾ą¤¤","ą¤ą¤°ą„ą¤Ø","ą¤ą¤¾","ą¤ą¤¾ą¤®","ą¤ą¤¾ą¤Æ","ą¤ą¤¾ą¤¹ą„","ą¤ą¤æą¤µą¤¾","ą¤ą„","ą¤ą„ą¤²ą¤¾","ą¤ą„ą¤²ą„","ą¤ą„ą¤²ą„","ą¤ą„ą¤ą„","ą¤ą„ą¤²ą„ą¤Æą¤¾","ą¤ą„ą¤ą¤Ø","ą¤ą¤¾ą¤¤","ą¤ą¤¾ą¤²ą¤¾","ą¤ą¤¾ą¤²ą„","ą¤ą¤¾ą¤²ą„","ą¤ą¤¾ą¤²ą„ą¤²ą„ą¤Æą¤¾","ą¤ą¤¾","ą¤”ą„","ą¤¤ą¤°","ą¤¤ą¤°ą„","ą¤¤ą¤øą„ą¤","ą¤¤ą¤¾","ą¤¤ą„","ą¤¤ą„ą¤Ø","ą¤¤ą„","ą¤¤ą„","ą¤¤ą„ą¤Æą¤¾","ą¤¤ą„ą¤Æą¤¾ą¤ą¤¾","ą¤¤ą„ą¤Æą¤¾ą¤ą„","ą¤¤ą„ą¤Æą¤¾ą¤ą„ą¤Æą¤¾","ą¤¤ą„ą¤Æą¤¾ą¤Øą¤¾","ą¤¤ą„ą¤Æą¤¾ą¤Øą„","ą¤¤ą„ą¤Æą¤¾ą¤®ą„ą¤³ą„","ą¤¤ą„ą¤°ą„","ą¤¦ą¤æą¤²ą„","ą¤¦ą„ą¤Ø","ą¤Ø","ą¤Øą¤¾ą¤¹ą„","ą¤Øą¤æą¤°ą„ą¤£ą„ą¤Æ","ą¤Ŗą¤£","ą¤Ŗą¤®","ą¤Ŗą¤°ą¤Æą¤¤ą¤Ø","ą¤Ŗą¤¾ą¤ą„ą¤²","ą¤®","ą¤®ą¤¾ą¤¤ą„ą¤°","ą¤®ą¤¾ą¤¹ą¤æą¤¤ą„","ą¤®ą„","ą¤®ą„ą¤¬ą„","ą¤®ą„ą¤¹ą¤£ą¤ą„","ą¤®ą„ą¤¹ą¤£ą¤¾ą¤²ą„","ą¤®ą„ą¤¹ą¤£ą„ą¤Ø","ą¤Æą¤¾","ą¤Æą¤¾ą¤ą¤¾","ą¤Æą¤¾ą¤ą„","ą¤Æą¤¾ą¤ą„ą¤Æą¤¾","ą¤Æą¤¾ą¤Øą¤¾","ą¤Æą¤¾ą¤Øą„","ą¤Æą„ą¤£ą¤¾ą¤°","ą¤Æą„ą¤¤","ą¤Æą„ą¤„ą„ą¤²","ą¤Æą„ą¤„ą„","ą¤²ą¤¾ą¤","ą¤µ","ą¤µą„ą¤Æą¤ą¤¤","ą¤øą¤°ą„ą¤µ","ą¤øą¤¾ą¤ą¤æą¤¤ą„ą¤²ą„","ą¤øą„ą¤°ą„","ą¤¹ą¤ą¤¾ą¤°","ą¤¹ą¤¾","ą¤¹ą„","ą¤¹ą„","ą¤¹ą„ą¤£ą¤¾ą¤°","ą¤¹ą„ą¤¤","ą¤¹ą„ą¤¤ą¤¾","ą¤¹ą„ą¤¤ą„","ą¤¹ą„ą¤¤ą„"],"no":["alle","andre","arbeid","at","av","bare","begge","ble","blei","bli","blir","blitt","bort","bra","bruke","bĆ„de","bĆ„e","da","de","deg","dei","deim","deira","deires","dem","den","denne","der","dere","deres","det","dette","di","din","disse","ditt","du","dykk","dykkar","dĆ„","eg","ein","eit","eitt","eller","elles","en","ene","eneste","enhver","enn","er","et","ett","etter","folk","for","fordi","forsĆ»ke","fra","fĆ„","fĆør","fĆ»r","fĆ»rst","gjorde","gjĆ»re","god","gĆ„","ha","hadde","han","hans","har","hennar","henne","hennes","her","hjĆ„","ho","hoe","honom","hoss","hossen","hun","hva","hvem","hver","hvilke","hvilken","hvis","hvor","hvordan","hvorfor","i","ikke","ikkje","ingen","ingi","inkje","inn","innen","inni","ja","jeg","kan","kom","korleis","korso","kun","kunne","kva","kvar","kvarhelst","kven","kvi","kvifor","lage","lang","lik","like","makt","man","mange","me","med","medan","meg","meget","mellom","men","mens","mer","mest","mi","min","mine","mitt","mot","mye","mykje","mĆ„","mĆ„te","navn","ned","nei","no","noe","noen","noka","noko","nokon","nokor","nokre","ny","nĆ„","nĆ„r","og","ogsĆ„","om","opp","oss","over","part","punkt","pĆ„","rett","riktig","samme","sant","seg","selv","si","sia","sidan","siden","sin","sine","sist","sitt","sjĆøl","skal","skulle","slik","slutt","so","som","somme","somt","start","stille","sĆ„","sĆ„nn","tid","til","tilbake","tilstand","um","under","upp","ut","uten","var","vart","varte","ved","verdi","vere","verte","vi","vil","ville","vite","vore","vors","vort","vĆ„r","vƦre","vƦrt","vƶre","vƶrt","Ć„"],"fa":["!",",",".",":",";","Ų","Ų","Ų","Ų¢ŲØŲ§ŲÆ","Ų¢Ų±Ł","Ų¢Ų±Ū","Ų¢Ł
ŲÆ","Ų¢Ł
ŲÆŁ","Ų¢Ł","Ų¢ŁŲ§Ł","Ų¢ŁŲ¬Ų§","Ų¢ŁŲ·ŁŲ±","Ų¢ŁŁŲÆŲ±","Ų¢ŁŁŁ","Ų¢ŁŁŲ§","Ų¢ŁŚŁ","Ų¢ŁŚ©Ł","Ų¢ŁŲ±ŲÆ","Ų¢ŁŲ±ŲÆŁ","Ų¢ŁŲÆ","Ų¢Ū","Ų¢ŪŲ§","Ų¢ŪŁŲÆ","Ų§ŲŖŁŲ§ŁŲ§","Ų§Ų«Ų±Ł","Ų§ŲŲŖŲ±Ų§Ł
Ų§","Ų§ŲŲŖŁ
Ų§ŁŲ§","Ų§Ų®ŪŲ±","Ų§Ų±Ū","Ų§Ų²","Ų§Ų²Ų¬Ł
ŁŁ","Ų§Ų³Ų§Ų³Ų§","Ų§Ų³ŲŖ","Ų§Ų³ŲŖŁŲ§ŲÆ","Ų§Ų³ŲŖŁŲ§ŲÆŁ","Ų§Ų“","Ų§Ų“Ś©Ų§Ų±Ų§","Ų§ŲµŁŲ§","Ų§ŲµŁŁŲ§","Ų§Ų¹ŁŲ§Ł
","Ų§ŲŗŁŲØ","Ų§ŁŁŁŁ","Ų§ŁŲ§Ł","Ų§ŁŲØŲŖŁ","Ų§ŁŲØŲŖŁŁ","Ų§Ł
","Ų§Ł
Ų§","Ų§Ł
Ų±ŁŲ²","Ų§Ł
Ų±ŁŲ²Ł","Ų§Ł
Ų³Ų§Ł","Ų§Ł
Ų“ŲØ","Ų§Ł
ŁŲ±","Ų§Ł","Ų§ŁŲ¬Ų§Ł
","Ų§ŁŲÆ","Ų§ŁŲ“Ų§Ų§ŁŁŁ","Ų§ŁŲµŲ§ŁŲ§","Ų§ŁŲ·ŁŲ±","Ų§ŁŁŲÆŲ±","Ų§ŁŁŲ§","Ų§ŁŚŁŲ§Ł","Ų§ŁŚ©Ł","Ų§ŁŚÆŲ§Ų±","Ų§Ł","Ų§ŁŁ","Ų§ŁŁŲ§","Ų§Ł","Ų§ŁŲ“Ų§Ł","Ų§ŁŁ
","Ų§ŁŁ","Ų§ŁŁŁŁ","Ų§Ś©Ų«Ų±Ų§","Ų§Ś©ŁŁŁ","Ų§ŚÆŲ±","Ų§Ū","Ų§ŪŲ§","Ų§ŪŲÆ","Ų§ŪŲ“Ų§Ł","Ų§ŪŁ
","Ų§ŪŁ","Ų§ŪŁŲ¬Ų§","Ų§ŪŁŲÆ","Ų§ŪŁŲ·ŁŲ±","Ų§ŪŁŁŲÆŲ±","Ų§ŪŁŁŲ§","Ų§ŪŁŚŁŪŁ","Ų§ŪŁŚ©","Ų§ŪŁŚ©Ł","Ų§ŪŁŚÆŁŁŁ","ŲØŲ§","ŲØŲ§Ų±","ŲØŲ§Ų±Ų©","ŲØŲ§Ų±Ł","ŲØŲ§Ų±ŁŲ§","ŲØŲ§Ų²","ŲØŲ§Ų²ŁŁ
","ŲØŲ§Ų“","ŲØŲ§Ų“ŲÆ","ŲØŲ§Ų“Ł
","ŲØŲ§Ų“ŁŲÆ","ŲØŲ§Ų“ŁŁ
","ŲØŲ§Ų“Ū","ŲØŲ§Ų“ŪŲÆ","ŲØŲ§Ų“ŪŁ
","ŲØŲ§ŁŲ§","ŲØŲ§ŁŲ§Ų®Ų±Ł","ŲØŲ§ŁŲ§ŪŁ","ŲØŲ§ŁŲ·ŲØŲ¹","ŲØŲ§ŁŲÆ","ŲØŲ§ŪŲÆ","ŲØŲŖŁŲ§Ł","ŲØŲŖŁŲ§ŁŲÆ","ŲØŲŖŁŲ§ŁŪ","ŲØŲŖŁŲ§ŁŪŁ
","ŲØŲ®Ų“","ŲØŲ®Ų“Ū","ŲØŲ®ŁŲ§Ł","ŲØŲ®ŁŲ§ŁŲÆ","ŲØŲ®ŁŲ§ŁŁ
","ŲØŲ®ŁŲ§ŁŁŲÆ","ŲØŲ®ŁŲ§ŁŪ","ŲØŲ®ŁŲ§ŁŪŲÆ","ŲØŲ®ŁŲ§ŁŪŁ
","ŲØŲÆ","ŲØŲÆŁŁ","ŲØŲ±","ŲØŲ±Ų§ŲØŲ±","ŲØŲ±Ų§ŲØŲ±Ł","ŲØŲ±Ų§ŲŲŖŪ","ŲØŲ±Ų§Ų³Ų§Ų³","ŲØŲ±Ų§Ų³ŲŖŪ","ŲØŲ±Ų§Ł","ŲØŲ±Ų§Ū","ŲØŲ±Ų§ŪŁ","ŲØŲ±Ų®ŁŲ±ŲÆŲ§Ų±","ŲØŲ±Ų®Ł","ŲØŲ±Ų®Ū","ŲØŲ±ŲÆŲ§Ų±Ł","ŲØŲ±Ų¹Ś©Ų³","ŲØŲ±ŁŲ²","ŲØŲ²Ų±ŚÆ","ŲØŲ²ŁŲÆŪ","ŲØŲ³Ų§","ŲØŲ³ŁŲ§Ų±","ŲØŲ³ŁŲ§Ų±Ł","ŲØŲ³ŪŲ§Ų±","ŲØŲ³ŪŲ§Ų±Ū","ŲØŲ·ŁŲ±","ŲØŲ¹ŲÆ","ŲØŲ¹ŲÆŲ§","ŲØŲ¹ŲÆŁŲ§","ŲØŲ¹Ų±Ū","ŲØŲ¹Ų¶Ų§","ŲØŲ¹Ų¶Ł","ŲØŁŲ§ŁŲ§ŲµŁŁ","ŲØŁŁŁ","ŲØŁŁ","ŲØŁŚ©Ł","ŲØŁŪ","ŲØŁŲ§ŲØŲ±Ų§ŁŁ","ŲØŁŲ§ŲØŲ±Ų§ŪŁ","ŲØŁŲÆŁ","ŲØŁ","ŲØŁŲŖŲ±","ŲØŁŲŖŲ±ŁŁ","ŲØŁŲÆ","ŲØŁŲÆŁ
","ŲØŁŲÆŁ","ŲØŁŲÆŁŲÆ","ŲØŁŲÆŁ","ŲØŁŲÆŪ","ŲØŁŲÆŪŲÆ","ŲØŁŲÆŪŁ
","ŲØŁŪŚŁ","ŲØŁ","ŲØŁŲ³ŲŖ","ŲØŁŲ“","ŲØŁŲ“ŲŖŲ±","ŲØŁŲ“ŲŖŲ±Ł","ŲØŁŁ","ŲØŚ©Ł","ŲØŚ©ŁŲÆ","ŲØŚ©ŁŁ
","ŲØŚ©ŁŁŲÆ","ŲØŚ©ŁŪ","ŲØŚ©ŁŪŲÆ","ŲØŚ©ŁŪŁ
","ŲØŚÆŁ","ŲØŚÆŁŪŲÆ","ŲØŚÆŁŪŁ
","ŲØŚÆŁŪŁŲÆ","ŲØŚÆŁŪŪ","ŲØŚÆŁŪŪŲÆ","ŲØŚÆŁŪŪŁ
","ŲØŚÆŪŲ±","ŲØŚÆŪŲ±ŲÆ","ŲØŚÆŪŲ±Ł
","ŲØŚÆŪŲ±ŁŲÆ","ŲØŚÆŪŲ±Ū","ŲØŚÆŪŲ±ŪŲÆ","ŲØŚÆŪŲ±ŪŁ
","ŲØŪ","ŲØŪŲ§","ŲØŪŲ§ŲØ","ŲØŪŲ§ŲØŲÆ","ŲØŪŲ§ŲØŁ
","ŲØŪŲ§ŲØŁŲÆ","ŲØŪŲ§ŲØŪ","ŲØŪŲ§ŲØŪŲÆ","ŲØŪŲ§ŲØŪŁ
","ŲØŪŲ§ŁŲ±","ŲØŪŲ§ŁŲ±ŲÆ","ŲØŪŲ§ŁŲ±Ł
","ŲØŪŲ§ŁŲ±ŁŲÆ","ŲØŪŲ§ŁŲ±Ū","ŲØŪŲ§ŁŲ±ŪŲÆ","ŲØŪŲ§ŁŲ±ŪŁ
","ŲØŪŲ§ŪŲÆ","ŲØŪŲ§ŪŁ
","ŲØŪŲ§ŪŁŲÆ","ŲØŪŲ§ŪŪ","ŲØŪŲ§ŪŪŲÆ","ŲØŪŲ§ŪŪŁ
","ŲØŪŲ±ŁŁ","ŲØŪŲ±ŁŁŁ","ŲØŪŲ“","ŲØŪŲ“ŲŖŲ±","ŲØŪŲ“ŲŖŲ±Ū","ŲØŪŁ","ŲŖ","ŲŖŲ§","ŲŖŲ§Ų²Ł","ŲŖŲ§ŁŁŁŁ","ŲŖŲ§Ł","ŲŖŲ§Ś©ŁŁŁ","ŲŖŲŲŖ","ŲŖŲ±","ŲŖŲ± ŲØŲ±Ų§Ų³Ų§Ų³","ŲŖŲ±ŁŁ","ŲŖŁŲ±ŪŲØŲ§","ŲŖŁŁŪŲŲ§","ŲŖŁ
Ų§Ł
","ŲŖŁ
Ų§Ł
Ų§","ŲŖŁ
Ų§Ł
Ł","ŲŖŁŁŲ§","ŲŖŁ","ŲŖŁŲ§ŁŲÆ","ŲŖŁŲ§ŁŲ³ŲŖ","ŲŖŁŲ§ŁŲ³ŲŖŁ
","ŲŖŁŲ§ŁŲ³ŲŖŁ","ŲŖŁŲ§ŁŲ³ŲŖŁŲÆ","ŲŖŁŲ§ŁŲ³ŲŖŁ","ŲŖŁŲ§ŁŲ³ŲŖŪ","ŲŖŁŲ§ŁŲ³ŲŖŪŁ
","ŲŖŁŲ§ŁŁ
","ŲŖŁŲ§ŁŁŲÆ","ŲŖŁŲ§ŁŪ","ŲŖŁŲ§ŁŪŲÆ","ŲŖŁŲ§ŁŪŁ
","ŲŖŁŲ³Ų·","ŲŖŁŁŁ","ŲŖŁŪŁ","Ų«Ų§ŁŪŲ§","Ų¬Ų§","Ų¬Ų§Ł","Ų¬Ų§ŁŁ","Ų¬Ų§Ū","Ų¬ŲÆŲ§","Ų¬ŲÆŁŲÆ","Ų¬ŲÆŪŲÆ","Ų¬Ų±ŁŲ§Ł","Ų¬Ų±ŪŲ§Ł","Ų¬Ų²","Ų¬ŁŁŚÆŁŲ±Ł","Ų¬ŁŁŪŁ","Ų¬Ł
Ų¹Ų§","Ų¬ŁŲ§Ų","Ų¬ŁŲŖ","ŲŲ§Ų¶Ų±","ŲŲ§Ł","ŲŲ§ŁŲ§","ŲŲŖŁ
Ų§","ŲŲŖŁ","ŲŲŖŪ","ŲŲÆŲ§Ś©Ų«Ų±","ŲŲÆŁŲÆŲ§","ŲŲÆŁŲÆŁ","ŲŁ","Ų®Ų§Ų±Ų¬Ł","Ų®ŲØ","Ų®ŲÆŁ
Ų§ŲŖ","Ų®ŲµŁŲµŲ§","Ų®ŁŲ§ŲµŁ","Ų®ŁŲ§Ų³ŲŖ","Ų®ŁŲ§Ų³ŲŖŁ
","Ų®ŁŲ§Ų³ŲŖŁ","Ų®ŁŲ§Ų³ŲŖŁŲÆ","Ų®ŁŲ§Ų³ŲŖŁ","Ų®ŁŲ§Ų³ŲŖŪ","Ų®ŁŲ§Ų³ŲŖŪŲÆ","Ų®ŁŲ§Ų³ŲŖŪŁ
","Ų®ŁŲ§ŁŲÆ","Ų®ŁŲ§ŁŁ
","Ų®ŁŲ§ŁŁŲÆ","Ų®ŁŲ§ŁŁŁ
","Ų®ŁŲ§ŁŪ","Ų®ŁŲ§ŁŪŲÆ","Ų®ŁŲ§ŁŪŁ
","Ų®ŁŲØ","Ų®ŁŲÆ","Ų®ŁŲÆŲŖ","Ų®ŁŲÆŲŖŲ§Ł","Ų®ŁŲÆŲ“","Ų®ŁŲÆŲ“Ų§Ł","Ų®ŁŲÆŁ
","Ų®ŁŲÆŁ
Ų§Ł","Ų®ŁŲ“ŲØŲ®ŲŖŲ§ŁŁ","Ų®ŁŁŲ“","Ų®ŁŪŲ“","Ų®ŁŪŲ“ŲŖŁ","Ų®ŪŲ§Ł","Ų®ŪŲ±","Ų®ŪŁŪ","ŲÆŲ§ŲÆ","ŲÆŲ§ŲÆŁ
","ŲÆŲ§ŲÆŁ","ŲÆŲ§ŲÆŁŲÆ","ŲÆŲ§ŲÆŁ","ŲÆŲ§ŲÆŪ","ŲÆŲ§ŲÆŪŲÆ","ŲÆŲ§ŲÆŪŁ
","ŲÆŲ§Ų±","ŲÆŲ§Ų±ŲÆ","ŲÆŲ§Ų±Ł
","ŲÆŲ§Ų±ŁŲÆ","ŲÆŲ§Ų±ŁŁ
","ŲÆŲ§Ų±Ū","ŲÆŲ§Ų±ŪŲÆ","ŲÆŲ§Ų±ŪŁ
","ŲÆŲ§Ų“ŲŖ","ŲÆŲ§Ų“ŲŖŁ
","ŲÆŲ§Ų“ŲŖŁ","ŲÆŲ§Ų“ŲŖŁŲÆ","ŲÆŲ§Ų“ŲŖŁ","ŲÆŲ§Ų“ŲŖŪ","ŲÆŲ§Ų“ŲŖŪŲÆ","ŲÆŲ§Ų“ŲŖŪŁ
","ŲÆŲ§ŁŲ³ŲŖ","ŲÆŲ§ŁŁŲÆ","ŲÆŲ§ŪŁ
","ŲÆŲ§ŪŁ
Ų§","ŲÆŲ±","ŲÆŲ±ŲØŲ§Ų±Ł","ŲÆŲ±Ł
Ų¬Ł
ŁŲ¹","ŲÆŲ±ŁŁ","ŲÆŲ±ŪŲŗ","ŲÆŁŪŁŲ§","ŲÆŁŲØŲ§ŁŁ","ŲÆŁ","ŲÆŁŲÆ","ŲÆŁŁ
","ŲÆŁŁŲÆ","ŲÆŁŪ","ŲÆŁŪŲÆ","ŲÆŁŪŁ
","ŲÆŁ","ŲÆŁŲØŲ§Ų±Ł","ŲÆŁŁ
","ŲÆŁŲÆŁ","ŲÆŁŲ±ŁŲ²","ŲÆŁŚÆŲ±","ŲÆŁŚÆŲ±Ų§Ł","ŲÆŁŚÆŲ±Ł","ŲÆŪŲ±","ŲÆŪŲ±ŁŲ²","ŲÆŪŚÆŲ±","ŲÆŪŚÆŲ±Ų§Ł","ŲÆŪŚÆŲ±Ū","Ų±Ų§","Ų±Ų§ŲŲŖ","Ų±Ų§Ų³Ų§","Ų±Ų§Ų³ŲŖŪ","Ų±Ų§Ł","Ų±Ų³Ł
Ų§","Ų±Ų³ŪŲÆ","Ų±ŁŲŖ","Ų±ŁŲŖŁ","Ų±Ł","Ų±ŁŲØ","Ų±ŁŲ²","Ų±ŁŲ²Ų§ŁŁ","Ų±ŁŲ²ŁŲ§Ł","Ų±ŁŁ","Ų±ŁŪ","Ų±ŁŪŁ","Ų±ŁŲ²Ł","Ų²Ł
Ų§Ł","Ų²Ł
Ų§ŁŪ","Ų²Ł
ŪŁŁ","Ų²ŁŲÆ","Ų²ŁŲ§ŲÆ","Ų²ŁŲ±","Ų²ŁŲ±Ų§","Ų²ŪŲ±","Ų²ŪŲ±Ł","Ų³Ų§ŲØŁ","Ų³Ų§Ų®ŲŖŁ","Ų³Ų§Ų²Ł","Ų³Ų§ŁŲ§ŁŁ","Ų³Ų§ŁŪŲ§ŁŁ","Ų³Ų§ŪŲ±","Ų³Ų±Ų§Ų³Ų±","Ų³Ų±Ų§ŁŲ¬Ų§Ł
","Ų³Ų±ŪŲ¹Ų§","Ų³Ų±ŪŁ","Ų³Ų¹Ł","Ų³Ł
ŲŖŁ","Ų³ŁŁ
","Ų³ŁŁ","Ų³ŁŪ","Ų³ŁŪŁ","Ų³Ł¾Ų³","Ų“Ų§Ł","Ų“Ų§ŁŲÆ","Ų“Ų§ŪŲÆ","Ų“Ų®ŲµŲ§","Ų“ŲÆ","Ų“ŲÆŁ
","Ų“ŲÆŁ","Ų“ŲÆŁŲÆ","Ų“ŲÆŁ","Ų“ŲÆŪ","Ų“ŲÆŪŲÆ","Ų“ŲÆŪŲÆŲ§","Ų“ŲÆŪŁ
","Ų“Ų“","Ų“Ų“ ŁŲÆŲ§Ų“ŲŖŁ","Ų“Ł
Ų§","Ų“ŁŲ§Ų³Ł","Ų“ŁŲÆ","Ų“ŁŁ
","Ų“ŁŁŲÆ","Ų“ŁŁŲÆŁ","Ų“ŁŪ","Ų“ŁŪŲÆ","Ų“ŁŪŁ
","ŲµŲ±ŁŲ§","ŲµŁŲ±ŲŖ","Ų¶ŲÆŁŁ","Ų¶ŲÆŁŁ","Ų¶Ł
Ł","Ų·ŲØŲ¹Ų§","Ų·ŲØŁŁ","Ų·ŲØŪŲ¹ŲŖŲ§","Ų·Ų±Ł","Ų·Ų±ŁŁ","Ų·Ų±ŪŁ","Ų·ŁŲ±","Ų·Ł","Ų·Ū","ŲøŲ§ŁŲ±Ų§","Ų¹ŲÆŁ
","Ų¹ŁŲØŁ","Ų¹ŁŁŲŖŁ","Ų¹ŁŪŁ","Ų¹Ł
ŲÆŲ§","Ų¹Ł
ŲÆŲŖŲ§","Ų¹Ł
Ł","Ų¹Ł
ŁŲ§","Ų¹ŁŁŲ§Ł","Ų¹ŁŁŲ§ŁŁ","ŲŗŲ§ŁŲØŲ§","ŲŗŁŲ±","ŲŗŪŲ±","ŁŲ±ŲÆŲ§","ŁŲ¹ŁŲ§","ŁŁŲ·","ŁŁŲ±","ŁŁŁ","ŁŲ§ŲØŁ","ŁŲØŁ","ŁŲØŁŲ§","ŁŲÆŲ±Ū","ŁŲµŲÆŁ","ŁŲ·Ų¹Ų§","ŁŲ±ŲÆ","ŁŲ±ŲÆŁ
","ŁŲ±ŲÆŁ","ŁŲ±ŲÆŁŲÆ","ŁŲ±ŲÆŁ","ŁŲ³Ł","ŁŁ","ŁŁ
ŲŖŲ±","ŁŁŲÆ","ŁŁŁ
","ŁŁŁŲÆ","ŁŁŁŲÆ","ŁŁŁŁ
","ŁŁ","ŁŲ§Ų§ŁŁ","ŁŲ·ŁŲ§","ŁŲ·ŁŲ§Ł","Ł
Ų§","Ł
Ų§Ł","Ł
Ų§ŁŁŲÆ","Ł
Ų§ŁŁŲÆŁ","Ł
ŲØŲ§ŲÆŲ§","Ł
ŲŖŲ§Ų³ŁŲ§ŁŁ","Ł
ŲŖŲ¹Ų§ŁŲØŲ§","Ł
Ų«Ł","Ł
Ų«ŁŲ§","Ł
Ų«ŁŁ","Ł
Ų¬Ų§ŁŪ","Ł
Ų¬ŲÆŲÆŲ§","Ł
Ų¬Ł
ŁŲ¹Ų§","Ł
Ų®ŲŖŁŁ","Ł
ŲÆŲ§Ł
","Ł
ŲÆŲŖ","Ł
ŲÆŁŲŖŪ","Ł
Ų±ŲÆŁ
","Ł
Ų±Ų³Ū","Ł
Ų³ŲŖŁŪŁ
Ų§","Ł
Ų³ŁŁ
Ų§","Ł
Ų·Ł
ŪŁŲ§","Ł
Ų¹Ł
ŁŁŲ§","Ł
ŁŲ§ŲØŁ","Ł
Ł
Ś©Ł","Ł
Ł","Ł
ŁŲ§Ų±ŲÆ","Ł
ŁŲ±ŲÆ","Ł
ŁŁŲŖŲ§","Ł
Ł","Ł
ŁŁŁŲ§Ų±ŲÆ","Ł
ŁŁŁŁŁ","Ł
ŚÆŲ±","Ł
Ū","Ł
Ū Ų“ŁŲÆ","Ł
ŪŲ§Ł","Ł
ŪāŲ±Ų³ŲÆ","Ł
ŪāŲ±ŁŲÆ","Ł
ŪāŲ“ŁŲÆ","Ł
ŪāŚ©ŁŪŁ
","ŁŲ§Ų“Ł","ŁŲ§Ł
","ŁŲ§ŚÆŲ§Ł","ŁŲ§ŚÆŁŲ§Ł","ŁŲ§ŚÆŁŲ§ŁŪ","ŁŲØŲ§ŁŲÆ","ŁŲØŲ§ŪŲÆ","ŁŲØŁŲÆ","ŁŲ®Ų³ŲŖ","ŁŲ®Ų³ŲŖŁŁ","ŁŲ®ŁŲ§ŁŲÆ","ŁŲ®ŁŲ§ŁŁ
","ŁŲ®ŁŲ§ŁŁŲÆ","ŁŲ®ŁŲ§ŁŪ","ŁŲ®ŁŲ§ŁŪŲÆ","ŁŲ®ŁŲ§ŁŪŁ
","ŁŲÆŲ§Ų±ŲÆ","ŁŲÆŲ§Ų±Ł
","ŁŲÆŲ§Ų±ŁŲÆ","ŁŲÆŲ§Ų±Ū","ŁŲÆŲ§Ų±ŪŲÆ","ŁŲÆŲ§Ų±ŪŁ
","ŁŲÆŲ§Ų“ŲŖ","ŁŲÆŲ§Ų“ŲŖŁ
","ŁŲÆŲ§Ų“ŲŖŁŲÆ","ŁŲÆŲ§Ų“ŲŖŁ","ŁŲÆŲ§Ų“ŲŖŪ","ŁŲÆŲ§Ų“ŲŖŪŲÆ","ŁŲÆŲ§Ų“ŲŖŪŁ
","ŁŲ²ŲÆŁŁ","ŁŲ²ŲÆŁ","ŁŲ²ŲÆŪŚ©Ł","ŁŲ³ŲØŲŖŲ§","ŁŲ“Ų§Ł","ŁŲ“ŲÆŁ","ŁŲøŁŲ±","ŁŲøŪŲ±","ŁŁŲ±ŲÆŁ","ŁŁ
Ų§ŁŲÆ","ŁŁ
Ł","ŁŁ
Ū","ŁŁ
ŪāŲ“ŁŲÆ","ŁŁ","ŁŁŲ§ŪŲŖŲ§","ŁŁŲ¹","ŁŁŲ¹Ł","ŁŁŲ¹Ū","ŁŁŲ²","ŁŁŲ³ŲŖ","ŁŚÆŲ§Ł","ŁŪŲ²","ŁŪŲ³ŲŖ","ŁŲ§","ŁŲ§Ł","ŁŲ§ŁŁ","ŁŲ§Ū","ŁŲ§ŪŪ","ŁŲØŚ","ŁŲ±","ŁŲ±ŚŁ","ŁŲ±ŚÆŲ²","ŁŲ²Ų§Ų±","ŁŲ³ŲŖ","ŁŲ³ŲŖŁ
","ŁŲ³ŲŖŁŲÆ","ŁŲ³ŲŖŁŁ
","ŁŲ³ŲŖŪ","ŁŲ³ŲŖŪŲÆ","ŁŲ³ŲŖŪŁ
","ŁŁŲŖ","ŁŁ
","ŁŁ
Ų§Ł","ŁŁ
Ł","ŁŁ
ŁŲ§Ų±Ł","ŁŁ
ŁŁ","ŁŁ
ŚŁŲ§Ł","ŁŁ
ŚŁŁŁ","ŁŁ
ŚŁŪŁ","ŁŁ
ŚŁŁ","ŁŁ
ŪŲ“Ł","ŁŁ
ŪŁ","ŁŁŁŲ²","ŁŁŚÆŲ§Ł
","ŁŁŚÆŲ§Ł
Ł","ŁŁŚÆŲ§Ł
Ū","ŁŁŚ","ŁŪŚ","ŁŪŚŚÆŲ§Ł","Ł","ŁŲ§ŁŲ¹Ų§","ŁŲ§ŁŲ¹Ū","ŁŲ¬ŁŲÆ","ŁŲ³Ų·Ł","ŁŲ¶Ų¹","ŁŁŲŖŁ","ŁŁŲŖŪ","ŁŁŲŖŪŚ©Ł","ŁŁŪ","ŁŁ","ŁŚÆŁ","ŁŪ","ŁŪŚŁ","ŁŲ§","ŁŲ§ŲØŲÆ","ŁŁ","ŁŁŲÆŁŚÆŲ±","ŁŁŁ","ŁŁ","ŁŖ","Ł¾Ų§Ų±Ų³Ų§Ł","Ł¾Ų§Ų¹ŪŁŁ","Ł¾Ų³","Ł¾ŁŲ¬","Ł¾ŁŲ“","Ł¾ŪŲÆŲ§","Ł¾ŪŲ“","Ł¾ŪŲ“Ų§Ł¾ŪŲ“","Ł¾ŪŲ“ŲŖŲ±","Ł¾ŪŲ“Ł","ŚŲ±Ų§","ŚŲ·ŁŲ±","ŚŁŲÆŲ±","ŚŁŲ§Ł","ŚŁŲ§ŁŚŁ","ŚŁŲ§ŁŚ©Ł","ŚŁŲÆ","ŚŁŲÆŪŁ","ŚŁŁŁ","ŚŁŪŁ","ŚŁ","ŚŁŲ§Ų±","ŚŁ","ŚŁŁ","ŚŁŲ²Ł","ŚŚÆŁŁŁ","ŚŪŲ²","ŚŪŲ²Ū","ŚŪŲ³ŲŖ","Ś©Ų§Ų“","Ś©Ų§Ł
Ł","Ś©Ų§Ł
ŁŲ§","Ś©ŲŖŲØŲ§","Ś©Ų¬Ų§","Ś©Ų¬Ų§Ų³ŲŖ","Ś©ŲÆŲ§Ł
","Ś©Ų±ŲÆ","Ś©Ų±ŲÆŁ
","Ś©Ų±ŲÆŁ","Ś©Ų±ŲÆŁŲÆ","Ś©Ų±ŲÆŁ","Ś©Ų±ŲÆŪ","Ś©Ų±ŲÆŪŲÆ","Ś©Ų±ŲÆŪŁ
","Ś©Ų³","Ś©Ų³Ų§ŁŪ","Ś©Ų³Ū","Ś©Ł","Ś©ŁŲ§","Ś©Ł
","Ś©Ł
Ų§Ś©Ų§Ł","Ś©Ł
ŲŖŲ±","Ś©Ł
ŲŖŲ±Ū","Ś©Ł
Ū","Ś©Ł","Ś©ŁŲ§Ų±","Ś©ŁŲ§Ų±Ł","Ś©ŁŲÆ","Ś©ŁŁ
","Ś©ŁŁŲÆ","Ś©ŁŁŲÆŁ","Ś©ŁŁŁ","Ś©ŁŁŁŪ","Ś©ŁŪ","Ś©ŁŪŲÆ","Ś©ŁŪŁ
","Ś©Ł","Ś©Ł","Ś©ŁŪ","Ś©Ū","ŚÆŲ§Ł","ŚÆŲ§ŁŪ","ŚÆŲ°Ų§Ų±Ł","ŚÆŲ°Ų§Ų“ŲŖŁ","ŚÆŲ°Ų“ŲŖŁ","ŚÆŲ±ŲÆŲÆ","ŚÆŲ±ŁŲŖ","ŚÆŲ±ŁŲŖŁ
","ŚÆŲ±ŁŲŖŁ","ŚÆŲ±ŁŲŖŁŲÆ","ŚÆŲ±ŁŲŖŁ","ŚÆŲ±ŁŲŖŪ","ŚÆŲ±ŁŲŖŪŲÆ","ŚÆŲ±ŁŲŖŪŁ
","ŚÆŲ±ŁŁŁ","ŚÆŁŲŖ","ŚÆŁŲŖŁ
","ŚÆŁŲŖŁ","ŚÆŁŲŖŁŲÆ","ŚÆŁŲŖŁ","ŚÆŁŲŖŪ","ŚÆŁŲŖŪŲÆ","ŚÆŁŲŖŪŁ
","ŚÆŁ","ŚÆŁŚÆŲ§Ł","ŚÆŁ","ŚÆŁŁŲÆ","ŚÆŁŁŁŲÆ","ŚÆŁŪŲ§","ŚÆŁŪŲÆ","ŚÆŁŪŁ
","ŚÆŁŪŁŲÆ","ŚÆŁŪŪ","ŚÆŁŪŪŲÆ","ŚÆŁŪŪŁ
","ŚÆŁŲ±ŲÆ","ŚÆŁŲ±Ł","ŚÆŪŲ±ŲÆ","ŚÆŪŲ±Ł
","ŚÆŪŲ±ŁŲÆ","ŚÆŪŲ±Ū","ŚÆŪŲ±ŪŲÆ","ŚÆŪŲ±ŪŁ
","Ū","ŪŲ§","ŪŲ§ŲØŲÆ","ŪŲ§ŲØŁ
","ŪŲ§ŲØŁŲÆ","ŪŲ§ŲØŪ","ŪŲ§ŲØŪŲÆ","ŪŲ§ŲØŪŁ
","ŪŲ§ŁŲŖ","ŪŲ§ŁŲŖŁ
","ŪŲ§ŁŲŖŁ","ŪŲ§ŁŲŖŁ","ŪŲ§ŁŲŖŪ","ŪŲ§ŁŲŖŪŲÆ","ŪŲ§ŁŲŖŪŁ
","ŪŲ¹ŁŪ","ŪŁŪŁŲ§","ŪŁ","ŪŚ©","ŪŚ©Ū","Ū°","Ū±","Ū²","Ū³","Ū“","Ūµ","Ū¶","Ū·","Ūø","Ū¹"],"pl":["a","aby","ach","acz","aczkolwiek","aj","albo","ale","ależ","ani","aż","bardziej","bardzo","bez","bo","bowiem","by","byli","bym","bynajmniej","byÄ","byÅ","byÅa","byÅo","byÅy","bÄdzie","bÄdÄ
","cali","caÅa","caÅy","chce","choÄ","ci","ciebie","ciÄ","co","cokolwiek","coraz","coÅ","czasami","czasem","czemu","czy","czyli","czÄsto","daleko","dla","dlaczego","dlatego","do","dobrze","dokÄ
d","doÅÄ","dr","dużo","dwa","dwaj","dwie","dwoje","dzisiaj","dziÅ","gdy","gdyby","gdyż","gdzie","gdziekolwiek","gdzieÅ","go","godz","hab","i","ich","ii","iii","ile","im","inna","inne","inny","innych","inż","iv","ix","iż","ja","jak","jakaÅ","jakby","jaki","jakichÅ","jakie","jakiÅ","jakiż","jakkolwiek","jako","jakoÅ","je","jeden","jedna","jednak","jednakże","jedno","jednym","jedynie","jego","jej","jemu","jest","jestem","jeszcze","jeÅli","jeżeli","już","jÄ
","każdy","kiedy","kierunku","kilka","kilku","kimÅ","kto","ktokolwiek","ktoÅ","ktĆ³ra","ktĆ³re","ktĆ³rego","ktĆ³rej","ktĆ³ry","ktĆ³rych","ktĆ³rym","ktĆ³rzy","ku","lat","lecz","lub","ma","majÄ
","mam","mamy","maÅo","mgr","mi","miaÅ","mimo","miÄdzy","mnie","mnÄ
","mogÄ
","moi","moim","moja","moje","może","możliwe","można","mu","musi","my","mĆ³j","na","nad","nam","nami","nas","nasi","nasz","nasza","nasze","naszego","naszych","natomiast","natychmiast","nawet","nic","nich","nie","niech","niego","niej","niemu","nigdy","nim","nimi","niÄ
","niż","no","nowe","np","nr","o","o.o.","obok","od","ok","okoÅo","on","ona","one","oni","ono","oraz","oto","owszem","pan","pana","pani","pl","po","pod","podczas","pomimo","ponad","ponieważ","powinien","powinna","powinni","powinno","poza","prawie","prof","przecież","przed","przede","przedtem","przez","przy","raz","razie","roku","rĆ³wnież","sam","sama","siÄ","skÄ
d","sobie","sobÄ
","sposĆ³b","swoje","sÄ
","ta","tak","taka","taki","takich","takie","także","tam","te","tego","tej","tel","temu","ten","teraz","też","to","tobie","tobÄ
","toteż","totobÄ
","trzeba","tu","tutaj","twoi","twoim","twoja","twoje","twym","twĆ³j","ty","tych","tylko","tym","tys","tzw","tÄ","u","ul","vi","vii","viii","vol","w","wam","wami","was","wasi","wasz","wasza","wasze","we","wedÅug","wie","wiele","wielu","wiÄc","wiÄcej","wszyscy","wszystkich","wszystkie","wszystkim","wszystko","wtedy","www","wy","wÅaÅnie","wÅrĆ³d","xi","xii","xiii","xiv","xv","z","za","zapewne","zawsze","zaÅ","ze","zeznowu","znowu","znĆ³w","zostaÅ","zÅ","żaden","żadna","żadne","żadnych","że","żeby"],"pt":["a","acerca","adeus","agora","ainda","alem","algmas","algo","algumas","alguns","ali","alĆ©m","ambas","ambos","ano","anos","antes","ao","aonde","aos","apenas","apoio","apontar","apos","apĆ³s","aquela","aquelas","aquele","aqueles","aqui","aquilo","as","assim","atravĆ©s","atrĆ”s","atĆ©","aĆ","baixo","bastante","bem","boa","boas","bom","bons","breve","cada","caminho","catorze","cedo","cento","certamente","certeza","cima","cinco","coisa","com","como","comprido","conhecido","conselho","contra","contudo","corrente","cuja","cujas","cujo","cujos","custa","cĆ”","da","daquela","daquelas","daquele","daqueles","dar","das","de","debaixo","dela","delas","dele","deles","demais","dentro","depois","desde","desligado","dessa","dessas","desse","desses","desta","destas","deste","destes","deve","devem","deverĆ”","dez","dezanove","dezasseis","dezassete","dezoito","dia","diante","direita","dispoe","dispoem","diversa","diversas","diversos","diz","dizem","dizer","do","dois","dos","doze","duas","durante","dĆ”","dĆ£o","dĆŗvida","e","ela","elas","ele","eles","em","embora","enquanto","entao","entre","entĆ£o","era","eram","essa","essas","esse","esses","esta","estado","estamos","estar","estarĆ”","estas","estava","estavam","este","esteja","estejam","estejamos","estes","esteve","estive","estivemos","estiver","estivera","estiveram","estiverem","estivermos","estivesse","estivessem","estiveste","estivestes","estivĆ©ramos","estivĆ©ssemos","estou","estĆ”","estĆ”s","estĆ”vamos","estĆ£o","eu","exemplo","falta","farĆ”","favor","faz","fazeis","fazem","fazemos","fazer","fazes","fazia","faƧo","fez","fim","final","foi","fomos","for","fora","foram","forem","forma","formos","fosse","fossem","foste","fostes","fui","fĆ“ramos","fĆ“ssemos","geral","grande","grandes","grupo","ha","haja","hajam","hajamos","havemos","havia","hei","hoje","hora","horas","houve","houvemos","houver","houvera","houveram","houverei","houverem","houveremos","houveria","houveriam","houvermos","houverĆ”","houverĆ£o","houverĆamos","houvesse","houvessem","houvĆ©ramos","houvĆ©ssemos","hĆ”","hĆ£o","iniciar","inicio","ir","irĆ”","isso","ista","iste","isto","jĆ”","lado","lhe","lhes","ligado","local","logo","longe","lugar","lĆ”","maior","maioria","maiorias","mais","mal","mas","me","mediante","meio","menor","menos","meses","mesma","mesmas","mesmo","mesmos","meu","meus","mil","minha","minhas","momento","muito","muitos","mĆ”ximo","mĆŖs","na","nada","nao","naquela","naquelas","naquele","naqueles","nas","nem","nenhuma","nessa","nessas","nesse","nesses","nesta","nestas","neste","nestes","no","noite","nome","nos","nossa","nossas","nosso","nossos","nova","novas","nove","novo","novos","num","numa","numas","nunca","nuns","nĆ£o","nĆvel","nĆ³s","nĆŗmero","o","obra","obrigada","obrigado","oitava","oitavo","oito","onde","ontem","onze","os","ou","outra","outras","outro","outros","para","parece","parte","partir","paucas","pegar","pela","pelas","pelo","pelos","perante","perto","pessoas","pode","podem","poder","poderĆ”","podia","pois","ponto","pontos","por","porque","porquĆŖ","portanto","posiĆ§Ć£o","possivelmente","posso","possĆvel","pouca","pouco","poucos","povo","primeira","primeiras","primeiro","primeiros","promeiro","propios","proprio","prĆ³pria","prĆ³prias","prĆ³prio","prĆ³prios","prĆ³xima","prĆ³ximas","prĆ³ximo","prĆ³ximos","puderam","pĆ“de","pƵe","pƵem","quais","qual","qualquer","quando","quanto","quarta","quarto","quatro","que","quem","quer","quereis","querem","queremas","queres","quero","questĆ£o","quieto","quinta","quinto","quinze","quĆ”is","quĆŖ","relaĆ§Ć£o","sabe","sabem","saber","se","segunda","segundo","sei","seis","seja","sejam","sejamos","sem","sempre","sendo","ser","serei","seremos","seria","seriam","serĆ”","serĆ£o","serĆamos","sete","seu","seus","sexta","sexto","sim","sistema","sob","sobre","sois","somente","somos","sou","sua","suas","sĆ£o","sĆ©tima","sĆ©timo","sĆ³","tal","talvez","tambem","tambĆ©m","tanta","tantas","tanto","tarde","te","tem","temos","tempo","tendes","tenha","tenham","tenhamos","tenho","tens","tentar","tentaram","tente","tentei","ter","terceira","terceiro","terei","teremos","teria","teriam","terĆ”","terĆ£o","terĆamos","teu","teus","teve","tinha","tinham","tipo","tive","tivemos","tiver","tivera","tiveram","tiverem","tivermos","tivesse","tivessem","tiveste","tivestes","tivĆ©ramos","tivĆ©ssemos","toda","todas","todo","todos","trabalhar","trabalho","treze","trĆŖs","tu","tua","tuas","tudo","tĆ£o","tĆ©m","tĆŖm","tĆnhamos","um","uma","umas","uns","usa","usar","vai","vais","valor","veja","vem","vens","ver","verdade","verdadeiro","vez","vezes","viagem","vindo","vinte","vocĆŖ","vocĆŖs","vos","vossa","vossas","vosso","vossos","vĆ”rios","vĆ£o","vĆŖm","vĆ³s","zero","Ć ","Ć s","Ć”rea","Ć©","Ć©ramos","Ć©s","Ćŗltimo"],"ro":["a","abia","acea","aceasta","aceastÄ","aceea","aceeasi","acei","aceia","acel","acela","acelasi","acele","acelea","acest","acesta","aceste","acestea","acestei","acestia","acestui","aceÅti","aceÅtia","acolo","acord","acum","adica","ai","aia","aibÄ","aici","aiurea","al","ala","alaturi","ale","alea","alt","alta","altceva","altcineva","alte","altfel","alti","altii","altul","am","anume","apoi","ar","are","as","asa","asemenea","asta","astazi","astea","astfel","astÄzi","asupra","atare","atat","atata","atatea","atatia","ati","atit","atita","atitea","atitia","atunci","au","avea","avem","aveÅ£i","avut","azi","aÅ","aÅadar","aÅ£i","b","ba","bine","bucur","bunÄ","c","ca","cam","cand","capat","care","careia","carora","caruia","cat","catre","caut","ce","cea","ceea","cei","ceilalti","cel","cele","celor","ceva","chiar","ci","cinci","cind","cine","cineva","cit","cita","cite","citeva","citi","citiva","conform","contra","cu","cui","cum","cumva","curĆ¢nd","curĆ®nd","cĆ¢nd","cĆ¢t","cĆ¢te","cĆ¢tva","cĆ¢Å£i","cĆ®nd","cĆ®t","cĆ®te","cĆ®tva","cĆ®Å£i","cÄ","cÄci","cÄrei","cÄror","cÄrui","cÄtre","d","da","daca","dacÄ","dar","dat","datoritÄ","datÄ","dau","de","deasupra","deci","decit","degraba","deja","deoarece","departe","desi","despre","deÅi","din","dinaintea","dintr","dintr-","dintre","doar","doi","doilea","douÄ","drept","dupa","dupÄ","dÄ","e","ea","ei","el","ele","era","eram","este","eu","exact","eÅti","f","face","fara","fata","fel","fi","fie","fiecare","fii","fim","fiu","fiÅ£i","foarte","fost","frumos","fÄrÄ","g","geaba","graÅ£ie","h","halbÄ","i","ia","iar","ieri","ii","il","imi","in","inainte","inapoi","inca","incit","insa","intr","intre","isi","iti","j","k","l","la","le","li","lor","lui","lĆ¢ngÄ","lĆ®ngÄ","m","ma","mai","mare","mea","mei","mele","mereu","meu","mi","mie","mine","mod","mult","multa","multe","multi","multÄ","mulÅ£i","mulÅ£umesc","mĆ¢ine","mĆ®ine","mÄ","n","ne","nevoie","ni","nici","niciodata","nicÄieri","nimeni","nimeri","nimic","niste","niÅte","noastre","noastrÄ","noi","noroc","nostri","nostru","nou","noua","nouÄ","noÅtri","nu","numai","o","opt","or","ori","oricare","orice","oricine","oricum","oricĆ¢nd","oricĆ¢t","oricĆ®nd","oricĆ®t","oriunde","p","pai","parca","patra","patru","patrulea","pe","pentru","peste","pic","pina","plus","poate","pot","prea","prima","primul","prin","printr-","putini","puÅ£in","puÅ£ina","puÅ£inÄ","pĆ¢nÄ","pĆ®nÄ","r","rog","s","sa","sa-mi","sa-ti","sai","sale","sau","se","si","sint","sintem","spate","spre","sub","sunt","suntem","sunteÅ£i","sus","sutÄ","sĆ®nt","sĆ®ntem","sĆ®nteÅ£i","sÄ","sÄi","sÄu","t","ta","tale","te","ti","timp","tine","toata","toate","toatÄ","tocmai","tot","toti","totul","totusi","totuÅi","toÅ£i","trei","treia","treilea","tu","tuturor","tÄi","tÄu","u","ul","ului","un","una","unde","undeva","unei","uneia","unele","uneori","unii","unor","unora","unu","unui","unuia","unul","v","va","vi","voastre","voastrÄ","voi","vom","vor","vostru","vouÄ","voÅtri","vreme","vreo","vreun","vÄ","x","z","zece","zero","zi","zice","Ć®i","Ć®l","Ć®mi","Ć®mpotriva","Ć®n","Ć®nainte","Ć®naintea","Ć®ncotro","Ć®ncĆ¢t","Ć®ncĆ®t","Ć®ntre","Ć®ntrucĆ¢t","Ć®ntrucĆ®t","Ć®Å£i","Äla","Älea","Ästa","Ästea","ÄÅtia","Åapte","Åase","Åi","Åtiu","Å£i","Å£ie"],"ru":["c","Š°","Š°Š»Š»Š¾","Š±ŠµŠ·","Š±ŠµŠ»ŃŠ¹","Š±Š»ŠøŠ·ŠŗŠ¾","Š±Š¾Š»ŠµŠµ","Š±Š¾Š»ŃŃŠµ","Š±Š¾Š»ŃŃŠ¾Š¹","Š±ŃŠ“ŠµŠ¼","Š±ŃŠ“ŠµŃ","Š±ŃŠ“ŠµŃŠµ","Š±ŃŠ“ŠµŃŃ","Š±ŃŠ“ŃŠ¾","Š±ŃŠ“Ń","Š±ŃŠ“ŃŃ","Š±ŃŠ“Ń","Š±Ń","Š±ŃŠ²Š°ŠµŃ","Š±ŃŠ²Ń","Š±ŃŠ»","Š±ŃŠ»Š°","Š±ŃŠ»Šø","Š±ŃŠ»Š¾","Š±ŃŃŃ","Š²","Š²Š°Š¶Š½Š°Ń","Š²Š°Š¶Š½Š¾Šµ","Š²Š°Š¶Š½ŃŠµ","Š²Š°Š¶Š½ŃŠ¹","Š²Š°Š¼","Š²Š°Š¼Šø","Š²Š°Ń","Š²Š°Ń","Š²Š°ŃŠ°","Š²Š°ŃŠµ","Š²Š°ŃŠø","Š²Š²ŠµŃŃ
","Š²Š“Š°Š»Šø","Š²Š“ŃŃŠ³","Š²ŠµŠ“Ń","Š²ŠµŠ·Š“Šµ","Š²ŠµŃŠ½ŃŃŃŃŃ","Š²ŠµŃŃ","Š²ŠµŃŠµŃ","Š²Š·Š³Š»ŃŠ“","Š²Š·ŃŃŃ","Š²ŠøŠ“","Š²ŠøŠ“ŠµŠ»","Š²ŠøŠ“ŠµŃŃ","Š²Š¼ŠµŃŃŠµ","Š²Š½Šµ","Š²Š½ŠøŠ·","Š²Š½ŠøŠ·Ń","Š²Š¾","Š²Š¾Š“Š°","Š²Š¾Š¹Š½Š°","Š²Š¾ŠŗŃŃŠ³","Š²Š¾Š½","Š²Š¾Š¾Š±ŃŠµ","Š²Š¾ŠæŃŠ¾Ń","Š²Š¾ŃŠµŠ¼Š½Š°Š“ŃŠ°ŃŃŠ¹","Š²Š¾ŃŠµŠ¼Š½Š°Š“ŃŠ°ŃŃ","Š²Š¾ŃŠµŠ¼Ń","Š²Š¾ŃŃŠ¼Š¾Š¹","Š²Š¾Ń","Š²ŠæŃŠ¾ŃŠµŠ¼","Š²ŃŠµŠ¼ŠµŠ½Šø","Š²ŃŠµŠ¼Ń","Š²ŃŠµ","Š²ŃŠµ ŠµŃŠµ","Š²ŃŠµŠ³Š“Š°","Š²ŃŠµŠ³Š¾","Š²ŃŠµŠ¼","Š²ŃŠµŠ¼Šø","Š²ŃŠµŠ¼Ń","Š²ŃŠµŃ
","Š²ŃŠµŃ","Š²ŃŃ","Š²ŃŃŠ“Ń","Š²ŃŃ","Š²ŃŃ","Š²ŃŠ¾ŃŠ¾Š¹","Š²Ń","Š²ŃŠ¹ŃŠø","Š³","Š³Š“Šµ","Š³Š»Š°Š²Š½ŃŠ¹","Š³Š»Š°Š·","Š³Š¾Š²Š¾ŃŠøŠ»","Š³Š¾Š²Š¾ŃŠøŃ","Š³Š¾Š²Š¾ŃŠøŃŃ","Š³Š¾Š“","Š³Š¾Š“Š°","Š³Š¾Š“Ń","Š³Š¾Š»Š¾Š²Š°","Š³Š¾Š»Š¾Ń","Š³Š¾ŃŠ¾Š“","Š“Š°","Š“Š°Š²Š°ŃŃ","Š“Š°Š²Š½Š¾","Š“Š°Š¶Šµ","Š“Š°Š»ŠµŠŗŠøŠ¹","Š“Š°Š»ŠµŠŗŠ¾","Š“Š°Š»ŃŃŠµ","Š“Š°ŃŠ¾Š¼","Š“Š°ŃŃ","Š“Š²Š°","Š“Š²Š°Š“ŃŠ°ŃŃŠ¹","Š“Š²Š°Š“ŃŠ°ŃŃ","Š“Š²Šµ","Š“Š²ŠµŠ½Š°Š“ŃŠ°ŃŃŠ¹","Š“Š²ŠµŠ½Š°Š“ŃŠ°ŃŃ","Š“Š²ŠµŃŃ","Š“Š²ŃŃ
","Š“ŠµŠ²ŃŃŠ½Š°Š“ŃŠ°ŃŃŠ¹","Š“ŠµŠ²ŃŃŠ½Š°Š“ŃŠ°ŃŃ","Š“ŠµŠ²ŃŃŃŠ¹","Š“ŠµŠ²ŃŃŃ","Š“ŠµŠ¹ŃŃŠ²ŠøŃŠµŠ»ŃŠ½Š¾","Š“ŠµŠ»","Š“ŠµŠ»Š°Š»","Š“ŠµŠ»Š°ŃŃ","Š“ŠµŠ»Š°Ń","Š“ŠµŠ»Š¾","Š“ŠµŠ½Ń","Š“ŠµŠ½ŃŠ³Šø","Š“ŠµŃŃŃŃŠ¹","Š“ŠµŃŃŃŃ","Š“Š»Ń","Š“Š¾","Š“Š¾Š²Š¾Š»ŃŠ½Š¾","Š“Š¾Š»Š³Š¾","Š“Š¾Š»Š¶ŠµŠ½","Š“Š¾Š»Š¶Š½Š¾","Š“Š¾Š»Š¶Š½ŃŠ¹","Š“Š¾Š¼","Š“Š¾ŃŠ¾Š³Š°","Š“ŃŃŠ³","Š“ŃŃŠ³Š°Ń","Š“ŃŃŠ³ŠøŠµ","Š“ŃŃŠ³ŠøŃ
","Š“ŃŃŠ³Š¾","Š“ŃŃŠ³Š¾Šµ","Š“ŃŃŠ³Š¾Š¹","Š“ŃŠ¼Š°ŃŃ","Š“ŃŃŠ°","Šµ","ŠµŠ³Š¾","ŠµŠµ","ŠµŠ¹","ŠµŠ¼Ń","ŠµŃŠ»Šø","ŠµŃŃŃ","ŠµŃŠµ","ŠµŃŃ","ŠµŃ","ŠµŃ","Š¶","Š¶Š“Š°ŃŃ","Š¶Šµ","Š¶ŠµŠ½Š°","Š¶ŠµŠ½ŃŠøŠ½Š°","Š¶ŠøŠ·Š½Ń","Š¶ŠøŃŃ","Š·Š°","Š·Š°Š½ŃŃ","Š·Š°Š½ŃŃŠ°","Š·Š°Š½ŃŃŠ¾","Š·Š°Š½ŃŃŃ","Š·Š°ŃŠµŠ¼","Š·Š°ŃŠ¾","Š·Š°ŃŠµŠ¼","Š·Š“ŠµŃŃ","Š·ŠµŠ¼Š»Ń","Š·Š½Š°ŃŃ","Š·Š½Š°ŃŠøŃ","Š·Š½Š°ŃŠøŃŃ","Šø","ŠøŠ“Šø","ŠøŠ“ŃŠø","ŠøŠ·","ŠøŠ»Šø","ŠøŠ¼","ŠøŠ¼ŠµŠµŃ","ŠøŠ¼ŠµŠ»","ŠøŠ¼ŠµŠ½Š½Š¾","ŠøŠ¼ŠµŃŃ","ŠøŠ¼Šø","ŠøŠ¼Ń","ŠøŠ½Š¾Š³Š“Š°","ŠøŃ
","Šŗ","ŠŗŠ°Š¶Š“Š°Ń","ŠŗŠ°Š¶Š“Š¾Šµ","ŠŗŠ°Š¶Š“ŃŠµ","ŠŗŠ°Š¶Š“ŃŠ¹","ŠŗŠ°Š¶ŠµŃŃŃ","ŠŗŠ°Š·Š°ŃŃŃŃ","ŠŗŠ°Šŗ","ŠŗŠ°ŠŗŠ°Ń","ŠŗŠ°ŠŗŠ¾Š¹","ŠŗŠµŠ¼","ŠŗŠ½ŠøŠ³Š°","ŠŗŠ¾Š³Š“Š°","ŠŗŠ¾Š³Š¾","ŠŗŠ¾Š¼","ŠŗŠ¾Š¼Š½Š°ŃŠ°","ŠŗŠ¾Š¼Ń","ŠŗŠ¾Š½ŠµŃ","ŠŗŠ¾Š½ŠµŃŠ½Š¾","ŠŗŠ¾ŃŠ¾ŃŠ°Ń","ŠŗŠ¾ŃŠ¾ŃŠ¾Š³Š¾","ŠŗŠ¾ŃŠ¾ŃŠ¾Š¹","ŠŗŠ¾ŃŠ¾ŃŃŠµ","ŠŗŠ¾ŃŠ¾ŃŃŠ¹","ŠŗŠ¾ŃŠ¾ŃŃŃ
","ŠŗŃŠ¾Š¼Šµ","ŠŗŃŃŠ³Š¾Š¼","ŠŗŃŠ¾","ŠŗŃŠ“Š°","Š»ŠµŠ¶Š°ŃŃ","Š»ŠµŃ","Š»Šø","Š»ŠøŃŠ¾","Š»ŠøŃŃ","Š»ŃŃŃŠµ","Š»ŃŠ±ŠøŃŃ","Š»ŃŠ“Šø","Š¼","Š¼Š°Š»ŠµŠ½ŃŠŗŠøŠ¹","Š¼Š°Š»Š¾","Š¼Š°ŃŃ","Š¼Š°ŃŠøŠ½Š°","Š¼ŠµŠ¶Š“Ń","Š¼ŠµŠ»Ń","Š¼ŠµŠ½ŠµŠµ","Š¼ŠµŠ½ŃŃŠµ","Š¼ŠµŠ½Ń","Š¼ŠµŃŃŠ¾","Š¼ŠøŠ»Š»ŠøŠ¾Š½Š¾Š²","Š¼ŠøŠ¼Š¾","Š¼ŠøŠ½ŃŃŠ°","Š¼ŠøŃ","Š¼ŠøŃŠ°","Š¼Š½Šµ","Š¼Š½Š¾Š³Š¾","Š¼Š½Š¾Š³Š¾ŃŠøŃŠ»ŠµŠ½Š½Š°Ń","Š¼Š½Š¾Š³Š¾ŃŠøŃŠ»ŠµŠ½Š½Š¾Šµ","Š¼Š½Š¾Š³Š¾ŃŠøŃŠ»ŠµŠ½Š½ŃŠµ","Š¼Š½Š¾Š³Š¾ŃŠøŃŠ»ŠµŠ½Š½ŃŠ¹","Š¼Š½Š¾Š¹","Š¼Š½Š¾Ń","Š¼Š¾Š³","Š¼Š¾Š³Ń","Š¼Š¾Š³ŃŃ","Š¼Š¾Š¶","Š¼Š¾Š¶ŠµŃ","Š¼Š¾Š¶ŠµŃ Š±ŃŃŃ","Š¼Š¾Š¶Š½Š¾","Š¼Š¾Š¶Ń
Š¾","Š¼Š¾Šø","Š¼Š¾Š¹","Š¼Š¾Ń","Š¼Š¾ŃŠŗŠ²Š°","Š¼Š¾ŃŃ","Š¼Š¾Ń","Š¼Š¾Ń","Š¼Ń","Š½Š°","Š½Š°Š²ŠµŃŃ
Ń","Š½Š°Š“","Š½Š°Š“Š¾","Š½Š°Š·Š°Š“","Š½Š°ŠøŠ±Š¾Š»ŠµŠµ","Š½Š°Š¹ŃŠø","Š½Š°ŠŗŠ¾Š½ŠµŃ","Š½Š°Š¼","Š½Š°Š¼Šø","Š½Š°ŃŠ¾Š“","Š½Š°Ń","Š½Š°ŃŠ°Š»Š°","Š½Š°ŃŠ°ŃŃ","Š½Š°Ń","Š½Š°ŃŠ°","Š½Š°ŃŠµ","Š½Š°ŃŠø","Š½Šµ","Š½ŠµŠ³Š¾","Š½ŠµŠ“Š°Š²Š½Š¾","Š½ŠµŠ“Š°Š»ŠµŠŗŠ¾","Š½ŠµŠµ","Š½ŠµŠ¹","Š½ŠµŠŗŠ¾ŃŠ¾ŃŃŠ¹","Š½ŠµŠ»ŃŠ·Ń","Š½ŠµŠ¼","Š½ŠµŠ¼Š½Š¾Š³Š¾","Š½ŠµŠ¼Ń","Š½ŠµŠæŃŠµŃŃŠ²Š½Š¾","Š½ŠµŃŠµŠ“ŠŗŠ¾","Š½ŠµŃŠŗŠ¾Š»ŃŠŗŠ¾","Š½ŠµŃ","Š½ŠµŃ","Š½ŠµŃ","Š½Šø","Š½ŠøŠ±ŃŠ“Ń","Š½ŠøŠ¶Šµ","Š½ŠøŠ·ŠŗŠ¾","Š½ŠøŠŗŠ°ŠŗŠ¾Š¹","Š½ŠøŠŗŠ¾Š³Š“Š°","Š½ŠøŠŗŃŠ¾","Š½ŠøŠŗŃŠ“Š°","Š½ŠøŠ¼","Š½ŠøŠ¼Šø","Š½ŠøŃ
","Š½ŠøŃŠµŠ³Š¾","Š½ŠøŃŃŠ¾","Š½Š¾","Š½Š¾Š²ŃŠ¹","Š½Š¾Š³Š°","Š½Š¾ŃŃ","Š½Ń","Š½ŃŠ¶Š½Š¾","Š½ŃŠ¶Š½ŃŠ¹","Š½Ń
","Š¾","Š¾Š±","Š¾Š±Š°","Š¾Š±ŃŃŠ½Š¾","Š¾Š“ŠøŠ½","Š¾Š“ŠøŠ½Š½Š°Š“ŃŠ°ŃŃŠ¹","Š¾Š“ŠøŠ½Š½Š°Š“ŃŠ°ŃŃ","Š¾Š“Š½Š°Š¶Š“Ń","Š¾Š“Š½Š°ŠŗŠ¾","Š¾Š“Š½Š¾Š³Š¾","Š¾Š“Š½Š¾Š¹","Š¾ŠŗŠ°Š·Š°ŃŃŃŃ","Š¾ŠŗŠ½Š¾","Š¾ŠŗŠ¾Š»Š¾","Š¾Š½","Š¾Š½Š°","Š¾Š½Šø","Š¾Š½Š¾","Š¾ŠæŃŃŃ","Š¾ŃŠ¾Š±ŠµŠ½Š½Š¾","Š¾ŃŃŠ°ŃŃŃŃ","Š¾Ń","Š¾ŃŠ²ŠµŃŠøŃŃ","Š¾ŃŠµŃ","Š¾ŃŠŗŃŠ“Š°","Š¾ŃŠ¾Š²ŃŃŠ“Ń","Š¾ŃŃŃŠ“Š°","Š¾ŃŠµŠ½Ń","ŠæŠµŃŠ²ŃŠ¹","ŠæŠµŃŠµŠ“","ŠæŠøŃŠ°ŃŃ","ŠæŠ»ŠµŃŠ¾","ŠæŠ¾","ŠæŠ¾Š“","ŠæŠ¾Š“Š¾Š¹Š“Šø","ŠæŠ¾Š“ŃŠ¼Š°ŃŃ","ŠæŠ¾Š¶Š°Š»ŃŠ¹ŃŃŠ°","ŠæŠ¾Š·Š¶Šµ","ŠæŠ¾Š¹ŃŠø","ŠæŠ¾ŠŗŠ°","ŠæŠ¾Š»","ŠæŠ¾Š»ŃŃŠøŃŃ","ŠæŠ¾Š¼Š½ŠøŃŃ","ŠæŠ¾Š½ŠøŠ¼Š°ŃŃ","ŠæŠ¾Š½ŃŃŃ","ŠæŠ¾Ń","ŠæŠ¾ŃŠ°","ŠæŠ¾ŃŠ»Šµ","ŠæŠ¾ŃŠ»ŠµŠ“Š½ŠøŠ¹","ŠæŠ¾ŃŠ¼Š¾ŃŃŠµŃŃ","ŠæŠ¾ŃŃŠµŠ“Šø","ŠæŠ¾ŃŠ¾Š¼","ŠæŠ¾ŃŠ¾Š¼Ń","ŠæŠ¾ŃŠµŠ¼Ń","ŠæŠ¾ŃŃŠø","ŠæŃŠ°Š²Š“Š°","ŠæŃŠµŠŗŃŠ°ŃŠ½Š¾","ŠæŃŠø","ŠæŃŠ¾","ŠæŃŠ¾ŃŃŠ¾","ŠæŃŠ¾ŃŠøŠ²","ŠæŃŠ¾ŃŠµŠ½ŃŠ¾Š²","ŠæŃŃŃ","ŠæŃŃŠ½Š°Š“ŃŠ°ŃŃŠ¹","ŠæŃŃŠ½Š°Š“ŃŠ°ŃŃ","ŠæŃŃŃŠ¹","ŠæŃŃŃ","ŃŠ°Š±Š¾ŃŠ°","ŃŠ°Š±Š¾ŃŠ°ŃŃ","ŃŠ°Š·","ŃŠ°Š·Š²Šµ","ŃŠ°Š½Š¾","ŃŠ°Š½ŃŃŠµ","ŃŠµŠ±ŠµŠ½Š¾Šŗ","ŃŠµŃŠøŃŃ","ŃŠ¾ŃŃŠøŃ","ŃŃŠŗŠ°","ŃŃŃŃŠŗŠøŠ¹","ŃŃŠ“","ŃŃŠ“Š¾Š¼","Ń","Ń ŠŗŠµŠ¼","ŃŠ°Š¼","ŃŠ°Š¼Š°","ŃŠ°Š¼Šø","ŃŠ°Š¼ŠøŠ¼","ŃŠ°Š¼ŠøŠ¼Šø","ŃŠ°Š¼ŠøŃ
","ŃŠ°Š¼Š¾","ŃŠ°Š¼Š¾Š³Š¾","ŃŠ°Š¼Š¾Š¹","ŃŠ°Š¼Š¾Š¼","ŃŠ°Š¼Š¾Š¼Ń","ŃŠ°Š¼Ń","ŃŠ°Š¼ŃŠ¹","ŃŠ²ŠµŃ","ŃŠ²Š¾Šµ","ŃŠ²Š¾ŠµŠ³Š¾","ŃŠ²Š¾ŠµŠ¹","ŃŠ²Š¾Šø","ŃŠ²Š¾ŠøŃ
","ŃŠ²Š¾Š¹","ŃŠ²Š¾Ń","ŃŠ“ŠµŠ»Š°ŃŃ","ŃŠµŠ°Š¾Š¹","ŃŠµŠ±Šµ","ŃŠµŠ±Ń","ŃŠµŠ³Š¾Š“Š½Ń","ŃŠµŠ“ŃŠ¼Š¾Š¹","ŃŠµŠ¹ŃŠ°Ń","ŃŠµŠ¼Š½Š°Š“ŃŠ°ŃŃŠ¹","ŃŠµŠ¼Š½Š°Š“ŃŠ°ŃŃ","ŃŠµŠ¼Ń","ŃŠøŠ“ŠµŃŃ","ŃŠøŠ»Š°","ŃŠøŃ
","ŃŠŗŠ°Š·Š°Š»","ŃŠŗŠ°Š·Š°Š»Š°","ŃŠŗŠ°Š·Š°ŃŃ","ŃŠŗŠ¾Š»ŃŠŗŠ¾","ŃŠ»ŠøŃŠŗŠ¾Š¼","ŃŠ»Š¾Š²Š¾","ŃŠ»ŃŃŠ°Š¹","ŃŠ¼Š¾ŃŃŠµŃŃ","ŃŠ½Š°ŃŠ°Š»Š°","ŃŠ½Š¾Š²Š°","ŃŠ¾","ŃŠ¾Š±Š¾Š¹","ŃŠ¾Š±Š¾Ń","ŃŠ¾Š²ŠµŃŃŠŗŠøŠ¹","ŃŠ¾Š²ŃŠµŠ¼","ŃŠæŠ°ŃŠøŠ±Š¾","ŃŠæŃŠ¾ŃŠøŃŃ","ŃŃŠ°Š·Ń","ŃŃŠ°Š»","ŃŃŠ°ŃŃŠ¹","ŃŃŠ°ŃŃ","ŃŃŠ¾Š»","ŃŃŠ¾ŃŠ¾Š½Š°","ŃŃŠ¾ŃŃŃ","ŃŃŃŠ°Š½Š°","ŃŃŃŃ","ŃŃŠøŃŠ°ŃŃ","Ń","ŃŠ°","ŃŠ°Šŗ","ŃŠ°ŠŗŠ°Ń","ŃŠ°ŠŗŠ¶Šµ","ŃŠ°ŠŗŠø","ŃŠ°ŠŗŠøŠµ","ŃŠ°ŠŗŠ¾Šµ","ŃŠ°ŠŗŠ¾Š¹","ŃŠ°Š¼","ŃŠ²Š¾Šø","ŃŠ²Š¾Š¹","ŃŠ²Š¾Ń","ŃŠ²Š¾Ń","ŃŠµ","ŃŠµŠ±Šµ","ŃŠµŠ±Ń","ŃŠµŠ¼","ŃŠµŠ¼Šø","ŃŠµŠæŠµŃŃ","ŃŠµŃ
","ŃŠ¾","ŃŠ¾Š±Š¾Š¹","ŃŠ¾Š±Š¾Ń","ŃŠ¾Š²Š°ŃŠøŃ","ŃŠ¾Š³Š“Š°","ŃŠ¾Š³Š¾","ŃŠ¾Š¶Šµ","ŃŠ¾Š»ŃŠŗŠ¾","ŃŠ¾Š¼","ŃŠ¾Š¼Ń","ŃŠ¾Ń","ŃŠ¾Ń","ŃŃŠµŃŠøŠ¹","ŃŃŠø","ŃŃŠøŠ½Š°Š“ŃŠ°ŃŃŠ¹","ŃŃŠøŠ½Š°Š“ŃŠ°ŃŃ","ŃŃ","ŃŃŠ“Š°","ŃŃŃ","ŃŃ","ŃŃŃŃŃ","Ń","ŃŠ²ŠøŠ“ŠµŃŃ","ŃŠ¶","ŃŠ¶Šµ","ŃŠ»ŠøŃŠ°","ŃŠ¼ŠµŃŃ","ŃŃŃŠ¾","Ń
Š¾ŃŠ¾ŃŠøŠ¹","Ń
Š¾ŃŠ¾ŃŠ¾","Ń
Š¾ŃŠµŠ» Š±Ń","Ń
Š¾ŃŠµŃŃ","Ń
Š¾ŃŃ","Ń
Š¾ŃŃ","Ń
Š¾ŃŠµŃŃ","ŃŠ°Ń","ŃŠ°ŃŃŠ¾","ŃŠ°ŃŃŃ","ŃŠ°ŃŠµ","ŃŠµŠ³Š¾","ŃŠµŠ»Š¾Š²ŠµŠŗ","ŃŠµŠ¼","ŃŠµŠ¼Ń","ŃŠµŃŠµŠ·","ŃŠµŃŠ²ŠµŃŃŃŠ¹","ŃŠµŃŃŃŠµ","ŃŠµŃŃŃŠ½Š°Š“ŃŠ°ŃŃŠ¹","ŃŠµŃŃŃŠ½Š°Š“ŃŠ°ŃŃ","ŃŃŠ¾","ŃŃŠ¾Š±","ŃŃŠ¾Š±Ń","ŃŃŃŃ","ŃŠµŃŃŠ½Š°Š“ŃŠ°ŃŃŠ¹","ŃŠµŃŃŠ½Š°Š“ŃŠ°ŃŃ","ŃŠµŃŃŠ¾Š¹","ŃŠµŃŃŃ","ŃŃŠ°","ŃŃŠø","ŃŃŠøŠ¼","ŃŃŠøŠ¼Šø","ŃŃŠøŃ
","ŃŃŠ¾","ŃŃŠ¾Š³Š¾","ŃŃŠ¾Š¹","ŃŃŠ¾Š¼","ŃŃŠ¾Š¼Ń","ŃŃŠ¾Ń","ŃŃŃ","Ń","ŃŠ²Š»ŃŃŃŃ"],"sk":["a","aby","aj","ak","akej","akejže","ako","akom","akomže","akou","akouže","akože","akĆ”","akĆ”Å¾e","akĆ©","akĆ©ho","akĆ©hože","akĆ©mu","akĆ©muže","akĆ©Å¾e","akĆŗ","akĆŗže","akĆ½","akĆ½ch","akĆ½chže","akĆ½m","akĆ½mi","akĆ½miže","akĆ½mže","akĆ½Å¾e","ale","alebo","ani","asi","avÅ”ak","až","ba","bez","bezo","bol","bola","boli","bolo","bude","budem","budeme","budete","budeÅ”","budĆŗ","buÄ","by","byÅ„","cez","cezo","dnes","do","eÅ”te","ho","hoci","i","iba","ich","im","inej","inom","inĆ”","inĆ©","inĆ©ho","inĆ©mu","inĆ","inĆŗ","inĆ½","inĆ½ch","inĆ½m","inĆ½mi","ja","je","jeho","jej","jemu","ju","k","kam","kamže","každou","každĆ”","každĆ©","každĆ©ho","každĆ©mu","každĆ","každĆŗ","každĆ½","každĆ½ch","každĆ½m","každĆ½mi","kde","kej","kejže","keÄ","keÄže","kie","kieho","kiehože","kiemu","kiemuže","kieže","koho","kom","komu","kou","kouže","kto","ktorej","ktorou","ktorĆ”","ktorĆ©","ktorĆ","ktorĆŗ","ktorĆ½","ktorĆ½ch","ktorĆ½m","ktorĆ½mi","ku","kĆ”","kĆ”Å¾e","kĆ©","kĆ©Å¾e","kĆŗ","kĆŗže","kĆ½","kĆ½ho","kĆ½hože","kĆ½m","kĆ½mu","kĆ½muže","kĆ½Å¾e","lebo","leda","ledaže","len","ma","majĆŗ","mal","mala","mali","maÅ„","medzi","mi","mne","mnou","moja","moje","mojej","mojich","mojim","mojimi","mojou","moju","možno","mu","musia","musieÅ„","musĆ","musĆm","musĆme","musĆte","musĆÅ”","my","mĆ”","mĆ”m","mĆ”me","mĆ”te","mĆ”Å”","mĆ“cÅ„","mĆ“j","mĆ“jho","mĆ“Å¾e","mĆ“Å¾em","mĆ“Å¾eme","mĆ“Å¾ete","mĆ“Å¾eÅ”","mĆ“Å¾u","mÅa","na","nad","nado","najmƤ","nami","naÅ”a","naÅ”e","naÅ”ej","naÅ”i","naÅ”ich","naÅ”im","naÅ”imi","naÅ”ou","ne","nech","neho","nej","nejakej","nejakom","nejakou","nejakĆ”","nejakĆ©","nejakĆ©ho","nejakĆ©mu","nejakĆŗ","nejakĆ½","nejakĆ½ch","nejakĆ½m","nejakĆ½mi","nemu","než","nich","nie","niektorej","niektorom","niektorou","niektorĆ”","niektorĆ©","niektorĆ©ho","niektorĆ©mu","niektorĆŗ","niektorĆ½","niektorĆ½ch","niektorĆ½m","niektorĆ½mi","nielen","nieÄo","nim","nimi","niÄ","niÄoho","niÄom","niÄomu","niÄĆm","no","nĆ”m","nĆ”s","nĆ”Å”","nĆ”Å”ho","nĆm","o","od","odo","on","ona","oni","ono","ony","oÅ","oÅho","po","pod","podo","podľa","pokiaľ","popod","popri","potom","poza","pre","pred","predo","preto","pretože","preÄo","pri","prĆ”ve","s","sa","seba","sebe","sebou","sem","si","sme","so","som","ste","svoj","svoja","svoje","svojho","svojich","svojim","svojimi","svojou","svoju","svojĆm","sĆŗ","ta","tak","takej","takejto","takĆ”","takĆ”to","takĆ©","takĆ©ho","takĆ©hoto","takĆ©mu","takĆ©muto","takĆ©to","takĆ","takĆŗ","takĆŗto","takĆ½","takĆ½to","takže","tam","teba","tebe","tebou","teda","tej","tejto","ten","tento","ti","tie","tieto","tiež","to","toho","tohoto","tohto","tom","tomto","tomu","tomuto","toto","tou","touto","tu","tvoj","tvoja","tvoje","tvojej","tvojho","tvoji","tvojich","tvojim","tvojimi","tvojĆm","ty","tĆ”","tĆ”to","tĆ","tĆto","tĆŗ","tĆŗto","tĆ½ch","tĆ½m","tĆ½mi","tĆ½mto","u","už","v","vami","vaÅ”a","vaÅ”e","vaÅ”ej","vaÅ”i","vaÅ”ich","vaÅ”im","vaÅ”Ćm","veÄ","viac","vo","vy","vĆ”m","vĆ”s","vĆ”Å”","vĆ”Å”ho","vÅ”ak","vÅ”etci","vÅ”etka","vÅ”etko","vÅ”etky","vÅ”etok","z","za","zaÄo","zaÄože","zo","Ć”no","Äej","Äi","Äia","Äie","Äieho","Äiemu","Äiu","Äo","Äoho","Äom","Äomu","Äou","Äože","ÄĆ","ÄĆm","ÄĆmi","ÄalÅ”ia","ÄalÅ”ie","ÄalÅ”ieho","ÄalÅ”iemu","ÄalÅ”iu","ÄalÅ”om","ÄalÅ”ou","ÄalÅ”Ć","ÄalÅ”Ćch","ÄalÅ”Ćm","ÄalÅ”Ćmi","Åom","Åou","Åu","že"],"sl":["a","ali","april","avgust","b","bi","bil","bila","bile","bili","bilo","biti","blizu","bo","bodo","bojo","bolj","bom","bomo","boste","bova","boÅ”","brez","c","cel","cela","celi","celo","d","da","daleÄ","dan","danes","datum","december","deset","deseta","deseti","deseto","devet","deveta","deveti","deveto","do","dober","dobra","dobri","dobro","dokler","dol","dolg","dolga","dolgi","dovolj","drug","druga","drugi","drugo","dva","dve","e","eden","en","ena","ene","eni","enkrat","eno","etc.","f","februar","g","g.","ga","ga.","gor","gospa","gospod","h","halo","i","idr.","ii","iii","in","iv","ix","iz","j","januar","jaz","je","ji","jih","jim","jo","julij","junij","jutri","k","kadarkoli","kaj","kajti","kako","kakor","kamor","kamorkoli","kar","karkoli","katerikoli","kdaj","kdo","kdorkoli","ker","ki","kje","kjer","kjerkoli","ko","koder","koderkoli","koga","komu","kot","kratek","kratka","kratke","kratki","l","lahka","lahke","lahki","lahko","le","lep","lepa","lepe","lepi","lepo","leto","m","maj","majhen","majhna","majhni","malce","malo","manj","marec","me","med","medtem","mene","mesec","mi","midva","midve","mnogo","moj","moja","moje","mora","morajo","moram","moramo","morate","moraÅ”","morem","mu","n","na","nad","naj","najina","najino","najmanj","naju","najveÄ","nam","narobe","nas","nato","nazaj","naÅ”","naÅ”a","naÅ”e","ne","nedavno","nedelja","nek","neka","nekaj","nekatere","nekateri","nekatero","nekdo","neke","nekega","neki","nekje","neko","nekoga","nekoÄ","ni","nikamor","nikdar","nikjer","nikoli","niÄ","nje","njega","njegov","njegova","njegovo","njej","njemu","njen","njena","njeno","nji","njih","njihov","njihova","njihovo","njiju","njim","njo","njun","njuna","njuno","no","nocoj","november","npr.","o","ob","oba","obe","oboje","od","odprt","odprta","odprti","okoli","oktober","on","onadva","one","oni","onidve","osem","osma","osmi","osmo","oz.","p","pa","pet","peta","petek","peti","peto","po","pod","pogosto","poleg","poln","polna","polni","polno","ponavadi","ponedeljek","ponovno","potem","povsod","pozdravljen","pozdravljeni","prav","prava","prave","pravi","pravo","prazen","prazna","prazno","prbl.","precej","pred","prej","preko","pri","pribl.","približno","primer","pripravljen","pripravljena","pripravljeni","proti","prva","prvi","prvo","r","ravno","redko","res","reÄ","s","saj","sam","sama","same","sami","samo","se","sebe","sebi","sedaj","sedem","sedma","sedmi","sedmo","sem","september","seveda","si","sicer","skoraj","skozi","slab","smo","so","sobota","spet","sreda","srednja","srednji","sta","ste","stran","stvar","sva","t","ta","tak","taka","take","taki","tako","takoj","tam","te","tebe","tebi","tega","težak","težka","težki","težko","ti","tista","tiste","tisti","tisto","tj.","tja","to","toda","torek","tretja","tretje","tretji","tri","tu","tudi","tukaj","tvoj","tvoja","tvoje","u","v","vaju","vam","vas","vaÅ”","vaÅ”a","vaÅ”e","ve","vedno","velik","velika","veliki","veliko","vendar","ves","veÄ","vi","vidva","vii","viii","visok","visoka","visoke","visoki","vsa","vsaj","vsak","vsaka","vsakdo","vsake","vsaki","vsakomur","vse","vsega","vsi","vso","vÄasih","vÄeraj","x","z","za","zadaj","zadnji","zakaj","zaprta","zaprti","zaprto","zdaj","zelo","zunaj","Ä","Äe","Äesto","Äetrta","Äetrtek","Äetrti","Äetrto","Äez","Äigav","Å”","Å”est","Å”esta","Å”esti","Å”esto","Å”tiri","ž","že"],"so":["aad","albaabkii","atabo","ay","ayaa","ayee","ayuu","dhan","hadana","in","inuu","isku","jiray","jirtay","ka","kale","kasoo","ku","kuu","lakin","markii","oo","si","soo","uga","ugu","uu","waa","waxa","waxuu"],"st":["a","ba","bane","bona","e","ea","eaba","empa","ena","ha","hae","hape","ho","hore","ka","ke","la","le","li","me","mo","moo","ne","o","oa","re","sa","se","tloha","tsa","tse"],"es":["0","1","2","3","4","5","6","7","8","9","_","a","actualmente","acuerdo","adelante","ademas","ademĆ”s","adrede","afirmĆ³","agregĆ³","ahi","ahora","ahĆ","al","algo","alguna","algunas","alguno","algunos","algĆŗn","alli","allĆ","alrededor","ambos","ampleamos","antano","antaƱo","ante","anterior","antes","apenas","aproximadamente","aquel","aquella","aquellas","aquello","aquellos","aqui","aquĆ©l","aquĆ©lla","aquĆ©llas","aquĆ©llos","aquĆ","arriba","arribaabajo","asegurĆ³","asi","asĆ","atras","aun","aunque","ayer","aƱadiĆ³","aĆŗn","b","bajo","bastante","bien","breve","buen","buena","buenas","bueno","buenos","c","cada","casi","cerca","cierta","ciertas","cierto","ciertos","cinco","claro","comentĆ³","como","con","conmigo","conocer","conseguimos","conseguir","considera","considerĆ³","consigo","consigue","consiguen","consigues","contigo","contra","cosas","creo","cual","cuales","cualquier","cuando","cuanta","cuantas","cuanto","cuantos","cuatro","cuenta","cuĆ”l","cuĆ”les","cuĆ”ndo","cuĆ”nta","cuĆ”ntas","cuĆ”nto","cuĆ”ntos","cĆ³mo","d","da","dado","dan","dar","de","debajo","debe","deben","debido","decir","dejĆ³","del","delante","demasiado","demĆ”s","dentro","deprisa","desde","despacio","despues","despuĆ©s","detras","detrĆ”s","dia","dias","dice","dicen","dicho","dieron","diferente","diferentes","dijeron","dijo","dio","donde","dos","durante","dĆa","dĆas","dĆ³nde","e","ejemplo","el","ella","ellas","ello","ellos","embargo","empleais","emplean","emplear","empleas","empleo","en","encima","encuentra","enfrente","enseguida","entonces","entre","era","erais","eramos","eran","eras","eres","es","esa","esas","ese","eso","esos","esta","estaba","estabais","estaban","estabas","estad","estada","estadas","estado","estados","estais","estamos","estan","estando","estar","estaremos","estarĆ”","estarĆ”n","estarĆ”s","estarĆ©","estarĆ©is","estarĆa","estarĆais","estarĆamos","estarĆan","estarĆas","estas","este","estemos","esto","estos","estoy","estuve","estuviera","estuvierais","estuvieran","estuvieras","estuvieron","estuviese","estuvieseis","estuviesen","estuvieses","estuvimos","estuviste","estuvisteis","estuviĆ©ramos","estuviĆ©semos","estuvo","estĆ”","estĆ”bamos","estĆ”is","estĆ”n","estĆ”s","estĆ©","estĆ©is","estĆ©n","estĆ©s","ex","excepto","existe","existen","explicĆ³","expresĆ³","f","fin","final","fue","fuera","fuerais","fueran","fueras","fueron","fuese","fueseis","fuesen","fueses","fui","fuimos","fuiste","fuisteis","fuĆ©ramos","fuĆ©semos","g","general","gran","grandes","gueno","h","ha","haber","habia","habida","habidas","habido","habidos","habiendo","habla","hablan","habremos","habrĆ”","habrĆ”n","habrĆ”s","habrĆ©","habrĆ©is","habrĆa","habrĆais","habrĆamos","habrĆan","habrĆas","habĆ©is","habĆa","habĆais","habĆamos","habĆan","habĆas","hace","haceis","hacemos","hacen","hacer","hacerlo","haces","hacia","haciendo","hago","han","has","hasta","hay","haya","hayamos","hayan","hayas","hayĆ”is","he","hecho","hemos","hicieron","hizo","horas","hoy","hube","hubiera","hubierais","hubieran","hubieras","hubieron","hubiese","hubieseis","hubiesen","hubieses","hubimos","hubiste","hubisteis","hubiĆ©ramos","hubiĆ©semos","hubo","i","igual","incluso","indicĆ³","informo","informĆ³","intenta","intentais","intentamos","intentan","intentar","intentas","intento","ir","j","junto","k","l","la","lado","largo","las","le","lejos","les","llegĆ³","lleva","llevar","lo","los","luego","lugar","m","mal","manera","manifestĆ³","mas","mayor","me","mediante","medio","mejor","mencionĆ³","menos","menudo","mi","mia","mias","mientras","mio","mios","mis","misma","mismas","mismo","mismos","modo","momento","mucha","muchas","mucho","muchos","muy","mĆ”s","mĆ","mĆa","mĆas","mĆo","mĆos","n","nada","nadie","ni","ninguna","ningunas","ninguno","ningunos","ningĆŗn","no","nos","nosotras","nosotros","nuestra","nuestras","nuestro","nuestros","nueva","nuevas","nuevo","nuevos","nunca","o","ocho","os","otra","otras","otro","otros","p","pais","para","parece","parte","partir","pasada","pasado","paƬs","peor","pero","pesar","poca","pocas","poco","pocos","podeis","podemos","poder","podria","podriais","podriamos","podrian","podrias","podrĆ”","podrĆ”n","podrĆa","podrĆan","poner","por","por quĆ©","porque","posible","primer","primera","primero","primeros","principalmente","pronto","propia","propias","propio","propios","proximo","prĆ³ximo","prĆ³ximos","pudo","pueda","puede","pueden","puedo","pues","q","qeu","que","quedĆ³","queremos","quien","quienes","quiere","quiza","quizas","quizĆ”","quizĆ”s","quiĆ©n","quiĆ©nes","quĆ©","r","raras","realizado","realizar","realizĆ³","repente","respecto","s","sabe","sabeis","sabemos","saben","saber","sabes","sal","salvo","se","sea","seamos","sean","seas","segun","segunda","segundo","segĆŗn","seis","ser","sera","seremos","serĆ”","serĆ”n","serĆ”s","serĆ©","serĆ©is","serĆa","serĆais","serĆamos","serĆan","serĆas","seĆ”is","seƱalĆ³","si","sido","siempre","siendo","siete","sigue","siguiente","sin","sino","sobre","sois","sola","solamente","solas","solo","solos","somos","son","soy","soyos","su","supuesto","sus","suya","suyas","suyo","suyos","sĆ©","sĆ","sĆ³lo","t","tal","tambien","tambiĆ©n","tampoco","tan","tanto","tarde","te","temprano","tendremos","tendrĆ”","tendrĆ”n","tendrĆ”s","tendrĆ©","tendrĆ©is","tendrĆa","tendrĆais","tendrĆamos","tendrĆan","tendrĆas","tened","teneis","tenemos","tener","tenga","tengamos","tengan","tengas","tengo","tengĆ”is","tenida","tenidas","tenido","tenidos","teniendo","tenĆ©is","tenĆa","tenĆais","tenĆamos","tenĆan","tenĆas","tercera","ti","tiempo","tiene","tienen","tienes","toda","todas","todavia","todavĆa","todo","todos","total","trabaja","trabajais","trabajamos","trabajan","trabajar","trabajas","trabajo","tras","trata","travĆ©s","tres","tu","tus","tuve","tuviera","tuvierais","tuvieran","tuvieras","tuvieron","tuviese","tuvieseis","tuviesen","tuvieses","tuvimos","tuviste","tuvisteis","tuviĆ©ramos","tuviĆ©semos","tuvo","tuya","tuyas","tuyo","tuyos","tĆŗ","u","ultimo","un","una","unas","uno","unos","usa","usais","usamos","usan","usar","usas","uso","usted","ustedes","v","va","vais","valor","vamos","van","varias","varios","vaya","veces","ver","verdad","verdadera","verdadero","vez","vosotras","vosotros","voy","vuestra","vuestras","vuestro","vuestros","w","x","y","ya","yo","z","Ć©l","Ć©ramos","Ć©sa","Ć©sas","Ć©se","Ć©sos","Ć©sta","Ć©stas","Ć©ste","Ć©stos","Ćŗltima","Ćŗltimas","Ćŗltimo","Ćŗltimos"],"sw":["akasema","alikuwa","alisema","baada","basi","bila","cha","chini","hadi","hapo","hata","hivyo","hiyo","huku","huo","ili","ilikuwa","juu","kama","karibu","katika","kila","kima","kisha","kubwa","kutoka","kuwa","kwa","kwamba","kwenda","kwenye","la","lakini","mara","mdogo","mimi","mkubwa","mmoja","moja","muda","mwenye","na","naye","ndani","ng","ni","nini","nonkungu","pamoja","pia","sana","sasa","sauti","tafadhali","tena","tu","vile","wa","wakati","wake","walikuwa","wao","watu","wengine","wote","ya","yake","yangu","yao","yeye","yule","za","zaidi","zake"],"sv":["aderton","adertonde","adjƶ","aldrig","alla","allas","allt","alltid","alltsĆ„","andra","andras","annan","annat","artonde","artonn","att","av","bakom","bara","behƶva","behƶvas","behƶvde","behƶvt","beslut","beslutat","beslutit","bland","blev","bli","blir","blivit","bort","borta","bra","bƤst","bƤttre","bĆ„da","bĆ„das","dag","dagar","dagarna","dagen","de","del","delen","dem","den","denna","deras","dess","dessa","det","detta","dig","din","dina","dit","ditt","dock","dom","du","dƤr","dƤrfƶr","dĆ„","e","efter","eftersom","ej","elfte","eller","elva","emot","en","enkel","enkelt","enkla","enligt","ens","er","era","ers","ert","ett","ettusen","fanns","fem","femte","femtio","femtionde","femton","femtonde","fick","fin","finnas","finns","fjorton","fjortonde","fjƤrde","fler","flera","flesta","fram","framfƶr","frĆ„n","fyra","fyrtio","fyrtionde","fĆ„","fĆ„r","fĆ„tt","fƶljande","fƶr","fƶre","fƶrlĆ„t","fƶrra","fƶrsta","genast","genom","gick","gjorde","gjort","god","goda","godare","godast","gott","gƤlla","gƤller","gƤllt","gƤrna","gĆ„","gĆ„r","gĆ„tt","gƶr","gƶra","ha","hade","haft","han","hans","har","heller","hellre","helst","helt","henne","hennes","hit","hon","honom","hundra","hundraen","hundraett","hur","hƤr","hƶg","hƶger","hƶgre","hƶgst","i","ibland","icke","idag","igen","igĆ„r","imorgon","in","infƶr","inga","ingen","ingenting","inget","innan","inne","inom","inte","inuti","ja","jag","jo","ju","just","jƤmfƶrt","kan","kanske","knappast","kom","komma","kommer","kommit","kr","kunde","kunna","kunnat","kvar","legat","ligga","ligger","lika","likstƤlld","likstƤllda","lilla","lite","liten","litet","lƤnge","lƤngre","lƤngst","lƤtt","lƤttare","lƤttast","lĆ„ngsam","lĆ„ngsammare","lĆ„ngsammast","lĆ„ngsamt","lĆ„ngt","lĆ„t","man","med","mej","mellan","men","mer","mera","mest","mig","min","mina","mindre","minst","mitt","mittemot","mot","mycket","mĆ„nga","mĆ„ste","mƶjlig","mƶjligen","mƶjligt","mƶjligtvis","ned","nederst","nedersta","nedre","nej","ner","ni","nio","nionde","nittio","nittionde","nitton","nittonde","nog","noll","nr","nu","nummer","nƤr","nƤsta","nĆ„gon","nĆ„gonting","nĆ„got","nĆ„gra","nĆ„n","nĆ„nting","nĆ„t","nƶdvƤndig","nƶdvƤndiga","nƶdvƤndigt","nƶdvƤndigtvis","och","ocksĆ„","ofta","oftast","olika","olikt","om","oss","pĆ„","rakt","redan","rƤtt","sa","sade","sagt","samma","sedan","senare","senast","sent","sex","sextio","sextionde","sexton","sextonde","sig","sin","sina","sist","sista","siste","sitt","sitta","sju","sjunde","sjuttio","sjuttionde","sjutton","sjuttonde","sjƤlv","sjƤtte","ska","skall","skulle","slutligen","smĆ„","smĆ„tt","snart","som","stor","stora","stort","stƶrre","stƶrst","sƤga","sƤger","sƤmre","sƤmst","sĆ„","sĆ„dan","sĆ„dana","sĆ„dant","ta","tack","tar","tidig","tidigare","tidigast","tidigt","till","tills","tillsammans","tio","tionde","tjugo","tjugoen","tjugoett","tjugonde","tjugotre","tjugotvĆ„","tjungo","tolfte","tolv","tre","tredje","trettio","trettionde","tretton","trettonde","tvĆ„","tvĆ„hundra","under","upp","ur","ursƤkt","ut","utan","utanfƶr","ute","va","vad","var","vara","varfƶr","varifrĆ„n","varit","varje","varken","vars","varsĆ„god","vart","vem","vems","verkligen","vi","vid","vidare","viktig","viktigare","viktigast","viktigt","vilka","vilkas","vilken","vilket","vill","vƤl","vƤnster","vƤnstra","vƤrre","vĆ„r","vĆ„ra","vĆ„rt","Ƥn","Ƥnnu","Ƥr","Ƥven","Ć„t","Ć„tminstone","Ć„tta","Ć„ttio","Ć„ttionde","Ć„ttonde","ƶver","ƶvermorgon","ƶverst","ƶvre"],"th":["ąøąø„ą¹ąø²ąø§","ąøąø§ą¹ąø²","ąøąø±ąø","ąøąø±ąø","ąøąø²ąø£","ąøą¹","ąøą¹ąøąø","ąøąøąø°","ąøąø","ąøąøąø","ąøąø¶ą¹ąø","ąøąø","ąøąø£ąø±ą¹ąø","ąøąø§ąø²ąø”","ąøąø·ąø","ąøąø°","ąøąø±ąø","ąøąø²ąø","ąøąø¶ąø","ąøą¹ąø§ąø","ąøąø¶ą¹ąø","ąøąø±ąø","ąøą¹ąø§ąø¢","ąøą¹ąø²ąø","ąøąø±ą¹ąø","ąøąø±ą¹ąøą¹ąøą¹","ąøąø²ąø”","ąøą¹ąø","ąøą¹ąø²ąø","ąøą¹ąø²ąøą¹","ąøą¹ąøąø","ąøąø¶ąø","ąøąø¹ąø","ąøą¹ąø²","ąøąø±ą¹ąø","ąøąø±ą¹ąøąøąøµą¹","ąøąø²ąø","ąøąøµą¹","ąøąøµą¹ąøŖąøøąø","ąøąøøąø","ąøą¹ąø²","ąøą¹ąø²ą¹ąø«ą¹","ąøąøąøąøąø²ąø","ąøąø±ąø","ąøąø±ą¹ąø","ąøąøµą¹","ąøą¹ąø²","ąøą¹ąø²","ąøąø²ąø","ąøąø„","ąøą¹ąø²ąø","ąøąø","ąøąø£ą¹ąøąø”","ąø”ąø²","ąø”ąø²ąø","ąø”ąøµ","ąø¢ąø±ąø","ąø£ąø§ąø”","ąø£ąø°ąø«ąø§ą¹ąø²ąø","ąø£ąø±ąø","ąø£ąø²ąø¢","ąø£ą¹ąø§ąø”","ąø„ąø","ąø§ąø±ąø","ąø§ą¹ąø²","ąøŖąøøąø","ąøŖą¹ąø","ąøŖą¹ąø§ąø","ąøŖą¹ąø²ąø«ąø£ąø±ąø","ąø«ąøąø¶ą¹ąø","ąø«ąø£ąø·ąø","ąø«ąø„ąø±ąø","ąø«ąø„ąø±ąøąøąø²ąø","ąø«ąø„ąø²ąø¢","ąø«ąø²ąø","ąøąø¢ąø²ąø","ąøąø¢ąø¹ą¹","ąøąø¢ą¹ąø²ąø","ąøąøąø","ąøąø°ą¹ąø£","ąøąø²ąø","ąøąøµąø","ą¹ąøąø²","ą¹ąøą¹ąø²","ą¹ąøąø¢","ą¹ąøąøąø²ąø°","ą¹ąøą¹ąø","ą¹ąøąøµąø¢ąø§","ą¹ąøąøµąø¢ąø§ąøąø±ąø","ą¹ąøąø·ą¹ąøąøąøąø²ąø","ą¹ąøąø“ąø","ą¹ąøąø“ąøą¹ąøąø¢","ą¹ąøą¹ąø","ą¹ąøą¹ąøąøąø²ąø£","ą¹ąøąø£ąø²ąø°","ą¹ąøąø·ą¹ąø","ą¹ąø”ąø·ą¹ąø","ą¹ąø£ąø²","ą¹ąø£ąø“ą¹ąø”","ą¹ąø„ąø¢","ą¹ąø«ą¹ąø","ą¹ąøąø","ą¹ąøą¹","ą¹ąøąø","ą¹ąø£ąø","ą¹ąø„ąø°","ą¹ąø„ą¹ąø§","ą¹ąø«ą¹ąø","ą¹ąøąø¢","ą¹ąø","ą¹ąø«ą¹","ą¹ąøą¹","ą¹ąø","ą¹ąø”ą¹","ą¹ąø§ą¹","ą¹ąø"],"tl":["akin","aking","ako","alin","am","amin","aming","ang","ano","anumang","apat","at","atin","ating","ay","bababa","bago","bakit","bawat","bilang","dahil","dalawa","dapat","din","dito","doon","gagawin","gayunman","ginagawa","ginawa","ginawang","gumawa","gusto","habang","hanggang","hindi","huwag","iba","ibaba","ibabaw","ibig","ikaw","ilagay","ilalim","ilan","inyong","isa","isang","itaas","ito","iyo","iyon","iyong","ka","kahit","kailangan","kailanman","kami","kanila","kanilang","kanino","kanya","kanyang","kapag","kapwa","karamihan","katiyakan","katulad","kaya","kaysa","ko","kong","kulang","kumuha","kung","laban","lahat","lamang","likod","lima","maaari","maaaring","maging","mahusay","makita","marami","marapat","masyado","may","mayroon","mga","minsan","mismo","mula","muli","na","nabanggit","naging","nagkaroon","nais","nakita","namin","napaka","narito","nasaan","ng","ngayon","ni","nila","nilang","nito","niya","niyang","noon","o","pa","paano","pababa","paggawa","pagitan","pagkakaroon","pagkatapos","palabas","pamamagitan","panahon","pangalawa","para","paraan","pareho","pataas","pero","pumunta","pumupunta","sa","saan","sabi","sabihin","sarili","sila","sino","siya","tatlo","tayo","tulad","tungkol","una","walang"],"tr":["acaba","acep","adamakıllı","adeta","ait","altmĆ½Ć¾","altmıÅ","altĆ½","altı","ama","amma","anca","ancak","arada","artĆ½k","aslında","aynen","ayrıca","az","aƧıkƧa","aƧıkƧası","bana","bari","bazen","bazĆ½","bazı","baÅkası","baÅ£ka","belki","ben","benden","beni","benim","beri","beriki","beĆ¾","beÅ","beÅ£","bilcĆ¼mle","bile","bin","binaen","binaenaleyh","bir","biraz","birazdan","birbiri","birden","birdenbire","biri","birice","birileri","birisi","birkaƧ","birkaƧı","birkez","birlikte","birƧok","birƧoÄu","birĆ¾ey","birĆ¾eyi","birÅey","birÅeyi","birÅ£ey","bitevi","biteviye","bittabi","biz","bizatihi","bizce","bizcileyin","bizden","bize","bizi","bizim","bizimki","bizzat","boÅuna","bu","buna","bunda","bundan","bunlar","bunları","bunların","bunu","bunun","buracıkta","burada","buradan","burası","bƶyle","bƶylece","bƶylecene","bƶylelikle","bƶylemesine","bƶylesine","bĆ¼sbĆ¼tĆ¼n","bĆ¼tĆ¼n","cuk","cĆ¼mlesi","da","daha","dahi","dahil","dahilen","daima","dair","dayanarak","de","defa","dek","demin","demincek","deminden","denli","derakap","derhal","derken","deÄil","deÄil","deÄin","diye","diÄer","diÄer","diÄeri","doksan","dokuz","dolayı","dolayısıyla","doÄru","dƶrt","edecek","eden","ederek","edilecek","ediliyor","edilmesi","ediyor","elbet","elbette","elli","emme","en","enikonu","epey","epeyce","epeyi","esasen","esnasında","etmesi","etraflı","etraflıca","etti","ettiÄi","ettiÄini","evleviyetle","evvel","evvela","evvelce","evvelden","evvelemirde","evveli","eÄer","eÄer","fakat","filanca","gah","gayet","gayetle","gayri","gayrı","gelgelelim","gene","gerek","gerƧi","geƧende","geƧenlerde","gibi","gibilerden","gibisinden","gine","gƶre","gırla","hakeza","halbuki","halen","halihazırda","haliyle","handiyse","hangi","hangisi","hani","hariƧ","hasebiyle","hasılı","hatta","hele","hem","henĆ¼z","hep","hepsi","her","herhangi","herkes","herkesin","hiƧ","hiƧbir","hiƧbiri","hoÅ","hulasaten","iken","iki","ila","ile","ilen","ilgili","ilk","illa","illaki","imdi","indinde","inen","insermi","ise","ister","itibaren","itibariyle","itibarıyla","iyi","iyice","iyicene","iƧin","iÅ","iÅte","iÅ£te","kadar","kaffesi","kah","kala","kanĆ½mca","karÅın","katrilyon","kaynak","kaƧı","kelli","kendi","kendilerine","kendini","kendisi","kendisine","kendisini","kere","kez","keza","kezalik","keÅke","keÅ£ke","ki","kim","kimden","kime","kimi","kimisi","kimse","kimsecik","kimsecikler","kĆ¼lliyen","kĆ½rk","kĆ½saca","kırk","kısaca","lakin","leh","lĆ¼tfen","maada","madem","mademki","mamafih","mebni","meÄer","meÄer","meÄerki","meÄerse","milyar","milyon","mu","mĆ¼","mĆ½","mı","nasĆ½l","nasıl","nasılsa","nazaran","naÅi","ne","neden","nedeniyle","nedenle","nedense","nerde","nerden","nerdeyse","nere","nerede","nereden","neredeyse","neresi","nereye","netekim","neye","neyi","neyse","nice","nihayet","nihayetinde","nitekim","niye","niƧin","o","olan","olarak","oldu","olduklarını","oldukƧa","olduÄu","olduÄunu","olmadı","olmadıÄı","olmak","olması","olmayan","olmaz","olsa","olsun","olup","olur","olursa","oluyor","on","ona","onca","onculayın","onda","ondan","onlar","onlardan","onlari","onlarĆ½n","onları","onların","onu","onun","oracık","oracıkta","orada","oradan","oranca","oranla","oraya","otuz","oysa","oysaki","pek","pekala","peki","pekƧe","peyderpey","raÄmen","sadece","sahi","sahiden","sana","sanki","sekiz","seksen","sen","senden","seni","senin","siz","sizden","sizi","sizin","sonra","sonradan","sonraları","sonunda","tabii","tam","tamam","tamamen","tamamıyla","tarafından","tek","trilyon","tĆ¼m","var","vardı","vasıtasıyla","ve","velev","velhasıl","velhasılıkelam","veya","veyahut","ya","yahut","yakinen","yakında","yakından","yakınlarda","yalnız","yalnızca","yani","yapacak","yapmak","yaptı","yaptıkları","yaptıÄı","yaptıÄını","yapılan","yapılması","yapıyor","yedi","yeniden","yenilerde","yerine","yetmiĆ¾","yetmiÅ","yetmiÅ£","yine","yirmi","yok","yoksa","yoluyla","yĆ¼z","yĆ¼zĆ¼nden","zarfında","zaten","zati","zira","Ƨabuk","ƧabukƧa","ƧeÅitli","Ƨok","Ƨokları","Ƨoklarınca","Ƨokluk","Ƨoklukla","ƧokƧa","ƧoÄu","ƧoÄun","ƧoÄunca","ƧoÄunlukla","Ć§Ć¼nkĆ¼","ƶbĆ¼r","ƶbĆ¼rkĆ¼","ƶbĆ¼rĆ¼","ƶnce","ƶnceden","ƶnceleri","ƶncelikle","ƶteki","ƶtekisi","ƶyle","ƶylece","ƶylelikle","ƶylemesine","ƶz","Ć¼zere","Ć¼Ć§","Ć¾ey","Ć¾eyden","Ć¾eyi","Ć¾eyler","Ć¾u","Ć¾una","Ć¾unda","Ć¾undan","Ć¾unu","Åayet","Åey","Åeyden","Åeyi","Åeyler","Åu","Åuna","Åuncacık","Åunda","Åundan","Åunlar","Åunları","Åunu","Åunun","Åura","Åuracık","Åuracıkta","Åurası","Åƶyle","Å£ayet","Å£imdi","Å£u","Å£Ć¶yle"],"uk":["Š°Š²Š¶ŠµŠ¶","Š°Š“Š¶Šµ","Š°Š»Šµ","Š±","Š±ŠµŠ·","Š±ŃŠ²","Š±ŃŠ»Š°","Š±ŃŠ»Šø","Š±ŃŠ»Š¾","Š±ŃŃŠø","Š±ŃŠ»ŃŃ","Š²Š°Š¼","Š²Š°Ń","Š²ŠµŃŃ","Š²Š·Š“Š¾Š²Š¶","Š²Šø","Š²Š½ŠøŠ·","Š²Š½ŠøŠ·Ń","Š²Š¾Š½Š°","Š²Š¾Š½Šø","Š²Š¾Š½Š¾","Š²ŃŠµ","Š²ŃŠµŃŠµŠ“ŠøŠ½Ń","Š²ŃŃŃ
","Š²ŃŠ“","Š²ŃŠ½","Š“Š°","Š“Š°Š²Š°Š¹","Š“Š°Š²Š°ŃŠø","Š“Šµ","Š“ŠµŃŠ¾","Š“Š»Ń","Š“Š¾","Š·","Š·Š°Š²Š¶Š“Šø","Š·Š°Š¼ŃŃŃŃ","Š¹","ŠŗŠ¾Š»Šø","Š»ŠµŠ“Š²Šµ","Š¼Š°Š¹Š¶Šµ","Š¼Šø","Š½Š°Š²ŠŗŠ¾Š»Š¾","Š½Š°Š²ŃŃŃ","Š½Š°Š¼","Š¾Ń","Š¾ŃŠ¶Šµ","Š¾ŃŠ¾Š¶","ŠæŠ¾Š·Š°","ŠæŃŠ¾","ŠæŃŠ“","ŃŠ°","ŃŠ°Šŗ","ŃŠ°ŠŗŠøŠ¹","ŃŠ°ŠŗŠ¾Š¶","ŃŠµ","ŃŠø","ŃŠ¾Š±ŃŠ¾","ŃŠ¾Š¶","ŃŠ¾ŃŠ¾","Ń
Š¾ŃŠ°","ŃŠµ","ŃŠµŠ¹","ŃŠø","ŃŠ¾Š³Š¾","ŃŠ¾","ŃŠŗ","ŃŠŗŠøŠ¹","ŃŠŗŠ¾Ń","Ń","ŃŠ·","ŃŠ½ŃŠøŃ
","ŃŃ
","ŃŃ"],"ur":["Ų¢Ų¦Ū","Ų¢Ų¦Ū","Ų¢Ų¬","Ų¢Ų®Ų±","Ų¢Ų®Ų±Ś©ŲØŲ±","Ų¢ŲÆŁŪ","Ų¢ŁŲØ","Ų¢Ł¹Ś¾","Ų¢ŪŲØ","Ų§Ų©","Ų§Ų®ŲØŲ²ŲŖ","Ų§Ų®ŲŖŲŖŲØŁ
","Ų§ŲÆŚ¾Ų±","Ų§Ų±ŲÆ","Ų§Ų±ŲÆŚÆŲ±ŲÆ","Ų§Ų±Ś©ŲØŁ","Ų§Ų“","Ų§Ų¶ŲŖŲ¹ŁŲØŁ","Ų§Ų¶ŲŖŲ¹ŁŲØŁŲ§ŲŖ","Ų§Ų¶Ų·Ų±Ų°","Ų§Ų¶Ś©ŲØ","Ų§Ų¶Ś©Ū","Ų§Ų¶Ś©Ū","Ų§Ų·Ų±Ų§Ł","Ų§ŲŗŪŲØ","Ų§ŁŲ±Ų§ŲÆ","Ų§ŁŚÆ","Ų§ŁŲ±","Ų§ŁŁŚŲØ","Ų§ŁŁŚŲØŲ¦Ū","Ų§ŁŁŚŪ","Ų§ŁŁŚŪ","Ų§Ł","Ų§Ł","Ų§ŁŲ°Ų±","Ų§ŁŪŪŚŗ","Ų§Ł¹Ś¾ŲØŁŲØ","Ų§Ł¾ŁŲØ","Ų§Ł¾ŁŪ","Ų§ŚŚ¾ŲØ","Ų§ŚŚ¾Ū","Ų§ŚŚ¾Ū","Ų§Ś©Ų«Ų±","Ų§Ś©Ł¹Ś¾ŲØ","Ų§Ś©Ł¹Ś¾Ū","Ų§Ś©Ł¹Ś¾Ū","Ų§Ś©ŪŁŲ§","Ų§Ś©ŪŁŪ","Ų§Ś©ŪŁŪ","Ų§ŚÆŲ±ŚŪ","Ų§ŪŁ","Ų§ŪŲ·Ū","Ų§ŪŚ©","ŲØ","ŲŖ","ŲŖŲØŲ²Ł","ŲŖŲŖ","ŲŖŲ±","ŲŖŲ±ŲŖŪŲŖ","ŲŖŲ±ŪŁ","ŲŖŲ¹Ų°Ų§ŲÆ","ŲŖŁ","ŲŖŁ","ŲŖŁŲØŁ
","ŲŖŁŪŪ","ŲŖŁŪŪŚŗ","ŲŖŁŪŲØ","ŲŖŚ©","ŲŖŚ¾ŲØ","ŲŖŚ¾ŁŚŲ§","ŲŖŚ¾ŁŚŪ","ŲŖŚ¾ŁŚŪ","ŲŖŚ¾Ū","ŲŖŚ¾Ū","ŲŖŪŁ","Ų«ŲØ","Ų«ŲØŲ¦ŪŚŗ","Ų«ŲØŲŖŲ±ŲŖŪŲŖ","Ų«ŲØŲ±Ū","Ų«ŲØŲ±Ū","Ų«ŲØŲ¹Ų«","Ų«ŲØŁŲ§","Ų«ŲØŁŲŖŲ±ŲŖŪŲŖ","Ų«ŲØŪŲ±","Ų«ŲÆŲØŲ¦Ū","Ų«Ų±Ų¢Śŗ","Ų«Ų±Ų§Śŗ","Ų«Ų±Ų“","Ų«Ų¹Ų°","Ų«ŲŗŪŲ±","Ų«ŁŁŲ°","Ų«ŁŁŲ°ŁŲ«ŲØŁŲ§","Ų«ŁŚ©Ū","Ų«Ł","Ų«ŁŲØ","Ų«ŁŲØŲ±ŪŲØ","Ų«ŁŲØŲ±ŪŪ","Ų«ŁŲØŲ±ŪŪ","Ų«ŁŲØŁŲØ","Ų«ŁŲ°","Ų«ŁŲ°Ś©Ų±Ł","Ų«ŁŲ°Ś©Ų±ŁŲØ","Ų«ŁŲ°Ū","Ų«ŚŲ§","Ų«ŚŁŚŗ","Ų«ŚŪ","Ų«ŚŪ","Ų«Ś¾Ų±","Ų«Ś¾Ų±Ų§","Ų«Ś¾Ų±Ų§ŪŁŲ§","Ų«Ś¾Ų±Ł¾ŁŲ±","Ų«Ś¾Ū","Ų«ŪŲŖ","Ų«ŪŲŖŲ±","Ų«ŪŲŖŲ±Ū","Ų«ŪŲŖŲ±ŪŁ","Ų«ŪŚ","Ų¬","Ų®ŲØ","Ų®ŲØŲ±ŪŲØ","Ų®ŲØŲ±ŪŪ","Ų®ŲØŲ±ŪŪ","Ų®ŲØŁŁŲø","Ų®ŲØŁŲØ","Ų®ŲØŁŲŖŲØ","Ų®ŲØŁŲŖŪ","Ų®ŲØŁŲŖŪ","Ų®ŲØŁŁŲØ","Ų®ŲŖ","Ų®ŲŖŁ","Ų®Ų¬Ś©Ū","Ų®Ųµ","Ų®Ų·Ų·Ų±Ų°","Ų®ŁŲ°Ū","Ų®Ł","Ų®ŁŲ§Ł","Ų®ŁŁŪŪ","Ų®ŁŚ©Ū","Ų®ŁŲØŲ©","Ų®ŚÆŪ","Ų®ŚÆŪŁŚŗ","Ų®ŚÆŪŪŚŗ","Ų®ŪŲ·ŲØ","Ų®ŪŲ·ŲØŚ©Ū","ŲÆŲ±","ŲÆŲ±Ų®ŲØŲŖ","ŲÆŲ±Ų®Ū","ŲÆŲ±Ų®Ū","ŲÆŲ±Ų²ŁŪŁŲŖ","ŲÆŲ±Ų¶ŲŖ","ŲÆŲ“","ŲÆŁŲ¹Ū","ŲÆŁŚŲ·Ł¾","ŲÆŁŚŲ·Ł¾Ū","ŲÆŁŚŲ·Ł¾ŪŲØŚŗ","ŲÆŁ","ŲÆŁŲ±","ŲÆŁŲ±Ų§Ł","ŲÆŁŲ¶Ų±Ų§","ŲÆŁŲ¶Ų±ŁŚŗ","ŲÆŁŲ¶Ų±Ū","ŲÆŁŲ¶Ų±Ū","ŲÆŁŁŁŚŗ","ŲÆŚ©Ś¾ŲØŲ¦ŪŚŗ","ŲÆŚ©Ś¾ŲØŲŖŲØ","ŲÆŚ©Ś¾ŲØŲŖŪ","ŲÆŚ©Ś¾ŲØŲŖŪ","ŲÆŚ©Ś¾ŲØŁ","ŲÆŚ©Ś¾ŲØŁŲØ","ŲÆŚ©Ś¾ŲØŪŲØ","ŲÆŪ","ŲÆŪŲØ","ŲÆŪŲŖŲØ","ŲÆŪŲŖŪ","ŲÆŪŲŖŪ","ŲÆŪŲ±","ŲÆŪŁŲØ","ŲÆŪŚ©Ś¾Ł","ŲÆŪŚ©Ś¾ŁŲØ","ŲÆŪŚ©Ś¾Ū","ŲÆŪŚ©Ś¾ŪŚŗ","ŲÆŪ","Ų±","Ų±Ų§Ų¶ŲŖŁŚŗ","Ų±Ų§Ų¶ŲŖŪ","Ų±Ų§Ų¶ŲŖŪ","Ų±Ų±ŪŲ¹Ū","Ų±Ų±ŪŲ¹Ū","Ų±Ś©Ł","Ų±Ś©Ś¾","Ų±Ś©Ś¾ŲØ","Ų±Ś©Ś¾ŲŖŲØ","Ų±Ś©Ś¾ŲŖŲØŪŁŚŗ","Ų±Ś©Ś¾ŲŖŪ","Ų±Ś©Ś¾ŲŖŪ","Ų±Ś©Ś¾Ū","Ų±Ś©Ś¾Ū","Ų±ŪŲØ","Ų±ŪŪ","Ų±ŪŪ","Ų²","Ų²ŲØŲµŁ","Ų²ŲØŲ¶Ų±","Ų²ŲØŁ","Ų²ŲØŁŲ§ŲŖ","Ų²ŲØŁŪŪ","Ų²ŲµŁŚŗ","Ų²ŲµŪ","Ų²ŲµŪ","Ų²ŁŲØŲ¦Ł","Ų²ŁŪŲŖŪŚŗ","Ų²ŁŪŁŲŖ","Ų²Ś©Ł","Ų²Ś©ŁŪŪ","Ų²ŪŲØŲÆŁ","ŲµŲØŁ","ŲµŲ³ŪŲ±","ŲµŁŲ±","ŲµŁŲ±ŲŖ","ŲµŁŲ±ŲŖŲ³ŲØŁ","ŲµŁŲ±ŲŖŁŚŗ","ŲµŁŲ±ŲŖŪŚŗ","Ų¶","Ų¶ŲØŲŖ","Ų¶ŲØŲŖŚ¾","Ų¶ŲØŲÆŁ","Ų¶ŲØŲ±Ų§","Ų¶ŲØŲ±Ū","Ų¶ŲØŁ","Ų¶ŲØŁŁŚŗ","Ų¶ŲŖ","Ų¶Ų±ŁŲ±","Ų¶Ų±ŁŲ±ŲŖ","Ų¶Ų±ŁŲ±Ū","Ų¶ŁŲ·ŁŪ","Ų¶ŁŚ","Ų¶ŁŚŲØ","Ų¶ŁŚŲŖŲØ","Ų¶ŁŚŲŖŪ","Ų¶ŁŚŲŖŪ","Ų¶ŁŚŁ","Ų¶ŁŚŁŲØ","Ų¶ŁŚŪ","Ų¶ŁŚŪŚŗ","Ų¶Ś©ŲØ","Ų¶Ś©ŲŖŲØ","Ų¶Ś©ŲŖŪ","Ų¶Ś©ŲŖŪ","Ų¶Ś©ŁŲØ","Ų¶Ś©Ū","Ų¶Ś©Ū","Ų¶ŪŲ°Ś¾ŲØ","Ų¶ŪŲ°Ś¾Ū","Ų¶ŪŲ°Ś¾Ū","Ų¶ŪŚ©ŁŚ","Ų¶Ū","Ų·Ų±Ł","Ų·Ų±ŪŁ","Ų·Ų±ŪŁŁŚŗ","Ų·Ų±ŪŁŪ","Ų·Ų±ŪŁŪ","Ų·ŁŲ±","Ų·ŁŲ±Ł¾Ų±","ŲøŲØŪŲ±","Ų¹","Ų¹Ų°ŲÆ","Ų¹ŲøŪŁ","Ų¹ŁŲ§ŁŁŚŗ","Ų¹ŁŲ§ŁŪ","Ų¹ŁŲ§ŁŪ","Ų¹ŁŲ§ŁŁ","Ų¹ŁŁŁŪ","ŲŗŲØŪŲ°","ŲŗŲ®Ųµ","ŲŗŲ°","ŲŗŲ±ŁŲ¹","ŲŗŲ±ŁŲ¹ŲØŲŖ","ŲŗŪ","ŁŲ±ŲÆ","ŁŪ","Ł","ŁŲ¬Ł","ŁŲ¬ŪŁŪ","ŁŲ·Ł","ŁŲ¦Ū","ŁŲ§","ŁŲ§Ų²ŁŪ","ŁŁ","ŁŁŲ¬ŲØ","ŁŁŲ¬Ū","ŁŁŲ¬Ū","ŁŁŲ³ŲØŲŖ","ŁŁŲ³Ū","ŁŁŚÆ","ŁŁŚÆŁŚŗ","ŁŚŚ©Ł¾Ł","ŁŚÆŲŖŲØ","ŁŚÆŲŖŪ","ŁŚÆŲŖŪ","ŁŚÆŁŲØ","ŁŚÆŪ","ŁŚÆŪŚŗ","ŁŚÆŪ","ŁŪ","ŁŪŲØ","ŁŪŁŲØ","ŁŪŚŗ","ŁŪ","Ł","ŁŲŖŲ¹ŁŁ","ŁŲ®ŲŖŁŁ","ŁŲ³ŲŖŲ±Ł
","ŁŲ³ŲŖŲ±ŁŪ","ŁŲ³Ų·ŁŲ“","ŁŲ³ŪŲ°","ŁŲ·Ų¦ŁŪ","ŁŲ·Ų¦ŁŪ","ŁŲ·ŲØŲ¦Ł","ŁŲ·ŲŖŲ¹ŁŁ","ŁŲ·ŁŁ","ŁŲ¹ŁŁŁ
","ŁŲ»ŲŖŁŁ","ŁŁŲ§","ŁŁŚ©Ł","ŁŁŚ©ŁŲØŲŖ","ŁŁŚ©ŁŪ","ŁŁŲØŲ¶ŲŖ","ŁŚŲ§","ŁŚŁŲØ","ŁŚŪ","ŁŚ©ŁŁ","ŁŚÆŲ±","ŁŪŲ±Ų«ŲØŁ","ŁŪŲ±Ų§","ŁŪŲ±Ū","ŁŪŲ±Ū","ŁŪŚŗ","Ł","ŁŲ§Ų±","ŁŲ§ŁŪ","ŁŁ","ŁŲ¦Ū","ŁŲ¦Ū","ŁŲØ","ŁŲØŁ¾Ų·ŁŲ°","ŁŲØŚÆŲ³ŪŲ±","ŁŲ·Ų¬ŲŖ","ŁŁŲ·Ū","ŁŁ","ŁŁŲ®ŁŲ§Ł","ŁŚ©ŲØŁŁŲØ","ŁŚ©ŲŖŪ","ŁŪ","ŁŪŪŚŗ","ŁŪŲØ","ŁŪ","Ł Ų¢Ų“","Ł¹Ś¾ŪŚ©","Ł¾ŲØŲ¦Ū","Ł¾ŲØŲ“","Ł¾ŲØŁŲØ","Ł¾ŲØŁŚ","Ł¾Ų±","Ł¾Ų±Ų§ŁŲØ","Ł¾Ų·ŁŲ°","Ł¾Ł","Ł¾ŁŲ±Ų§","Ł¾ŁŚŚ¾ŲØ","Ł¾ŁŚŚ¾ŲŖŲØ","Ł¾ŁŚŚ¾ŲŖŪ","Ł¾ŁŚŚ¾ŲŖŪ","Ł¾ŁŚŚ¾Ł","Ł¾ŁŚŚ¾ŁŚŗ","Ł¾ŁŚŚ¾ŁŲØ","Ł¾ŁŚŚ¾ŪŚŗ","Ł¾ŚŚ¾ŁŲ§","Ł¾Ś¾Ų±","Ł¾ŪŁŲ§","Ł¾ŪŁŪ","Ł¾ŪŁŪŲ¶Ū","Ł¾ŪŁŪŲ¶Ū","Ł¾ŪŁŪŲ¶ŪŪŪ","Ł¾ŪŲ¹","ŚŲØŲ±","ŚŲØŪŲØ","ŚŲØŪŁŲØ","ŚŲØŪŪ","ŚŁŲ§","ŚŁŁ","ŚŁŪŚŗ","ŚŁŪ","ŚŚ©ŲØ","ŚŚ©Ū","ŚŚ©ŪŚŗ","ŚŚ©Ū","ŚŚ¾ŁŁ¹ŲØ","ŚŚ¾ŁŁ¹ŁŚŗ","ŚŚ¾ŁŁ¹Ū","ŚŚ¾ŁŁ¹Ū","ŚŚ¾Ū","ŚŪŲ³ŪŚŗ","ŚŚ¾ŁŁŚŲ§","ŚŚ¾ŁŁŚŁŪŲØ","ŚŚ¾ŁŁŚŁ","ŚŚ¾ŁŁŚŁŲØ","ŚŚ¾ŁŁŚŪ","ŚŚ¾ŁŁŚŪŚŗ","Ś©","Ś©Ų¦Ū","Ś©Ų¦Ū","Ś©ŲØ","Ś©ŲØŁŪ","Ś©ŲØŁ
","Ś©ŲŖ","Ś©Ų¬Ś¾Ū","Ś©Ų±Ų§","Ś©Ų±ŲŖŲØ","Ś©Ų±ŲŖŲØŪŁŚŗ","Ś©Ų±ŲŖŪ","Ś©Ų±ŲŖŪ","Ś©Ų±ŲŖŪŪŁ","Ś©Ų±Ų±ŪŲØ","Ś©Ų±Ų±ŪŪ","Ś©Ų±Ų±ŪŪ","Ś©Ų±Ł","Ś©Ų±ŁŲØ","Ś©Ų±ŪŚŗ","Ś©Ų±Ū","Ś©Ų·Ū","Ś©Ł","Ś©Ł","Ś©ŁŲ¦Ū","Ś©ŁŲŖŲ±","Ś©ŁŲ±Ų§","Ś©ŁŲ±ŁŚŗ","Ś©ŁŲ±Ł","Ś©ŁŲ±Ū","Ś©ŁŲ·Ł","Ś©ŁŁ","Ś©ŁŁŲ·ŲØ","Ś©ŁŁŲ·Ū","Ś©ŁŁŲ·Ū","Ś©Ś¾ŁŁŲ§","Ś©Ś¾ŁŁŁ","Ś©Ś¾ŁŁŁŲØ","Ś©Ś¾ŁŁŪ","Ś©Ś¾ŁŁŪŚŗ","Ś©Ś¾ŁŁŪ","Ś©Ū","Ś©ŪŲØ","Ś©ŪŲŖŲØ","Ś©ŪŲŖŪ","Ś©ŪŲŖŪ","Ś©ŪŁ","Ś©ŪŁŚŗ","Ś©ŪŁŲØ","Ś©ŪŪ","Ś©ŪŪŚŗ","Ś©ŪŪ","Ś©Ū","Ś©ŪŲØ","Ś©ŪŲ·ŲØ","Ś©ŪŲ·Ų±Ł","Ś©ŪŲ·Ū","Ś©ŪŁŲ¦Ū","Ś©ŪŁŁŚ©Ū","Ś©ŪŁŚŗ","Ś©ŪŪ","Ś©Ū","Ś©ŪŲ«Ų¹Ų°","Ś©ŪŲ±Ų±ŪŲ¹Ū","ŚÆŲ¦Ū","ŚÆŲ¦Ū","ŚÆŲØ","ŚÆŲ±ŲÆ","ŚÆŲ±ŁŁ","ŚÆŲ±ŁŁ¾","ŚÆŲ±ŁŪŁŚŗ","ŚÆŁŲŖŪ","ŚÆŪ","ŚÆŪŲØ","ŚÆŪ","ŪŲ±","ŪŁ","ŪŁ","ŪŁŲ¦Ū","ŪŁŲ¦Ū","ŪŁŲ§","ŪŁŲØŲ±Ų§","ŪŁŲØŲ±Ū","ŪŁŲØŲ±Ū","ŪŁŲŖŲØ","ŪŁŲŖŪ","ŪŁŲŖŪ","ŪŁŲ±ŪŲØ","ŪŁŲ±ŪŪ","ŪŁŲ±ŪŪ","ŪŁŲ¶Ś©ŲŖŲØ","ŪŁŲ¶Ś©ŲŖŪ","ŪŁŲ¶Ś©ŲŖŪ","ŪŁŁŲØ","ŪŁŁŪ","ŪŁŁŪ","ŪŁŚŚ©ŲØ","ŪŁŚŚ©Ū","ŪŁŚŚ©Ū","ŪŁŚÆŲ¦Ū","ŪŁŚÆŲ¦Ū","ŪŁŚÆŪŲØ","ŪŁŚŗ","ŪŪ","ŪŪŚŗ","ŪŪ","Ū","ŪŁŪŁŪ","ŪŪ","ŪŪŲØŚŗ"],"vi":["a ha","a-lĆ“","ai","ai ai","ai nįŗ„y","alĆ“","amen","anh","bao giį»","bao lĆ¢u","bao nhiĆŖu","bao nįŗ£","bay biįŗæn","biįŗæt","biįŗæt bao","biįŗæt bao nhiĆŖu","biįŗæt chį»«ng nĆ o","biįŗæt mįŗ„y","biįŗæt ÄĆ¢u","biįŗæt ÄĆ¢u chį»«ng","biįŗæt ÄĆ¢u Äįŗ„y","bĆ ","bĆ i","bĆ”c","bĆ¢y bįŗ©y","bĆ¢y chį»«","bĆ¢y giį»","bĆ¢y nhiĆŖu","bĆØn","bĆ©ng","bĆ“ng","bįŗ”n","bįŗ£n","bįŗ„t chį»£t","bįŗ„t cį»©","bįŗ„t giĆ”c","bįŗ„t kƬ","bįŗ„t kį»","bįŗ„t kį»³","bįŗ„t luįŗn","bįŗ„t nhĘ°į»£c","bįŗ„t quĆ”","bįŗ„t thƬnh lƬnh","bįŗ„t tį»","bįŗ„t Äį»","bįŗ„y","bįŗ„y chįŗ§y","bįŗ„y chį»«","bįŗ„y giį»","bįŗ„y lĆ¢u","bįŗ„y lĆ¢u nay","bįŗ„y nay","bįŗ„y nhiĆŖu","bįŗp bĆ bįŗp bƵm","bįŗp bƵm","bįŗÆt Äįŗ§u tį»«","bįŗ±ng","bįŗ±ng khĆ“ng","bįŗ±ng nįŗ„y","bįŗ±ng įŗ„y","bį»n","bį»t","bį»","bį» mįŗ¹","bį»ng","bį»ng chį»c","bį»ng dĘ°ng","bį»ng khĆ“ng","bį»ng nhiĆŖn","bį»ng ÄĆ¢u","bį»","bį»i phįŗ§n","bį»","bį»i","bį»i chĘ°ng","bį»i nhĘ°ng","bį»i thįŗæ","bį»i vƬ","bį»i vįŗy","bį»©c","cao","cha","cha chįŗ£","chao Ć“i","chiįŗæc","cho","cho nĆŖn","cho tį»i","cho tį»i khi","cho Äįŗæn","cho Äįŗæn khi","choa","chu cha","chui cha","chung cį»„c","chung qui","chung quy","chung quy lįŗ”i","chuyį»n","chĆ nh chįŗ”nh","chĆ chįŗæt","chĆnh","chĆnh lĆ ","chĆnh thį»","chĆ¹n chĆ¹n","chĆ¹n chÅ©n","chĆŗ","chĆŗ mĆ y","chĆŗ mƬnh","chĆŗng mƬnh","chĆŗng ta","chĆŗng tĆ“i","chÄn chįŗÆn","chÄng","chĘ°a","chįŗ§m chįŗp","chįŗc","chįŗÆc","chįŗÆc hįŗ³n","chįŗ³ng lįŗ½","chįŗ³ng nhį»Æng","chįŗ³ng nį»Æa","chįŗ³ng phįŗ£i","chįŗæt nį»i","chįŗæt thįŗt","chįŗæt tiį»t","chį»","chį»n","chį»c chį»c","chį»","chį» chi","chį»£t","chį»§n","chį»©","chį»© lį»","coi bį»","coi mĆ²i","con","cu cįŗu","cuį»n","cuį»c","cĆ ng","cĆ”c","cĆ”i","cĆ¢y","cĆ²n","cĆ³","cĆ³ chÄng lĆ ","cĆ³ dį»
","cĆ³ thį»","cĆ³ vįŗ»","cĆ³c khĆ“","cĆ“","cĆ“ mƬnh","cĆ“ng nhiĆŖn","cĆ¹ng","cĆ¹ng cį»±c","cĆ¹ng nhau","cĆ¹ng vį»i","cÄn","cÄn cįŗÆt","cÅ©ng","cÅ©ng nhĘ°","cÅ©ng vįŗy","cÅ©ng vįŗy thĆ“i","cĘ”","cĘ” chį»«ng","cĘ” hį»","cĘ” mĆ ","cĘ”n","cįŗ£","cįŗ£ thįŗ£y","cįŗ£ thį»","cįŗ£m Ę”n","cįŗ§n","cįŗt lį»±c","cįŗt sį»©c","cįŗu","cį» lai","cį»§a","cį»©","cį»© viį»c","cį»±c lį»±c","do","do vƬ","do vįŗy","do ÄĆ³","duy","dĆ o","dƬ","dĆ¹ cho","dĆ¹ rįŗ±ng","dĘ°į»i","dįŗ”","dįŗ§n dĆ ","dįŗ§n dįŗ§n","dįŗ§u sao","dįŗ«u","dįŗ«u sao","dį»
sį»£","dį»
thĘ°į»ng","dį» chį»«ng","dį»Æ","em","giį»Æa","gƬ","hay","hoĆ n toĆ n","hoįŗ·c","hĘ”n","hįŗ§u hįŗæt","hį»","hį»i","khi","khĆ”c","khĆ“ng","luĆ“n","lĆ ","lĆ m","lĆŖn","lĆŗc","lįŗ”i","lįŗ§n","lį»n","muį»n","mĆ ","mƬnh","mį»i","mį»t","mį»t cĆ”ch","mį»i","mį»£","ngay","ngay cįŗ£","ngay khi","ngay lĆŗc","ngay lįŗp tį»©c","ngay tį»©c khįŗÆc","ngay tį»«","nghe chį»«ng","nghe ÄĆ¢u","nghen","nghiį»
m nhiĆŖn","nghį»m","ngoĆ i","ngoĆ i ra","ngoįŗ£i","ngĆ y","ngĆ y cĆ ng","ngĆ y ngĆ y","ngĆ y xĘ°a","ngĆ y xį»a","ngĆ“i","ngƵ hįŗ§u","ngÄn ngįŗÆt","ngĘ°Ę”i","ngĘ°į»i","ngį»n","ngį»t","ngį» nhį»”","nh","nhau","nhiĆŖn hįŗu","nhiį»u","nhiį»t liį»t","nhung nhÄng","nhĆ ","nhĆ¢n dį»p","nhĆ¢n tiį»n","nhĆ©","nhĆ³n nhĆ©n","nhĘ°","nhĘ° chĘ”i","nhĘ° khĆ“ng","nhĘ° quįŗ£","nhĘ° thį»","nhĘ° tuį»ng","nhĘ° vįŗy","nhĘ°ng","nhĘ°ng mĆ ","nhĘ°į»£c bįŗ±ng","nhįŗ„t","nhįŗ„t loįŗ”t","nhįŗ„t luįŗt","nhįŗ„t mį»±c","nhįŗ„t nhįŗ„t","nhįŗ„t quyįŗæt","nhįŗ„t sinh","nhįŗ„t thiįŗæt","nhįŗ„t tĆ¢m","nhįŗ„t tį»","nhįŗ„t ÄĆ”n","nhįŗ„t Äį»nh","nhįŗn","nhį»","nhį»” ra","nhį»Æng","nhį»Æng ai","nhį»Æng nhĘ°","nĆ o","nĆ y","nĆŖn","nĆŖn chi","nĆ³","nĆ³c","nĆ³i","nÄm","nĘ”i","nįŗ„y","nįŗæu","nįŗæu nhĘ°","nį»n","nį»","nį»","nį»©c nį»","nį»Æa","oai oĆ”i","oĆ”i","pho","phĆØ","phĆ³c","phĆ³t","phÄn phįŗÆt","phĘ°Ę”ng chi","phįŗ£i","phįŗ£i chi","phįŗ£i chÄng","phįŗÆt","phį» phui","phį»ng","phį»ng nhĘ°","phį»c","phį»„t","phį»©t","qua","qua quĆt","qua quĆ½t","quyįŗæt","quyįŗæt nhiĆŖn","quyį»n","quĆ”","quĆ” chį»«ng","quĆ” lįŗÆm","quĆ” sĆ”","quĆ” thį»","quĆ” trį»i","quĆ” xĆ”","quĆ” Äį»i","quĆ” Äį»","quĆ” Ę°","quĆ½ hį»","quįŗ£","quįŗ£ lĆ ","quįŗ£ tang","quįŗ£ thįŗt","quįŗ£ tƬnh","quįŗ£ vįŗy","quįŗ£ ÄĆŗng","ra","ra phįŗæt","ra sao","ra trĆ²","ren rĆ©n","riu rĆu","riĆŖng","riį»t","rĆ y","rĆ”o","rĆ”o trį»i","rĆ©n","rĆch","rĆ³n rĆ©n","rĆŗt cį»„c","rÄng","rįŗ„t","rįŗ±ng","rįŗ±ng lĆ ","rį»t cuį»c","rį»t cį»„c","rį»i","rį»©a","sa sįŗ£","sao","sau","sau chĆ³t","sau cuį»i","sau cĆ¹ng","sau ÄĆ³","so","song le","suĆ½t","sƬ","sįŗ”ch","sįŗ„t","sįŗÆp","sįŗ½","sį»","sį» lĆ ","sį»t sį»t","sį» dÄ©","sį»±","tanh","tha hį»","than Ć“i","thanh","theo","thi thoįŗ£ng","thoįŗ”t","thoįŗ”t nhiĆŖn","thoįŗÆt","thuįŗ§n","thĆ ","thĆ lĆ ","thĆ rįŗ±ng","thĆ nh ra","thĆ nh thį»","thĆ”i quĆ”","thĆ”ng","thƬ","thƬ thĆ“i","thƬnh lƬnh","thĆm","thĆ“i","thĆŗng thįŗÆng","thĘ°Ę”ng Ć“i","thĘ°į»ng","thįŗ£o hĆØn","thįŗ£o nĆ o","thįŗ„y","thįŗ©y","thįŗm","thįŗm chĆ","thįŗt lį»±c","thįŗt ra","thįŗt vįŗy","thįŗæ","thįŗæ lĆ ","thįŗæ mĆ ","thįŗæ nĆ o","thįŗæ nĆŖn","thįŗæ ra","thįŗæ thƬ","thįŗæ Ć ","thįŗæch","thį»nh thoįŗ£ng","thį»m","thį»c","thį»c thĆ”o","thį»t","thį»t nhiĆŖn","thį»c","thį»i gian","thį»„c mįŗ”ng","thį»a","thį»±c ra","thį»±c sį»±","thį»±c vįŗy","tiįŗæp theo","tiįŗæp ÄĆ³","tiį»n thį»","toĆ ","toĆ© khĆ³i","toįŗ¹t","trong","trĆŖn","trĘ°į»c","trĘ°į»c kia","trĘ°į»c nay","trĘ°į»c tiĆŖn","trĘ°į»c ÄĆ¢y","trĘ°į»c ÄĆ³","trįŗæu trĆ”o","trį»n","trį»t","trį»u trįŗ”o","trį»ng","trį»i Äįŗ„t Ę”i","trį»« phi","tuy","tuy nhiĆŖn","tuy rįŗ±ng","tuy thįŗæ","tuy vįŗy","tuyį»t nhiĆŖn","tuįŗ§n tį»±","tuį»t luį»t","tuį»t tuį»n tuį»t","tuį»t tuį»t","tĆ tĆ ","tĆŖnh","tĆt mĆ¹","tĆ² te","tĆ“i","tĆ“ng tį»c","tĆ¹ tƬ","tÄm tįŗÆp","tįŗ”i","tįŗ”i vƬ","tįŗ„m","tįŗ„n","tįŗ„t cįŗ£","tįŗ„t thįŗ£y","tįŗ„t tįŗ§n tįŗt","tįŗ„t tįŗt","tįŗÆp","tįŗÆp lį»±","tį»t","tį» ra","tį» vįŗ»","tį»c tįŗ£","tį»i Ę°","tį»t","tį»","tį»i","tį»©c thƬ","tį»©c tį»c","tį»«","tį»«ng","tį»± vƬ","tį»±u trung","veo","veo veo","viį»c","vung thiĆŖn Äį»a","vung tĆ n tĆ”n","vung tĆ”n tĆ n","vĆ ","vĆ o","vĆ¢ng","vĆØo","vƬ","vƬ chĘ°ng","vƬ thįŗæ","vƬ vįŗy","vĆ bįŗ±ng","vĆ dĆ¹","vĆ phį»ng","vĆ thį»","vĆ“ hƬnh trung","vĆ“ kį»","vĆ“ luįŗn","vĆ“ vĆ n","vÄng tĆŖ","vįŗ”n nhįŗ„t","vįŗ£ chÄng","vįŗ£ lįŗ”i","vįŗ«n","vįŗy","vįŗy lĆ ","vįŗy thƬ","vį»","vį» tįŗ„t","vį»n dÄ©","vį»i","vį»i lįŗ”i","vį»","vį»„t","vį»«a","vį»«a mį»i","xa xįŗ£","xiįŗæt bao","xon xĆ³n","xoĆ nh xoįŗ”ch","xoĆ©t","xoįŗ³n","xoįŗ¹t","xuįŗ„t kƬ bįŗ„t Ć½","xuįŗ„t kį»³ bįŗ„t Ć½","xuį»","xuį»ng","xÄm xĆŗi","xÄm xÄm","xÄm xįŗÆm","xį»nh xį»ch","xį»p","Ć ","Ć Ę”i","Ć o","Ć”","Ć” Ć ","Ć”i","Ć”i chĆ ","Ć”i dĆ ","Ć”ng","Ć¢u lĆ ","Ć“ hay","Ć“ hĆ“","Ć“ kĆŖ","Ć“ kƬa","Ć“i chao","Ć“i thĆ“i","Ć“ng","Ćŗi","Ćŗi chĆ ","Ćŗi dĆ o","Ć½","Ć½ chį»«ng","Ć½ da","Äang","Äi","Äiį»u","ÄĆ nh Äįŗ”ch","ÄĆ”ng lĆ","ÄĆ”ng lĆ½","ÄĆ”ng lįŗ½","ÄĆ”nh ÄĆ¹ng","ÄĆ”o Äį»","ÄĆ¢y","ÄĆ£","ÄĆ³","ÄĘ°į»£c","Äįŗ”i loįŗ”i","Äįŗ”i nhĆ¢n","Äįŗ”i phĆ m","Äįŗ”i Äį»","Äįŗæn","Äįŗæn nį»i","Äį»u","Äį»","Ę”","Ę” hay","Ę” kƬa","Ę”i","Ę°","įŗ”","įŗ” Ę”i","įŗ„y","įŗ§u Ę”","įŗÆt","įŗÆt hįŗ³n","įŗÆt lĆ ","į»i dĆ o","į»i giį»i","į»i giį»i Ę”i","į»","į»ng","į»","į»","į»","į» trĆŖn","į»§a","į»© hį»±","į»© į»«","į»«","į»"],"yo":["a","an","bĆ”","bĆ","bįŗ¹Ģrįŗ¹Ģ","fĆŗn","fįŗ¹Ģ","gbogbo","inĆŗ","jĆ¹","jįŗ¹","jįŗ¹Ģ","kan","kƬ","kĆ","kĆ²","lĆ”ti","lĆØ","lį»","mi","mo","mĆ”a","mį»Ģ","ni","nĆ”Ć ","nĆ","nĆgbĆ ","nĆtorĆ","nĒ¹kan","o","padĆ ","pĆ©","pĆŗpį»Ģ","pįŗ¹ĢlĆŗ","rįŗ¹Ģ","sƬ","sĆ","sĆnĆŗ","sĢ£","ti","tĆ","wĆ ","wĆ”","wį»n","wį»Ģn","yƬĆ","Ć ti","Ć wį»n","Ć©","Ć","Ć²un","Ć³","Å","ÅlĆ”","į¹£e","į¹£Ć©","į¹£Ć¹gbį»Ģn","įŗ¹mį»Ģ","į»jį»Ģ","į»Ģpį»Ģlį»pį»Ģ"],"zu":["futhi","kahle","kakhulu","kanye","khona","kodwa","kungani","kusho","la","lakhe","lapho","mina","ngesikhathi","nje","phansi","phezulu","u","ukuba","ukuthi","ukuze","uma","wahamba","wakhe","wami","wase","wathi","yakhe","zakhe","zonke"]}
diff --git a/contentcuration/kolibri_public/import_metadata_view.py b/contentcuration/kolibri_public/import_metadata_view.py
new file mode 100644
index 0000000000..534596cbb4
--- /dev/null
+++ b/contentcuration/kolibri_public/import_metadata_view.py
@@ -0,0 +1,174 @@
+"""
+This is a copy with modifications of the file:
+https://github.com/learningequality/kolibri/blob/c7417e1d558a1e1e52ac8423927d61a0e44da576/kolibri/core/content/public_api.py
+"""
+from uuid import UUID
+
+from django.db import connection
+from django.db.models import Q
+from django.http import HttpResponseBadRequest
+from django.utils.decorators import method_decorator
+from kolibri_content import base_models
+from kolibri_content import models as kolibri_content_models
+from kolibri_content.constants.schema_versions import CONTENT_SCHEMA_VERSION # Use kolibri_content
+from kolibri_content.constants.schema_versions import MIN_CONTENT_SCHEMA_VERSION # Use kolibri_content
+from kolibri_public import models # Use kolibri_public models
+from kolibri_public.views import metadata_cache
+from rest_framework.generics import get_object_or_404
+from rest_framework.permissions import AllowAny
+from rest_framework.response import Response
+from rest_framework.viewsets import GenericViewSet
+
+
+def _get_kc_and_base_models(model):
+ try:
+ kc_model = getattr(kolibri_content_models, model.__name__)
+ base_model = getattr(base_models, model.__name__)
+ except AttributeError:
+ # This will happen if it's a M2M through model, which only exist on ContentNode
+ through_model_name = model.__name__.replace("ContentNode_", "")
+ kc_model = getattr(kolibri_content_models.ContentNode, through_model_name).through
+ # Through models are not defined for the abstract base models, so we just cheat and
+ # use these instead.
+ base_model = kc_model
+ return kc_model, base_model
+
+
+# Add the standard metadata_cache decorator to this endpoint to align
+# with other public endpoints
+@method_decorator(metadata_cache, name="dispatch")
+class ImportMetadataViewset(GenericViewSet):
+ # Add an explicit allow any permission class to override the Studio default
+ permission_classes = (AllowAny,)
+ default_content_schema = CONTENT_SCHEMA_VERSION
+ min_content_schema = MIN_CONTENT_SCHEMA_VERSION
+
+ def _error_message(self, low):
+ error = "Schema version is too "
+ if low:
+ error += "low"
+ else:
+ error += "high"
+ if self.default_content_schema == self.min_content_schema:
+ error += ", exports only suported for version {}".format(
+ self.default_content_schema
+ )
+ else:
+ error += ", exports only suported for versions {} to {}".format(
+ self.min_content_schema, self.default_content_schema
+ )
+ return error
+
+ def retrieve(self, request, pk=None): # noqa: C901
+ """
+ An endpoint to retrieve all content metadata required for importing a content node
+ all of its ancestors, and any relevant needed metadata.
+
+ :param request: request object
+ :param pk: id parent node
+ :return: an object with keys for each content metadata table and a schema_version key
+ """
+
+ content_schema = request.query_params.get(
+ "schema_version", self.default_content_schema
+ )
+
+ try:
+ if int(content_schema) > int(self.default_content_schema):
+ return HttpResponseBadRequest(self._error_message(False))
+ if int(content_schema) < int(self.min_content_schema):
+ return HttpResponseBadRequest(self._error_message(True))
+ # Remove reference to SQLAlchemy schema bases
+ except ValueError:
+ return HttpResponseBadRequest(
+ "Schema version is not parseable by this version of Kolibri"
+ )
+ except AttributeError:
+ return HttpResponseBadRequest(
+ "Schema version is not known by this version of Kolibri"
+ )
+
+ # Get the model for the target node here - we do this so that we trigger a 404 immediately if the node
+ # does not exist.
+ node = get_object_or_404(models.ContentNode.objects.all(), pk=pk)
+
+ nodes = node.get_ancestors(include_self=True)
+
+ data = {}
+
+ files = models.File.objects.filter(contentnode__in=nodes)
+ through_tags = models.ContentNode.tags.through.objects.filter(
+ contentnode__in=nodes
+ )
+ assessmentmetadata = models.AssessmentMetaData.objects.filter(
+ contentnode__in=nodes
+ )
+ localfiles = models.LocalFile.objects.filter(files__in=files).distinct()
+ tags = models.ContentTag.objects.filter(
+ id__in=through_tags.values_list("contenttag_id", flat=True)
+ ).distinct()
+ languages = models.Language.objects.filter(
+ Q(id__in=files.values_list("lang_id", flat=True))
+ | Q(id__in=nodes.values_list("lang_id", flat=True))
+ )
+ node_ids = nodes.values_list("id", flat=True)
+ prerequisites = models.ContentNode.has_prerequisite.through.objects.filter(
+ from_contentnode_id__in=node_ids, to_contentnode_id__in=node_ids
+ )
+ related = models.ContentNode.related.through.objects.filter(
+ from_contentnode_id__in=node_ids, to_contentnode_id__in=node_ids
+ )
+ channel_metadata = models.ChannelMetadata.objects.filter(id=node.channel_id)
+
+ cursor = connection.cursor()
+
+ for qs in [
+ nodes,
+ files,
+ through_tags,
+ assessmentmetadata,
+ localfiles,
+ tags,
+ languages,
+ prerequisites,
+ related,
+ channel_metadata,
+ ]:
+ # First get the kolibri_content model and base model to which this is equivalent
+ kc_model, base_model = _get_kc_and_base_models(qs.model)
+ # Map the table name from the kolibri_public table name to the equivalent Kolibri table name
+ table_name = kc_model._meta.db_table
+ # Tweak our introspection here to rely on Django model meta instead of SQLAlchemy
+ # Read valid field names from the combination of the base model, and the mptt tree fields
+ # of the kc_model - because the base model is abstract, it does not get the mptt fields applied
+ # to its meta fields attribute, so we need to read the actual fields from the kc_model, but filter
+ # them only to names valid for the base model.
+ field_names = {field.column for field in base_model._meta.fields}
+ if hasattr(base_model, "_mptt_meta"):
+ field_names.add(base_model._mptt_meta.parent_attr)
+ field_names.add(base_model._mptt_meta.tree_id_attr)
+ field_names.add(base_model._mptt_meta.left_attr)
+ field_names.add(base_model._mptt_meta.right_attr)
+ field_names.add(base_model._mptt_meta.level_attr)
+ raw_fields = [field.column for field in kc_model._meta.fields if field.column in field_names]
+ if qs.model is models.Language:
+ raw_fields = [rf for rf in raw_fields if rf != "lang_name"] + ["native_name"]
+ qs = qs.values(*raw_fields)
+ # Avoid using the Django queryset directly, as it will coerce the database values
+ # via its field 'from_db_value' transformers, whereas import metadata is read
+ # directly from the database.
+ # One example is for JSON field data that is stored as a string in the database,
+ # we want to avoid that being coerced to Python objects.
+ cursor.execute(*qs.query.sql_with_params())
+ data[table_name] = [
+ # Coerce any UUIDs to their hex representation, as Postgres raw values will be UUIDs
+ dict(zip(raw_fields, (value.hex if isinstance(value, UUID) else value for value in row))) for row in cursor
+ ]
+ if qs.model is models.Language:
+ for lang in data[table_name]:
+ lang["lang_name"] = lang["native_name"]
+ del lang["native_name"]
+
+ data["schema_version"] = content_schema
+
+ return Response(data)
diff --git a/contentcuration/kolibri_public/management/__init__.py b/contentcuration/kolibri_public/management/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contentcuration/kolibri_public/management/commands/__init__.py b/contentcuration/kolibri_public/management/commands/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contentcuration/kolibri_public/management/commands/export_channels_to_kolibri_public.py b/contentcuration/kolibri_public/management/commands/export_channels_to_kolibri_public.py
new file mode 100644
index 0000000000..076053ad90
--- /dev/null
+++ b/contentcuration/kolibri_public/management/commands/export_channels_to_kolibri_public.py
@@ -0,0 +1,99 @@
+import logging
+import os
+import shutil
+import tempfile
+from datetime import datetime
+from datetime import timedelta
+
+from django.conf import settings
+from django.core.files.storage import default_storage as storage
+from django.core.management import call_command
+from django.core.management.base import BaseCommand
+from django.db.models import F
+from django.db.models import Q
+from django.utils import timezone
+from kolibri_content.apps import KolibriContentConfig
+from kolibri_content.models import ChannelMetadata as ExportedChannelMetadata
+from kolibri_content.router import get_active_content_database
+from kolibri_content.router import using_content_database
+from kolibri_public.models import ChannelMetadata
+from kolibri_public.utils.mapper import ChannelMapper
+
+from contentcuration.models import Channel
+from contentcuration.models import User
+from contentcuration.utils.publish import create_content_database
+
+logger = logging.getLogger(__file__)
+
+
+class Command(BaseCommand):
+ def add_arguments(self, parser):
+ parser.add_argument(
+ "--channel-id",
+ type=str,
+ dest="channel_id",
+ help="The channel_id for which generate kolibri_public models [default: all channels]"
+ )
+
+ def handle(self, *args, **options):
+ ids_to_export = []
+
+ if options["channel_id"]:
+ ids_to_export.append(options["channel_id"])
+ else:
+ self._republish_problem_channels()
+ public_channel_ids = set(Channel.objects.filter(public=True, deleted=False, main_tree__published=True).values_list("id", flat=True))
+ kolibri_public_channel_ids = set(ChannelMetadata.objects.all().values_list("id", flat=True))
+ ids_to_export = public_channel_ids.difference(kolibri_public_channel_ids)
+
+ count = 0
+ for channel_id in ids_to_export:
+ try:
+ self._export_channel(channel_id)
+ count += 1
+ except FileNotFoundError:
+ logger.warning("Tried to export channel {} to kolibri_public but its published channel database could not be found".format(channel_id))
+ except Exception as e:
+ logger.exception("Failed to export channel {} to kolibri_public because of error: {}".format(channel_id, e))
+ logger.info("Successfully put {} channels into kolibri_public".format(count))
+
+ def _export_channel(self, channel_id):
+ logger.info("Putting channel {} into kolibri_public".format(channel_id))
+ db_location = os.path.join(settings.DB_ROOT, "{id}.sqlite3".format(id=channel_id))
+ with storage.open(db_location) as storage_file:
+ with tempfile.NamedTemporaryFile(suffix=".sqlite3") as db_file:
+ shutil.copyfileobj(storage_file, db_file)
+ db_file.seek(0)
+ with using_content_database(db_file.name):
+ # Run migration to handle old content databases published prior to current fields being added.
+ call_command("migrate", app_label=KolibriContentConfig.label, database=get_active_content_database())
+ channel = ExportedChannelMetadata.objects.get(id=channel_id)
+ logger.info("Found channel {} for id: {} mapping now".format(channel.name, channel_id))
+ mapper = ChannelMapper(channel)
+ mapper.run()
+
+ def _republish_problem_channels(self):
+ twenty_19 = datetime(year=2019, month=1, day=1)
+ five_minutes = timedelta(minutes=5)
+ try:
+ chef_user = User.objects.get(email="chef@learningequality.org")
+ except User.DoesNotExist:
+ logger.error("Could not find chef user to republish channels")
+ return
+ channel_qs = Channel.objects.filter(
+ public=True,
+ deleted=False,
+ main_tree__published=True
+ ).filter(
+ Q(last_published__isnull=True) |
+ Q(last_published__lt=twenty_19, main_tree__modified__lte=F("last_published") + five_minutes)
+ )
+
+ for channel in channel_qs:
+ try:
+ kolibri_temp_db = create_content_database(channel, True, chef_user.id, False)
+ os.remove(kolibri_temp_db)
+ channel.last_published = timezone.now()
+ channel.save()
+ except Exception as e:
+ logger.exception("Failed to export channel {} to kolibri_public because of error: {}".format(channel.id, e))
diff --git a/contentcuration/kolibri_public/migrations/0001_initial.py b/contentcuration/kolibri_public/migrations/0001_initial.py
new file mode 100644
index 0000000000..f2186f6236
--- /dev/null
+++ b/contentcuration/kolibri_public/migrations/0001_initial.py
@@ -0,0 +1,361 @@
+# Generated by Django 3.2.14 on 2023-02-08 20:44
+import django.db.models.deletion
+import kolibri_content.fields
+import mptt.fields
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ("contentcuration", "0141_add_task_signature"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="ContentNode",
+ fields=[
+ (
+ "id",
+ kolibri_content.fields.UUIDField(primary_key=True, serialize=False),
+ ),
+ (
+ "license_name",
+ models.CharField(blank=True, max_length=50, null=True),
+ ),
+ ("license_description", models.TextField(blank=True, null=True)),
+ ("title", models.CharField(max_length=200)),
+ ("coach_content", models.BooleanField(default=False)),
+ ("content_id", kolibri_content.fields.UUIDField(db_index=True)),
+ ("channel_id", kolibri_content.fields.UUIDField(db_index=True)),
+ ("description", models.TextField(blank=True, null=True)),
+ ("sort_order", models.FloatField(blank=True, null=True)),
+ ("license_owner", models.CharField(blank=True, max_length=200)),
+ ("author", models.CharField(blank=True, max_length=200)),
+ (
+ "kind",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("topic", "Topic"),
+ ("video", "Video"),
+ ("audio", "Audio"),
+ ("exercise", "Exercise"),
+ ("document", "Document"),
+ ("html5", "HTML5 App"),
+ ("slideshow", "Slideshow"),
+ ("h5p", "H5P"),
+ ("zim", "Zim"),
+ ("quiz", "Quiz"),
+ ],
+ max_length=200,
+ ),
+ ),
+ ("available", models.BooleanField(default=False)),
+ (
+ "options",
+ kolibri_content.fields.JSONField(blank=True, default={}, null=True),
+ ),
+ ("grade_levels", models.TextField(blank=True, null=True)),
+ ("resource_types", models.TextField(blank=True, null=True)),
+ ("learning_activities", models.TextField(blank=True, null=True)),
+ ("accessibility_labels", models.TextField(blank=True, null=True)),
+ ("categories", models.TextField(blank=True, null=True)),
+ ("learner_needs", models.TextField(blank=True, null=True)),
+ ("duration", models.PositiveIntegerField(blank=True, null=True)),
+ (
+ "num_coach_contents",
+ models.IntegerField(blank=True, default=0, null=True),
+ ),
+ (
+ "on_device_resources",
+ models.IntegerField(blank=True, default=0, null=True),
+ ),
+ (
+ "ancestors",
+ kolibri_content.fields.JSONField(
+ blank=True, default=[], load_kwargs={"strict": False}, null=True
+ ),
+ ),
+ ("lft", models.PositiveIntegerField(editable=False)),
+ ("rght", models.PositiveIntegerField(editable=False)),
+ ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)),
+ ("level", models.PositiveIntegerField(editable=False)),
+ (
+ "learning_activities_bitmask_0",
+ models.BigIntegerField(blank=True, default=0, null=True),
+ ),
+ (
+ "categories_bitmask_0",
+ models.BigIntegerField(blank=True, default=0, null=True),
+ ),
+ (
+ "grade_levels_bitmask_0",
+ models.BigIntegerField(blank=True, default=0, null=True),
+ ),
+ (
+ "accessibility_labels_bitmask_0",
+ models.BigIntegerField(blank=True, default=0, null=True),
+ ),
+ (
+ "learner_needs_bitmask_0",
+ models.BigIntegerField(blank=True, default=0, null=True),
+ ),
+ (
+ "has_prerequisite",
+ models.ManyToManyField(
+ blank=True,
+ related_name="prerequisite_for",
+ to="kolibri_public.ContentNode",
+ ),
+ ),
+ (
+ "lang",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to="contentcuration.language",
+ ),
+ ),
+ (
+ "parent",
+ mptt.fields.TreeForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="children",
+ to="kolibri_public.contentnode",
+ ),
+ ),
+ (
+ "related",
+ models.ManyToManyField(
+ blank=True,
+ related_name="_kolibri_public_contentnode_related_+",
+ to="kolibri_public.ContentNode",
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.CreateModel(
+ name="ContentTag",
+ fields=[
+ (
+ "id",
+ kolibri_content.fields.UUIDField(primary_key=True, serialize=False),
+ ),
+ ("tag_name", models.CharField(blank=True, max_length=30)),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.CreateModel(
+ name="LocalFile",
+ fields=[
+ (
+ "id",
+ models.CharField(max_length=32, primary_key=True, serialize=False),
+ ),
+ (
+ "extension",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("mp4", "MP4 Video"),
+ ("webm", "WEBM Video"),
+ ("vtt", "VTT Subtitle"),
+ ("mp3", "MP3 Audio"),
+ ("pdf", "PDF Document"),
+ ("jpg", "JPG Image"),
+ ("jpeg", "JPEG Image"),
+ ("png", "PNG Image"),
+ ("gif", "GIF Image"),
+ ("json", "JSON"),
+ ("svg", "SVG Image"),
+ ("perseus", "Perseus Exercise"),
+ ("graphie", "Graphie Exercise"),
+ ("zip", "HTML5 Zip"),
+ ("h5p", "H5P"),
+ ("zim", "ZIM"),
+ ("epub", "ePub Document"),
+ ],
+ max_length=40,
+ ),
+ ),
+ ("available", models.BooleanField(default=False)),
+ ("file_size", models.IntegerField(blank=True, null=True)),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.CreateModel(
+ name="File",
+ fields=[
+ (
+ "id",
+ kolibri_content.fields.UUIDField(primary_key=True, serialize=False),
+ ),
+ (
+ "preset",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("high_res_video", "High Resolution"),
+ ("low_res_video", "Low Resolution"),
+ ("video_thumbnail", "Thumbnail"),
+ ("video_subtitle", "Subtitle"),
+ ("video_dependency", "Video (dependency)"),
+ ("audio", "Audio"),
+ ("audio_thumbnail", "Thumbnail"),
+ ("audio_dependency", "audio (dependency)"),
+ ("document", "Document"),
+ ("epub", "ePub Document"),
+ ("document_thumbnail", "Thumbnail"),
+ ("exercise", "Exercise"),
+ ("exercise_thumbnail", "Thumbnail"),
+ ("exercise_image", "Exercise Image"),
+ ("exercise_graphie", "Exercise Graphie"),
+ ("channel_thumbnail", "Channel Thumbnail"),
+ ("topic_thumbnail", "Thumbnail"),
+ ("html5_zip", "HTML5 Zip"),
+ ("html5_dependency", "HTML5 Dependency (Zip format)"),
+ ("html5_thumbnail", "HTML5 Thumbnail"),
+ ("h5p", "H5P Zip"),
+ ("h5p_thumbnail", "H5P Thumbnail"),
+ ("zim", "Zim"),
+ ("zim_thumbnail", "Zim Thumbnail"),
+ ("qti", "QTI Zip"),
+ ("qti_thumbnail", "QTI Thumbnail"),
+ ("slideshow_image", "Slideshow Image"),
+ ("slideshow_thumbnail", "Slideshow Thumbnail"),
+ ("slideshow_manifest", "Slideshow Manifest"),
+ ],
+ max_length=150,
+ ),
+ ),
+ ("supplementary", models.BooleanField(default=False)),
+ ("thumbnail", models.BooleanField(default=False)),
+ ("priority", models.IntegerField(blank=True, db_index=True, null=True)),
+ (
+ "contentnode",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="files",
+ to="kolibri_public.contentnode",
+ ),
+ ),
+ (
+ "lang",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to="contentcuration.language",
+ ),
+ ),
+ (
+ "local_file",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="files",
+ to="kolibri_public.localfile",
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.AddField(
+ model_name="contentnode",
+ name="tags",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="tagged_content",
+ to="kolibri_public.ContentTag",
+ ),
+ ),
+ migrations.CreateModel(
+ name="ChannelMetadata",
+ fields=[
+ (
+ "id",
+ kolibri_content.fields.UUIDField(primary_key=True, serialize=False),
+ ),
+ ("name", models.CharField(max_length=200)),
+ ("description", models.CharField(blank=True, max_length=400)),
+ ("tagline", models.CharField(blank=True, max_length=150, null=True)),
+ ("author", models.CharField(blank=True, max_length=400)),
+ ("version", models.IntegerField(default=0)),
+ ("thumbnail", models.TextField(blank=True)),
+ ("last_updated", kolibri_content.fields.DateTimeTzField(null=True)),
+ ("min_schema_version", models.CharField(max_length=50)),
+ (
+ "published_size",
+ models.BigIntegerField(blank=True, default=0, null=True),
+ ),
+ (
+ "total_resource_count",
+ models.IntegerField(blank=True, default=0, null=True),
+ ),
+ (
+ "order",
+ models.PositiveIntegerField(blank=True, default=0, null=True),
+ ),
+ ("public", models.BooleanField()),
+ (
+ "included_languages",
+ models.ManyToManyField(
+ blank=True,
+ related_name="public_channels",
+ to="contentcuration.Language",
+ verbose_name="languages",
+ ),
+ ),
+ (
+ "root",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="kolibri_public.contentnode",
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.CreateModel(
+ name="AssessmentMetaData",
+ fields=[
+ (
+ "id",
+ kolibri_content.fields.UUIDField(primary_key=True, serialize=False),
+ ),
+ ("assessment_item_ids", kolibri_content.fields.JSONField(default=[])),
+ ("number_of_assessments", models.IntegerField()),
+ ("mastery_model", kolibri_content.fields.JSONField(default={})),
+ ("randomize", models.BooleanField(default=False)),
+ ("is_manipulable", models.BooleanField(default=False)),
+ (
+ "contentnode",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="assessmentmetadata",
+ to="kolibri_public.contentnode",
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ ]
diff --git a/contentcuration/kolibri_public/migrations/0002_mptttreeidmanager.py b/contentcuration/kolibri_public/migrations/0002_mptttreeidmanager.py
new file mode 100644
index 0000000000..fb216e0557
--- /dev/null
+++ b/contentcuration/kolibri_public/migrations/0002_mptttreeidmanager.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.14 on 2023-03-14 01:07
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('kolibri_public', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='MPTTTreeIDManager',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ],
+ ),
+ ]
diff --git a/contentcuration/kolibri_public/migrations/0003_alter_file_preset.py b/contentcuration/kolibri_public/migrations/0003_alter_file_preset.py
new file mode 100644
index 0000000000..d03263eda2
--- /dev/null
+++ b/contentcuration/kolibri_public/migrations/0003_alter_file_preset.py
@@ -0,0 +1,53 @@
+# Generated by Django 3.2.24 on 2024-03-22 23:30
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("kolibri_public", "0002_mptttreeidmanager"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="file",
+ name="preset",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("high_res_video", "High Resolution"),
+ ("low_res_video", "Low Resolution"),
+ ("video_thumbnail", "Thumbnail"),
+ ("video_subtitle", "Subtitle"),
+ ("video_dependency", "Video (dependency)"),
+ ("audio", "Audio"),
+ ("audio_thumbnail", "Thumbnail"),
+ ("audio_dependency", "audio (dependency)"),
+ ("document", "Document"),
+ ("epub", "ePub Document"),
+ ("document_thumbnail", "Thumbnail"),
+ ("exercise", "Exercise"),
+ ("exercise_thumbnail", "Thumbnail"),
+ ("exercise_image", "Exercise Image"),
+ ("exercise_graphie", "Exercise Graphie"),
+ ("channel_thumbnail", "Channel Thumbnail"),
+ ("topic_thumbnail", "Thumbnail"),
+ ("html5_zip", "HTML5 Zip"),
+ ("html5_dependency", "HTML5 Dependency (Zip format)"),
+ ("html5_thumbnail", "HTML5 Thumbnail"),
+ ("h5p", "H5P Zip"),
+ ("h5p_thumbnail", "H5P Thumbnail"),
+ ("zim", "Zim"),
+ ("zim_thumbnail", "Zim Thumbnail"),
+ ("qti", "QTI Zip"),
+ ("qti_thumbnail", "QTI Thumbnail"),
+ ("slideshow_image", "Slideshow Image"),
+ ("slideshow_thumbnail", "Slideshow Thumbnail"),
+ ("slideshow_manifest", "Slideshow Manifest"),
+ ("imscp_zip", "IMSCP Zip"),
+ ],
+ max_length=150,
+ ),
+ ),
+ ]
diff --git a/contentcuration/kolibri_public/migrations/__init__.py b/contentcuration/kolibri_public/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contentcuration/kolibri_public/models.py b/contentcuration/kolibri_public/models.py
new file mode 100644
index 0000000000..c0056d9cf9
--- /dev/null
+++ b/contentcuration/kolibri_public/models.py
@@ -0,0 +1,122 @@
+from django.db import models
+from django.db.models import F
+from kolibri_content import base_models
+from kolibri_content.fields import JSONField
+from kolibri_public.search import bitmask_fieldnames
+from kolibri_public.search import metadata_bitmasks
+from mptt.managers import TreeManager
+from mptt.querysets import TreeQuerySet
+
+from contentcuration.models import Language
+
+
+class ContentTag(base_models.ContentTag):
+ pass
+
+
+class ContentNodeQueryset(TreeQuerySet):
+ def has_all_labels(self, field_name, labels):
+ bitmasks = metadata_bitmasks[field_name]
+ bits = {}
+ for label in labels:
+ if label in bitmasks:
+ bitmask_fieldname = bitmasks[label]["bitmask_field_name"]
+ if bitmask_fieldname not in bits:
+ bits[bitmask_fieldname] = 0
+ bits[bitmask_fieldname] += bitmasks[label]["bits"]
+
+ filters = {}
+ annotations = {}
+ for bitmask_fieldname, bits in bits.items():
+ annotation_fieldname = "{}_{}".format(bitmask_fieldname, "masked")
+ # To get the correct result, i.e. an AND that all the labels are present,
+ # we need to check that the aggregated value is euqal to the bits.
+ # If we wanted an OR (which would check for any being present),
+ # we would have to use GREATER THAN 0 here.
+ filters[annotation_fieldname] = bits
+ # This ensures that the annotated value is the result of the AND operation
+ # so if all the values are present, the result will be the same as the bits
+ # but if any are missing, it will not be equal to the bits, but will only be
+ # 0 if none of the bits are present.
+ annotations[annotation_fieldname] = F(bitmask_fieldname).bitand(bits)
+
+ return self.annotate(**annotations).filter(**filters)
+
+
+class ContentNodeManager(
+ models.Manager.from_queryset(ContentNodeQueryset), TreeManager
+):
+ def get_queryset(self, *args, **kwargs):
+ """
+ Ensures that this manager always returns nodes in tree order.
+ """
+ return (
+ super(TreeManager, self)
+ .get_queryset(*args, **kwargs)
+ .order_by(self.tree_id_attr, self.left_attr)
+ )
+
+
+class ContentNode(base_models.ContentNode):
+ lang = models.ForeignKey(Language, blank=True, null=True, on_delete=models.SET_NULL)
+
+ # Fields used only on Kolibri and not imported from a content database
+ # Total number of coach only resources for this node
+ num_coach_contents = models.IntegerField(default=0, null=True, blank=True)
+ # Total number of available resources on the device under this topic - if this is not a topic
+ # then it is 1
+ on_device_resources = models.IntegerField(default=0, null=True, blank=True)
+
+ # Use this to annotate ancestor information directly onto the ContentNode, as it can be a
+ # costly lookup
+ # Don't use strict loading as the titles used to construct the ancestors can contain
+ # control characters, which will fail strict loading.
+ ancestors = JSONField(
+ default=[], null=True, blank=True, load_kwargs={"strict": False}
+ )
+
+ objects = ContentNodeManager()
+
+
+for field_name in bitmask_fieldnames:
+ field = models.BigIntegerField(default=0, null=True, blank=True)
+ field.contribute_to_class(ContentNode, field_name)
+
+
+class File(base_models.File):
+ lang = models.ForeignKey(Language, blank=True, null=True, on_delete=models.SET_NULL)
+
+
+class LocalFile(base_models.LocalFile):
+ pass
+
+
+class AssessmentMetaData(base_models.AssessmentMetaData):
+ pass
+
+
+class ChannelMetadata(base_models.ChannelMetadata):
+ # precalculated fields during annotation/migration
+ published_size = models.BigIntegerField(default=0, null=True, blank=True)
+ total_resource_count = models.IntegerField(default=0, null=True, blank=True)
+ included_languages = models.ManyToManyField(
+ Language, related_name="public_channels", verbose_name="languages", blank=True
+ )
+ order = models.PositiveIntegerField(default=0, null=True, blank=True)
+ public = models.BooleanField()
+
+
+class MPTTTreeIDManager(models.Model):
+ """
+ This is added as insurance against concurrency issues. It seems far less likely than for our
+ regular channel tree_ids, but is here as a safety.
+
+ Because MPTT uses plain integers for tree IDs and does not use an auto-incrementing field for them,
+ the same ID can sometimes be assigned to two trees if two channel create ops happen concurrently.
+
+ As we are using this table only for the ID generation, it does not need any fields.
+
+ We resolve this by creating a dummy table and using its ID as the tree index to take advantage of the db's
+ concurrency-friendly way of generating sequential integer IDs. There is a custom migration that ensures
+ that the number of records (and thus id) matches the max tree ID number when this table gets added.
+ """
diff --git a/contentcuration/kolibri_public/search.py b/contentcuration/kolibri_public/search.py
new file mode 100644
index 0000000000..6f1e3648ff
--- /dev/null
+++ b/contentcuration/kolibri_public/search.py
@@ -0,0 +1,142 @@
+"""
+Avoiding direct model imports in here so that we can import these functions into places
+that should not initiate the Django app registry.
+
+Modified from:
+https://github.com/learningequality/kolibri/blob/0f6bb6781a4453cd9fdc836d52b65dd69e395b20/kolibri/core/content/utils/search.py
+
+With model import references changed to either contentcuration or kolibri_public as appropriate
+and any non-Postgres logic removed in favour of Postgres only.
+"""
+import hashlib
+
+from django.contrib.postgres.aggregates import BitOr
+from django.core.cache import cache
+from django.db.models import Case
+from django.db.models import F
+from django.db.models import Max
+from django.db.models import Value
+from django.db.models import When
+from le_utils.constants.labels.accessibility_categories import (
+ ACCESSIBILITYCATEGORIESLIST,
+)
+from le_utils.constants.labels.learning_activities import LEARNINGACTIVITIESLIST
+from le_utils.constants.labels.levels import LEVELSLIST
+from le_utils.constants.labels.needs import NEEDSLIST
+from le_utils.constants.labels.subjects import SUBJECTSLIST
+
+
+metadata_lookup = {
+ "learning_activities": LEARNINGACTIVITIESLIST,
+ "categories": SUBJECTSLIST,
+ "grade_levels": LEVELSLIST,
+ "accessibility_labels": ACCESSIBILITYCATEGORIESLIST,
+ "learner_needs": NEEDSLIST,
+}
+
+
+metadata_bitmasks = {}
+
+bitmask_fieldnames = {}
+
+
+for key, labels in metadata_lookup.items():
+ bitmask_lookup = {}
+ i = 0
+ while labels[i: i + 64]:
+ bitmask_field_name = "{}_bitmask_{}".format(key, i)
+ bitmask_fieldnames[bitmask_field_name] = []
+ for j, label in enumerate(labels):
+ info = {
+ "bitmask_field_name": bitmask_field_name,
+ "field_name": key,
+ "bits": 2**j,
+ "label": label,
+ }
+ bitmask_lookup[label] = info
+ bitmask_fieldnames[bitmask_field_name].append(info)
+ i += 64
+ metadata_bitmasks[key] = bitmask_lookup
+
+
+def _get_available_languages(base_queryset):
+ # Updated to use contentcuration Language model
+ from contentcuration.models import Language
+
+ langs = Language.objects.filter(
+ id__in=base_queryset.exclude(lang=None).values_list("lang_id", flat=True).distinct()
+ # Updated to use contentcuration field names
+ # Convert language objects to dicts mapped to the kolibri field names
+ ).values("id", lang_name=F("native_name"))
+ return list(langs)
+
+
+def _get_available_channels(base_queryset):
+ # Updated to use the kolibri_public ChannelMetadata model
+ from kolibri_public.models import ChannelMetadata
+
+ return list(
+ ChannelMetadata.objects.filter(
+ id__in=base_queryset.values_list("channel_id", flat=True).distinct()
+ ).values("id", "name")
+ )
+
+
+# Remove the SQLite Bitwise OR definition as not needed.
+
+
+def get_available_metadata_labels(base_queryset):
+ # Updated to use the kolibri_public ChannelMetadata model
+ from kolibri_public.models import ChannelMetadata
+
+ content_cache_key = str(
+ ChannelMetadata.objects.all().aggregate(updated=Max("last_updated"))["updated"]
+ )
+ cache_key = "search-labels:{}:{}".format(
+ content_cache_key,
+ hashlib.md5(str(base_queryset.query).encode("utf8")).hexdigest(),
+ )
+ if cache_key not in cache:
+ base_queryset = base_queryset.order_by()
+ aggregates = {}
+ for field in bitmask_fieldnames:
+ field_agg = field + "_agg"
+ aggregates[field_agg] = BitOr(field)
+ output = {}
+ agg = base_queryset.aggregate(**aggregates)
+ for field, values in bitmask_fieldnames.items():
+ bit_value = agg[field + "_agg"]
+ for value in values:
+ if value["field_name"] not in output:
+ output[value["field_name"]] = []
+ if bit_value is not None and bit_value & value["bits"]:
+ output[value["field_name"]].append(value["label"])
+ output["languages"] = _get_available_languages(base_queryset)
+ output["channels"] = _get_available_channels(base_queryset)
+ cache.set(cache_key, output, timeout=None)
+ return cache.get(cache_key)
+
+
+def get_all_contentnode_label_metadata():
+ # Updated to use the kolibri_public ContentNode model
+ from kolibri_public.models import ContentNode
+
+ return get_available_metadata_labels(ContentNode.objects.filter(available=True))
+
+
+def annotate_label_bitmasks(queryset):
+ update_statements = {}
+ for bitmask_fieldname, label_info in bitmask_fieldnames.items():
+ update_statements[bitmask_fieldname] = sum(
+ Case(
+ When(
+ **{
+ info["field_name"] + "__contains": info["label"],
+ "then": Value(info["bits"]),
+ }
+ ),
+ default=Value(0),
+ )
+ for info in label_info
+ )
+ queryset.update(**update_statements)
diff --git a/contentcuration/kolibri_public/stopwords.py b/contentcuration/kolibri_public/stopwords.py
new file mode 100644
index 0000000000..ce112936e7
--- /dev/null
+++ b/contentcuration/kolibri_public/stopwords.py
@@ -0,0 +1,17 @@
+import io
+import json
+import os
+
+# load stopwords file
+stopwords_path = os.path.abspath(
+ os.path.join(
+ os.path.dirname(__file__), "constants", "stopwords-all.json"
+ )
+)
+with io.open(stopwords_path, mode="r", encoding="utf-8") as f:
+ stopwords = json.load(f)
+
+# load into a set
+stopwords_set = set()
+for values in stopwords.values():
+ stopwords_set.update(values)
diff --git a/contentcuration/kolibri_public/tests/base.py b/contentcuration/kolibri_public/tests/base.py
new file mode 100644
index 0000000000..1e5f341ab2
--- /dev/null
+++ b/contentcuration/kolibri_public/tests/base.py
@@ -0,0 +1,362 @@
+import copy
+import random
+import uuid
+from itertools import chain
+
+from django.core.exceptions import FieldDoesNotExist
+from kolibri_public import models as kolibri_public_models
+from le_utils.constants import content_kinds
+from le_utils.constants import format_presets
+from le_utils.constants.labels.accessibility_categories import (
+ ACCESSIBILITYCATEGORIESLIST,
+)
+from le_utils.constants.labels.learning_activities import LEARNINGACTIVITIESLIST
+from le_utils.constants.labels.levels import LEVELSLIST
+from le_utils.constants.labels.needs import NEEDSLIST
+from le_utils.constants.labels.subjects import SUBJECTSLIST
+
+
+def to_dict(instance):
+ opts = instance._meta
+ data = {}
+ for f in chain(opts.concrete_fields, opts.private_fields):
+ data[f.name] = f.value_from_object(instance)
+ return data
+
+
+def uuid4_hex():
+ return uuid.uuid4().hex
+
+
+def choices(sequence, k):
+ return [random.choice(sequence) for _ in range(0, k)]
+
+
+OKAY_TAG = "okay_tag"
+BAD_TAG = "tag_is_too_long_because_it_is_over_30_characters"
+
+PROBLEMATIC_HTML5_NODE = "ab9d3fd905c848a6989936c609405abb"
+
+BUILDER_DEFAULT_OPTIONS = {
+ "problematic_tags": False,
+ "problematic_nodes": False,
+}
+
+
+class ChannelBuilder(object):
+ """
+ This class is purely to generate all the relevant data for a single
+ channel for use during testing.
+ """
+
+ __TREE_CACHE = {}
+
+ tree_keys = (
+ "channel",
+ "files",
+ "localfiles",
+ "node_to_files_map",
+ "localfile_to_files_map",
+ "root_node",
+ )
+
+ def __init__(self, levels=3, num_children=5, models=kolibri_public_models, options=None):
+ self.levels = levels
+ self.num_children = num_children
+ self.models = models
+ self.options = BUILDER_DEFAULT_OPTIONS.copy()
+ if options:
+ self.options.update(options)
+
+ self.content_tags = {}
+ self._excluded_channel_fields = None
+ self._excluded_node_fields = None
+
+ self.modified = set()
+
+ try:
+ self.load_data()
+ except KeyError:
+ self.generate_new_tree()
+ self.save_data()
+
+ self.generate_nodes_from_root_node()
+
+ @property
+ def cache_key(self):
+ return "{}_{}_{}".format(self.levels, self.num_children, self.models)
+
+ def generate_new_tree(self):
+ self.channel = self.channel_data()
+ self.files = {}
+ self.localfiles = {}
+
+ self.node_to_files_map = {}
+ self.localfile_to_files_map = {}
+ self.content_tag_map = {}
+
+ tags = [OKAY_TAG]
+ if self.options["problematic_tags"]:
+ tags.append(BAD_TAG)
+ for tag_name in tags:
+ self.content_tag_data(tag_name)
+
+ self.root_node = self.generate_topic()
+ if "root_id" in self.channel:
+ self.channel["root_id"] = self.root_node["id"]
+ if "root_pk" in self.channel:
+ self.channel["root_pk"] = self.root_node["id"]
+
+ if self.levels:
+ self.root_node["children"] = self.recurse_and_generate(
+ self.root_node["id"], self.levels
+ )
+ if self.options["problematic_nodes"]:
+ self.root_node["children"].extend(self.generate_problematic_nodes())
+
+ def generate_problematic_nodes(self):
+ nodes = []
+ html5_not_a_topic = self.contentnode_data(
+ node_id=PROBLEMATIC_HTML5_NODE,
+ kind=content_kinds.HTML5,
+ parent_id=self.root_node["id"],
+ )
+ # the problem: this node is not a topic, but it has children
+ html5_not_a_topic["children"] = [
+ self.contentnode_data(parent_id=PROBLEMATIC_HTML5_NODE)
+ ]
+ nodes.append(html5_not_a_topic)
+ return nodes
+
+ def load_data(self):
+ try:
+ data = copy.deepcopy(self.__TREE_CACHE[self.cache_key])
+
+ for key in self.tree_keys:
+ setattr(self, key, data[key])
+ except KeyError:
+ print(
+ "No tree cache found for {} levels and {} children per level".format(
+ self.levels, self.num_children
+ )
+ )
+ raise
+
+ def save_data(self):
+ data = {}
+
+ for key in self.tree_keys:
+ data[key] = getattr(self, key)
+
+ self.__TREE_CACHE[self.cache_key] = copy.deepcopy(data)
+
+ def generate_nodes_from_root_node(self):
+ self._django_nodes = self.models.ContentNode.objects.build_tree_nodes(self.root_node)
+
+ self.nodes = {n["id"]: n for n in map(to_dict, self._django_nodes)}
+
+ def insert_into_default_db(self):
+ self.models.ContentTag.objects.bulk_create(
+ (self.models.ContentTag(**tag) for tag in self.content_tags.values())
+ )
+ self.models.ContentNode.objects.bulk_create(self._django_nodes)
+ self.models.ContentNode.tags.through.objects.bulk_create(
+ (
+ self.models.ContentNode.tags.through(
+ contentnode_id=node["id"], contenttag_id=tag["id"]
+ )
+ for node in self.nodes.values()
+ for tag in self.content_tags.values()
+ )
+ )
+ self.models.ChannelMetadata.objects.create(**self.channel)
+ self.models.LocalFile.objects.bulk_create(
+ (self.models.LocalFile(**local) for local in self.localfiles.values())
+ )
+ self.models.File.objects.bulk_create((self.models.File(**f) for f in self.files.values()))
+
+ def recurse_tree_until_leaf_container(self, parent):
+ if not parent.get("children"):
+ parent["children"] = []
+ return parent
+ child = random.choice(parent["children"])
+ if child["kind"] != content_kinds.TOPIC:
+ return parent
+ return self.recurse_tree_until_leaf_container(child)
+
+ @property
+ def resources(self):
+ return filter(lambda x: x["kind"] != content_kinds.TOPIC, self.nodes.values())
+
+ def get_resource_localfiles(self, ids):
+ localfiles = {}
+ for r in ids:
+ for f in self.node_to_files_map.get(r, []):
+ file = self.files[f]
+ localfile = self.localfiles[file["local_file_id"]]
+ localfiles[localfile["id"]] = localfile
+ return list(localfiles.values())
+
+ @property
+ def data(self):
+ return {
+ "content_channel": [self.channel],
+ "content_contentnode": list(self.nodes.values()),
+ "content_file": list(self.files.values()),
+ "content_localfile": list(self.localfiles.values()),
+ "content_contenttag": list(self.content_tags.values()),
+ }
+
+ def recurse_and_generate(self, parent_id, levels):
+ children = []
+ for i in range(0, self.num_children):
+ if levels == 0:
+ node = self.generate_leaf(parent_id)
+ else:
+ node = self.generate_topic(parent_id=parent_id)
+ node["children"] = self.recurse_and_generate(node["id"], levels - 1)
+ children.append(node)
+ return children
+
+ def generate_topic(self, parent_id=None):
+ data = self.contentnode_data(
+ kind=content_kinds.TOPIC, root=parent_id is None, parent_id=parent_id
+ )
+ thumbnail = self.localfile_data(extension="png")
+ self.file_data(
+ data["id"],
+ thumbnail["id"],
+ thumbnail=True,
+ preset=format_presets.TOPIC_THUMBNAIL,
+ )
+ return data
+
+ def generate_leaf(self, parent_id):
+ node = self.contentnode_data(parent_id=parent_id, kind=content_kinds.VIDEO)
+ localfile = self.localfile_data()
+ thumbnail = self.localfile_data(extension="png")
+ self.file_data(node["id"], localfile["id"], preset=format_presets.VIDEO_LOW_RES)
+ self.file_data(
+ node["id"],
+ thumbnail["id"],
+ thumbnail=True,
+ preset=format_presets.VIDEO_THUMBNAIL,
+ )
+ for tag_id in self.content_tags:
+ self.content_tag_map[node["id"]] = [tag_id]
+ return node
+
+ def channel_data(self, channel_id=None, version=1):
+ channel_data = {
+ "root_id": None,
+ "root_pk": None,
+ "last_updated": None,
+ "version": 1,
+ "author": "Outis",
+ "description": "Test channel",
+ "tagline": None,
+ "min_schema_version": "1",
+ "thumbnail": "",
+ "name": "testing",
+ "public": True,
+ "id": channel_id or uuid4_hex(),
+ }
+ if self._excluded_channel_fields is None:
+ self._excluded_channel_fields = []
+ for key in channel_data:
+ try:
+ self.models.ChannelMetadata._meta.get_field(key)
+ except FieldDoesNotExist:
+ self._excluded_channel_fields.append(key)
+ for field in self._excluded_channel_fields:
+ del channel_data[field]
+ return channel_data
+
+ def content_tag_data(self, tag_name):
+ data = {
+ "id": uuid4_hex(),
+ "tag_name": tag_name,
+ }
+ self.content_tags[data["id"]] = data
+ return data
+
+ def localfile_data(self, extension="mp4"):
+ data = {
+ "file_size": random.randint(1, 1000),
+ "extension": extension,
+ "available": False,
+ "id": uuid4_hex(),
+ }
+
+ self.localfiles[data["id"]] = data
+
+ return data
+
+ def file_data(
+ self,
+ contentnode_id,
+ local_file_id,
+ thumbnail=False,
+ preset=None,
+ supplementary=False,
+ ):
+ data = {
+ "priority": None,
+ "supplementary": supplementary or thumbnail,
+ "id": uuid4_hex(),
+ "local_file_id": local_file_id or uuid4_hex(),
+ "contentnode_id": contentnode_id,
+ "thumbnail": thumbnail,
+ "preset": preset,
+ "lang_id": None,
+ }
+ self.files[data["id"]] = data
+ if contentnode_id not in self.node_to_files_map:
+ self.node_to_files_map[contentnode_id] = []
+ self.node_to_files_map[contentnode_id].append(data["id"])
+ if local_file_id not in self.localfile_to_files_map:
+ self.localfile_to_files_map[local_file_id] = []
+ self.localfile_to_files_map[local_file_id].append(data["id"])
+ return data
+
+ def contentnode_data(
+ self, node_id=None, content_id=None, parent_id=None, kind=None, root=False
+ ):
+ # First kind in choices is Topic, so exclude it here.
+ kind = kind or random.choice(content_kinds.choices[1:])[0]
+ contentnode_data = {
+ "options": "{}",
+ "content_id": content_id or uuid4_hex(),
+ "channel_id": self.channel["id"],
+ "description": "Blah blah blah",
+ "id": node_id or uuid4_hex(),
+ "license_name": "GNU",
+ "license_owner": "",
+ "license_description": None,
+ "lang_id": None,
+ "author": "",
+ "title": "Test",
+ "parent_id": None if root else parent_id or uuid4_hex(),
+ "kind": kind,
+ "coach_content": False,
+ "available": False,
+ "learning_activities": ",".join(
+ set(choices(LEARNINGACTIVITIESLIST, k=random.randint(1, 3)))
+ ),
+ "accessibility_labels": ",".join(
+ set(choices(ACCESSIBILITYCATEGORIESLIST, k=random.randint(1, 3)))
+ ),
+ "grade_levels": ",".join(set(choices(LEVELSLIST, k=random.randint(1, 2)))),
+ "categories": ",".join(set(choices(SUBJECTSLIST, k=random.randint(1, 10)))),
+ "learner_needs": ",".join(set(choices(NEEDSLIST, k=random.randint(1, 5)))),
+ }
+ if self._excluded_node_fields is None:
+ self._excluded_node_fields = []
+ for key in contentnode_data:
+ try:
+ self.models.ContentNode._meta.get_field(key)
+ except FieldDoesNotExist:
+ self._excluded_node_fields.append(key)
+ for field in self._excluded_node_fields:
+ del contentnode_data[field]
+ return contentnode_data
diff --git a/contentcuration/kolibri_public/tests/test_content_app.py b/contentcuration/kolibri_public/tests/test_content_app.py
new file mode 100644
index 0000000000..50c4a6a35b
--- /dev/null
+++ b/contentcuration/kolibri_public/tests/test_content_app.py
@@ -0,0 +1,502 @@
+"""
+To run this test, type this in command line
+"""
+import datetime
+import uuid
+from calendar import timegm
+
+from django.core.cache import cache
+from django.core.management import call_command
+from django.urls import reverse
+from django.utils.http import http_date
+from kolibri_public import models
+from kolibri_public.tests.base import ChannelBuilder
+from kolibri_public.tests.base import OKAY_TAG
+from le_utils.constants import content_kinds
+from rest_framework.test import APITestCase
+
+from contentcuration.models import generate_storage_url
+from contentcuration.models import Language
+
+
+kind_activity_map = {
+ content_kinds.EXERCISE: "practice",
+ content_kinds.VIDEO: "watch",
+ content_kinds.AUDIO: "listen",
+ content_kinds.DOCUMENT: "read",
+ content_kinds.HTML5: "explore",
+}
+
+
+def infer_learning_activity(kind):
+ activity = kind_activity_map.get(kind)
+ if activity:
+ return [activity]
+ return []
+
+
+class ContentNodeAPIBase(object):
+ @classmethod
+ def setUpTestData(cls):
+ call_command("loadconstants")
+ builder = ChannelBuilder()
+ builder.insert_into_default_db()
+ models.ContentNode.objects.all().update(available=True)
+ cls.root = models.ContentNode.objects.get(id=builder.root_node["id"])
+ cls.is_prereq = models.ContentNode.objects.exclude(
+ kind=content_kinds.TOPIC
+ ).first()
+ cls.has_prereq = models.ContentNode.objects.exclude(kind=content_kinds.TOPIC)[1]
+ cls.has_prereq.has_prerequisite.add(cls.is_prereq)
+ cls.channel_data = builder.channel
+
+ def _get(self, *args, **kwargs):
+ return self.client.get(*args, **kwargs)
+
+ def _assert_headers(self, response, last_updated):
+ self.assertEqual(response.headers["Vary"], "Accept")
+ self.assertEqual(
+ response.headers["Cache-Control"],
+ "max-age=300, public, stale-while-revalidate=100",
+ )
+ self.assertEqual(
+ response.headers["Last-Modified"],
+ http_date(timegm(last_updated.utctimetuple())),
+ )
+
+ def map_language(self, lang):
+ if lang:
+ return {
+ f: getattr(lang, f)
+ for f in [
+ "id",
+ "lang_code",
+ "lang_subcode",
+ "lang_name",
+ "lang_direction",
+ ]
+ }
+
+ def _assert_node(self, actual, expected):
+ assessmentmetadata = (
+ expected.assessmentmetadata.all()
+ .values(
+ "assessment_item_ids",
+ "number_of_assessments",
+ "mastery_model",
+ "randomize",
+ "is_manipulable",
+ "contentnode",
+ )
+ .first()
+ )
+ thumbnail = None
+ files = []
+ for f in expected.files.all():
+ "local_file__id",
+ "local_file__available",
+ "local_file__file_size",
+ "local_file__extension",
+ "lang_id",
+ file = {}
+ for field in [
+ "id",
+ "priority",
+ "preset",
+ "supplementary",
+ "thumbnail",
+ ]:
+ file[field] = getattr(f, field)
+ file["checksum"] = f.local_file_id
+ for field in [
+ "available",
+ "file_size",
+ "extension",
+ ]:
+ file[field] = getattr(f.local_file, field)
+ file["lang"] = self.map_language(f.lang)
+ file["storage_url"] = generate_storage_url(
+ "{}.{}".format(file["checksum"], file["extension"])
+ )
+ files.append(file)
+ if f.thumbnail:
+ thumbnail = generate_storage_url(
+ "{}.{}".format(file["checksum"], file["extension"])
+ )
+ self.assertEqual(
+ actual,
+ {
+ "id": expected.id,
+ "available": expected.available,
+ "author": expected.author,
+ "channel_id": expected.channel_id,
+ "coach_content": expected.coach_content,
+ "content_id": expected.content_id,
+ "description": expected.description,
+ "duration": expected.duration,
+ "learning_activities": expected.learning_activities.split(",")
+ if expected.learning_activities
+ else [],
+ "grade_levels": expected.grade_levels.split(",")
+ if expected.grade_levels
+ else [],
+ "resource_types": expected.resource_types.split(",")
+ if expected.resource_types
+ else [],
+ "accessibility_labels": expected.accessibility_labels.split(",")
+ if expected.accessibility_labels
+ else [],
+ "categories": expected.categories.split(",")
+ if expected.categories
+ else [],
+ "kind": expected.kind,
+ "lang": self.map_language(expected.lang),
+ "license_description": expected.license_description,
+ "license_name": expected.license_name,
+ "license_owner": expected.license_owner,
+ "num_coach_contents": expected.num_coach_contents,
+ "options": expected.options,
+ "parent": expected.parent_id,
+ "sort_order": expected.sort_order,
+ "title": expected.title,
+ "lft": expected.lft,
+ "rght": expected.rght,
+ "tree_id": expected.tree_id,
+ "ancestors": [],
+ "tags": list(
+ expected.tags.all()
+ .order_by("tag_name")
+ .values_list("tag_name", flat=True)
+ ),
+ "thumbnail": thumbnail,
+ "assessmentmetadata": assessmentmetadata,
+ "is_leaf": expected.kind != "topic",
+ "files": files,
+ },
+ )
+
+ def _assert_nodes(self, data, nodes):
+ for actual, expected in zip(
+ sorted(data, key=lambda x: x["id"]), sorted(nodes, key=lambda x: x.id)
+ ):
+ self._assert_node(actual, expected)
+
+ def test_contentnode_list(self):
+ nodes = self.root.get_descendants(include_self=True).filter(available=True)
+ expected_output = len(nodes)
+ response = self._get(reverse("publiccontentnode-list"))
+ self.assertEqual(len(response.data), expected_output)
+ self._assert_nodes(response.data, nodes)
+
+ def test_contentnode_list_labels(self):
+ nodes = self.root.get_descendants(include_self=True).filter(available=True)
+ response = self._get(reverse("publiccontentnode-list"), data={"max_results": 1})
+ node_languages = Language.objects.filter(contentnode__in=nodes)
+ self.assertEqual(len(response.data["labels"]["languages"]), node_languages.distinct().count())
+ for lang in response.data["labels"]["languages"]:
+ self.assertTrue(node_languages.filter(native_name=lang["lang_name"]).exists())
+
+ def test_contentnode_list_headers(self):
+ channel = models.ChannelMetadata.objects.get()
+ channel.last_updated = datetime.datetime.now()
+ channel.save()
+ response = self._get(reverse("publiccontentnode-list"))
+ self._assert_headers(response, channel.last_updated)
+
+ def _recurse_and_assert(self, data, nodes, recursion_depth=0):
+ recursion_depths = []
+ for actual, expected in zip(data, nodes):
+ children = actual.pop("children", None)
+ self._assert_node(actual, expected)
+ if children:
+ child_nodes = models.ContentNode.objects.filter(
+ available=True, parent=expected
+ )
+ if children["more"] is None:
+ self.assertEqual(len(child_nodes), len(children["results"]))
+ else:
+ self.assertGreater(len(child_nodes), len(children["results"]))
+ self.assertEqual(children["more"]["id"], expected.id)
+ self.assertEqual(
+ children["more"]["params"]["next__gt"], child_nodes[11].rght
+ )
+ self.assertEqual(
+ children["more"]["params"]["depth"], 2 - recursion_depth
+ )
+ if self.baseurl:
+ self.assertEqual(
+ children["more"]["params"]["baseurl"], self.baseurl
+ )
+ recursion_depths.append(
+ self._recurse_and_assert(
+ children["results"],
+ child_nodes,
+ recursion_depth=recursion_depth + 1,
+ )
+ )
+ return recursion_depth if not recursion_depths else max(recursion_depths)
+
+ def test_contentnode_tree(self):
+ response = self._get(
+ reverse("publiccontentnode_tree-detail", kwargs={"pk": self.root.id})
+ )
+ self._recurse_and_assert([response.data], [self.root])
+
+ def test_contentnode_tree_headers(self):
+ channel = models.ChannelMetadata.objects.get()
+ channel.last_updated = datetime.datetime.now()
+ channel.save()
+ response = self._get(
+ reverse("publiccontentnode_tree-detail", kwargs={"pk": self.root.id})
+ )
+ self._assert_headers(response, channel.last_updated)
+
+ def test_contentnode_tree_filtered_queryset_node(self):
+ response = self.client.get(
+ reverse("publiccontentnode_tree-detail", kwargs={"pk": self.root.id})
+ + "?parent={}".format(uuid.uuid4().hex)
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_contentnode_tree_depth_1(self):
+ response = self._get(
+ reverse("publiccontentnode_tree-detail", kwargs={"pk": self.root.id}),
+ data={"depth": 1},
+ )
+ self._recurse_and_assert([response.data], [self.root])
+
+ def test_contentnode_tree_singleton_path(self):
+ builder = ChannelBuilder(levels=5, num_children=1)
+ builder.insert_into_default_db()
+ models.ContentNode.objects.all().update(available=True)
+ root = models.ContentNode.objects.get(id=builder.root_node["id"])
+ response = self._get(
+ reverse("publiccontentnode_tree-detail", kwargs={"pk": root.id})
+ )
+ max_depth = self._recurse_and_assert([response.data], [root])
+ # Should recurse all the way down the tree through a total of 6 levels
+ # including the root.
+ self.assertEqual(max_depth, 6)
+
+ def test_contentnode_tree_singleton_child(self):
+ builder = ChannelBuilder(levels=5, num_children=2)
+ builder.insert_into_default_db()
+ models.ContentNode.objects.all().update(available=True)
+ root = models.ContentNode.objects.get(id=builder.root_node["id"])
+ first_child = root.children.first()
+ first_child.available = False
+ first_child.save()
+ response = self._get(
+ reverse("publiccontentnode_tree-detail", kwargs={"pk": root.id})
+ )
+ max_depth = self._recurse_and_assert([response.data], [root])
+ # Should recurse an extra level to find multiple descendants under the first grandchild.
+ self.assertEqual(max_depth, 3)
+
+ def test_contentnode_tree_singleton_grandchild(self):
+ builder = ChannelBuilder(levels=5, num_children=2)
+ builder.insert_into_default_db()
+ models.ContentNode.objects.all().update(available=True)
+ root = models.ContentNode.objects.get(id=builder.root_node["id"])
+ first_grandchild = root.children.first().children.first()
+ first_grandchild.available = False
+ first_grandchild.save()
+ response = self._get(
+ reverse("publiccontentnode_tree-detail", kwargs={"pk": root.id})
+ )
+ max_depth = self._recurse_and_assert([response.data], [root])
+ # Should recurse an extra level to find multiple descendants under the first child.
+ self.assertEqual(max_depth, 3)
+
+
+class ContentNodeAPITestCase(ContentNodeAPIBase, APITestCase):
+ """
+ Testcase for content API methods
+ """
+
+ def test_prerequisite_for_filter(self):
+ response = self.client.get(
+ reverse("publiccontentnode-list"),
+ data={"prerequisite_for": self.has_prereq.id},
+ )
+ self.assertEqual(response.data[0]["id"], self.is_prereq.id)
+
+ def test_has_prerequisite_filter(self):
+ response = self.client.get(
+ reverse("publiccontentnode-list"),
+ data={"has_prerequisite": self.is_prereq.id},
+ )
+ self.assertEqual(response.data[0]["id"], self.has_prereq.id)
+
+ def test_related_filter(self):
+ self.has_prereq.related.add(self.is_prereq)
+ response = self.client.get(
+ reverse("publiccontentnode-list"), data={"related": self.has_prereq.id}
+ )
+ self.assertEqual(response.data[0]["id"], self.is_prereq.id)
+
+ def test_contentnode_retrieve(self):
+ node = models.ContentNode.objects.all().first()
+ response = self.client.get(
+ reverse("publiccontentnode-detail", kwargs={"pk": node.id})
+ )
+ self.assertEqual(response.data["id"], node.id)
+
+ def test_contentnode_ids(self):
+ nodes = models.ContentNode.objects.all()[:2]
+ response = self.client.get(
+ reverse("publiccontentnode-list"),
+ data={"ids": ",".join([n.id for n in nodes])},
+ )
+ self.assertEqual(len(response.data), 2)
+ for i, node in enumerate(nodes):
+ self.assertEqual(response.data[i]["title"], node.title)
+
+ def test_contentnode_parent(self):
+ parent = models.ContentNode.objects.filter(kind=content_kinds.TOPIC).first()
+ children = parent.get_children()
+ response = self.client.get(
+ reverse("publiccontentnode-list"),
+ data={"parent": parent.id, "include_coach_content": False},
+ )
+ self.assertEqual(len(response.data), children.count())
+ for i in range(len(children)):
+ self.assertEqual(response.data[i]["title"], children[i].title)
+
+ def test_contentnode_tags(self):
+ tags = ["tag_1", "tag_2", "tag_3"]
+ for tag_name in tags:
+ tag = models.ContentTag.objects.create(
+ id=uuid.uuid4().hex, tag_name=tag_name
+ )
+ self.root.tags.add(tag)
+
+ response = self.client.get(
+ reverse("publiccontentnode-detail", kwargs={"pk": self.root.id})
+ )
+ # added by channel builder
+ tags.append(OKAY_TAG)
+ self.assertEqual(set(response.data["tags"]), set(tags))
+
+ def test_channelmetadata_list(self):
+ response = self.client.get(reverse("publicchannel-list", kwargs={}))
+ self.assertEqual(response.data[0]["name"], "testing")
+
+ def test_channelmetadata_list_headers(self):
+ channel = models.ChannelMetadata.objects.get()
+ channel.last_updated = datetime.datetime.now()
+ channel.save()
+ response = self.client.get(reverse("publicchannel-list", kwargs={}))
+ self._assert_headers(response, channel.last_updated)
+
+ def test_channelmetadata_retrieve(self):
+ data = models.ChannelMetadata.objects.values()[0]
+ response = self.client.get(
+ reverse("publicchannel-detail", kwargs={"pk": data["id"]})
+ )
+ self.assertEqual(response.data["name"], "testing")
+
+ def test_channelmetadata_retrieve_headers(self):
+ channel = models.ChannelMetadata.objects.get()
+ channel.last_updated = datetime.datetime.now()
+ channel.save()
+ response = self.client.get(
+ reverse("publicchannel-detail", kwargs={"pk": channel.id})
+ )
+ self._assert_headers(response, channel.last_updated)
+
+ def test_channelmetadata_langfield(self):
+ data = models.ChannelMetadata.objects.first()
+ root_lang = models.Language.objects.first()
+ data.root.lang = root_lang
+ data.root.save()
+
+ response = self.client.get(
+ reverse("publicchannel-detail", kwargs={"pk": data.id})
+ )
+ self.assertEqual(response.data["lang_code"], root_lang.lang_code)
+ self.assertEqual(response.data["lang_name"], root_lang.native_name)
+
+ def test_channelmetadata_langfield_none(self):
+ data = models.ChannelMetadata.objects.first()
+
+ response = self.client.get(
+ reverse("publicchannel-detail", kwargs={"pk": data.id})
+ )
+ self.assertEqual(response.data["lang_code"], None)
+ self.assertEqual(response.data["lang_name"], None)
+
+ def test_channelmetadata_content_available_param_filter_lowercase_true(self):
+ response = self.client.get(
+ reverse("publicchannel-list"), {"available": "true"}
+ )
+ self.assertEqual(response.data[0]["id"], self.channel_data["id"])
+
+ def test_channelmetadata_content_available_param_filter_uppercase_true(self):
+ response = self.client.get(
+ reverse("publicchannel-list"), {"available": True}
+ )
+ self.assertEqual(response.data[0]["id"], self.channel_data["id"])
+
+ def test_channelmetadata_content_unavailable_param_filter_false(self):
+ models.ContentNode.objects.all().update(available=False)
+ response = self.client.get(
+ reverse("publicchannel-list"), {"available": False}
+ )
+ self.assertEqual(response.data[0]["id"], self.channel_data["id"])
+
+ def test_channelmetadata_content_available_field_true(self):
+ response = self.client.get(reverse("publicchannel-list"))
+ self.assertEqual(response.data[0]["available"], True)
+
+ def test_channelmetadata_content_available_field_false(self):
+ models.ContentNode.objects.all().update(available=False)
+ response = self.client.get(reverse("publicchannel-list"))
+ self.assertEqual(response.data[0]["available"], False)
+
+ def test_channelmetadata_has_exercises_filter(self):
+ # Has nothing else for that matter...
+ no_exercise_channel = models.ContentNode.objects.create(
+ pk="6a406ac66b224106aa2e93f73a94333d",
+ channel_id="f8ec4a5d14cd4716890999da596032d2",
+ content_id="ded4a083e75f4689b386fd2b706e792a",
+ kind="topic",
+ title="no exercise channel",
+ )
+ models.ChannelMetadata.objects.create(
+ id="63acff41781543828861ade41dbdd7ff",
+ name="no exercise channel metadata",
+ root=no_exercise_channel,
+ public=True,
+ )
+ models.ContentNode.objects.create(
+ pk=uuid.uuid4().hex,
+ channel_id=self.channel_data["id"],
+ content_id=uuid.uuid4().hex,
+ kind="exercise",
+ title="exercise",
+ parent=self.root,
+ available=True,
+ )
+ no_filter_response = self.client.get(reverse("publicchannel-list"))
+ self.assertEqual(len(no_filter_response.data), 2)
+ with_filter_response = self.client.get(
+ reverse("publicchannel-list"), {"has_exercise": True}
+ )
+ self.assertEqual(len(with_filter_response.data), 1)
+ self.assertEqual(with_filter_response.data[0]["name"], self.channel_data["name"])
+
+ def test_filtering_coach_content_anon(self):
+ response = self.client.get(
+ reverse("publiccontentnode-list"),
+ data={"include_coach_content": False},
+ )
+ total = models.ContentNode.objects.filter(coach_content=False).count()
+ self.assertEqual(len(response.data), total)
+
+ def tearDown(self):
+ """
+ clean up files/folders created during the test
+ """
+ cache.clear()
+ super(ContentNodeAPITestCase, self).tearDown()
diff --git a/contentcuration/kolibri_public/tests/test_importmetadata_api.py b/contentcuration/kolibri_public/tests/test_importmetadata_api.py
new file mode 100644
index 0000000000..b9184f6de1
--- /dev/null
+++ b/contentcuration/kolibri_public/tests/test_importmetadata_api.py
@@ -0,0 +1,124 @@
+import datetime
+from calendar import timegm
+
+from django.db import connection
+from django.db.models import Q
+from django.urls import reverse
+from django.utils.http import http_date
+from kolibri_content import base_models
+from kolibri_content import models as content
+from kolibri_content.constants.schema_versions import CONTENT_SCHEMA_VERSION
+from kolibri_public import models as public
+from kolibri_public.tests.test_content_app import ChannelBuilder
+from le_utils.constants import content_kinds
+from rest_framework.test import APITestCase
+
+
+class ImportMetadataTestCase(APITestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.builder = ChannelBuilder()
+ cls.builder.insert_into_default_db()
+ public.ContentNode.objects.all().update(available=True)
+ cls.root = public.ContentNode.objects.get(id=cls.builder.root_node["id"])
+ cls.node = cls.root.get_descendants().exclude(kind=content_kinds.TOPIC).first()
+ cls.all_nodes = cls.node.get_ancestors(include_self=True)
+ cls.files = public.File.objects.filter(contentnode__in=cls.all_nodes)
+ cls.assessmentmetadata = public.AssessmentMetaData.objects.filter(
+ contentnode__in=cls.all_nodes
+ )
+ cls.localfiles = public.LocalFile.objects.filter(
+ files__in=cls.files
+ ).distinct()
+ cls.languages = public.Language.objects.filter(
+ Q(id__in=cls.files.values_list("lang_id", flat=True))
+ | Q(id__in=cls.all_nodes.values_list("lang_id", flat=True))
+ )
+ cls.through_tags = public.ContentNode.tags.through.objects.filter(
+ contentnode__in=cls.all_nodes
+ )
+ cls.tags = public.ContentTag.objects.filter(
+ id__in=cls.through_tags.values_list("contenttag_id", flat=True)
+ ).distinct()
+
+ def _assert_data(self, Model, ContentModel, queryset):
+ response = self.client.get(
+ reverse("publicimportmetadata-detail", kwargs={"pk": self.node.id})
+ )
+ fields = Model._meta.fields
+ BaseModel = getattr(base_models, Model.__name__, Model)
+ field_names = {field.column for field in BaseModel._meta.fields}
+ if hasattr(BaseModel, "_mptt_meta"):
+ field_names.add(BaseModel._mptt_meta.parent_attr)
+ field_names.add(BaseModel._mptt_meta.tree_id_attr)
+ field_names.add(BaseModel._mptt_meta.left_attr)
+ field_names.add(BaseModel._mptt_meta.right_attr)
+ field_names.add(BaseModel._mptt_meta.level_attr)
+ for response_data, obj in zip(response.data[ContentModel._meta.db_table], queryset):
+ # Ensure that we are not returning any empty objects
+ self.assertNotEqual(response_data, {})
+ for field in fields:
+ if field.column in field_names:
+ value = response_data[field.column]
+ if hasattr(field, "from_db_value"):
+ value = field.from_db_value(value, None, connection)
+ self.assertEqual(value, getattr(obj, field.column))
+
+ def test_import_metadata_nodes(self):
+ self._assert_data(public.ContentNode, content.ContentNode, self.all_nodes)
+
+ def test_import_metadata_files(self):
+ self._assert_data(public.File, content.File, self.files)
+
+ def test_import_metadata_assessmentmetadata(self):
+ self._assert_data(public.AssessmentMetaData, content.AssessmentMetaData, self.assessmentmetadata)
+
+ def test_import_metadata_localfiles(self):
+ self._assert_data(public.LocalFile, content.LocalFile, self.localfiles)
+
+ def test_import_metadata_languages(self):
+ self._assert_data(public.Language, content.Language, self.languages)
+
+ def test_import_metadata_through_tags(self):
+ self._assert_data(public.ContentNode.tags.through, content.ContentNode.tags.through, self.through_tags)
+
+ def test_import_metadata_tags(self):
+ self._assert_data(public.ContentTag, content.ContentTag, self.tags)
+
+ def test_schema_version_too_low(self):
+ response = self.client.get(
+ reverse("publicimportmetadata-detail", kwargs={"pk": self.node.id})
+ + "?schema_version=1"
+ )
+ self.assertEqual(response.status_code, 400)
+
+ def test_schema_version_too_high(self):
+ response = self.client.get(
+ reverse("publicimportmetadata-detail", kwargs={"pk": self.node.id})
+ + "?schema_version={}".format(int(CONTENT_SCHEMA_VERSION) + 1)
+ )
+ self.assertEqual(response.status_code, 400)
+
+ def test_schema_version_just_right(self):
+ response = self.client.get(
+ reverse("publicimportmetadata-detail", kwargs={"pk": self.node.id})
+ + "?schema_version={}".format(CONTENT_SCHEMA_VERSION)
+ )
+ self.assertEqual(response.status_code, 200)
+
+ def test_headers(self):
+ channel = public.ChannelMetadata.objects.get()
+ channel.last_updated = datetime.datetime.now()
+ channel.save()
+ response = self.client.get(
+ reverse("publicimportmetadata-detail", kwargs={"pk": self.node.id})
+ )
+ self.assertEqual(response.headers["Vary"], "Accept")
+ self.assertEqual(
+ response.headers["Cache-Control"],
+ "max-age=300, public, stale-while-revalidate=100",
+ )
+ self.assertEqual(
+ response.headers["Last-Modified"],
+ http_date(timegm(channel.last_updated.utctimetuple())),
+ )
diff --git a/contentcuration/kolibri_public/tests/test_mapper.py b/contentcuration/kolibri_public/tests/test_mapper.py
new file mode 100644
index 0000000000..4b56813464
--- /dev/null
+++ b/contentcuration/kolibri_public/tests/test_mapper.py
@@ -0,0 +1,122 @@
+import os
+import tempfile
+
+from django.core.management import call_command
+from django.test import TestCase
+from kolibri_content import models as kolibri_content_models
+from kolibri_content.router import cleanup_content_database_connection
+from kolibri_content.router import get_active_content_database
+from kolibri_content.router import using_content_database
+from kolibri_public import models as kolibri_public_models
+from kolibri_public.tests.base import ChannelBuilder
+from kolibri_public.tests.base import OKAY_TAG
+from kolibri_public.utils.mapper import ChannelMapper
+from le_utils.constants import content_kinds
+
+from contentcuration.models import Channel
+
+
+class ChannelMapperTest(TestCase):
+
+ @property
+ def overrides(self):
+ return {
+ kolibri_public_models.ContentNode: {
+ "available": True,
+ "tree_id": self.mapper.tree_id,
+ },
+ kolibri_public_models.LocalFile: {
+ "available": True,
+ }
+ }
+
+ @classmethod
+ def setUpClass(cls):
+ super(ChannelMapperTest, cls).setUpClass()
+ call_command("loadconstants")
+ _, cls.tempdb = tempfile.mkstemp(suffix=".sqlite3")
+
+ with using_content_database(cls.tempdb):
+ call_command("migrate", "content", database=get_active_content_database(), no_input=True)
+ builder = ChannelBuilder(models=kolibri_content_models, options={
+ "problematic_tags": True,
+ "problematic_nodes": True,
+ })
+ builder.insert_into_default_db()
+ cls.source_root = kolibri_content_models.ContentNode.objects.get(id=builder.root_node["id"])
+ cls.channel = kolibri_content_models.ChannelMetadata.objects.get(id=builder.channel["id"])
+ contentcuration_channel = Channel.objects.create(id=cls.channel.id, name=cls.channel.name, public=True)
+ contentcuration_channel.main_tree.published = True
+ contentcuration_channel.main_tree.save()
+ cls.mapper = ChannelMapper(cls.channel)
+ cls.mapper.run()
+ cls.mapped_root = cls.mapper.mapped_root
+
+ def _assert_model(self, source, mapped, Model):
+ for field in Model._meta.fields:
+ column = field.column
+ if hasattr(source, column):
+ if Model in self.overrides and column in self.overrides[Model]:
+ self.assertEqual(self.overrides[Model][column], getattr(mapped, column))
+ else:
+ self.assertEqual(getattr(source, column), getattr(mapped, column))
+
+ def _assert_node(self, source, mapped):
+ """
+ :param source: kolibri_content_models.ContentNode
+ :param mapped: kolibri_public_models.ContentNode
+ """
+ self._assert_model(source, mapped, kolibri_public_models.ContentNode)
+
+ for src, mpd in zip(source.assessmentmetadata.all(), mapped.assessmentmetadata.all()):
+ self._assert_model(src, mpd, kolibri_public_models.AssessmentMetaData)
+
+ for src, mpd in zip(source.files.all(), mapped.files.all()):
+ self._assert_model(src, mpd, kolibri_public_models.File)
+ self._assert_model(src.local_file, mpd.local_file, kolibri_public_models.LocalFile)
+
+ # should only map OKAY_TAG and not BAD_TAG
+ for mapped_tag in mapped.tags.all():
+ self.assertEqual(OKAY_TAG, mapped_tag.tag_name)
+
+ self.assertEqual(mapped.ancestors, [{"id": ancestor.id, "title": ancestor.title} for ancestor in source.get_ancestors()])
+
+ def _recurse_and_assert(self, sources, mappeds, recursion_depth=0):
+ recursion_depths = []
+ for source, mapped in zip(sources, mappeds):
+ self._assert_node(source, mapped)
+ source_children = source.children.all()
+ mapped_children = mapped.children.all()
+ if mapped.kind == content_kinds.TOPIC:
+ self.assertEqual(len(source_children), len(mapped_children))
+ else:
+ self.assertEqual(0, len(mapped_children))
+
+ recursion_depths.append(
+ self._recurse_and_assert(
+ source_children,
+ mapped_children,
+ recursion_depth=recursion_depth + 1,
+ )
+ )
+ return recursion_depth if not recursion_depths else max(recursion_depths)
+
+ def test_map(self):
+ with using_content_database(self.tempdb):
+ self._recurse_and_assert([self.source_root], [self.mapped_root])
+ self._assert_model(self.channel, self.mapper.mapped_channel, kolibri_public_models.ChannelMetadata)
+
+ def test_map_replace(self):
+ with using_content_database(self.tempdb):
+ mapper = ChannelMapper(self.channel)
+ mapper.run()
+ self._recurse_and_assert([self.source_root], [mapper.mapped_root])
+ self._assert_model(self.channel, self.mapper.mapped_channel, kolibri_public_models.ChannelMetadata)
+
+ @classmethod
+ def tearDownClass(cls):
+ # Clean up datbase connection after the test
+ cleanup_content_database_connection(cls.tempdb)
+ super(ChannelMapperTest, cls).tearDownClass()
+ if os.path.exists(cls.tempdb):
+ os.remove(cls.tempdb)
diff --git a/contentcuration/contentcuration/tests/test_public_api.py b/contentcuration/kolibri_public/tests/test_public_v1_api.py
similarity index 92%
rename from contentcuration/contentcuration/tests/test_public_api.py
rename to contentcuration/kolibri_public/tests/test_public_v1_api.py
index 9d36cfae19..ecaac9c177 100644
--- a/contentcuration/contentcuration/tests/test_public_api.py
+++ b/contentcuration/kolibri_public/tests/test_public_v1_api.py
@@ -4,8 +4,8 @@
from django.core.cache import cache
from django.urls import reverse
-from .base import BaseAPITestCase
-from .testdata import generated_base64encoding
+from contentcuration.tests.base import BaseAPITestCase
+from contentcuration.tests.testdata import generated_base64encoding
class PublicAPITestCase(BaseAPITestCase):
@@ -30,6 +30,7 @@ def test_info_endpoint(self):
response = self.client.get(reverse("info"))
self.assertEqual(response.data["application"], "studio")
self.assertEqual(response.data["device_name"], "Kolibri Studio")
+ self.assertEqual(response.data["instance_id"], "ef896e7b7bbf5a359371e6f7afd28742")
def test_empty_public_channels(self):
"""
diff --git a/contentcuration/kolibri_public/urls.py b/contentcuration/kolibri_public/urls.py
new file mode 100644
index 0000000000..cc6ff65cb5
--- /dev/null
+++ b/contentcuration/kolibri_public/urls.py
@@ -0,0 +1,34 @@
+from django.urls import include
+from django.urls import path
+from django.urls import re_path
+from kolibri_public import views_v1
+from kolibri_public.import_metadata_view import ImportMetadataViewset
+from kolibri_public.views import ChannelMetadataViewSet
+from kolibri_public.views import ContentNodeTreeViewset
+from kolibri_public.views import ContentNodeViewset
+from rest_framework import routers
+
+
+public_content_v2_router = routers.SimpleRouter()
+public_content_v2_router.register(
+ r"channel", ChannelMetadataViewSet, basename="publicchannel"
+)
+public_content_v2_router.register(
+ r"contentnode", ContentNodeViewset, basename="publiccontentnode"
+)
+public_content_v2_router.register(
+ r"contentnode_tree",
+ ContentNodeTreeViewset,
+ basename="publiccontentnode_tree",
+)
+public_content_v2_router.register(
+ r"importmetadata", ImportMetadataViewset, basename="publicimportmetadata"
+)
+
+urlpatterns = [
+ re_path(r'^api/public/channel/(?P[^/]+)', views_v1.get_channel_name_by_id, name='get_channel_name_by_id'),
+ re_path(r'^api/public/(?P[^/]+)/channels$', views_v1.get_public_channel_list, name='get_public_channel_list'),
+ re_path(r'^api/public/(?P[^/]+)/channels/lookup/(?P[^/]+)', views_v1.get_public_channel_lookup, name='get_public_channel_lookup'),
+ re_path(r'^api/public/info', views_v1.InfoViewSet.as_view({'get': 'list'}), name='info'),
+ path("api/public/v2/", include(public_content_v2_router.urls)),
+]
diff --git a/contentcuration/kolibri_public/utils/annotation.py b/contentcuration/kolibri_public/utils/annotation.py
new file mode 100644
index 0000000000..3cb800a3d9
--- /dev/null
+++ b/contentcuration/kolibri_public/utils/annotation.py
@@ -0,0 +1,83 @@
+"""
+Functions in here are the subset of annotation functions from Kolibri related to channel metadata.
+https://github.com/learningequality/kolibri/blob/caec91dd2da5617adfb50332fb698068248e8e47/kolibri/core/content/utils/annotation.py#L731
+"""
+import datetime
+
+from django.db.models import Q
+from django.db.models import Sum
+from kolibri_public.models import ChannelMetadata
+from kolibri_public.models import ContentNode
+from kolibri_public.models import LocalFile
+from le_utils.constants import content_kinds
+
+from contentcuration.models import Channel
+
+
+def set_channel_metadata_fields(channel_id, public=None):
+ # Remove unneeded db_lock
+ channel = ChannelMetadata.objects.get(id=channel_id)
+ calculate_published_size(channel)
+ calculate_total_resource_count(channel)
+ calculate_included_languages(channel)
+ calculate_next_order(channel, public=public)
+ # Add this to ensure we keep this up to date.
+ channel.last_updated = datetime.datetime.now()
+
+ if public is not None:
+ channel.public = public
+ channel.save()
+
+
+def files_for_nodes(nodes):
+ return LocalFile.objects.filter(files__contentnode__in=nodes)
+
+
+def total_file_size(files_or_nodes):
+ if issubclass(files_or_nodes.model, LocalFile):
+ localfiles = files_or_nodes
+ elif issubclass(files_or_nodes.model, ContentNode):
+ localfiles = files_for_nodes(files_or_nodes)
+ else:
+ raise TypeError("Expected queryset for LocalFile or ContentNode")
+ return localfiles.distinct().aggregate(Sum("file_size"))["file_size__sum"] or 0
+
+
+def calculate_published_size(channel):
+ content_nodes = ContentNode.objects.filter(channel_id=channel.id)
+ channel.published_size = total_file_size(
+ files_for_nodes(content_nodes).filter(available=True)
+ )
+ channel.save()
+
+
+def calculate_total_resource_count(channel):
+ content_nodes = ContentNode.objects.filter(channel_id=channel.id)
+ channel.total_resource_count = (
+ content_nodes.filter(available=True).exclude(kind=content_kinds.TOPIC).count()
+ )
+ channel.save()
+
+
+def calculate_included_languages(channel):
+ content_nodes = ContentNode.objects.filter(
+ channel_id=channel.id, available=True
+ ).exclude(lang=None)
+ languages = content_nodes.order_by("lang").values_list("lang", flat=True).distinct()
+ channel.included_languages.add(*list(languages))
+
+
+def calculate_next_order(channel, public=False):
+ # This has been edited from the source Kolibri, in order
+ # to make the order match given by the public channel API on Studio.
+ if public:
+ channel_list_order = list(Channel.objects.filter(
+ # Ensure that this channel is always included in the list.
+ Q(public=True, deleted=False, main_tree__published=True) | Q(id=channel.id)
+ ).order_by("-priority").values_list("id", flat=True))
+ # this shouldn't happen, but if we're exporting a channel database to Kolibri Public
+ # and the channel does not actually exist locally, then this would fail
+ if channel.id in channel_list_order:
+ order = channel_list_order.index(channel.id)
+ channel.order = order
+ channel.save()
diff --git a/contentcuration/kolibri_public/utils/mapper.py b/contentcuration/kolibri_public/utils/mapper.py
new file mode 100644
index 0000000000..f7605d7e1b
--- /dev/null
+++ b/contentcuration/kolibri_public/utils/mapper.py
@@ -0,0 +1,243 @@
+from django.db import transaction
+from django.db.models.functions import Length
+from kolibri_content import models as kolibri_content_models
+from kolibri_content.base_models import MAX_TAG_LENGTH
+from kolibri_public import models as kolibri_public_models
+from kolibri_public.search import annotate_label_bitmasks
+from kolibri_public.utils.annotation import set_channel_metadata_fields
+from le_utils.constants import content_kinds
+
+
+BATCH_SIZE = 1000
+
+
+class ChannelMapper(object):
+ """
+ This object handles mapping from models defined in the kolibri_content app
+ and maps them to models in the kolibri_public app.
+ Uses the ChannelMetadata as the starting point as all other information is
+ Foreign Keyed from the root ContentNode.
+ """
+
+ def __init__(self, channel, public=True):
+ self.channel = channel
+ self.public = public
+
+ @property
+ def overrides(self):
+ return {
+ kolibri_public_models.ContentNode: {
+ "available": True,
+ "tree_id": self.tree_id,
+ },
+ kolibri_public_models.LocalFile: {
+ "available": True,
+ }
+ }
+
+ def _handle_old_tree_if_exists(self):
+ try:
+ old_channel = kolibri_public_models.ChannelMetadata.objects.get(id=self.channel.id)
+ self.tree_id = old_channel.root.tree_id
+ old_channel.root.get_descendants(include_self=True).delete()
+ except kolibri_public_models.ChannelMetadata.DoesNotExist:
+ self.tree_id = kolibri_public_models.MPTTTreeIDManager.objects.create().id
+
+ def run(self):
+ with transaction.atomic():
+ self._handle_old_tree_if_exists()
+ self.mapped_root = self.map_root(self.channel.root)
+ self.mapped_channel = self._map_model(self.channel, kolibri_public_models.ChannelMetadata)
+ self.mapped_channel.public = self.public
+ self.mapped_channel.save_base(raw=True)
+ annotate_label_bitmasks(self.mapped_root.get_descendants(include_self=True))
+ # Rather than set the ancestors fields after mapping, like it is done in Kolibri
+ # here we set it during mapping as we are already recursing through the tree.
+ set_channel_metadata_fields(self.mapped_channel.id, public=self.public)
+
+ def _map_model(self, source, Model):
+ properties = {}
+ for field in Model._meta.fields:
+ column = field.column
+ if hasattr(source, column):
+ properties[column] = getattr(source, column)
+ if Model in self.overrides and column in self.overrides[Model]:
+ properties[column] = self.overrides[Model][column]
+
+ return Model(**properties)
+
+ def _map_and_bulk_create_model(self, sources, Model):
+ cloned_sources = [self._map_model(source, Model) for source in sources]
+
+ Model.objects.bulk_create(cloned_sources, ignore_conflicts=True)
+
+ def _map_node(self, source, ancestors):
+ node = self._map_model(source, kolibri_public_models.ContentNode)
+ node.ancestors = ancestors
+ return node
+
+ def _extend_ancestors(self, ancestors, new_ancestor):
+ return ancestors + [{"id": new_ancestor.id, "title": new_ancestor.title.replace('"', '\\"')}]
+
+ def _recurse_to_create_tree(
+ self,
+ source,
+ nodes_by_parent,
+ ancestors,
+ ):
+ nodes_to_create = [self._map_node(source, ancestors)]
+
+ if source.kind == content_kinds.TOPIC and source.id in nodes_by_parent:
+ children = sorted(nodes_by_parent[source.id], key=lambda x: x.lft)
+ ancestors = self._extend_ancestors(ancestors, source)
+ for child in children:
+ nodes_to_create.extend(self._recurse_to_create_tree(
+ child,
+ nodes_by_parent,
+ ancestors,
+ ))
+ return nodes_to_create
+
+ def map_root(
+ self,
+ root,
+ batch_size=None,
+ progress_tracker=None
+ ):
+ """
+ :type progress_tracker: contentcuration.utils.celery.ProgressTracker|None
+ """
+ if batch_size is None:
+ batch_size = BATCH_SIZE
+
+ return self._map(
+ root,
+ batch_size,
+ progress_tracker=progress_tracker,
+ )[0]
+
+ def _map(
+ self,
+ node,
+ batch_size,
+ ancestors=[],
+ progress_tracker=None,
+ ):
+ """
+ :type progress_tracker: contentcuration.utils.celery.ProgressTracker|None
+ """
+ if node.rght - node.lft < batch_size:
+ copied_nodes = self._deep_map(
+ node,
+ ancestors,
+ )
+ if progress_tracker:
+ progress_tracker.increment(len(copied_nodes))
+ return copied_nodes
+ node_copy = self._shallow_map(
+ node,
+ ancestors,
+ )
+ ancestors = self._extend_ancestors(ancestors, node)
+ if progress_tracker:
+ progress_tracker.increment()
+ children = node.get_children().order_by("lft")
+ for child in children:
+ self._map(
+ child,
+ batch_size,
+ ancestors=ancestors,
+ progress_tracker=progress_tracker,
+ )
+ return [node_copy]
+
+ def _copy_tags(self, node_ids):
+ initial_source_tag_mappings = kolibri_content_models.ContentNode.tags.through.objects.filter(
+ contentnode_id__in=node_ids
+ )
+
+ source_tags = (
+ kolibri_content_models.ContentTag.objects
+ .annotate(
+ tag_name_len=Length("tag_name"),
+ )
+ .filter(
+ id__in=initial_source_tag_mappings.values_list("contenttag_id", flat=True),
+ tag_name_len__lte=MAX_TAG_LENGTH,
+ )
+ )
+
+ source_tag_mappings = (
+ initial_source_tag_mappings
+ .filter(
+ contenttag_id__in=source_tags.values_list("id", flat=True),
+ )
+ )
+
+ self._map_and_bulk_create_model(source_tags, kolibri_public_models.ContentTag)
+
+ self._map_and_bulk_create_model(source_tag_mappings, kolibri_public_models.ContentNode.tags.through)
+
+ def _copy_assessment_metadata(self, node_ids):
+ node_assessmentmetadata = kolibri_content_models.AssessmentMetaData.objects.filter(contentnode_id__in=node_ids)
+
+ self._map_and_bulk_create_model(node_assessmentmetadata, kolibri_public_models.AssessmentMetaData)
+
+ def _copy_files(self, node_ids):
+ node_files = kolibri_content_models.File.objects.filter(contentnode_id__in=node_ids)
+
+ local_files = kolibri_content_models.LocalFile.objects.filter(id__in=node_files.values_list("local_file_id", flat=True))
+
+ self._map_and_bulk_create_model(local_files, kolibri_public_models.LocalFile)
+
+ self._map_and_bulk_create_model(node_files, kolibri_public_models.File)
+
+ def _copy_associated_objects(self, nodes):
+ node_ids = [node.id for node in nodes]
+
+ self._copy_files(node_ids)
+
+ self._copy_assessment_metadata(node_ids)
+
+ self._copy_tags(node_ids)
+
+ def _shallow_map(
+ self,
+ node,
+ ancestors,
+ ):
+ mapped_node = self._map_node(node, ancestors)
+
+ mapped_node.save_base(raw=True)
+
+ self._copy_associated_objects([node])
+ return mapped_node
+
+ def _deep_map(
+ self,
+ node,
+ ancestors,
+ ):
+ source_nodes = node.get_descendants(include_self=True)
+
+ nodes_by_parent = {}
+ for source_node in source_nodes:
+ if source_node.parent_id not in nodes_by_parent:
+ nodes_by_parent[source_node.parent_id] = []
+ nodes_by_parent[source_node.parent_id].append(source_node)
+
+ nodes_to_create = self._recurse_to_create_tree(
+ node,
+ nodes_by_parent,
+ ancestors,
+ )
+
+ mapped_nodes = kolibri_public_models.ContentNode.objects.bulk_create(nodes_to_create)
+
+ # filter to only the nodes that were created, since some source nodes could have
+ # been problematic
+ self._copy_associated_objects(source_nodes.filter(
+ id__in=[mapped_node.id for mapped_node in mapped_nodes],
+ ))
+
+ return mapped_nodes
diff --git a/contentcuration/kolibri_public/views.py b/contentcuration/kolibri_public/views.py
new file mode 100644
index 0000000000..8c66b1993b
--- /dev/null
+++ b/contentcuration/kolibri_public/views.py
@@ -0,0 +1,769 @@
+"""
+The code in this file is vendored and modified from Kolibri.
+The main changes are to remove unneeded parts of the code,
+and to swap in Kolibri Studio specific code in place
+of Kolibri.
+Copied from:
+https://github.com/learningequality/kolibri/blob/b8ef7212f9ab44660e2c7cabeb0122311e5ae5ed/kolibri/core/content/api.py
+"""
+import logging
+import re
+from collections import OrderedDict
+from functools import reduce
+
+from django.core.exceptions import ValidationError
+from django.db.models import Exists
+from django.db.models import F
+from django.db.models import Max
+from django.db.models import OuterRef
+from django.db.models import Q
+from django.http import Http404
+from django.utils.cache import patch_cache_control
+from django.utils.cache import patch_response_headers
+from django.utils.decorators import method_decorator
+from django.utils.translation import ugettext as _
+from django.views.decorators.http import last_modified
+from django_filters.rest_framework import BaseInFilter
+from django_filters.rest_framework import BooleanFilter
+from django_filters.rest_framework import CharFilter
+from django_filters.rest_framework import ChoiceFilter
+from django_filters.rest_framework import DjangoFilterBackend
+from django_filters.rest_framework import FilterSet
+from django_filters.rest_framework import NumberFilter
+from django_filters.rest_framework import UUIDFilter
+from kolibri_public import models
+from kolibri_public.search import get_available_metadata_labels
+from kolibri_public.stopwords import stopwords_set
+from le_utils.constants import content_kinds
+from rest_framework.permissions import AllowAny
+from rest_framework.response import Response
+
+from contentcuration.middleware.locale import locale_exempt
+from contentcuration.middleware.session import session_exempt
+from contentcuration.models import generate_storage_url
+from contentcuration.utils.pagination import ValuesViewsetCursorPagination
+from contentcuration.viewsets.base import BaseValuesViewset
+from contentcuration.viewsets.base import ReadOnlyValuesViewset
+
+
+logger = logging.getLogger(__name__)
+
+
+def get_last_modified(*args, **kwargs):
+ return models.ChannelMetadata.objects.all().aggregate(updated=Max("last_updated"))["updated"]
+
+
+def metadata_cache(some_func):
+ """
+ Decorator to apply consistent caching across metadata endpoints
+ """
+ # 5 minutes
+ cache_timeout = 300
+
+ @last_modified(get_last_modified)
+ def wrapper_func(*args, **kwargs):
+ response = some_func(*args, **kwargs)
+ patch_response_headers(response, cache_timeout=cache_timeout)
+ # The above function does call patch_cache_control within it
+ # but it is intelligent enough to combine successive invocations.
+ # Set stale while revalidate to 100 seconds, which is how long a request
+ # would take before timing out, so within that time frame, we should have
+ # refreshed the cache.
+ patch_cache_control(response, public=True, stale_while_revalidate=100)
+
+ return response
+
+ return locale_exempt(session_exempt(wrapper_func))
+
+
+MODALITIES = set(["QUIZ"])
+
+
+class ChannelMetadataFilter(FilterSet):
+ available = BooleanFilter(method="filter_available", label="Available")
+ has_exercise = BooleanFilter(method="filter_has_exercise", label="Has exercises")
+
+ class Meta:
+ model = models.ChannelMetadata
+ fields = ("available", "has_exercise")
+
+ def filter_has_exercise(self, queryset, name, value):
+ queryset = queryset.annotate(
+ has_exercise=Exists(
+ models.ContentNode.objects.filter(
+ kind=content_kinds.EXERCISE,
+ available=True,
+ channel_id=OuterRef("id"),
+ )
+ )
+ )
+
+ return queryset.filter(has_exercise=True)
+
+ def filter_available(self, queryset, name, value):
+ return queryset.filter(root__available=value)
+
+
+@method_decorator(metadata_cache, name="dispatch")
+class ChannelMetadataViewSet(ReadOnlyValuesViewset):
+ filter_backends = (DjangoFilterBackend,)
+ # Update from filter_class to filterset_class for newer version of Django Filters
+ filterset_class = ChannelMetadataFilter
+ # Add an explicit allow any permission class to override the Studio default
+ permission_classes = (AllowAny,)
+
+ values = (
+ "author",
+ "description",
+ "tagline",
+ "id",
+ "last_updated",
+ "root__lang__lang_code",
+ # Read from native_name from content curation model
+ "root__lang__native_name",
+ "name",
+ "root",
+ "thumbnail",
+ "version",
+ "root__available",
+ "root__num_coach_contents",
+ "public",
+ "total_resource_count",
+ "published_size",
+ )
+
+ field_map = {
+ "num_coach_contents": "root__num_coach_contents",
+ "available": "root__available",
+ "lang_code": "root__lang__lang_code",
+ # Map to lang_name to map from native_name to map from content curation model
+ # to how we want to expose it for Kolibri.
+ "lang_name": "root__lang__native_name",
+ }
+
+ def get_queryset(self):
+ return models.ChannelMetadata.objects.all()
+
+ def consolidate(self, items, queryset):
+ included_languages = {}
+ for (
+ channel_id,
+ language_id,
+ ) in models.ChannelMetadata.included_languages.through.objects.filter(
+ channelmetadata__in=queryset
+ ).values_list(
+ "channelmetadata_id", "language_id"
+ ):
+ if channel_id not in included_languages:
+ included_languages[channel_id] = []
+ included_languages[channel_id].append(language_id)
+ for item in items:
+ item["included_languages"] = included_languages.get(item["id"], [])
+ item["last_published"] = item["last_updated"]
+ return items
+
+
+class UUIDInFilter(BaseInFilter, UUIDFilter):
+ pass
+
+
+class CharInFilter(BaseInFilter, CharFilter):
+ pass
+
+
+contentnode_filter_fields = [
+ "parent",
+ "parent__isnull",
+ "prerequisite_for",
+ "has_prerequisite",
+ "related",
+ "exclude_content_ids",
+ "ids",
+ "content_id",
+ "channel_id",
+ "kind",
+ "include_coach_content",
+ "kind_in",
+ "contains_quiz",
+ "grade_levels",
+ "resource_types",
+ "learning_activities",
+ "accessibility_labels",
+ "categories",
+ "learner_needs",
+ "keywords",
+ "channels",
+ "languages",
+ "tree_id",
+ "lft__gt",
+ "rght__lt",
+]
+
+
+# return the result of and-ing a list of queries
+def intersection(queries):
+ if queries:
+ return reduce(lambda x, y: x & y, queries)
+ return None
+
+
+def union(queries):
+ if queries:
+ return reduce(lambda x, y: x | y, queries)
+ return None
+
+
+class ContentNodeFilter(FilterSet):
+ ids = UUIDInFilter(field_name="id")
+ kind = ChoiceFilter(
+ method="filter_kind",
+ choices=(content_kinds.choices + (("content", _("Resource")),)),
+ )
+ exclude_content_ids = CharFilter(method="filter_exclude_content_ids")
+ kind_in = CharFilter(method="filter_kind_in")
+ parent = UUIDFilter("parent")
+ parent__isnull = BooleanFilter(field_name="parent", lookup_expr="isnull")
+ include_coach_content = BooleanFilter(method="filter_include_coach_content")
+ contains_quiz = CharFilter(method="filter_contains_quiz")
+ grade_levels = CharFilter(method="bitmask_contains_and")
+ resource_types = CharFilter(method="bitmask_contains_and")
+ learning_activities = CharFilter(method="bitmask_contains_and")
+ accessibility_labels = CharFilter(method="bitmask_contains_and")
+ categories = CharFilter(method="bitmask_contains_and")
+ learner_needs = CharFilter(method="bitmask_contains_and")
+ keywords = CharFilter(method="filter_keywords")
+ channels = UUIDInFilter(field_name="channel_id")
+ languages = CharInFilter(field_name="lang_id")
+ categories__isnull = BooleanFilter(field_name="categories", lookup_expr="isnull")
+ lft__gt = NumberFilter(field_name="lft", lookup_expr="gt")
+ rght__lt = NumberFilter(field_name="rght", lookup_expr="lt")
+ authors = CharFilter(method="filter_by_authors")
+ tags = CharFilter(method="filter_by_tags")
+ descendant_of = UUIDFilter(method="filter_descendant_of")
+
+ class Meta:
+ model = models.ContentNode
+ fields = contentnode_filter_fields
+
+ def filter_by_authors(self, queryset, name, value):
+ """
+ Show content filtered by author
+
+ :param queryset: all content nodes for this channel
+ :param value: an array of authors to filter by
+ :return: content nodes that match the authors
+ """
+ authors = value.split(",")
+ return queryset.filter(author__in=authors).order_by("lft")
+
+ def filter_by_tags(self, queryset, name, value):
+ """
+ Show content filtered by tag
+
+ :param queryset: all content nodes for this channel
+ :param value: an array of tags to filter by
+ :return: content nodes that match the tags
+ """
+ tags = value.split(",")
+ return queryset.filter(tags__tag_name__in=tags).order_by("lft").distinct()
+
+ def filter_descendant_of(self, queryset, name, value):
+ """
+ Show content that is descendant of the given node
+
+ :param queryset: all content nodes for this channel
+ :param value: the root node to filter descendant of
+ :return: all descendants content
+ """
+ try:
+ node = models.ContentNode.objects.values("lft", "rght", "tree_id").get(
+ pk=value
+ )
+ except (models.ContentNode.DoesNotExist, ValueError):
+ return queryset.none()
+ return queryset.filter(
+ lft__gt=node["lft"], rght__lt=node["rght"], tree_id=node["tree_id"]
+ )
+
+ def filter_kind(self, queryset, name, value):
+ """
+ Show only content of a given kind.
+
+ :param queryset: all content nodes for this channel
+ :param value: 'content' for everything except topics, or one of the content kind constants
+ :return: content nodes of the given kind
+ """
+ if value == "content":
+ return queryset.exclude(kind=content_kinds.TOPIC).order_by("lft")
+ return queryset.filter(kind=value).order_by("lft")
+
+ def filter_kind_in(self, queryset, name, value):
+ """
+ Show only content of given kinds.
+
+ :param queryset: all content nodes for this channel
+ :param value: A list of content node kinds
+ :return: content nodes of the given kinds
+ """
+ kinds = value.split(",")
+ return queryset.filter(kind__in=kinds).order_by("lft")
+
+ def filter_exclude_content_ids(self, queryset, name, value):
+ return queryset.exclude_by_content_ids(value.split(","))
+
+ def filter_include_coach_content(self, queryset, name, value):
+ if value:
+ return queryset
+ return queryset.filter(coach_content=False)
+
+ def filter_contains_quiz(self, queryset, name, value):
+ if value:
+ quizzes = models.ContentNode.objects.filter(
+ options__contains='"modality": "QUIZ"'
+ ).get_ancestors(include_self=True)
+ return queryset.filter(pk__in=quizzes.values_list("pk", flat=True))
+ return queryset
+
+ def filter_keywords(self, queryset, name, value):
+ # all words with punctuation removed
+ all_words = [w for w in re.split('[?.,!";: ]', value) if w]
+ # words in all_words that are not stopwords
+ critical_words = [w for w in all_words if w not in stopwords_set]
+ words = critical_words if critical_words else all_words
+ query = union(
+ [
+ # all critical words in title
+ intersection([Q(title__icontains=w) for w in words]),
+ # all critical words in description
+ intersection([Q(description__icontains=w) for w in words]),
+ ]
+ )
+
+ return queryset.filter(query)
+
+ def bitmask_contains_and(self, queryset, name, value):
+ return queryset.has_all_labels(name, value.split(","))
+
+
+def map_file(file):
+ file["checksum"] = file.pop("local_file__id")
+ file["available"] = file.pop("local_file__available")
+ file["file_size"] = file.pop("local_file__file_size")
+ file["extension"] = file.pop("local_file__extension")
+ # Swap in the contentcuration generate_storage_url function here
+ file["storage_url"] = generate_storage_url("{}.{}".format(file["checksum"], file["extension"]))
+ return file
+
+
+def _split_text_field(text):
+ return text.split(",") if text else []
+
+
+class BaseContentNodeMixin(object):
+ """
+ A base mixin for viewsets that need to return the same format of data
+ serialization for ContentNodes.
+ """
+
+ filter_backends = (DjangoFilterBackend,)
+ # Update from filter_class to filterset_class for newer version of Django Filters
+ filterset_class = ContentNodeFilter
+ # Add an explicit allow any permission class to override the Studio default
+ permission_classes = (AllowAny,)
+
+ values = (
+ "id",
+ "author",
+ "available",
+ "channel_id",
+ "coach_content",
+ "content_id",
+ "description",
+ "kind",
+ "lang_id",
+ "license_description",
+ "license_name",
+ "license_owner",
+ "num_coach_contents",
+ "options",
+ "parent",
+ "sort_order",
+ "title",
+ "lft",
+ "rght",
+ "tree_id",
+ "learning_activities",
+ "grade_levels",
+ "resource_types",
+ "accessibility_labels",
+ "categories",
+ "duration",
+ "ancestors",
+ )
+
+ field_map = {
+ "learning_activities": lambda x: _split_text_field(x["learning_activities"]),
+ "grade_levels": lambda x: _split_text_field(x["grade_levels"]),
+ "resource_types": lambda x: _split_text_field(x["resource_types"]),
+ "accessibility_labels": lambda x: _split_text_field(x["accessibility_labels"]),
+ "categories": lambda x: _split_text_field(x["categories"]),
+ }
+
+ def get_queryset(self):
+ return models.ContentNode.objects.filter(available=True)
+
+ def get_related_data_maps(self, items, queryset):
+ assessmentmetadata_map = {
+ a["contentnode"]: a
+ for a in models.AssessmentMetaData.objects.filter(
+ contentnode__in=queryset
+ ).values(
+ "assessment_item_ids",
+ "number_of_assessments",
+ "mastery_model",
+ "randomize",
+ "is_manipulable",
+ "contentnode",
+ )
+ }
+
+ files_map = {}
+
+ files = list(
+ models.File.objects.filter(contentnode__in=queryset).values(
+ "id",
+ "contentnode",
+ "local_file__id",
+ "priority",
+ "local_file__available",
+ "local_file__file_size",
+ "local_file__extension",
+ "preset",
+ "lang_id",
+ "supplementary",
+ "thumbnail",
+ )
+ )
+
+ lang_ids = set([obj["lang_id"] for obj in items + files])
+
+ languages_map = {
+ lang["id"]: lang
+ # Add an annotation for lang_name to map to native_name to map from content curation model
+ # to how we want to expose it for Kolibri.
+ for lang in models.Language.objects.filter(id__in=lang_ids).annotate(lang_name=F("native_name")).values(
+ "id", "lang_code", "lang_subcode", "lang_name", "lang_direction"
+ )
+ }
+
+ for f in files:
+ contentnode_id = f.pop("contentnode")
+ if contentnode_id not in files_map:
+ files_map[contentnode_id] = []
+ lang_id = f.pop("lang_id")
+ f["lang"] = languages_map.get(lang_id)
+ files_map[contentnode_id].append(map_file(f))
+
+ tags_map = {}
+
+ for t in (
+ models.ContentTag.objects.filter(tagged_content__in=queryset)
+ .values(
+ "tag_name",
+ "tagged_content",
+ )
+ .order_by("tag_name")
+ ):
+ if t["tagged_content"] not in tags_map:
+ tags_map[t["tagged_content"]] = [t["tag_name"]]
+ else:
+ tags_map[t["tagged_content"]].append(t["tag_name"])
+
+ return assessmentmetadata_map, files_map, languages_map, tags_map
+
+ def consolidate(self, items, queryset):
+ output = []
+ if items:
+ (
+ assessmentmetadata,
+ files_map,
+ languages_map,
+ tags,
+ ) = self.get_related_data_maps(items, queryset)
+ for item in items:
+ item["assessmentmetadata"] = assessmentmetadata.get(item["id"])
+ item["tags"] = tags.get(item["id"], [])
+ item["files"] = files_map.get(item["id"], [])
+ thumb_file = next(
+ iter(filter(lambda f: f["thumbnail"] is True, item["files"])),
+ None,
+ )
+ if thumb_file:
+ item["thumbnail"] = thumb_file["storage_url"]
+ else:
+ item["thumbnail"] = None
+ lang_id = item.pop("lang_id")
+ item["lang"] = languages_map.get(lang_id)
+ item["is_leaf"] = item.get("kind") != content_kinds.TOPIC
+ output.append(item)
+ return output
+
+
+class OptionalContentNodePagination(ValuesViewsetCursorPagination):
+ ordering = ("lft", "id")
+ page_size_query_param = "max_results"
+
+ def paginate_queryset(self, queryset, request, view=None):
+ # Record the queryset for use in returning available filters
+ self.queryset = queryset
+ return super(OptionalContentNodePagination, self).paginate_queryset(
+ queryset, request, view=view
+ )
+
+ def get_paginated_response(self, data):
+ return Response(
+ OrderedDict(
+ [
+ ("more", self.get_more()),
+ ("results", data),
+ ("labels", get_available_metadata_labels(self.queryset)),
+ ]
+ )
+ )
+
+ def get_paginated_response_schema(self, schema):
+ return {
+ "type": "object",
+ "properties": {
+ "more": {
+ "type": "object",
+ "nullable": True,
+ "example": {
+ "cursor": "asdadshjashjadh",
+ },
+ },
+ "results": schema,
+ "labels": {
+ "type": "object",
+ "example": {"accessibility_labels": ["id1", "id2"]},
+ },
+ },
+ }
+
+
+@method_decorator(metadata_cache, name="dispatch")
+class ContentNodeViewset(BaseContentNodeMixin, ReadOnlyValuesViewset):
+ pagination_class = OptionalContentNodePagination
+
+
+# The max recursed page size should be less than 25 for a couple of reasons:
+# 1. At this size the query appears to be relatively performant, and will deliver most of the tree
+# data to the frontend in a single query.
+# 2. In the case where the tree topology means that this will not produce the full query, the limit of
+# 25 immediate children and 25 grand children means that we are at most using 1 + 25 + 25 * 25 = 651
+# SQL parameters in the query to get the nodes for serialization - this means that we should not ever
+# run into an issue where we hit a SQL parameters limit in the queries in here.
+# If we find that this page size is too high, we should lower it, but for the reasons noted above, we
+# should not raise it.
+NUM_CHILDREN = 12
+NUM_GRANDCHILDREN_PER_CHILD = 12
+
+
+class TreeQueryMixin(object):
+ def validate_and_return_params(self, request):
+ depth = request.query_params.get("depth", 2)
+ next__gt = request.query_params.get("next__gt")
+
+ try:
+ depth = int(depth)
+ if 1 > depth or depth > 2:
+ raise ValueError
+ except ValueError:
+ raise ValidationError("Depth query parameter must have the value 1 or 2")
+
+ if next__gt is not None:
+ try:
+ next__gt = int(next__gt)
+ if 1 > next__gt:
+ raise ValueError
+ except ValueError:
+ raise ValidationError(
+ "next__gt query parameter must be a positive integer if specified"
+ )
+
+ return depth, next__gt
+
+ def _get_gc_by_parent(self, child_ids):
+ # Use this to keep track of how many grand children we have accumulated per child of the parent node
+ gc_by_parent = {}
+ # Iterate through the grand children of the parent node in lft order so we follow the tree traversal order
+ for gc in (
+ self.filter_queryset(self.get_queryset())
+ .filter(parent_id__in=child_ids)
+ .values("id", "parent_id")
+ .order_by("lft")
+ ):
+ # If we have not already added a list of nodes to the gc_by_parent map, initialize it here
+ if gc["parent_id"] not in gc_by_parent:
+ gc_by_parent[gc["parent_id"]] = []
+ gc_by_parent[gc["parent_id"]].append(gc["id"])
+ return gc_by_parent
+
+ def get_grandchild_ids(self, child_ids, depth, page_size):
+ grandchild_ids = []
+ if depth == 2:
+ # Use this to keep track of how many grand children we have accumulated per child of the parent node
+ gc_by_parent = self._get_gc_by_parent(child_ids)
+ singletons = []
+ # Now loop through each of the child_ids we passed in
+ # that have any children, check if any of them have only one
+ # child, and also add up to the page size to the list of
+ # grandchild_ids.
+ for child_id in gc_by_parent:
+ gc_ids = gc_by_parent[child_id]
+ if len(gc_ids) == 1:
+ singletons.append(gc_ids[0])
+ # Only add up to the page size to the list
+ grandchild_ids.extend(gc_ids[:page_size])
+ if singletons:
+ grandchild_ids.extend(
+ self.get_grandchild_ids(singletons, depth, page_size)
+ )
+ return grandchild_ids
+
+ def get_child_ids(self, parent_id, next__gt):
+ # Get a list of child_ids of the parent node up to the pagination limit
+ child_qs = self.get_queryset().filter(parent_id=parent_id)
+ if next__gt is not None:
+ child_qs = child_qs.filter(lft__gt=next__gt)
+ return child_qs.values_list("id", flat=True).order_by("lft")[0:NUM_CHILDREN]
+
+ def get_tree_queryset(self, request, pk):
+ # Get the model for the parent node here - we do this so that we trigger a 404 immediately if the node
+ # does not exist (or exists but is not available, or is filtered).
+ parent_id = (
+ pk
+ if pk and self.filter_queryset(self.get_queryset()).filter(id=pk).exists()
+ else None
+ )
+
+ if parent_id is None:
+ raise Http404
+ depth, next__gt = self.validate_and_return_params(request)
+
+ child_ids = self.get_child_ids(parent_id, next__gt)
+
+ ancestor_ids = []
+
+ while next__gt is None and len(child_ids) == 1:
+ ancestor_ids.extend(child_ids)
+ child_ids = self.get_child_ids(child_ids[0], next__gt)
+
+ # Get a flat list of ids for grandchildren we will be returning
+ gc_ids = self.get_grandchild_ids(child_ids, depth, NUM_GRANDCHILDREN_PER_CHILD)
+ return self.filter_queryset(self.get_queryset()).filter(
+ Q(id=parent_id)
+ | Q(id__in=ancestor_ids)
+ | Q(id__in=child_ids)
+ | Q(id__in=gc_ids)
+ )
+
+
+@method_decorator(metadata_cache, name="dispatch")
+class ContentNodeTreeViewset(
+ BaseContentNodeMixin, TreeQueryMixin, BaseValuesViewset
+):
+ def retrieve(self, request, pk=None):
+ """
+ A nested, paginated representation of the children and grandchildren of a specific node
+
+ GET parameters on request can be:
+ depth - a value of either 1 or 2 indicating the depth to recurse the tree, either 1 or 2 levels
+ if this parameter is missing it will default to 2.
+ next__gt - a value to return child nodes with a lft value greater than this, if missing defaults to None
+
+ The pagination object returned for "children" will have this form:
+ results - a list of serialized children, that can also have their own nested children attribute.
+ more - a dictionary or None, if a dictionary, will have an id key that is the id of the parent object
+ for these children, and a params key that is a dictionary of the required query parameters to query more
+ children for this parent - at a minimum this will include next__gt and depth, but may also include
+ other query parameters for filtering content nodes.
+
+ The "more" property describes the "id" required to do URL reversal on this endpoint, and the params that should
+ be passed as query parameters to get the next set of results for pagination.
+
+ :param request: request object
+ :param pk: id parent node
+ :return: an object representing the parent with a pagination object as "children"
+ """
+
+ queryset = self.get_tree_queryset(request, pk)
+
+ # We explicitly order by lft here, so that the nodes are in tree traversal order, so we can iterate over them and build
+ # out our nested representation, being sure that any ancestors have already been processed.
+ nodes = self.serialize(queryset.order_by("lft"))
+
+ # The serialized parent representation is the first node in the lft order
+ parent = nodes[0]
+
+ # Use this to keep track of descendants of the parent node
+ # this will allow us to do lookups for any further descendants, in order
+ # to insert them into the "children" property
+ descendants_by_id = {}
+
+ # Iterate through all the descendants that we have serialized
+ for desc in nodes[1:]:
+ # Add them to the descendants_by_id map so that
+ # descendants can reference them later
+ descendants_by_id[desc["id"]] = desc
+ # First check to see whether it is a direct child of the
+ # parent node that we initially queried
+ if desc["parent"] == pk:
+ # The parent of this descendant is the parent node
+ # for this query
+ desc_parent = parent
+ # When we request more results for pagination, we want to return
+ # both nodes at this level, and the nodes at the lower level
+ more_depth = 2
+ # For the parent node the page size is the maximum number of children
+ # we are returning (regardless of whether they have a full representation)
+ page_size = NUM_CHILDREN
+ elif desc["parent"] in descendants_by_id:
+ # Otherwise, check to see if our descendant's parent is in the
+ # descendants_by_id map - if it failed the first condition,
+ # it really should not fail this
+ desc_parent = descendants_by_id[desc["parent"]]
+ # When we request more results for pagination, we only want to return
+ # nodes at this level, and not any of its children
+ more_depth = 1
+ # For a child node, the page size is the maximum number of grandchildren
+ # per node that we are returning if it is a recursed node
+ page_size = NUM_GRANDCHILDREN_PER_CHILD
+ else:
+ # If we get to here, we have a node that is not in the tree subsection we are
+ # trying to return, so we just ignore it. This shouldn't happen.
+ continue
+ if "children" not in desc_parent:
+ # If the parent of the descendant does not already have its `children` property
+ # initialized, do so here.
+ desc_parent["children"] = {"results": [], "more": None}
+ # Add this descendant to the results for the children pagination object
+ desc_parent["children"]["results"].append(desc)
+ # Only bother updating the URL for more if we have hit the page size limit
+ # otherwise it will just continue to be None
+ if len(desc_parent["children"]["results"]) == page_size:
+ # Any subsequent queries to get siblings of this node can restrict themselves
+ # to looking for nodes with lft greater than the rght value of this descendant
+ next__gt = desc["rght"]
+ # If the rght value of this descendant is exactly 1 less than the rght value of
+ # its parent, then there are no more children that can be queried.
+ # So only in this instance do we update the more URL
+ if desc["rght"] + 1 < desc_parent["rght"]:
+ params = request.query_params.copy()
+ params["next__gt"] = next__gt
+ params["depth"] = more_depth
+ desc_parent["children"]["more"] = {
+ "id": desc_parent["id"],
+ "params": params,
+ }
+ return Response(parent)
diff --git a/contentcuration/contentcuration/views/public.py b/contentcuration/kolibri_public/views_v1.py
similarity index 62%
rename from contentcuration/contentcuration/views/public.py
rename to contentcuration/kolibri_public/views_v1.py
index 63bc6aef5c..6b118f67d9 100644
--- a/contentcuration/contentcuration/views/public.py
+++ b/contentcuration/kolibri_public/views_v1.py
@@ -1,12 +1,15 @@
import json
from django.conf import settings
+from django.contrib.sites.models import Site
from django.db.models import Q
from django.db.models import TextField
from django.db.models import Value
from django.http import HttpResponseNotFound
from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import cache_page
+from kolibri_content.constants.schema_versions import MIN_CONTENT_SCHEMA_VERSION
+from le_utils.uuidv5 import generate_ecosystem_namespaced_uuid
from rest_framework import viewsets
from rest_framework.decorators import api_view
from rest_framework.decorators import permission_classes
@@ -91,22 +94,93 @@ def get_channel_name_by_id(request, channel_id):
return Response(channel_info)
+device_info_keys = {
+ "1": [
+ "application",
+ "kolibri_version",
+ "instance_id",
+ "device_name",
+ "operating_system",
+ ],
+ "2": [
+ "application",
+ "kolibri_version",
+ "instance_id",
+ "device_name",
+ "operating_system",
+ "subset_of_users_device",
+ ],
+ "3": [
+ "application",
+ "kolibri_version",
+ "instance_id",
+ "device_name",
+ "operating_system",
+ "subset_of_users_device",
+ "min_content_schema_version",
+ ],
+}
+
+DEVICE_INFO_VERSION = "3"
+INSTANCE_ID = None
+
+
+def get_instance_id():
+ """
+ Returns a namespaced UUID for Studio based of the domain. The current site is configured
+ through Django settings
+ :return: A uuid
+ """
+ global INSTANCE_ID
+
+ if INSTANCE_ID is None:
+ INSTANCE_ID = generate_ecosystem_namespaced_uuid(Site.objects.get_current().domain).hex
+ return INSTANCE_ID
+
+
+def get_device_info(version=DEVICE_INFO_VERSION):
+ """
+ Returns metadata information about the device
+ The default kwarg version should always be the latest
+ version of device info that this function supports.
+ We maintain historic versions for backwards compatibility
+ """
+
+ if version not in device_info_keys:
+ version = DEVICE_INFO_VERSION
+
+ all_info = {
+ "application": "studio",
+ "kolibri_version": "0.16.0",
+ "instance_id": get_instance_id(),
+ 'device_name': "Kolibri Studio",
+ "operating_system": None,
+ "subset_of_users_device": False,
+ "min_content_schema_version": MIN_CONTENT_SCHEMA_VERSION,
+ }
+
+ info = {}
+
+ # By this point, we have validated that the version is in device_info_keys
+ for key in device_info_keys.get(version, []):
+ info[key] = all_info[key]
+
+ return info
+
+
class InfoViewSet(viewsets.ViewSet):
"""
An equivalent endpoint in kolibri which allows kolibri devices to know
if this device can serve content.
- Spec doc: https://docs.google.com/document/d/1XKXQe25sf9Tht6uIXvqb3T40KeY3BLkkexcV08wvR9M/edit#
+ Ref: https://github.com/learningequality/kolibri/blob/develop/kolibri/core/public/api.py#L53
"""
permission_classes = (AllowAny, )
def list(self, request):
"""Returns metadata information about the type of device"""
+ # Default to version 1, as earlier versions of Kolibri
+ # will not have sent a "v" query param.
+ version = request.query_params.get("v", "1")
- info = {'application': 'studio',
- 'kolibri_version': None,
- 'instance_id': None,
- 'device_name': "Kolibri Studio",
- 'operating_system': None,
- }
- return Response(info)
+ return Response(get_device_info(version))
diff --git a/contentcuration/locale/ar/LC_MESSAGES/README.md b/contentcuration/locale/ar/LC_MESSAGES/README.md
new file mode 100644
index 0000000000..0f82b94d50
--- /dev/null
+++ b/contentcuration/locale/ar/LC_MESSAGES/README.md
@@ -0,0 +1 @@
+The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
diff --git a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.csv b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.csv
index d7b2742295..e01681618e 100644
--- a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.csv
+++ b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.csv
@@ -74,13 +74,13 @@
"AccountCreated.accountCreatedTitle","Account successfully created","
-- CONTEXT --
","ŲŖŁ
Ų„ŁŲ“Ų§Ų” Ų§ŁŲŲ³Ų§ŲØ ŲØŁŲ¬Ų§Ų"
-"AccountCreated.continueToSignIn","Continue to sign-in","
+"AccountCreated.backToLogin","Continue to sign-in page","
-- CONTEXT --
-","Ų§ŁŁ
ŲŖŲ§ŲØŲ¹Ų© ŁŲŖŲ³Ų¬ŁŁ Ų§ŁŲÆŲ®ŁŁ"
+","Ų§ŁŁ
ŲŖŲ§ŲØŲ¹Ų© Ų„ŁŁ ŲµŁŲŲ© ŲŖŲ³Ų¬ŁŁ Ų§ŁŲÆŲ®ŁŁ"
"AccountDeleted.accountDeletedTitle","Account successfully deleted","
-- CONTEXT --
","ŲŖŁ
ŲŲ°Ł Ų§ŁŲŲ³Ų§ŲØ ŲØŁŲ¬Ų§Ų"
-"AccountDeleted.continueToSignIn","Continue to sign-in page","
+"AccountDeleted.backToLogin","Continue to sign-in page","
-- CONTEXT --
","Ų§ŁŁ
ŲŖŲ§ŲØŲ¹Ų© Ų„ŁŁ ŲµŁŲŲ© ŲŖŲ³Ų¬ŁŁ Ų§ŁŲÆŲ®ŁŁ"
"AccountNotActivated.requestNewLink","Request a new activation link","
@@ -287,9 +287,6 @@
"BrowsingCard.coach","Resource for coaches","
-- CONTEXT --
","Ł
ŲµŲÆŲ± Ų®Ų§Ųµ ŲØŲ§ŁŁ
ŲÆŲ±ŲØŁŁ"
-"BrowsingCard.goToPluralLocationsAction","In {count, number} {count, plural, one {location} other {locations}}","
--- CONTEXT --
-","ŁŁ {count, number} {count, plural, zero {Ł
ŁŲ§ŁŲ¹} one {Ł
ŁŁŲ¹} two {Ł
ŁŁŲ¹ŁŁ} few {Ł
ŁŲ§ŁŲ¹} many {Ł
ŁŁŲ¹Ų§Ł} other {Ł
ŁŲ§ŁŲ¹}}"
"BrowsingCard.goToSingleLocationAction","Go to location","
-- CONTEXT --
","Ų§ŁŲŖŁŲ¬ŁŁ Ų„ŁŁ Ų§ŁŁ
ŁŁŲ¹"
@@ -1272,6 +1269,9 @@ A type of math category. See https://en.wikipedia.org/wiki/Algebra","Ų§ŁŲ¬ŲØŲ±"
"CommonMetadataStrings.all","All","
-- CONTEXT --
A label for everything in the group of activities.","Ų§ŁŁŁ"
+"CommonMetadataStrings.allContent","Viewed in its entirety","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners studied it all, for example they saw all pages of a document.","Ł
Ų¹Ų±ŁŲ¶ ŲØŲ§ŁŁŲ§Ł
Ł"
"CommonMetadataStrings.allLevelsBasicSkills","All levels -- basic skills","
-- CONTEXT --
Refers to a type of educational level.","ŁŲ§ŁŲ© Ų§ŁŁ
Ų³ŲŖŁŁŲ§ŲŖ -- Ų§ŁŁ
ŁŲ§Ų±Ų§ŲŖ Ų§ŁŲ£Ų³Ų§Ų³ŁŲ©"
@@ -1323,6 +1323,9 @@ Science category type. See https://en.wikipedia.org/wiki/Chemistry","Ų§ŁŁŁŁ
"CommonMetadataStrings.civicEducation","Civic education","
-- CONTEXT --
Category type. Civic education is the study of the rights and obligations of citizens in society. See https://en.wikipedia.org/wiki/Civics","Ų§ŁŲŖŲ±ŲØŁŲ© Ų§ŁŁ
ŲÆŁŁŲ©"
+"CommonMetadataStrings.completeDuration","When time spent is equal to duration","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners spent given time studying it.","Ų¹ŁŲÆŁ
Ų§ ŁŁŁŁ Ų§ŁŁŁŲŖ Ų§ŁŁ
Ų³ŲŖŲŗŲ±ŁŁ Ł
Ų³Ų§ŁŁŲ§Ł ŁŁŁ
ŲÆŲ© Ų§ŁŲ²Ł
ŁŁŲ©"
"CommonMetadataStrings.completion","Completion","CommonMetadataStrings.completion
-- CONTEXT --
@@ -1342,6 +1345,9 @@ Category type. See https://en.wikipedia.org/wiki/Everyday_life","Ų§ŁŲŁŲ§Ų© Ų§
"CommonMetadataStrings.dance","Dance","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Dance","Ų§ŁŲ±ŁŲµ"
+"CommonMetadataStrings.determinedByResource","Determined by the resource","
+-- CONTEXT --
+One of the completion criteria types. Typically used for embedded html5/h5p resources that contain their own completion criteria, for example reaching a score in an educational game.","Ł
ŁŲŲÆŁŲÆ ŲØŁŲ§Ų³Ų·Ų© Ų§ŁŁ
ŲµŲÆŲ±"
"CommonMetadataStrings.digitalLiteracy","Digital literacy","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Digital_literacy","Ų§ŁŲ„ŁŁ
Ų§Ł
Ų§ŁŲ±ŁŁ
Ł"
@@ -1363,6 +1369,9 @@ Category type. See https://en.wikipedia.org/wiki/Entrepreneurship","Ų±ŁŲ§ŲÆŲ©
"CommonMetadataStrings.environment","Environment","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Environmental_studies","Ų§ŁŲØŁŲ¦Ų©"
+"CommonMetadataStrings.exactTime","Time to complete","
+-- CONTEXT --
+One of the completion criteria types. A subset of ""When time spent is equal to duration"". For example, for an audio resource with this criteria, learnes need to hear the whole length of audio for the resource to be considered complete.","Ų§ŁŁ
ŲÆŲ© Ų§ŁŁŲ§Ų²Ł
Ų© ŁŁŲ„ŁŁ
Ų§Ł"
"CommonMetadataStrings.explore","Explore","
-- CONTEXT --
Resource and filter label for the type of learning activity. Translate as a VERB","Ų§Ų³ŲŖŁŲ“Ų§Ł"
@@ -1378,6 +1387,9 @@ Category type","ŁŁŁ
Ų¹ŁŁ
ŁŁ"
"CommonMetadataStrings.geometry","Geometry","
-- CONTEXT --
Category type.","Ų¹ŁŁ
Ų§ŁŁŁŲÆŲ³Ų©"
+"CommonMetadataStrings.goal","When goal is met","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners reached a given goal, for example 100% correct.","Ų¹ŁŲÆŁ
Ų§ ŁŲŖŁ
ŲŖŲŁŁŁ Ų§ŁŁŲÆŁ"
"CommonMetadataStrings.guides","Guides","
-- CONTEXT --
Category label in the Kolibri resources library; refers to any guide-type material for teacher professional development.","Ų§ŁŲ„Ų±Ų“Ų§ŲÆŲ§ŲŖ"
@@ -1428,6 +1440,9 @@ Refers to a level of learning. Approximately corresponds to the first half of pr
"CommonMetadataStrings.lowerSecondary","Lower secondary","
-- CONTEXT --
Refers to a level of learning. Approximately corresponds to the first half of secondary school (high school).","Ų§ŁŁ
Ų±ŲŁŲ© Ų§ŁŲ£ŁŁŁ Ł
Ł Ų§ŁŲŖŲ¹ŁŁŁ
Ų§ŁŲ«Ų§ŁŁŁ"
+"CommonMetadataStrings.masteryMofN","Goal: {m} out of {n}","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners answered m questions out of n correctly.","Ų§ŁŁŲÆŁ: {m} Ł
Ł Ų£ŲµŁ {n}"
"CommonMetadataStrings.mathematics","Mathematics","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Mathematics","Ų§ŁŲ±ŁŲ§Ų¶ŁŲ§ŲŖ"
@@ -1465,6 +1480,9 @@ Category type. See https://en.wikipedia.org/wiki/Political_science.","Ų§ŁŲ¹ŁŁ
"CommonMetadataStrings.practice","Practice","
-- CONTEXT --
Resource and filter label for the type of learning activity with questions and answers. Translate as a VERB","ŲŖŲÆŲ±ŁŲØ"
+"CommonMetadataStrings.practiceQuiz","Practice quiz","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria represents a quiz.","Ų§Ų®ŲŖŲØŲ§Ų± ŁŲµŁŲ± ŁŁŲŖŁ
Ų±Ł"
"CommonMetadataStrings.preschool","Preschool","
-- CONTEXT --
Refers to a level of education offered to children before they begin compulsory education at primary school.
@@ -1491,6 +1509,9 @@ School subject category","Ų§ŁŁŲ±Ų§Ų”Ų© ŁŲ§ŁŁŲŖŲ§ŲØŲ©"
"CommonMetadataStrings.readingComprehension","Reading comprehension","
-- CONTEXT --
Category type.","Ų§Ų³ŲŖŁŲ¹Ų§ŲØ Ų§ŁŁŲ±Ų§Ų”Ų©"
+"CommonMetadataStrings.reference","Reference material","
+-- CONTEXT --
+One of the completion criteria types. Progress made on a resource with this criteria is not tracked.","Ł
Ų§ŲÆŲ© Ł
Ų±Ų¬Ų¹ŁŲ©"
"CommonMetadataStrings.reflect","Reflect","
-- CONTEXT --
Resource and filter label for the type of learning activity. Translate as a VERB","Ų§ŁŲŖŲ£Ł
ŁŁ"
@@ -1614,27 +1635,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ų§ŁŁŲŖŲ§ŲØŲ©"
"CommunityStandardsModal.studioItem5","Hosting. Uploading your own materials (limited to materials you know are appropriately licensed to do so) from a local hard drive or other locations on the internet","
-- CONTEXT --
","Ų§ŁŲ§Ų³ŲŖŲ¶Ų§ŁŲ©. Ų±ŁŲ¹ Ų§ŁŁ
ŁŲ§ŲÆ Ų§ŁŲ®Ų§ŲµŲ© ŲØŁ (ŁŁŲŖŲµŲ± Ų°ŁŁ Ų¹ŁŁ Ų§ŁŁ
ŁŲ§ŲÆ Ų§ŁŲŖŁ ŲŖŲ¹Ų±Ł Ų£ŁŁŲ§ Ł
Ų±Ų®ŲµŲ© ŲØŲ“ŁŁ Ł
ŁŲ§Ų³ŲØ ŁŁŁŁŲ§Ł
ŲØŲ°ŁŁ) Ų³ŁŲ§Ų” Ł
Ł ŁŲ±Ųµ ŲµŁŲØ Ł
ŲŁŁ Ų£Ł Ł
ŁŲ§ŁŲ¹ Ų£Ų®Ų±Ł Ų¹ŁŁ Ų§ŁŲ„ŁŲŖŲ±ŁŲŖ"
-"CompletionOptions.allContent","Viewed in its entirety","
--- CONTEXT --
-","Ł
Ų¹Ų±ŁŲ¶ ŲØŲ§ŁŁŲ§Ł
Ł"
-"CompletionOptions.completeDuration","When time spent is equal to duration","
--- CONTEXT --
-","Ų¹ŁŲÆŁ
Ų§ ŁŁŁŁ Ų§ŁŁŁŲŖ Ų§ŁŁ
Ų³ŲŖŲŗŲ±ŁŁ Ł
Ų³Ų§ŁŁŁŲ§ ŁŁŁ
ŲÆŲ© Ų§ŁŲ²Ł
ŁŁŲ©"
-"CompletionOptions.determinedByResource","Determined by the resource","
--- CONTEXT --
-","Ł
ŁŲŲÆŁŲÆ ŲØŁŲ§Ų³Ų·Ų© Ų§ŁŁ
ŲµŲÆŲ±"
-"CompletionOptions.exactTime","Time to complete","
+"CompletionOptions.learnersCanMarkComplete","Allow learners to mark as complete","
-- CONTEXT --
-","Ų§ŁŁ
ŲÆŲ© Ų§ŁŁŲ§Ų²Ł
Ų© ŁŁŲ„ŁŁ
Ų§Ł"
-"CompletionOptions.goal","When goal is met","
--- CONTEXT --
-","Ų¹ŁŲÆŁ
Ų§ ŁŲŖŁ
ŲŖŲŁŁŁ Ų§ŁŁŲÆŁ"
-"CompletionOptions.practiceQuiz","Practice quiz","
--- CONTEXT --
-","Ų§Ų®ŲŖŲØŲ§Ų± ŁŲµŁŲ± ŁŁŲŖŁ
Ų±ŁŁ"
-"CompletionOptions.reference","Reference material","
--- CONTEXT --
-","Ł
Ų§ŲÆŲ© Ł
Ų±Ų¬Ų¹ŁŲ©"
+","Ų§ŁŲ³Ł
Ų§Ų ŁŁŁ
ŲŖŲ¹ŁŁ
ŁŁ ŲØŲŖŲŲÆŁŲÆŁ Ų¹ŁŁ Ų£ŁŁ Ł
ŁŲŖŁ
Ł"
"CompletionOptions.referenceHint","Progress will not be tracked on reference material unless learners mark it as complete","
-- CONTEXT --
","ŁŁ ŁŲŖŁ
ŲŖŲŖŲØŲ¹ Ų§ŁŲŖŁŲÆŁ
ŁŁ Ų§ŁŁ
ŁŲ§ŲÆ Ų§ŁŁ
Ų±Ų¬Ų¹ŁŲ© Ł
Ų§ ŁŁ
ŁŲµŁŁŁŲ§ Ų§ŁŁ
ŲŖŲ¹ŁŁ
ŁŁ Ų¹ŁŁ Ų£ŁŁŲ§ Ł
ŁŲŖŁ
ŁŲ©"
@@ -1926,9 +1929,27 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ų§ŁŁŲŖŲ§ŲØŲ©"
"ContentNodeChangedIcon.isUpdatedTopic","Folder has been updated since last publish","
-- CONTEXT --
","ŲŖŁ
ŲŖŲŲÆŁŲ« Ų§ŁŁ
Ų¬ŁŲÆ Ł
ŁŲ° Ų¢Ų®Ų± ŲŖŲ§Ų±ŁŲ® ŁŁŁŲ“Ų±"
+"ContentNodeCopyTaskProgress.copyErrorTopic","Some resources failed to copy","
+-- CONTEXT --
+","ŁŲ“Ł ŁŲ³Ų® ŲØŲ¹Ų¶ Ų§ŁŁ
ŲµŲ§ŲÆŲ±"
+"ContentNodeEditListItem.copiedSnackbar","Copy operation complete","
+-- CONTEXT --
+","Ų§ŁŲŖŁ
ŁŲŖ Ų¹Ł
ŁŁŲ© Ų§ŁŁŲ³Ų®"
+"ContentNodeEditListItem.creatingCopies","Copying...","
+-- CONTEXT --
+","Ų¬Ų§Ų±Ł Ų§ŁŁŲ³Ų®..."
"ContentNodeEditListItem.optionsTooltip","Options","
-- CONTEXT --
","Ų§ŁŲ®ŁŲ§Ų±Ų§ŲŖ"
+"ContentNodeEditListItem.removeNode","Remove","
+-- CONTEXT --
+","Ų„Ų²Ų§ŁŲ©"
+"ContentNodeEditListItem.retryCopy","Retry","
+-- CONTEXT --
+","Ų„Ų¹Ų§ŲÆŲ© Ų§ŁŁ
ŲŲ§ŁŁŲ©"
+"ContentNodeEditListItem.undo","Undo","
+-- CONTEXT --
+","ŲŖŲ±Ų§Ų¬Ų¹"
"ContentNodeIcon.audio","Audio","
-- CONTEXT --
","Ų§ŁŁ
ŁŁ Ų§ŁŲµŁŲŖŁ"
@@ -1956,12 +1977,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ų§ŁŁŲŖŲ§ŲØŲ©"
"ContentNodeLearningActivityIcon.multipleLearningActivities","Multiple learning activities","
-- CONTEXT --
","Ų£ŁŲ“Ų·Ų© ŲŖŲ¹ŁŁ
Ł
ŲŖŲ¹ŲÆŲÆŲ©"
-"ContentNodeLearningActivityIcon.topic","Folder","
--- CONTEXT --
-","Ł
Ų¬ŁŲÆ"
"ContentNodeListItem.coachTooltip","Resource for coaches","
-- CONTEXT --
","Ł
ŲµŲÆŲ± Ų®Ų§Ųµ ŲØŲ§ŁŁ
ŲÆŲ±ŲØ"
+"ContentNodeListItem.copyingError","Copy failed.","
+-- CONTEXT --
+","ŁŲ“Ł Ų§ŁŁŲ³Ų®."
"ContentNodeListItem.copyingTask","Copying","
-- CONTEXT --
","Ų¬Ų§Ų±Ł Ų§ŁŁŲ³Ų®"
@@ -2013,9 +2034,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ų§ŁŁŲŖŲ§ŲØŲ©"
"ContentNodeOptions.newSubtopic","New folder","
-- CONTEXT --
","Ł
Ų¬ŁŲÆ Ų¬ŲÆŁŲÆ"
-"ContentNodeOptions.remove","Remove","
+"ContentNodeOptions.remove","Delete","
-- CONTEXT --
-","Ų„Ų²Ų§ŁŲ©"
+","ŲŲ°Ł"
"ContentNodeOptions.removedFromClipboard","Deleted from clipboard","
-- CONTEXT --
","ŲŖŁ
Ų§ŁŲŲ°Ł Ł
Ł Ų§ŁŲŲ§ŁŲøŲ©"
@@ -2121,12 +2142,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ų§ŁŁŲŖŲ§ŲØŲ©"
"CountryField.noCountriesFound","No countries found","
-- CONTEXT --
","ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ ŲÆŁŁ"
-"Create.ToSCheck","I have read and agree to the terms of service","
+"Create.ToSRequiredMessage","Please accept our terms of service and policy","
-- CONTEXT --
-","ŁŁŲÆ ŁŲ±Ų£ŲŖ Ų“Ų±ŁŲ· Ų§ŁŲ®ŲÆŁ
Ų© ŁŲ£ŁŲ§ŁŁ Ų¹ŁŁŁŲ§"
-"Create.ToSRequiredMessage","Please accept our terms of service","
+","Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŁ
ŁŲ§ŁŁŲ© Ų¹ŁŁ Ų§ŁŲ³ŁŲ§Ų³Ų© ŁŲ“Ų±ŁŲ· Ų§ŁŲ®ŲÆŁ
Ų©"
+"Create.agreement","I have read and agree to terms of service and the privacy policy","
-- CONTEXT --
-","Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŁ
ŁŲ§ŁŁŲ© Ų¹ŁŁ Ų“Ų±ŁŲ· Ų§ŁŲ®ŲÆŁ
Ų©"
+","ŁŁŲÆ ŁŲ±Ų£ŲŖ Ų“Ų±ŁŲ· Ų§ŁŲ®ŲÆŁ
Ų© ŁŲ³ŁŲ§Ų³Ų© Ų§ŁŲ®ŲµŁŲµŁŲ© ŁŲ£ŁŲ§ŁŁ Ų¹ŁŁŁŲ§"
"Create.backToLoginButton","Sign in","
-- CONTEXT --
","ŲŖŲ³Ų¬Ł Ų§ŁŲÆŲ®ŁŁ"
@@ -2217,12 +2238,6 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ų§ŁŁŲŖŲ§ŲØŲ©"
"Create.personalDemoSourceOption","Personal demo","
-- CONTEXT --
","Ų¹Ų±Ų¶ Ų“Ų®ŲµŁ"
-"Create.privacyPolicyCheck","I have read and agree to the privacy policy","
--- CONTEXT --
-","ŁŁŲÆ ŁŲ±Ų£ŲŖ Ų³ŁŲ§Ų³Ų© Ų§ŁŲ®ŲµŁŲµŁŲ© ŁŲ£ŁŲ§ŁŁ Ų¹ŁŁŁŲ§"
-"Create.privacyPolicyRequiredMessage","Please accept our privacy policy","
--- CONTEXT --
-","ŁŲ±Ų¬Ł Ų§ŁŁ
ŁŲ§ŁŁŲ© Ų¹ŁŁ Ų³ŁŲ§Ų³Ų© Ų§ŁŲ®ŲµŁŲµŁŲ©"
"Create.registrationFailed","There was an error registering your account. Please try again","
-- CONTEXT --
","ŲŲµŁ Ų®Ų·Ų£ Ų£Ų«ŁŲ§Ų” ŲŖŲ³Ų¬ŁŁ ŲŲ³Ų§ŲØŁ. Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŁ
ŲŲ§ŁŁŲ© Ł
Ų¬ŲÆŲÆŲ§Ł"
@@ -2259,10 +2274,10 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ų§ŁŁŲŖŲ§ŲØŲ©"
"Create.usageLabel","How do you plan on using Kolibri Studio (check all that apply)","
-- CONTEXT --
","ŁŁŁ ŲŖŲ®Ų·Ų· ŁŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł (ŲŲÆŲÆ ŁŁ Ł
Ų§ ŁŁŲ·ŲØŁ)"
-"Create.viewPrivacyPolicyLink","View privacy policy","
+"Create.viewPrivacyPolicyLink","View Privacy Policy","
-- CONTEXT --
","Ų¹Ų±Ų¶ Ų³ŁŲ§Ų³Ų© Ų§ŁŲ®ŲµŁŲµŁŲ©"
-"Create.viewToSLink","View terms of service","
+"Create.viewToSLink","View Terms of Service","
-- CONTEXT --
","Ų¹Ų±Ų¶ Ų“Ų±ŁŲ· Ų§ŁŲ®ŲÆŁ
Ų©"
"Create.websiteSourceOption","Learning Equality website","
@@ -2413,6 +2428,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ų§ŁŁŲŖŲ§ŲØŲ©"
"Details.authorsLabel","Authors","
-- CONTEXT --
","Ų§ŁŁ
Ų¤ŁŁŁŁ"
+"Details.categoriesHeading","Categories","
+-- CONTEXT --
+","Ų§ŁŁŲ¦Ų§ŲŖ"
"Details.coachDescription","Resources for coaches are only visible to coaches in Kolibri","
-- CONTEXT --
","ŁŁ
ŁŁ Ų¹Ų±Ų¶ Ł
ŲµŲ§ŲÆŲ± Ų§ŁŁ
ŲÆŲ±ŁŁŲØŁŁ ŁŁŲ· ŁŁŁ
ŲÆŲ±ŁŁŲØŁŁ ŁŁ ŁŁŁŁŲØŲ±Ł"
@@ -2437,6 +2455,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ų§ŁŁŲŖŲ§ŲØŲ©"
"Details.languagesHeading","Languages","
-- CONTEXT --
","Ų§ŁŁŲŗŲ§ŲŖ"
+"Details.levelsHeading","Levels","
+-- CONTEXT --
+","Ų§ŁŁ
Ų³ŲŖŁŁŲ§ŲŖ"
"Details.licensesLabel","Licenses","
-- CONTEXT --
","Ų§ŁŲŖŲ±Ų§Ų®ŁŲµ"
@@ -2527,9 +2548,6 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"DetailsTabView.languageHelpText","Leave blank to use the folder language","
-- CONTEXT --
","Ų§ŲŖŲ±ŁŁ ŁŲ§Ų±ŲŗŁŲ§ Ł
Ł Ų£Ų¬Ł Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
ŁŲŗŲ© Ų§ŁŁ
Ų¬ŁŲÆ"
-"DetailsTabView.learnersCanMarkComplete","Allow learners to mark as complete","
--- CONTEXT --
-","Ų§ŁŲ³Ł
Ų§Ų ŁŁŁ
ŲŖŲ¹ŁŁ
ŁŁ ŲØŲŖŲŲÆŁŲÆŁ Ų¹ŁŁ Ų£ŁŁ Ł
ŁŲŖŁ
Ł"
"DetailsTabView.noTagsFoundText","No results found for ""{text}"". Press 'Enter' key to create a new tag","
-- CONTEXT --
","ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ ŁŲŖŲ§Ų¦Ų¬ ŲØŲŲ« ŁŁ""{text}"". Ų„Ų¶ŲŗŲ· Ų¹ŁŁ 'Ų„ŲÆŲ®Ų§Ł' ŁŲ„ŁŲ“Ų§Ų” ŲŖŲµŁŁŁ Ų¬ŲÆŁŲÆ"
@@ -3028,6 +3046,9 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"MainNavigationDrawer.administrationLink","Administration","
-- CONTEXT --
","Ų§ŲÆŲ§Ų±Ų©"
+"MainNavigationDrawer.changeLanguage","Change language","
+-- CONTEXT --
+","ŲŖŲŗŁŁŲ± Ų§ŁŁŲŗŲ©"
"MainNavigationDrawer.channelsLink","Channels","
-- CONTEXT --
","Ų§ŁŁŁŁŲ§ŲŖ Ų§ŁŲŖŲ¹ŁŁŁ
ŁŲ©"
@@ -3199,9 +3220,6 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"PoliciesModal.checkboxText","I have agreed to the above terms","
-- CONTEXT --
","ŁŁŲÆ ŁŲ§ŁŁŲŖ Ų¹ŁŁ Ų§ŁŲ“Ų±ŁŲ· Ų§ŁŁŲ§Ų±ŲÆŲ© Ų£Ų¹ŁŲ§Ł"
-"PoliciesModal.checkboxValidationErrorMessage","Field is required","
--- CONTEXT --
-","ŁŲ°Ų§ Ų§ŁŲŁŁ Ł
Ų·ŁŁŲØ"
"PoliciesModal.closeButton","Close","
-- CONTEXT --
","Ų„ŲŗŁŲ§Ł"
@@ -3232,9 +3250,12 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"ProgressModal.syncError","Last attempt to sync failed","
-- CONTEXT --
","ŁŲ“ŁŲŖ Ų¢Ų®Ų± Ł
ŲŲ§ŁŁŲ© ŁŁŁ
Ų²Ų§Ł
ŁŲ©"
-"ProgressModal.syncHeader","Syncing channel","
+"ProgressModal.syncHeader","Syncing resources","
-- CONTEXT --
-","Ł
Ų²Ų§Ł
ŁŲ© Ų§ŁŁŁŲ§Ų©"
+","Ł
Ų²Ų§Ł
ŁŲ© Ų§ŁŁ
ŲµŲ§ŲÆŲ±"
+"ProgressModal.syncedSnackbar","Resources synced","
+-- CONTEXT --
+","Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŲŖŁ ŲŖŁ
Ł
Ų²Ų§Ł
ŁŲŖŁŲ§"
"ProgressModal.unpublishedText","Unpublished","
-- CONTEXT --
","ŁŁ
ŁŲŖŁ
Ų§ŁŁŲ“Ų±"
@@ -3550,9 +3571,6 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"ResourcePanel.coachResources","Resources for coaches","
-- CONTEXT --
","Ł
ŲµŲ§ŲÆŲ± ŁŁŁ
ŲÆŲ±ŁŁŲØŁŁ"
-"ResourcePanel.completion","Completion","
--- CONTEXT --
-","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"ResourcePanel.copyrightHolder","Copyright holder","
-- CONTEXT --
","ŲµŲ§ŲŲØ ŲŁŁŁ Ų§ŁŁŲ“Ų±"
@@ -3577,27 +3595,37 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"ResourcePanel.license","License","
-- CONTEXT --
","Ų§ŁŲŖŲ±Ų®ŁŲµ"
-"ResourcePanel.masteryMofN","Goal: {m} out of {n}","
--- CONTEXT --
-","Ų§ŁŁŲÆŁ: {m} Ł
Ł Ų£ŲµŁ {n}"
"ResourcePanel.nextSteps","Next steps","
-- CONTEXT --
","Ų§ŁŲ®Ų·ŁŲ§ŲŖ Ų§ŁŲŖŲ§ŁŁŲ©"
-"ResourcePanel.noCopyrightHolderError","Missing copyright holder","
+"ResourcePanel.noCompletionCriteriaError","Completion criteria are required","ResourcePanel.noCompletionCriteriaError
+
-- CONTEXT --
-","ŲµŲ§ŲŲØ ŲŁŁŁ Ų§ŁŁŲ“Ų± ŲŗŁŲ± Ł
ŁŲ¬ŁŲÆ"
-"ResourcePanel.noFilesError","Missing files","
+Error message notification when a specific metadata is missing.","Ł
Ų¹Ų§ŁŁŲ± Ų§ŁŲ„ŁŲ¬Ų§Ų² Ł
Ų·ŁŁŲØŲ©"
+"ResourcePanel.noCopyrightHolderError","Copyright holder is required","
-- CONTEXT --
-","Ł
ŁŁŲ§ŲŖ ŁŲ§ŁŲµŲ©"
-"ResourcePanel.noLicenseDescriptionError","Missing license description","
+","Ų§Ų³Ł
ŲµŲ§ŲŲØ ŲŁŁŁ Ų§ŁŁŲ“Ų± Ł
Ų·ŁŁŲØ"
+"ResourcePanel.noDurationError","Duration is required","
-- CONTEXT --
-","ŁŲµŁ ŲŖŲ±Ų®ŁŲµ Ł
ŁŁŁŲÆ"
-"ResourcePanel.noLicenseError","Missing license","
+","Ų§ŁŁ
ŲÆŲ© Ų§ŁŲ²Ł
ŁŁŲ© Ł
Ų·ŁŁŲØŲ©"
+"ResourcePanel.noFilesError","File is required","ResourcePanel.noFilesError
+
-- CONTEXT --
-","ŲŖŲ±Ų®ŁŲµ Ł
ŁŁŁŲÆ"
-"ResourcePanel.noMasteryModelError","Missing mastery criteria","
+Error message notification when a file is missing.","Ų§ŁŁ
ŁŁ Ł
Ų·ŁŁŲØ"
+"ResourcePanel.noLearningActivityError","Learning activity is required","
-- CONTEXT --
-","Ł
Ų¹Ų§ŁŁŲ± Ų„ŲŖŁŲ§Ł Ł
ŁŁŁŲÆŲ©"
+","ŁŲ“Ų§Ų· Ų§ŁŲŖŲ¹ŁŁ
Ł
Ų·ŁŁŲØ"
+"ResourcePanel.noLicenseDescriptionError","License description is required","ResourcePanel.noLicenseDescriptionError
+
+-- CONTEXT --
+Error message notification when a specific metadata is missing.","ŁŲµŁ Ų§ŁŲŖŲ±Ų®ŁŲµ Ł
Ų·ŁŁŲØ"
+"ResourcePanel.noLicenseError","License is required","
+-- CONTEXT --
+","Ų§ŁŲŖŲ±Ų®ŁŲµ Ł
Ų·ŁŁŲØ"
+"ResourcePanel.noMasteryModelError","Mastery criteria are required","ResourcePanel.noMasteryModelError
+
+-- CONTEXT --
+Error message notification when a specific metadata is missing.","Ł
Ų¹Ų§ŁŁŲ± Ų§ŁŲ§ŲŖŁŲ§Ł Ł
Ų·ŁŁŲØŲ©"
"ResourcePanel.noQuestionsError","Exercise is empty","
-- CONTEXT --
","Ų§ŁŲŖŁ
Ų±ŁŁ ŁŲ§Ų±Ųŗ"
@@ -3925,36 +3953,42 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"SyncResourcesModal.confirmSyncModalTitle","Confirm sync","
-- CONTEXT --
","ŲŖŲ£ŁŁŲÆ Ų§ŁŁ
Ų²Ų§Ł
ŁŲ©"
+"SyncResourcesModal.confirmSyncModalWarningExplainer","Warning: this will overwrite any changes you have made to copied or imported resources.","
+-- CONTEXT --
+","ŲŖŲŲ°ŁŲ±: Ų³ŁŲ¤ŲÆŁ ŁŲ°Ų§ Ų„ŁŁ Ų§Ų³ŲŖŲØŲÆŲ§Ł Ų£Ł ŲŖŲŗŁŁŲ±Ų§ŲŖ Ų£Ų¬Ų±ŁŲŖŁŲ§ Ų¹ŁŁ Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŁ
ŁŲ³ŁŲ®Ų© Ų£Ł Ų§ŁŁ
Ų³ŲŖŁŲ±ŲÆŲ©."
"SyncResourcesModal.continueButtonLabel","Continue","
-- CONTEXT --
","Ł
ŲŖŲ§ŲØŲ¹Ų©"
"SyncResourcesModal.syncButtonLabel","Sync","
-- CONTEXT --
","Ł
Ų²Ų§Ł
ŁŲ©"
-"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints","
+"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints in exercises and quizzes","
-- CONTEXT --
-","ŲŖŲŲÆŁŲ« Ų§ŁŲ£Ų³Ų¦ŁŲ© ŁŲ§ŁŲ„Ų¬Ų§ŲØŲ§ŲŖ ŁŲ§ŁŲŖŁŁ
ŁŲŲ§ŲŖ"
+","ŲŖŲŲÆŁŲ« Ų§ŁŲ£Ų³Ų¦ŁŲ© ŁŲ§ŁŲ„Ų¬Ų§ŲØŲ§ŲŖ ŁŲ§ŁŲŖŁŁ
ŁŲŲ§ŲŖ ŁŁ Ų§ŁŲŖŁ
Ų§Ų±ŁŁ ŁŲ§ŁŲ§Ų®ŲŖŲØŲ§Ų±Ų§ŲŖ Ų§ŁŁŲµŁŲ±Ų©"
"SyncResourcesModal.syncExercisesTitle","Assessment details","
-- CONTEXT --
","ŲŖŁŲ§ŲµŁŁ Ų§ŁŲŖŁŁŁŁ
"
-"SyncResourcesModal.syncFilesExplainer","Update all file information","
+"SyncResourcesModal.syncFilesExplainer","Update all files, including: thumbnails, subtitles, and captions","
-- CONTEXT --
-","ŲŖŲŲÆŁŲ« ŁŲ§ŁŲ© Ł
Ų¹ŁŁŁ
Ų§ŲŖ Ų§ŁŁ
ŁŁ"
+","ŲŖŲŲÆŁŲ« Ų¬Ł
ŁŲ¹ Ų§ŁŁ
ŁŁŲ§ŲŖŲ ŲØŁ
Ų§ ŁŁ Ų°ŁŁ: Ų§ŁŲµŁŲ± Ų§ŁŁ
ŲµŲŗŲ± ŁŲ§ŁŲŖŲ±Ų¬Ł
Ų§ŲŖ ŁŲ¹ŁŲ§ŁŁŁ Ų§ŁŲµŁŲ±"
"SyncResourcesModal.syncFilesTitle","Files","
-- CONTEXT --
","Ų§ŁŁ
ŁŁŲ§ŲŖ"
-"SyncResourcesModal.syncModalExplainer","Sync and update your resources with their original source.","
+"SyncResourcesModal.syncModalExplainer","Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files.","
-- CONTEXT --
-","Ł
Ų²Ų§Ł
ŁŲ© ŁŲŖŲŲÆŁŲ« Ł
ŲµŲ§ŲÆŲ±Ł Ł
Ų¹ Ł
ŲµŲÆŲ±ŁŲ§ Ų§ŁŲ£ŲµŁŁ."
+","ŲŖŲ³Ł
Ų Ł
Ų²Ų§Ł
ŁŲ© Ų§ŁŁ
ŲµŲ§ŲÆŲ± ŁŁ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł ŲØŲŖŲŲÆŁŲ« Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŁ
ŁŲ³ŁŲ®Ų© Ų£Ł Ų§ŁŁ
Ų³ŲŖŁŲ±ŲÆŲ© ŁŁ ŁŲ°Ł Ų§ŁŁŁŲ§Ų©Ų Ł
Ų¹ Ų£Ł ŲŖŲŗŁŁŲ±Ų§ŲŖ ŁŲŖŁ
Ų„ŲÆŲ®Ų§ŁŁŲ§ Ų¹ŁŁ Ł
ŁŁŲ§ŲŖ Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŲ£ŲµŁŁŲ©."
+"SyncResourcesModal.syncModalSelectAttributes","Select what you would like to sync:","
+-- CONTEXT --
+","ŲŲÆŲÆ Ł
Ų§ ŲŖŲ±ŁŲÆ Ł
Ų²Ų§Ł
ŁŲŖŁ:"
"SyncResourcesModal.syncModalTitle","Sync resources","
-- CONTEXT --
","Ł
Ų²Ų§Ł
ŁŲ© Ų§ŁŁ
ŲµŲ§ŲÆŲ±"
-"SyncResourcesModal.syncTagsExplainer","Update all tags","
+"SyncResourcesModal.syncResourceDetailsExplainer","Update information about the resource: learning activity, level, requirements, category, tags, audience, and source","
-- CONTEXT --
-","ŲŖŲŲÆŁŲ« ŁŲ§ŁŲ© Ų§ŁŲŖŲµŁŁŁŲ§ŲŖ"
-"SyncResourcesModal.syncTagsTitle","Tags","
+","ŲŖŲŲÆŁŲ« Ų§ŁŁ
Ų¹ŁŁŁ
Ų§ŲŖ ŲŁŁ Ų§ŁŁ
ŲµŲÆŲ±: ŁŲ“Ų§Ų· Ų§ŁŲŖŲ¹ŁŁŁ
ŁŲ§ŁŁ
Ų³ŲŖŁŁ ŁŲ§ŁŁ
ŲŖŲ·ŁŲØŲ§ŲŖ ŁŲ§ŁŁŲ¦Ų© ŁŲ§ŁŁŲ³ŁŁ
ŁŲ§ŁŲ¬Ł
ŁŁŲ± ŁŲ§ŁŁ
ŲµŲÆŲ±"
+"SyncResourcesModal.syncResourceDetailsTitle","Resource details","
-- CONTEXT --
-","Ų§ŁŲŖŲµŁŁŁŲ§ŲŖ"
+","ŲŖŁŲ§ŲµŁŁ Ų§ŁŁ
ŲµŲÆŲ±"
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer","Update resource titles and descriptions","
-- CONTEXT --
","ŲŖŲŲÆŁŲ« Ų¹ŁŲ§ŁŁŁ ŁŲŖŁŲµŁŁŲ§ŲŖ Ų§ŁŁ
ŲµŲÆŲ±"
@@ -4403,9 +4437,10 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"TreeViewBase.noChangesText","No changes found in channel","
-- CONTEXT --
","ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ ŲŖŲŗŁŁŲ±Ų§ŲŖ ŁŁ Ų§ŁŁŁŲ§Ų©"
-"TreeViewBase.noLanguageSetError","Missing channel language","
+"TreeViewBase.noLanguageSetError","Channel language is required","TreeViewBase.noLanguageSetError
+
-- CONTEXT --
-","ŁŲŗŲ© Ų§ŁŁŁŲ§Ų© ŲŗŁŲ± Ł
ŁŲ¬ŁŲÆŲ©"
+Error message notification when a specific metadata is missing.","ŁŲŗŲ© Ų§ŁŁŁŲ§Ų© Ł
Ų·ŁŁŲØŲ©"
"TreeViewBase.openTrash","Open trash","
-- CONTEXT --
","ŁŲŖŲ Ų³ŁŲ© Ų§ŁŁ
ŁŁ
ŁŲ§ŲŖ"
@@ -4448,14 +4483,14 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
","Ł
ŁŁŲ§ŲŖ ŲŗŁŲ± Ł
ŲÆŲ¹ŁŁ
Ų©"
"Uploader.unsupportedFilesText","{count, plural,
=1 {# file will not be uploaded.}
- other {# files will not be uploaded.}}
+ other {# files will not be uploaded.}}
{extensionCount, plural,
=1 {Supported file type is}
other {Supported file types are}} {extensions}","
-- CONTEXT --
","{count, plural, zero {# Ł
ŁŁŲ§ŲŖ ŁŁ ŁŲŖŁ
ŲŖŲŁ
ŁŁŁŲ§} one {# Ł
ŁŁŲ§ŲŖ ŁŁ ŁŲŖŁ
ŲŖŲŁ
ŁŁŁŲ§.} two {# Ł
ŁŁŲ§Ł ŁŁ ŁŲŖŁ
ŲŖŲŁ
ŁŁŁŁ
Ų§.} few {# Ł
ŁŁŲ§ŲŖ ŁŁ ŁŲŖŁ
ŲŖŲŁ
ŁŁŁŲ§.} many {# Ł
ŁŁŲ§Ł ŁŁ ŁŲŖŁ
ŲŖŲŁ
ŁŁŁ.}
=1 {# Ł
ŁŁŲ§Ł ŁŁ ŁŲŖŁ
ŲŖŲŁ
ŁŁŁ.}
- other {# Ł
ŁŁŲ§ŲŖ ŁŁ ŁŲŖŁ
ŲŖŲŁ
ŁŁŁŲ§.}}
+ other {# Ł
ŁŁŲ§ŲŖ ŁŁ ŁŲŖŁ
ŲŖŲŁ
ŁŁŁŲ§.}}
{extensionCount, plural,
=1 {ŁŁŲ¹ Ų§ŁŁ
ŁŁ Ų§ŁŁ
ŲÆŲ¹ŁŁ
ŁŁ}
other {Ų£ŁŁŲ§Ų¹ Ų§ŁŁ
ŁŁŲ§ŲŖ Ų§ŁŁ
ŲÆŲ¹ŁŁ
Ų© ŁŁ}} {extensions}"
@@ -4474,12 +4509,9 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"UsingStudio.bestPractice2","It is preferable to create multiple small channels rather than one giant channel with many layers of folders.","
-- CONTEXT --
","ŁŁŁŲ¶ŁŁ Ų„ŁŲ“Ų§Ų” ŁŁŁŲ§ŲŖ ŲµŲŗŁŲ±Ų© Ł
ŲŖŲ¹ŲÆŲÆŲ© Ų¹ŁŲ¶Ų§Ł Ų¹Ł Ų„ŁŲ“Ų§Ų” ŁŁŲ§Ų© ŁŲØŁŲ±Ų© ŁŲ§ŲŲÆŲ© ŲØŁ
Ų¬ŁŲÆŲ§ŲŖ Ł
ŲŖŲ¹ŲÆŲÆŲ©."
-"UsingStudio.bestPractice3","Reload the page often to ensure your work is saved to the server and no network errors have occurred. Use CTRL+R on Linux/Windows or ā+R on Mac.","
--- CONTEXT --
-","ŁŁ
ŲØŲ„Ų¹Ų§ŲÆŲ© ŲŖŲŁ
ŁŁ Ų§ŁŲµŁŲŲ© ŲØŲ“ŁŁ ŲÆŁŲ±Ł ŁŁŲŖŲ£ŁŲÆ Ł
Ł ŲŁŲø Ų¹Ł
ŁŁ Ų¹ŁŁ Ų§ŁŲ®Ų§ŲÆŁ
ŁŲ¹ŲÆŁ
ŲŲÆŁŲ« Ų£Ų®Ų·Ų§Ų” ŁŁ Ų§ŁŲ“ŲØŁŲ©. Ų§Ų³ŲŖŲ®ŲÆŁ
CTRL + R ŁŁ Ų£ŁŲøŁ
Ų© ŲŖŲ“ŲŗŁŁ Linux / Windows Ų£Ł ā + R Ų¹ŁŁ ŁŲøŲ§Ł
Mac."
-"UsingStudio.bestPractice4","Avoid simultaneous edits on the same channel. Channels should not be edited by multiple users at the same time or by the same user in multiple browser windows.","
+"UsingStudio.bestPractice3","Reload the page to confirm your work has been saved to the server. Use CTRL+R on Linux/Windows or ā+R on Mac.","
-- CONTEXT --
-","ŲŖŲ¬ŁŲØ Ų§ŁŁŁŲ§Ł
ŲØŲ¹Ł
ŁŁŲ§ŲŖ ŲŖŲŲ±ŁŲ± Ł
ŲŖŲ²Ų§Ł
ŁŲ© ŁŁ ŁŁŲ³ Ų§ŁŁŁŲ§Ų©. ŁŲ§ ŁŲ¬ŁŲ² Ų£Ł ŁŲŖŁ
ŲŖŲŲ±ŁŲ± Ų§ŁŁŁŁŲ§ŲŖ ŲØŁŲ§Ų³Ų·Ų© Ų¹ŲÆŲ© Ł
Ų³ŲŖŲ®ŲÆŁ
ŁŁ ŁŁ ŁŁŲ³ Ų§ŁŁŁŲŖ Ų£Ł ŲØŁŲ§Ų³Ų·Ų© ŁŁŲ³ Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
ŁŁ Ų¹ŲÆŲ© ŁŁŲ§ŁŲ° ŲŖŲµŁŲ."
+","Ų£Ų¹ŲÆ ŲŖŲŁ
ŁŁ Ų§ŁŲµŁŲŲ© ŁŲŖŲ£ŁŁŲÆ Ų£Ł Ų¹Ł
ŁŁ ŲŖŁ
ŲŁŲøŁ Ų„ŁŁ Ų§ŁŲ®Ų§ŲÆŁ
. Ų§Ų³ŲŖŲ®ŲÆŁ
CTRL+R Ų¹ŁŁ ŁŲøŲ§Ł
Linux/Windows Ų£Ł ā+R Ų¹ŁŁ ŁŲøŲ§Ł
Mac."
"UsingStudio.bestPractice5","It is possible that you will encounter timeout errors in your browser when performing operations like import and sync, on large channels. Don't be alarmed by this error message and do not repeat the same operation again right away. It doesn't mean the operation has failed- Kolibri Studio is still working in the background. Wait a few minutes and reload the page before continuing your edits.","
-- CONTEXT --
","ŁŲÆ ŲŖŁŲ§Ų¬Ł Ų£Ų®Ų·Ų§Ų” Ł
Ų±ŲŖŲØŲ·Ų© ŲØŲ§ŁŲŖŁŲ§Ų” Ų§ŁŁ
ŁŁŲ© Ų¹ŁŁ Ł
ŲŖŲµŁŲŁ Ų¹ŁŲÆ Ų§ŁŁŁŲ§Ł
ŲØŲ¹Ł
ŁŁŲ§ŲŖ Ł
Ų«Ł Ų§ŁŲ§Ų³ŲŖŁŲ±Ų§ŲÆ ŁŲ§ŁŁ
Ų²Ų§Ł
ŁŲ© Ų¹ŁŁ Ų§ŁŁŁŁŲ§ŲŖ Ų§ŁŁŲØŁŲ±Ų©. ŁŲ§ ŲŖŲŖŁŲ§Ų¬Ų£ ŲØŲ±Ų³Ų§ŁŲ© Ų§ŁŲ®Ų·Ų£ ŲŖŁŁ ŁŁŲ§ ŲŖŁŲ±Ų± Ų§ŁŲ¹Ł
ŁŁŲ© Ł
Ų¬ŲÆŲÆŲ§Ł Ų¹ŁŁ Ų§ŁŁŁŲ±. ŁŲ°Ł Ų§ŁŲ±Ų³Ų§ŁŲ© ŁŲ§ ŲŖŲ¹ŁŁ Ų£Ł Ų§ŁŲ¹Ł
ŁŁŲ© ŁŲ“ŁŲŖ- ŁŲ§ ŁŲ²Ų§Ł Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł ŁŲ¹Ł
Ł ŁŁ Ų§ŁŲ®ŁŁŁŲ©. Ų§ŁŲŖŲøŲ± ŁŲØŲ¶Ų¹ ŲÆŁŲ§Ų¦Ł Ų«Ł
Ų£Ų¹ŲÆ ŲŖŲŁ
ŁŁ Ų§ŁŲµŁŲŲ© ŁŲØŁ Ł
ŲŖŲ§ŲØŲ¹Ų© Ų¹Ł
ŁŁŲ§ŲŖ Ų§ŁŲŖŲŲ±ŁŲ±."
@@ -4489,9 +4521,6 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"UsingStudio.bestPractice7","PUBLISH periodically and import your channel into Kolibri to preview the content and obtain a local backup copy of your channel.","
-- CONTEXT --
","ŁŁ
ŲØŲ§ŁŁŲ“Ų± ŲØŲ“ŁŁ ŲÆŁŲ±Ł ŁŲ§Ų³ŲŖŁŲ±ŲÆ ŁŁŲ§ŲŖŁ Ų„ŁŁ ŁŁŁŁŲØŲ±Ł Ł
Ł Ų£Ų¬Ł Ł
Ų¹Ų§ŁŁŲ© Ų§ŁŁ
ŲŲŖŁŁ ŁŲ§ŁŲŲµŁŁ Ų¹ŁŁ ŁŲ³Ų®Ų© Ų§ŲŲŖŁŲ§Ų·ŁŲ© Ł
ŲŁŁŲ© Ł
Ł ŁŁŲ§ŲŖŁ."
-"UsingStudio.bestPractice8","Do not edit the channel after you click PUBLISH. Wait for the notification email before resuming editing operations.","
--- CONTEXT --
-","ŁŲ§ ŲŖŁŁ
ŲØŲ¹Ł
ŁŁŲ§ŲŖ ŲŖŲŲ±ŁŲ± ŁŁ Ų§ŁŁŁŲ§Ų© ŲØŲ¹ŲÆ Ų§ŁŁŁŲ± Ų¹ŁŁ Ų®ŁŲ§Ų± ""ŁŲ“Ų±"". Ų§ŁŲŖŲøŲ± Ų„Ų“Ų¹Ų§Ų± Ų§ŁŲØŲ±ŁŲÆ Ų§ŁŲ„ŁŁŲŖŲ±ŁŁŁ ŁŲØŁ Ł
ŲŖŲ§ŲØŲ¹Ų© Ų¹Ł
ŁŁŲ§ŲŖ Ų§ŁŲŖŲŲ±ŁŲ±."
"UsingStudio.bestPractice9","Report issues as you encounter them.","
-- CONTEXT --
","ŁŁ
ŲØŲ§ŁŲ„ŲØŁŲ§Ųŗ Ų¹Ł Ų§ŁŁ
Ų“Ų§ŁŁ Ų¹ŁŲÆŁ
Ų§ ŲŖŁŲ§Ų¬ŁŁŲ§."
@@ -4501,18 +4530,13 @@ Heading for the section in the resource editing window","Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł"
"UsingStudio.communityStandardsLink","Community standards","
-- CONTEXT --
","Ł
Ų¹Ų§ŁŁŲ± Ų§ŁŁ
Ų¬ŲŖŁ
Ų¹"
-"UsingStudio.issue1","Two users have reported isolated incidents where content they imported from another channel disappeared, leaving only empty folders and subfolders. In one report, the content later re-appeared. They did not experience these problems consistently, and the incidents may possibly involve issues with a slow or unstable internet connection. If you run into this issue, please contact us as soon as possible and let us know as much information as you can remember.","
--- CONTEXT --
-","ŁŁŲÆ Ų£ŲØŁŲŗ Ų§Ų«ŁŲ§Ł Ł
Ł Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
ŁŁ Ų¹Ł ŲŁŲ§ŲÆŲ« Ł
ŁŁŲµŁŲ© ŲŁŲ« Ų§Ų®ŲŖŁŁ Ų§ŁŁ
ŲŲŖŁŁ Ų§ŁŲ°Ł ŁŲ§Ł
Ų§ ŲØŲ§Ų³ŲŖŁŲ±Ų§ŲÆŁ Ł
Ł ŁŁŲ§Ų© Ų£Ų®Ų±ŁŲ ŁŁŁ
ŁŲØŁ Ų³ŁŁ Ų§ŁŁ
Ų¬ŁŲÆŲ§ŲŖ Ų§ŁŲ±Ų¦ŁŲ³ŁŲ© ŁŲ§ŁŁ
Ų¬ŁŲÆŲ§ŲŖ Ų§ŁŁŲ±Ų¹ŁŲ© Ų§ŁŁŲ§Ų±ŲŗŲ©. ŁŲ£ŁŲ“ŁŲ± ŁŁ Ų£ŲŲÆ Ų§ŁŲŖŁŲ§Ų±ŁŲ± Ų£Ł Ų§ŁŁ
ŲŲŖŁŁ ŁŲÆ ŲøŁŲ± ŁŁ Ł
Ų§ ŲØŲ¹ŲÆ. ŁŁ
ŁŁŲ§Ų¬Ł Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
Ų§Ł ŁŲ°Ł Ų§ŁŁ
Ų“Ų§ŁŁ ŲØŲ“ŁŁ Ł
Ų³ŲŖŁ
Ų±Ų ŁŁŲÆ ŲŖŲŖŲ¶Ł
Ł ŲŖŁŁ Ų§ŁŲŁŲ§ŲÆŲ« Ł
Ų“Ų§ŁŁ Ł
Ų±ŲŖŲØŲ·Ų© ŲØŲ§ŲŖŲµŲ§Ł Ų§ŁŲ„ŁŲŖŲ±ŁŲŖ Ų§ŁŲØŲ·ŁŲ” Ų£Ł ŲŗŁŲ± Ų§ŁŁ
Ų³ŲŖŁŲ±. Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŲ§ŲŖŲµŲ§Ł ŲØŁŲ§ ŲØŲ£Ų³Ų±Ų¹ ŁŁŲŖ Ł
Ł
ŁŁ ŁŁ ŲŲ§Ł ŲµŲ§ŲÆŁŲŖ ŁŲ°Ł Ų§ŁŁ
Ų“ŁŁŲ© Ł
Ł Ų£Ų¬Ł Ų„Ų·ŁŲ§Ų¹ŁŲ§ Ų¹ŁŁ ŁŁ Ł
Ų§ ŁŁ
ŁŁŁ ŲŖŲ°ŁŲ±Ł Ł
Ł Ł
Ų¹ŁŁŁ
Ų§ŲŖ."
-"UsingStudio.issue2","Some operations in Studio are currently very slow, and so it may appear that the change you attempted to make timed out or did not take effect. In many cases, the change is still being processed and will appear once it is complete. If, after 5-10 minutes, the change still has not taken effect even after a browser refresh, please file an issue. We are working on solutions to these issues.","
+"UsingStudio.issue1","There have been reports where users have observed the disappearance of changes they've recently made to their channels. The issue seems related to opening multiple tabs of Kolibri Studio, and eventually signing out. We advise that you disable any āMemory Saver/Sleepingā tab browser feature for Kolibri Studio, and reload each tab before signing out. We're actively investigating this issue, so if you run into it, please contact us with as much information as possible.","UsingStudio.issue1
+
-- CONTEXT --
-","ŲØŲ¹Ų¶ Ų§ŁŲ¹Ł
ŁŁŲ§ŲŖ ŁŁ Ų§ŁŲ§Ų³ŲŖŁŲÆŁŁ ŲØŲ·ŁŲ¦Ų© Ų¬ŲÆŁŲ§ ŁŁ Ų§ŁŁŁŲŖ Ų§ŁŲŲ§ŁŁŲ ŁŁŁŲ°Ų§ ŁŲÆ ŁŲØŲÆŁ Ų£Ł Ų§ŁŲŖŲŗŁŁŲ± Ų§ŁŲ°Ł ŲŲ§ŁŁŲŖ Ų§ŁŁŁŲ§Ł
ŲØŁ ŁŲÆ Ų§ŁŲŖŁŁ Ų£Ł ŁŁ
ŁŲŖŁ
ŲØŁŲ¬Ų§Ų. ŁŁ ŁŲ«ŁŲ± Ł
Ł Ų§ŁŲŲ§ŁŲ§ŲŖŲ ŁŲØŁŁ Ų§ŁŲŖŲŗŁŁŲ± ŁŁŲÆ Ų§ŁŁ
Ų¹Ų§ŁŲ¬Ų© ŁŲ³ŁŲøŁŲ± ŲŲ§Ł Ų§ŁŲŖŁ
Ų§ŁŁ. ŁŁ ŲŲ§Ł ŁŁ
ŁŁŲŖŁ
Ł Ų§ŁŲŖŲŗŁŁŲ± Ų®ŁŲ§Ł Ł„-Ł”Ł ŲÆŁŲ§Ų¦Ł ŲŲŖŁ ŲØŲ¹ŲÆ ŲŖŲŲÆŁŲ« Ų§ŁŁ
ŲŖŲµŁŲŲ Ų§ŁŲ±Ų¬Ų§Ų” Ų„Ų¹ŁŲ§Ł
ŁŲ§ ŲØŲ§ŁŁ
Ų“ŁŁŲ©. ŁŲŁ ŁŲ¹Ł
Ł Ų¹ŁŁ Ų„ŁŲ¬Ų§ŲÆ ŲŁŁŁ ŁŁŲ°Ł Ų§ŁŁ
Ų“Ų§ŁŁ."
+A description of an issue that has been reported by users - the recommendation is to disable any memory saver feature in the browser while they are using Kolibri Studio.","ŁŁŲ§Ł ŲŖŁŲ§Ų±ŁŲ± ŲŖŁŁŲÆ ŲØŲ£Ł ŲØŲ¹Ų¶ Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
ŁŁ ŁŲ§ŲŲøŁŲ§ Ų§Ų®ŲŖŁŲ§Ų” Ų§ŁŲŖŲŗŁŁŲ±Ų§ŲŖ Ų§ŁŲŖŁ Ų£Ų¬Ų±ŁŁŲ§ Ł
Ų¤Ų®Ų±Ų§Ł Ų¹ŁŁ ŁŁŁŲ§ŲŖŁŁ
. ŁŲØŲÆŁ Ų£Ł Ų§ŁŁ
Ų“ŁŁŲ© ŲŖŲŖŲ¹ŁŁ ŲØŁŲŖŲ Ų¹ŁŲ§Ł
Ų§ŲŖ ŲŖŲØŁŁŲØ Ł
ŲŖŲ¹ŲÆŲÆŲ© Ł
Ł Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł ŁŲŖŲ³Ų¬ŁŁ Ų§ŁŲ®Ų±ŁŲ¬ ŁŁ ŁŁŲ§ŁŲ© Ų§ŁŁ
Ų·Ų§Ł. ŁŁŲµŲ ŲØŲŖŲ¹Ų·ŁŁ Ų£Ł Ł
ŁŲ²Ų© ""ŲŁŲø Ų§ŁŲ°Ų§ŁŲ±Ų©/Ų§ŁŁŁŁ
"" ŁŁ Ų§ŁŁ
ŲŖŲµŁŲ Ų¹ŁŲÆ Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±ŁŲ ŁŲ„Ų¹Ų§ŲÆŲ© ŲŖŲŁ
ŁŁ ŁŁ Ų¹ŁŲ§Ł
Ų© ŲŖŲØŁŁŲØ ŁŲØŁ ŲŖŲ³Ų¬ŁŁ Ų§ŁŲ®Ų±ŁŲ¬. ŁŲŁ ŁŲØŲŲ« ŲØŲ“ŁŁŁ Ł
ŁŲ«Ł ŁŁ ŁŲ°Ł Ų§ŁŁ
Ų“ŁŁŲ©Ų ŁŲ°Ų§ Ų„Ų°Ų§ ŁŲ§Ų¬ŁŲŖŁŲ§Ų ŁŁŲ±Ų¬Ł Ų§ŁŲ§ŲŖŲµŲ§Ł ŲØŁŲ§ ŁŲŖŲ²ŁŁŲÆŁŲ§ ŲØŲ£ŁŲØŲ± ŁŲÆŲ±Ł Ł
Ł
ŁŁ Ł
Ł Ų§ŁŁ
Ų¹ŁŁŁ
Ų§ŲŖ."
"UsingStudio.issueLink1","Reports of disappearing content","
-- CONTEXT --
","ŲŖŁŲ§Ų±ŁŲ± Ł
ŲŖŲ¹ŁŁŲ© ŲØŲ§Ų®ŲŖŁŲ§Ų” Ų§ŁŁ
ŲŲŖŁŁ"
-"UsingStudio.issueLink2","Slow performance can lead to unexpected errors in the interface","
--- CONTEXT --
-","Ų§ŁŲ£ŲÆŲ§Ų” Ų§ŁŲØŲ·ŁŲ” ŁŁ
ŁŁ Ų£Ł ŁŲ¤ŲÆŁ Ų„ŁŁ Ų£Ų®Ų·Ų§Ų” ŲŗŁŲ± Ł
ŲŖŁŁŲ¹Ų© ŁŁ ŁŲ§Ų¬ŁŲ© Ų§ŁŲ¹Ų±Ų¶"
"UsingStudio.issuesPageLink","View all issues","
-- CONTEXT --
","Ų¹Ų±Ų¶ Ų¬Ł
ŁŲ¹ Ų§ŁŁ
Ų“ŁŁŲ§ŲŖ"
diff --git a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json
index be4fa9c946..f8a49cacdd 100644
--- a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json
@@ -24,9 +24,9 @@
"Account.unableToDeleteAdminAccount": "ŁŲ§ ŁŁ
ŁŁ ŲŲ°Ł ŲŲ³Ų§ŲØ Ų§ŁŁ
Ų³Ų¤ŁŁ",
"Account.usernameLabel": "Ų§Ų³Ł
Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
",
"AccountCreated.accountCreatedTitle": "ŲŖŁ
Ų„ŁŲ“Ų§Ų” Ų§ŁŲŲ³Ų§ŲØ ŲØŁŲ¬Ų§Ų",
- "AccountCreated.continueToSignIn": "Ų§ŁŁ
ŲŖŲ§ŲØŲ¹Ų© ŁŲŖŲ³Ų¬ŁŁ Ų§ŁŲÆŲ®ŁŁ",
+ "AccountCreated.backToLogin": "Ų§ŁŁ
ŲŖŲ§ŲØŲ¹Ų© Ų„ŁŁ ŲµŁŲŲ© ŲŖŲ³Ų¬ŁŁ Ų§ŁŲÆŲ®ŁŁ",
"AccountDeleted.accountDeletedTitle": "ŲŖŁ
ŲŲ°Ł Ų§ŁŲŲ³Ų§ŲØ ŲØŁŲ¬Ų§Ų",
- "AccountDeleted.continueToSignIn": "Ų§ŁŁ
ŲŖŲ§ŲØŲ¹Ų© Ų„ŁŁ ŲµŁŲŲ© ŲŖŲ³Ų¬ŁŁ Ų§ŁŲÆŲ®ŁŁ",
+ "AccountDeleted.backToLogin": "Ų§ŁŁ
ŲŖŲ§ŲØŲ¹Ų© Ų„ŁŁ ŲµŁŲŲ© ŲŖŲ³Ų¬ŁŁ Ų§ŁŲÆŲ®ŁŁ",
"AccountNotActivated.requestNewLink": "ŁŁ
ŲØŲ·ŁŲØ Ų±Ų§ŲØŲ· ŲŖŁŲ¹ŁŁ Ų¬ŲÆŁŲÆ",
"AccountNotActivated.text": "ŁŲ±Ų¬Ł Ų§ŁŲŖŲŁŁ Ł
Ł Ų±Ų§ŲØŲ· Ų§ŁŲŖŁŲ¹ŁŁ Ų¹ŁŁ ŲØŲ±ŁŲÆŁ Ų§ŁŲ„ŁŁŲŖŲ±ŁŁŁ Ų£Ł Ų·ŁŲØ Ų±Ų§ŲØŲ· Ų¬ŲÆŁŲÆ.",
"AccountNotActivated.title": "ŁŁ
ŁŲŖŁ
ŲŖŁŲ¹ŁŁ Ų§ŁŲŲ³Ų§ŲØ",
@@ -95,7 +95,6 @@
"AssessmentTab.incompleteItemsCountMessage": "{invalidItemsCount} {invalidItemsCount, plural, zero {Ų£Ų³Ų¦ŁŲ©} one {Ų³Ų¤Ų§Ł} two {Ų³Ų¤Ų§ŁŲ§Ł} few {Ų£Ų³Ų¦ŁŲ©} many {Ų³Ų¤Ų§ŁŲ§Ł} other {Ų£Ų³Ų¦ŁŲ©}} ŲŗŁŲ± Ł
ŁŲŖŁ
ŁŲ©",
"BrowsingCard.addToClipboardAction": "ŁŲ³Ų® Ų„ŁŁ Ų§ŁŲŲ§ŁŲøŲ©",
"BrowsingCard.coach": "Ł
ŲµŲÆŲ± Ų®Ų§Ųµ ŲØŲ§ŁŁ
ŲÆŲ±ŲØŁŁ",
- "BrowsingCard.goToPluralLocationsAction": "ŁŁ {count, number} {count, plural, zero {Ł
ŁŲ§ŁŲ¹} one {Ł
ŁŁŲ¹} two {Ł
ŁŁŲ¹ŁŁ} few {Ł
ŁŲ§ŁŲ¹} many {Ł
ŁŁŲ¹Ų§Ł} other {Ł
ŁŲ§ŁŲ¹}}",
"BrowsingCard.goToSingleLocationAction": "Ų§ŁŲŖŁŲ¬ŁŁ Ų„ŁŁ Ų§ŁŁ
ŁŁŲ¹",
"BrowsingCard.hasCoachTooltip": "{value, number, integer} {value, plural, zero {Ł
ŁŲ§ŁŲ¹} one {Ł
ŲµŲÆŲ± ŁŁŁ
ŲÆŲ±ŲØŁŁ} two {Ł
ŲµŲÆŲ±ŁŁŁ ŁŁŁ
ŲÆŲ±ŲØŁŁ} few {Ł
ŲµŲ§ŲÆŲ± ŁŁŁ
ŲÆŲ±ŲØŁŁ} many {Ł
ŲµŲÆŲ±Ų§Ł ŁŁŁ
ŲÆŲ±ŲØŁŁ} other {Ł
ŲµŲ§ŲÆŲ± ŁŁŁ
ŲÆŲ±ŲØŁŁ}}",
"BrowsingCard.previewAction": "Ų¹Ų±Ų¶ Ų§ŁŲŖŁŲ§ŲµŁŁ",
@@ -414,6 +413,7 @@
"CommonMetadataStrings.accessibility": "Ų„Ł
ŁŲ§ŁŁŲ© Ų§ŁŁŲµŁŁ",
"CommonMetadataStrings.algebra": "Ų§ŁŲ¬ŲØŲ±",
"CommonMetadataStrings.all": "Ų§ŁŁŁ",
+ "CommonMetadataStrings.allContent": "Ł
Ų¹Ų±ŁŲ¶ ŲØŲ§ŁŁŲ§Ł
Ł",
"CommonMetadataStrings.allLevelsBasicSkills": "ŁŲ§ŁŲ© Ų§ŁŁ
Ų³ŲŖŁŁŲ§ŲŖ -- Ų§ŁŁ
ŁŲ§Ų±Ų§ŲŖ Ų§ŁŲ£Ų³Ų§Ų³ŁŲ©",
"CommonMetadataStrings.allLevelsWorkSkills": "ŁŲ§ŁŲ© Ų§ŁŁ
Ų³ŲŖŁŁŲ§ŲŖ -- Ł
ŁŲ§Ų±Ų§ŲŖ Ų§ŁŲ¹Ł
Ł",
"CommonMetadataStrings.altText": "ŁŲŖŲ¶Ł
Ł Ų£ŁŲµŲ§ŁŁŲ§ ŁŲµŁŲ© ŲØŲÆŁŁŲ© ŁŁŲµŁŲ±",
@@ -430,12 +430,14 @@
"CommonMetadataStrings.category": "Ų§ŁŁŲ¦Ų©",
"CommonMetadataStrings.chemistry": "Ų§ŁŁŁŁ
ŁŲ§Ų”",
"CommonMetadataStrings.civicEducation": "Ų§ŁŲŖŲ±ŲØŁŲ© Ų§ŁŁ
ŲÆŁŁŲ©",
+ "CommonMetadataStrings.completeDuration": "Ų¹ŁŲÆŁ
Ų§ ŁŁŁŁ Ų§ŁŁŁŲŖ Ų§ŁŁ
Ų³ŲŖŲŗŲ±ŁŁ Ł
Ų³Ų§ŁŁŲ§Ł ŁŁŁ
ŲÆŲ© Ų§ŁŲ²Ł
ŁŁŲ©",
"CommonMetadataStrings.completion": "Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł",
"CommonMetadataStrings.computerScience": "Ų¹ŁŁ
Ų§ŁŲŲ§Ų³ŁŲØ",
"CommonMetadataStrings.create": "Ų„ŁŲ“Ų§Ų”",
"CommonMetadataStrings.currentEvents": "Ų§ŁŁŲ¹Ų§ŁŁŲ§ŲŖ Ų§ŁŲŲ§ŁŁŲ©",
"CommonMetadataStrings.dailyLife": "Ų§ŁŲŁŲ§Ų© Ų§ŁŁŁŁ
ŁŲ©",
"CommonMetadataStrings.dance": "Ų§ŁŲ±ŁŲµ",
+ "CommonMetadataStrings.determinedByResource": "Ł
ŁŲŲÆŁŲÆ ŲØŁŲ§Ų³Ų·Ų© Ų§ŁŁ
ŲµŲÆŲ±",
"CommonMetadataStrings.digitalLiteracy": "Ų§ŁŲ„ŁŁ
Ų§Ł
Ų§ŁŲ±ŁŁ
Ł",
"CommonMetadataStrings.diversity": "Ų§ŁŲŖŁŁŲ¹",
"CommonMetadataStrings.drama": "Ų§ŁŲÆŲ±Ų§Ł
Ų§",
@@ -443,11 +445,13 @@
"CommonMetadataStrings.earthScience": "Ų¹ŁŁ
Ų§ŁŲ£Ų±Ų¶",
"CommonMetadataStrings.entrepreneurship": "Ų±ŁŲ§ŲÆŲ© Ų§ŁŲ£Ų¹Ł
Ų§Ł",
"CommonMetadataStrings.environment": "Ų§ŁŲØŁŲ¦Ų©",
+ "CommonMetadataStrings.exactTime": "Ų§ŁŁ
ŲÆŲ© Ų§ŁŁŲ§Ų²Ł
Ų© ŁŁŲ„ŁŁ
Ų§Ł",
"CommonMetadataStrings.explore": "Ų§Ų³ŲŖŁŲ“Ų§Ł",
"CommonMetadataStrings.financialLiteracy": "Ų§ŁŲ„ŁŁ
Ų§Ł
Ų§ŁŁ
Ų§ŁŁ",
"CommonMetadataStrings.forBeginners": "ŁŁŁ
ŲØŲŖŲÆŲ¦ŁŁ",
"CommonMetadataStrings.forTeachers": "ŁŁŁ
Ų¹ŁŁ
ŁŁ",
"CommonMetadataStrings.geometry": "Ų¹ŁŁ
Ų§ŁŁŁŲÆŲ³Ų©",
+ "CommonMetadataStrings.goal": "Ų¹ŁŲÆŁ
Ų§ ŁŲŖŁ
ŲŖŲŁŁŁ Ų§ŁŁŲÆŁ",
"CommonMetadataStrings.guides": "Ų§ŁŲ„Ų±Ų“Ų§ŲÆŲ§ŲŖ",
"CommonMetadataStrings.highContrast": "ŁŲŖŲ¶Ł
Ł ŁŲµŁŲ§ ŲØŲ³Ų·ŁŲ¹Ł Ų¹Ų§ŁŁ ŁŁŁ
ŲŖŲ¹ŁŁ
ŁŁ Ų§ŁŲ°ŁŁ ŁŲ¹Ų§ŁŁŁ Ł
Ł Ų¶Ų¹Ł Ų§ŁŲ±Ų¤ŁŲ©",
"CommonMetadataStrings.history": "Ų§ŁŲ³Ų¬Ł",
@@ -464,6 +468,7 @@
"CommonMetadataStrings.longActivity": "ŁŲ“Ų§Ų· Ų·ŁŁŁ",
"CommonMetadataStrings.lowerPrimary": "Ų§ŁŁ
Ų±ŲŁŲ© Ų§ŁŲ£ŁŁŁ Ł
Ł Ų§ŁŲŖŲ¹ŁŁŁ
Ų§ŁŲ§ŲØŲŖŲÆŲ§Ų¦Ł",
"CommonMetadataStrings.lowerSecondary": "Ų§ŁŁ
Ų±ŲŁŲ© Ų§ŁŲ£ŁŁŁ Ł
Ł Ų§ŁŲŖŲ¹ŁŁŁ
Ų§ŁŲ«Ų§ŁŁŁ",
+ "CommonMetadataStrings.masteryMofN": "Ų§ŁŁŲÆŁ: {m} Ł
Ł Ų£ŲµŁ {n}",
"CommonMetadataStrings.mathematics": "Ų§ŁŲ±ŁŲ§Ų¶ŁŲ§ŲŖ",
"CommonMetadataStrings.mechanicalEngineering": "Ų§ŁŁŁŲÆŲ³Ų© Ų§ŁŁ
ŁŁŲ§ŁŁŁŁŲ©",
"CommonMetadataStrings.mediaLiteracy": "Ų§ŁŲŖŲ«ŁŁŁ Ų§ŁŲ„Ų¹ŁŲ§Ł
Ł",
@@ -476,6 +481,7 @@
"CommonMetadataStrings.physics": "Ų§ŁŁŁŲ²ŁŲ§Ų”",
"CommonMetadataStrings.politicalScience": "Ų§ŁŲ¹ŁŁŁ
Ų§ŁŲ³ŁŲ§Ų³ŁŲ©",
"CommonMetadataStrings.practice": "ŲŖŲÆŲ±ŁŲØ",
+ "CommonMetadataStrings.practiceQuiz": "Ų§Ų®ŲŖŲØŲ§Ų± ŁŲµŁŲ± ŁŁŲŖŁ
Ų±Ł",
"CommonMetadataStrings.preschool": "Ų§ŁŁ
ŲÆŲ±Ų³Ų© Ų§ŁŲŖŲŲ¶ŁŲ±ŁŲ©",
"CommonMetadataStrings.professionalSkills": "Ł
ŁŲ§Ų±Ų§ŲŖ Ų§ŲŲŖŲ±Ų§ŁŁŲ©",
"CommonMetadataStrings.programming": "Ų§ŁŲØŲ±Ł
Ų¬Ų©",
@@ -484,6 +490,7 @@
"CommonMetadataStrings.readReference": "Ł
Ų±Ų¬Ų¹",
"CommonMetadataStrings.readingAndWriting": "Ų§ŁŁŲ±Ų§Ų”Ų© ŁŲ§ŁŁŲŖŲ§ŲØŲ©",
"CommonMetadataStrings.readingComprehension": "Ų§Ų³ŲŖŁŲ¹Ų§ŲØ Ų§ŁŁŲ±Ų§Ų”Ų©",
+ "CommonMetadataStrings.reference": "Ł
Ų§ŲÆŲ© Ł
Ų±Ų¬Ų¹ŁŲ©",
"CommonMetadataStrings.reflect": "Ų§ŁŲŖŲ£Ł
ŁŁ",
"CommonMetadataStrings.school": "Ł
ŲÆŲ±Ų³Ų©",
"CommonMetadataStrings.sciences": "Ų§ŁŲ¹ŁŁŁ
",
@@ -523,13 +530,7 @@
"CommunityStandardsModal.studioItem3": "Ų§ŁŁ
Ų“Ų§Ų±ŁŲ©. Ł
Ł Ų®ŁŲ§Ł Ų„ŁŲ“Ų§Ų” ŁŁŲ“Ų± ŁŁŁŲ§ŲŖ Ų¬ŲÆŁŲÆŲ© ŲØŁ
Ų§ ŁŲ¬ŲÆŲŖŁŲ Ų„Ł
Ų§ ŁŁŁ
Ų“Ų§Ų±ŁŲ© Ł
Ų¹ Ų§ŁŲŖŲ·ŲØŁŁŲ§ŲŖ Ų§ŁŲ®Ų§ŲµŲ© ŲØŁ ŲØŲ“ŁŁ Ų®Ų§Ųµ Ų£Ł ŁŁŁ
Ų“Ų§Ų±ŁŲ© Ł
Ų¹ Ų§ŁŲ¢Ų®Ų±ŁŁ Ų¹ŁŁ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł.",
"CommunityStandardsModal.studioItem4": "Ų§ŁŲŖŲ¹ŲÆŁŁ Ł Ų§ŁŲ„ŁŲ“Ų§Ų”. Ų£Ł Ų„Ų¶Ų§ŁŲ© ŲŖŁ
Ų§Ų±ŁŁ Ų§ŁŲŖŁŁŁŁ
Ų§ŁŲ®Ų§ŲµŲ© ŲØŁ Ų„ŁŁ Ų£Ł Ł
ŁŲ§ŲÆ Ł
ŁŲ¬ŁŲÆŲ©",
"CommunityStandardsModal.studioItem5": "Ų§ŁŲ§Ų³ŲŖŲ¶Ų§ŁŲ©. Ų±ŁŲ¹ Ų§ŁŁ
ŁŲ§ŲÆ Ų§ŁŲ®Ų§ŲµŲ© ŲØŁ (ŁŁŲŖŲµŲ± Ų°ŁŁ Ų¹ŁŁ Ų§ŁŁ
ŁŲ§ŲÆ Ų§ŁŲŖŁ ŲŖŲ¹Ų±Ł Ų£ŁŁŲ§ Ł
Ų±Ų®ŲµŲ© ŲØŲ“ŁŁ Ł
ŁŲ§Ų³ŲØ ŁŁŁŁŲ§Ł
ŲØŲ°ŁŁ) Ų³ŁŲ§Ų” Ł
Ł ŁŲ±Ųµ ŲµŁŲØ Ł
ŲŁŁ Ų£Ł Ł
ŁŲ§ŁŲ¹ Ų£Ų®Ų±Ł Ų¹ŁŁ Ų§ŁŲ„ŁŲŖŲ±ŁŲŖ",
- "CompletionOptions.allContent": "Ł
Ų¹Ų±ŁŲ¶ ŲØŲ§ŁŁŲ§Ł
Ł",
- "CompletionOptions.completeDuration": "Ų¹ŁŲÆŁ
Ų§ ŁŁŁŁ Ų§ŁŁŁŲŖ Ų§ŁŁ
Ų³ŲŖŲŗŲ±ŁŁ Ł
Ų³Ų§ŁŁŁŲ§ ŁŁŁ
ŲÆŲ© Ų§ŁŲ²Ł
ŁŁŲ©",
- "CompletionOptions.determinedByResource": "Ł
ŁŲŲÆŁŲÆ ŲØŁŲ§Ų³Ų·Ų© Ų§ŁŁ
ŲµŲÆŲ±",
- "CompletionOptions.exactTime": "Ų§ŁŁ
ŲÆŲ© Ų§ŁŁŲ§Ų²Ł
Ų© ŁŁŲ„ŁŁ
Ų§Ł",
- "CompletionOptions.goal": "Ų¹ŁŲÆŁ
Ų§ ŁŲŖŁ
ŲŖŲŁŁŁ Ų§ŁŁŲÆŁ",
- "CompletionOptions.practiceQuiz": "Ų§Ų®ŲŖŲØŲ§Ų± ŁŲµŁŲ± ŁŁŲŖŁ
Ų±ŁŁ",
- "CompletionOptions.reference": "Ł
Ų§ŲÆŲ© Ł
Ų±Ų¬Ų¹ŁŲ©",
+ "CompletionOptions.learnersCanMarkComplete": "Ų§ŁŲ³Ł
Ų§Ų ŁŁŁ
ŲŖŲ¹ŁŁ
ŁŁ ŲØŲŖŲŲÆŁŲÆŁ Ų¹ŁŁ Ų£ŁŁ Ł
ŁŲŖŁ
Ł",
"CompletionOptions.referenceHint": "ŁŁ ŁŲŖŁ
ŲŖŲŖŲØŲ¹ Ų§ŁŲŖŁŲÆŁ
ŁŁ Ų§ŁŁ
ŁŲ§ŲÆ Ų§ŁŁ
Ų±Ų¬Ų¹ŁŲ© Ł
Ų§ ŁŁ
ŁŲµŁŁŁŲ§ Ų§ŁŁ
ŲŖŲ¹ŁŁ
ŁŁ Ų¹ŁŁ Ų£ŁŁŲ§ Ł
ŁŲŖŁ
ŁŲ©",
"ConstantStrings.All Rights Reserved": "Ų¬Ł
ŁŲ¹ Ų§ŁŲŁŁŁ Ł
ŲŁŁŲøŲ©",
"ConstantStrings.All Rights Reserved_description": "ŲŖŲ“ŁŲ± Ų±Ų®ŲµŲ© -Ų¬Ł
ŁŲ¹ Ų§ŁŲŁŁŁ Ł
ŲŁŁŲøŲ©- Ų„ŁŁ Ų£Ł ŲµŲ§ŲŲØ ŲŁŁŁ Ų§ŁŲ·ŲØŲ¹ ŁŲ§ŁŁŲ“Ų± ŁŲŲŖŁŲø ŲØŲ¬Ł
ŁŲ¹ Ų§ŁŲŁŁŁ Ų§ŁŁ
ŁŲµŁŲµ Ų¹ŁŁŁŲ§ ŁŁ ŁŲ§ŁŁŁ ŲŁŁŁ Ų§ŁŲ·ŲØŲ¹ ŁŲ§ŁŁŲ“Ų± ŲØŁ
ŁŲ¬ŲØ Ų§ŲŖŁŲ§ŁŁŲ© ŲŁŁŁ ŁŲ“Ų± Ł
ŲŲÆŲÆŲ© Ų£Ł ŁŲŲŖŁŲø ŲØŁŲ§ ŁŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ł Ų§ŁŲ®Ų§Ųµ.",
@@ -627,7 +628,13 @@
"ContentNodeChangedIcon.isNewTopic": "Ł
Ų¬ŁŲÆ ŲŗŁŲ± Ł
ŁŲ“ŁŲ±",
"ContentNodeChangedIcon.isUpdatedResource": "ŲŖŁ
Ų§ŁŲŖŲŲÆŁŲ« Ł
ŁŲ° Ų¢Ų®Ų± ŲŖŲ§Ų±ŁŲ® ŁŁŁŲ“Ų±",
"ContentNodeChangedIcon.isUpdatedTopic": "ŲŖŁ
ŲŖŲŲÆŁŲ« Ų§ŁŁ
Ų¬ŁŲÆ Ł
ŁŲ° Ų¢Ų®Ų± ŲŖŲ§Ų±ŁŲ® ŁŁŁŲ“Ų±",
+ "ContentNodeCopyTaskProgress.copyErrorTopic": "ŁŲ“Ł ŁŲ³Ų® ŲØŲ¹Ų¶ Ų§ŁŁ
ŲµŲ§ŲÆŲ±",
+ "ContentNodeEditListItem.copiedSnackbar": "Ų§ŁŲŖŁ
ŁŲŖ Ų¹Ł
ŁŁŲ© Ų§ŁŁŲ³Ų®",
+ "ContentNodeEditListItem.creatingCopies": "Ų¬Ų§Ų±Ł Ų§ŁŁŲ³Ų®...",
"ContentNodeEditListItem.optionsTooltip": "Ų§ŁŲ®ŁŲ§Ų±Ų§ŲŖ",
+ "ContentNodeEditListItem.removeNode": "Ų„Ų²Ų§ŁŲ©",
+ "ContentNodeEditListItem.retryCopy": "Ų„Ų¹Ų§ŲÆŲ© Ų§ŁŁ
ŲŲ§ŁŁŲ©",
+ "ContentNodeEditListItem.undo": "ŲŖŲ±Ų§Ų¬Ų¹",
"ContentNodeIcon.audio": "Ų§ŁŁ
ŁŁ Ų§ŁŲµŁŲŖŁ",
"ContentNodeIcon.document": "Ł
ŁŁ ŁŲµŁŁ",
"ContentNodeIcon.exercise": "Ų§ŁŲŖŁ
Ų±ŁŁ",
@@ -637,8 +644,8 @@
"ContentNodeIcon.unsupported": "ŲŗŁŲ± Ł
ŲÆŲ¹ŁŁ
",
"ContentNodeIcon.video": "Ų§ŁŁŁŲÆŁŁ",
"ContentNodeLearningActivityIcon.multipleLearningActivities": "Ų£ŁŲ“Ų·Ų© ŲŖŲ¹ŁŁ
Ł
ŲŖŲ¹ŲÆŲÆŲ©",
- "ContentNodeLearningActivityIcon.topic": "Ł
Ų¬ŁŲÆ",
"ContentNodeListItem.coachTooltip": "Ł
ŲµŲÆŲ± Ų®Ų§Ųµ ŲØŲ§ŁŁ
ŲÆŲ±ŲØ",
+ "ContentNodeListItem.copyingError": "ŁŲ“Ł Ų§ŁŁŲ³Ų®.",
"ContentNodeListItem.copyingTask": "Ų¬Ų§Ų±Ł Ų§ŁŁŲ³Ų®",
"ContentNodeListItem.hasCoachTooltip": "{value, number, integer} {value, plural, zero {Ł
ŲµŲ§ŲÆŲ± ŁŁŁ
ŲÆŲ±ŲØŁŁ} one {Ł
ŲµŲÆŲ± ŁŁŁ
ŲÆŲ±ŲØŁŁ} two {Ł
ŲµŲÆŲ±ŁŁŁ ŁŁŁ
ŲÆŲ±ŲØŁŁ} few {Ł
ŲµŲ§ŲÆŲ± ŁŁŁ
ŲÆŲ±ŲØŁŁ} many {Ł
ŲµŲÆŲ±Ų§Ł ŁŁŁ
ŲÆŲ±ŲØŁŁ} other {Ł
ŲµŲ§ŲÆŲ± ŁŁŁ
ŲÆŲ±ŲØŁŁ}}",
"ContentNodeListItem.openTopic": "ŁŲŖŲ Ł
Ų¬ŁŲÆ",
@@ -656,7 +663,7 @@
"ContentNodeOptions.move": "ŁŁŁ Ų„ŁŁ",
"ContentNodeOptions.moveTo": "ŁŁŁ Ų„ŁŁ...",
"ContentNodeOptions.newSubtopic": "Ł
Ų¬ŁŲÆ Ų¬ŲÆŁŲÆ",
- "ContentNodeOptions.remove": "Ų„Ų²Ų§ŁŲ©",
+ "ContentNodeOptions.remove": "ŲŲ°Ł",
"ContentNodeOptions.removedFromClipboard": "ŲŖŁ
Ų§ŁŲŲ°Ł Ł
Ł Ų§ŁŲŲ§ŁŲøŲ©",
"ContentNodeOptions.removedItems": "ŲŖŁ
Ų„ŁŲ±Ų³Ų§ŁŁŲ§ Ų„ŁŁ Ų³ŁŲ© Ų§ŁŁ
ŁŁ
ŁŲ§ŲŖ",
"ContentNodeOptions.undo": "ŲŖŲ±Ų§Ų¬Ų¹",
@@ -692,8 +699,8 @@
"CountryField.locationLabel": "ŲŲÆŲÆ ŁŁ Ł
Ų§ ŁŁŲ·ŲØŁ",
"CountryField.locationRequiredMessage": "ŁŲ°Ų§ Ų§ŁŲŁŁ Ł
Ų·ŁŁŲØ",
"CountryField.noCountriesFound": "ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ ŲÆŁŁ",
- "Create.ToSCheck": "ŁŁŲÆ ŁŲ±Ų£ŲŖ Ų“Ų±ŁŲ· Ų§ŁŲ®ŲÆŁ
Ų© ŁŲ£ŁŲ§ŁŁ Ų¹ŁŁŁŲ§",
- "Create.ToSRequiredMessage": "Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŁ
ŁŲ§ŁŁŲ© Ų¹ŁŁ Ų“Ų±ŁŲ· Ų§ŁŲ®ŲÆŁ
Ų©",
+ "Create.ToSRequiredMessage": "Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŁ
ŁŲ§ŁŁŲ© Ų¹ŁŁ Ų§ŁŲ³ŁŲ§Ų³Ų© ŁŲ“Ų±ŁŲ· Ų§ŁŲ®ŲÆŁ
Ų©",
+ "Create.agreement": "ŁŁŲÆ ŁŲ±Ų£ŲŖ Ų“Ų±ŁŲ· Ų§ŁŲ®ŲÆŁ
Ų© ŁŲ³ŁŲ§Ų³Ų© Ų§ŁŲ®ŲµŁŲµŁŲ© ŁŲ£ŁŲ§ŁŁ Ų¹ŁŁŁŲ§",
"Create.backToLoginButton": "ŲŖŲ³Ų¬Ł Ų§ŁŲÆŲ®ŁŁ",
"Create.basicInformationHeader": "Ł
Ų¹ŁŁŁ
Ų§ŲŖ Ų£Ų³Ų§Ų³ŁŲ©",
"Create.conferenceSourceOption": "Ł
Ų¤ŲŖŁ
Ų±",
@@ -724,8 +731,6 @@
"Create.passwordLabel": "ŁŁŁ
Ų© Ų§ŁŁ
Ų±ŁŲ±",
"Create.passwordMatchMessage": "ŁŁŁ
ŲŖŲ§ Ų§ŁŁ
Ų±ŁŲ± ŲŗŁŲ± Ł
ŲŖŲ·Ų§ŲØŁŲŖŁŁ",
"Create.personalDemoSourceOption": "Ų¹Ų±Ų¶ Ų“Ų®ŲµŁ",
- "Create.privacyPolicyCheck": "ŁŁŲÆ ŁŲ±Ų£ŲŖ Ų³ŁŲ§Ų³Ų© Ų§ŁŲ®ŲµŁŲµŁŲ© ŁŲ£ŁŲ§ŁŁ Ų¹ŁŁŁŲ§",
- "Create.privacyPolicyRequiredMessage": "ŁŲ±Ų¬Ł Ų§ŁŁ
ŁŲ§ŁŁŲ© Ų¹ŁŁ Ų³ŁŲ§Ų³Ų© Ų§ŁŲ®ŲµŁŲµŁŲ©",
"Create.registrationFailed": "ŲŲµŁ Ų®Ų·Ų£ Ų£Ų«ŁŲ§Ų” ŲŖŲ³Ų¬ŁŁ ŲŲ³Ų§ŲØŁ. Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŁ
ŲŲ§ŁŁŲ© Ł
Ų¬ŲÆŲÆŲ§Ł",
"Create.registrationFailedOffline": "ŁŲØŲÆŁ Ų£ŁŁ ŲŗŁŲ± Ł
ŲŖŲµŁ. ŁŁŲ±Ų¬Ł Ų§ŁŲ§ŲŖŲµŲ§Ł ŲØŲ§ŁŲ„ŁŲŖŲ±ŁŲŖ ŁŲ„ŁŲ“Ų§Ų” ŲŲ³Ų§ŲØ.",
"Create.sequencingUsageOption": "Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§ŁŁ
ŲŖŲ·ŁŲØŲ§ŲŖ Ų§ŁŲ£Ų³Ų§Ų³ŁŲ© ŁŲŖŲ±ŲŖŁŲØ Ų§ŁŁ
ŁŲ§ŲÆ ŲØŲ“ŁŁ Ł
ŲŖŲ³ŁŲ³Ł",
@@ -784,6 +789,7 @@
"Details.assessmentsIncludedText": "Ų§ŁŲŖŁŁŁŁ
Ų§ŲŖ",
"Details.authorToolTip": "Ų§ŁŲ“Ų®Ųµ Ų£Ł Ų§ŁŁ
ŁŲøŁ
Ų© Ų§ŁŲ°Ł/ Ų§ŁŲŖŁ Ų£ŁŲ“Ų£/ŲŖ ŁŲ°Ų§ Ų§ŁŁ
ŲŲŖŁŁ",
"Details.authorsLabel": "Ų§ŁŁ
Ų¤ŁŁŁŁ",
+ "Details.categoriesHeading": "Ų§ŁŁŲ¦Ų§ŲŖ",
"Details.coachDescription": "ŁŁ
ŁŁ Ų¹Ų±Ų¶ Ł
ŲµŲ§ŲÆŲ± Ų§ŁŁ
ŲÆŲ±ŁŁŲØŁŁ ŁŁŲ· ŁŁŁ
ŲÆŲ±ŁŁŲØŁŁ ŁŁ ŁŁŁŁŲØŲ±Ł",
"Details.coachHeading": "Ł
ŲµŲ§ŲÆŲ± ŁŁŁ
ŲÆŲ±ŁŁŲØŁŁ",
"Details.containsContentHeading": "ŁŲŖŲ¶Ł
Ł Ł
ŲŲŖŁŁ Ł
Ł",
@@ -792,6 +798,7 @@
"Details.creationHeading": "ŲŖŁ
Ų§ŁŲ„ŁŲ“Ų§Ų” ŁŁ",
"Details.currentVersionHeading": "Ų§ŁŲ„ŲµŲÆŲ§Ų± Ų§ŁŁ
ŁŲ“ŁŲ±",
"Details.languagesHeading": "Ų§ŁŁŲŗŲ§ŲŖ",
+ "Details.levelsHeading": "Ų§ŁŁ
Ų³ŲŖŁŁŲ§ŲŖ",
"Details.licensesLabel": "Ų§ŁŲŖŲ±Ų§Ų®ŁŲµ",
"Details.primaryLanguageHeading": "Ų§ŁŁŲŗŲ© Ų§ŁŲ£Ų³Ų§Ų³ŁŲ©",
"Details.providerToolTip": "Ų§ŁŁ
ŁŲøŁ
Ų© Ų§ŁŲŖŁ ŁŁŁŁŲŖ ŲØŲ§ŁŁ
ŲŲŖŁŁ Ų£Ł ŲŖŁŁŁ
ŲØŁŲ“Ų± Ų§ŁŁ
ŲŲŖŁŁ",
@@ -820,7 +827,6 @@
"DetailsTabView.importedFromButtonText": "Ų§Ų³ŲŖŁŲ±Ų§ŲÆ Ł
Ł {channel}",
"DetailsTabView.languageChannelHelpText": "Ų§ŲŖŲ±ŁŁ ŁŲ§Ų±ŲŗŲ§Ł Ł
Ł Ų£Ų¬Ł Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
ŁŲŗŲ© Ų§ŁŁŁŲ§Ų©",
"DetailsTabView.languageHelpText": "Ų§ŲŖŲ±ŁŁ ŁŲ§Ų±ŲŗŁŲ§ Ł
Ł Ų£Ų¬Ł Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
ŁŲŗŲ© Ų§ŁŁ
Ų¬ŁŲÆ",
- "CompletionOptions.learnersCanMarkComplete": "Ų§ŁŲ³Ł
Ų§Ų ŁŁŁ
ŲŖŲ¹ŁŁ
ŁŁ ŲØŲŖŲŲÆŁŲÆŁ Ų¹ŁŁ Ų£ŁŁ Ł
ŁŲŖŁ
Ł",
"DetailsTabView.noTagsFoundText": "ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ ŁŲŖŲ§Ų¦Ų¬ ŲØŲŲ« ŁŁ\"{text}\". Ų„Ų¶ŲŗŲ· Ų¹ŁŁ 'Ų„ŲÆŲ®Ų§Ł' ŁŲ„ŁŲ“Ų§Ų” ŲŖŲµŁŁŁ Ų¬ŲÆŁŲÆ",
"DetailsTabView.providerLabel": "Ł
ŁŁŲ± Ų§ŁŲ®ŲÆŁ
Ų©",
"DetailsTabView.providerToolTip": "Ų§ŁŁ
ŁŲøŁ
Ų© Ų§ŁŲŖŁ ŁŁŁŁŲŖ ŲØŲ§ŁŁ
ŲŲŖŁŁ Ų£Ł ŲŖŁŁŁ
ŲØŁŲ“Ų± Ų§ŁŁ
ŲŲŖŁŁ",
@@ -979,6 +985,7 @@
"Main.privacyPolicyLink": "Ų³ŁŲ§Ų³Ų© Ų§ŁŲ®ŲµŁŲµŁŲ©",
"Main.signInButton": "ŲŖŲ³Ų¬Ł Ų§ŁŲÆŲ®ŁŁ",
"MainNavigationDrawer.administrationLink": "Ų§ŲÆŲ§Ų±Ų©",
+ "MainNavigationDrawer.changeLanguage": "ŲŖŲŗŁŁŲ± Ų§ŁŁŲŗŲ©",
"MainNavigationDrawer.channelsLink": "Ų§ŁŁŁŁŲ§ŲŖ Ų§ŁŲŖŲ¹ŁŁŁ
ŁŲ©",
"MainNavigationDrawer.copyright": "Ā© {year} Ų§ŁŁ
Ų³Ų§ŁŲ§Ų© ŁŁ Ų§ŁŲŖŲ¹ŁŁ
",
"MainNavigationDrawer.giveFeedback": "ŁŲÆŁŁ
Ł
ŁŲ§ŲŲøŲ§ŲŖŁ",
@@ -1029,7 +1036,6 @@
"PermissionsError.goToHomePageAction": "Ų§ŁŲŖŁŁ Ų„ŁŁ Ų§ŁŲµŁŲŲ© Ų§ŁŲ±Ų¦ŁŲ³ŁŲ©",
"PermissionsError.permissionDeniedHeader": "ŁŁ ŁŲ³ŁŲŖ ŲŖŲ³Ų¬ŁŁ Ų§ŁŲÆŲ®ŁŁŲ",
"PoliciesModal.checkboxText": "ŁŁŲÆ ŁŲ§ŁŁŲŖ Ų¹ŁŁ Ų§ŁŲ“Ų±ŁŲ· Ų§ŁŁŲ§Ų±ŲÆŲ© Ų£Ų¹ŁŲ§Ł",
- "PoliciesModal.checkboxValidationErrorMessage": "ŁŲ°Ų§ Ų§ŁŲŁŁ Ł
Ų·ŁŁŲØ",
"PoliciesModal.closeButton": "Ų„ŲŗŁŲ§Ł",
"PoliciesModal.continueButton": "Ł
ŲŖŲ§ŲØŲ¹Ų©",
"PoliciesModal.lastUpdated": "ŲŖŲ§Ų±ŁŲ® Ų¢Ų®Ų± ŲŖŲŲÆŁŲ« {date}",
@@ -1040,7 +1046,8 @@
"ProgressModal.lastPublished": "Ł
ŁŲ“ŁŲ± {last_published}",
"ProgressModal.publishHeader": "ŁŲ“Ų± Ų§ŁŁŁŲ§Ų©",
"ProgressModal.syncError": "ŁŲ“ŁŲŖ Ų¢Ų®Ų± Ł
ŲŲ§ŁŁŲ© ŁŁŁ
Ų²Ų§Ł
ŁŲ©",
- "ProgressModal.syncHeader": "Ł
Ų²Ų§Ł
ŁŲ© Ų§ŁŁŁŲ§Ų©",
+ "ProgressModal.syncHeader": "Ł
Ų²Ų§Ł
ŁŲ© Ų§ŁŁ
ŲµŲ§ŲÆŲ±",
+ "ProgressModal.syncedSnackbar": "Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŲŖŁ ŲŖŁ
Ł
Ų²Ų§Ł
ŁŲŖŁŲ§",
"ProgressModal.unpublishedText": "ŁŁ
ŁŲŖŁ
Ų§ŁŁŲ“Ų±",
"PublishModal.cancelButton": "Ų„ŁŲŗŲ§Ų”",
"PublishModal.descriptionDescriptionTooltip": "Ų³ŁŲŖŁ
Ų„ŲøŁŲ§Ų± ŁŲ°Ų§ Ų§ŁŁŲµŁ ŁŁ
Ų³Ų¤ŁŁŁ ŁŁŁŁŲØŲ±Ł ŁŲØŁ ŁŁŲ§Ł
ŁŁ
ŲØŲŖŲŲÆŁŲ« Ų„ŲµŲÆŲ§Ų±Ų§ŲŖ Ų§ŁŁŁŲ§Ų© ",
@@ -1146,7 +1153,6 @@
"ResourcePanel.author": "Ų§ŁŁ
Ų¤ŁŁ",
"ResourcePanel.availableFormats": "Ų§ŁŲµŁŲŗ Ų§ŁŁ
ŲŖŲ§ŲŲ©",
"ResourcePanel.coachResources": "Ł
ŲµŲ§ŲÆŲ± ŁŁŁ
ŲÆŲ±ŁŁŲØŁŁ",
- "ResourcePanel.completion": "Ų§ŁŲ§Ų³ŲŖŁŁ
Ų§Ł",
"ResourcePanel.copyrightHolder": "ŲµŲ§ŲŲØ ŲŁŁŁ Ų§ŁŁŲ“Ų±",
"ResourcePanel.description": "Ų§ŁŲŖŁŁŲµŁŁ",
"ResourcePanel.details": "Ų§ŁŲŖŁŲ§ŲµŁŁ",
@@ -1155,13 +1161,15 @@
"ResourcePanel.incompleteQuestionError": "{count, plural, zero {# Ų£Ų³Ų¦ŁŲ© ŁŁ
ŲŖŁŲŖŁ
Ł} one {# Ų³Ų¤Ų§Ł ŁŁ
ŁŁŲŖŁ
Ł} two {# Ų³Ų¤Ų§ŁŲ§Ł ŁŁ
ŁŁŲŖŁ
ŁŲ§} few {# Ų£Ų³Ų¦ŁŲ© ŁŁ
ŲŖŁŲŖŁ
Ł} many {# Ų³Ų¤Ų§ŁŲ§Ł ŁŁ
ŁŁŲŖŁ
Ł} other {# Ų£Ų³Ų¦ŁŲ© ŁŁ
ŲŖŁŲŖŁ
Ł}}",
"ResourcePanel.language": "Ų§ŁŁŲŗŲ©",
"ResourcePanel.license": "Ų§ŁŲŖŲ±Ų®ŁŲµ",
- "ResourcePanel.masteryMofN": "Ų§ŁŁŲÆŁ: {m} Ł
Ł Ų£ŲµŁ {n}",
"ResourcePanel.nextSteps": "Ų§ŁŲ®Ų·ŁŲ§ŲŖ Ų§ŁŲŖŲ§ŁŁŲ©",
- "ResourcePanel.noCopyrightHolderError": "ŲµŲ§ŲŲØ ŲŁŁŁ Ų§ŁŁŲ“Ų± ŲŗŁŲ± Ł
ŁŲ¬ŁŲÆ",
- "ResourcePanel.noFilesError": "Ł
ŁŁŲ§ŲŖ ŁŲ§ŁŲµŲ©",
- "ResourcePanel.noLicenseDescriptionError": "ŁŲµŁ ŲŖŲ±Ų®ŁŲµ Ł
ŁŁŁŲÆ",
- "ResourcePanel.noLicenseError": "ŲŖŲ±Ų®ŁŲµ Ł
ŁŁŁŲÆ",
- "ResourcePanel.noMasteryModelError": "Ł
Ų¹Ų§ŁŁŲ± Ų„ŲŖŁŲ§Ł Ł
ŁŁŁŲÆŲ©",
+ "ResourcePanel.noCompletionCriteriaError": "Ł
Ų¹Ų§ŁŁŲ± Ų§ŁŲ„ŁŲ¬Ų§Ų² Ł
Ų·ŁŁŲØŲ©",
+ "ResourcePanel.noCopyrightHolderError": "Ų§Ų³Ł
ŲµŲ§ŲŲØ ŲŁŁŁ Ų§ŁŁŲ“Ų± Ł
Ų·ŁŁŲØ",
+ "ResourcePanel.noDurationError": "Ų§ŁŁ
ŲÆŲ© Ų§ŁŲ²Ł
ŁŁŲ© Ł
Ų·ŁŁŲØŲ©",
+ "ResourcePanel.noFilesError": "Ų§ŁŁ
ŁŁ Ł
Ų·ŁŁŲØ",
+ "ResourcePanel.noLearningActivityError": "ŁŲ“Ų§Ų· Ų§ŁŲŖŲ¹ŁŁ
Ł
Ų·ŁŁŲØ",
+ "ResourcePanel.noLicenseDescriptionError": "ŁŲµŁ Ų§ŁŲŖŲ±Ų®ŁŲµ Ł
Ų·ŁŁŲØ",
+ "ResourcePanel.noLicenseError": "Ų§ŁŲŖŲ±Ų®ŁŲµ Ł
Ų·ŁŁŲØ",
+ "ResourcePanel.noMasteryModelError": "Ł
Ų¹Ų§ŁŁŲ± Ų§ŁŲ§ŲŖŁŲ§Ł Ł
Ų·ŁŁŲØŲ©",
"ResourcePanel.noQuestionsError": "Ų§ŁŲŖŁ
Ų±ŁŁ ŁŲ§Ų±Ųŗ",
"ResourcePanel.originalChannel": "ŲŖŁ
Ų§Ų³ŲŖŁŲ±Ų§ŲÆŁ Ł
Ł",
"ResourcePanel.previousSteps": "Ų§ŁŲ®Ų·ŁŲ§ŲŖ Ų§ŁŲ³Ų§ŲØŁŲ©",
@@ -1271,16 +1279,18 @@
"SyncResourcesModal.cancelButtonLabel": "Ų„ŁŲŗŲ§Ų”",
"SyncResourcesModal.confirmSyncModalExplainer": "Ų£ŁŲŖ Ų¹ŁŁ ŁŲ“Ł Ł
Ų²Ų§Ł
ŁŲ© ŁŲŖŲŲÆŁŲ« Ł
Ų§ ŁŁŁ:",
"SyncResourcesModal.confirmSyncModalTitle": "ŲŖŲ£ŁŁŲÆ Ų§ŁŁ
Ų²Ų§Ł
ŁŲ©",
+ "SyncResourcesModal.confirmSyncModalWarningExplainer": "ŲŖŲŲ°ŁŲ±: Ų³ŁŲ¤ŲÆŁ ŁŲ°Ų§ Ų„ŁŁ Ų§Ų³ŲŖŲØŲÆŲ§Ł Ų£Ł ŲŖŲŗŁŁŲ±Ų§ŲŖ Ų£Ų¬Ų±ŁŲŖŁŲ§ Ų¹ŁŁ Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŁ
ŁŲ³ŁŲ®Ų© Ų£Ł Ų§ŁŁ
Ų³ŲŖŁŲ±ŲÆŲ©.",
"SyncResourcesModal.continueButtonLabel": "Ł
ŲŖŲ§ŲØŲ¹Ų©",
"SyncResourcesModal.syncButtonLabel": "Ł
Ų²Ų§Ł
ŁŲ©",
- "SyncResourcesModal.syncExercisesExplainer": "ŲŖŲŲÆŁŲ« Ų§ŁŲ£Ų³Ų¦ŁŲ© ŁŲ§ŁŲ„Ų¬Ų§ŲØŲ§ŲŖ ŁŲ§ŁŲŖŁŁ
ŁŲŲ§ŲŖ",
+ "SyncResourcesModal.syncExercisesExplainer": "ŲŖŲŲÆŁŲ« Ų§ŁŲ£Ų³Ų¦ŁŲ© ŁŲ§ŁŲ„Ų¬Ų§ŲØŲ§ŲŖ ŁŲ§ŁŲŖŁŁ
ŁŲŲ§ŲŖ ŁŁ Ų§ŁŲŖŁ
Ų§Ų±ŁŁ ŁŲ§ŁŲ§Ų®ŲŖŲØŲ§Ų±Ų§ŲŖ Ų§ŁŁŲµŁŲ±Ų©",
"SyncResourcesModal.syncExercisesTitle": "ŲŖŁŲ§ŲµŁŁ Ų§ŁŲŖŁŁŁŁ
",
- "SyncResourcesModal.syncFilesExplainer": "ŲŖŲŲÆŁŲ« ŁŲ§ŁŲ© Ł
Ų¹ŁŁŁ
Ų§ŲŖ Ų§ŁŁ
ŁŁ",
+ "SyncResourcesModal.syncFilesExplainer": "ŲŖŲŲÆŁŲ« Ų¬Ł
ŁŲ¹ Ų§ŁŁ
ŁŁŲ§ŲŖŲ ŲØŁ
Ų§ ŁŁ Ų°ŁŁ: Ų§ŁŲµŁŲ± Ų§ŁŁ
ŲµŲŗŲ± ŁŲ§ŁŲŖŲ±Ų¬Ł
Ų§ŲŖ ŁŲ¹ŁŲ§ŁŁŁ Ų§ŁŲµŁŲ±",
"SyncResourcesModal.syncFilesTitle": "Ų§ŁŁ
ŁŁŲ§ŲŖ",
- "SyncResourcesModal.syncModalExplainer": "Ł
Ų²Ų§Ł
ŁŲ© ŁŲŖŲŲÆŁŲ« Ł
ŲµŲ§ŲÆŲ±Ł Ł
Ų¹ Ł
ŲµŲÆŲ±ŁŲ§ Ų§ŁŲ£ŲµŁŁ.",
+ "SyncResourcesModal.syncModalExplainer": "ŲŖŲ³Ł
Ų Ł
Ų²Ų§Ł
ŁŲ© Ų§ŁŁ
ŲµŲ§ŲÆŲ± ŁŁ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł ŲØŲŖŲŲÆŁŲ« Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŁ
ŁŲ³ŁŲ®Ų© Ų£Ł Ų§ŁŁ
Ų³ŲŖŁŲ±ŲÆŲ© ŁŁ ŁŲ°Ł Ų§ŁŁŁŲ§Ų©Ų Ł
Ų¹ Ų£Ł ŲŖŲŗŁŁŲ±Ų§ŲŖ ŁŲŖŁ
Ų„ŲÆŲ®Ų§ŁŁŲ§ Ų¹ŁŁ Ł
ŁŁŲ§ŲŖ Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŲ£ŲµŁŁŲ©.",
+ "SyncResourcesModal.syncModalSelectAttributes": "ŲŲÆŲÆ Ł
Ų§ ŲŖŲ±ŁŲÆ Ł
Ų²Ų§Ł
ŁŲŖŁ:",
"SyncResourcesModal.syncModalTitle": "Ł
Ų²Ų§Ł
ŁŲ© Ų§ŁŁ
ŲµŲ§ŲÆŲ±",
- "SyncResourcesModal.syncTagsExplainer": "ŲŖŲŲÆŁŲ« ŁŲ§ŁŲ© Ų§ŁŲŖŲµŁŁŁŲ§ŲŖ",
- "SyncResourcesModal.syncTagsTitle": "Ų§ŁŲŖŲµŁŁŁŲ§ŲŖ",
+ "SyncResourcesModal.syncResourceDetailsExplainer": "ŲŖŲŲÆŁŲ« Ų§ŁŁ
Ų¹ŁŁŁ
Ų§ŲŖ ŲŁŁ Ų§ŁŁ
ŲµŲÆŲ±: ŁŲ“Ų§Ų· Ų§ŁŲŖŲ¹ŁŁŁ
ŁŲ§ŁŁ
Ų³ŲŖŁŁ ŁŲ§ŁŁ
ŲŖŲ·ŁŲØŲ§ŲŖ ŁŲ§ŁŁŲ¦Ų© ŁŲ§ŁŁŲ³ŁŁ
ŁŲ§ŁŲ¬Ł
ŁŁŲ± ŁŲ§ŁŁ
ŲµŲÆŲ±",
+ "SyncResourcesModal.syncResourceDetailsTitle": "ŲŖŁŲ§ŲµŁŁ Ų§ŁŁ
ŲµŲÆŲ±",
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer": "ŲŖŲŲÆŁŲ« Ų¹ŁŲ§ŁŁŁ ŁŲŖŁŲµŁŁŲ§ŲŖ Ų§ŁŁ
ŲµŲÆŲ±",
"SyncResourcesModal.syncTitlesAndDescriptionsTitle": "Ų§ŁŲ¹ŁŲ§ŁŁŁ ŁŲ§ŁŲŖŁŲµŁŁŲ§ŲŖ",
"TechnicalTextBlock.copiedToClipboardConfirmation": "ŲŖŁ
Ł Ų§ŁŁŁŲ³Ų® Ų„ŁŁ Ų§ŁŲŲ§ŁŲøŲ©",
@@ -1421,7 +1431,7 @@
"TreeViewBase.getToken": "Ų§ŲŲµŁ Ų¹ŁŁ Ų±Ł
Ų² ŲŖŲ¹Ų±ŁŁ",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, zero {Ł
ŲµŲ§ŲÆŲ± ŁŁ
ŲŖŁŲŖŁ
Ł ŁŁŲ§ ŁŁ
ŁŁ ŁŲ“Ų±ŁŲ§} one {Ł
ŲµŲÆŲ± ŁŁ
ŁŁŲŖŁ
Ł ŁŁŲ§ ŁŁ
ŁŁ ŁŲ“Ų±Ł} two {Ł
ŲµŲÆŲ±Ų§Ł ŁŁ
ŁŁŲŖŁ
ŁŲ§ ŁŁŲ§ ŁŁ
ŁŁ ŁŲ“Ų±ŁŁ
Ų§} few {Ł
ŲµŲ§ŲÆŲ± ŁŁ
ŲŖŁŲŖŁ
Ł ŁŁŲ§ ŁŁ
ŁŁ ŁŲ“Ų±ŁŲ§} many {Ł
ŲµŲÆŲ±Ų§Ł ŁŁ
ŁŁŲŖŁ
Ł ŁŁŲ§ ŁŁ
ŁŁ ŁŲ“Ų±Ł} other {Ł
ŲµŲ§ŲÆŲ± ŁŁ
ŲŖŁŲŖŁ
Ł ŁŁŲ§ ŁŁ
ŁŁ ŁŲ“Ų±ŁŲ§}}",
"TreeViewBase.noChangesText": "ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ ŲŖŲŗŁŁŲ±Ų§ŲŖ ŁŁ Ų§ŁŁŁŲ§Ų©",
- "TreeViewBase.noLanguageSetError": "ŁŲŗŲ© Ų§ŁŁŁŲ§Ų© ŲŗŁŲ± Ł
ŁŲ¬ŁŲÆŲ©",
+ "TreeViewBase.noLanguageSetError": "ŁŲŗŲ© Ų§ŁŁŁŲ§Ų© Ł
Ų·ŁŁŲØŲ©",
"TreeViewBase.openTrash": "ŁŲŖŲ Ų³ŁŲ© Ų§ŁŁ
ŁŁ
ŁŲ§ŲŖ",
"TreeViewBase.publishButton": "ŁŲ“Ų±",
"TreeViewBase.publishButtonTitle": "Ų¬Ų¹Ł ŁŲ°Ł Ų§ŁŁŁŲ§Ų© Ł
ŲŖŲ§ŲŲ© ŁŁŲ§Ų³ŲŖŁŲ±Ų§ŲÆ Ų„ŁŁ ŁŁŁŁŲØŲ±Ł",
@@ -1440,19 +1450,15 @@
"UsingStudio.aboutStudioText": "ŁŲ®Ų¶Ų¹ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł ŁŲ¹Ł
ŁŁŲ© ŲŖŲ·ŁŁŲ± ŲÆŲ§Ų¦Ł
Ų©Ų ŁŲØŲ°ŁŁ ŁŲÆ ŲŖŲŖŲ³ŲØŲØ ŲØŲ¹Ų¶ Ų§ŁŲŖŲŗŁŁŲ±Ų§ŲŖ ŁŁ ŲŲÆŁŲ« Ų³ŁŁŁ Ų£Ł ŲŖŲŲÆŁŲ§ŲŖ ŲŗŁŲ± Ł
ŲŖŁŁŲ¹Ų© (ŲŖŲ³Ł
ŁŁ Ų£ŁŲ¶Ų§Ł ŲØŁ\"Ų§ŁŁ
Ų“Ų§ŁŁ\"). ŁŁ ŲŲ§Ł ŁŲ§Ų¬ŁŲŖ Ł
Ų“ŁŁŲ©Ų ŁŲ±Ų¬Ł Ų„Ų¹ŁŲ§Ł
ŁŲ§ ŲŲ§Ł ŲŲÆŁŲ«ŁŲ§ ŁŁ
Ų³Ų§Ų¹ŲÆŲŖŁŲ§ ŁŁ ŲŁŁŲ§. (Ų§ŁŲøŲ± Ų£ŲÆŁŲ§Ł ŁŁŲŲµŁŁ Ų¹ŁŁ ŲŖŲ¹ŁŁŁ
Ų§ŲŖ ŲŁŁ Ų·Ų±ŁŁŲ© Ų§ŁŲ„ŲØŁŲ§Ųŗ Ų¹Ł Ų§ŁŁ
Ų“Ų§ŁŁ).",
"UsingStudio.bestPractice1": "Ų¹ŁŲÆ Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų¹Ł
ŁŁŲ§ŲŖ Ų§ŁŲ§Ų³ŲŖŁŲ±Ų§ŲÆ ŁŲ§ŁŲŲ§ŁŲøŲ©Ų ŁŁ
ŲØŲ°ŁŁ Ų¶Ł
Ł Ł
Ų¬Ł
ŁŲ¹Ų§ŲŖ ŁŲ±Ų¹ŁŲ© ŲµŲŗŁŲ±Ų© Ł
Ł Ų§ŁŁ
Ų¬ŁŲÆŲ§ŲŖ ŲØŲÆŁŲ§Ł Ł
Ł Ų§ŁŁŁŁŲ§ŲŖ Ų§ŁŁŲ§Ł
ŁŲ© ŁŁ Ų§ŁŁ
Ų±Ų© Ų§ŁŁŲ§ŲŲÆŲ© (Ų®ŲµŁŲµŲ§Ł ŁŁŁŁŁŲ§ŲŖ Ų§ŁŁŲØŁŲ±Ų©).",
"UsingStudio.bestPractice2": "ŁŁŁŲ¶ŁŁ Ų„ŁŲ“Ų§Ų” ŁŁŁŲ§ŲŖ ŲµŲŗŁŲ±Ų© Ł
ŲŖŲ¹ŲÆŲÆŲ© Ų¹ŁŲ¶Ų§Ł Ų¹Ł Ų„ŁŲ“Ų§Ų” ŁŁŲ§Ų© ŁŲØŁŲ±Ų© ŁŲ§ŲŲÆŲ© ŲØŁ
Ų¬ŁŲÆŲ§ŲŖ Ł
ŲŖŲ¹ŲÆŲÆŲ©.",
- "UsingStudio.bestPractice3": "ŁŁ
ŲØŲ„Ų¹Ų§ŲÆŲ© ŲŖŲŁ
ŁŁ Ų§ŁŲµŁŲŲ© ŲØŲ“ŁŁ ŲÆŁŲ±Ł ŁŁŲŖŲ£ŁŲÆ Ł
Ł ŲŁŲø Ų¹Ł
ŁŁ Ų¹ŁŁ Ų§ŁŲ®Ų§ŲÆŁ
ŁŲ¹ŲÆŁ
ŲŲÆŁŲ« Ų£Ų®Ų·Ų§Ų” ŁŁ Ų§ŁŲ“ŲØŁŲ©. Ų§Ų³ŲŖŲ®ŲÆŁ
CTRL + R ŁŁ Ų£ŁŲøŁ
Ų© ŲŖŲ“ŲŗŁŁ Linux / Windows Ų£Ł ā + R Ų¹ŁŁ ŁŲøŲ§Ł
Mac.",
- "UsingStudio.bestPractice4": "ŲŖŲ¬ŁŲØ Ų§ŁŁŁŲ§Ł
ŲØŲ¹Ł
ŁŁŲ§ŲŖ ŲŖŲŲ±ŁŲ± Ł
ŲŖŲ²Ų§Ł
ŁŲ© ŁŁ ŁŁŲ³ Ų§ŁŁŁŲ§Ų©. ŁŲ§ ŁŲ¬ŁŲ² Ų£Ł ŁŲŖŁ
ŲŖŲŲ±ŁŲ± Ų§ŁŁŁŁŲ§ŲŖ ŲØŁŲ§Ų³Ų·Ų© Ų¹ŲÆŲ© Ł
Ų³ŲŖŲ®ŲÆŁ
ŁŁ ŁŁ ŁŁŲ³ Ų§ŁŁŁŲŖ Ų£Ł ŲØŁŲ§Ų³Ų·Ų© ŁŁŲ³ Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
ŁŁ Ų¹ŲÆŲ© ŁŁŲ§ŁŲ° ŲŖŲµŁŲ.",
+ "UsingStudio.bestPractice3": "Ų£Ų¹ŲÆ ŲŖŲŁ
ŁŁ Ų§ŁŲµŁŲŲ© ŁŲŖŲ£ŁŁŲÆ Ų£Ł Ų¹Ł
ŁŁ ŲŖŁ
ŲŁŲøŁ Ų„ŁŁ Ų§ŁŲ®Ų§ŲÆŁ
. Ų§Ų³ŲŖŲ®ŲÆŁ
CTRL+R Ų¹ŁŁ ŁŲøŲ§Ł
Linux/Windows Ų£Ł ā+R Ų¹ŁŁ ŁŲøŲ§Ł
Mac.",
"UsingStudio.bestPractice5": "ŁŲÆ ŲŖŁŲ§Ų¬Ł Ų£Ų®Ų·Ų§Ų” Ł
Ų±ŲŖŲØŲ·Ų© ŲØŲ§ŁŲŖŁŲ§Ų” Ų§ŁŁ
ŁŁŲ© Ų¹ŁŁ Ł
ŲŖŲµŁŲŁ Ų¹ŁŲÆ Ų§ŁŁŁŲ§Ł
ŲØŲ¹Ł
ŁŁŲ§ŲŖ Ł
Ų«Ł Ų§ŁŲ§Ų³ŲŖŁŲ±Ų§ŲÆ ŁŲ§ŁŁ
Ų²Ų§Ł
ŁŲ© Ų¹ŁŁ Ų§ŁŁŁŁŲ§ŲŖ Ų§ŁŁŲØŁŲ±Ų©. ŁŲ§ ŲŖŲŖŁŲ§Ų¬Ų£ ŲØŲ±Ų³Ų§ŁŲ© Ų§ŁŲ®Ų·Ų£ ŲŖŁŁ ŁŁŲ§ ŲŖŁŲ±Ų± Ų§ŁŲ¹Ł
ŁŁŲ© Ł
Ų¬ŲÆŲÆŲ§Ł Ų¹ŁŁ Ų§ŁŁŁŲ±. ŁŲ°Ł Ų§ŁŲ±Ų³Ų§ŁŲ© ŁŲ§ ŲŖŲ¹ŁŁ Ų£Ł Ų§ŁŲ¹Ł
ŁŁŲ© ŁŲ“ŁŲŖ- ŁŲ§ ŁŲ²Ų§Ł Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł ŁŲ¹Ł
Ł ŁŁ Ų§ŁŲ®ŁŁŁŲ©. Ų§ŁŲŖŲøŲ± ŁŲØŲ¶Ų¹ ŲÆŁŲ§Ų¦Ł Ų«Ł
Ų£Ų¹ŲÆ ŲŖŲŁ
ŁŁ Ų§ŁŲµŁŲŲ© ŁŲØŁ Ł
ŲŖŲ§ŲØŲ¹Ų© Ų¹Ł
ŁŁŲ§ŲŖ Ų§ŁŲŖŲŲ±ŁŲ±.",
"UsingStudio.bestPractice6": "ŁŁ
ŲØŲ¶ŲŗŲ· Ł
ŁŲ§Ų·Ų¹ Ų§ŁŁŁŲÆŁŁ ŁŲØŁ ŲŖŲŁ
ŁŁŁŲ§ (Ų£ŁŁŁ ŁŲøŲ±Ų© Ų¹ŁŁ ŁŲ°Ł Ų§ŁŲŖŲ¹ŁŁŁ
Ų§ŲŖ).",
"UsingStudio.bestPractice7": "ŁŁ
ŲØŲ§ŁŁŲ“Ų± ŲØŲ“ŁŁ ŲÆŁŲ±Ł ŁŲ§Ų³ŲŖŁŲ±ŲÆ ŁŁŲ§ŲŖŁ Ų„ŁŁ ŁŁŁŁŲØŲ±Ł Ł
Ł Ų£Ų¬Ł Ł
Ų¹Ų§ŁŁŲ© Ų§ŁŁ
ŲŲŖŁŁ ŁŲ§ŁŲŲµŁŁ Ų¹ŁŁ ŁŲ³Ų®Ų© Ų§ŲŲŖŁŲ§Ų·ŁŲ© Ł
ŲŁŁŲ© Ł
Ł ŁŁŲ§ŲŖŁ.",
- "UsingStudio.bestPractice8": "ŁŲ§ ŲŖŁŁ
ŲØŲ¹Ł
ŁŁŲ§ŲŖ ŲŖŲŲ±ŁŲ± ŁŁ Ų§ŁŁŁŲ§Ų© ŲØŲ¹ŲÆ Ų§ŁŁŁŲ± Ų¹ŁŁ Ų®ŁŲ§Ų± \"ŁŲ“Ų±\". Ų§ŁŲŖŲøŲ± Ų„Ų“Ų¹Ų§Ų± Ų§ŁŲØŲ±ŁŲÆ Ų§ŁŲ„ŁŁŲŖŲ±ŁŁŁ ŁŲØŁ Ł
ŲŖŲ§ŲØŲ¹Ų© Ų¹Ł
ŁŁŲ§ŲŖ Ų§ŁŲŖŲŲ±ŁŲ±.",
"UsingStudio.bestPractice9": "ŁŁ
ŲØŲ§ŁŲ„ŲØŁŲ§Ųŗ Ų¹Ł Ų§ŁŁ
Ų“Ų§ŁŁ Ų¹ŁŲÆŁ
Ų§ ŲŖŁŲ§Ų¬ŁŁŲ§.",
"UsingStudio.bestPractices": "Ų£ŁŲ¶Ł Ų§ŁŁ
Ł
Ų§Ų±Ų³Ų§ŲŖ",
"UsingStudio.communityStandardsLink": "Ł
Ų¹Ų§ŁŁŲ± Ų§ŁŁ
Ų¬ŲŖŁ
Ų¹",
- "UsingStudio.issue1": "ŁŁŲÆ Ų£ŲØŁŲŗ Ų§Ų«ŁŲ§Ł Ł
Ł Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
ŁŁ Ų¹Ł ŲŁŲ§ŲÆŲ« Ł
ŁŁŲµŁŲ© ŲŁŲ« Ų§Ų®ŲŖŁŁ Ų§ŁŁ
ŲŲŖŁŁ Ų§ŁŲ°Ł ŁŲ§Ł
Ų§ ŲØŲ§Ų³ŲŖŁŲ±Ų§ŲÆŁ Ł
Ł ŁŁŲ§Ų© Ų£Ų®Ų±ŁŲ ŁŁŁ
ŁŲØŁ Ų³ŁŁ Ų§ŁŁ
Ų¬ŁŲÆŲ§ŲŖ Ų§ŁŲ±Ų¦ŁŲ³ŁŲ© ŁŲ§ŁŁ
Ų¬ŁŲÆŲ§ŲŖ Ų§ŁŁŲ±Ų¹ŁŲ© Ų§ŁŁŲ§Ų±ŲŗŲ©. ŁŲ£ŁŲ“ŁŲ± ŁŁ Ų£ŲŲÆ Ų§ŁŲŖŁŲ§Ų±ŁŲ± Ų£Ł Ų§ŁŁ
ŲŲŖŁŁ ŁŲÆ ŲøŁŲ± ŁŁ Ł
Ų§ ŲØŲ¹ŲÆ. ŁŁ
ŁŁŲ§Ų¬Ł Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
Ų§Ł ŁŲ°Ł Ų§ŁŁ
Ų“Ų§ŁŁ ŲØŲ“ŁŁ Ł
Ų³ŲŖŁ
Ų±Ų ŁŁŲÆ ŲŖŲŖŲ¶Ł
Ł ŲŖŁŁ Ų§ŁŲŁŲ§ŲÆŲ« Ł
Ų“Ų§ŁŁ Ł
Ų±ŲŖŲØŲ·Ų© ŲØŲ§ŲŖŲµŲ§Ł Ų§ŁŲ„ŁŲŖŲ±ŁŲŖ Ų§ŁŲØŲ·ŁŲ” Ų£Ł ŲŗŁŲ± Ų§ŁŁ
Ų³ŲŖŁŲ±. Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŲ§ŲŖŲµŲ§Ł ŲØŁŲ§ ŲØŲ£Ų³Ų±Ų¹ ŁŁŲŖ Ł
Ł
ŁŁ ŁŁ ŲŲ§Ł ŲµŲ§ŲÆŁŲŖ ŁŲ°Ł Ų§ŁŁ
Ų“ŁŁŲ© Ł
Ł Ų£Ų¬Ł Ų„Ų·ŁŲ§Ų¹ŁŲ§ Ų¹ŁŁ ŁŁ Ł
Ų§ ŁŁ
ŁŁŁ ŲŖŲ°ŁŲ±Ł Ł
Ł Ł
Ų¹ŁŁŁ
Ų§ŲŖ.",
- "UsingStudio.issue2": "ŲØŲ¹Ų¶ Ų§ŁŲ¹Ł
ŁŁŲ§ŲŖ ŁŁ Ų§ŁŲ§Ų³ŲŖŁŲÆŁŁ ŲØŲ·ŁŲ¦Ų© Ų¬ŲÆŁŲ§ ŁŁ Ų§ŁŁŁŲŖ Ų§ŁŲŲ§ŁŁŲ ŁŁŁŲ°Ų§ ŁŲÆ ŁŲØŲÆŁ Ų£Ł Ų§ŁŲŖŲŗŁŁŲ± Ų§ŁŲ°Ł ŲŲ§ŁŁŲŖ Ų§ŁŁŁŲ§Ł
ŲØŁ ŁŲÆ Ų§ŁŲŖŁŁ Ų£Ł ŁŁ
ŁŲŖŁ
ŲØŁŲ¬Ų§Ų. ŁŁ ŁŲ«ŁŲ± Ł
Ł Ų§ŁŲŲ§ŁŲ§ŲŖŲ ŁŲØŁŁ Ų§ŁŲŖŲŗŁŁŲ± ŁŁŲÆ Ų§ŁŁ
Ų¹Ų§ŁŲ¬Ų© ŁŲ³ŁŲøŁŲ± ŲŲ§Ł Ų§ŁŲŖŁ
Ų§ŁŁ. ŁŁ ŲŲ§Ł ŁŁ
ŁŁŲŖŁ
Ł Ų§ŁŲŖŲŗŁŁŲ± Ų®ŁŲ§Ł Ł„-Ł”Ł ŲÆŁŲ§Ų¦Ł ŲŲŖŁ ŲØŲ¹ŲÆ ŲŖŲŲÆŁŲ« Ų§ŁŁ
ŲŖŲµŁŲŲ Ų§ŁŲ±Ų¬Ų§Ų” Ų„Ų¹ŁŲ§Ł
ŁŲ§ ŲØŲ§ŁŁ
Ų“ŁŁŲ©. ŁŲŁ ŁŲ¹Ł
Ł Ų¹ŁŁ Ų„ŁŲ¬Ų§ŲÆ ŲŁŁŁ ŁŁŲ°Ł Ų§ŁŁ
Ų“Ų§ŁŁ.",
+ "UsingStudio.issue1": "ŁŁŲ§Ł ŲŖŁŲ§Ų±ŁŲ± ŲŖŁŁŲÆ ŲØŲ£Ł ŲØŲ¹Ų¶ Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
ŁŁ ŁŲ§ŲŲøŁŲ§ Ų§Ų®ŲŖŁŲ§Ų” Ų§ŁŲŖŲŗŁŁŲ±Ų§ŲŖ Ų§ŁŲŖŁ Ų£Ų¬Ų±ŁŁŲ§ Ł
Ų¤Ų®Ų±Ų§Ł Ų¹ŁŁ ŁŁŁŲ§ŲŖŁŁ
. ŁŲØŲÆŁ Ų£Ł Ų§ŁŁ
Ų“ŁŁŲ© ŲŖŲŖŲ¹ŁŁ ŲØŁŲŖŲ Ų¹ŁŲ§Ł
Ų§ŲŖ ŲŖŲØŁŁŲØ Ł
ŲŖŲ¹ŲÆŲÆŲ© Ł
Ł Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł ŁŲŖŲ³Ų¬ŁŁ Ų§ŁŲ®Ų±ŁŲ¬ ŁŁ ŁŁŲ§ŁŲ© Ų§ŁŁ
Ų·Ų§Ł. ŁŁŲµŲ ŲØŲŖŲ¹Ų·ŁŁ Ų£Ł Ł
ŁŲ²Ų© \"ŲŁŲø Ų§ŁŲ°Ų§ŁŲ±Ų©/Ų§ŁŁŁŁ
\" ŁŁ Ų§ŁŁ
ŲŖŲµŁŲ Ų¹ŁŲÆ Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±ŁŲ ŁŲ„Ų¹Ų§ŲÆŲ© ŲŖŲŁ
ŁŁ ŁŁ Ų¹ŁŲ§Ł
Ų© ŲŖŲØŁŁŲØ ŁŲØŁ ŲŖŲ³Ų¬ŁŁ Ų§ŁŲ®Ų±ŁŲ¬. ŁŲŁ ŁŲØŲŲ« ŲØŲ“ŁŁŁ Ł
ŁŲ«Ł ŁŁ ŁŲ°Ł Ų§ŁŁ
Ų“ŁŁŲ©Ų ŁŲ°Ų§ Ų„Ų°Ų§ ŁŲ§Ų¬ŁŲŖŁŲ§Ų ŁŁŲ±Ų¬Ł Ų§ŁŲ§ŲŖŲµŲ§Ł ŲØŁŲ§ ŁŲŖŲ²ŁŁŲÆŁŲ§ ŲØŲ£ŁŲØŲ± ŁŲÆŲ±Ł Ł
Ł
ŁŁ Ł
Ł Ų§ŁŁ
Ų¹ŁŁŁ
Ų§ŲŖ.",
"UsingStudio.issueLink1": "ŲŖŁŲ§Ų±ŁŲ± Ł
ŲŖŲ¹ŁŁŲ© ŲØŲ§Ų®ŲŖŁŲ§Ų” Ų§ŁŁ
ŲŲŖŁŁ",
- "UsingStudio.issueLink2": "Ų§ŁŲ£ŲÆŲ§Ų” Ų§ŁŲØŲ·ŁŲ” ŁŁ
ŁŁ Ų£Ł ŁŲ¤ŲÆŁ Ų„ŁŁ Ų£Ų®Ų·Ų§Ų” ŲŗŁŲ± Ł
ŲŖŁŁŲ¹Ų© ŁŁ ŁŲ§Ų¬ŁŲ© Ų§ŁŲ¹Ų±Ų¶",
"UsingStudio.issuesPageLink": "Ų¹Ų±Ų¶ Ų¬Ł
ŁŲ¹ Ų§ŁŁ
Ų“ŁŁŲ§ŲŖ",
"UsingStudio.notableIssues": "Ł
Ų“Ų§ŁŁ ŲØŲ§Ų±Ų²Ų©",
"UsingStudio.policiesLink": "Ų³ŁŲ§Ų³Ų© Ų§ŁŲ®ŲµŁŲµŁŲ©",
@@ -1498,4 +1504,5 @@
"sharedVue.masteryModelNWholeNumber": "ŁŲ¬ŲØ Ų£Ł ŁŁŁŁ Ų±ŁŁ
Ų§Ł ŁŁŁŲ§Ł",
"sharedVue.masteryModelRequired": "Ų§ŁŲ„ŲŖŁŲ§Ł Ł
Ų·ŁŁŲØ",
"sharedVue.shortActivityLteThirty": "ŁŲ¬ŲØ Ų£Ł ŲŖŁŁŁ Ų§ŁŁŁŁ
Ų© Ł
Ų³Ų§ŁŁŲ© Ų£Ł Ų£ŁŁ Ł
Ł 30",
- "sharedVue.titleRequired": "Ų§ŁŲ¹ŁŁŲ§Ł Ų„ŁŲ²Ų§Ł
Ł"}
+ "sharedVue.titleRequired": "Ų§ŁŲ¹ŁŁŲ§Ł Ų„ŁŲ²Ų§Ł
Ł"
+}
diff --git a/contentcuration/locale/ar/LC_MESSAGES/django.po b/contentcuration/locale/ar/LC_MESSAGES/django.po
index 5eb82393c6..4309dba0c3 100644
--- a/contentcuration/locale/ar/LC_MESSAGES/django.po
+++ b/contentcuration/locale/ar/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-16 00:55+0000\n"
-"PO-Revision-Date: 2022-10-17 20:08\n"
+"POT-Creation-Date: 2023-05-10 21:55+0000\n"
+"PO-Revision-Date: 2023-05-24 17:17\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Language: ar_SA\n"
@@ -17,36 +17,8 @@ msgstr ""
"X-Crowdin-File: /unstable/django.po\n"
"X-Crowdin-File-ID: 4322\n"
-#: contentcuration/api.py:140
-msgid "Date/Time Created"
-msgstr "ŲŖŲ§Ų±ŁŲ®/ŁŁŲŖ Ų§ŁŲ„ŁŲ“Ų§Ų”"
-
-#: contentcuration/api.py:141 contentcuration/api.py:142
-msgid "Not Available"
-msgstr "ŲŗŁŲ± Ł
ŲŖŲ§Ų"
-
-#: contentcuration/api.py:145
-msgid "Ricecooker Version"
-msgstr "Ų„ŲµŲÆŲ§Ų± Ricecooker"
-
-#: contentcuration/api.py:150 contentcuration/utils/csv_writer.py:164
-msgid "File Size"
-msgstr "ŲŲ¬Ł
Ų§ŁŁ
ŁŁ"
-
-#: contentcuration/api.py:161
-msgid "# of {}s"
-msgstr "# Ł
Ł {}s"
-
-#: contentcuration/api.py:165
-msgid "# of Questions"
-msgstr "# Ł
Ł Ų§ŁŲ§Ų³Ų¦ŁŲ©"
-
-#: contentcuration/api.py:175
-msgid "# of Subtitles"
-msgstr "# Ł
Ł Ų§ŁŲŖŲ±Ų¬Ł
Ų§ŲŖ"
-
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
-#: contentcuration/settings.py:287
+#: contentcuration/settings.py:290
msgid "Arabic"
msgstr "Ų§ŁŲ¹Ų±ŲØŁŁŲ©"
@@ -54,40 +26,40 @@ msgstr "Ų§ŁŲ¹Ų±ŲØŁŁŲ©"
msgid "The site is currently in read-only mode. Please try again later."
msgstr "Ų§ŁŁ
ŁŁŲ¹ ŲŲ§ŁŁŲ§Ł ŁŁ ŁŲ¶Ų¹ Ų§ŁŁŲ±Ų§Ų”Ų© ŁŁŲ·. Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŁ
ŲŲ§ŁŁŲ© Ł
Ų¬ŲÆŲÆŲ§Ł ŁŁ ŁŁŲŖ ŁŲ§ŲŁ."
-#: contentcuration/models.py:279
+#: contentcuration/models.py:295
msgid "Not enough space. Check your storage under Settings page."
msgstr "ŁŲ§ ŲŖŁŲ¬ŲÆ Ł
Ų³Ų§ŲŲ© ŁŲ§ŁŁŲ©. ŲŖŲŁŁ Ł
Ł Ł
Ų³Ų§ŲŲ© Ų§ŁŲŖŲ®Ų²ŁŁ Ų§ŁŲ®Ų§ŲµŲ© ŲØŁ ŲŖŲŲŖ ŲµŁŲŲ© Ų§ŁŲ„Ų¹ŲÆŲ§ŲÆŲ§ŲŖ."
-#: contentcuration/models.py:294 contentcuration/models.py:301
+#: contentcuration/models.py:308 contentcuration/models.py:315
msgid "Out of storage! Request more space under Settings > Storage."
msgstr "ŁŁŲ°ŲŖ Ł
Ų³Ų§ŲŲ© Ų§ŁŲŖŲ®Ų²ŁŁ! ŁŁ
ŲØŲ·ŁŲØ Ų§ŁŁ
Ų²ŁŲÆ Ł
Ł Ų§ŁŁ
Ų³Ų§ŲŲ© Ł
Ł Ų§ŁŲ„Ų¹ŲÆŲ§ŲÆŲ§ŲŖ> Ų§ŁŲŖŲ®Ų²ŁŁ."
-#: contentcuration/models.py:1410
+#: contentcuration/models.py:1730
msgid " (Original)"
msgstr " (Ų£ŲµŁŁ)"
-#: contentcuration/settings.py:285
+#: contentcuration/settings.py:288
msgid "English"
msgstr "Ų§ŁŲ„ŁŲ¬ŁŁŲ²ŁŲ©"
-#: contentcuration/settings.py:286
+#: contentcuration/settings.py:289
msgid "Spanish"
msgstr "Ų§ŁŲ„Ų³ŲØŲ§ŁŁŲ©"
-#: contentcuration/settings.py:288
+#: contentcuration/settings.py:291
msgid "French"
msgstr "Ų§ŁŁŲ±ŁŲ³ŁŲ©"
-#: contentcuration/tasks.py:251
-msgid "Unknown error starting task. Please contact support."
-msgstr "ŲŲµŁ Ų®Ų·Ų£ ŲŗŁŲ± Ł
Ų¹Ų±ŁŁ Ų£Ų«ŁŲ§Ų” ŲØŲÆŲ” Ų§ŁŁ
ŁŁ
Ų©. Ų§ŁŲ±Ų¬Ų§Ų” Ų§ŁŲ§ŲŖŲµŲ§Ł ŲØŲ§ŁŲÆŲ¹Ł
."
+#: contentcuration/settings.py:292
+msgid "Portuguese"
+msgstr "Ų§ŁŲØŲ±ŲŖŲŗŲ§ŁŁŲ©"
-#: contentcuration/templates/base.html:83
+#: contentcuration/templates/base.html:38
#: contentcuration/templates/channel_list.html:14
msgid "Kolibri Studio"
msgstr "Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł"
-#: contentcuration/templates/base.html:166
+#: contentcuration/templates/base.html:129
msgid "Contentworkshop.learningequality.org has been deprecated. Please go to studio.learningequality.org for the latest version of Studio"
msgstr "ŁŁ
ŁŲ¹ŲÆ ŁŁŲ“Ų¬ŁŲ¹ Ų¹ŁŁ Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
Contentworkshop.learningequality.org. ŁŲ±Ų¬Ł Ų§ŁŲŖŁŲ¬ŁŁ Ų„ŁŁ studio.learningequality.org ŁŁŲŲµŁŁ Ų¹ŁŁ Ų¢Ų®Ų± Ų„ŲµŲÆŲ§Ų± Ų§ŁŲ§Ų³ŲŖŁŲÆŁŁ"
@@ -100,238 +72,16 @@ msgid "We're sorry, this channel was not found."
msgstr "ŁŲ¹ŲŖŲ°Ų±Ų ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ ŁŲ°Ł Ų§ŁŁŁŲ§Ų©."
#: contentcuration/templates/channel_not_found.html:24
-#: contentcuration/templates/permissions/open_channel_fail.html:14
#: contentcuration/templates/staging_not_found.html:23
#: contentcuration/templates/unauthorized.html:23
msgid "Go Home"
msgstr "Ų§ŁŲ°ŁŲ§ŲØ Ų§ŁŁ Ų§ŁŲµŁŲŲ© Ų§ŁŲ±Ų¦ŁŲ³ŁŲ©"
-#: contentcuration/templates/exercise_list.html:67
-msgid "Previous"
-msgstr "Ų§ŁŲ³Ų¤Ų§Ł Ų§ŁŲ³Ų§ŲØŁ"
-
-#: contentcuration/templates/exercise_list.html:73
-msgid "current"
-msgstr "Ų§ŁŲŲ§ŁŁ"
-
-#: contentcuration/templates/exercise_list.html:77
-msgid "Next"
-msgstr "Ų§ŁŲŖŲ§ŁŁ"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:206
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:165
-msgid "Language not set"
-msgstr "ŁŁ
ŁŲŖŁ
ŲŖŲ¹ŁŁŁ Ų§ŁŁŲŗŲ©"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:212
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:171
-#, python-format
-msgid "This file was generated on %(date)s"
-msgstr "ŲŖŁ
Ų„ŁŲ“Ų§Ų” ŁŲ°Ų§ Ų§ŁŁ
ŁŁ ŁŁ %(date)s"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:231
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:189
-msgid "Created"
-msgstr "ŲŖŁ
Ł Ų§ŁŲ„ŁŲ“Ų§Ų”"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:232
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:190
-msgid "Last Published"
-msgstr "Ų¢Ų®Ų± Ł
Ų§ ŁŁŲ“Ų±"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:233
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:191
-msgid "Unpublished"
-msgstr "ŁŁ
ŁŲŖŁ
Ų§ŁŁŲ“Ų±"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:245
-msgid "USING THIS CHANNEL"
-msgstr "Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
ŁŲ°Ł Ų§ŁŁŁŲ§Ų©"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:247
-msgid "Copy one of the following into Kolibri to import this channel:"
-msgstr "Ų§ŁŲ³Ų® ŁŲ§ŲŲÆŲ§Ł Ł
Ł
Ų§ ŁŁŁ Ų„ŁŁ ŁŁŁŁŲØŲ±Ł ŁŲ§Ų³ŲŖŁŲ±Ų§ŲÆ ŁŲ°Ł Ų§ŁŁŁŲ§Ų©:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:252
-msgid "Tokens (Recommended):"
-msgstr "Ų§ŁŲ±Ł
ŁŲ² Ų§ŁŲŖŲ¹Ų±ŁŁŁŲ© (ŁŁŲµŲ ŲØŁŲ§):"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:260
-msgid "Channel ID:"
-msgstr "Ł
ŁŲ¹Ų±ŁŁ Ų§ŁŁŁŲ§Ų©:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:267
-msgid "Channel must be published to import into Kolibri"
-msgstr "ŁŲ¬ŲØ ŁŲ“Ų± Ų§ŁŁŁŲ§Ų© ŁŲ§Ų³ŲŖŁŲ±Ų§ŲÆŁŲ§ Ų„ŁŁ ŁŁŁŁŲØŲ±Ł"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:273
-msgid "WHAT'S INSIDE"
-msgstr "Ł
Ų§ ŁŁ Ų§ŁŁ
Ų¶Ł
ŁŁ"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:283
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:213
-#, python-format
-msgid "%(count)s Resource"
-msgid_plural "%(count)s Resources"
-msgstr[0] "%(count)s Ł
ŲµŲÆŲ±Ų§Ł"
-msgstr[1] "%(count)s Ł
ŲµŲÆŲ±"
-msgstr[2] "%(count)s Ł
ŲµŲÆŲ±Ų§Ł"
-msgstr[3] "%(count)s Ł
ŲµŲÆŲ±Ų§Ł"
-msgstr[4] "%(count)s Ł
ŲµŲÆŲ±Ų§Ł"
-msgstr[5] "%(count)s Ł
ŲµŲÆŲ±Ų§Ł"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:304
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:234
-msgid "Includes"
-msgstr "ŁŲŖŲ¶Ł
Ł"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:310
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:236
-msgid "Languages"
-msgstr "Ų§ŁŁŲŗŲ§ŲŖ"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:316
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:245
-msgid "Subtitles"
-msgstr "Ų§ŁŲŖŲ±Ų¬Ł
Ų§ŲŖ"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:324
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:254
-msgid "For Educators"
-msgstr "ŁŁŁ
Ų¹ŁŁ
ŁŁ"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:256
-msgid "Coach Content"
-msgstr "Ų§ŁŁ
ŲŲŖŁŁ Ų§ŁŲ®Ų§Ųµ ŲØŲ§ŁŁ
ŲÆŲ±ŲØ"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:257
-msgid "Assessments"
-msgstr "Ų§ŁŲŖŁŁŁŁ
Ų§ŲŖ"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:334
-msgid "Content Tags"
-msgstr "ŁŲ³ŁŁ
Ų§ŁŁ
ŲŲŖŁŁ"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:338
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:270
-msgid "No tags found"
-msgstr "ŁŲ§ ŲŖŁŲ¬ŲÆ ŁŲ³ŁŁ
."
-
-#: contentcuration/templates/export/channel_detail_pdf.html:343
-#: contentcuration/templates/export/channel_detail_pdf.html:448
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:274
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:337
-msgid "This channel is empty"
-msgstr "ŁŲ°Ł Ų§ŁŁŁŲ§Ų© ŁŲ§Ų±ŲŗŲ©"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:348
-msgid "SOURCE"
-msgstr "Ų§ŁŁ
ŲµŲÆŲ±"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:360
-msgid "This channel features resources created by:"
-msgstr "ŁŲ°Ł Ų§ŁŁŁŲ§Ų© ŲŖŁ
ŁŲ² Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŲŖŁ ŲŖŁ
Ų„ŁŲ“Ų§Ų¤ŁŲ§ Ł
Ł Ų®ŁŲ§Ł:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:361
-#: contentcuration/templates/export/channel_detail_pdf.html:379
-#: contentcuration/templates/export/channel_detail_pdf.html:398
-#: contentcuration/templates/export/channel_detail_pdf.html:420
-#: contentcuration/templates/export/channel_detail_pdf.html:440
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:293
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:305
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:317
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:333
-msgid "Information not available"
-msgstr "Ų§ŁŁ
Ų¹ŁŁŁ
Ų§ŲŖ ŲŗŁŲ± Ł
ŲŖŁŁŲ±Ų©"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:378
-msgid "The material in this channel was provided by:"
-msgstr "ŲŖŁ
ŲŖŁŁŁŲ± Ų§ŁŁ
ŁŲ§ŲÆ Ų§ŁŁ
ŁŲ¬ŁŲÆŲ© ŁŁ ŁŲ°Ł Ų§ŁŁŁŲ§Ų© Ų¹Ł Ų·Ų±ŁŁ:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:397
-msgid "Material in this channel was originally hosted by:"
-msgstr "ŲŖŁ
ŲŖ Ų§ŲµŲŖŲ¶Ų§ŁŲ© Ų§ŁŁ
ŁŲ§ŲÆ Ų§ŁŁ
ŁŲ¬ŁŲÆŲ© ŁŁ ŁŲ°Ł Ų§ŁŁŁŲ§Ų© ŁŁ Ų§ŁŲ£ŲµŁ Ł
Ł ŁŲØŁ:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:416
-msgid "This channel includes the following licenses:"
-msgstr "ŲŖŲŲŖŁŁ ŁŲ°Ł Ų§ŁŁŁŲ§Ų© Ų¹ŁŁ Ų§ŁŲŖŲ±Ų§Ų®ŁŲµ Ų§ŁŲŖŲ§ŁŁŲ©:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:439
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:326
-msgid "Copyright Holders:"
-msgstr "Ł
Ų§ŁŁŁ ŲŁŁŁ Ų§ŁŁŲ“Ų±:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:192
-msgid "Token:"
-msgstr "Ų§ŁŲ±Ł
ŁŲ² Ų§ŁŲŖŲ¹Ų±ŁŁŁŲ©:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:203
-msgid "What's Inside"
-msgstr "Ł
Ų§ ŁŁ Ų§ŁŁ
Ų¶Ł
ŁŁ"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:239
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:248
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s Ų£ŁŲ«Ų±) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:267
-msgid "Most Common Tags"
-msgstr "Ų§ŁŁŲ³ŁŁ
Ų§ŁŲ£ŁŲ«Ų± Ų“ŁŁŲ¹Ų§Ł"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:280
-msgid "Source Information"
-msgstr "Ł
Ų¹ŁŁŁ
Ų§ŲŖ Ų§ŁŁ
ŲµŲÆŲ±"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:284
-msgid "Authors:"
-msgstr "Ų§ŁŁ
Ų¤ŁŁŁŁ:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:288
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s Ų£ŁŲ«Ų±) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:298
-msgid "Providers:"
-msgstr "Ł
ŁŲÆŁ
Ł Ų§ŁŲ®ŲÆŁ
Ų§ŲŖ:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:301
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:313
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:329
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s Ų£ŁŲ«Ų±) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:310
-msgid "Aggregators:"
-msgstr "Ų¬Ų§Ł
Ų¹Ł Ų§ŁŁ
ŲŲŖŁŁ:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-msgid "Licenses:"
-msgstr "Ų§ŁŲŖŲ±Ų§Ų®ŁŲµ:"
-
-#: contentcuration/templates/export/csv_email.txt:4
#: contentcuration/templates/export/user_csv_email.txt:4
#: contentcuration/templates/permissions/permissions_email.txt:4
#: contentcuration/templates/registration/activation_email.txt:4
#: contentcuration/templates/registration/activation_needed_email.txt:4
-#: contentcuration/templates/registration/channel_published_email.txt:4
+#: contentcuration/templates/registration/channel_published_email.html:10
#: contentcuration/templates/registration/password_reset_email.txt:3
#: contentcuration/templates/registration/registration_needed_email.txt:4
#: contentcuration/templates/settings/account_deleted_user_email.txt:3
@@ -341,43 +91,6 @@ msgstr "Ų§ŁŲŖŲ±Ų§Ų®ŁŲµ:"
msgid "Hello %(name)s,"
msgstr "Ł
Ų±ŲŲØŲ§ %(name)s,"
-#: contentcuration/templates/export/csv_email.txt:6
-#, python-format
-msgid "Your csv for %(channel_name)s has finished generating (attached)."
-msgstr "ŲŖŁ
Ų§ŁŲ§ŁŲŖŁŲ§Ų” Ł
Ł Ų„ŁŲ“Ų§Ų” Ł
ŁŁ csv Ų§ŁŲ®Ų§Ųµ ŲØŁ ŁŁ %(channel_name)s (Ł
Ų±ŁŁ)."
-
-#: contentcuration/templates/export/csv_email.txt:8
-#: contentcuration/templates/export/user_csv_email.txt:29
-#: contentcuration/templates/permissions/permissions_email.txt:21
-#: contentcuration/templates/registration/activation_email.txt:12
-#: contentcuration/templates/registration/activation_needed_email.txt:14
-#: contentcuration/templates/registration/channel_published_email.txt:15
-#: contentcuration/templates/registration/password_reset_email.txt:14
-#: contentcuration/templates/registration/registration_needed_email.txt:12
-#: contentcuration/templates/settings/account_deleted_user_email.txt:10
-#: contentcuration/templates/settings/storage_request_email.txt:46
-msgid "Thanks for using Kolibri Studio!"
-msgstr "Ų“ŁŲ±Ų§ ŁŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł!"
-
-#: contentcuration/templates/export/csv_email.txt:10
-#: contentcuration/templates/export/user_csv_email.txt:31
-#: contentcuration/templates/permissions/permissions_email.txt:23
-#: contentcuration/templates/registration/activation_email.txt:14
-#: contentcuration/templates/registration/activation_needed_email.txt:16
-#: contentcuration/templates/registration/channel_published_email.txt:17
-#: contentcuration/templates/registration/password_reset_email.txt:16
-#: contentcuration/templates/registration/registration_needed_email.txt:14
-#: contentcuration/templates/settings/account_deleted_user_email.txt:12
-#: contentcuration/templates/settings/issue_report_email.txt:26
-#: contentcuration/templates/settings/storage_request_email.txt:48
-msgid "The Learning Equality Team"
-msgstr "ŁŲ±ŁŁ Ų§ŁŁ
Ų³Ų§ŁŲ§Ų© ŁŁ Ų§ŁŲŖŲ¹ŁŁ
"
-
-#: contentcuration/templates/export/csv_email_subject.txt:1
-#, python-format
-msgid "CSV for %(channel_name)s"
-msgstr "Ł
ŁŁ CSV ŁŁ %(channel_name)s"
-
#: contentcuration/templates/export/user_csv_email.txt:6
msgid "Here is the information for your Kolibri Studio account"
msgstr "ŁŁŁ
Ų§ ŁŁŁ Ł
Ų¹ŁŁŁ
Ų§ŲŖ Ų¹Ł ŲŲ³Ų§ŲØŁ ŁŁ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł"
@@ -424,43 +137,64 @@ msgstr "ŲŖŁ
Ų„Ų±ŁŲ§Ł Ł
Ų¹ŁŁŁ
Ų§ŲŖ ŲØŲ®ŲµŁŲµ Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŲŖŁ ŲŁ
Ł
msgid "If you have any questions or concerns, please email us at %(legal_email)s."
msgstr "Ų„Ų°Ų§ ŁŲ§Ł ŁŲÆŁŁ Ų£ŁŲ© Ų£Ų³Ų¦ŁŲ© Ų£Ł Ų§Ų³ŲŖŁŲ³Ų§Ų±Ų§ŲŖŲ ŁŲ±Ų¬Ł Ų§ŁŲŖŁŲ§ŲµŁ Ł
Ų¹ŁŲ§ Ų¹ŲØŲ± Ų§ŁŲØŲ±ŁŲÆ Ų§ŁŲ„ŁŁŲŖŲ±ŁŁŁ %(legal_email)s."
+#: contentcuration/templates/export/user_csv_email.txt:29
+#: contentcuration/templates/permissions/permissions_email.txt:21
+#: contentcuration/templates/registration/activation_email.txt:12
+#: contentcuration/templates/registration/activation_needed_email.txt:14
+#: contentcuration/templates/registration/channel_published_email.html:23
+#: contentcuration/templates/registration/password_reset_email.txt:14
+#: contentcuration/templates/registration/registration_needed_email.txt:12
+#: contentcuration/templates/settings/account_deleted_user_email.txt:10
+#: contentcuration/templates/settings/storage_request_email.txt:46
+msgid "Thanks for using Kolibri Studio!"
+msgstr "Ų“ŁŲ±Ų§ ŁŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł!"
+
+#: contentcuration/templates/export/user_csv_email.txt:31
+#: contentcuration/templates/permissions/permissions_email.html:118
+#: contentcuration/templates/permissions/permissions_email.txt:23
+#: contentcuration/templates/registration/activation_email.html:111
+#: contentcuration/templates/registration/activation_email.txt:14
+#: contentcuration/templates/registration/activation_needed_email.txt:16
+#: contentcuration/templates/registration/channel_published_email.html:25
+#: contentcuration/templates/registration/password_reset_email.html:111
+#: contentcuration/templates/registration/password_reset_email.txt:16
+#: contentcuration/templates/registration/registration_needed_email.txt:14
+#: contentcuration/templates/registration/welcome_new_user_email.html:172
+#: contentcuration/templates/settings/account_deleted_user_email.txt:12
+#: contentcuration/templates/settings/issue_report_email.txt:26
+#: contentcuration/templates/settings/storage_request_email.txt:48
+msgid "The Learning Equality Team"
+msgstr "ŁŲ±ŁŁ Ų§ŁŁ
Ų³Ų§ŁŲ§Ų© ŁŁ Ų§ŁŲŖŲ¹ŁŁ
"
+
#: contentcuration/templates/export/user_csv_email_subject.txt:1
msgid "Your Kolibri Studio account information"
msgstr "Ł
Ų¹ŁŁŁ
Ų§ŲŖ ŲŲ³Ų§ŲØŁ Ų¹ŁŁ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł"
-#: contentcuration/templates/permissions/open_channel_fail.html:12
-msgid "There was an error opening this channel."
-msgstr "ŲŲµŁ Ų®Ų·Ų£ Ł
Ų§ Ų£Ų«ŁŲ§Ų” ŁŲŖŲ ŁŲ°Ł Ų§ŁŁŁŲ§Ų©."
-
-#: contentcuration/templates/permissions/open_channel_fail.html:13
-msgid "Try running ricecooker again."
-msgstr "ŲŲ§ŁŁ ŲŖŲ“ŲŗŁŁ Ų„ŲµŲÆŲ§Ų± ricecooker Ł
Ų¬ŲÆŲÆŲ§Ł."
-
-#: contentcuration/templates/permissions/permissions_email.html:93
-#: contentcuration/templates/registration/activation_email.html:91
-#: contentcuration/templates/registration/password_reset_email.html:91
+#: contentcuration/templates/permissions/permissions_email.html:92
+#: contentcuration/templates/registration/activation_email.html:90
+#: contentcuration/templates/registration/password_reset_email.html:90
msgid "Hello"
msgstr "Ł
Ų±ŲŲØŲ§"
-#: contentcuration/templates/permissions/permissions_email.html:94
+#: contentcuration/templates/permissions/permissions_email.html:93
msgid "has invited you to edit a channel at"
msgstr "ŁŲ§Ł
ŲØŲÆŲ¹ŁŲŖŁ ŁŲŖŲ¹ŲÆŁŁ ŁŁŲ§Ų© ŁŁ"
-#: contentcuration/templates/permissions/permissions_email.html:100
+#: contentcuration/templates/permissions/permissions_email.html:99
#, python-format
msgid "Invititation to %(share_mode)s channel"
msgstr "ŲÆŲ¹ŁŲ© ŁŁ %(share_mode)s Ų§ŁŁŁŲ§Ų©"
-#: contentcuration/templates/permissions/permissions_email.html:104
+#: contentcuration/templates/permissions/permissions_email.html:103
msgid "Click one of the following links to either accept or decline your invitation:"
msgstr "Ų§ŁŁŲ± Ų¹ŁŁ Ų£ŲŲÆ Ų§ŁŲ±ŁŲ§ŲØŲ· Ų§ŁŲŖŲ§ŁŁŲ© ŁŁŲØŁŁ Ų§ŁŲÆŲ¹ŁŲ© Ų§ŁŲ®Ų§ŲµŲ© ŲØŁ Ų£Ł Ų±ŁŲ¶ŁŲ§:"
-#: contentcuration/templates/permissions/permissions_email.html:107
-#: contentcuration/templates/permissions/permissions_email.html:109
+#: contentcuration/templates/permissions/permissions_email.html:106
+#: contentcuration/templates/permissions/permissions_email.html:108
msgid "ACCEPT"
msgstr "ŁŲØŁŁ"
-#: contentcuration/templates/permissions/permissions_email.html:112
+#: contentcuration/templates/permissions/permissions_email.html:111
msgid "DECLINE"
msgstr "Ų±ŁŲ¶"
@@ -498,24 +232,24 @@ msgstr "ŁŁŲÆ ŲŖŁ
ŲŖ ŲÆŲ¹ŁŲŖŁ ŁŲŖŲŲ±ŁŲ± %(channel)s"
msgid "You've been invited to view %(channel)s"
msgstr "ŁŁŲÆ ŲŖŁ
ŲŖ ŲÆŲ¹ŁŲŖŁ ŁŲ¹Ų±Ų¶ %(channel)s"
-#: contentcuration/templates/registration/activation_email.html:92
-msgid "Welcome to Kolibri! Here is the link to activate your account:"
-msgstr "Ł
Ų±ŲŲØŲ§ ŲØŁ ŁŁ ŁŁŁŁŲØŲ±Ł! Ų„ŁŁŁ Ų±Ų§ŲØŲ· ŲŖŁŲ¹ŁŁ ŲŲ³Ų§ŲØŁ:"
+#: contentcuration/templates/registration/activation_email.html:91
+msgid "Welcome to Kolibri Studio! Here is the link to activate your account:"
+msgstr "Ł
Ų±ŲŲØŲ§Ł ŲØŁ ŁŁ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł! Ų„ŁŁŁ Ų±Ų§ŲØŲ· ŲŖŁŲ¹ŁŁ ŲŲ³Ų§ŲØŁ:"
-#: contentcuration/templates/registration/activation_email.html:101
+#: contentcuration/templates/registration/activation_email.html:100
msgid "Click here to activate your account."
msgstr "Ų§ŁŁŲ± ŁŁŲ§ ŁŲŖŁŲ¹ŁŁ ŲŲ³Ų§ŲØŁ."
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
msgid "This link is valid for"
msgstr "ŁŲ°Ų§ Ų§ŁŲ±Ų§ŲØŲ· ŲµŲ§ŁŲ ŁŁ
ŲÆŲ©"
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
#, python-format
msgid "%(expiration_days)s days."
msgstr "%(expiration_days)s Ų£ŁŲ§Ł
."
-#: contentcuration/templates/registration/activation_email.html:104
+#: contentcuration/templates/registration/activation_email.html:103
msgid "ACTIVATE"
msgstr "ŲŖŁŲ¹ŁŁ"
@@ -543,27 +277,31 @@ msgstr "ŁŁŲÆ ŁŁ
ŲŖ ŲØŲ·ŁŲØ Ų±Ų§ŲØŲ· Ų„Ų¹Ų§ŲÆŲ© ŲŖŲ¹ŁŁŁ ŁŁŁ
Ų© Ų§ŁŁ
Ų±
msgid "Please activate your account by following the link below:"
msgstr "Ų§ŁŲ±Ų¬Ų§Ų” ŲŖŁŲ¹ŁŁ ŲŲ³Ų§ŲØŁ Ų¹Ł Ų·Ų±ŁŁ Ų§ŁŲ±Ų§ŲØŲ· Ų£ŲÆŁŲ§Ł"
-#: contentcuration/templates/registration/channel_published_email.txt:6
+#: contentcuration/templates/registration/channel_published_email.html:12
#, python-format
-msgid "%(channel_name)s has finished publishing! Here is the channel token (for importing it into Kolibri):"
-msgstr "Ų§ŁŲŖŁŲŖ %(channel_name)s Ł
Ł Ų§ŁŁŲ“Ų±! Ų„ŁŁŁ Ų±Ł
Ų² Ų§ŁŁŁŲ§Ų© (ŁŲ§Ų³ŲŖŁŲ±Ų§ŲÆŁ Ų„ŁŁ ŁŁŁŁŲØŲ±Ł)::"
+msgid "%(channel_name)s"
+msgstr "%(channel_name)s"
+
+#: contentcuration/templates/registration/channel_published_email.html:12
+msgid "has finished publishing! Here is the channel token (for importing it into Kolibri):"
+msgstr "ŁŲÆ Ų§Ų³ŲŖŁŁ
Ł Ų§ŁŁŲ“Ų±! Ų„ŁŁŁ Ų±Ł
Ų² Ų§ŁŁŁŲ§Ų© Ų§ŁŲŖŲ¹Ų±ŁŁŁ (ŁŲ§Ų³ŲŖŁŲ±Ų§ŲÆŁ Ų„ŁŁ ŁŁŁŁŲØŲ±Ł):"
-#: contentcuration/templates/registration/channel_published_email.txt:8
+#: contentcuration/templates/registration/channel_published_email.html:15
#, python-format
msgid "Token: %(channel_token)s"
msgstr "Ų§ŁŲ±Ł
Ų² Ų§ŁŲŖŲ¹Ų±ŁŁŁ: %(channel_token)s"
-#: contentcuration/templates/registration/channel_published_email.txt:10
+#: contentcuration/templates/registration/channel_published_email.html:17
#, python-format
msgid "ID (for Kolibri version 0.6.0 and below): %(channel_id)s"
msgstr "Ł
Ų¹Ų±ŁŁ (ŁŲ„ŲµŲÆŲ§Ų± 0.6.0 ŁŁ
Ų§ ŁŲØŁ Ł
Ł ŁŁŁŁŲØŲ±Ł): %(channel_id)s"
-#: contentcuration/templates/registration/channel_published_email.txt:12
+#: contentcuration/templates/registration/channel_published_email.html:20
#, python-format
msgid "Version notes: %(notes)s"
msgstr "Ł
ŁŲ§ŲŲøŲ§ŲŖ ŲŁŁ Ų§ŁŲ„ŲµŲÆŲ§Ų±: %(notes)s"
-#: contentcuration/templates/registration/channel_published_email.txt:21
+#: contentcuration/templates/registration/channel_published_email.html:28
msgid "You are receiving this email because you are subscribed to this channel."
msgstr " Ų§ŁŲŖ ŲŖŲŖŁŁŁ ŁŲ°Ų§ Ų§ŁŲØŲ±ŁŲÆ Ų§ŁŲ„ŁŁŲŖŲ±ŁŁŁ ŁŁ
Ų“ŲŖŲ±Ł ŁŁ ŁŲ°Ł Ų§ŁŁŁŲ§Ų©."
@@ -592,23 +330,23 @@ msgstr "Ų±Ų§ŲØŲ· Ų„Ų¹Ų§ŲÆŲ© ŲŖŲ¹ŁŁŁ ŁŁŁ
Ų© Ų§ŁŁ
Ų±ŁŲ± ŲŗŁŲ± ŲµŲ§ŁŲŲ
msgid "Request a new password reset."
msgstr "Ų·ŁŲØ Ų±Ų§ŲØŲ· Ų¬ŲÆŁŲÆ ŁŲ„Ų¹Ų§ŲÆŲ© ŲŖŲ¹ŁŁŁ ŁŁŁ
Ų© Ų§ŁŁ
Ų±ŁŲ±."
-#: contentcuration/templates/registration/password_reset_email.html:92
+#: contentcuration/templates/registration/password_reset_email.html:91
msgid "You are receiving this e-mail because you requested a password reset for your user account at"
msgstr "Ų£ŁŲŖ ŲŖŲŖŁŁŁ ŁŲ°Ų§ Ų§ŁŲØŲ±ŁŲÆ Ų§ŁŲ„ŁŁŲŖŲ±ŁŁŁ ŁŲ£ŁŁ Ų·ŁŲØŲŖ Ų„Ų¹Ų§ŲÆŲ© ŲŖŲ¹ŁŁŁ ŁŁŁ
Ų© Ų§ŁŁ
Ų±ŁŲ± ŁŲŲ³Ų§ŲØ Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
Ų§ŁŲ®Ų§Ųµ ŲØŁ Ų¹ŁŁ"
-#: contentcuration/templates/registration/password_reset_email.html:98
+#: contentcuration/templates/registration/password_reset_email.html:97
msgid "Reset my Password"
msgstr "Ų„Ų¹Ų§ŲÆŲ© ŲŖŲ¹ŁŁŁ ŁŁŁ
Ų© Ų§ŁŁ
Ų±ŁŲ± Ų§ŁŲ®Ų§ŲµŲ© ŲØŁ"
-#: contentcuration/templates/registration/password_reset_email.html:101
+#: contentcuration/templates/registration/password_reset_email.html:100
msgid "Please click the button below and choose a new password."
msgstr "Ų§ŁŁŲ± Ų¹ŁŁ Ų§ŁŲ²Ų± Ų£ŲÆŁŲ§Ł ŁŲ§Ų®ŲŖŁŲ§Ų± ŁŁŁ
Ų© Ł
Ų±ŁŲ± Ų¬ŲÆŁŲÆŲ©"
-#: contentcuration/templates/registration/password_reset_email.html:102
+#: contentcuration/templates/registration/password_reset_email.html:101
msgid "Your username is"
msgstr "Ų§Ų³Ł
Ų§ŁŁ
Ų³ŲŖŲ®ŲÆŁ
Ų§ŁŲ®Ų§Ųµ ŲØŁ ŁŁ"
-#: contentcuration/templates/registration/password_reset_email.html:104
+#: contentcuration/templates/registration/password_reset_email.html:103
msgid "RESET"
msgstr "Ų„Ų¹Ų§ŲÆŲ© ŲŖŲ¹ŁŁŁ"
@@ -651,6 +389,138 @@ msgstr "ŁŁŲÆ ŁŁ
ŲŖŁ ŲØŲ·ŁŲØ Ų„Ų¹Ų§ŲÆŲ© ŲŖŲ¹ŁŁŁ ŁŁŁ
Ų© Ų§ŁŁ
Ų±ŁŲ± Ų¹
msgid "Please create an account by following the link below:"
msgstr "Ų§ŁŲ±Ų¬Ų§Ų” ŲŖŁŲ¹ŁŁ ŲŲ³Ų§ŲØŁ Ų¹ŲØŲ± Ų§ŁŲ±Ų§ŲØŲ· Ų£ŲÆŁŲ§Ł:"
+#: contentcuration/templates/registration/welcome_new_user_email.html:78
+msgid "Welcome to Kolibri Studio!"
+msgstr "Ł
Ų±ŲŲØŲ§Ł ŲØŁ ŁŁ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł!"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:82
+#, python-format
+msgid "\n"
+" We're delighted to introduce you to Kolibri Studio , our curricular tool to add,\n"
+" organize, and manage your own resources or those from the Kolibri Content Library.\n"
+" "
+msgstr "\n"
+" ŁŁŲ³Ų¹ŲÆŁŲ§ Ų£Ł ŁŁŲÆŁ
ŁŁŁ
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł Ų ŁŁŁ Ų£ŲÆŲ§Ų© Ų§ŁŁ
ŁŁŲ§Ų¬ Ų®Ų§ŲµŲŖŁŲ§ ŁŲ„Ų¶Ų§ŁŲ© ŁŲŖŁŲøŁŁ
\n"
+" ŁŲ„ŲÆŲ§Ų±Ų© Ł
ŲµŲ§ŲÆŲ±Ł Ų£Ł ŲŖŁŁ Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŁŲ§ŲÆŁ
Ų© Ł
Ł Ł
ŁŲŖŲØŲ© Ł
ŲŲŖŁŁ ŁŁŁŁŲØŲ±Ł. "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:87
+msgid "View the Kolibri Content Library"
+msgstr "Ų¹Ų±Ų¶ Ł
ŁŲŖŲØŲ© Ł
ŲŲŖŁŁ ŁŁŁŁŲØŲ±Ł"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:93
+msgid "\n"
+" Using Kolibri Studio, you can explore pre-organized collections of open educational resources (OER), and bundle,\n"
+" tag, differentiate, re-order, and distribute them into custom channels.\n"
+" "
+msgstr "\n"
+" ŲØŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±ŁŲ ŁŁ
ŁŁŁ Ų§Ų³ŲŖŁŲ“Ų§Ł Ł
Ų¬Ł
ŁŲ¹Ų§ŲŖ Ł
ŁŲ³ŲØŁŲ© Ų§ŁŲŖŁŲøŁŁ
Ł
Ł Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų§ŁŲŖŲ¹ŁŁŁ
ŁŲ© Ų§ŁŁ
ŁŲŖŁŲŲ© (OER)Ų ŁŲŖŁŲøŁŁ
ŁŲ§ ŁŁ ŲŲ²Ł
\n"
+" ŁŲŖŁŲ¹ŁŁ Ų§ŁŁŲ³ŁŁ
ŁŲŖŁ
ŁŁŲ²ŁŲ§ ŁŲ„Ų¹Ų§ŲÆŲ© ŲŖŲ±ŲŖŁŲØŁŲ§ ŁŲŖŁŲ²ŁŲ¹ŁŲ§ Ų¹ŁŁ ŁŁŁŲ§ŲŖ Ł
ŁŲ®ŲµŲµŲ©. "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:99
+msgid "\n"
+" Using an admin account, you can then publish and import these custom channels--either your own or those shared\n"
+" with you -- into Kolibri with a unique \"token\" generated for each channel.\n"
+" "
+msgstr "\n"
+" ŲØŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
ŲŲ³Ų§ŲØ Ų§ŁŁ
ŁŲ“Ų±ŁŲ ŁŁ
ŁŁŁ ŁŲ“Ų± ŁŲ§Ų³ŲŖŁŲ±Ų§ŲÆ ŁŲ°Ł Ų§ŁŁŁŁŲ§ŲŖ Ų§ŁŁ
Ų®ŲµŲµŲ© Ų„ŁŁ ŁŁŁŁŲØŲ±Ł Ł
Ų¹ \"Ų±Ł
Ų²\" ŁŲ±ŁŲÆ ŲŖŁ
Ų„ŁŲ“Ų§Ų¤Ł ŁŁŁ ŁŁŲ§Ų©Ų Ų³ŁŲ§Ų” ŁŲ§ŁŲŖ ŁŁŁŲ§ŲŖŁ Ų£Ł ŁŁŁŲ§ŲŖ ŲŖŁ
ŲŖ Ł
Ų“Ų§Ų±ŁŲŖŁŲ§ Ł
Ų¹Ł.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:107
+msgid "\n"
+" Browse through the list of resources below* to learn more about Kolibri Studio and to begin creating your own\n"
+" custom channels:\n"
+" "
+msgstr "\n"
+" ŲŖŲµŁŲ ŁŲ§Ų¦Ł
Ų© Ų§ŁŁ
ŲµŲ§ŲÆŲ± Ų£ŲÆŁŲ§Ł* ŁŁ
Ų¹Ų±ŁŲ© Ų§ŁŁ
Ų²ŁŲÆ ŲŁŁ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł ŁŲ§ŁŲØŲÆŲ” ŁŁ Ų„ŁŲ“Ų§Ų”\n"
+" ŁŁŁŲ§ŲŖ Ł
Ų®ŲµŲµŲ©:\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:115
+msgid "Kolibri Studio User Guide"
+msgstr "ŲÆŁŁŁ Ł
Ų³ŲŖŲ®ŲÆŁ
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:120
+msgid "Content integration guide:"
+msgstr "ŲÆŁŁŁ ŲŖŁŲ§Ł
Ł Ų§ŁŁ
ŲŲŖŁŁ:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:122
+msgid "\n"
+" Information on licensing, compatible formats, technical integration and more.\n"
+" "
+msgstr "\n"
+" Ł
Ų¹ŁŁŁ
Ų§ŲŖ Ų¹Ł Ų§ŁŲŖŲ±Ų®ŁŲµ ŁŲ§ŁŲµŁŲŗ Ų§ŁŁ
ŲŖŁŲ§ŁŁŲ© ŁŲ§ŁŲŖŁŲ§Ł
Ł Ų§ŁŲŖŁŁŁ ŁŲ£ŁŲ«Ų± Ł
Ł Ų°ŁŁ.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:125
+msgid "\n"
+" Note that if you are adding a small number of resources, technical integration is not necessary. \n"
+" "
+msgstr "\n"
+" ŁŲ§ŲŲø Ų£ŁŁ Ų„Ų°Ų§ ŁŁŲŖ ŲŖŲ¶ŁŁ Ų¹ŲÆŲÆŲ§Ł ŲµŲŗŁŲ±Ų§Ł Ł
Ł Ų§ŁŁ
ŲµŲ§ŲÆŲ±Ų ŁŲ„Ł Ų§ŁŲŖŁŲ§Ł
Ł Ų§ŁŲŖŁŁŁ ŲŗŁŲ± Ų¶Ų±ŁŲ±Ł. \n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:130
+msgid "Step by step tutorials:"
+msgstr "ŲÆŲ±ŁŲ³ ŲŖŲÆŲ±ŁŲØŁŲ© Ų®Ų·ŁŲ© ŲØŲ®Ų·ŁŲ©:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:133
+msgid "Video format:"
+msgstr "ŲµŁŲŗŲ© Ų§ŁŁŁŲÆŁŁ:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:135
+msgid "Using Kolibri Studio: Your Content Workspace for Kolibri"
+msgstr "Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł: Ł
Ų³Ų§ŲŲ© Ų¹Ł
Ł Ų§ŁŁ
ŲŲŖŁŁ Ų§ŁŲ®Ų§Ųµ ŲØŁ ŁŁŁŁŁŲØŲ±Ł"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:137
+msgid "(*also available in French and Arabic)"
+msgstr "(*Ł
ŲŖŲ§Ų Ų£ŁŲ¶Ų§Ł ŲØŲ§ŁŁŲ±ŁŲ³ŁŲ© ŁŲ§ŁŲ¹Ų±ŲØŁŲ©)"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:140
+msgid "Slide gif format:"
+msgstr "Ų“Ų±ŁŲŲ© ŲØŲµŁŲŗŲ© Gif:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:144
+msgid "Step by step Studio tutorial"
+msgstr "ŲÆŲ±ŁŲ³ ŲŖŲÆŲ±ŁŲØŁŲ© ŁŁŲ§Ų³ŲŖŁŲÆŁŁ Ų®Ų·ŁŲ© ŲØŲ®Ų·ŁŲ©"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:151
+msgid "Video compression instructions:"
+msgstr "ŲŖŲ¹ŁŁŁ
Ų§ŲŖ Ų¶ŲŗŲ· Ų§ŁŁŁŲÆŁŁ:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:153
+msgid "\n"
+" For optimal results, videos should be compressed in order to achieve small file sizes. Compression ensures\n"
+" that the videos are well suited for offline distribution and playback on all Kolibri devices.\n"
+" "
+msgstr "\n"
+" ŁŲŖŲŁŁŁ Ų§ŁŁŲŖŲ§Ų¦Ų¬ Ų§ŁŁ
ŁŲ«ŁŁŲ ŁŲ¬ŲØ Ų¶ŲŗŲ· Ł
ŁŲ§Ų·Ų¹ Ų§ŁŁŁŲÆŁŁ Ł
Ł Ų£Ų¬Ł ŲŖŲŁŁŁ Ų£ŲŲ¬Ų§Ł
ŲµŲŗŁŲ±Ų© Ł
Ł Ų§ŁŁ
ŁŁŲ§ŲŖ. ŁŲ¶Ł
Ł Ų§ŁŲ¶ŲŗŲ·\n"
+" Ų£Ł ŲŖŁŁŁ Ł
ŁŲ§Ų·Ų¹ Ų§ŁŁŁŲÆŁŁ Ł
ŁŲ§Ų³ŲØŲ© ŲŖŁ
Ų§Ł
Ų§Ł ŁŁŲŖŁŲ²ŁŲ¹ ŲÆŁŁ Ų§ŲŖŲµŲ§Ł ŁŲ§ŁŲŖŲ“ŲŗŁŁ Ų¹ŁŁ Ų¬Ł
ŁŲ¹ Ų£Ų¬ŁŲ²Ų© ŁŁŁŁŲØŲ±Ł.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:158
+msgid "View the guide to video compression"
+msgstr "Ų¹Ų±Ų¶ Ų§ŁŲÆŁŁŁ ŁŲ¶ŲŗŲ· Ų§ŁŁŁŲÆŁŁ"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:165
+msgid "If you need support with Kolibri Studio, please reach out to us on our Community Forum."
+msgstr "Ų„Ų°Ų§ ŁŁŲŖ ŲØŲŲ§Ų¬Ų© Ų„ŁŁ ŲÆŲ¹Ł
ŁŲŖŲ¹ŁŁ ŲØŲ§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±ŁŲ ŁŲ±Ų¬Ł Ų§ŁŲŖŁŲ§ŲµŁ Ł
Ų¹ŁŲ§ Ų¹ŁŁ Ł
ŁŲŖŲÆŁŲ§ŲŖ Ł
Ų¬ŲŖŁ
Ų¹ŁŲ§."
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:167
+msgid "Access the Community Forum"
+msgstr "Ų§ŁŁŲµŁŁ Ų„ŁŁ Ł
ŁŲŖŲÆŁŲ§ŲŖ Ų§ŁŁ
Ų¬ŲŖŁ
Ų¹"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:171
+msgid "Thank You!"
+msgstr "Ų“ŁŲ±Ų§Ł ŁŁ!"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:178
+msgid "*resources are presented in English"
+msgstr "*Ų§ŁŁ
ŁŲ§Ų±ŲÆ Ł
Ų¹Ų±ŁŲ¶Ų© ŲØŲ§ŁŲ„ŁŲ¬ŁŁŲ²ŁŲ©"
+
+#: contentcuration/templates/registration/welcome_new_user_email_subject.txt:1
+msgid "Thank you for activating your Kolibri Studio account! Let's get started..."
+msgstr "Ų“ŁŲ±Ų§Ł ŁŁ ŁŲŖŁŲ¹ŁŁ ŲŲ³Ų§ŲØ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł Ų§ŁŲ®Ų§Ųµ ŲØŁ! ŁŁŲ§ ŁŲØŲÆŲ£..."
+
#: contentcuration/templates/settings/account_deleted_user_email.txt:5
#, python-format
msgid "Your %(email)s account on %(site_name)s has been deleted."
@@ -713,188 +583,107 @@ msgstr "ŁŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±ŁŲ ŁŁŲµŲ ŲØŲ§Ų³ŲŖŲ®ŲÆŲ§
msgid "You can also try updating your current browser."
msgstr "ŁŁ
ŁŁŁ Ų£ŁŲ¶Ų§Ł Ł
ŲŲ§ŁŁŲ© ŲŖŲŲÆŁŲ« Ł
ŲŖŲµŁŲŁ Ų§ŁŲŲ§ŁŁ."
-#: contentcuration/templatetags/license_tags.py:10
+#: contentcuration/templatetags/license_tags.py:11
msgid "The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials."
msgstr "ŁŲ³Ł
Ų ŲŖŲ±Ų®ŁŲµ Attribution ŁŁŲ¢Ų®Ų±ŁŁ ŲØŲŖŁŲ²ŁŲ¹ Ų¹Ł
ŁŁ ŁŲ„Ų¹Ų§ŲÆŲ© ŲÆŁ
Ų¬Ł ŁŲŖŲ¹ŲÆŁŁŁ ŁŲ§ŁŲØŁŲ§Ų” Ų¹ŁŁŁ ŁŁ Ų¹Ł
ŁŁŲ ŁŲŲŖŁ ŲŖŲ¬Ų§Ų±ŁŁŲ§Ų Ų·Ų§ŁŁ
Ų§ ŁŁŲ³ŲØŁŁ ŁŁ Ų§ŁŁŲ¶Ł ŁŁ Ų§ŁŲ„ŁŲ“Ų§Ų” Ų§ŁŲ£ŲµŁŁ. ŁŲ°Ų§ ŁŁ Ų£ŁŲ«Ų± Ų§ŁŲŖŲ±Ų§Ų®ŁŲµ Ų§ŁŁ
ŁŲÆŁ
Ų© ŲŖŁŲ§ŁŁŲ§Ł ŁŁŁŲµŁ ŲØŁ ŁŁŲŲµŁŁ Ų¹ŁŁ Ų£ŁŲØŲ± ŁŲÆŲ± Ł
Ł ŁŲ“Ų± ŁŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§ŁŁ
ŁŲ§ŲÆ Ų§ŁŁ
Ų±Ų®ŲµŲ©."
-#: contentcuration/templatetags/license_tags.py:15
+#: contentcuration/templatetags/license_tags.py:16
msgid "The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to \"copyleft\" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects."
msgstr "ŁŲ³Ł
Ų ŲŖŲ±Ų®ŁŲµ Attribution-ShareAlike ŁŁŲ¢Ų®Ų±ŁŁ ŲØŲŖŁŲ²ŁŲ¹ Ų¹Ł
ŁŁ ŁŲ„Ų¹Ų§ŲÆŲ© ŲÆŁ
Ų¬Ł ŁŲŖŲ¹ŲÆŁŁŁ ŁŲ§ŁŲØŁŲ§Ų” Ų¹ŁŁŁ ŁŁ Ų¹Ł
ŁŁŲ ŁŲŲŖŁ ŲŖŲ¬Ų§Ų±ŁŲ§ŁŲ Ų·Ų§ŁŁ
Ų§ ŁŁŲ³ŲØŁŁ ŁŁ Ų§ŁŁŲ¶Ł ŁŁŲ±Ų®ŲµŁŁ Ų„ŁŲ“Ų§Ų”Ų§ŲŖŁŁ
Ų§ŁŲ¬ŲÆŁŲÆŲ© ŲŖŲŲŖ Ų§ŁŲ“Ų±ŁŲ· Ų°Ų§ŲŖŁŲ§. ŁŲŗŲ§ŁŲØŲ§Ł Ł
Ų§ ŲŖŲŖŁ
Ł
ŁŲ§Ų±ŁŲ© ŁŲ°Ų§ Ų§ŁŲŖŲ±Ų®ŁŲµ ŲØŲŖŲ±Ų§Ų®ŁŲµ Ų§ŁŲØŲ±Ų§Ł
Ų¬ Ų§ŁŁ
Ų¬Ų§ŁŁŲ© ŁŁ
ŁŲŖŁŲŲ© Ų§ŁŁ
ŲµŲÆŲ± \"Ų§ŁŲŁŁŁ Ų§ŁŁ
ŲŖŲ±ŁŁŲ©\". Ų³ŲŖŲŁ
Ł ŁŁ Ų§ŁŲ£Ų¹Ł
Ų§Ł Ų§ŁŲ¬ŲÆŁŲÆŲ© Ų§ŁŁ
ŲØŁŁŲ© Ų¹ŁŁ Ų£Ų¹Ł
Ų§ŁŁ Ų§ŁŲŖŲ±Ų®ŁŲµ ŁŁŲ³ŁŲ ŁŁŲ°ŁŁŲ Ų³ŲŖŲ³Ł
Ų Ų£ŁŲ© Ł
Ų“ŲŖŁŲ§ŲŖ Ų£ŁŲ¶Ų§Ł ŲØŲ§ŁŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§ŁŲŖŲ¬Ų§Ų±Ł. Ų„ŁŁ Ų°Ų§ŲŖ Ų§ŁŲŖŲ±Ų®ŁŲµ Ų§ŁŲ°Ł ŲŖŲ³ŲŖŲ®ŲÆŁ
Ł ŁŁŁŁŲØŁŲÆŁŲ§Ų ŁŁŁŲµŁ ŲØŁ ŁŁŁ
ŁŲ§ŲÆ Ų§ŁŲŖŁ ŁŲÆ ŲŖŲ³ŲŖŁŁŲÆ Ł
Ł ŲÆŁ
Ų¬ Ł
ŲŲŖŁŁ Ł
Ł ŁŁŁŁŲØŁŲÆŁŲ§ ŁŁ
Ų“Ų§Ų±ŁŲ¹ Ł
Ų±Ų®ŲµŲ© Ł
Ł
Ų§Ų«ŁŲ©."
-#: contentcuration/templatetags/license_tags.py:25
+#: contentcuration/templatetags/license_tags.py:26
msgid "The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you."
msgstr "ŁŲ³Ł
Ų ŲŖŲ±Ų®ŁŲµ Attribution-NoDerivs ŲØŲ„Ų¹Ų§ŲÆŲ© Ų§ŁŲŖŁŲ²ŁŲ¹Ų Ų§ŁŲŖŲ¬Ų§Ų±Ł ŁŲŗŁŲ± Ų§ŁŲŖŲ¬Ų§Ų±ŁŲ Ų·Ų§ŁŁ
Ų§ Ų£ŁŁ ŁŲŖŁ
ŁŁŁŁ ŲØŲ“ŁŁ ŁŲ§Ł
Ł Ł
Ł ŲÆŁŁ ŲŖŲŗŁŁŲ±Ų Ł
Ų¹ ŁŲ³ŲØ Ų§ŁŁŲ¶Ł ŁŁ."
-#: contentcuration/templatetags/license_tags.py:28
+#: contentcuration/templatetags/license_tags.py:29
msgid "The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms."
msgstr "ŁŲ³Ł
Ų ŲŖŲ±Ų®ŁŲµ Attribution-NonCommercial ŁŁŲ¢Ų®Ų±ŁŁ ŲØŲ„Ų¹Ų§ŲÆŲ© ŲÆŁ
Ų¬ Ų£Ų¹Ł
Ų§ŁŁ ŁŲŖŲ¹ŲÆŁŁŁŲ§ ŁŲ§ŁŲØŁŲ§Ų” Ų¹ŁŁŁŲ§ ŲØŲ“ŁŁ ŲŗŁŲ± ŲŖŲ¬Ų§Ų±ŁŲ ŁŲ¹ŁŁ Ų§ŁŲ±ŲŗŁ
Ł
Ł Ų£Ł Ų£Ų¹Ł
Ų§ŁŁŁ
Ų§ŁŲ¬ŲÆŁŲÆŲ© ŁŲ¬ŲØ Ų£Ł ŲŖŁŲ³ŲØ Ų§ŁŁŲ¶Ł ŁŁ ŁŲ£Ł ŲŖŁŁŁ ŲŗŁŲ± ŲŖŲ¬Ų§Ų±ŁŲ©Ų ŁŁŲ§ ŁŲŖŲ¹ŁŁ Ų¹ŁŁŁŁ
ŲŖŲ±Ų®ŁŲµ Ų£Ų¹Ł
Ų§ŁŁŁ
Ų§ŁŁ
Ų“ŲŖŁŲ© ŁŁŁŲ§Ł ŁŁŲ“Ų±ŁŲ· ŁŁŲ³ŁŲ§."
-#: contentcuration/templatetags/license_tags.py:32
+#: contentcuration/templatetags/license_tags.py:33
msgid "The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms."
msgstr "ŁŲŖŁŲ ŲŖŲ±Ų®ŁŲµ Attribution-NonCommercial-ShareAlike ŁŁŲ¢Ų®Ų±ŁŁ Ų„Ų¹Ų§ŲÆŲ© Ł
Ų²Ų¬ Ų£Ų¹Ł
Ų§ŁŁ ŁŲŖŲ¹ŲÆŁŁŁŲ§ ŁŲ§ŁŲØŁŲ§Ų” Ų¹ŁŁŁŲ§ ŲØŲ“ŁŁ ŲŗŁŲ± ŲŖŲ¬Ų§Ų±ŁŲ Ų·Ų§ŁŁ
Ų§ Ų£ŁŁŁ
ŁŁŲ³ŲØŁŁ Ų§ŁŲ¹Ł
Ł Ų„ŁŁŁ ŁŁŲ±Ų®ŲµŁŁ Ų„ŲØŲÆŲ§Ų¹Ų§ŲŖŁŁ
Ų§ŁŲ¬ŲÆŁŲÆŲ© ŁŁŁŁŲ§ ŁŁŲ“Ų±ŁŲ· Ų§ŁŁ
Ł
Ų§Ų«ŁŲ©."
-#: contentcuration/templatetags/license_tags.py:36
+#: contentcuration/templatetags/license_tags.py:37
msgid "The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially."
msgstr "ŁŲ¹ŲŖŲØŲ± ŲŖŲ±Ų®ŁŲµ Attribution-NonCommercial-NoDerivs Ų§ŁŲ£ŁŲ«Ų± ŲŖŁŁŁŲÆŲ§Ł Ł
Ł ŲØŁŁ ŲŖŲ±Ų§Ų®ŁŲµŁŲ§ Ų§ŁŲ³ŲŖŲ© Ų§ŁŲ±Ų¦ŁŲ³ŁŲ©Ų ŲŁŲ« ŁŲ³Ł
Ų ŁŁŲ¢Ų®Ų±ŁŁ ŲØŲŖŁŲ²ŁŁ Ų£Ų¹Ł
Ų§ŁŁ ŁŁ
Ų“Ų§Ų±ŁŲŖŁŲ§ Ł
Ų¹ Ų§ŁŲ¢Ų®Ų±ŁŁ Ų·Ų§ŁŁ
Ų§ Ų£ŁŁŁ
ŁŁŲ³ŲØŁŁ Ų§ŁŲ¹Ł
Ł Ų„ŁŁŁŲ ŁŁŁŁ ŁŲ§ ŁŁ
ŁŁŁŁ
ŲŖŲŗŁŁŲ±ŁŲ§ ŲØŲ£Ł Ų“ŁŁ Ł
Ł Ų§ŁŲ£Ų“ŁŲ§Ł Ų£Ł Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
ŁŲ§ ŲŖŲ¬Ų§Ų±ŁŲ§Ł."
-#: contentcuration/templatetags/license_tags.py:40
+#: contentcuration/templatetags/license_tags.py:41
msgid "The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty."
msgstr "ŲŖŲ“ŁŲ± Ų±Ų®ŲµŲ© -Ų¬Ł
ŁŲ¹ Ų§ŁŲŁŁŁ Ł
ŲŁŁŲøŲ©- Ų„ŁŁ Ų£Ł ŲµŲ§ŲŲØ ŲŁŁŁ Ų§ŁŲ·ŲØŲ¹ ŁŲ§ŁŁŲ“Ų± ŁŲŲŖŁŲø ŲØŲ¬Ł
ŁŲ¹ Ų§ŁŲŁŁŁ Ų§ŁŁ
ŁŲµŁŲµ Ų¹ŁŁŁŲ§ ŁŁ ŁŲ§ŁŁŁ ŲŁŁŁ Ų§ŁŲ·ŲØŲ¹ ŁŲ§ŁŁŲ“Ų± ŲØŁ
ŁŲ¬ŲØ Ų§ŲŖŁŲ§ŁŁŲ© ŲŁŁŁ ŁŲ“Ų± Ł
ŲŲÆŲÆŲ© Ų£Ł ŁŲŲŖŁŲø ŲØŁŲ§ ŁŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ł Ų§ŁŲ®Ų§Ųµ."
-#: contentcuration/templatetags/license_tags.py:43
+#: contentcuration/templatetags/license_tags.py:44
msgid "Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights."
msgstr "ŲŖŁ
ŲŖŲŲÆŁŲÆ Ų£Ų¹Ł
Ų§Ł Ų§ŁŁ
ŁŁŁŲ© Ų§ŁŲ¹Ų§Ł
Ų¹ŁŁ Ų£ŁŁŲ§ Ų®Ų§ŁŁŲ© Ł
Ł Ų§ŁŁŁŁŲÆ Ų§ŁŁ
Ų¹Ų±ŁŁŲ© ŲØŁ
ŁŲ¬ŲØ ŁŲ§ŁŁŁ ŲŁŁŁ Ų§ŁŲ·ŲØŲ¹ ŁŲ§ŁŁŲ“Ų±Ų ŲØŁ
Ų§ ŁŁ Ų°ŁŁ Ų¬Ł
ŁŲ¹ Ų§ŁŲŁŁŁ Ų§ŁŁ
Ų¬Ų§ŁŲ±Ų© ŁŲ§ŁŁ
Ų±ŲŖŲØŲ·Ų©."
-#: contentcuration/templatetags/license_tags.py:46
+#: contentcuration/templatetags/license_tags.py:47
msgid "Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails."
msgstr "Ų§ŁŲŖŲ±Ų§Ų®ŁŲµ Ų§ŁŲ®Ų§ŲµŲ© ŁŁ ŲŖŲ±Ų§Ų®ŁŲµ Ł
Ų®ŲµŲµŲ© ŁŁŲ§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų¹ŁŲÆŁ
Ų§ ŁŲ§ ŲŖŁŲ·ŲØŁ Ų§ŁŲŖŲ±Ų§Ų®ŁŲµ Ų§ŁŲŲ§ŁŁŲ© Ų¹ŁŁ Ų§ŁŁ
ŲŲŖŁŁ. ŁŁŁŁ ŲµŲ§ŲŲØ ŁŲ°Ų§ Ų§ŁŲŖŲ±Ų®ŁŲµ Ł
Ų³Ų¤ŁŁŲ§Ł Ų¹Ł Ų„ŁŲ“Ų§Ų” ŁŲµŁ ŁŁ
Ų§ ŁŲŖŲ·ŁŲØŁ ŁŲ°Ų§ Ų§ŁŲŖŲ±Ų®ŁŲµ."
-#: contentcuration/templatetags/translation_tags.py:26
-msgid "100% Correct"
-msgstr "Ł”Ł Ł ŁŖ ŲµŲŁŲ"
-
-#: contentcuration/templatetags/translation_tags.py:27
-msgid "10 in a row"
-msgstr "Ł”Ł Ų¹ŁŁ Ų§ŁŲŖŁŲ§ŁŁ"
-
-#: contentcuration/templatetags/translation_tags.py:28
-msgid "2 in a row"
-msgstr "Ł¢ Ų¹ŁŁ Ų§ŁŲŖŁŲ§ŁŁ"
-
-#: contentcuration/templatetags/translation_tags.py:29
-msgid "3 in a row"
-msgstr "Ł£ Ų¹ŁŁ Ų§ŁŲŖŁŲ§ŁŁ"
-
-#: contentcuration/templatetags/translation_tags.py:30
-msgid "5 in a row"
-msgstr "Ł„ Ų¹ŁŁ Ų§ŁŲŖŁŲ§ŁŁ"
-
-#: contentcuration/templatetags/translation_tags.py:31
-msgid "M of N..."
-msgstr "M Ł
Ł N..."
-
-#: contentcuration/templatetags/translation_tags.py:32
-msgid "CC BY"
-msgstr "CC BY"
-
-#: contentcuration/templatetags/translation_tags.py:33
-msgid "CC BY-SA"
-msgstr "CC BY-SA"
-
-#: contentcuration/templatetags/translation_tags.py:34
-msgid "CC BY-ND"
-msgstr "CC BY-ND"
-
-#: contentcuration/templatetags/translation_tags.py:35
-msgid "CC BY-NC"
-msgstr "CC BY-NC"
-
-#: contentcuration/templatetags/translation_tags.py:36
-msgid "CC BY-NC-SA"
-msgstr "CC BY-NC-SA"
-
-#: contentcuration/templatetags/translation_tags.py:37
-msgid "CC BY-NC-ND"
-msgstr "CC BY-NC-ND"
-
-#: contentcuration/templatetags/translation_tags.py:38
-msgid "All Rights Reserved"
-msgstr "Ų¬Ł
ŁŲ¹ Ų§ŁŲŁŁŁ Ł
ŲŁŁŲøŲ©"
-
-#: contentcuration/templatetags/translation_tags.py:39
-msgid "Public Domain"
-msgstr "Ų§ŁŁ
ŁŁŁŲ© Ų§ŁŲ¹Ų§Ł
Ų©"
-
-#: contentcuration/templatetags/translation_tags.py:40
-msgid "Special Permissions"
-msgstr "ŲŖŲ±Ų§Ų®ŁŲµ Ų®Ų§ŲµŲ©"
-
-#: contentcuration/templatetags/translation_tags.py:49
-#, python-format
-msgid "%(filesize)s %(unit)s"
-msgstr "%(filesize)s %(unit)s"
-
-#: contentcuration/utils/csv_writer.py:138
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:45
+#: contentcuration/utils/csv_writer.py:108
msgid "No Channel"
msgstr "ŁŲ§ ŲŖŁŲ¬ŲÆ ŁŁŁŲ§ŲŖ"
-#: contentcuration/utils/csv_writer.py:139
+#: contentcuration/utils/csv_writer.py:46
msgid "No resource"
msgstr "ŁŲ§ ŲŖŁŲ¬ŲÆ Ł
ŲµŲ§ŲÆŲ±"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Channel"
msgstr "Ų§ŁŁŁŲ§Ų© Ų§ŁŲŖŲ¹ŁŁŁ
ŁŲ©"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Title"
msgstr "Ų§ŁŲ¹ŁŁŲ§Ł"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Kind"
msgstr "Ų§ŁŁŁŲ¹"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Filename"
msgstr "Ų§Ų³Ł
Ų§ŁŁ
ŁŁŁ"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
+msgid "File Size"
+msgstr "ŲŲ¬Ł
Ų§ŁŁ
ŁŁ"
+
+#: contentcuration/utils/csv_writer.py:71
msgid "URL"
msgstr "Ų¹ŁŁŲ§Ł URL"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Description"
msgstr "Ų§ŁŲŖŁŁŲµŁŁ"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Author"
msgstr "Ų§ŁŁ
Ų¤ŁŁ"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Language"
msgstr "Ų§ŁŁŲŗŲ©"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License"
msgstr "Ų§ŁŲŖŲ±Ų®ŁŲµ"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License Description"
msgstr "ŁŲµŁ Ų§ŁŲŖŲ±Ų®ŁŲµ"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Copyright Holder"
msgstr "Ł
Ų§ŁŁ ŲŁŁŁ Ų§ŁŁŲ“Ų±"
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "No Resource"
msgstr "ŁŲ§ ŲŖŁŲ¬ŲÆ Ł
ŲµŲ§ŲÆŲ±"
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "Staged File"
msgstr "Ł
ŁŁ Ł
Ų±ŲŁŁ"
-#: contentcuration/utils/format.py:15
-msgid "B"
-msgstr "ŲØŲ§ŁŲŖ"
-
-#: contentcuration/utils/format.py:17
-msgid "KB"
-msgstr "ŁŁŁŁ ŲØŲ§ŁŲŖ"
-
-#: contentcuration/utils/format.py:19
-msgid "MB"
-msgstr "Ł
ŁŲŗŲ§ ŲØŲ§ŁŲŖ"
-
-#: contentcuration/utils/format.py:21
-msgid "GB"
-msgstr "Ų¬ŁŲŗŲ§ ŲØŲ§ŁŲŖ"
-
-#: contentcuration/utils/format.py:23
-msgid "TB"
-msgstr "ŲŖŁŲ±Ų§ ŲØŲ§ŁŲŖ"
-
#: contentcuration/utils/incidents.py:7
msgid "There was a problem with a third-party service. This means certain operations might be blocked. We appreciate your patience while these issues are being resolved."
msgstr "ŲŲÆŲ«ŲŖ Ł
Ų“ŁŁŲ© ŁŁ Ų®ŲÆŁ
Ų© Ų®Ų§ŲµŲ© ŲØŲ¬ŁŲ© Ų®Ų§Ų±Ų¬ŁŲ©Ų ŁŁŲ°Ų§ ŁŲ¹ŁŁ Ų£ŁŁ ŁŲÆ ŁŲŖŁ
ŲŲøŲ± ŲØŲ¹Ų¶ Ų§ŁŲ¹Ł
ŁŁŲ§ŲŖ. ŁŁŲÆŲ± ŲµŲØŲ±Ł Ų£Ų«ŁŲ§Ų” ŲŁ ŁŲ°Ł Ų§ŁŁ
Ų“ŁŁŲ§ŲŖ."
@@ -919,23 +708,26 @@ msgstr "ŁŁŲ§Ų¬Ł Ł
Ų“ŁŁŲ§ŲŖ Ł
Ų¹ Ų®ŲÆŁ
Ų© Ł
Ų±ŲŖŲØŲ·Ų© ŲØŲ¬ŁŲ© Ų®Ų§Ų±Ų¬ŁŲ©
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here "
msgstr " ŁŁŲ§Ų¬Ł Ł
Ų“ŁŁŲ§ŲŖ Ł
Ų¹ Ł
Ų±ŁŲ² Ų§ŁŲØŁŲ§ŁŲ§ŲŖ Ų§ŁŲ®Ų§Ųµ ŲØŁŲ§. ŁŲ°Ų§ ŁŲ¹ŁŁ Ų£ŁŁ ŁŲÆ ŲŖŁŲ§Ų¬Ł Ł
Ų“Ų§ŁŁ ŁŁ Ų§ŁŲ“ŲØŁŲ§ŲŖ Ų£Ų«ŁŲ§Ų” Ų§Ų³ŲŖŲ®ŲÆŲ§Ł
Ų§ŁŲ§Ų³ŲŖŁŲÆŁŁ. ŁŁŲÆŲ± ŲµŲØŲ±Ł Ų£Ų«ŁŲ§Ų” ŲŁ ŁŲ°Ł Ų§ŁŁ
Ų“ŁŁŲ§ŲŖ. ŁŁŲŖŲŁŁ Ł
Ł ŲŲ§ŁŲ© ŁŲ°Ł Ų§ŁŲ®ŲÆŁ
Ų© ŁŁ
ŲØŲ²ŁŲ§Ų±Ų© ŁŁŲ§ "
-#: contentcuration/utils/publish.py:57
+#: contentcuration/utils/publish.py:96
msgid "Kolibri Studio Channel Published"
msgstr "ŲŖŁ
ŁŲ“Ų± ŁŁŲ§Ų© Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł"
-#: contentcuration/views/public.py:63 contentcuration/views/public.py:74
-msgid "Api endpoint {} is not available"
-msgstr "ŁŁŲ·Ų© ŁŁŲ§ŁŲ© Api {} ŲŗŁŲ± Ł
ŲŖŁŁŲ±Ų©"
-
-#: contentcuration/views/public.py:76
-msgid "No channel matching {} found"
-msgstr "ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ ŁŁŲ§Ų© Ł
Ų·Ų§ŲØŁŲ© {}"
-
-#: contentcuration/views/settings.py:110
+#: contentcuration/views/settings.py:111
msgid "Kolibri Studio issue report"
msgstr "Ų§ŁŲ„ŲØŁŲ§Ųŗ Ų¹Ł Ł
Ų“ŁŁŲ© ŁŁ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł"
-#: contentcuration/views/settings.py:144
+#: contentcuration/views/settings.py:143
msgid "Kolibri Studio account deleted"
msgstr "ŲŖŁ
ŲŲ°Ł ŲŲ³Ų§ŲØ Ų§Ų³ŲŖŁŲÆŁŁ ŁŁŁŁŲØŲ±Ł"
+#: kolibri_public/views.py:220
+msgid "Resource"
+msgstr "Ł
ŲµŲÆŲ±"
+
+#: kolibri_public/views_v1.py:63 kolibri_public/views_v1.py:74
+msgid "Api endpoint {} is not available"
+msgstr "ŁŁŲ·Ų© ŁŁŲ§ŁŲ© Api {} ŲŗŁŲ± Ł
ŲŖŁŁŲ±Ų©"
+
+#: kolibri_public/views_v1.py:76
+msgid "No channel matching {} found"
+msgstr "ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ ŁŁŲ§Ų© Ł
Ų·Ų§ŲØŁŲ© {}"
diff --git a/contentcuration/locale/en/LC_MESSAGES/README.md b/contentcuration/locale/en/LC_MESSAGES/README.md
new file mode 100644
index 0000000000..0f82b94d50
--- /dev/null
+++ b/contentcuration/locale/en/LC_MESSAGES/README.md
@@ -0,0 +1 @@
+The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
diff --git a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.csv b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.csv
new file mode 100644
index 0000000000..df9d1fd014
--- /dev/null
+++ b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.csv
@@ -0,0 +1,4586 @@
+Identifier,Source String,Context,Translation
+AccessibilityOptions.altText,Visual elements in the resource have descriptions that can be accessed by screen readers for the benefit of blind learners,"
+-- CONTEXT --
+",
+AccessibilityOptions.audioDescription,The resource contains a second narration audio track that provides additional information for the benefit of blind users and those with low vision,"
+-- CONTEXT --
+",
+AccessibilityOptions.highContrast,The resource text and visual elements are displayed with high contrast for the benefit of users with low vision,"
+-- CONTEXT --
+",
+AccessibilityOptions.signLanguage,Synchronized sign language intepretation is available for audio and video content,"
+-- CONTEXT --
+",
+AccessibilityOptions.taggedPdf,The document contains PDF tags that can be accessed by screen readers for the benefit of blind learners,"
+-- CONTEXT --
+",
+Account.apiDocumentation,API documentation,"
+-- CONTEXT --
+",
+Account.apiTokenHeading,API Token,"
+-- CONTEXT --
+",
+Account.apiTokenMessage,You will need this access token to run content integration scripts for bulk-uploading materials through the Kolibri Studio API.,"
+-- CONTEXT --
+",
+Account.basicInfoHeader,Basic Information,"
+-- CONTEXT --
+",
+Account.changePasswordAction,Change password,"
+-- CONTEXT --
+",
+Account.completelyDeleteAccountLabel,Completely remove your account from Kolibri Studio,"
+-- CONTEXT --
+",
+Account.deleteAccountLabel,Delete account,"
+-- CONTEXT --
+",
+Account.editFullNameAction,Edit full name,"
+-- CONTEXT --
+",
+Account.exportAccountDataHeading,Export account data,"
+-- CONTEXT --
+",
+Account.exportAccountDataLabel,You will receive an email with all information linked to your account,"
+-- CONTEXT --
+",
+Account.exportAccountDataModalMessage,You'll receive an email with your data when the export is completed,"
+-- CONTEXT --
+",
+Account.exportDataButton,Export data,"
+-- CONTEXT --
+",
+Account.exportFailed,Unable to export data. Please try again.,"
+-- CONTEXT --
+",
+Account.exportStartedHeader,Data export started,"
+-- CONTEXT --
+",
+Account.fullNameLabel,Full name,"
+-- CONTEXT --
+",
+Account.handleChannelsBeforeAccount,You must delete these channels manually or invite others to edit them before you can delete your account.,"
+-- CONTEXT --
+",
+Account.passwordLabel,Password,"
+-- CONTEXT --
+",
+Account.unableToDeleteAdminAccount,Unable to delete an admin account,"
+-- CONTEXT --
+",
+Account.usernameLabel,Username,"
+-- CONTEXT --
+",
+AccountCreated.accountCreatedTitle,Account successfully created,"
+-- CONTEXT --
+",
+AccountCreated.backToLogin,Continue to sign-in page,"
+-- CONTEXT --
+",
+AccountDeleted.accountDeletedTitle,Account successfully deleted,"
+-- CONTEXT --
+",
+AccountDeleted.backToLogin,Continue to sign-in page,"
+-- CONTEXT --
+",
+AccountNotActivated.requestNewLink,Request a new activation link,"
+-- CONTEXT --
+",
+AccountNotActivated.text,Please check your email for an activation link or request a new link.,"
+-- CONTEXT --
+",
+AccountNotActivated.title,Account has not been activated,"
+-- CONTEXT --
+",
+ActivationExpired.activationExpiredText,This activation link has been used already or has expired.,"
+-- CONTEXT --
+",
+ActivationExpired.activationExpiredTitle,Activation failed,"
+-- CONTEXT --
+",
+ActivationExpired.requestNewLink,Request a new activation link,"
+-- CONTEXT --
+",
+ActivationLinkReSent.activationReSentText,"If there is already an account with the email address provided, you should receive instructions shortly. If you don't see an email from us, please check your spam folder.","
+-- CONTEXT --
+",
+ActivationLinkReSent.activationReSentTitle,Instructions sent. Thank you!,"
+-- CONTEXT --
+",
+ActivationSent.header,Activation link sent,"
+-- CONTEXT --
+",
+ActivationSent.text,"Thank you for creating an account! To complete the process, please check your email for the activation link we sent you.","
+-- CONTEXT --
+",
+ActivityDuration.minutesRequired,Minutes,"
+-- CONTEXT --
+",
+ActivityDuration.notOptionalLabel,Time required for the resource to be marked as completed. This value will not be displayed to learners.,"
+-- CONTEXT --
+",
+ActivityDuration.optionalLabel,(Optional) Time required for the resource to be marked as completed. This value will not be displayed to learners.,"
+-- CONTEXT --
+",
+AddNextStepsPage.addedNextStepSnackbar,Added next step,"
+-- CONTEXT --
+",
+AddNextStepsPage.toolbarTitle,Add next step,"
+-- CONTEXT --
+",
+AddPreviousStepsPage.addedPreviousStepSnackbar,Added previous step,"
+-- CONTEXT --
+",
+AddPreviousStepsPage.toolbarTitle,Add previous step,"
+-- CONTEXT --
+",
+AddRelatedResourcesModal.addStepBtnLabel,Add,"
+-- CONTEXT --
+",
+AddRelatedResourcesModal.cancelBtnLabel,Cancel,"
+-- CONTEXT --
+",
+AddRelatedResourcesModal.previewStepBtnLabel,Preview,"
+-- CONTEXT --
+",
+AddRelatedResourcesModal.resourcesDisplayedText,Only showing available resources for,"
+-- CONTEXT --
+",
+AddRelatedResourcesModal.selectedAsCurrentResource,This is the current resource,"
+-- CONTEXT --
+",
+AddRelatedResourcesModal.selectedAsNextStep,Already selected as a next step,"
+-- CONTEXT --
+",
+AddRelatedResourcesModal.selectedAsPreviousStep,Already selected as a previous step,"
+-- CONTEXT --
+",
+AdministrationAppError.unauthorizedDetails,You need to be an administrator of Studio to view this page,"
+-- CONTEXT --
+",
+AdministrationIndex.channelsLabel,Channels,"
+-- CONTEXT --
+",
+AdministrationIndex.usersLabel,Users,"
+-- CONTEXT --
+",
+Alert.closeButtonLabel,OK,"
+-- CONTEXT --
+",
+Alert.dontShowAgain,Don't show this message again,"
+-- CONTEXT --
+",
+AnswersEditor.answersLabel,Answers,"
+-- CONTEXT --
+",
+AnswersEditor.newAnswerBtnLabel,New answer,"
+-- CONTEXT --
+",
+AnswersEditor.noAnswersPlaceholder,Question has no answer options,"
+-- CONTEXT --
+",
+AnswersEditor.numberFieldErrorLabel,Answer must be a number,"
+-- CONTEXT --
+",
+AppBar.administration,Administration,"
+-- CONTEXT --
+",
+AppBar.changeLanguage,Change language,"
+-- CONTEXT --
+",
+AppBar.help,Help and support,"
+-- CONTEXT --
+",
+AppBar.logIn,Sign in,"
+-- CONTEXT --
+",
+AppBar.logOut,Sign out,"
+-- CONTEXT --
+",
+AppBar.settings,Settings,"
+-- CONTEXT --
+",
+AppBar.title,Kolibri Studio,"
+-- CONTEXT --
+",
+AssessmentEditor.closeBtnLabel,Close,"
+-- CONTEXT --
+",
+AssessmentEditor.incompleteItemIndicatorLabel,Incomplete,"
+-- CONTEXT --
+",
+AssessmentEditor.newQuestionBtnLabel,New question,"
+-- CONTEXT --
+",
+AssessmentEditor.noQuestionsPlaceholder,Exercise has no questions,"
+-- CONTEXT --
+",
+AssessmentEditor.showAnswers,Show answers,"
+-- CONTEXT --
+",
+AssessmentEditor.toolbarItemLabel,question,"
+-- CONTEXT --
+",
+AssessmentItemEditor.dialogMessageChangeToInput,Switching to 'numeric input' will set all answers as correct and remove all non-numeric answers. Continue?,"
+-- CONTEXT --
+",
+AssessmentItemEditor.dialogMessageChangeToSingleSelection,Switching to 'single choice' will set only one answer as correct. Continue?,"
+-- CONTEXT --
+",
+AssessmentItemEditor.dialogMessageChangeToTrueFalse,Switching to 'true or false' will remove all current answers. Continue?,"
+-- CONTEXT --
+",
+AssessmentItemEditor.dialogSubmitBtnLabel,Change,"
+-- CONTEXT --
+",
+AssessmentItemEditor.dialogTitle,Changing question type,"
+-- CONTEXT --
+",
+AssessmentItemEditor.questionLabel,Question,"
+-- CONTEXT --
+",
+AssessmentItemEditor.questionTypeLabel,Response type,"
+-- CONTEXT --
+",
+AssessmentItemPreview.answersLabel,Answers,"
+-- CONTEXT --
+",
+AssessmentItemPreview.hintsToggleLabelHide,Hide hints,"
+-- CONTEXT --
+",
+AssessmentItemPreview.hintsToggleLabelShow,"Show {hintsCount} {hintsCount, plural, one {hint} other {hints}}","
+-- CONTEXT --
+",
+AssessmentItemPreview.noAnswersPlaceholder,Question has no answer options,"
+-- CONTEXT --
+",
+AssessmentItemToolbar.toolbarLabelAddAbove,Add {itemLabel} above,"
+-- CONTEXT --
+",
+AssessmentItemToolbar.toolbarLabelAddBelow,Add {itemLabel} below,"
+-- CONTEXT --
+",
+AssessmentItemToolbar.toolbarLabelDelete,Delete,"
+-- CONTEXT --
+",
+AssessmentItemToolbar.toolbarLabelEdit,Edit,"
+-- CONTEXT --
+",
+AssessmentItemToolbar.toolbarLabelMoveDown,Move down,"
+-- CONTEXT --
+",
+AssessmentItemToolbar.toolbarLabelMoveUp,Move up,"
+-- CONTEXT --
+",
+AssessmentTab.dialogCancelBtnLabel,Cancel,"
+-- CONTEXT --
+",
+AssessmentTab.dialogSubmitBtnLabel,Submit,"
+-- CONTEXT --
+",
+AssessmentTab.incompleteItemsCountMessage,"{invalidItemsCount} incomplete {invalidItemsCount, plural, one {question} other {questions}}","
+-- CONTEXT --
+",
+BrowsingCard.addToClipboardAction,Copy to clipboard,"
+-- CONTEXT --
+",
+BrowsingCard.coach,Resource for coaches,"
+-- CONTEXT --
+",
+BrowsingCard.goToSingleLocationAction,Go to location,"
+-- CONTEXT --
+",
+BrowsingCard.hasCoachTooltip,"{value, number, integer} {value, plural, one {resource for coaches} other {resources for coaches}}","
+-- CONTEXT --
+",
+BrowsingCard.previewAction,View details,"
+-- CONTEXT --
+",
+BrowsingCard.resourcesCount,"{count, number} {count, plural, one {resource} other {resources}}","
+-- CONTEXT --
+",
+BrowsingCard.tagsList,Tags: {tags},"
+-- CONTEXT --
+",
+BytesForHumansStrings.fileSizeInBytes,"{n, number, integer} B","
+-- CONTEXT --
+",
+BytesForHumansStrings.fileSizeInGigabytes,"{n, number, integer} GB","
+-- CONTEXT --
+",
+BytesForHumansStrings.fileSizeInKilobytes,"{n, number, integer} KB","
+-- CONTEXT --
+",
+BytesForHumansStrings.fileSizeInMegabytes,"{n, number, integer} MB","
+-- CONTEXT --
+",
+BytesForHumansStrings.fileSizeInTerabytes,"{n, number, integer} TB","
+-- CONTEXT --
+",
+CatalogFAQ.KolibriAnswer,"Kolibri is an open source ed-tech platform designed for low-resource communities, focused on:","
+-- CONTEXT --
+",
+CatalogFAQ.KolibriAnswerItem1,Overcoming infrastructural barriers that prevent equitable access to quality education for learners in low-resource and low-connectivity contexts,"
+-- CONTEXT --
+",
+CatalogFAQ.KolibriAnswerItem2,"Increasing the availability of open learning materials suitable for many curricula, learning goals, and situations","
+-- CONTEXT --
+",
+CatalogFAQ.KolibriAnswerItem3,Fostering innovative pedagogy and effective learning outcomes,"
+-- CONTEXT --
+",
+CatalogFAQ.KolibriQuestion,What is Kolibri?,"
+-- CONTEXT --
+",
+CatalogFAQ.aboutHeader,Welcome to the Kolibri Content Library Catalog! ,"
+-- CONTEXT --
+",
+CatalogFAQ.aboutKolibriHeader,About Kolibri,"
+-- CONTEXT --
+",
+CatalogFAQ.aboutLibraryHeader,About the Kolibri Content Library,"
+-- CONTEXT --
+",
+CatalogFAQ.channelAnswer,"A channel is Kolibriās unit of organization for digital content. It's a collection of resources organized by single institutions or creators, each of which may contain a set of books, games, textbooks, articles, simulations, exercises, and many more types of educational materials, all made available for use in Kolibri without the need for internet access. A channel isn't necessarily a course or a sequence, it's simply a collection of materials published or gathered together by one organization, as close to the provider's original layout as possible, while still organized for the best possible navigation in Kolibri.","
+-- CONTEXT --
+",
+CatalogFAQ.channelLink,What is a channel?,"
+-- CONTEXT --
+",
+CatalogFAQ.channelQuestion,What is a channel?,"
+-- CONTEXT --
+",
+CatalogFAQ.coachContentAnswer,"Most resources are directed at learners, but some, such as lesson plans, subject refreshers, professional learning guides, and similar, are directed at teachers and facilitators. In Kolibri, we mark this content as ""for coaches"" and limit its visibility to those with coach accounts. If you see coach materials here, they may require less planning for any facilitators using the resource!","
+-- CONTEXT --
+",
+CatalogFAQ.coachContentQuestion,What are 'resources for coaches'?,"
+-- CONTEXT --
+",
+CatalogFAQ.customContentAnswer,"To add your own materials, create an account on Kolibri Studio by going to https://studio.learningequality.org. Recommendations for public materials to be added to the Kolibri Content Library can be made by contacting content@learningequality.org.","
+-- CONTEXT --
+",
+CatalogFAQ.customContentQuestion,How can I add my own materials or recommend materials from other creators for this library?,"
+-- CONTEXT --
+",
+CatalogFAQ.descriptionP1,"Here you can learn more about the educational resources publicly available for use in Kolibri, which are organized into ""channels"". Use the filters to browse channels by keyword, language, or formats of the materials inside.","
+-- CONTEXT --
+",
+CatalogFAQ.descriptionP2,"Click on a channel to get a preview of what subjects and topics it covers, learn more about its creator, see how many resources the channel contains, and learn how to import it into Kolibri. You can also find coach-specific content (lesson plans, teacher professional guides, and other supplementary facilitation material), assessments and exercises, and captions for accessibility.","
+-- CONTEXT --
+",
+CatalogFAQ.descriptionP3,"Sharing the work of these resource creators is what inspires Learning Equality's efforts. We hope you find something that excites you about the potential of digital learning, online or off!","
+-- CONTEXT --
+",
+CatalogFAQ.downloadKolibriLink,Download Kolibri,"
+-- CONTEXT --
+",
+CatalogFAQ.downloadLink,Download,"
+-- CONTEXT --
+",
+CatalogFAQ.endoresementQuestion,Have these sources been vetted or endorsed as classroom-safe and ready?,"
+-- CONTEXT --
+",
+CatalogFAQ.endorsementAnswer,"We select sources with an educational affiliation or mandate, so you can trust that most resources in the Kolibri Content Library were designed for learning purposes. However, we are not able to guarantee the appropriateness of each individual item within any particular source. We recommend that educators and administrators conduct a thorough review of any digital content using their own criteria - including reorganization and re-curation, if necessary - before using it with learners. Since we recognize that there may be many different standards across situations for criteria like preferred levels of interactivity, subject/age appropriateness, cultural sensitivity and tone, among others, we have intentionally offered a wide range of materials to help meet the needs of all learners whatever they may be.","
+-- CONTEXT --
+",
+CatalogFAQ.faqHeader,Frequently asked questions,"
+-- CONTEXT --
+",
+CatalogFAQ.issueAnswer,"Please email us at content@learningequality.org and include the channel name, along with a description of the issue. If you notice an issue on a specific resource, please be sure to link that as well. We'd be happy to investigate and grateful for your feedback!","
+-- CONTEXT --
+",
+CatalogFAQ.issueQuestion,"I found a bug, broken link, or some mislabeled information within a resource. What should I do?","
+-- CONTEXT --
+",
+CatalogFAQ.maintenanceAnswerP1,"Because Kolibri is designed for learners and educators who are disconnected from the internet, content must first be packaged so that it can be used without internet connection. For most sources, our content team uses custom-written, automated scripts to bring content into Kolibri from a website, an app, or a private source such as a hard drive (with the appropriate permissions).","
+-- CONTEXT --
+",
+CatalogFAQ.maintenanceAnswerP2,"To learn more about how content is packaged for use on Kolibri and what types of formats are supported, please refer to our content integration guide.","
+-- CONTEXT --
+",
+CatalogFAQ.maintenanceQuestion,How is this library created and maintained?,"
+-- CONTEXT --
+",
+CatalogFAQ.makerAnswerP1,"Learning Equality, a 501(c)(3) nonprofit based in San Diego, California, is committed to enabling every person in the world to realize their right to a quality education, by supporting the creation, adaptation, and distribution of open educational resources, and creating supportive tools for innovative pedagogy.","
+-- CONTEXT --
+",
+CatalogFAQ.makerAnswerP2,"In recognition of the digital divide, Learning Equality started by bringing the Khan Academy experience offline to more than 6 million learners around the globe. Its second-generation product, Kolibri, is part of a broader ecosystem of products and tools that support curriculum alignment, blended learning pedagogies, and broader use of Open Educational Resources to improve learning.","
+-- CONTEXT --
+",
+CatalogFAQ.makerQuestion,Who are the makers of Kolibri?,"
+-- CONTEXT --
+",
+CatalogFAQ.newContentAnswer,Our content team routinely adds new sources and channels to the library and updates existing channels as content creators make new materials available.,"
+-- CONTEXT --
+",
+CatalogFAQ.newContentQuestion,Does Learning Equality add new materials?,"
+-- CONTEXT --
+",
+CatalogFAQ.ownershipAnswer,"No. Just like an online learning repository with links to external websites, we gather useful digital learning resources to help our community discover a rich variety of learning materials they may not have known about otherwise. All the materials in this educational library are fully credited to the creating organization, reformatted for best display on digital devices, and include any additional information the creator has shared with us. We only include content which is either openly licensed, available to distribute for special nonprofit or noncommercial purposes, or shared with us for distribution through agreement with the creator. Since materials in the library are intended for use in an open source platform, we do not profit financially from their use.","
+-- CONTEXT --
+",
+CatalogFAQ.ownershipQuestion,Does Learning Equality own these resources?,"
+-- CONTEXT --
+",
+CatalogFAQ.partialChannelAnswer,"When importing content into Kolibri, you can select the specific subsections of a channel you're interested in. If youād like to make changes such as editing the title or folder descriptions, or changing the order in which materials appear, please contact us at content@learningequality.org for early access to our Kolibri Studio tool, which can be used to make these changes.","
+-- CONTEXT --
+",
+CatalogFAQ.partialChannelQuestion,"I want to use some of the resources in this channel, but not all of it. What should I do?","
+-- CONTEXT --
+",
+CatalogFAQ.sampleContentAnswer,You can do this in three ways:,"
+-- CONTEXT --
+",
+CatalogFAQ.sampleContentAnswerItem1,"To see the original content source, click the ā® button and select 'Go to source website'","
+-- CONTEXT --
+",
+CatalogFAQ.sampleContentAnswerItem2,"To preview the content on one of our online demo servers (available in English, Spanish, Arabic, French, and Hindi), click the ā® button and select 'View channel on Kolibri'","
+-- CONTEXT --
+",
+CatalogFAQ.sampleContentAnswerItem3,Download Kolibri and import the channel on your device for full access offline.,"
+-- CONTEXT --
+",
+CatalogFAQ.sampleContentQuestion,How do I review the contents of the channels themselves?,"
+-- CONTEXT --
+",
+CatalogFAQ.selectionAnswerP1,"Our approach is unique in that we aim to assemble a library of resources which supports the diversity of needs Kolibri is designed to meet, rather than collecting all possible open educational resources.","
+-- CONTEXT --
+",
+CatalogFAQ.selectionAnswerP2,"To inform what we select, the Learning Equality team is continually maintaining our awareness of openly licensed digital resources available in the educational landscape. Most of our resources come from an organization, institution, or creator with learning design experience and an educational mandate. We prioritize providing a diversity of grade levels, subject areas and languages. Where possible, we also evaluate and seek input on the degree to which the materials may be suitable for the unique blended learning settings in which we work.","
+-- CONTEXT --
+",
+CatalogFAQ.selectionQuestion,How does Learning Equality determine what goes into this library?,"
+-- CONTEXT --
+",
+CatalogFAQ.usingContentAnswer,"Great! All of these resources have been specially packaged for use on Kolibri, our open source platform for offline learning, so please review how to get started with Kolibri first, then follow the instructions to import materials.","
+-- CONTEXT --
+",
+CatalogFAQ.usingContentQuestion,I found something I'm interested in and would like to start using it. What should I do?,"
+-- CONTEXT --
+",
+CatalogFAQ.usingKolibriAnswerP1,You can learn more about using Kolibri by doing any of the following:,"
+-- CONTEXT --
+",
+CatalogFAQ.usingKolibriAnswerP2,We invite you to use the Kolibri user documentation for further guidance.,"
+-- CONTEXT --
+",
+CatalogFAQ.usingKolibriItem1,Visit the Learning Equality website,"
+-- CONTEXT --
+",
+CatalogFAQ.usingKolibriItem2,View a demo of the platform,"
+-- CONTEXT --
+",
+CatalogFAQ.usingKolibriItem3,Download the software,"
+-- CONTEXT --
+",
+CatalogFAQ.usingKolibriQuestion,How can I use Kolibri?,"
+-- CONTEXT --
+",
+CatalogFAQ.usingResourcesHeader,About using these resources,"
+-- CONTEXT --
+",
+CatalogFAQ.viewDemoLink,View demo,"
+-- CONTEXT --
+",
+CatalogFAQ.viewDocsLink,View docs,"
+-- CONTEXT --
+",
+CatalogFAQ.viewGettingStartedLink,Documentation resources to get started with Kolibri,"
+-- CONTEXT --
+",
+CatalogFAQ.viewIntegrationGuide,View content integration guide,"
+-- CONTEXT --
+",
+CatalogFAQ.visitWebsiteLink,Visit website,"
+-- CONTEXT --
+",
+CatalogFilterBar.assessments,Assessments,"
+-- CONTEXT --
+",
+CatalogFilterBar.channelCount,"{count, plural,
+ =1 {# channel}
+ other {# channels}}","
+-- CONTEXT --
+",
+CatalogFilterBar.clearAll,Clear all,"
+-- CONTEXT --
+",
+CatalogFilterBar.close,Close,"
+-- CONTEXT --
+",
+CatalogFilterBar.coachContent,Coach content,"
+-- CONTEXT --
+",
+CatalogFilterBar.copyTitle,Copy collection token,"
+-- CONTEXT --
+",
+CatalogFilterBar.copyToken,Copy collection token,"
+-- CONTEXT --
+",
+CatalogFilterBar.copyTokenInstructions,Paste this token into Kolibri to import the channels contained in this collection,"
+-- CONTEXT --
+",
+CatalogFilterBar.keywords,"""{text}""","
+-- CONTEXT --
+",
+CatalogFilterBar.starred,Starred,"
+-- CONTEXT --
+",
+CatalogFilterBar.subtitles,Subtitles,"
+-- CONTEXT --
+",
+CatalogFilters.coachDescription,Resources for coaches are only visible to coaches in Kolibri,"
+-- CONTEXT --
+",
+CatalogFilters.coachLabel,Resources for coaches,"
+-- CONTEXT --
+",
+CatalogFilters.copyright,Ā© {year} Learning Equality,"
+-- CONTEXT --
+",
+CatalogFilters.formatLabel,Formats,"
+-- CONTEXT --
+",
+CatalogFilters.frequentlyAskedQuestionsLink,Frequently asked questions,"
+-- CONTEXT --
+",
+CatalogFilters.includesLabel,Display only channels with,"
+-- CONTEXT --
+",
+CatalogFilters.licenseLabel,Licenses,"
+-- CONTEXT --
+",
+CatalogFilters.searchLabel,Keywords,"
+-- CONTEXT --
+",
+CatalogFilters.searchText,Search,"
+-- CONTEXT --
+",
+CatalogFilters.starredLabel,Starred,"
+-- CONTEXT --
+",
+CatalogFilters.subtitlesLabel,Captions or subtitles,"
+-- CONTEXT --
+",
+CatalogList.cancelButton,Cancel,"
+-- CONTEXT --
+",
+CatalogList.channelSelectionCount,"{count, plural,
+ =1 {# channel selected}
+ other {# channels selected}}","
+-- CONTEXT --
+",
+CatalogList.downloadButton,Download,"
+-- CONTEXT --
+",
+CatalogList.downloadCSV,Download CSV,"
+-- CONTEXT --
+",
+CatalogList.downloadPDF,Download PDF,"
+-- CONTEXT --
+",
+CatalogList.downloadingMessage,Download started,"
+-- CONTEXT --
+",
+CatalogList.resultsText,"{count, plural,
+ =1 {# result found}
+ other {# results found}}","
+-- CONTEXT --
+",
+CatalogList.selectAll,Select all,"
+-- CONTEXT --
+",
+CatalogList.selectChannels,Download a summary of selected channels,"
+-- CONTEXT --
+",
+CategoryOptions.noCategoryFoundText,Category not found,"
+-- CONTEXT --
+",
+ChangePasswordForm.cancelAction,Cancel,"
+-- CONTEXT --
+",
+ChangePasswordForm.changePasswordHeader,Change password,"
+-- CONTEXT --
+",
+ChangePasswordForm.confirmNewPasswordLabel,Confirm new password,"
+-- CONTEXT --
+",
+ChangePasswordForm.formInvalidText,Passwords don't match,"
+-- CONTEXT --
+",
+ChangePasswordForm.newPasswordLabel,New password,"
+-- CONTEXT --
+",
+ChangePasswordForm.passwordChangeFailed,Failed to save new password,"
+-- CONTEXT --
+",
+ChangePasswordForm.paswordChangeSuccess,Password updated,"
+-- CONTEXT --
+",
+ChangePasswordForm.saveChangesAction,Save changes,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.assessmentsIncludedText,Assessments,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.catalogHeader,Kolibri Content Library channels,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.coachHeading,Resources for coaches,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.containsHeading,Contains,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.defaultNoItemsText,---,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.exported,Exported,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.formatsHeading,Formats,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.languagesHeading,Languages,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.numberOfChannels,{ num } channels,"
+-- CONTEXT --
+",
+ChannelCatalogFrontPage.subtitlesIncludedText,Captions or subtitles,"
+-- CONTEXT --
+",
+ChannelDeletedError.backToHomeAction,Back to home,"
+-- CONTEXT --
+",
+ChannelDeletedError.channelDeletedDetails,This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.,"
+-- CONTEXT --
+",
+ChannelDeletedError.channelDeletedHeader,Channel not found,"
+-- CONTEXT --
+",
+ChannelDetailsModal.downloadButton,Download channel summary,"
+-- CONTEXT --
+",
+ChannelDetailsModal.downloadCSV,Download CSV,"
+-- CONTEXT --
+",
+ChannelDetailsModal.downloadPDF,Download PDF,"
+-- CONTEXT --
+",
+ChannelExportStrings.aggregators,Aggregators,"
+-- CONTEXT --
+",
+ChannelExportStrings.assessments,Assessments,"
+-- CONTEXT --
+",
+ChannelExportStrings.authors,Authors,"
+-- CONTEXT --
+",
+ChannelExportStrings.coachContent,Resources for coaches,"
+-- CONTEXT --
+",
+ChannelExportStrings.copyrightHolders,Copyright holders,"
+-- CONTEXT --
+",
+ChannelExportStrings.description,Description,"
+-- CONTEXT --
+",
+ChannelExportStrings.downloadFilename,{year}_{month}_Kolibri_Content_Library,"
+-- CONTEXT --
+",
+ChannelExportStrings.id,Channel ID,"
+-- CONTEXT --
+",
+ChannelExportStrings.language,Language,"
+-- CONTEXT --
+",
+ChannelExportStrings.languages,Included languages,"
+-- CONTEXT --
+",
+ChannelExportStrings.licenses,Licenses,"
+-- CONTEXT --
+",
+ChannelExportStrings.name,Name,"
+-- CONTEXT --
+",
+ChannelExportStrings.no,No,"
+-- CONTEXT --
+",
+ChannelExportStrings.providers,Providers,"
+-- CONTEXT --
+",
+ChannelExportStrings.resources,Resources,"
+-- CONTEXT --
+",
+ChannelExportStrings.size,Total resources,"
+-- CONTEXT --
+",
+ChannelExportStrings.storage,Storage,"
+-- CONTEXT --
+",
+ChannelExportStrings.subtitles,Captions or subtitles,"
+-- CONTEXT --
+",
+ChannelExportStrings.tags,Tags,"
+-- CONTEXT --
+",
+ChannelExportStrings.token,Token,"
+-- CONTEXT --
+",
+ChannelExportStrings.yes,Yes,"
+-- CONTEXT --
+",
+ChannelInfoCard.resourceCount,"{count, number} {count, plural, one {resource} other {resources}}","
+-- CONTEXT --
+",
+ChannelInvitation.accept,Accept,"
+-- CONTEXT --
+",
+ChannelInvitation.acceptedSnackbar,Accepted invitation,"
+-- CONTEXT --
+",
+ChannelInvitation.cancel,Cancel,"
+-- CONTEXT --
+",
+ChannelInvitation.decline,Decline,"
+-- CONTEXT --
+",
+ChannelInvitation.declinedSnackbar,Declined invitation,"
+-- CONTEXT --
+",
+ChannelInvitation.decliningInvitation,Declining Invitation,"
+-- CONTEXT --
+",
+ChannelInvitation.decliningInvitationMessage,Are you sure you want to decline this invitation?,"
+-- CONTEXT --
+",
+ChannelInvitation.editText,{sender} has invited you to edit {channel},"
+-- CONTEXT --
+",
+ChannelInvitation.goToChannelSnackbarAction,Go to channel,"
+-- CONTEXT --
+",
+ChannelInvitation.viewText,{sender} has invited you to view {channel},"
+-- CONTEXT --
+",
+ChannelItem.cancel,Cancel,"
+-- CONTEXT --
+",
+ChannelItem.channelDeletedSnackbar,Channel deleted,"
+-- CONTEXT --
+",
+ChannelItem.channelLanguageNotSetIndicator,No language set,"
+-- CONTEXT --
+",
+ChannelItem.copyToken,Copy channel token,"
+-- CONTEXT --
+",
+ChannelItem.deleteChannel,Delete channel,"
+-- CONTEXT --
+",
+ChannelItem.deletePrompt,This channel will be permanently deleted. This cannot be undone.,"
+-- CONTEXT --
+",
+ChannelItem.deleteTitle,Delete this channel,"
+-- CONTEXT --
+",
+ChannelItem.details,Details,"
+-- CONTEXT --
+",
+ChannelItem.editChannel,Edit channel details,"
+-- CONTEXT --
+",
+ChannelItem.goToWebsite,Go to source website,"
+-- CONTEXT --
+",
+ChannelItem.lastPublished,Published {last_published},"
+-- CONTEXT --
+",
+ChannelItem.lastUpdated,Updated {updated},"
+-- CONTEXT --
+",
+ChannelItem.resourceCount,"{count, plural,
+ =1 {# resource}
+ other {# resources}}","
+-- CONTEXT --
+",
+ChannelItem.unpublishedText,Unpublished,"
+-- CONTEXT --
+",
+ChannelItem.versionText,Version {version},"
+-- CONTEXT --
+",
+ChannelItem.viewContent,View channel on Kolibri,"
+-- CONTEXT --
+",
+ChannelList.channel,New channel,"
+-- CONTEXT --
+",
+ChannelList.channelFilterLabel,Channels,"
+-- CONTEXT --
+",
+ChannelList.noChannelsFound,No channels found,"
+-- CONTEXT --
+",
+ChannelList.noMatchingChannels,There are no matching channels,"
+-- CONTEXT --
+",
+ChannelListAppError.channelPermissionsErrorDetails,Sign in or ask the owner of this channel to give you permission to edit or view,"
+-- CONTEXT --
+",
+ChannelListIndex.catalog,Content Library,"
+-- CONTEXT --
+",
+ChannelListIndex.channelSets,Collections,"
+-- CONTEXT --
+",
+ChannelListIndex.frequentlyAskedQuestions,Frequently asked questions,"
+-- CONTEXT --
+",
+ChannelListIndex.invitations,"You have {count, plural,
+ =1 {# invitation}
+ other {# invitations}}","
+-- CONTEXT --
+",
+ChannelListIndex.libraryTitle,Kolibri Content Library Catalog,"
+-- CONTEXT --
+",
+ChannelModal.APIText,Channels generated automatically are not editable.,"
+-- CONTEXT --
+",
+ChannelModal.changesSaved,Changes saved,"
+-- CONTEXT --
+",
+ChannelModal.channelDescription,Channel description,"
+-- CONTEXT --
+",
+ChannelModal.channelError,Field is required,"
+-- CONTEXT --
+",
+ChannelModal.channelName,Channel name,"
+-- CONTEXT --
+",
+ChannelModal.closeButton,Exit without saving,"
+-- CONTEXT --
+",
+ChannelModal.createButton,Create,"
+-- CONTEXT --
+",
+ChannelModal.creatingHeader,New channel,"
+-- CONTEXT --
+",
+ChannelModal.details,Channel details,"
+-- CONTEXT --
+",
+ChannelModal.editTab,Details,"
+-- CONTEXT --
+",
+ChannelModal.keepEditingButton,Keep editing,"
+-- CONTEXT --
+",
+ChannelModal.notFoundError,Channel does not exist,"
+-- CONTEXT --
+",
+ChannelModal.saveChangesButton,Save changes,"
+-- CONTEXT --
+",
+ChannelModal.shareTab,Sharing,"
+-- CONTEXT --
+",
+ChannelModal.unauthorizedError,You cannot edit this channel,"
+-- CONTEXT --
+",
+ChannelModal.unsavedChangesHeader,Unsaved changes,"
+-- CONTEXT --
+",
+ChannelModal.unsavedChangesText,You will lose any unsaved changes. Are you sure you want to exit?,"
+-- CONTEXT --
+",
+ChannelNotFoundError.backToHomeAction,Back to home,"
+-- CONTEXT --
+",
+ChannelNotFoundError.channelNotFoundDetails,This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.,"
+-- CONTEXT --
+",
+ChannelNotFoundError.channelNotFoundHeader,Channel not found,"
+-- CONTEXT --
+",
+ChannelSelectionList.noChannelsFound,No channels found,"
+-- CONTEXT --
+",
+ChannelSelectionList.searchText,Search for a channel,"
+-- CONTEXT --
+",
+ChannelSetItem.cancel,Cancel,"
+-- CONTEXT --
+",
+ChannelSetItem.delete,Delete collection,"
+-- CONTEXT --
+",
+ChannelSetItem.deleteChannelSetText,Are you sure you want to delete this collection?,"
+-- CONTEXT --
+",
+ChannelSetItem.deleteChannelSetTitle,Delete collection,"
+-- CONTEXT --
+",
+ChannelSetItem.edit,Edit collection,"
+-- CONTEXT --
+",
+ChannelSetItem.options,Options,"
+-- CONTEXT --
+",
+ChannelSetItem.saving,Saving,"
+-- CONTEXT --
+",
+ChannelSetList.aboutChannelSets,About collections,"
+-- CONTEXT --
+",
+ChannelSetList.aboutChannelSetsLink,Learn about collections,"
+-- CONTEXT --
+",
+ChannelSetList.addChannelSetTitle,New collection,"
+-- CONTEXT --
+",
+ChannelSetList.cancelButtonLabel,Close,"
+-- CONTEXT --
+",
+ChannelSetList.channelNumber,Number of channels,"
+-- CONTEXT --
+",
+ChannelSetList.channelSetsDescriptionText,A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.,"
+-- CONTEXT --
+",
+ChannelSetList.channelSetsDisclaimer,You will need Kolibri version 0.12.0 or higher to import channel collections,"
+-- CONTEXT --
+",
+ChannelSetList.channelSetsInstructionsText,You can make a collection by selecting the channels you want to be imported together.,"
+-- CONTEXT --
+",
+ChannelSetList.noChannelSetsFound,You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.,"
+-- CONTEXT --
+",
+ChannelSetList.options,Options,"
+-- CONTEXT --
+",
+ChannelSetList.title,Collection name,"
+-- CONTEXT --
+",
+ChannelSetList.token,Token ID,"
+-- CONTEXT --
+",
+ChannelSetModal.bookmark,Starred,"
+-- CONTEXT --
+",
+ChannelSetModal.channelAdded,Channel added,"
+-- CONTEXT --
+",
+ChannelSetModal.channelCountText,"{channelCount, plural, =0 {No published channels in your collection} =1 {# channel} other {# channels}}","
+-- CONTEXT --
+",
+ChannelSetModal.channelRemoved,Channel removed,"
+-- CONTEXT --
+",
+ChannelSetModal.channelSelectedCountText,"{channelCount, plural, =1 {# channel selected} other {# channels selected}}","
+-- CONTEXT --
+",
+ChannelSetModal.channels,Collection channels,"
+-- CONTEXT --
+",
+ChannelSetModal.closeButton,Exit without saving,"
+-- CONTEXT --
+",
+ChannelSetModal.collectionErrorText,This collection does not exist,"
+-- CONTEXT --
+",
+ChannelSetModal.createButton,Create,"
+-- CONTEXT --
+",
+ChannelSetModal.creatingChannelSet,New collection,"
+-- CONTEXT --
+",
+ChannelSetModal.edit,My Channels,"
+-- CONTEXT --
+",
+ChannelSetModal.finish,Finish,"
+-- CONTEXT --
+",
+ChannelSetModal.public,Public,"
+-- CONTEXT --
+",
+ChannelSetModal.publishedChannelsOnlyText,Only published channels are available for selection,"
+-- CONTEXT --
+",
+ChannelSetModal.removeText,Remove,"
+-- CONTEXT --
+",
+ChannelSetModal.saveButton,Save and close,"
+-- CONTEXT --
+",
+ChannelSetModal.selectChannelsHeader,Select channels,"
+-- CONTEXT --
+",
+ChannelSetModal.titleLabel,Collection name,"
+-- CONTEXT --
+",
+ChannelSetModal.titleRequiredText,Field is required,"
+-- CONTEXT --
+",
+ChannelSetModal.token,Collection token,"
+-- CONTEXT --
+",
+ChannelSetModal.tokenPrompt,Copy this token into Kolibri to import this collection onto your device.,"
+-- CONTEXT --
+",
+ChannelSetModal.unsavedChangesHeader,Unsaved changes,"
+-- CONTEXT --
+",
+ChannelSetModal.unsavedChangesText,You will lose any unsaved changes. Are you sure you want to exit?,"
+-- CONTEXT --
+",
+ChannelSetModal.view,View-Only,"
+-- CONTEXT --
+",
+ChannelSharing.alreadyHasAccessError,User already has access to this channel,"
+-- CONTEXT --
+",
+ChannelSharing.alreadyInvitedError,User already invited,"
+-- CONTEXT --
+",
+ChannelSharing.canEdit,Can edit,"
+-- CONTEXT --
+",
+ChannelSharing.canView,Can view,"
+-- CONTEXT --
+",
+ChannelSharing.emailLabel,Email,"
+-- CONTEXT --
+",
+ChannelSharing.emailRequiredMessage,Email is required,"
+-- CONTEXT --
+",
+ChannelSharing.invitationFailedError,Invitation failed to send. Please try again,"
+-- CONTEXT --
+",
+ChannelSharing.invitationSentMessage,Invitation sent,"
+-- CONTEXT --
+",
+ChannelSharing.inviteButton,Send invitation,"
+-- CONTEXT --
+",
+ChannelSharing.inviteSubheading,Invite collaborators,"
+-- CONTEXT --
+",
+ChannelSharing.validEmailMessage,Please enter a valid email,"
+-- CONTEXT --
+",
+ChannelSharingTable.cancelButton,Cancel,"
+-- CONTEXT --
+",
+ChannelSharingTable.currentUserText,{first_name} {last_name} (you),"
+-- CONTEXT --
+",
+ChannelSharingTable.deleteInvitation,Delete invitation,"
+-- CONTEXT --
+",
+ChannelSharingTable.deleteInvitationConfirm,Delete invitation,"
+-- CONTEXT --
+",
+ChannelSharingTable.deleteInvitationHeader,Delete invitation,"
+-- CONTEXT --
+",
+ChannelSharingTable.deleteInvitationText,Are you sure you would like to delete the invitation for {email}?,"
+-- CONTEXT --
+",
+ChannelSharingTable.editPermissionsGrantedMessage,Edit permissions granted,"
+-- CONTEXT --
+",
+ChannelSharingTable.editorsSubheading,"{count, plural,
+ =1 {# user who can edit}
+ other {# users who can edit}}","
+-- CONTEXT --
+",
+ChannelSharingTable.guestText,Guest,"
+-- CONTEXT --
+",
+ChannelSharingTable.invitationDeletedMessage,Invitation deleted,"
+-- CONTEXT --
+",
+ChannelSharingTable.invitationFailedError,Invitation failed to resend. Please try again,"
+-- CONTEXT --
+",
+ChannelSharingTable.invitationSentMessage,Invitation sent,"
+-- CONTEXT --
+",
+ChannelSharingTable.invitePendingText,Invite pending,"
+-- CONTEXT --
+",
+ChannelSharingTable.makeEditor,Grant edit permissions,"
+-- CONTEXT --
+",
+ChannelSharingTable.makeEditorConfirm,"Yes, grant permissions","
+-- CONTEXT --
+",
+ChannelSharingTable.makeEditorHeader,Grant edit permissions,"
+-- CONTEXT --
+",
+ChannelSharingTable.makeEditorText,Are you sure you would like to grant edit permissions to {first_name} {last_name}?,"
+-- CONTEXT --
+",
+ChannelSharingTable.noUsersText,No users found,"
+-- CONTEXT --
+",
+ChannelSharingTable.optionsDropdown,Options,"
+-- CONTEXT --
+",
+ChannelSharingTable.removeViewer,Revoke view permissions,"
+-- CONTEXT --
+",
+ChannelSharingTable.removeViewerConfirm,"Yes, revoke","
+-- CONTEXT --
+",
+ChannelSharingTable.removeViewerHeader,Revoke view permissions,"
+-- CONTEXT --
+",
+ChannelSharingTable.removeViewerText,Are you sure you would like to revoke view permissions for {first_name} {last_name}?,"
+-- CONTEXT --
+",
+ChannelSharingTable.resendInvitation,Resend invitation,"
+-- CONTEXT --
+",
+ChannelSharingTable.userRemovedMessage,User removed,"
+-- CONTEXT --
+",
+ChannelSharingTable.viewersSubheading,"{count, plural,
+ =1 {# user who can view}
+ other {# users who can view}}","
+-- CONTEXT --
+",
+ChannelStar.star,Add to starred channels,"
+-- CONTEXT --
+",
+ChannelStar.starred,Added to starred channels,"
+-- CONTEXT --
+",
+ChannelStar.unstar,Remove from starred channels,"
+-- CONTEXT --
+",
+ChannelStar.unstarred,Removed from starred channels,"
+-- CONTEXT --
+",
+ChannelThumbnail.cancel,Cancel,"
+-- CONTEXT --
+",
+ChannelThumbnail.crop,Crop,"
+-- CONTEXT --
+",
+ChannelThumbnail.croppingPrompt,Drag image to reframe,"
+-- CONTEXT --
+",
+ChannelThumbnail.defaultFilename,File,"
+-- CONTEXT --
+",
+ChannelThumbnail.noThumbnail,No thumbnail,"
+-- CONTEXT --
+",
+ChannelThumbnail.remove,Remove,"
+-- CONTEXT --
+",
+ChannelThumbnail.retryUpload,Retry upload,"
+-- CONTEXT --
+",
+ChannelThumbnail.save,Save,"
+-- CONTEXT --
+",
+ChannelThumbnail.upload,Upload image,"
+-- CONTEXT --
+",
+ChannelThumbnail.uploadFailed,Upload failed,"
+-- CONTEXT --
+",
+ChannelThumbnail.uploadingThumbnail,Uploading,"
+-- CONTEXT --
+",
+ChannelThumbnail.zoomIn,Zoom in,"
+-- CONTEXT --
+",
+ChannelThumbnail.zoomOut,Zoom out,"
+-- CONTEXT --
+",
+ChannelTokenModal.close,Close,"
+-- CONTEXT --
+",
+ChannelTokenModal.copyTitle,Copy channel token,"
+-- CONTEXT --
+",
+ChannelTokenModal.copyTokenInstructions,Paste this token into Kolibri to import this channel,"
+-- CONTEXT --
+",
+Clipboard.backToClipboard,Clipboard,"
+-- CONTEXT --
+",
+Clipboard.close,Close,"
+-- CONTEXT --
+",
+Clipboard.copiedItemsToClipboard,Copied in clipboard,"
+-- CONTEXT --
+",
+Clipboard.deleteSelectedButton,Delete,"
+-- CONTEXT --
+",
+Clipboard.duplicateSelectedButton,Make a copy,"
+-- CONTEXT --
+",
+Clipboard.emptyDefaultText,Use the clipboard to copy resources and move them to other folders and channels,"
+-- CONTEXT --
+",
+Clipboard.emptyDefaultTitle,No resources in your clipboard,"
+-- CONTEXT --
+",
+Clipboard.moveSelectedButton,Move,"
+-- CONTEXT --
+",
+Clipboard.removedFromClipboard,Deleted from clipboard,"
+-- CONTEXT --
+",
+Clipboard.selectAll,Select all,"
+-- CONTEXT --
+",
+CommonMetadataStrings.accessibility,Accessibility,"
+-- CONTEXT --
+Allows the user to filter for all the resources with accessibility features for learners with disabilities.",
+CommonMetadataStrings.algebra,Algebra,"
+-- CONTEXT --
+A type of math category. See https://en.wikipedia.org/wiki/Algebra",
+CommonMetadataStrings.all,All,"
+-- CONTEXT --
+A label for everything in the group of activities.",
+CommonMetadataStrings.allContent,Viewed in its entirety,"
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners studied it all, for example they saw all pages of a document.",
+CommonMetadataStrings.allLevelsBasicSkills,All levels -- basic skills,"
+-- CONTEXT --
+Refers to a type of educational level.",
+CommonMetadataStrings.allLevelsWorkSkills,All levels -- work skills,"
+-- CONTEXT --
+Refers to a type of educational level.",
+CommonMetadataStrings.altText,Includes alternative text descriptions for images,"
+-- CONTEXT --
+Alternative text, or alt text, is a written substitute for an image. It is used to describe information being provided by an image, graph, or any other visual element on a web page. It provides information about the context and function of an image for people with varying degrees of visual and cognitive impairments. When a screen reader encounters an image, it will read aloud the alternative text.
+https://www.med.unc.edu/webguide/accessibility/alt-text/",
+CommonMetadataStrings.anthropology,Anthropology,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Anthropology",
+CommonMetadataStrings.arithmetic,Arithmetic,"
+-- CONTEXT --
+Math category type. See https://en.wikipedia.org/wiki/Arithmetic",
+CommonMetadataStrings.arts,Arts,"
+-- CONTEXT --
+Refers to a category group type. See https://en.wikipedia.org/wiki/The_arts",
+CommonMetadataStrings.astronomy,Astronomy,"
+-- CONTEXT --
+Science category type. See https://en.wikipedia.org/wiki/Astronomy",
+CommonMetadataStrings.audioDescription,Includes audio descriptions,"
+-- CONTEXT --
+Content has narration used to provide information surrounding key visual elements for the benefit of blind and visually impaired users.
+https://en.wikipedia.org/wiki/Audio_description",
+CommonMetadataStrings.basicSkills,Basic skills,"
+-- CONTEXT --
+Category type. Basic skills refer to learning resources focused on aspects like literacy, numeracy and digital literacy.",
+CommonMetadataStrings.biology,Biology,"
+-- CONTEXT --
+Science category type. See https://en.wikipedia.org/wiki/Biology",
+CommonMetadataStrings.browseChannel,Browse channel,"
+-- CONTEXT --
+Heading on page where a user can browse the content within a channel",
+CommonMetadataStrings.calculus,Calculus,"
+-- CONTEXT --
+Math category type. https://en.wikipedia.org/wiki/Calculus",
+CommonMetadataStrings.captionsSubtitles,Includes captions or subtitles,"
+-- CONTEXT --
+Accessibility filter to search for video and audio resources that have text captions for users who are deaf or hard of hearing.
+https://www.w3.org/WAI/media/av/captions/",
+CommonMetadataStrings.category,Category,"
+-- CONTEXT --
+A title for the metadata that explains the subject matter of an activity",
+CommonMetadataStrings.chemistry,Chemistry,"
+-- CONTEXT --
+Science category type. See https://en.wikipedia.org/wiki/Chemistry",
+CommonMetadataStrings.civicEducation,Civic education,"
+-- CONTEXT --
+Category type. Civic education is the study of the rights and obligations of citizens in society. See https://en.wikipedia.org/wiki/Civics",
+CommonMetadataStrings.completeDuration,When time spent is equal to duration,"
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners spent given time studying it.",
+CommonMetadataStrings.completion,Completion,"
+-- CONTEXT --
+A title for the metadata that explains when an activity is finished",
+CommonMetadataStrings.computerScience,Computer science,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Computer_science",
+CommonMetadataStrings.create,Create,"
+-- CONTEXT --
+Resource and filter label for the type of learning activity. Translate as a VERB",
+CommonMetadataStrings.currentEvents,Current events,"
+-- CONTEXT --
+Category type. Could also be translated as 'News'. See https://en.wikipedia.org/wiki/News",
+CommonMetadataStrings.dailyLife,Daily life,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Everyday_life",
+CommonMetadataStrings.dance,Dance,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Dance",
+CommonMetadataStrings.determinedByResource,Determined by the resource,"
+-- CONTEXT --
+One of the completion criteria types. Typically used for embedded html5/h5p resources that contain their own completion criteria, for example reaching a score in an educational game.",
+CommonMetadataStrings.digitalLiteracy,Digital literacy,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Digital_literacy",
+CommonMetadataStrings.diversity,Diversity,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Diversity_(politics)",
+CommonMetadataStrings.drama,Drama,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Drama",
+CommonMetadataStrings.duration,Duration,"
+-- CONTEXT --
+A title for the metadata that explains how long an activity will take",
+CommonMetadataStrings.earthScience,Earth science,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Earth_science",
+CommonMetadataStrings.entrepreneurship,Entrepreneurship,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Entrepreneurship",
+CommonMetadataStrings.environment,Environment,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Environmental_studies",
+CommonMetadataStrings.exactTime,Time to complete,"
+-- CONTEXT --
+One of the completion criteria types. A subset of ""When time spent is equal to duration"". For example, for an audio resource with this criteria, learnes need to hear the whole length of audio for the resource to be considered complete.",
+CommonMetadataStrings.explore,Explore,"
+-- CONTEXT --
+Resource and filter label for the type of learning activity. Translate as a VERB",
+CommonMetadataStrings.financialLiteracy,Financial literacy,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Financial_literacy",
+CommonMetadataStrings.forBeginners,For beginners,"
+-- CONTEXT --
+Filter option and a label for the resources in the Kolibri Library.",
+CommonMetadataStrings.forTeachers,For teachers,"
+-- CONTEXT --
+Category type",
+CommonMetadataStrings.geometry,Geometry,"
+-- CONTEXT --
+Category type.",
+CommonMetadataStrings.goal,When goal is met,"
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners reached a given goal, for example 100% correct.",
+CommonMetadataStrings.guides,Guides,"
+-- CONTEXT --
+Category label in the Kolibri resources library; refers to any guide-type material for teacher professional development.",
+CommonMetadataStrings.highContrast,Includes high contrast text for learners with low vision,"
+-- CONTEXT --
+Accessibility filter used to search for resources that have high contrast color themes for users with low vision ('display' refers to digital content, not the hardware like screens or monitors).
+https://veroniiiica.com/2019/10/25/high-contrast-color-schemes-low-vision/",
+CommonMetadataStrings.history,History,"
+-- CONTEXT --
+Category type.",
+CommonMetadataStrings.industryAndSectorSpecific,Industry and sector specific,"
+-- CONTEXT --
+Subcategory type for technical and vocational training.",
+CommonMetadataStrings.languageLearning,Language learning,"
+-- CONTEXT --
+Category type.",
+CommonMetadataStrings.learningActivity,Learning Activity,"
+-- CONTEXT --
+A title for the category of education material interaction, i.e. watch, read, listen",
+CommonMetadataStrings.learningSkills,Learning skills,"
+-- CONTEXT --
+A category label and type of basic skill.
+https://en.wikipedia.org/wiki/Study_skills",
+CommonMetadataStrings.lessonPlans,Lesson plans,"
+-- CONTEXT --
+Category label in the Kolibri resources library; refers to lesson planning materials for teachers.",
+CommonMetadataStrings.level,Level,"
+-- CONTEXT --
+Refers to the educational learning level, such a preschool, primary, secondary, etc.",
+CommonMetadataStrings.listen,Listen,"
+-- CONTEXT --
+Resource and filter label for the type of learning activity with audio. Translate as a VERB",
+CommonMetadataStrings.literacy,Literacy,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Literacy",
+CommonMetadataStrings.literature,Literature,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Literature",
+CommonMetadataStrings.logicAndCriticalThinking,Logic and critical thinking,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Critical_thinking",
+CommonMetadataStrings.longActivity,Long activity,"
+-- CONTEXT --
+Label with time estimation for learning activities that take more than 30 minutes.",
+CommonMetadataStrings.lowerPrimary,Lower primary,"
+-- CONTEXT --
+Refers to a level of learning. Approximately corresponds to the first half of primary school.",
+CommonMetadataStrings.lowerSecondary,Lower secondary,"
+-- CONTEXT --
+Refers to a level of learning. Approximately corresponds to the first half of secondary school (high school).",
+CommonMetadataStrings.masteryMofN,Goal: {m} out of {n},"
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners answered m questions out of n correctly.",
+CommonMetadataStrings.mathematics,Mathematics,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Mathematics",
+CommonMetadataStrings.mechanicalEngineering,Mechanical engineering,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Mechanical_engineering.",
+CommonMetadataStrings.mediaLiteracy,Media literacy,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Media_literacy",
+CommonMetadataStrings.mentalHealth,Mental health,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Mental_health",
+CommonMetadataStrings.music,Music,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Music",
+CommonMetadataStrings.needsInternet,Internet connection,"
+-- CONTEXT --
+Refers to a filter for resources.",
+CommonMetadataStrings.needsMaterials,Other supplies,"
+-- CONTEXT --
+Refers to a filter for resources.
+",
+CommonMetadataStrings.numeracy,Numeracy,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Numeracy",
+CommonMetadataStrings.peers,Working with peers,"
+-- CONTEXT --
+Refers to a filter for resources that require a learner to work with other learners to be used.",
+CommonMetadataStrings.physics,Physics,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Physics.",
+CommonMetadataStrings.politicalScience,Political science,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Political_science.",
+CommonMetadataStrings.practice,Practice,"
+-- CONTEXT --
+Resource and filter label for the type of learning activity with questions and answers. Translate as a VERB",
+CommonMetadataStrings.practiceQuiz,Practice quiz,"
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria represents a quiz.",
+CommonMetadataStrings.preschool,Preschool,"
+-- CONTEXT --
+Refers to a level of education offered to children before they begin compulsory education at primary school.
+
+See https://en.wikipedia.org/wiki/Preschool",
+CommonMetadataStrings.professionalSkills,Professional skills,"
+-- CONTEXT --
+Category type. Refers to skills that are related to a profession or a job.",
+CommonMetadataStrings.programming,Programming,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Computer_programming",
+CommonMetadataStrings.publicHealth,Public health,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Public_health.",
+CommonMetadataStrings.read,Read,"
+-- CONTEXT --
+Resource and filter label for the type of learning activity with documents. Translate as a VERB",
+CommonMetadataStrings.readReference,Reference,"
+-- CONTEXT --
+Label displayed for the 'Read' learning activity, used instead of the time duration information, to indicate a resource that may not need sequential reading from the beginning to the end. Similar concept as the 'reference' books in the traditional library, that the user just 'consults', and does not read from cover to cover.",
+CommonMetadataStrings.readingAndWriting,Reading and writing,"
+-- CONTEXT --
+School subject category",
+CommonMetadataStrings.readingComprehension,Reading comprehension,"
+-- CONTEXT --
+Category type.",
+CommonMetadataStrings.reference,Reference material,"
+-- CONTEXT --
+One of the completion criteria types. Progress made on a resource with this criteria is not tracked.",
+CommonMetadataStrings.reflect,Reflect,"
+-- CONTEXT --
+Resource and filter label for the type of learning activity. Translate as a VERB",
+CommonMetadataStrings.school,School,"
+-- CONTEXT --
+Category type.",
+CommonMetadataStrings.sciences,Sciences,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Science",
+CommonMetadataStrings.shortActivity,Short activity,"
+-- CONTEXT --
+Label with time estimation for learning activities that take less than 30 minutes.",
+CommonMetadataStrings.signLanguage,Includes sign language captions,"
+-- CONTEXT --
+https://en.wikipedia.org/wiki/Sign_language
+https://en.wikipedia.org/wiki/List_of_sign_languages
+Wherever communities of deaf people exist, sign languages have developed as useful means of communication, and they form the core of local Deaf cultures. Although signing is used primarily by the deaf and hard of hearing, it is also used by hearing individuals, such as those unable to physically speak, those who have trouble with spoken language due to a disability or condition (augmentative and alternative communication), or those with deaf family members, such as children of deaf adults. ",
+CommonMetadataStrings.skillsTraining,Skills training,"
+-- CONTEXT --
+Subcategory type for technical and vocational training.",
+CommonMetadataStrings.socialSciences,Social sciences,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Social_science",
+CommonMetadataStrings.sociology,Sociology,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Sociology",
+CommonMetadataStrings.softwareTools,Other software tools,"
+-- CONTEXT --
+Refers to a filter for resources that need additional software to be used.",
+CommonMetadataStrings.softwareToolsAndTraining,Software tools and training,"
+-- CONTEXT --
+Subcategory type for technical and vocational training.",
+CommonMetadataStrings.specializedProfessionalTraining,Specialized professional training,"
+-- CONTEXT --
+Level of education that refers to training for a profession (job).",
+CommonMetadataStrings.statistics,Statistics,"
+-- CONTEXT --
+A math category. See https://en.wikipedia.org/wiki/Statistics",
+CommonMetadataStrings.taggedPdf,Tagged PDF,"
+-- CONTEXT --
+A tagged PDF includes hidden accessibility markups (tags) that make the document accessible to those who use screen readers and other assistive technology (AT).
+
+https://taggedpdf.com/what-is-a-tagged-pdf/",
+CommonMetadataStrings.teacher,Working with a teacher,"
+-- CONTEXT --
+Refers to a filter for resources that require a learner to work with a teacher to be used.",
+CommonMetadataStrings.technicalAndVocationalTraining,Technical and vocational training,"
+-- CONTEXT --
+A level of education. See https://en.wikipedia.org/wiki/TVET_(Technical_and_Vocational_Education_and_Training)",
+CommonMetadataStrings.tertiary,Tertiary,"
+-- CONTEXT --
+A level of education. See https://en.wikipedia.org/wiki/Tertiary_education",
+CommonMetadataStrings.toUseWithPaperAndPencil,Paper and pencil,"
+-- CONTEXT --
+Refers to a filter for resources.
+",
+CommonMetadataStrings.topicLabel,Folder,"
+-- CONTEXT --
+A collection of resources and other subfolders within a channel. Nested folders allow a channel to be organized as a tree or hierarchy.",
+CommonMetadataStrings.upperPrimary,Upper primary,"
+-- CONTEXT --
+Refers to a level of education. Approximately corresponds to the second half of primary school.
+",
+CommonMetadataStrings.upperSecondary,Upper secondary,"
+-- CONTEXT --
+Refers to a level of education. Approximately corresponds to the second half of secondary school.",
+CommonMetadataStrings.visualArt,Visual art,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Visual_arts",
+CommonMetadataStrings.watch,Watch,"
+-- CONTEXT --
+Resource and filter label for the type of learning activity with video. Translate as a VERB",
+CommonMetadataStrings.webDesign,Web design,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Web_design",
+CommonMetadataStrings.work,Work,"
+-- CONTEXT --
+Top level category group that contains resources for acquisition of professional skills.",
+CommonMetadataStrings.writing,Writing,"
+-- CONTEXT --
+Category type. See https://en.wikipedia.org/wiki/Writing",
+CommunityStandardsModal.communityStandardsHeader,Community Standards,"
+-- CONTEXT --
+",
+CommunityStandardsModal.coreValuesLink,Learn more about Learning Equality's core values,"
+-- CONTEXT --
+",
+CommunityStandardsModal.description,"Learning Equality is a nonprofit organization dedicated to enabling equitable access to quality educational experiences. Along with our statement of Core Values, these Community Standards are intended to foster a supportive and inclusive environment for our users.","
+-- CONTEXT --
+",
+CommunityStandardsModal.libraryDescription,"The Kolibri Library is both a grassroots and curated effort to provide a wide variety of materials for all learners and learning purposes. To help us achieve these goals, we invite you to use Kolibri Studio in ways that:","
+-- CONTEXT --
+",
+CommunityStandardsModal.libraryItem1,"Model good practices in open sharing and respect copyright. Create an ethical sharing community by labeling licenses, making sure you know the appropriate licenses for what you upload, and ensuring that appropriate written permissions are documented if needed. Studio is primarily designed to host materials which are openly licensed or come with special permissions for re-distribution and reproduction.","
+-- CONTEXT --
+",
+CommunityStandardsModal.libraryItem2,"Keep materials clear, organized, and usable. We welcome resources created at all levels of production! To help them reach as many learners as possible, we invite you to fully utilize all metadata fields and aim for quality in comprehensibility, legibility, or digitization such that the content is usable and understandable.","
+-- CONTEXT --
+",
+CommunityStandardsModal.libraryItem3,"Respect the community. Avoid obscenity and vulgarity, beyond specific educational purposes they might serve in some contexts. Hate speech of any kind, or promotion of violence or discrimination, will never be tolerated.","
+-- CONTEXT --
+",
+CommunityStandardsModal.libraryItem4,"Kolibri Studio is for educational purposes only. It is not intended to be used for non-educational purposes such as recruitment, indoctrination, advertisement, file sharing, or personal media hosting.","
+-- CONTEXT --
+",
+CommunityStandardsModal.studioDescription,"Kolibri Studio gives you access to the Kolibri Library, a growing library of educational materials, which we encourage you to use as your own. We built Kolibri Studio to help you prepare educational materials in a variety of ways, including but not limited to:","
+-- CONTEXT --
+",
+CommunityStandardsModal.studioItem1,"Browsing. Selecting appropriate educational materials for your situation from the Kolibri Library, the listing of sources available on the public channels page in Kolibri Studio","
+-- CONTEXT --
+",
+CommunityStandardsModal.studioItem2,"Curating. Reorganizing the materials in these channels by selecting, deleting, and reordering appropriate items","
+-- CONTEXT --
+",
+CommunityStandardsModal.studioItem3,"Sharing. Creating and publishing new channels with what you find, either to share with your own implementations privately or to share with others on Kolibri Studio.","
+-- CONTEXT --
+",
+CommunityStandardsModal.studioItem4,Modifying & Creating. Adding your own assessment exercises to any existing materials,"
+-- CONTEXT --
+",
+CommunityStandardsModal.studioItem5,Hosting. Uploading your own materials (limited to materials you know are appropriately licensed to do so) from a local hard drive or other locations on the internet,"
+-- CONTEXT --
+",
+CompletionOptions.learnersCanMarkComplete,Allow learners to mark as complete,"
+-- CONTEXT --
+",
+CompletionOptions.referenceHint,Progress will not be tracked on reference material unless learners mark it as complete,"
+-- CONTEXT --
+",
+ConstantStrings.All Rights Reserved,All Rights Reserved,"
+-- CONTEXT --
+",
+ConstantStrings.All Rights Reserved_description,"The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty.","
+-- CONTEXT --
+",
+ConstantStrings.CC BY,CC BY,"
+-- CONTEXT --
+",
+ConstantStrings.CC BY-NC,CC BY-NC,"
+-- CONTEXT --
+",
+ConstantStrings.CC BY-NC-ND,CC BY-NC-ND,"
+-- CONTEXT --
+",
+ConstantStrings.CC BY-NC-ND_description,"The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially.","
+-- CONTEXT --
+",
+ConstantStrings.CC BY-NC-SA,CC BY-NC-SA,"
+-- CONTEXT --
+",
+ConstantStrings.CC BY-NC-SA_description,"The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms.","
+-- CONTEXT --
+",
+ConstantStrings.CC BY-NC_description,"The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms.","
+-- CONTEXT --
+",
+ConstantStrings.CC BY-ND,CC BY-ND,"
+-- CONTEXT --
+",
+ConstantStrings.CC BY-ND_description,"The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you.","
+-- CONTEXT --
+",
+ConstantStrings.CC BY-SA,CC BY-SA,"
+-- CONTEXT --
+",
+ConstantStrings.CC BY-SA_description,"The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to ""copyleft"" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects.","
+-- CONTEXT --
+",
+ConstantStrings.CC BY_description,"The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials.","
+-- CONTEXT --
+",
+ConstantStrings.Public Domain,Public Domain,"
+-- CONTEXT --
+",
+ConstantStrings.Public Domain_description,"Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights.","
+-- CONTEXT --
+",
+ConstantStrings.Special Permissions,Special Permissions,"
+-- CONTEXT --
+",
+ConstantStrings.Special Permissions_description,Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails.,"
+-- CONTEXT --
+",
+ConstantStrings.audio,Audio,"
+-- CONTEXT --
+",
+ConstantStrings.audio_thumbnail,Thumbnail,"
+-- CONTEXT --
+",
+ConstantStrings.bookmark,Starred,"
+-- CONTEXT --
+",
+ConstantStrings.coach,Coaches,"
+-- CONTEXT --
+",
+ConstantStrings.do_all,Goal: 100% correct,"
+-- CONTEXT --
+",
+ConstantStrings.do_all_description,Learner must answer all questions in the exercise correctly (not recommended for long exercises),"
+-- CONTEXT --
+",
+ConstantStrings.document,Document,"
+-- CONTEXT --
+",
+ConstantStrings.document_thumbnail,Thumbnail,"
+-- CONTEXT --
+",
+ConstantStrings.edit,My channels,"
+-- CONTEXT --
+",
+ConstantStrings.epub,EPub document,"
+-- CONTEXT --
+",
+ConstantStrings.exercise,Exercise,"
+-- CONTEXT --
+",
+ConstantStrings.exercise_thumbnail,Thumbnail,"
+-- CONTEXT --
+",
+ConstantStrings.firstCopy,Copy of {title},"
+-- CONTEXT --
+",
+ConstantStrings.gif,GIF image,"
+-- CONTEXT --
+",
+ConstantStrings.h5p,H5P App,"
+-- CONTEXT --
+",
+ConstantStrings.high_res_video,High resolution,"
+-- CONTEXT --
+",
+ConstantStrings.html5,HTML5 App,"
+-- CONTEXT --
+",
+ConstantStrings.html5_thumbnail,Thumbnail,"
+-- CONTEXT --
+",
+ConstantStrings.html5_zip,HTML5 zip,"
+-- CONTEXT --
+",
+ConstantStrings.input_question,Numeric input,"
+-- CONTEXT --
+",
+ConstantStrings.jpeg,JPEG image,"
+-- CONTEXT --
+",
+ConstantStrings.jpg,JPG image,"
+-- CONTEXT --
+",
+ConstantStrings.json,JSON,"
+-- CONTEXT --
+",
+ConstantStrings.learner,Anyone,"
+-- CONTEXT --
+",
+ConstantStrings.low_res_video,Low resolution,"
+-- CONTEXT --
+",
+ConstantStrings.m_of_n,M of N...,"
+-- CONTEXT --
+",
+ConstantStrings.m_of_n_description,"Learner must answer M questions correctly from the last N answered questions. For example, ā3 of 5ā means learners must answer 3 questions correctly out of the 5 most recently answered.","
+-- CONTEXT --
+",
+ConstantStrings.mp3,MP3 audio,"
+-- CONTEXT --
+",
+ConstantStrings.mp4,MP4 video,"
+-- CONTEXT --
+",
+ConstantStrings.multiple_selection,Multiple choice,"
+-- CONTEXT --
+",
+ConstantStrings.nthCopy,"Copy {n, number, integer} of {title}","
+-- CONTEXT --
+",
+ConstantStrings.num_correct_in_a_row_10,Goal: 10 in a row,"
+-- CONTEXT --
+",
+ConstantStrings.num_correct_in_a_row_10_description,Learner must answer 10 questions in a row correctly,"
+-- CONTEXT --
+",
+ConstantStrings.num_correct_in_a_row_2,Goal: 2 in a row,"
+-- CONTEXT --
+",
+ConstantStrings.num_correct_in_a_row_2_description,Learner must answer 2 questions in a row correctly,"
+-- CONTEXT --
+",
+ConstantStrings.num_correct_in_a_row_3,Goal: 3 in a row,"
+-- CONTEXT --
+",
+ConstantStrings.num_correct_in_a_row_3_description,Learner must answer 3 questions in a row correctly,"
+-- CONTEXT --
+",
+ConstantStrings.num_correct_in_a_row_5,Goal: 5 in a row,"
+-- CONTEXT --
+",
+ConstantStrings.num_correct_in_a_row_5_description,Learner must answer 5 questions in a row correctly,"
+-- CONTEXT --
+",
+ConstantStrings.pdf,PDF document,"
+-- CONTEXT --
+",
+ConstantStrings.perseus,Perseus Exercise,"
+-- CONTEXT --
+",
+ConstantStrings.perseus_question,Khan Academy question,"
+-- CONTEXT --
+",
+ConstantStrings.png,PNG image,"
+-- CONTEXT --
+",
+ConstantStrings.public,Content library,"
+-- CONTEXT --
+",
+ConstantStrings.single_selection,Single choice,"
+-- CONTEXT --
+",
+ConstantStrings.slideshow,Slideshow,"
+-- CONTEXT --
+",
+ConstantStrings.svg,SVG image,"
+-- CONTEXT --
+",
+ConstantStrings.topic,Folder,"
+-- CONTEXT --
+",
+ConstantStrings.topic_thumbnail,Thumbnail,"
+-- CONTEXT --
+",
+ConstantStrings.true_false,True/False,"
+-- CONTEXT --
+",
+ConstantStrings.unknown_question,Unknown question type,"
+-- CONTEXT --
+",
+ConstantStrings.video,Video,"
+-- CONTEXT --
+",
+ConstantStrings.video_subtitle,Captions,"
+-- CONTEXT --
+",
+ConstantStrings.video_thumbnail,Thumbnail,"
+-- CONTEXT --
+",
+ConstantStrings.view,View-only,"
+-- CONTEXT --
+",
+ConstantStrings.vtt,VTT caption,"
+-- CONTEXT --
+",
+ConstantStrings.webm,WEBM video,"
+-- CONTEXT --
+",
+ConstantStrings.zip,HTML5 zip,"
+-- CONTEXT --
+",
+ContentDefaults.aggregator,Aggregator,"
+-- CONTEXT --
+",
+ContentDefaults.author,Author,"
+-- CONTEXT --
+",
+ContentDefaults.copyrightHolder,Copyright holder,"
+-- CONTEXT --
+",
+ContentDefaults.defaultsSubTitle,New resources will be automatically given these values,"
+-- CONTEXT --
+",
+ContentDefaults.defaultsTitle,Default copyright settings for new resources (optional),"
+-- CONTEXT --
+",
+ContentDefaults.documents,Documents,"
+-- CONTEXT --
+",
+ContentDefaults.html5,HTML5 apps,"
+-- CONTEXT --
+",
+ContentDefaults.license,License,"
+-- CONTEXT --
+",
+ContentDefaults.licenseDescription,License description,"
+-- CONTEXT --
+",
+ContentDefaults.noLicense,No license selected,"
+-- CONTEXT --
+",
+ContentDefaults.provider,Provider,"
+-- CONTEXT --
+",
+ContentDefaults.thumbnailsTitle,Automatically generate thumbnails for the following resource types,"
+-- CONTEXT --
+",
+ContentDefaults.videos,Videos,"
+-- CONTEXT --
+",
+ContentNodeChangedIcon.containsNew,Contains unpublished resources,"
+-- CONTEXT --
+",
+ContentNodeChangedIcon.containsNewAndUpdated,Contains unpublished resources and changes,"
+-- CONTEXT --
+",
+ContentNodeChangedIcon.containsUpdated,Contains unpublished changes,"
+-- CONTEXT --
+",
+ContentNodeChangedIcon.isNewResource,Unpublished,"
+-- CONTEXT --
+",
+ContentNodeChangedIcon.isNewTopic,Unpublished folder,"
+-- CONTEXT --
+",
+ContentNodeChangedIcon.isUpdatedResource,Updated since last publish,"
+-- CONTEXT --
+",
+ContentNodeChangedIcon.isUpdatedTopic,Folder has been updated since last publish,"
+-- CONTEXT --
+",
+ContentNodeCopyTaskProgress.copyErrorTopic,Some resources failed to copy,"
+-- CONTEXT --
+",
+ContentNodeEditListItem.copiedSnackbar,Copy operation complete,"
+-- CONTEXT --
+",
+ContentNodeEditListItem.creatingCopies,Copying...,"
+-- CONTEXT --
+",
+ContentNodeEditListItem.optionsTooltip,Options,"
+-- CONTEXT --
+",
+ContentNodeEditListItem.removeNode,Remove,"
+-- CONTEXT --
+",
+ContentNodeEditListItem.retryCopy,Retry,"
+-- CONTEXT --
+",
+ContentNodeEditListItem.undo,Undo,"
+-- CONTEXT --
+",
+ContentNodeIcon.audio,Audio,"
+-- CONTEXT --
+",
+ContentNodeIcon.document,Document,"
+-- CONTEXT --
+",
+ContentNodeIcon.exercise,Exercise,"
+-- CONTEXT --
+",
+ContentNodeIcon.html5,HTML5 App,"
+-- CONTEXT --
+",
+ContentNodeIcon.slideshow,Slideshow,"
+-- CONTEXT --
+",
+ContentNodeIcon.topic,Folder,"
+-- CONTEXT --
+",
+ContentNodeIcon.unsupported,Unsupported,"
+-- CONTEXT --
+",
+ContentNodeIcon.video,Video,"
+-- CONTEXT --
+",
+ContentNodeLearningActivityIcon.multipleLearningActivities,Multiple learning activities,"
+-- CONTEXT --
+",
+ContentNodeListItem.coachTooltip,Resource for coaches,"
+-- CONTEXT --
+",
+ContentNodeListItem.copyingError,Copy failed.,"
+-- CONTEXT --
+",
+ContentNodeListItem.copyingTask,Copying,"
+-- CONTEXT --
+",
+ContentNodeListItem.hasCoachTooltip,"{value, number, integer} {value, plural, one {resource for coaches} other {resources for coaches}}","
+-- CONTEXT --
+",
+ContentNodeListItem.openTopic,Open folder,"
+-- CONTEXT --
+",
+ContentNodeListItem.questions,"{value, number, integer} {value, plural, one {question} other {questions}}","
+-- CONTEXT --
+",
+ContentNodeListItem.resources,"{value, number, integer} {value, plural, one {resource} other {resources}}","
+-- CONTEXT --
+",
+ContentNodeOptions.copiedItemsToClipboard,Copied in clipboard,"
+-- CONTEXT --
+",
+ContentNodeOptions.copiedSnackbar,Copy operation complete,"
+-- CONTEXT --
+",
+ContentNodeOptions.copiedToClipboardSnackbar,Copied to clipboard,"
+-- CONTEXT --
+",
+ContentNodeOptions.copyToClipboard,Copy to clipboard,"
+-- CONTEXT --
+",
+ContentNodeOptions.creatingCopies,Copying...,"
+-- CONTEXT --
+",
+ContentNodeOptions.editDetails,Edit details,"
+-- CONTEXT --
+",
+ContentNodeOptions.editTopicDetails,Edit folder details,"
+-- CONTEXT --
+",
+ContentNodeOptions.goToOriginalLocation,Go to original location,"
+-- CONTEXT --
+",
+ContentNodeOptions.makeACopy,Make a copy,"
+-- CONTEXT --
+",
+ContentNodeOptions.move,Move,"
+-- CONTEXT --
+",
+ContentNodeOptions.moveTo,Move to...,"
+-- CONTEXT --
+",
+ContentNodeOptions.newSubtopic,New folder,"
+-- CONTEXT --
+",
+ContentNodeOptions.remove,Delete,"
+-- CONTEXT --
+",
+ContentNodeOptions.removedFromClipboard,Deleted from clipboard,"
+-- CONTEXT --
+",
+ContentNodeOptions.removedItems,Sent to trash,"
+-- CONTEXT --
+",
+ContentNodeOptions.undo,Undo,"
+-- CONTEXT --
+",
+ContentNodeOptions.viewDetails,View details,"
+-- CONTEXT --
+",
+ContentNodeStrings.untitled,Untitled,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.cancel,Cancel,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.crop,Crop,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.croppingPrompt,Drag image to reframe,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.defaultFilename,File,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.generate,Generate from file,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.generatingThumbnail,Generating from file,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.noThumbnail,No thumbnail,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.remove,Remove,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.retryUpload,Retry upload,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.save,Save,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.upload,Upload image,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.uploadFailed,Upload failed,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.uploadingThumbnail,Uploading,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.zoomIn,Zoom in,"
+-- CONTEXT --
+",
+ContentNodeThumbnail.zoomOut,Zoom out,"
+-- CONTEXT --
+",
+ContentNodeValidator.allIncompleteDescendantsText,"{count, plural, one {{count, number, integer} resource is incomplete and cannot be published} other {All {count, number, integer} resources are incomplete and cannot be published}}","
+-- CONTEXT --
+",
+ContentNodeValidator.incompleteDescendantsText,"{count, number, integer} {count, plural, one {resource is incomplete} other {resources are incomplete}}","
+-- CONTEXT --
+",
+ContentNodeValidator.incompleteText,Incomplete,"
+-- CONTEXT --
+",
+ContentNodeValidator.missingTitle,Missing title,"
+-- CONTEXT --
+",
+ContentRenderer.noFileText,Select a file to preview,"
+-- CONTEXT --
+",
+ContentRenderer.previewNotSupported,Preview unavailable,"
+-- CONTEXT --
+",
+ContentTreeList.allChannelsLabel,Channels,"
+-- CONTEXT --
+",
+ContentTreeList.noResourcesOrTopics,There are no resources or folders here,"
+-- CONTEXT --
+",
+ContentTreeList.selectAllAction,Select all,"
+-- CONTEXT --
+",
+CopyToken.copiedTokenId,Token copied,"
+-- CONTEXT --
+",
+CopyToken.copyFailed,Copy failed,"
+-- CONTEXT --
+",
+CopyToken.copyPrompt,Copy token to import channel into Kolibri,"
+-- CONTEXT --
+",
+CountryField.locationLabel,Select all that apply,"
+-- CONTEXT --
+",
+CountryField.locationRequiredMessage,Field is required,"
+-- CONTEXT --
+",
+CountryField.noCountriesFound,No countries found,"
+-- CONTEXT --
+",
+Create.ToSRequiredMessage,Please accept our terms of service and policy,"
+-- CONTEXT --
+",
+Create.agreement,I have read and agree to terms of service and the privacy policy,"
+-- CONTEXT --
+",
+Create.backToLoginButton,Sign in,"
+-- CONTEXT --
+",
+Create.basicInformationHeader,Basic information,"
+-- CONTEXT --
+",
+Create.conferenceSourceOption,Conference,"
+-- CONTEXT --
+",
+Create.conferenceSourcePlaceholder,Name of conference,"
+-- CONTEXT --
+",
+Create.confirmPasswordLabel,Confirm password,"
+-- CONTEXT --
+",
+Create.contactMessage,Questions or concerns? Please email us at content@learningequality.org,"
+-- CONTEXT --
+",
+Create.conversationSourceOption,Conversation with Learning Equality,"
+-- CONTEXT --
+",
+Create.createAnAccountTitle,Create an account,"
+-- CONTEXT --
+",
+Create.creatingExercisesUsageOption,Creating exercises,"
+-- CONTEXT --
+",
+Create.emailExistsMessage,An account with this email already exists,"
+-- CONTEXT --
+",
+Create.errorsMessage,Please fix the errors below,"
+-- CONTEXT --
+",
+Create.fieldRequiredMessage,Field is required,"
+-- CONTEXT --
+",
+Create.findingUsageOption,Finding and adding additional content sources,"
+-- CONTEXT --
+",
+Create.finishButton,Finish,"
+-- CONTEXT --
+",
+Create.firstNameLabel,First name,"
+-- CONTEXT --
+",
+Create.forumSourceOption,Learning Equality community forum,"
+-- CONTEXT --
+",
+Create.githubSourceOption,Learning Equality GitHub,"
+-- CONTEXT --
+",
+Create.lastNameLabel,Last name,"
+-- CONTEXT --
+",
+Create.locationLabel,Where do you plan to use Kolibri Studio? (check all that apply),"
+-- CONTEXT --
+",
+Create.newsletterSourceOption,Learning Equality newsletter,"
+-- CONTEXT --
+",
+Create.organizationSourceOption,Organization,"
+-- CONTEXT --
+",
+Create.organizationSourcePlaceholder,Name of organization,"
+-- CONTEXT --
+",
+Create.organizingUsageOption,Organizing or aligning existing materials,"
+-- CONTEXT --
+",
+Create.otherSourceOption,Other,"
+-- CONTEXT --
+",
+Create.otherSourcePlaceholder,Please describe,"
+-- CONTEXT --
+",
+Create.otherUsageOption,Other,"
+-- CONTEXT --
+",
+Create.otherUsagePlaceholder,Please describe,"
+-- CONTEXT --
+",
+Create.passwordLabel,Password,"
+-- CONTEXT --
+",
+Create.passwordMatchMessage,Passwords don't match,"
+-- CONTEXT --
+",
+Create.personalDemoSourceOption,Personal demo,"
+-- CONTEXT --
+",
+Create.registrationFailed,There was an error registering your account. Please try again,"
+-- CONTEXT --
+",
+Create.registrationFailedOffline,You seem to be offline. Please connect to the internet to create an account.,"
+-- CONTEXT --
+",
+Create.sequencingUsageOption,Using prerequisites to put materials in a sequence,"
+-- CONTEXT --
+",
+Create.sharingUsageOption,Sharing materials publicly,"
+-- CONTEXT --
+",
+Create.socialMediaSourceOption,Social media,"
+-- CONTEXT --
+",
+Create.sourceLabel,How did you hear about us?,"
+-- CONTEXT --
+",
+Create.sourcePlaceholder,Select one,"
+-- CONTEXT --
+",
+Create.storingUsageExample,e.g. 500MB,"
+-- CONTEXT --
+",
+Create.storingUsageOption,Storing materials for private or local use,"
+-- CONTEXT --
+",
+Create.storingUsagePlaceholder,How much storage do you need?,"
+-- CONTEXT --
+",
+Create.taggingUsageOption,Tagging content sources for discovery,"
+-- CONTEXT --
+",
+Create.usageLabel,How do you plan on using Kolibri Studio (check all that apply),"
+-- CONTEXT --
+",
+Create.viewPrivacyPolicyLink,View Privacy Policy,"
+-- CONTEXT --
+",
+Create.viewToSLink,View Terms of Service,"
+-- CONTEXT --
+",
+Create.websiteSourceOption,Learning Equality website,"
+-- CONTEXT --
+",
+CurrentTopicView.COMFORTABLE_VIEW,Comfortable view,"
+-- CONTEXT --
+",
+CurrentTopicView.COMPACT_VIEW,Compact view,"
+-- CONTEXT --
+",
+CurrentTopicView.DEFAULT_VIEW,Default view,"
+-- CONTEXT --
+",
+CurrentTopicView.addButton,Add,"
+-- CONTEXT --
+",
+CurrentTopicView.addExercise,New exercise,"
+-- CONTEXT --
+",
+CurrentTopicView.addTopic,New folder,"
+-- CONTEXT --
+",
+CurrentTopicView.copiedItems,Copy operation complete,"
+-- CONTEXT --
+",
+CurrentTopicView.copiedItemsToClipboard,Copied to clipboard,"
+-- CONTEXT --
+",
+CurrentTopicView.copySelectedButton,Copy to clipboard,"
+-- CONTEXT --
+",
+CurrentTopicView.copyToClipboardButton,Copy to clipboard,"
+-- CONTEXT --
+",
+CurrentTopicView.creatingCopies,Copying...,"
+-- CONTEXT --
+",
+CurrentTopicView.deleteSelectedButton,Delete,"
+-- CONTEXT --
+",
+CurrentTopicView.duplicateSelectedButton,Make a copy,"
+-- CONTEXT --
+",
+CurrentTopicView.editButton,Edit,"
+-- CONTEXT --
+",
+CurrentTopicView.editSelectedButton,Edit,"
+-- CONTEXT --
+",
+CurrentTopicView.importFromChannels,Import from channels,"
+-- CONTEXT --
+",
+CurrentTopicView.moveSelectedButton,Move,"
+-- CONTEXT --
+",
+CurrentTopicView.optionsButton,Options,"
+-- CONTEXT --
+",
+CurrentTopicView.removedItems,Sent to trash,"
+-- CONTEXT --
+",
+CurrentTopicView.selectAllLabel,Select all,"
+-- CONTEXT --
+",
+CurrentTopicView.selectionCount,"{topicCount, plural,
+ =1 {# folder}
+ other {# folders}}, {resourceCount, plural,
+ =1 {# resource}
+ other {# resources}}","
+-- CONTEXT --
+",
+CurrentTopicView.undo,Undo,"
+-- CONTEXT --
+",
+CurrentTopicView.uploadFiles,Upload files,"
+-- CONTEXT --
+",
+CurrentTopicView.viewModeTooltip,View,"
+-- CONTEXT --
+",
+DeleteAccountForm.cancelButton,Cancel,"
+-- CONTEXT --
+",
+DeleteAccountForm.deleteAccountConfirmationPrompt,Are you sure you want to permanently delete your account? This cannot be undone,"
+-- CONTEXT --
+",
+DeleteAccountForm.deleteAccountEnterEmail,Enter your email address to continue,"
+-- CONTEXT --
+",
+DeleteAccountForm.deleteAccountLabel,Delete account,"
+-- CONTEXT --
+",
+DeleteAccountForm.deletionFailed,Failed to delete account,"
+-- CONTEXT --
+",
+DeleteAccountForm.deletionFailedText,Failed to delete your account. Please contact us here: https://community.learningequality.org.,"
+-- CONTEXT --
+",
+DeleteAccountForm.emailAddressLabel,Email address,"
+-- CONTEXT --
+",
+DeleteAccountForm.emailInvalidText,Email does not match your account email,"
+-- CONTEXT --
+",
+DeleteAccountForm.fieldRequired,Field is required,"
+-- CONTEXT --
+",
+Details.AVERAGE,Average,"
+-- CONTEXT --
+",
+Details.LARGE,Large,"
+-- CONTEXT --
+",
+Details.SMALL,Small,"
+-- CONTEXT --
+",
+Details.VERY_LARGE,Very large,"
+-- CONTEXT --
+",
+Details.VERY_SMALL,Very small,"
+-- CONTEXT --
+",
+Details.aggregatorToolTip,Website or organization hosting the content collection but not necessarily the creator or copyright holder,"
+-- CONTEXT --
+",
+Details.aggregatorsLabel,Aggregators,"
+-- CONTEXT --
+",
+Details.assessmentsIncludedText,Assessments,"
+-- CONTEXT --
+",
+Details.authorToolTip,Person or organization who created this content,"
+-- CONTEXT --
+",
+Details.authorsLabel,Authors,"
+-- CONTEXT --
+",
+Details.categoriesHeading,Categories,"
+-- CONTEXT --
+",
+Details.coachDescription,Resources for coaches are only visible to coaches in Kolibri,"
+-- CONTEXT --
+",
+Details.coachHeading,Resources for coaches,"
+-- CONTEXT --
+",
+Details.containsContentHeading,Contains content from,"
+-- CONTEXT --
+",
+Details.containsHeading,Contains,"
+-- CONTEXT --
+",
+Details.copyrightHoldersLabel,Copyright holders,"
+-- CONTEXT --
+",
+Details.creationHeading,Created on,"
+-- CONTEXT --
+",
+Details.currentVersionHeading,Published version,"
+-- CONTEXT --
+",
+Details.languagesHeading,Languages,"
+-- CONTEXT --
+",
+Details.levelsHeading,Levels,"
+-- CONTEXT --
+",
+Details.licensesLabel,Licenses,"
+-- CONTEXT --
+",
+Details.primaryLanguageHeading,Primary language,"
+-- CONTEXT --
+",
+Details.providerToolTip,Organization that commissioned or is distributing the content,"
+-- CONTEXT --
+",
+Details.providersLabel,Providers,"
+-- CONTEXT --
+",
+Details.publishedHeading,Published on,"
+-- CONTEXT --
+",
+Details.resourceHeading,Total resources,"
+-- CONTEXT --
+",
+Details.sampleFromChannelHeading,Sample content from this channel,"
+-- CONTEXT --
+",
+Details.sampleFromTopicHeading,Sample content from this topic,"
+-- CONTEXT --
+",
+Details.sizeHeading,Channel size,"
+-- CONTEXT --
+",
+Details.sizeText,{text} ({size}),"
+-- CONTEXT --
+",
+Details.subtitlesHeading,Captions and subtitles,"
+-- CONTEXT --
+",
+Details.tagsHeading,Common tags,"
+-- CONTEXT --
+",
+Details.tokenHeading,Channel token,"
+-- CONTEXT --
+",
+Details.unpublishedText,Unpublished,"
+-- CONTEXT --
+",
+DetailsTabView.aggregatorLabel,Aggregator,"
+-- CONTEXT --
+",
+DetailsTabView.aggregatorToolTip,Website or org hosting the content collection but not necessarily the creator or copyright holder,"
+-- CONTEXT --
+",
+DetailsTabView.assessmentOptionsLabel,Assessment options,"
+-- CONTEXT --
+",
+DetailsTabView.audienceHeader,Audience,"
+-- CONTEXT --
+",
+DetailsTabView.authorLabel,Author,"
+-- CONTEXT --
+",
+DetailsTabView.authorToolTip,Person or organization who created this content,"
+-- CONTEXT --
+",
+DetailsTabView.basicInfoHeader,Basic information,"
+-- CONTEXT --
+",
+DetailsTabView.completionLabel,Completion,"
+-- CONTEXT --
+",
+DetailsTabView.copyrightHolderLabel,Copyright holder,"
+-- CONTEXT --
+",
+DetailsTabView.descriptionLabel,Description,"
+-- CONTEXT --
+",
+DetailsTabView.detectedImportText,"{count, plural,
+ =1 {# resource has view-only permission}
+ other {# resources have view-only permission}}","
+-- CONTEXT --
+",
+DetailsTabView.importedFromButtonText,Imported from {channel},"
+-- CONTEXT --
+",
+DetailsTabView.languageChannelHelpText,Leave blank to use the channel language,"
+-- CONTEXT --
+",
+DetailsTabView.languageHelpText,Leave blank to use the folder language,"
+-- CONTEXT --
+",
+DetailsTabView.noTagsFoundText,"No results found for ""{text}"". Press 'Enter' key to create a new tag","
+-- CONTEXT --
+",
+DetailsTabView.providerLabel,Provider,"
+-- CONTEXT --
+",
+DetailsTabView.providerToolTip,Organization that commissioned or is distributing the content,"
+-- CONTEXT --
+",
+DetailsTabView.randomizeQuestionLabel,Randomize question order for learners,"
+-- CONTEXT --
+",
+DetailsTabView.sourceHeader,Source,"
+-- CONTEXT --
+",
+DetailsTabView.tagsLabel,Tags,"
+-- CONTEXT --
+",
+DetailsTabView.thumbnailHeader,Thumbnail,"
+-- CONTEXT --
+",
+DetailsTabView.titleLabel,Title,"
+-- CONTEXT --
+",
+Diff.negativeSign,-,"
+-- CONTEXT --
+",
+Diff.positiveSign,+,"
+-- CONTEXT --
+",
+DiffTable.headerDiff,Net changes,"
+-- CONTEXT --
+",
+DiffTable.headerLive,Live,"
+-- CONTEXT --
+",
+DiffTable.headerStaged,Staged,"
+-- CONTEXT --
+",
+DiffTable.headerType,Type,"
+-- CONTEXT --
+",
+DiffTable.typeAudios,Audios,"
+-- CONTEXT --
+",
+DiffTable.typeDocuments,Documents,"
+-- CONTEXT --
+",
+DiffTable.typeExercises,Exercises,"
+-- CONTEXT --
+",
+DiffTable.typeFileSize,File size,"
+-- CONTEXT --
+",
+DiffTable.typeHtml5Apps,HTML5 apps,"
+-- CONTEXT --
+",
+DiffTable.typeSlideshows,Slideshows,"
+-- CONTEXT --
+",
+DiffTable.typeTopics,Folders,"
+-- CONTEXT --
+",
+DiffTable.typeVersion,API version,"
+-- CONTEXT --
+",
+DiffTable.typeVideos,Videos,"
+-- CONTEXT --
+",
+EditList.selectAllLabel,Select all,"
+-- CONTEXT --
+",
+EditListItem.questionCount,"{count, plural,
+ =1 {# question}
+ other {# questions}}","
+-- CONTEXT --
+",
+EditModal.addTopic,Add new folder,"
+-- CONTEXT --
+",
+EditModal.addTopicsHeader,New folder,"
+-- CONTEXT --
+",
+EditModal.cancelUploadsButton,Exit,"
+-- CONTEXT --
+",
+EditModal.closeWithoutSavingButton,Close without saving,"
+-- CONTEXT --
+",
+EditModal.createExerciseHeader,New exercise,"
+-- CONTEXT --
+",
+EditModal.dismissDialogButton,Cancel,"
+-- CONTEXT --
+",
+EditModal.editFilesHeader,Edit files,"
+-- CONTEXT --
+",
+EditModal.editingDetailsHeader,Edit details,"
+-- CONTEXT --
+",
+EditModal.finishButton,Finish,"
+-- CONTEXT --
+",
+EditModal.invalidNodesFound,"{count, plural,
+ =1 {# incomplete resource found}
+ other {# incomplete resources found}}","
+-- CONTEXT --
+",
+EditModal.invalidNodesFoundText,Incomplete resources will not be published until these errors are resolved,"
+-- CONTEXT --
+",
+EditModal.keepEditingButton,Keep editing,"
+-- CONTEXT --
+",
+EditModal.loadErrorText,Failed to load content,"
+-- CONTEXT --
+",
+EditModal.okButton,OK,"
+-- CONTEXT --
+",
+EditModal.saveAnywaysButton,Exit anyway,"
+-- CONTEXT --
+",
+EditModal.saveFailedHeader,Save failed,"
+-- CONTEXT --
+",
+EditModal.saveFailedText,There was a problem saving your content,"
+-- CONTEXT --
+",
+EditModal.uploadButton,Upload more,"
+-- CONTEXT --
+",
+EditModal.uploadFilesHeader,Upload files,"
+-- CONTEXT --
+",
+EditModal.uploadInProgressHeader,Upload in progress,"
+-- CONTEXT --
+",
+EditModal.uploadInProgressText,Uploads that are in progress will be lost if you exit,"
+-- CONTEXT --
+",
+EditSearchModal.cancelAction,Cancel,"
+-- CONTEXT --
+",
+EditSearchModal.changesSavedSnackbar,Changes saved,"
+-- CONTEXT --
+",
+EditSearchModal.editSavedSearchTitle,Edit search title,"
+-- CONTEXT --
+",
+EditSearchModal.fieldRequired,Field is required,"
+-- CONTEXT --
+",
+EditSearchModal.saveChangesAction,Save,"
+-- CONTEXT --
+",
+EditSearchModal.searchTitleLabel,Search title,"
+-- CONTEXT --
+",
+EditView.details,Details,"
+-- CONTEXT --
+",
+EditView.editingMultipleCount,"Editing details for {topicCount, plural,
+ =1 {# folder}
+ other {# folders}}, {resourceCount, plural,
+ =1 {# resource}
+ other {# resources}}","
+-- CONTEXT --
+",
+EditView.errorBannerText,Please provide the required information,"
+-- CONTEXT --
+",
+EditView.invalidFieldsToolTip,Some required information is missing,"
+-- CONTEXT --
+",
+EditView.noItemsToEditText,Please select resources or folders to edit,"
+-- CONTEXT --
+",
+EditView.preview,Preview,"
+-- CONTEXT --
+",
+EditView.questions,Questions,"
+-- CONTEXT --
+",
+EditView.related,Related,"
+-- CONTEXT --
+",
+EmailField.emailLabel,Email,"
+-- CONTEXT --
+",
+EmailField.emailRequiredMessage,Field is required,"
+-- CONTEXT --
+",
+EmailField.validEmailMessage,Please enter a valid email,"
+-- CONTEXT --
+",
+ExpandableList.less,Show less,"
+-- CONTEXT --
+",
+ExpandableList.more,Show more ({more}),"
+-- CONTEXT --
+",
+FilePreview.exitFullscreen,Exit fullscreen,"
+-- CONTEXT --
+",
+FilePreview.fullscreenModeText,Fullscreen mode,"
+-- CONTEXT --
+",
+FilePreview.viewFullscreen,View fullscreen,"
+-- CONTEXT --
+",
+FileStatusText.selectFile,Select file,"
+-- CONTEXT --
+",
+FileStorage.requestStorage,Request storage,"
+-- CONTEXT --
+",
+FileStorage.storageFull,Storage limit reached,"
+-- CONTEXT --
+",
+FileStorage.storageFullWithSize,Total storage limit reached: {used} of {total},"
+-- CONTEXT --
+",
+FileStorage.storageLow,Storage is running low,"
+-- CONTEXT --
+",
+FileStorage.storageLowWithSize,Total storage is running low: {used} of {total},"
+-- CONTEXT --
+",
+FileStorage.storageUsed,Total storage used: {used} of {total},"
+-- CONTEXT --
+",
+FileUpload.fileError,Unsupported file type,"
+-- CONTEXT --
+",
+FileUpload.filesHeader,Preview files,"
+-- CONTEXT --
+",
+FileUpload.noFileText,Missing files,"
+-- CONTEXT --
+",
+FileUploadDefault.acceptsHelp,Supported file types: {extensions},"
+-- CONTEXT --
+",
+FileUploadDefault.chooseFilesButton,Select files,"
+-- CONTEXT --
+",
+FileUploadDefault.dropHereText,"Drag and drop your files here, or select your files manually","
+-- CONTEXT --
+",
+FileUploadDefault.uploadToText,Upload to '{title}',"
+-- CONTEXT --
+",
+FileUploadItem.removeFileButton,Remove,"
+-- CONTEXT --
+",
+FileUploadItem.retryUpload,Retry upload,"
+-- CONTEXT --
+",
+FileUploadItem.unknownFile,Unknown filename,"
+-- CONTEXT --
+",
+FileUploadItem.uploadButton,Select file,"
+-- CONTEXT --
+",
+FileUploadItem.uploadFailed,Upload failed,"
+-- CONTEXT --
+",
+ForgotPassword.forgotPasswordFailed,Failed to send a password reset link. Please try again.,"
+-- CONTEXT --
+",
+ForgotPassword.forgotPasswordPrompt,Please enter your email address to receive instructions for resetting your password,"
+-- CONTEXT --
+",
+ForgotPassword.forgotPasswordTitle,Reset your password,"
+-- CONTEXT --
+",
+ForgotPassword.submitButton,Submit,"
+-- CONTEXT --
+",
+FormulasMenu.btnLabelInsert,Insert,"
+-- CONTEXT --
+",
+FormulasMenu.formulasMenuTitle,Special characters,"
+-- CONTEXT --
+",
+FullNameForm.cancelAction,Cancel,"
+-- CONTEXT --
+",
+FullNameForm.changesSavedMessage,Changes saved,"
+-- CONTEXT --
+",
+FullNameForm.editNameHeader,Edit full name,"
+-- CONTEXT --
+",
+FullNameForm.failedToSaveMessage,Failed to save changes,"
+-- CONTEXT --
+",
+FullNameForm.fieldRequired,Field is required,"
+-- CONTEXT --
+",
+FullNameForm.firstNameLabel,First name,"
+-- CONTEXT --
+",
+FullNameForm.lastNameLabel,Last name,"
+-- CONTEXT --
+",
+FullNameForm.saveChangesAction,Save changes,"
+-- CONTEXT --
+",
+GenericError.backToHomeAction,Back to home,"
+-- CONTEXT --
+",
+GenericError.genericErrorDetails,Try refreshing this page or going back to the home page,"
+-- CONTEXT --
+",
+GenericError.genericErrorHeader,"Sorry, something went wrong","
+-- CONTEXT --
+",
+GenericError.helpByReportingAction,Help us by reporting this error,"
+-- CONTEXT --
+",
+GenericError.refreshAction,Refresh,"
+-- CONTEXT --
+",
+HintsEditor.hintsLabel,Hints,"
+-- CONTEXT --
+",
+HintsEditor.newHintBtnLabel,New hint,"
+-- CONTEXT --
+",
+HintsEditor.noHintsPlaceholder,Question has no hints,"
+-- CONTEXT --
+",
+ImageOnlyThumbnail.thumbnail,{title} thumbnail,"
+-- CONTEXT --
+",
+ImagesMenu.acceptsText,Supported file types: {acceptedFormats},"
+-- CONTEXT --
+",
+ImagesMenu.altTextHint,"The image description is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load","
+-- CONTEXT --
+",
+ImagesMenu.altTextLabel,Image description,"
+-- CONTEXT --
+",
+ImagesMenu.btnLabelCancel,Cancel,"
+-- CONTEXT --
+",
+ImagesMenu.btnLabelInsert,Insert,"
+-- CONTEXT --
+",
+ImagesMenu.currentImageDefaultText,Current image,"
+-- CONTEXT --
+",
+ImagesMenu.defaultDropText,"Drag and drop an image here, or upload manually","
+-- CONTEXT --
+",
+ImagesMenu.imageHeader,Upload image,"
+-- CONTEXT --
+",
+ImagesMenu.selectFile,Select file,"
+-- CONTEXT --
+",
+ImagesMenu.selectFileButton,Select file,"
+-- CONTEXT --
+",
+ImportFromChannelsModal.addButton,Add,"
+-- CONTEXT --
+",
+ImportFromChannelsModal.addedText,Added,"
+-- CONTEXT --
+",
+ImportFromChannelsModal.importAction,Import,"
+-- CONTEXT --
+",
+ImportFromChannelsModal.importTitle,Import from other channels,"
+-- CONTEXT --
+",
+ImportFromChannelsModal.removeButton,Remove,"
+-- CONTEXT --
+",
+ImportFromChannelsModal.resourcesAddedSnackbar,"{count, number} {count, plural, one {resource selected} other {resources selected}}","
+-- CONTEXT --
+",
+ImportFromChannelsModal.resourcesRemovedSnackbar,"{count, number} {count, plural, one {resource removed} other {resources removed}}","
+-- CONTEXT --
+",
+ImportFromChannelsModal.resourcesSelected,"{count, number} {count, plural, one {resource selected} other {resources selected}}","
+-- CONTEXT --
+",
+ImportFromChannelsModal.reviewAction,Review,"
+-- CONTEXT --
+",
+ImportFromChannelsModal.reviewTitle,Resource selection,"
+-- CONTEXT --
+",
+InfoModal.close,Close,"
+-- CONTEXT --
+",
+LanguageDropdown.labelText,Language,"
+-- CONTEXT --
+",
+LanguageDropdown.languageItemText,{language} ({code}),"
+-- CONTEXT --
+",
+LanguageDropdown.languageRequired,Field is required,"
+-- CONTEXT --
+",
+LanguageDropdown.noDataText,Language not found,"
+-- CONTEXT --
+",
+LanguageFilter.languageLabel,Languages,"
+-- CONTEXT --
+",
+LanguageFilter.noMatchingLanguageText,No language matches the search,"
+-- CONTEXT --
+",
+LanguageSwitcherList.showMoreLanguagesSelector,More languages,"
+-- CONTEXT --
+",
+LanguageSwitcherModal.cancelAction,Cancel,"
+-- CONTEXT --
+",
+LanguageSwitcherModal.changeLanguageModalHeader,Change language,"
+-- CONTEXT --
+",
+LanguageSwitcherModal.confirmAction,Confirm,"
+-- CONTEXT --
+",
+LicenseDropdown.learnMoreButton,Learn More,"
+-- CONTEXT --
+",
+LicenseDropdown.licenseDescriptionLabel,License description,"
+-- CONTEXT --
+",
+LicenseDropdown.licenseInfoHeader,About licenses,"
+-- CONTEXT --
+",
+LicenseDropdown.licenseLabel,License,"
+-- CONTEXT --
+",
+Main.TOSLink,Terms of service,"
+-- CONTEXT --
+",
+Main.copyright,Ā© {year} Learning Equality,"
+-- CONTEXT --
+",
+Main.createAccountButton,Create an account,"
+-- CONTEXT --
+",
+Main.forgotPasswordLink,Forgot your password?,"
+-- CONTEXT --
+",
+Main.guestModeLink,Explore without an account,"
+-- CONTEXT --
+",
+Main.kolibriStudio,Kolibri Studio,"
+-- CONTEXT --
+",
+Main.loginFailed,Email or password is incorrect,"
+-- CONTEXT --
+",
+Main.loginFailedOffline,You seem to be offline. Please connect to the internet before signing in.,"
+-- CONTEXT --
+",
+Main.loginToProceed,You must sign in to view that page,"
+-- CONTEXT --
+",
+Main.passwordLabel,Password,"
+-- CONTEXT --
+",
+Main.privacyPolicyLink,Privacy policy,"
+-- CONTEXT --
+",
+Main.signInButton,Sign in,"
+-- CONTEXT --
+",
+MainNavigationDrawer.administrationLink,Administration,"
+-- CONTEXT --
+",
+MainNavigationDrawer.changeLanguage,Change language,"
+-- CONTEXT --
+",
+MainNavigationDrawer.channelsLink,Channels,"
+-- CONTEXT --
+",
+MainNavigationDrawer.copyright,Ā© {year} Learning Equality,"
+-- CONTEXT --
+",
+MainNavigationDrawer.giveFeedback,Give feedback,"
+-- CONTEXT --
+",
+MainNavigationDrawer.helpLink,Help and support,"
+-- CONTEXT --
+",
+MainNavigationDrawer.logoutLink,Sign out,"
+-- CONTEXT --
+",
+MainNavigationDrawer.settingsLink,Settings,"
+-- CONTEXT --
+",
+MarkdownEditor.bold,Bold (Ctrl+B),"
+-- CONTEXT --
+",
+MarkdownEditor.formulas,Insert formula (Ctrl+F),"
+-- CONTEXT --
+",
+MarkdownEditor.image,Insert image (Ctrl+P),"
+-- CONTEXT --
+",
+MarkdownEditor.italic,Italic (Ctrl+I),"
+-- CONTEXT --
+",
+MarkdownEditor.minimize,Minimize (Ctrl+M),"
+-- CONTEXT --
+",
+MarkdownImageField.editImageOption,Edit,"
+-- CONTEXT --
+",
+MarkdownImageField.removeImageOption,Remove,"
+-- CONTEXT --
+",
+MarkdownImageField.resizeImageOption,Resize,"
+-- CONTEXT --
+",
+MasteryCriteriaGoal.labelText,Goal,"
+-- CONTEXT --
+",
+MasteryCriteriaMofNFields.mHint,Correct answers needed,"
+-- CONTEXT --
+",
+MasteryCriteriaMofNFields.nHint,Recent answers,"
+-- CONTEXT --
+",
+MessageLayout.backToLogin,Continue to sign-in page,"
+-- CONTEXT --
+",
+MoveModal.addTopic,Add new folder,"
+-- CONTEXT --
+",
+MoveModal.cancel,Cancel,"
+-- CONTEXT --
+",
+MoveModal.emptyTopicText,No resources found,"
+-- CONTEXT --
+",
+MoveModal.goToLocationButton,Go to location,"
+-- CONTEXT --
+",
+MoveModal.moveHere,Move here,"
+-- CONTEXT --
+",
+MoveModal.moveItems,"Move {topicCount, plural,
+ =1 {# folder}
+ other {# folders}}, {resourceCount, plural,
+ =1 {# resource}
+ other {# resources}} into:","
+-- CONTEXT --
+",
+MoveModal.movedMessage,Moved to {title},"
+-- CONTEXT --
+",
+MoveModal.resourcesCount,"{count, plural,
+ =1 {# resource}
+ other {# resources}}","
+-- CONTEXT --
+",
+MoveModal.topicCreatedMessage,New folder created,"
+-- CONTEXT --
+",
+MultiSelect.noItemsFound,No items found,"
+-- CONTEXT --
+",
+NewTopicModal.cancel,Cancel,"
+-- CONTEXT --
+",
+NewTopicModal.create,Create,"
+-- CONTEXT --
+",
+NewTopicModal.createTopic,Create new folder,"
+-- CONTEXT --
+",
+NewTopicModal.topicTitle,Folder title,"
+-- CONTEXT --
+",
+NewTopicModal.topicTitleRequired,Folder title is required,"
+-- CONTEXT --
+",
+NodePanel.emptyChannelSubText,"Create, upload, or import resources from other channels","
+-- CONTEXT --
+",
+NodePanel.emptyChannelText,"Click ""ADD"" to start building your channel","
+-- CONTEXT --
+",
+NodePanel.emptyTopicText,Nothing in this folder yet,"
+-- CONTEXT --
+",
+NodePanel.emptyViewOnlyChannelText,Nothing in this channel yet,"
+-- CONTEXT --
+",
+NodeTreeNavigation.noResourcesDefaultText,No resources found,"
+-- CONTEXT --
+",
+OfflineText.offlineIndicatorText,Offline,"
+-- CONTEXT --
+",
+OfflineText.offlineText,You seem to be offline. Your changes will be saved once your connection is back.,"
+-- CONTEXT --
+",
+PageNotFoundError.backToHomeAction,Back to home,"
+-- CONTEXT --
+",
+PageNotFoundError.pageNotFoundDetails,"Sorry, that page does not exist","
+-- CONTEXT --
+",
+PageNotFoundError.pageNotFoundHeader,Page not found,"
+-- CONTEXT --
+",
+PasswordField.fieldRequiredMessage,Field is required,"
+-- CONTEXT --
+",
+PasswordField.passwordLabel,Password,"
+-- CONTEXT --
+",
+PasswordInstructionsSent.passwordInstructionsHeader,Instructions sent. Thank you!,"
+-- CONTEXT --
+",
+PasswordInstructionsSent.passwordInstructionsText,"If there is already an account with the email address provided, you should receive the instructions shortly. If you don't see an email from us, please check your spam folder.","
+-- CONTEXT --
+",
+PermissionsError.goToHomePageAction,Go to home page,"
+-- CONTEXT --
+",
+PermissionsError.permissionDeniedHeader,Did you forget to sign in?,"
+-- CONTEXT --
+",
+PoliciesModal.checkboxText,I have agreed to the above terms,"
+-- CONTEXT --
+",
+PoliciesModal.closeButton,Close,"
+-- CONTEXT --
+",
+PoliciesModal.continueButton,Continue,"
+-- CONTEXT --
+",
+PoliciesModal.lastUpdated,Last updated {date},"
+-- CONTEXT --
+",
+PrivacyPolicyModal.privacyHeader,Privacy policy,"
+-- CONTEXT --
+",
+PrivacyPolicyModal.updatedPrivacyHeader,Updated privacy policy,"
+-- CONTEXT --
+",
+ProgressBar.progressText,{percent}%,"
+-- CONTEXT --
+",
+ProgressModal.defaultErrorText,Last attempt to publish failed,"
+-- CONTEXT --
+",
+ProgressModal.lastPublished,Published {last_published},"
+-- CONTEXT --
+",
+ProgressModal.publishHeader,Publishing channel,"
+-- CONTEXT --
+",
+ProgressModal.syncError,Last attempt to sync failed,"
+-- CONTEXT --
+",
+ProgressModal.syncHeader,Syncing resources,"
+-- CONTEXT --
+",
+ProgressModal.syncedSnackbar,Resources synced,"
+-- CONTEXT --
+",
+ProgressModal.unpublishedText,Unpublished,"
+-- CONTEXT --
+",
+PublishModal.cancelButton,Cancel,"
+-- CONTEXT --
+",
+PublishModal.descriptionDescriptionTooltip,This description will be shown to Kolibri admins before they update channel versions,"
+-- CONTEXT --
+",
+PublishModal.descriptionRequiredMessage,Please describe what's new in this version before publishing,"
+-- CONTEXT --
+",
+PublishModal.incompleteCount,"{count, plural, =1 {# incomplete resource} other {# incomplete resources}}","
+-- CONTEXT --
+",
+PublishModal.incompleteInstructions,Click 'Continue' to confirm that you would like to publish anyway.,"
+-- CONTEXT --
+",
+PublishModal.incompleteWarning,Incomplete resources will not be published and made available for download in Kolibri.,"
+-- CONTEXT --
+",
+PublishModal.nextButton,Continue,"
+-- CONTEXT --
+",
+PublishModal.publishButton,Publish,"
+-- CONTEXT --
+",
+PublishModal.publishMessageLabel,Describe what's new in this channel version,"
+-- CONTEXT --
+",
+PublishModal.versionDescriptionLabel,Version description,"
+-- CONTEXT --
+",
+RelatedResourcesList.removeBtnLabel,Remove,"
+-- CONTEXT --
+",
+RelatedResourcesTab.addNextStepBtnLabel,Add next step,"
+-- CONTEXT --
+",
+RelatedResourcesTab.addPreviousStepBtnLabel,Add previous step,"
+-- CONTEXT --
+",
+RelatedResourcesTab.dialogCloseBtnLabel,Close,"
+-- CONTEXT --
+",
+RelatedResourcesTab.nextStepsExplanation,Recommended resources that build on skills or concepts learned in this resource,"
+-- CONTEXT --
+",
+RelatedResourcesTab.nextStepsTitle,Next steps,"
+-- CONTEXT --
+",
+RelatedResourcesTab.previewHelpText,Related resources are displayed as recommendations when learners engage with this resource,"
+-- CONTEXT --
+",
+RelatedResourcesTab.previousStepsExplanation,Recommended resources that introduce skills or concepts needed in order to use this resource,"
+-- CONTEXT --
+",
+RelatedResourcesTab.previousStepsTitle,Previous steps,"
+-- CONTEXT --
+",
+RelatedResourcesTab.removeNextStepBtnLabel,Remove next step,"
+-- CONTEXT --
+",
+RelatedResourcesTab.removePreviousStepBtnLabel,Remove previous step,"
+-- CONTEXT --
+",
+RelatedResourcesTab.removedNextStepSnackbar,Removed next step,"
+-- CONTEXT --
+",
+RelatedResourcesTab.removedPreviousStepSnackbar,Removed previous step,"
+-- CONTEXT --
+",
+RelatedResourcesTab.resourcePreviewDialogHelpText,Related resources in Kolibri display as recommendations alongside the resource that a learner is currently engaging with,"
+-- CONTEXT --
+",
+RelatedResourcesTab.resourcePreviewDialogTitle,Related resources,"
+-- CONTEXT --
+",
+RelatedResourcesTab.showPreviewBtnLabel,Show me,"
+-- CONTEXT --
+",
+RelatedResourcesTab.tooManyNextStepsWarning,Limit the number of next steps to create a more guided learning experience,"
+-- CONTEXT --
+",
+RelatedResourcesTab.tooManyPreviousStepsWarning,Limit the number of previous steps to create a more guided learning experience,"
+-- CONTEXT --
+",
+ReportErrorModal.closeAction,Close,"
+-- CONTEXT --
+",
+ReportErrorModal.emailDescription,Contact the support team with your error details and weāll do our best to help.,"
+-- CONTEXT --
+",
+ReportErrorModal.emailPrompt,Send an email to the developers,"
+-- CONTEXT --
+",
+ReportErrorModal.errorDetailsHeader,Error details,"
+-- CONTEXT --
+",
+ReportErrorModal.forumPostingTips,Include a description of what you were trying to do and what you clicked on when the error appeared.,"
+-- CONTEXT --
+",
+ReportErrorModal.forumPrompt,Visit the community forums,"
+-- CONTEXT --
+",
+ReportErrorModal.forumUseTips,"Search the community forum to see if others encountered similar issues. If there are none reported, please open a new forum post and paste the error details below inside so we can rectify the error in a future version of Kolibri Studio.","
+-- CONTEXT --
+",
+ReportErrorModal.reportErrorHeader,Report Error,"
+-- CONTEXT --
+",
+RequestForm.approximatelyHowManyResourcesLabel,Approximately how many individual resources are you planning to upload?,"
+-- CONTEXT --
+",
+RequestForm.audiencePlaceholder,"In-school learners, adult learners, teachers, etc","
+-- CONTEXT --
+",
+RequestForm.authorLabel,"Who is the author (creator), curator (organizer), and/or aggregator (maintainer) of your content? Please specify","
+-- CONTEXT --
+",
+RequestForm.averageSizeOfResourceLabel,Average size of each resource,"
+-- CONTEXT --
+",
+RequestForm.coupleMonthsLabel,1-2 months,"
+-- CONTEXT --
+",
+RequestForm.explainNeedsInDetailLabel,"Please write a paragraph explaining your needs and use case for Kolibri Studio, and how it will integrate into your programs. Include information about who is curating, deploying, and using the content. Is this work being coordinated by an organization, as part of an educational program? Include justification for the additional space being requested and explanation of the time sensitive nature of your request.","
+-- CONTEXT --
+",
+RequestForm.fieldRequiredText,Field is required,"
+-- CONTEXT --
+",
+RequestForm.forProfitLabel,For-profit or social enterprise company,"
+-- CONTEXT --
+",
+RequestForm.grassrootsLabel,Grassroots and/or volunteer initiative,"
+-- CONTEXT --
+",
+RequestForm.howAreYouUsingYourContentLabel,How are you using your content?,"
+-- CONTEXT --
+",
+RequestForm.howOftenImportedToKolibriLabel,"How many times will this content be imported from Studio into new Kolibri installations per month, on average?","
+-- CONTEXT --
+",
+RequestForm.intendedAudienceLabel,Who is the intended audience for your channel? How big is your audience?,"
+-- CONTEXT --
+",
+RequestForm.kindOfContentQuestionLabel,What types of resources do you plan to upload? Please specify,"
+-- CONTEXT --
+",
+RequestForm.largeIntlNgoLabel,Larger international NGOs or government agencies,"
+-- CONTEXT --
+",
+RequestForm.learnMoreButton,Learn More,"
+-- CONTEXT --
+",
+RequestForm.licenseInfoHeader,About licenses,"
+-- CONTEXT --
+",
+RequestForm.licensingQuestionLabel,What is the licensing of the content you are uploading? (Check all that apply),"
+-- CONTEXT --
+",
+RequestForm.mediumNgoLabel,Medium-sized NGO with budget < $500k,"
+-- CONTEXT --
+",
+RequestForm.natureOfYourContentLabel,Nature of your content,"
+-- CONTEXT --
+",
+RequestForm.notAffiliatedLabel,I am not affiliated with an organization for this work,"
+-- CONTEXT --
+",
+RequestForm.numberOfResourcesPlaceholder,Number of resources,"
+-- CONTEXT --
+",
+RequestForm.oneWeekLabel,1 week,"
+-- CONTEXT --
+",
+RequestForm.organizationNamePlaceholder,Organization name,"
+-- CONTEXT --
+",
+RequestForm.organizationalAffiliationLabel,Organizational affiliation,"
+-- CONTEXT --
+",
+RequestForm.otherLabel,Other,"
+-- CONTEXT --
+",
+RequestForm.pasteLinkPlaceholder,Paste link here,"
+-- CONTEXT --
+",
+RequestForm.provideSampleLinkLabel,Please provide a link to a sample of your content (on Kolibri Studio or from source site),"
+-- CONTEXT --
+",
+RequestForm.requestFailed,Unable to send request. Please try again.,"
+-- CONTEXT --
+",
+RequestForm.requestSent,Your storage request has been submitted for processing.,"
+-- CONTEXT --
+",
+RequestForm.responsePlaceholder,Response,"
+-- CONTEXT --
+",
+RequestForm.selectAllThatApplyPlaceholder,Select all that apply,"
+-- CONTEXT --
+",
+RequestForm.sendRequestAction,Send request,"
+-- CONTEXT --
+",
+RequestForm.sixPlusMonthsLabel,6+ months,"
+-- CONTEXT --
+",
+RequestForm.sizePlaceholder,Size,"
+-- CONTEXT --
+",
+RequestForm.smallNgoLabel,Small NGO with annual budget < $25k,"
+-- CONTEXT --
+",
+RequestForm.storageAmountRequestedPlaceholder,Amount requested (e.g. 10GB),"
+-- CONTEXT --
+",
+RequestForm.targetRegionsLabel,Target region(s) for your content (if applicable),"
+-- CONTEXT --
+",
+RequestForm.threeToSixMonthsLabel,3-6 months,"
+-- CONTEXT --
+",
+RequestForm.timelineLabel,"To better understand the time sensitive nature of your request, please indicate an approximate timeline by when you need this additional storage:","
+-- CONTEXT --
+",
+RequestForm.twoToFourWeeksLabel,2-4 weeks,"
+-- CONTEXT --
+",
+RequestForm.typeOfContentPlaceholder,Types of resources,"
+-- CONTEXT --
+",
+RequestForm.typeOfOrganizationLabel,What type of organization or group is coordinating the use of Kolibri (if applicable)?,"
+-- CONTEXT --
+",
+RequestForm.unknownLabel,Unknown,"
+-- CONTEXT --
+",
+RequestForm.uploadingOnBehalfLabel,I am uploading content on behalf of:,"
+-- CONTEXT --
+",
+RequestForm.usageLabel,Tell us more about your use of Kolibri,"
+-- CONTEXT --
+",
+RequestForm.whoCanUseContentLabel,Who can use your content?,"
+-- CONTEXT --
+",
+RequestForm.willYouMakeYourChannelPublicLabel,"If the content is openly licensed, would you be willing to consider making your channels public to other Kolibri users if requested in the future?","
+-- CONTEXT --
+",
+RequestNewActivationLink.activationExpiredText,This activation link has been used already or has expired.,"
+-- CONTEXT --
+",
+RequestNewActivationLink.activationExpiredTitle,Activation failed,"
+-- CONTEXT --
+",
+RequestNewActivationLink.activationRequestFailed,Failed to send a new activation link. Please try again.,"
+-- CONTEXT --
+",
+RequestNewActivationLink.submitButton,Submit,"
+-- CONTEXT --
+",
+ResetLinkExpired.requestNewLink,Request a new password reset link,"
+-- CONTEXT --
+",
+ResetLinkExpired.resetExpiredText,This password reset link has been used already or has expired.,"
+-- CONTEXT --
+",
+ResetLinkExpired.resetExpiredTitle,Reset link expired,"
+-- CONTEXT --
+",
+ResetPassword.passwordConfirmLabel,Confirm password,"
+-- CONTEXT --
+",
+ResetPassword.passwordLabel,New password,"
+-- CONTEXT --
+",
+ResetPassword.passwordMatchMessage,Passwords don't match,"
+-- CONTEXT --
+",
+ResetPassword.resetPasswordFailed,Failed to reset password. Please try again.,"
+-- CONTEXT --
+",
+ResetPassword.resetPasswordPrompt,Enter and confirm your new password,"
+-- CONTEXT --
+",
+ResetPassword.resetPasswordTitle,Reset your password,"
+-- CONTEXT --
+",
+ResetPassword.submitButton,Submit,"
+-- CONTEXT --
+",
+ResetPasswordSuccess.header,Password reset successfully,"
+-- CONTEXT --
+",
+ResetPasswordSuccess.text,Your password has been reset. You may sign in now.,"
+-- CONTEXT --
+",
+ResourcePanel.aggregator,Aggregator,"
+-- CONTEXT --
+",
+ResourcePanel.audience,Audience,"
+-- CONTEXT --
+",
+ResourcePanel.author,Author,"
+-- CONTEXT --
+",
+ResourcePanel.availableFormats,Available formats,"
+-- CONTEXT --
+",
+ResourcePanel.coachResources,Resources for coaches,"
+-- CONTEXT --
+",
+ResourcePanel.copyrightHolder,Copyright holder,"
+-- CONTEXT --
+",
+ResourcePanel.description,Description,"
+-- CONTEXT --
+",
+ResourcePanel.details,Details,"
+-- CONTEXT --
+",
+ResourcePanel.fileSize,Size,"
+-- CONTEXT --
+",
+ResourcePanel.files,Files,"
+-- CONTEXT --
+",
+ResourcePanel.incompleteQuestionError,"{count, plural, one {# incomplete question} other {# incomplete questions}}","
+-- CONTEXT --
+",
+ResourcePanel.language,Language,"
+-- CONTEXT --
+",
+ResourcePanel.license,License,"
+-- CONTEXT --
+",
+ResourcePanel.nextSteps,Next steps,"
+-- CONTEXT --
+",
+ResourcePanel.noCompletionCriteriaError,Completion criteria are required,"
+-- CONTEXT --
+",
+ResourcePanel.noCopyrightHolderError,Copyright holder is required,"
+-- CONTEXT --
+",
+ResourcePanel.noDurationError,Duration is required,"
+-- CONTEXT --
+",
+ResourcePanel.noFilesError,File is required,"
+-- CONTEXT --
+",
+ResourcePanel.noLearningActivityError,Learning activity is required,"
+-- CONTEXT --
+",
+ResourcePanel.noLicenseDescriptionError,License description is required,"
+-- CONTEXT --
+",
+ResourcePanel.noLicenseError,License is required,"
+-- CONTEXT --
+",
+ResourcePanel.noMasteryModelError,Mastery criteria are required,"
+-- CONTEXT --
+",
+ResourcePanel.noQuestionsError,Exercise is empty,"
+-- CONTEXT --
+",
+ResourcePanel.originalChannel,Imported from,"
+-- CONTEXT --
+",
+ResourcePanel.previousSteps,Previous steps,"
+-- CONTEXT --
+",
+ResourcePanel.provider,Provider,"
+-- CONTEXT --
+",
+ResourcePanel.questionCount,"{value, number, integer} {value, plural, one {question} other {questions}}","
+-- CONTEXT --
+",
+ResourcePanel.questions,Questions,"
+-- CONTEXT --
+",
+ResourcePanel.relatedResources,Related resources,"
+-- CONTEXT --
+",
+ResourcePanel.resources,Resources,"
+-- CONTEXT --
+",
+ResourcePanel.showAnswers,Show answers,"
+-- CONTEXT --
+",
+ResourcePanel.source,Source,"
+-- CONTEXT --
+",
+ResourcePanel.subtitles,Captions and subtitles,"
+-- CONTEXT --
+",
+ResourcePanel.tags,Tags,"
+-- CONTEXT --
+",
+ResourcePanel.totalResources,Total resources,"
+-- CONTEXT --
+",
+ResourcePanel.visibleTo,Visible to,"
+-- CONTEXT --
+",
+ResourcesNeededOptions.furtherExplanation,Please add to the 'Description' field any additional supplies learners will need in order to use this resource,"
+-- CONTEXT --
+",
+ResourcesNeededOptions.resourcesNeededLabel,Requirements,"
+-- CONTEXT --
+",
+ReviewSelectionsPage.noResourcesSelected,No resources selected,"
+-- CONTEXT --
+",
+ReviewSelectionsPage.removeAction,Remove,"
+-- CONTEXT --
+",
+ReviewSelectionsPage.resourcesInTopic,"{count, number} {count, plural, one {resource} other {resources}}","
+-- CONTEXT --
+",
+ReviewSelectionsPage.reviewSelectionHeader,Review selections,"
+-- CONTEXT --
+",
+SavedSearchesModal.cancelAction,Cancel,"
+-- CONTEXT --
+",
+SavedSearchesModal.closeButtonLabel,Close,"
+-- CONTEXT --
+",
+SavedSearchesModal.deleteAction,Delete,"
+-- CONTEXT --
+",
+SavedSearchesModal.deleteConfirmation,Are you sure you want to delete this saved search?,"
+-- CONTEXT --
+",
+SavedSearchesModal.deleteSearchTitle,Delete saved search,"
+-- CONTEXT --
+",
+SavedSearchesModal.editAction,Edit,"
+-- CONTEXT --
+",
+SavedSearchesModal.filterCount,"{count, number} {count, plural, one {filter} other {filters}}","
+-- CONTEXT --
+",
+SavedSearchesModal.noSavedSearches,You do not have any saved searches,"
+-- CONTEXT --
+",
+SavedSearchesModal.savedSearchesTitle,Saved searches,"
+-- CONTEXT --
+",
+SavedSearchesModal.searchDeletedSnackbar,Saved search deleted,"
+-- CONTEXT --
+",
+SavingIndicator.lastSaved,Saved {saved},"
+-- CONTEXT --
+",
+SavingIndicator.savedNow,Saved just now,"
+-- CONTEXT --
+",
+SavingIndicator.savingIndicator,Saving...,"
+-- CONTEXT --
+",
+SearchFilterBar.assessments,Assessments,"
+-- CONTEXT --
+",
+SearchFilterBar.clearAll,Clear all,"
+-- CONTEXT --
+",
+SearchFilterBar.coachContent,Resources for coaches,"
+-- CONTEXT --
+",
+SearchFilterBar.createdAfter,Added after '{date}',"
+-- CONTEXT --
+",
+SearchFilterBar.topicsHidden,Folders excluded,"
+-- CONTEXT --
+",
+SearchFilters.addedAfterDateLabel,Added after,"
+-- CONTEXT --
+",
+SearchFilters.assessmentsLabel,Show assessments only,"
+-- CONTEXT --
+",
+SearchFilters.channelSourceLabel,Channel/source,"
+-- CONTEXT --
+",
+SearchFilters.channelTypeLabel,Channel type,"
+-- CONTEXT --
+",
+SearchFilters.channelsHeader,Channels,"
+-- CONTEXT --
+",
+SearchFilters.coachContentLabel,Show resources for coaches,"
+-- CONTEXT --
+",
+SearchFilters.filtersHeader,Filter options,"
+-- CONTEXT --
+",
+SearchFilters.hideTopicsLabel,Hide folders,"
+-- CONTEXT --
+",
+SearchFilters.kindLabel,Format,"
+-- CONTEXT --
+",
+SearchFilters.licensesLabel,License,"
+-- CONTEXT --
+",
+SearchOrBrowseWindow.backToBrowseAction,Back to browse,"
+-- CONTEXT --
+",
+SearchOrBrowseWindow.copiedToClipboard,Copied to clipboard,"
+-- CONTEXT --
+",
+SearchOrBrowseWindow.copyFailed,Failed to copy to clipboard,"
+-- CONTEXT --
+",
+SearchOrBrowseWindow.searchAction,Search,"
+-- CONTEXT --
+",
+SearchOrBrowseWindow.searchLabel,Search for resourcesā¦,"
+-- CONTEXT --
+",
+SearchResultsList.failedToLoad,Failed to load search results,"
+-- CONTEXT --
+",
+SearchResultsList.resultsPerPageLabel,Results per page,"
+-- CONTEXT --
+",
+SearchResultsList.saveSearchAction,Save search,"
+-- CONTEXT --
+",
+SearchResultsList.savedSearchesLabel,View saved searches,"
+-- CONTEXT --
+",
+SearchResultsList.searchResultsCount,"{count, number} {count, plural, one {result} other {results}} for '{searchTerm}'","
+-- CONTEXT --
+",
+SearchResultsList.searchSavedSnackbar,Search saved,"
+-- CONTEXT --
+",
+SettingsIndex.accountLabel,Account,"
+-- CONTEXT --
+",
+SettingsIndex.settingsTitle,Settings,"
+-- CONTEXT --
+",
+SettingsIndex.storageLabel,Storage,"
+-- CONTEXT --
+",
+SettingsIndex.usingStudioLabel,About Studio,"
+-- CONTEXT --
+",
+StagingTreePage.backToViewing,Back to viewing,"
+-- CONTEXT --
+",
+StagingTreePage.cancelDeployBtn,Cancel,"
+-- CONTEXT --
+",
+StagingTreePage.channelDeployed,Channel has been deployed,"
+-- CONTEXT --
+",
+StagingTreePage.closeSummaryDetailsDialogBtn,Close,"
+-- CONTEXT --
+",
+StagingTreePage.collapseAllButton,Collapse all,"
+-- CONTEXT --
+",
+StagingTreePage.confirmDeployBtn,Deploy channel,"
+-- CONTEXT --
+",
+StagingTreePage.deploy,Deploy,"
+-- CONTEXT --
+",
+StagingTreePage.deployChannel,Deploy channel,"
+-- CONTEXT --
+",
+StagingTreePage.deployDialogDescription,You are about to replace all live resources with staged resources.,"
+-- CONTEXT --
+",
+StagingTreePage.emptyChannelSubText,No changes to review! The channel contains all the most recent folders and resources.,"
+-- CONTEXT --
+",
+StagingTreePage.emptyChannelText,No resources found,"
+-- CONTEXT --
+",
+StagingTreePage.emptyTopicText,This topic is empty,"
+-- CONTEXT --
+",
+StagingTreePage.liveResources,Live resources,"
+-- CONTEXT --
+",
+StagingTreePage.openCurrentLocationButton,Expand to current folder location,"
+-- CONTEXT --
+",
+StagingTreePage.openSummaryDetailsDialogBtn,View summary,"
+-- CONTEXT --
+",
+StagingTreePage.resourcesCount,"{count, number} {count, plural, one { resource } other { resources }}","
+-- CONTEXT --
+",
+StagingTreePage.reviewMode,Review mode,"
+-- CONTEXT --
+",
+StagingTreePage.stagedResources,Staged resources,"
+-- CONTEXT --
+",
+StagingTreePage.summaryDetailsDialogTitle,Summary details,"
+-- CONTEXT --
+",
+StagingTreePage.topicsCount,"{count, number} {count, plural, one { folder } other { folders }}","
+-- CONTEXT --
+",
+StagingTreePage.totalResources,Total resources,"
+-- CONTEXT --
+",
+StagingTreePage.totalSize,Total size,"
+-- CONTEXT --
+",
+StagingTreePage.viewDetails,View details,"
+-- CONTEXT --
+",
+StatusStrings.noStorageError,Not enough space,"
+-- CONTEXT --
+",
+StatusStrings.uploadFailedError,Upload failed,"
+-- CONTEXT --
+",
+StatusStrings.uploadFileSize,{uploaded} of {total},"
+-- CONTEXT --
+",
+Storage.hideFormAction,Close form,"
+-- CONTEXT --
+",
+Storage.learnMoreAboutImportingContentFromChannels,Learn more about how to import resources from other channels,"
+-- CONTEXT --
+",
+Storage.requestMoreSpaceHeading,Request more space,"
+-- CONTEXT --
+",
+Storage.requestMoreSpaceMessage,Please use this form to request additional uploading storage for your Kolibri Studio account. The resources you import from our public library to your channels do not count towards your storage limit.,"
+-- CONTEXT --
+",
+Storage.showFormAction,Open form,"
+-- CONTEXT --
+",
+Storage.spaceUsedOfMax,{qty} of {max},"
+-- CONTEXT --
+",
+Storage.storagePercentageUsed,{qty}% storage used,"
+-- CONTEXT --
+",
+StudioTree.missingTitle,Missing title,"
+-- CONTEXT --
+",
+StudioTree.optionsTooltip,Options,"
+-- CONTEXT --
+",
+SubtitlesList.acceptedFormatsTooltip,Supported formats: {extensions},"
+-- CONTEXT --
+",
+SubtitlesList.addSubtitleText,Add captions,"
+-- CONTEXT --
+",
+SubtitlesList.subtitlesHeader,Captions and subtitles,"
+-- CONTEXT --
+",
+SupplementaryItem.languageText,{language} ({code}),"
+-- CONTEXT --
+",
+SupplementaryItem.retryUpload,Retry upload,"
+-- CONTEXT --
+",
+SupplementaryItem.uploadFailed,Upload failed,"
+-- CONTEXT --
+",
+SupplementaryList.selectFileText,Select file,"
+-- CONTEXT --
+",
+SyncResourcesModal.backButtonLabel,Back,"
+-- CONTEXT --
+",
+SyncResourcesModal.cancelButtonLabel,Cancel,"
+-- CONTEXT --
+",
+SyncResourcesModal.confirmSyncModalExplainer,You are about to sync and update the following:,"
+-- CONTEXT --
+",
+SyncResourcesModal.confirmSyncModalTitle,Confirm sync,"
+-- CONTEXT --
+",
+SyncResourcesModal.confirmSyncModalWarningExplainer,Warning: this will overwrite any changes you have made to copied or imported resources.,"
+-- CONTEXT --
+",
+SyncResourcesModal.continueButtonLabel,Continue,"
+-- CONTEXT --
+",
+SyncResourcesModal.syncButtonLabel,Sync,"
+-- CONTEXT --
+",
+SyncResourcesModal.syncExercisesExplainer,"Update questions, answers, and hints in exercises and quizzes","
+-- CONTEXT --
+",
+SyncResourcesModal.syncExercisesTitle,Assessment details,"
+-- CONTEXT --
+",
+SyncResourcesModal.syncFilesExplainer,"Update all files, including: thumbnails, subtitles, and captions","
+-- CONTEXT --
+",
+SyncResourcesModal.syncFilesTitle,Files,"
+-- CONTEXT --
+",
+SyncResourcesModal.syncModalExplainer,Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files.,"
+-- CONTEXT --
+",
+SyncResourcesModal.syncModalSelectAttributes,Select what you would like to sync:,"
+-- CONTEXT --
+",
+SyncResourcesModal.syncModalTitle,Sync resources,"
+-- CONTEXT --
+",
+SyncResourcesModal.syncResourceDetailsExplainer,"Update information about the resource: learning activity, level, requirements, category, tags, audience, and source","
+-- CONTEXT --
+",
+SyncResourcesModal.syncResourceDetailsTitle,Resource details,"
+-- CONTEXT --
+",
+SyncResourcesModal.syncTitlesAndDescriptionsExplainer,Update resource titles and descriptions,"
+-- CONTEXT --
+",
+SyncResourcesModal.syncTitlesAndDescriptionsTitle,Titles and descriptions,"
+-- CONTEXT --
+",
+TechnicalTextBlock.copiedToClipboardConfirmation,Copied to clipboard,"
+-- CONTEXT --
+",
+TechnicalTextBlock.copiedToClipboardFailure,Copy to clipboard failed,"
+-- CONTEXT --
+",
+TechnicalTextBlock.copyToClipboardButtonPrompt,Copy to clipboard,"
+-- CONTEXT --
+",
+Template.templateString,"You have {count, plural,
+ =1 {# node for testing}
+ other {# nodes for testing}}","
+-- CONTEXT --
+",
+TermsOfServiceModal.ToSHeader,Terms of Service,"
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseHeader,Acceptable Use Restrictions,"
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem1,Will be in strict accordance with these Terms;,"
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem10,"Will not interfere with, disrupt, or attack any service or network; and","
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem11,"Will not be used to create, distribute, or enable material that is - or that facilitates or operates in conjunction with - malware, spyware, adware, or other malicious programs or code.","
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem2,"Will comply with all applicable laws and regulations (including, without limitation, all applicable laws regarding online conduct and acceptable content, privacy, data protection, and the transmission of technical data exported from the United States or the country in which you reside);","
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem3,"Will not use the Services for any unlawful purposes, to publish illegal content, or in furtherance of illegal activities;","
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem4,"Will not transmit any material that is defamatory, offensive or otherwise objectionable in relation to your use of the Service;","
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem5,Will not infringe or misappropriate the intellectual property rights of any third party;,"
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem6,"Will not overburden Learning Equality's systems, as determined by us in our sole discretion, including but not limited to excessive bandwidth utilization or number of requests;","
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem7,Will not attempt to circumvent your assigned storage quota or other account restrictions through technical or other means;,"
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem8,Will not disclose sensitive personal information of others;,"
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseItem9,Will not be used to send spam or bulk unsolicited messages;,"
+-- CONTEXT --
+",
+TermsOfServiceModal.acceptableUseP1,You represent and warrant that your use of the Service:,"
+-- CONTEXT --
+",
+TermsOfServiceModal.accountTermsHeader,Account Terms,"
+-- CONTEXT --
+",
+TermsOfServiceModal.accountTermsP1,"When you register for an account on the Service, you agree to provide us with complete and accurate information. You will be solely responsible and liable for any activity that occurs under your username. You are responsible for keeping your account information up-to-date and for keeping your access credentials (password and API token) private and secure.","
+-- CONTEXT --
+",
+TermsOfServiceModal.accountTermsP2,"You are responsible for maintaining the security of your account and any Service-related content, and you are fully responsible for all activities that occur under your account and any other actions taken in connection with the Service. You shall not share or misuse your access credentials. You must immediately notify us of any unauthorized uses of your account, or of any other breach of security. We will not be liable for any acts or omissions by you, including any damages of any kind incurred as a result of such acts or omissions.","
+-- CONTEXT --
+",
+TermsOfServiceModal.accountTermsP3,"Access to and use of the Service is only for those over the age of 13 (or 16 in the European Union). If you are younger than this, you may not register for or use the Service. Any person who registers as a user or provides their personal information to the Service represents that they are 13 years of age or older (or 16 years or older in the European Union).","
+-- CONTEXT --
+",
+TermsOfServiceModal.arbitrationHeader,Arbitration Agreement,"
+-- CONTEXT --
+",
+TermsOfServiceModal.arbitrationP1,"Except for claims for injunctive or equitable relief or claims regarding intellectual property rights (which may be brought in any competent court without the posting of a bond), any dispute arising under the Agreement shall be finally settled in accordance with the Comprehensive Arbitration Rules of the Judicial Arbitration and Mediation Service, Inc. (""JAMS"") by three arbitrators appointed in accordance with such Rules. The arbitration shall take place in San Diego, California, in the English language and the arbitral decision may be enforced in any court. The prevailing party in any action or proceeding to enforce the Agreement shall be entitled to costs and attorneys' fees.","
+-- CONTEXT --
+",
+TermsOfServiceModal.cancellationHeader,Cancellation or Termination,"
+-- CONTEXT --
+",
+TermsOfServiceModal.cancellationItem1,"You must stop all activities authorized by these Terms, including your use of the Service.","
+-- CONTEXT --
+",
+TermsOfServiceModal.cancellationItem2,"You must not register and create a new account under your name, a fake or borrowed name, or the name of any third party, even if you may be acting on behalf of the third party.","
+-- CONTEXT --
+",
+TermsOfServiceModal.cancellationP1,"We may terminate or restrict your access to all or any part of the Service at any time, with or without cause, with or without notice, effective immediately. We have the right (though not the obligation) to, in our sole discretion, (i) close down an account or remove content due to prolonged inactivity, (ii) refuse or remove any content that, in our reasonable opinion, violates any Learning Equality policy (including our Community Standards) or is in any way harmful or objectionable, or (iii) terminate or deny access to and use of the Service to any individual or entity for any reason. We will have no obligation to provide a refund of any amounts previously paid.","
+-- CONTEXT --
+",
+TermsOfServiceModal.cancellationP2,If we end your rights to use the Service:,"
+-- CONTEXT --
+",
+TermsOfServiceModal.cancellationP3,"In addition to terminating or suspending your account, we reserve the right to take appropriate legal action, including without limitation pursuing civil, criminal, and injunctive action for violating these Terms.","
+-- CONTEXT --
+",
+TermsOfServiceModal.cancellationP4,"All provisions of the Agreement which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity, and limitations of liability.","
+-- CONTEXT --
+",
+TermsOfServiceModal.changesToToSHeader,Changes to these Terms of Service,"
+-- CONTEXT --
+",
+TermsOfServiceModal.changesToToSP1,"We are constantly updating our Service and that means sometimes we have to change the legal terms under which our Service is offered. These Terms may only be modified by a written amendment signed by an authorized executive of Learning Equality, or by the posting by Learning Equality of a revised version. If we make changes that are material, we will let you know by posting on one of our blogs, or by sending you an email or other communication before the changes take effect. The notice will designate a reasonable period of time after which the new terms will take effect. If you disagree with our changes, then you should stop using the Service within the designated notice period, or once the changes become effective. Your continued use of the Service will be subject to the new terms. However, any dispute that arose before the changes shall be governed by the Terms (including the binding individual arbitration clause) that were in place when the dispute arose.","
+-- CONTEXT --
+",
+TermsOfServiceModal.communicationsHeader,Communications with Learning Equality,"
+-- CONTEXT --
+",
+TermsOfServiceModal.communicationsP1,"For contractual purposes, you (1) consent to receive communications from us in an electronic form via the email address you have submitted or via the Service; and (2) agree that all Terms of Service, agreements, notices, disclosures, and other communications that we provide to you electronically satisfy any legal requirement that those communications would satisfy if they were on paper. This section does not affect your non-waivable rights.","
+-- CONTEXT --
+",
+TermsOfServiceModal.communityStandardsHeader,Community Standards,"
+-- CONTEXT --
+",
+TermsOfServiceModal.communityStandardsLink,Learn more about Studio's community standards,"
+-- CONTEXT --
+",
+TermsOfServiceModal.communityStandardsP1,"For more information about the intended use of the Service, and standards around Content, please see our Community Standards page.","
+-- CONTEXT --
+",
+TermsOfServiceModal.definitionsHeader,Definitions,"
+-- CONTEXT --
+",
+TermsOfServiceModal.definitionsP1,"These are the Terms for the web application hosted at https://studio.learningequality.org/, along with any API's or other interfaces it provides (the ""Service""), controlled and operated by Learning Equality (""Learning Equality"", ""we"", ""us"" and ""our""). We are registered as a nonprofit organization in California, USA under EIN 46-2676188, and have our registered office at 9700 Gilman Dr, PMB 323, La Jolla, CA 92093.","
+-- CONTEXT --
+",
+TermsOfServiceModal.definitionsP2,"These Terms describe our commitments to you, and your rights and responsibilities when using the Service. If you breach any of these Terms, your right to access and use of the Service and Service will be terminated. Please read them carefully and reach out to us if you have any questions.","
+-- CONTEXT --
+",
+TermsOfServiceModal.definitionsP3,"""Content"" refers to media files (such as videos, audio files, HTML5 content, or other materials) that are hosted on the Service, along with their associated descriptive metadata.","
+-- CONTEXT --
+",
+TermsOfServiceModal.definitionsP4,"Throughout these Terms, ""you"" applies to both individuals and entities that access or use the Service. If you are an individual using the Service on behalf of an entity, you represent and warrant that you have the authority to bind that entity to the Agreement and that by using our Service, you are accepting the Agreement on behalf of that entity.","
+-- CONTEXT --
+",
+TermsOfServiceModal.disclaimerP1,"Before using this website, you should read the following important information relating to it. These Terms of Service (""Terms"") govern your use of this website and form a legally binding agreement between you and us regarding your use of our website.","
+-- CONTEXT --
+",
+TermsOfServiceModal.disclaimerP2,"If, for any reason, you are unable or unwilling to agree to all of these Terms, please immediately discontinue using or attempting to use the service.","
+-- CONTEXT --
+",
+TermsOfServiceModal.disclaimerP3,By continuing to use the Service you agree to these terms which will bind you.,"
+-- CONTEXT --
+",
+TermsOfServiceModal.dmcaHeader,DMCA Policy,"
+-- CONTEXT --
+",
+TermsOfServiceModal.dmcaLink,Report a violation,"
+-- CONTEXT --
+",
+TermsOfServiceModal.dmcaP1,"As we ask others to respect our intellectual property rights, we respect the intellectual property rights of others. If you believe that material located on or associated with the Service violates your copyright, please notify us in accordance with our Digital Millennium Copyright Act (""DMCA"") Policy. We will respond to all such notices, including as required or appropriate by removing the infringing material or disabling all links to the infringing material. We will terminate a visitor's access to and use of the website if, under appropriate circumstances, the visitor is determined to be a repeat infringer of copyrights or other intellectual property rights. In the case of such termination, we will have no obligation to provide a refund of any payments or other forms of restitution.","
+-- CONTEXT --
+",
+TermsOfServiceModal.indemnificationHeader,Indemnification,"
+-- CONTEXT --
+",
+TermsOfServiceModal.indemnificationP1,"You agree to indemnify and hold harmless Learning Equality, its contractors, and its licensors, and their respective directors, officers, employees, and agents from and against any and all losses, liabilities, demands, damages, costs, claims, and expenses, including attorneys' fees, arising out of or related to your use of the Service, including but not limited to your violation of the Agreement, Content that you upload or author, and any other activities conducted using the Service.","
+-- CONTEXT --
+",
+TermsOfServiceModal.intellectualPropertyHeader,Intellectual Property Notice,"
+-- CONTEXT --
+",
+TermsOfServiceModal.intellectualPropertyP1,"The Agreement does not transfer from Learning Equality to you any Learning Equality or third party intellectual property, and all right, title, and interest in and to such property will remain (as between the parties) solely with Learning Equality. ""Kolibri"", ""Kolibri Studio"", ""Learning Equality"", the Kolibri logo, and all other trademarks, service marks, graphics, and logos used in connection with learningequality.org or the Service, are trademarks or registered trademarks of Learning Equality or Learning Equality's licensors. Other trademarks, service marks, graphics, and logos used in connection with the Service may be the trademarks of other third parties. Your use of the Service grants you no right or license to reproduce or otherwise use any Learning Equality or third party trademarks and any such use may constitute an infringement of the holder's rights","
+-- CONTEXT --
+",
+TermsOfServiceModal.jurisdictionHeader,Jurisdiction and Applicable Law,"
+-- CONTEXT --
+",
+TermsOfServiceModal.jurisdictionP1,"Except to the extent any applicable law provides otherwise, the Agreement and any access to or use of the Service will be governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions. The proper venue for any disputes arising out of or relating to the Agreement and any access to or use of the Service will be the state and federal courts located in San Diego County, California.","
+-- CONTEXT --
+",
+TermsOfServiceModal.liabilityHeader,Limitation of Liability,"
+-- CONTEXT --
+",
+TermsOfServiceModal.liabilityP1,"To the extent legally permitted under the applicable law, Learning Equality shall not be responsible for any loss or damage to you, your customers or third parties caused by failure of the website to function. In no event will Learning Equality be liable for any special, consequential, incidental, or indirect damages (including, without limitation, those resulting from lost profits, cost of substitute goods or services, lost data or business interruption) in connection with the use of the website or Service of in connection with any other claim arising from these Terms of Service. The aggregate liability of Learning Equality arising from or relating to these Terms and the Service, regardless of the form of action or claim (contract, tort or otherwise) and even if you have been advised of the possibility of such damages shall not exceed the amount paid by you during the twelve (12) month period prior to the cause of action. Nothing in these Terms shall limit or exclude Learning Equality liability for gross negligence or for death or personal injury. Applicable law may not allow the exclusion or limitation of incidental or consequential damages, so the above limitation or exclusion may not apply to you.","
+-- CONTEXT --
+",
+TermsOfServiceModal.licensingHeader,Licensing and Copyright,"
+-- CONTEXT --
+",
+TermsOfServiceModal.licensingList1Item1,"Copyright ownership of the Content is retained by the original copyright holder and must be indicated, and license information must be marked so as to accurately reflect the copyright holder's intentions around the distribution and use of that Content.","
+-- CONTEXT --
+",
+TermsOfServiceModal.licensingList1Item2,"If you are not yourself the copyright holder, you must have the rights to distribute the uploaded Content, either through explicit written permission from the copyright holder, or as allowed by the terms of the license under which the Content has been released.","
+-- CONTEXT --
+",
+TermsOfServiceModal.licensingList1Item3,"If you are the copyright holder of the uploaded content, then by marking the Content you upload with a particular license, you are agreeing for the Content to be distributed and used under the terms of that license in perpetuity.","
+-- CONTEXT --
+",
+TermsOfServiceModal.licensingList2Item1,"Descriptive metadata: This includes primary metadata associated with a single piece of Content, for example, titles, descriptions, and other elements which constitute a definitive part of the Content regardless of which system it appears on. These metadata elements will fall under the same copyright and licensing as the Content itself.","
+-- CONTEXT --
+",
+TermsOfServiceModal.licensingList2Item2,"Organizational metadata: This defines how a piece of content may be used, aids with discovery, and places it within some broader structure of relations on the Service, for example, tags, curation into folders (including the titles of those folders), and other elements pertaining to the display and ordering of Content on the system itself. By using the Service, you agree that work you do to generate organizational metadata elements are released into the Public Domain, and may be made available for others to use, without any claim to copyright or restricted licensing. We may also share, leverage and distribute this organizational metadata. This is so that we can benefit others and improve the impact of our platforms.","
+-- CONTEXT --
+",
+TermsOfServiceModal.licensingP1,"The Service allows you to upload and distribute Content. When you do, the following terms apply:","
+-- CONTEXT --
+",
+TermsOfServiceModal.licensingP2,"We follow a policy of making content, including its associated metadata, as open as possible while following the appropriate copyright laws. With this in mind, we distinguish between:","
+-- CONTEXT --
+",
+TermsOfServiceModal.miscellaneousHeader,Miscellaneous,"
+-- CONTEXT --
+",
+TermsOfServiceModal.miscellaneousP1,"The Agreement constitutes the entire agreement between Learning Equality and you concerning the subject matter hereof. If any part of the Agreement is held invalid or unenforceable, that part will be construed to reflect the parties' original intent, and the remaining portions will remain in full force and effect. A waiver by either party of any term or condition of the Agreement or any breach thereof, in any one instance, will not waive such term or condition or any subsequent breach thereof.","
+-- CONTEXT --
+",
+TermsOfServiceModal.miscellaneousP2,"You may assign your rights under the Agreement to any party that consents to, and agrees to be bound by, its terms and conditions; Learning Equality may assign its rights under the Agreement without condition. The Agreement will be binding upon and will inure to the benefit of the parties, their successors and permitted assigns.","
+-- CONTEXT --
+",
+TermsOfServiceModal.miscellaneousP3,"If you have any questions about the Service or these Terms, please contact us at legal@learningequality.org.","
+-- CONTEXT --
+",
+TermsOfServiceModal.prompt,Please read these terms and conditions carefully,"
+-- CONTEXT --
+",
+TermsOfServiceModal.thirdPartyHeader,Third Party Content and Third Party Applications,"
+-- CONTEXT --
+",
+TermsOfServiceModal.thirdPartyP1,"The links to third party websites, any third party content, and any third party applications may be provided for your convenience and information only. The content on any linked website or in any third party application is not under our control and we are not responsible for the content of linked websites and/or third party applications, including any further links contained in a third party website. We make no representations or warranties in connection with any third party content or third party applications, which at all times and in each instance is provided ""as is."" Third party applications may be subject to additional policies and conditions or agreements between you and the provider of such third party applications. You agree to fully comply with all such additional policies, conditions and agreements. If you decide to access any third party content, and/or any third party application, you do so entirely at your own risk.","
+-- CONTEXT --
+",
+TermsOfServiceModal.thirdPartyRightsHeader,Third Party Rights,"
+-- CONTEXT --
+",
+TermsOfServiceModal.thirdPartyRightsP1,Nothing in our Terms is intended to confer on any third party any benefit or any right (under the Contracts (Rights of Third Parties) Act 1999 UK or otherwise) to enforce any provision of our Terms or any agreement entered into in connection with it.,"
+-- CONTEXT --
+",
+TermsOfServiceModal.updatedToSHeader,Updated terms of service,"
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentHeader,User-Generated Content,"
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList1Item1,"We do not endorse any uploaded Content or represent that Content is accurate, useful, or non-harmful. Content could be offensive, indecent, or objectionable; include technical inaccuracies, typographical mistakes, or other errors; or violate or infringe the privacy, publicity rights, intellectual property rights (see our Copyright Infringement and DMCA Policy section to submit copyright complaints), or other proprietary rights of third parties.","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList1Item2,"If you upload or author Content, or otherwise make (or allow any third party to make) Content available on the Service, you are entirely responsible for the Content, and any harm resulting from, that Content or your conduct.","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList1Item3,You are responsible for ensuring that you have proper permissions to upload and distribute any and all uploaded Content and for ensuring that the copyright holder and licensing are properly evidenced on the uploaded Content.,"
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList1Item4,"We disclaim any responsibility for any harm resulting from anyone's use or downloading of Content. If you access or use any Content, you are responsible for taking precautions as necessary to protect yourself and your computer systems from viruses, worms, Trojan horses, and other harmful or destructive content.","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList1Item5,"We are not a party to, and will have no responsibility or liability for, any communications, transactions, interactions, or disputes between you and the provider of any Content.","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList1Item6,"Please note that additional third party terms and conditions may apply to the downloading, copying, or use of Content.","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList2Item1,We do not have any control over those websites and are not responsible for their contents or their use.,"
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList2Item2,The existence of a link to or from the Service does not represent or imply that we endorse such website.,"
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList2Item3,"You are responsible for taking precautions as necessary to protect yourself and your computer systems from viruses, worms, Trojan horses, and other harmful or destructive content.","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList3Item1,"remove or force upgrades of copies of Content that have already been downloaded from the Service, except in cases in which the Kolibri Learning Application is running on a server that is under our control. This may mean that when we delete uploaded content not all copies will be removed.","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentList3Item2,"remove or change the licensing on old versions of Content that others have made copies of, should you change the licensing on your content and/or request a removal of the Content from us. When a Creative Commons license is applied to a specific version of a piece of Content, the rights conferred to others for distribution and use of that Content cannot be revoked. Whilst we cannot remove or force updates on copies of the Content, we would let you update the license on your own copy of the Content moving forward, and for future versions.","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentP1,"We have not reviewed, and cannot review, all of the Content (such as, but not limited to, text, photo, video, audio, code, computer software, or other materials) uploaded to or authored using the Service by users or anyone else and are not responsible for any use or effects of such Content. So, for example:","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentP2,"We also have not reviewed, and cannot review, all of the material made available through the websites and web pages that link to, or are linked from the Service. For example:","
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentP3,We reserve the right to remove any Content that violates our Terms or for any other reason.,"
+-- CONTEXT --
+",
+TermsOfServiceModal.userContentP4,Please note we cannot:,"
+-- CONTEXT --
+",
+TermsOfServiceModal.warrantyHeader,Disclaimer of Warranties,"
+-- CONTEXT --
+",
+TermsOfServiceModal.warrantyHeaderP1,"You acknowledge that the website and the Service is provided ""as is"" and ""as available"", with all faults and without warranty of any kind, and we hereby disclaim all warranties and conditions with respect to the website and Service, either express, implied or statutory, including, but not limited to, any implied warranties and/or conditions of merchantability, of satisfactory quality, of fitness for a particular purpose, of accuracy, and non-infringement of third party rights. Any use of the Service and website is at your own risk. Some jurisdictions do not allow the exclusion of implied warranties, so the above limitations may not apply to you.","
+-- CONTEXT --
+",
+TermsOfServiceModal.yourPrivacyHeader,Your Privacy,"
+-- CONTEXT --
+",
+TermsOfServiceModal.yourPrivacyLink,Learn more about Studio's privacy policy,"
+-- CONTEXT --
+",
+TermsOfServiceModal.yourPrivacyP1,"We take your privacy seriously. Please read our Privacy Policy to see how we collect, use and protect your personal data.","
+-- CONTEXT --
+",
+TextArea.fieldRequiredMessage,Field is required,"
+-- CONTEXT --
+",
+TextField.fieldRequiredMessage,Field is required,"
+-- CONTEXT --
+",
+Thumbnail.thumbnail,{title} thumbnail,"
+-- CONTEXT --
+",
+ThumbnailGenerator.generatedDefaultFilename,Generated thumbnail,"
+-- CONTEXT --
+",
+ThumbnailGenerator.thumbnailGenerationFailedHeader,Unable to generate thumbnail,"
+-- CONTEXT --
+",
+ThumbnailGenerator.thumbnailGenerationFailedText,There was a problem generating a thumbnail,"
+-- CONTEXT --
+",
+TitleStrings.catalogTitle,Kolibri Content Library Catalog,"
+-- CONTEXT --
+",
+TitleStrings.defaultTitle,Kolibri Studio,"
+-- CONTEXT --
+",
+TitleStrings.tabTitle,{title} - {site},"
+-- CONTEXT --
+",
+ToggleText.less,Show less,"
+-- CONTEXT --
+",
+ToggleText.more,Show more,"
+-- CONTEXT --
+",
+TrashModal.deleteButton,Delete,"
+-- CONTEXT --
+",
+TrashModal.deleteConfirmationCancelButton,Cancel,"
+-- CONTEXT --
+",
+TrashModal.deleteConfirmationDeleteButton,Delete permanently,"
+-- CONTEXT --
+",
+TrashModal.deleteConfirmationHeader,"Permanently delete {topicCount, plural,
+ =1 {# folder}
+ other {# folders}}, {resourceCount, plural,
+ =1 {# resource}
+ other {# resources}}?","
+-- CONTEXT --
+",
+TrashModal.deleteConfirmationText,You cannot undo this action. Are you sure you want to continue?,"
+-- CONTEXT --
+",
+TrashModal.deleteSuccessMessage,Permanently deleted,"
+-- CONTEXT --
+",
+TrashModal.deletedHeader,Removed,"
+-- CONTEXT --
+",
+TrashModal.restoreButton,Restore,"
+-- CONTEXT --
+",
+TrashModal.selectAllHeader,Select all,"
+-- CONTEXT --
+",
+TrashModal.selectedCountText,"{topicCount, plural,
+ =1 {# folder}
+ other {# folders}}, {resourceCount, plural,
+ =1 {# resource}
+ other {# resources}}","
+-- CONTEXT --
+",
+TrashModal.trashEmptySubtext,Resources removed from this channel will appear here,"
+-- CONTEXT --
+",
+TrashModal.trashEmptyText,Trash is empty,"
+-- CONTEXT --
+",
+TrashModal.trashModalTitle,Trash,"
+-- CONTEXT --
+",
+TreeView.closeDrawer,Close,"
+-- CONTEXT --
+",
+TreeView.collapseAllButton,Collapse all,"
+-- CONTEXT --
+",
+TreeView.openCurrentLocationButton,Expand to current folder location,"
+-- CONTEXT --
+",
+TreeView.showSidebar,Show sidebar,"
+-- CONTEXT --
+",
+TreeView.updatedResourcesReadyForReview,Updated resources are ready for review,"
+-- CONTEXT --
+",
+TreeViewBase.apiGenerated,Generated by API,"
+-- CONTEXT --
+",
+TreeViewBase.cancel,Cancel,"
+-- CONTEXT --
+",
+TreeViewBase.channelDeletedSnackbar,Channel deleted,"
+-- CONTEXT --
+",
+TreeViewBase.channelDetails,View channel details,"
+-- CONTEXT --
+",
+TreeViewBase.deleteChannel,Delete channel,"
+-- CONTEXT --
+",
+TreeViewBase.deleteChannelButton,Delete channel,"
+-- CONTEXT --
+",
+TreeViewBase.deletePrompt,This channel will be permanently deleted. This cannot be undone.,"
+-- CONTEXT --
+",
+TreeViewBase.deleteTitle,Delete this channel,"
+-- CONTEXT --
+",
+TreeViewBase.editChannel,Edit channel details,"
+-- CONTEXT --
+",
+TreeViewBase.emptyChannelTooltip,You cannot publish an empty channel,"
+-- CONTEXT --
+",
+TreeViewBase.getToken,Get token,"
+-- CONTEXT --
+",
+TreeViewBase.incompleteDescendantsText,"{count, number, integer} {count, plural, one {resource is incomplete and cannot be published} other {resources are incomplete and cannot be published}}","
+-- CONTEXT --
+",
+TreeViewBase.noChangesText,No changes found in channel,"
+-- CONTEXT --
+",
+TreeViewBase.noLanguageSetError,Channel language is required,"
+-- CONTEXT --
+",
+TreeViewBase.openTrash,Open trash,"
+-- CONTEXT --
+",
+TreeViewBase.publishButton,Publish,"
+-- CONTEXT --
+",
+TreeViewBase.publishButtonTitle,Make this channel available for import into Kolibri,"
+-- CONTEXT --
+",
+TreeViewBase.shareChannel,Share channel,"
+-- CONTEXT --
+",
+TreeViewBase.syncChannel,Sync resources,"
+-- CONTEXT --
+",
+TreeViewBase.viewOnly,View-only,"
+-- CONTEXT --
+",
+Uploader.listDelimiter,", ","
+-- CONTEXT --
+",
+Uploader.maxFileSizeText,"{count, plural,
+ =1 {# file will not be uploaded.}
+ other {# files will not be uploaded.}} File size must be under {size}","
+-- CONTEXT --
+",
+Uploader.noStorageHeader,Not enough space,"
+-- CONTEXT --
+",
+Uploader.remainingStorage,Remaining storage: {size},"
+-- CONTEXT --
+",
+Uploader.tooLargeFilesHeader,Max file size exceeded,"
+-- CONTEXT --
+",
+Uploader.unsupportedFilesHeader,Unsupported files,"
+-- CONTEXT --
+",
+Uploader.unsupportedFilesText,"{count, plural,
+ =1 {# file will not be uploaded.}
+ other {# files will not be uploaded.}}
+ {extensionCount, plural,
+ =1 {Supported file type is}
+ other {Supported file types are}} {extensions}","
+-- CONTEXT --
+",
+Uploader.uploadSize,Upload is too large: {size},"
+-- CONTEXT --
+",
+UsingStudio.aboutStudio,About Kolibri Studio,"
+-- CONTEXT --
+",
+UsingStudio.aboutStudioText,"Kolibri Studio is undergoing active development, and as such, some changes could cause unexpected behavior or challenges (also known as ""issues""). If you encounter an issue, please notify us as soon as they occur to help us resolve them. (See below for instructions on how to report issues.)","
+-- CONTEXT --
+",
+UsingStudio.bestPractice1,"When using import and clipboard operations, work with small subsets of folders instead of whole channels at once (especially for large channels).","
+-- CONTEXT --
+",
+UsingStudio.bestPractice2,It is preferable to create multiple small channels rather than one giant channel with many layers of folders.,"
+-- CONTEXT --
+",
+UsingStudio.bestPractice3,Reload the page to confirm your work has been saved to the server. Use CTRL+R on Linux/Windows or ā+R on Mac.,"
+-- CONTEXT --
+",
+UsingStudio.bestPractice5,"It is possible that you will encounter timeout errors in your browser when performing operations like import and sync, on large channels. Don't be alarmed by this error message and do not repeat the same operation again right away. It doesn't mean the operation has failed- Kolibri Studio is still working in the background. Wait a few minutes and reload the page before continuing your edits.","
+-- CONTEXT --
+",
+UsingStudio.bestPractice6,Compress videos before uploading them (see these instructions).,"
+-- CONTEXT --
+",
+UsingStudio.bestPractice7,PUBLISH periodically and import your channel into Kolibri to preview the content and obtain a local backup copy of your channel.,"
+-- CONTEXT --
+",
+UsingStudio.bestPractice9,Report issues as you encounter them.,"
+-- CONTEXT --
+",
+UsingStudio.bestPractices,Best practices,"
+-- CONTEXT --
+",
+UsingStudio.communityStandardsLink,Community standards,"
+-- CONTEXT --
+",
+UsingStudio.issue1,"There have been reports where users have observed the disappearance of changes they've recently made to their channels. The issue seems related to opening multiple tabs of Kolibri Studio, and eventually signing out. We advise that you disable any āMemory Saver/Sleepingā tab browser feature for Kolibri Studio, and reload each tab before signing out. We're actively investigating this issue, so if you run into it, please contact us with as much information as possible.","
+-- CONTEXT --
+A user facing description of an issue that has been reported by users.",
+UsingStudio.issueLink1,Reports of disappearing content,"
+-- CONTEXT --
+",
+UsingStudio.issuesPageLink,View all issues,"
+-- CONTEXT --
+",
+UsingStudio.notableIssues,Notable issues,"
+-- CONTEXT --
+",
+UsingStudio.policiesLink,Privacy policy,"
+-- CONTEXT --
+",
+UsingStudio.resourcesHeader,Kolibri Studio resources,"
+-- CONTEXT --
+",
+UsingStudio.termsOfServiceLink,Terms of service,"
+-- CONTEXT --
+",
+UsingStudio.userDocsLink,User guide,"
+-- CONTEXT --
+",
+VisibilityDropdown.coach,"Resources are visible only to coaches (teachers, facilitators, administrators)","
+-- CONTEXT --
+",
+VisibilityDropdown.labelText,Visible to,"
+-- CONTEXT --
+",
+VisibilityDropdown.learner,Resources are visible to anyone,"
+-- CONTEXT --
+",
+VisibilityDropdown.visibilityDescription,Visibility determines what type of Kolibri users can see resources.,"
+-- CONTEXT --
+",
+VisibilityDropdown.visibilityHeader,About resource visibility,"
+-- CONTEXT --
+",
+VisibilityDropdown.visibilityRequired,Field is required,"
+-- CONTEXT --
+",
+channelEditVue.errorChooseAtLeastOneCorrectAnswer,Choose at least one correct answer,"
+-- CONTEXT --
+",
+channelEditVue.errorMissingAnswer,Choose a correct answer,"
+-- CONTEXT --
+",
+channelEditVue.errorProvideAtLeastOneCorrectAnswer,Provide at least one correct answer,"
+-- CONTEXT --
+",
+channelEditVue.errorQuestionRequired,Question is required,"
+-- CONTEXT --
+",
+channelEditVue.false,False,"
+-- CONTEXT --
+",
+channelEditVue.questionTypeInput,Numeric input,"
+-- CONTEXT --
+",
+channelEditVue.questionTypeMultipleSelection,Multiple choice,"
+-- CONTEXT --
+",
+channelEditVue.questionTypePerseus,Perseus,"
+-- CONTEXT --
+",
+channelEditVue.questionTypeSingleSelection,Single choice,"
+-- CONTEXT --
+",
+channelEditVue.questionTypeTrueFalse,True/False,"
+-- CONTEXT --
+",
+channelEditVue.true,True,"
+-- CONTEXT --
+",
+formStrings.errorText,"Please fix {count, plural,
+ =1 {# error}
+ other {# errors}} below","
+-- CONTEXT --
+",
+sharedVue.activityDurationGteOne,Value must be equal to or greater than 1,"
+-- CONTEXT --
+",
+sharedVue.activityDurationRequired,This field is required,"
+-- CONTEXT --
+",
+sharedVue.activityDurationTooLongWarning,"This value is very high. Please make sure this is how long learners should work on the resource for, in order to complete it.","
+-- CONTEXT --
+",
+sharedVue.confirmLogout,Changes you made may not be saved. Are you sure you want to leave this page?,"
+-- CONTEXT --
+",
+sharedVue.copyrightHolderRequired,Copyright holder is required,"
+-- CONTEXT --
+",
+sharedVue.durationRequired,Duration is required,"
+-- CONTEXT --
+",
+sharedVue.fieldRequired,This field is required,"
+-- CONTEXT --
+",
+sharedVue.learningActivityRequired,Learning activity is required,"
+-- CONTEXT --
+",
+sharedVue.licenseDescriptionRequired,Special permissions license must have a description,"
+-- CONTEXT --
+",
+sharedVue.licenseRequired,License is required,"
+-- CONTEXT --
+",
+sharedVue.longActivityGtThirty,Value must be greater than 30,"
+-- CONTEXT --
+",
+sharedVue.longActivityLteOneTwenty,Value must be equal or less than 120,"
+-- CONTEXT --
+",
+sharedVue.masteryModelMGtZero,Must be at least 1,"
+-- CONTEXT --
+",
+sharedVue.masteryModelMLteN,Must be lesser than or equal to N,"
+-- CONTEXT --
+",
+sharedVue.masteryModelMRequired,Required,"
+-- CONTEXT --
+",
+sharedVue.masteryModelMWholeNumber,Must be a whole number,"
+-- CONTEXT --
+",
+sharedVue.masteryModelNGtZero,Must be at least 1,"
+-- CONTEXT --
+",
+sharedVue.masteryModelNRequired,Required,"
+-- CONTEXT --
+",
+sharedVue.masteryModelNWholeNumber,Must be a whole number,"
+-- CONTEXT --
+",
+sharedVue.masteryModelRequired,Mastery is required,"
+-- CONTEXT --
+",
+sharedVue.shortActivityLteThirty,Value must be equal or less than 30,"
+-- CONTEXT --
+",
+sharedVue.titleRequired,Title is required,"
+-- CONTEXT --
+",
diff --git a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json
new file mode 100644
index 0000000000..59fe75ac7a
--- /dev/null
+++ b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json
@@ -0,0 +1,1508 @@
+{
+ "AccessibilityOptions.altText": "Visual elements in the resource have descriptions that can be accessed by screen readers for the benefit of blind learners",
+ "AccessibilityOptions.audioDescription": "The resource contains a second narration audio track that provides additional information for the benefit of blind users and those with low vision",
+ "AccessibilityOptions.highContrast": "The resource text and visual elements are displayed with high contrast for the benefit of users with low vision",
+ "AccessibilityOptions.signLanguage": "Synchronized sign language intepretation is available for audio and video content",
+ "AccessibilityOptions.taggedPdf": "The document contains PDF tags that can be accessed by screen readers for the benefit of blind learners",
+ "Account.apiDocumentation": "API documentation",
+ "Account.apiTokenHeading": "API Token",
+ "Account.apiTokenMessage": "You will need this access token to run content integration scripts for bulk-uploading materials through the Kolibri Studio API.",
+ "Account.basicInfoHeader": "Basic Information",
+ "Account.changePasswordAction": "Change password",
+ "Account.completelyDeleteAccountLabel": "Completely remove your account from Kolibri Studio",
+ "Account.deleteAccountLabel": "Delete account",
+ "Account.editFullNameAction": "Edit full name",
+ "Account.exportAccountDataHeading": "Export account data",
+ "Account.exportAccountDataLabel": "You will receive an email with all information linked to your account",
+ "Account.exportAccountDataModalMessage": "You'll receive an email with your data when the export is completed",
+ "Account.exportDataButton": "Export data",
+ "Account.exportFailed": "Unable to export data. Please try again.",
+ "Account.exportStartedHeader": "Data export started",
+ "Account.fullNameLabel": "Full name",
+ "Account.handleChannelsBeforeAccount": "You must delete these channels manually or invite others to edit them before you can delete your account.",
+ "Account.passwordLabel": "Password",
+ "Account.unableToDeleteAdminAccount": "Unable to delete an admin account",
+ "Account.usernameLabel": "Username",
+ "AccountCreated.accountCreatedTitle": "Account successfully created",
+ "AccountCreated.backToLogin": "Continue to sign-in page",
+ "AccountDeleted.accountDeletedTitle": "Account successfully deleted",
+ "AccountDeleted.backToLogin": "Continue to sign-in page",
+ "AccountNotActivated.requestNewLink": "Request a new activation link",
+ "AccountNotActivated.text": "Please check your email for an activation link or request a new link.",
+ "AccountNotActivated.title": "Account has not been activated",
+ "ActivationExpired.activationExpiredText": "This activation link has been used already or has expired.",
+ "ActivationExpired.activationExpiredTitle": "Activation failed",
+ "ActivationExpired.requestNewLink": "Request a new activation link",
+ "ActivationLinkReSent.activationReSentText": "If there is already an account with the email address provided, you should receive instructions shortly. If you don't see an email from us, please check your spam folder.",
+ "ActivationLinkReSent.activationReSentTitle": "Instructions sent. Thank you!",
+ "ActivationSent.header": "Activation link sent",
+ "ActivationSent.text": "Thank you for creating an account! To complete the process, please check your email for the activation link we sent you.",
+ "ActivityDuration.minutesRequired": "Minutes",
+ "ActivityDuration.notOptionalLabel": "Time required for the resource to be marked as completed. This value will not be displayed to learners.",
+ "ActivityDuration.optionalLabel": "(Optional) Time required for the resource to be marked as completed. This value will not be displayed to learners.",
+ "AddNextStepsPage.addedNextStepSnackbar": "Added next step",
+ "AddNextStepsPage.toolbarTitle": "Add next step",
+ "AddPreviousStepsPage.addedPreviousStepSnackbar": "Added previous step",
+ "AddPreviousStepsPage.toolbarTitle": "Add previous step",
+ "AddRelatedResourcesModal.addStepBtnLabel": "Add",
+ "AddRelatedResourcesModal.cancelBtnLabel": "Cancel",
+ "AddRelatedResourcesModal.previewStepBtnLabel": "Preview",
+ "AddRelatedResourcesModal.resourcesDisplayedText": "Only showing available resources for",
+ "AddRelatedResourcesModal.selectedAsCurrentResource": "This is the current resource",
+ "AddRelatedResourcesModal.selectedAsNextStep": "Already selected as a next step",
+ "AddRelatedResourcesModal.selectedAsPreviousStep": "Already selected as a previous step",
+ "AdministrationAppError.unauthorizedDetails": "You need to be an administrator of Studio to view this page",
+ "AdministrationIndex.channelsLabel": "Channels",
+ "AdministrationIndex.usersLabel": "Users",
+ "Alert.closeButtonLabel": "OK",
+ "Alert.dontShowAgain": "Don't show this message again",
+ "AnswersEditor.answersLabel": "Answers",
+ "AnswersEditor.newAnswerBtnLabel": "New answer",
+ "AnswersEditor.noAnswersPlaceholder": "Question has no answer options",
+ "AnswersEditor.numberFieldErrorLabel": "Answer must be a number",
+ "AppBar.administration": "Administration",
+ "AppBar.changeLanguage": "Change language",
+ "AppBar.help": "Help and support",
+ "AppBar.logIn": "Sign in",
+ "AppBar.logOut": "Sign out",
+ "AppBar.settings": "Settings",
+ "AppBar.title": "Kolibri Studio",
+ "AssessmentEditor.closeBtnLabel": "Close",
+ "AssessmentEditor.incompleteItemIndicatorLabel": "Incomplete",
+ "AssessmentEditor.newQuestionBtnLabel": "New question",
+ "AssessmentEditor.noQuestionsPlaceholder": "Exercise has no questions",
+ "AssessmentEditor.showAnswers": "Show answers",
+ "AssessmentEditor.toolbarItemLabel": "question",
+ "AssessmentItemEditor.dialogMessageChangeToInput": "Switching to 'numeric input' will set all answers as correct and remove all non-numeric answers. Continue?",
+ "AssessmentItemEditor.dialogMessageChangeToSingleSelection": "Switching to 'single choice' will set only one answer as correct. Continue?",
+ "AssessmentItemEditor.dialogMessageChangeToTrueFalse": "Switching to 'true or false' will remove all current answers. Continue?",
+ "AssessmentItemEditor.dialogSubmitBtnLabel": "Change",
+ "AssessmentItemEditor.dialogTitle": "Changing question type",
+ "AssessmentItemEditor.questionLabel": "Question",
+ "AssessmentItemEditor.questionTypeLabel": "Response type",
+ "AssessmentItemPreview.answersLabel": "Answers",
+ "AssessmentItemPreview.hintsToggleLabelHide": "Hide hints",
+ "AssessmentItemPreview.hintsToggleLabelShow": "Show {hintsCount} {hintsCount, plural, one {hint} other {hints}}",
+ "AssessmentItemPreview.noAnswersPlaceholder": "Question has no answer options",
+ "AssessmentItemToolbar.toolbarLabelAddAbove": "Add {itemLabel} above",
+ "AssessmentItemToolbar.toolbarLabelAddBelow": "Add {itemLabel} below",
+ "AssessmentItemToolbar.toolbarLabelDelete": "Delete",
+ "AssessmentItemToolbar.toolbarLabelEdit": "Edit",
+ "AssessmentItemToolbar.toolbarLabelMoveDown": "Move down",
+ "AssessmentItemToolbar.toolbarLabelMoveUp": "Move up",
+ "AssessmentTab.dialogCancelBtnLabel": "Cancel",
+ "AssessmentTab.dialogSubmitBtnLabel": "Submit",
+ "AssessmentTab.incompleteItemsCountMessage": "{invalidItemsCount} incomplete {invalidItemsCount, plural, one {question} other {questions}}",
+ "BrowsingCard.addToClipboardAction": "Copy to clipboard",
+ "BrowsingCard.coach": "Resource for coaches",
+ "BrowsingCard.goToSingleLocationAction": "Go to location",
+ "BrowsingCard.hasCoachTooltip": "{value, number, integer} {value, plural, one {resource for coaches} other {resources for coaches}}",
+ "BrowsingCard.previewAction": "View details",
+ "BrowsingCard.resourcesCount": "{count, number} {count, plural, one {resource} other {resources}}",
+ "BrowsingCard.tagsList": "Tags: {tags}",
+ "BytesForHumansStrings.fileSizeInBytes": "{n, number, integer} B",
+ "BytesForHumansStrings.fileSizeInGigabytes": "{n, number, integer} GB",
+ "BytesForHumansStrings.fileSizeInKilobytes": "{n, number, integer} KB",
+ "BytesForHumansStrings.fileSizeInMegabytes": "{n, number, integer} MB",
+ "BytesForHumansStrings.fileSizeInTerabytes": "{n, number, integer} TB",
+ "CatalogFAQ.KolibriAnswer": "Kolibri is an open source ed-tech platform designed for low-resource communities, focused on:",
+ "CatalogFAQ.KolibriAnswerItem1": "Overcoming infrastructural barriers that prevent equitable access to quality education for learners in low-resource and low-connectivity contexts",
+ "CatalogFAQ.KolibriAnswerItem2": "Increasing the availability of open learning materials suitable for many curricula, learning goals, and situations",
+ "CatalogFAQ.KolibriAnswerItem3": "Fostering innovative pedagogy and effective learning outcomes",
+ "CatalogFAQ.KolibriQuestion": "What is Kolibri?",
+ "CatalogFAQ.aboutHeader": "Welcome to the Kolibri Content Library Catalog! ",
+ "CatalogFAQ.aboutKolibriHeader": "About Kolibri",
+ "CatalogFAQ.aboutLibraryHeader": "About the Kolibri Content Library",
+ "CatalogFAQ.channelAnswer": "A channel is Kolibriās unit of organization for digital content. It's a collection of resources organized by single institutions or creators, each of which may contain a set of books, games, textbooks, articles, simulations, exercises, and many more types of educational materials, all made available for use in Kolibri without the need for internet access. A channel isn't necessarily a course or a sequence, it's simply a collection of materials published or gathered together by one organization, as close to the provider's original layout as possible, while still organized for the best possible navigation in Kolibri.",
+ "CatalogFAQ.channelLink": "What is a channel?",
+ "CatalogFAQ.channelQuestion": "What is a channel?",
+ "CatalogFAQ.coachContentAnswer": "Most resources are directed at learners, but some, such as lesson plans, subject refreshers, professional learning guides, and similar, are directed at teachers and facilitators. In Kolibri, we mark this content as \"for coaches\" and limit its visibility to those with coach accounts. If you see coach materials here, they may require less planning for any facilitators using the resource!",
+ "CatalogFAQ.coachContentQuestion": "What are 'resources for coaches'?",
+ "CatalogFAQ.customContentAnswer": "To add your own materials, create an account on Kolibri Studio by going to https://studio.learningequality.org. Recommendations for public materials to be added to the Kolibri Content Library can be made by contacting content@learningequality.org.",
+ "CatalogFAQ.customContentQuestion": "How can I add my own materials or recommend materials from other creators for this library?",
+ "CatalogFAQ.descriptionP1": "Here you can learn more about the educational resources publicly available for use in Kolibri, which are organized into \"channels\". Use the filters to browse channels by keyword, language, or formats of the materials inside.",
+ "CatalogFAQ.descriptionP2": "Click on a channel to get a preview of what subjects and topics it covers, learn more about its creator, see how many resources the channel contains, and learn how to import it into Kolibri. You can also find coach-specific content (lesson plans, teacher professional guides, and other supplementary facilitation material), assessments and exercises, and captions for accessibility.",
+ "CatalogFAQ.descriptionP3": "Sharing the work of these resource creators is what inspires Learning Equality's efforts. We hope you find something that excites you about the potential of digital learning, online or off!",
+ "CatalogFAQ.downloadKolibriLink": "Download Kolibri",
+ "CatalogFAQ.downloadLink": "Download",
+ "CatalogFAQ.endoresementQuestion": "Have these sources been vetted or endorsed as classroom-safe and ready?",
+ "CatalogFAQ.endorsementAnswer": "We select sources with an educational affiliation or mandate, so you can trust that most resources in the Kolibri Content Library were designed for learning purposes. However, we are not able to guarantee the appropriateness of each individual item within any particular source. We recommend that educators and administrators conduct a thorough review of any digital content using their own criteria - including reorganization and re-curation, if necessary - before using it with learners. Since we recognize that there may be many different standards across situations for criteria like preferred levels of interactivity, subject/age appropriateness, cultural sensitivity and tone, among others, we have intentionally offered a wide range of materials to help meet the needs of all learners whatever they may be.",
+ "CatalogFAQ.faqHeader": "Frequently asked questions",
+ "CatalogFAQ.issueAnswer": "Please email us at content@learningequality.org and include the channel name, along with a description of the issue. If you notice an issue on a specific resource, please be sure to link that as well. We'd be happy to investigate and grateful for your feedback!",
+ "CatalogFAQ.issueQuestion": "I found a bug, broken link, or some mislabeled information within a resource. What should I do?",
+ "CatalogFAQ.maintenanceAnswerP1": "Because Kolibri is designed for learners and educators who are disconnected from the internet, content must first be packaged so that it can be used without internet connection. For most sources, our content team uses custom-written, automated scripts to bring content into Kolibri from a website, an app, or a private source such as a hard drive (with the appropriate permissions).",
+ "CatalogFAQ.maintenanceAnswerP2": "To learn more about how content is packaged for use on Kolibri and what types of formats are supported, please refer to our content integration guide.",
+ "CatalogFAQ.maintenanceQuestion": "How is this library created and maintained?",
+ "CatalogFAQ.makerAnswerP1": "Learning Equality, a 501(c)(3) nonprofit based in San Diego, California, is committed to enabling every person in the world to realize their right to a quality education, by supporting the creation, adaptation, and distribution of open educational resources, and creating supportive tools for innovative pedagogy.",
+ "CatalogFAQ.makerAnswerP2": "In recognition of the digital divide, Learning Equality started by bringing the Khan Academy experience offline to more than 6 million learners around the globe. Its second-generation product, Kolibri, is part of a broader ecosystem of products and tools that support curriculum alignment, blended learning pedagogies, and broader use of Open Educational Resources to improve learning.",
+ "CatalogFAQ.makerQuestion": "Who are the makers of Kolibri?",
+ "CatalogFAQ.newContentAnswer": "Our content team routinely adds new sources and channels to the library and updates existing channels as content creators make new materials available.",
+ "CatalogFAQ.newContentQuestion": "Does Learning Equality add new materials?",
+ "CatalogFAQ.ownershipAnswer": "No. Just like an online learning repository with links to external websites, we gather useful digital learning resources to help our community discover a rich variety of learning materials they may not have known about otherwise. All the materials in this educational library are fully credited to the creating organization, reformatted for best display on digital devices, and include any additional information the creator has shared with us. We only include content which is either openly licensed, available to distribute for special nonprofit or noncommercial purposes, or shared with us for distribution through agreement with the creator. Since materials in the library are intended for use in an open source platform, we do not profit financially from their use.",
+ "CatalogFAQ.ownershipQuestion": "Does Learning Equality own these resources?",
+ "CatalogFAQ.partialChannelAnswer": "When importing content into Kolibri, you can select the specific subsections of a channel you're interested in. If youād like to make changes such as editing the title or folder descriptions, or changing the order in which materials appear, please contact us at content@learningequality.org for early access to our Kolibri Studio tool, which can be used to make these changes.",
+ "CatalogFAQ.partialChannelQuestion": "I want to use some of the resources in this channel, but not all of it. What should I do?",
+ "CatalogFAQ.sampleContentAnswer": "You can do this in three ways:",
+ "CatalogFAQ.sampleContentAnswerItem1": "To see the original content source, click the ā® button and select 'Go to source website'",
+ "CatalogFAQ.sampleContentAnswerItem2": "To preview the content on one of our online demo servers (available in English, Spanish, Arabic, French, and Hindi), click the ā® button and select 'View channel on Kolibri'",
+ "CatalogFAQ.sampleContentAnswerItem3": "Download Kolibri and import the channel on your device for full access offline.",
+ "CatalogFAQ.sampleContentQuestion": "How do I review the contents of the channels themselves?",
+ "CatalogFAQ.selectionAnswerP1": "Our approach is unique in that we aim to assemble a library of resources which supports the diversity of needs Kolibri is designed to meet, rather than collecting all possible open educational resources.",
+ "CatalogFAQ.selectionAnswerP2": "To inform what we select, the Learning Equality team is continually maintaining our awareness of openly licensed digital resources available in the educational landscape. Most of our resources come from an organization, institution, or creator with learning design experience and an educational mandate. We prioritize providing a diversity of grade levels, subject areas and languages. Where possible, we also evaluate and seek input on the degree to which the materials may be suitable for the unique blended learning settings in which we work.",
+ "CatalogFAQ.selectionQuestion": "How does Learning Equality determine what goes into this library?",
+ "CatalogFAQ.usingContentAnswer": "Great! All of these resources have been specially packaged for use on Kolibri, our open source platform for offline learning, so please review how to get started with Kolibri first, then follow the instructions to import materials.",
+ "CatalogFAQ.usingContentQuestion": "I found something I'm interested in and would like to start using it. What should I do?",
+ "CatalogFAQ.usingKolibriAnswerP1": "You can learn more about using Kolibri by doing any of the following:",
+ "CatalogFAQ.usingKolibriAnswerP2": "We invite you to use the Kolibri user documentation for further guidance.",
+ "CatalogFAQ.usingKolibriItem1": "Visit the Learning Equality website",
+ "CatalogFAQ.usingKolibriItem2": "View a demo of the platform",
+ "CatalogFAQ.usingKolibriItem3": "Download the software",
+ "CatalogFAQ.usingKolibriQuestion": "How can I use Kolibri?",
+ "CatalogFAQ.usingResourcesHeader": "About using these resources",
+ "CatalogFAQ.viewDemoLink": "View demo",
+ "CatalogFAQ.viewDocsLink": "View docs",
+ "CatalogFAQ.viewGettingStartedLink": "Documentation resources to get started with Kolibri",
+ "CatalogFAQ.viewIntegrationGuide": "View content integration guide",
+ "CatalogFAQ.visitWebsiteLink": "Visit website",
+ "CatalogFilterBar.assessments": "Assessments",
+ "CatalogFilterBar.channelCount": "{count, plural,\n =1 {# channel}\n other {# channels}}",
+ "CatalogFilterBar.clearAll": "Clear all",
+ "CatalogFilterBar.close": "Close",
+ "CatalogFilterBar.coachContent": "Coach content",
+ "CatalogFilterBar.copyTitle": "Copy collection token",
+ "CatalogFilterBar.copyToken": "Copy collection token",
+ "CatalogFilterBar.copyTokenInstructions": "Paste this token into Kolibri to import the channels contained in this collection",
+ "CatalogFilterBar.keywords": "\"{text}\"",
+ "CatalogFilterBar.starred": "Starred",
+ "CatalogFilterBar.subtitles": "Subtitles",
+ "CatalogFilters.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
+ "CatalogFilters.coachLabel": "Resources for coaches",
+ "CatalogFilters.copyright": "Ā© {year} Learning Equality",
+ "CatalogFilters.formatLabel": "Formats",
+ "CatalogFilters.frequentlyAskedQuestionsLink": "Frequently asked questions",
+ "CatalogFilters.includesLabel": "Display only channels with",
+ "CatalogFilters.licenseLabel": "Licenses",
+ "CatalogFilters.searchLabel": "Keywords",
+ "CatalogFilters.searchText": "Search",
+ "CatalogFilters.starredLabel": "Starred",
+ "CatalogFilters.subtitlesLabel": "Captions or subtitles",
+ "CatalogList.cancelButton": "Cancel",
+ "CatalogList.channelSelectionCount": "{count, plural,\n =1 {# channel selected}\n other {# channels selected}}",
+ "CatalogList.downloadButton": "Download",
+ "CatalogList.downloadCSV": "Download CSV",
+ "CatalogList.downloadPDF": "Download PDF",
+ "CatalogList.downloadingMessage": "Download started",
+ "CatalogList.resultsText": "{count, plural,\n =1 {# result found}\n other {# results found}}",
+ "CatalogList.selectAll": "Select all",
+ "CatalogList.selectChannels": "Download a summary of selected channels",
+ "CategoryOptions.noCategoryFoundText": "Category not found",
+ "ChangePasswordForm.cancelAction": "Cancel",
+ "ChangePasswordForm.changePasswordHeader": "Change password",
+ "ChangePasswordForm.confirmNewPasswordLabel": "Confirm new password",
+ "ChangePasswordForm.formInvalidText": "Passwords don't match",
+ "ChangePasswordForm.newPasswordLabel": "New password",
+ "ChangePasswordForm.passwordChangeFailed": "Failed to save new password",
+ "ChangePasswordForm.paswordChangeSuccess": "Password updated",
+ "ChangePasswordForm.saveChangesAction": "Save changes",
+ "ChannelCatalogFrontPage.assessmentsIncludedText": "Assessments",
+ "ChannelCatalogFrontPage.catalogHeader": "Kolibri Content Library channels",
+ "ChannelCatalogFrontPage.coachHeading": "Resources for coaches",
+ "ChannelCatalogFrontPage.containsHeading": "Contains",
+ "ChannelCatalogFrontPage.defaultNoItemsText": "---",
+ "ChannelCatalogFrontPage.exported": "Exported",
+ "ChannelCatalogFrontPage.formatsHeading": "Formats",
+ "ChannelCatalogFrontPage.languagesHeading": "Languages",
+ "ChannelCatalogFrontPage.numberOfChannels": "{ num } channels",
+ "ChannelCatalogFrontPage.subtitlesIncludedText": "Captions or subtitles",
+ "ChannelDeletedError.backToHomeAction": "Back to home",
+ "ChannelDeletedError.channelDeletedDetails": "This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.",
+ "ChannelDeletedError.channelDeletedHeader": "Channel not found",
+ "ChannelDetailsModal.downloadButton": "Download channel summary",
+ "ChannelDetailsModal.downloadCSV": "Download CSV",
+ "ChannelDetailsModal.downloadPDF": "Download PDF",
+ "ChannelExportStrings.aggregators": "Aggregators",
+ "ChannelExportStrings.assessments": "Assessments",
+ "ChannelExportStrings.authors": "Authors",
+ "ChannelExportStrings.coachContent": "Resources for coaches",
+ "ChannelExportStrings.copyrightHolders": "Copyright holders",
+ "ChannelExportStrings.description": "Description",
+ "ChannelExportStrings.downloadFilename": "{year}_{month}_Kolibri_Content_Library",
+ "ChannelExportStrings.id": "Channel ID",
+ "ChannelExportStrings.language": "Language",
+ "ChannelExportStrings.languages": "Included languages",
+ "ChannelExportStrings.licenses": "Licenses",
+ "ChannelExportStrings.name": "Name",
+ "ChannelExportStrings.no": "No",
+ "ChannelExportStrings.providers": "Providers",
+ "ChannelExportStrings.resources": "Resources",
+ "ChannelExportStrings.size": "Total resources",
+ "ChannelExportStrings.storage": "Storage",
+ "ChannelExportStrings.subtitles": "Captions or subtitles",
+ "ChannelExportStrings.tags": "Tags",
+ "ChannelExportStrings.token": "Token",
+ "ChannelExportStrings.yes": "Yes",
+ "ChannelInfoCard.resourceCount": "{count, number} {count, plural, one {resource} other {resources}}",
+ "ChannelInvitation.accept": "Accept",
+ "ChannelInvitation.acceptedSnackbar": "Accepted invitation",
+ "ChannelInvitation.cancel": "Cancel",
+ "ChannelInvitation.decline": "Decline",
+ "ChannelInvitation.declinedSnackbar": "Declined invitation",
+ "ChannelInvitation.decliningInvitation": "Declining Invitation",
+ "ChannelInvitation.decliningInvitationMessage": "Are you sure you want to decline this invitation?",
+ "ChannelInvitation.editText": "{sender} has invited you to edit {channel}",
+ "ChannelInvitation.goToChannelSnackbarAction": "Go to channel",
+ "ChannelInvitation.viewText": "{sender} has invited you to view {channel}",
+ "ChannelItem.cancel": "Cancel",
+ "ChannelItem.channelDeletedSnackbar": "Channel deleted",
+ "ChannelItem.channelLanguageNotSetIndicator": "No language set",
+ "ChannelItem.copyToken": "Copy channel token",
+ "ChannelItem.deleteChannel": "Delete channel",
+ "ChannelItem.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
+ "ChannelItem.deleteTitle": "Delete this channel",
+ "ChannelItem.details": "Details",
+ "ChannelItem.editChannel": "Edit channel details",
+ "ChannelItem.goToWebsite": "Go to source website",
+ "ChannelItem.lastPublished": "Published {last_published}",
+ "ChannelItem.lastUpdated": "Updated {updated}",
+ "ChannelItem.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}",
+ "ChannelItem.unpublishedText": "Unpublished",
+ "ChannelItem.versionText": "Version {version}",
+ "ChannelItem.viewContent": "View channel on Kolibri",
+ "ChannelList.channel": "New channel",
+ "ChannelList.channelFilterLabel": "Channels",
+ "ChannelList.noChannelsFound": "No channels found",
+ "ChannelList.noMatchingChannels": "There are no matching channels",
+ "ChannelListAppError.channelPermissionsErrorDetails": "Sign in or ask the owner of this channel to give you permission to edit or view",
+ "ChannelListIndex.catalog": "Content Library",
+ "ChannelListIndex.channelSets": "Collections",
+ "ChannelListIndex.frequentlyAskedQuestions": "Frequently asked questions",
+ "ChannelListIndex.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}",
+ "ChannelListIndex.libraryTitle": "Kolibri Content Library Catalog",
+ "ChannelModal.APIText": "Channels generated automatically are not editable.",
+ "ChannelModal.changesSaved": "Changes saved",
+ "ChannelModal.channelDescription": "Channel description",
+ "ChannelModal.channelError": "Field is required",
+ "ChannelModal.channelName": "Channel name",
+ "ChannelModal.closeButton": "Exit without saving",
+ "ChannelModal.createButton": "Create",
+ "ChannelModal.creatingHeader": "New channel",
+ "ChannelModal.details": "Channel details",
+ "ChannelModal.editTab": "Details",
+ "ChannelModal.keepEditingButton": "Keep editing",
+ "ChannelModal.notFoundError": "Channel does not exist",
+ "ChannelModal.saveChangesButton": "Save changes",
+ "ChannelModal.shareTab": "Sharing",
+ "ChannelModal.unauthorizedError": "You cannot edit this channel",
+ "ChannelModal.unsavedChangesHeader": "Unsaved changes",
+ "ChannelModal.unsavedChangesText": "You will lose any unsaved changes. Are you sure you want to exit?",
+ "ChannelNotFoundError.backToHomeAction": "Back to home",
+ "ChannelNotFoundError.channelNotFoundDetails": "This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.",
+ "ChannelNotFoundError.channelNotFoundHeader": "Channel not found",
+ "ChannelSelectionList.noChannelsFound": "No channels found",
+ "ChannelSelectionList.searchText": "Search for a channel",
+ "ChannelSetItem.cancel": "Cancel",
+ "ChannelSetItem.delete": "Delete collection",
+ "ChannelSetItem.deleteChannelSetText": "Are you sure you want to delete this collection?",
+ "ChannelSetItem.deleteChannelSetTitle": "Delete collection",
+ "ChannelSetItem.edit": "Edit collection",
+ "ChannelSetItem.options": "Options",
+ "ChannelSetItem.saving": "Saving",
+ "ChannelSetList.aboutChannelSets": "About collections",
+ "ChannelSetList.aboutChannelSetsLink": "Learn about collections",
+ "ChannelSetList.addChannelSetTitle": "New collection",
+ "ChannelSetList.cancelButtonLabel": "Close",
+ "ChannelSetList.channelNumber": "Number of channels",
+ "ChannelSetList.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.",
+ "ChannelSetList.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections",
+ "ChannelSetList.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.",
+ "ChannelSetList.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.",
+ "ChannelSetList.options": "Options",
+ "ChannelSetList.title": "Collection name",
+ "ChannelSetList.token": "Token ID",
+ "ChannelSetModal.bookmark": "Starred",
+ "ChannelSetModal.channelAdded": "Channel added",
+ "ChannelSetModal.channelCountText": "{channelCount, plural, =0 {No published channels in your collection} =1 {# channel} other {# channels}}",
+ "ChannelSetModal.channelRemoved": "Channel removed",
+ "ChannelSetModal.channelSelectedCountText": "{channelCount, plural, =1 {# channel selected} other {# channels selected}}",
+ "ChannelSetModal.channels": "Collection channels",
+ "ChannelSetModal.closeButton": "Exit without saving",
+ "ChannelSetModal.collectionErrorText": "This collection does not exist",
+ "ChannelSetModal.createButton": "Create",
+ "ChannelSetModal.creatingChannelSet": "New collection",
+ "ChannelSetModal.edit": "My Channels",
+ "ChannelSetModal.finish": "Finish",
+ "ChannelSetModal.public": "Public",
+ "ChannelSetModal.publishedChannelsOnlyText": "Only published channels are available for selection",
+ "ChannelSetModal.removeText": "Remove",
+ "ChannelSetModal.saveButton": "Save and close",
+ "ChannelSetModal.selectChannelsHeader": "Select channels",
+ "ChannelSetModal.titleLabel": "Collection name",
+ "ChannelSetModal.titleRequiredText": "Field is required",
+ "ChannelSetModal.token": "Collection token",
+ "ChannelSetModal.tokenPrompt": "Copy this token into Kolibri to import this collection onto your device.",
+ "ChannelSetModal.unsavedChangesHeader": "Unsaved changes",
+ "ChannelSetModal.unsavedChangesText": "You will lose any unsaved changes. Are you sure you want to exit?",
+ "ChannelSetModal.view": "View-Only",
+ "ChannelSharing.alreadyHasAccessError": "User already has access to this channel",
+ "ChannelSharing.alreadyInvitedError": "User already invited",
+ "ChannelSharing.canEdit": "Can edit",
+ "ChannelSharing.canView": "Can view",
+ "ChannelSharing.emailLabel": "Email",
+ "ChannelSharing.emailRequiredMessage": "Email is required",
+ "ChannelSharing.invitationFailedError": "Invitation failed to send. Please try again",
+ "ChannelSharing.invitationSentMessage": "Invitation sent",
+ "ChannelSharing.inviteButton": "Send invitation",
+ "ChannelSharing.inviteSubheading": "Invite collaborators",
+ "ChannelSharing.validEmailMessage": "Please enter a valid email",
+ "ChannelSharingTable.cancelButton": "Cancel",
+ "ChannelSharingTable.currentUserText": "{first_name} {last_name} (you)",
+ "ChannelSharingTable.deleteInvitation": "Delete invitation",
+ "ChannelSharingTable.deleteInvitationConfirm": "Delete invitation",
+ "ChannelSharingTable.deleteInvitationHeader": "Delete invitation",
+ "ChannelSharingTable.deleteInvitationText": "Are you sure you would like to delete the invitation for {email}?",
+ "ChannelSharingTable.editPermissionsGrantedMessage": "Edit permissions granted",
+ "ChannelSharingTable.editorsSubheading": "{count, plural,\n =1 {# user who can edit}\n other {# users who can edit}}",
+ "ChannelSharingTable.guestText": "Guest",
+ "ChannelSharingTable.invitationDeletedMessage": "Invitation deleted",
+ "ChannelSharingTable.invitationFailedError": "Invitation failed to resend. Please try again",
+ "ChannelSharingTable.invitationSentMessage": "Invitation sent",
+ "ChannelSharingTable.invitePendingText": "Invite pending",
+ "ChannelSharingTable.makeEditor": "Grant edit permissions",
+ "ChannelSharingTable.makeEditorConfirm": "Yes, grant permissions",
+ "ChannelSharingTable.makeEditorHeader": "Grant edit permissions",
+ "ChannelSharingTable.makeEditorText": "Are you sure you would like to grant edit permissions to {first_name} {last_name}?",
+ "ChannelSharingTable.noUsersText": "No users found",
+ "ChannelSharingTable.optionsDropdown": "Options",
+ "ChannelSharingTable.removeViewer": "Revoke view permissions",
+ "ChannelSharingTable.removeViewerConfirm": "Yes, revoke",
+ "ChannelSharingTable.removeViewerHeader": "Revoke view permissions",
+ "ChannelSharingTable.removeViewerText": "Are you sure you would like to revoke view permissions for {first_name} {last_name}?",
+ "ChannelSharingTable.resendInvitation": "Resend invitation",
+ "ChannelSharingTable.userRemovedMessage": "User removed",
+ "ChannelSharingTable.viewersSubheading": "{count, plural,\n =1 {# user who can view}\n other {# users who can view}}",
+ "ChannelStar.star": "Add to starred channels",
+ "ChannelStar.starred": "Added to starred channels",
+ "ChannelStar.unstar": "Remove from starred channels",
+ "ChannelStar.unstarred": "Removed from starred channels",
+ "ChannelThumbnail.cancel": "Cancel",
+ "ChannelThumbnail.crop": "Crop",
+ "ChannelThumbnail.croppingPrompt": "Drag image to reframe",
+ "ChannelThumbnail.defaultFilename": "File",
+ "ChannelThumbnail.noThumbnail": "No thumbnail",
+ "ChannelThumbnail.remove": "Remove",
+ "ChannelThumbnail.retryUpload": "Retry upload",
+ "ChannelThumbnail.save": "Save",
+ "ChannelThumbnail.upload": "Upload image",
+ "ChannelThumbnail.uploadFailed": "Upload failed",
+ "ChannelThumbnail.uploadingThumbnail": "Uploading",
+ "ChannelThumbnail.zoomIn": "Zoom in",
+ "ChannelThumbnail.zoomOut": "Zoom out",
+ "ChannelTokenModal.close": "Close",
+ "ChannelTokenModal.copyTitle": "Copy channel token",
+ "ChannelTokenModal.copyTokenInstructions": "Paste this token into Kolibri to import this channel",
+ "Clipboard.backToClipboard": "Clipboard",
+ "Clipboard.close": "Close",
+ "Clipboard.copiedItemsToClipboard": "Copied in clipboard",
+ "Clipboard.deleteSelectedButton": "Delete",
+ "Clipboard.duplicateSelectedButton": "Make a copy",
+ "Clipboard.emptyDefaultText": "Use the clipboard to copy resources and move them to other folders and channels",
+ "Clipboard.emptyDefaultTitle": "No resources in your clipboard",
+ "Clipboard.moveSelectedButton": "Move",
+ "Clipboard.removedFromClipboard": "Deleted from clipboard",
+ "Clipboard.selectAll": "Select all",
+ "CommonMetadataStrings.accessibility": "Accessibility",
+ "CommonMetadataStrings.algebra": "Algebra",
+ "CommonMetadataStrings.all": "All",
+ "CommonMetadataStrings.allContent": "Viewed in its entirety",
+ "CommonMetadataStrings.allLevelsBasicSkills": "All levels -- basic skills",
+ "CommonMetadataStrings.allLevelsWorkSkills": "All levels -- work skills",
+ "CommonMetadataStrings.altText": "Includes alternative text descriptions for images",
+ "CommonMetadataStrings.anthropology": "Anthropology",
+ "CommonMetadataStrings.arithmetic": "Arithmetic",
+ "CommonMetadataStrings.arts": "Arts",
+ "CommonMetadataStrings.astronomy": "Astronomy",
+ "CommonMetadataStrings.audioDescription": "Includes audio descriptions",
+ "CommonMetadataStrings.basicSkills": "Basic skills",
+ "CommonMetadataStrings.biology": "Biology",
+ "CommonMetadataStrings.browseChannel": "Browse channel",
+ "CommonMetadataStrings.calculus": "Calculus",
+ "CommonMetadataStrings.captionsSubtitles": "Includes captions or subtitles",
+ "CommonMetadataStrings.category": "Category",
+ "CommonMetadataStrings.chemistry": "Chemistry",
+ "CommonMetadataStrings.civicEducation": "Civic education",
+ "CommonMetadataStrings.completeDuration": "When time spent is equal to duration",
+ "CommonMetadataStrings.completion": "Completion",
+ "CommonMetadataStrings.computerScience": "Computer science",
+ "CommonMetadataStrings.create": "Create",
+ "CommonMetadataStrings.currentEvents": "Current events",
+ "CommonMetadataStrings.dailyLife": "Daily life",
+ "CommonMetadataStrings.dance": "Dance",
+ "CommonMetadataStrings.determinedByResource": "Determined by the resource",
+ "CommonMetadataStrings.digitalLiteracy": "Digital literacy",
+ "CommonMetadataStrings.diversity": "Diversity",
+ "CommonMetadataStrings.drama": "Drama",
+ "CommonMetadataStrings.duration": "Duration",
+ "CommonMetadataStrings.earthScience": "Earth science",
+ "CommonMetadataStrings.entrepreneurship": "Entrepreneurship",
+ "CommonMetadataStrings.environment": "Environment",
+ "CommonMetadataStrings.exactTime": "Time to complete",
+ "CommonMetadataStrings.explore": "Explore",
+ "CommonMetadataStrings.financialLiteracy": "Financial literacy",
+ "CommonMetadataStrings.forBeginners": "For beginners",
+ "CommonMetadataStrings.forTeachers": "For teachers",
+ "CommonMetadataStrings.geometry": "Geometry",
+ "CommonMetadataStrings.goal": "When goal is met",
+ "CommonMetadataStrings.guides": "Guides",
+ "CommonMetadataStrings.highContrast": "Includes high contrast text for learners with low vision",
+ "CommonMetadataStrings.history": "History",
+ "CommonMetadataStrings.industryAndSectorSpecific": "Industry and sector specific",
+ "CommonMetadataStrings.languageLearning": "Language learning",
+ "CommonMetadataStrings.learningActivity": "Learning Activity",
+ "CommonMetadataStrings.learningSkills": "Learning skills",
+ "CommonMetadataStrings.lessonPlans": "Lesson plans",
+ "CommonMetadataStrings.level": "Level",
+ "CommonMetadataStrings.listen": "Listen",
+ "CommonMetadataStrings.literacy": "Literacy",
+ "CommonMetadataStrings.literature": "Literature",
+ "CommonMetadataStrings.logicAndCriticalThinking": "Logic and critical thinking",
+ "CommonMetadataStrings.longActivity": "Long activity",
+ "CommonMetadataStrings.lowerPrimary": "Lower primary",
+ "CommonMetadataStrings.lowerSecondary": "Lower secondary",
+ "CommonMetadataStrings.masteryMofN": "Goal: {m} out of {n}",
+ "CommonMetadataStrings.mathematics": "Mathematics",
+ "CommonMetadataStrings.mechanicalEngineering": "Mechanical engineering",
+ "CommonMetadataStrings.mediaLiteracy": "Media literacy",
+ "CommonMetadataStrings.mentalHealth": "Mental health",
+ "CommonMetadataStrings.music": "Music",
+ "CommonMetadataStrings.needsInternet": "Internet connection",
+ "CommonMetadataStrings.needsMaterials": "Other supplies",
+ "CommonMetadataStrings.numeracy": "Numeracy",
+ "CommonMetadataStrings.peers": "Working with peers",
+ "CommonMetadataStrings.physics": "Physics",
+ "CommonMetadataStrings.politicalScience": "Political science",
+ "CommonMetadataStrings.practice": "Practice",
+ "CommonMetadataStrings.practiceQuiz": "Practice quiz",
+ "CommonMetadataStrings.preschool": "Preschool",
+ "CommonMetadataStrings.professionalSkills": "Professional skills",
+ "CommonMetadataStrings.programming": "Programming",
+ "CommonMetadataStrings.publicHealth": "Public health",
+ "CommonMetadataStrings.read": "Read",
+ "CommonMetadataStrings.readReference": "Reference",
+ "CommonMetadataStrings.readingAndWriting": "Reading and writing",
+ "CommonMetadataStrings.readingComprehension": "Reading comprehension",
+ "CommonMetadataStrings.reference": "Reference material",
+ "CommonMetadataStrings.reflect": "Reflect",
+ "CommonMetadataStrings.school": "School",
+ "CommonMetadataStrings.sciences": "Sciences",
+ "CommonMetadataStrings.shortActivity": "Short activity",
+ "CommonMetadataStrings.signLanguage": "Includes sign language captions",
+ "CommonMetadataStrings.skillsTraining": "Skills training",
+ "CommonMetadataStrings.socialSciences": "Social sciences",
+ "CommonMetadataStrings.sociology": "Sociology",
+ "CommonMetadataStrings.softwareTools": "Other software tools",
+ "CommonMetadataStrings.softwareToolsAndTraining": "Software tools and training",
+ "CommonMetadataStrings.specializedProfessionalTraining": "Specialized professional training",
+ "CommonMetadataStrings.statistics": "Statistics",
+ "CommonMetadataStrings.taggedPdf": "Tagged PDF",
+ "CommonMetadataStrings.teacher": "Working with a teacher",
+ "CommonMetadataStrings.technicalAndVocationalTraining": "Technical and vocational training",
+ "CommonMetadataStrings.tertiary": "Tertiary",
+ "CommonMetadataStrings.toUseWithPaperAndPencil": "Paper and pencil",
+ "CommonMetadataStrings.topicLabel": "Folder",
+ "CommonMetadataStrings.upperPrimary": "Upper primary",
+ "CommonMetadataStrings.upperSecondary": "Upper secondary",
+ "CommonMetadataStrings.visualArt": "Visual art",
+ "CommonMetadataStrings.watch": "Watch",
+ "CommonMetadataStrings.webDesign": "Web design",
+ "CommonMetadataStrings.work": "Work",
+ "CommonMetadataStrings.writing": "Writing",
+ "CommunityStandardsModal.communityStandardsHeader": "Community Standards",
+ "CommunityStandardsModal.coreValuesLink": "Learn more about Learning Equality's core values",
+ "CommunityStandardsModal.description": "Learning Equality is a nonprofit organization dedicated to enabling equitable access to quality educational experiences. Along with our statement of Core Values, these Community Standards are intended to foster a supportive and inclusive environment for our users.",
+ "CommunityStandardsModal.libraryDescription": "The Kolibri Library is both a grassroots and curated effort to provide a wide variety of materials for all learners and learning purposes. To help us achieve these goals, we invite you to use Kolibri Studio in ways that:",
+ "CommunityStandardsModal.libraryItem1": "Model good practices in open sharing and respect copyright. Create an ethical sharing community by labeling licenses, making sure you know the appropriate licenses for what you upload, and ensuring that appropriate written permissions are documented if needed. Studio is primarily designed to host materials which are openly licensed or come with special permissions for re-distribution and reproduction.",
+ "CommunityStandardsModal.libraryItem2": "Keep materials clear, organized, and usable. We welcome resources created at all levels of production! To help them reach as many learners as possible, we invite you to fully utilize all metadata fields and aim for quality in comprehensibility, legibility, or digitization such that the content is usable and understandable.",
+ "CommunityStandardsModal.libraryItem3": "Respect the community. Avoid obscenity and vulgarity, beyond specific educational purposes they might serve in some contexts. Hate speech of any kind, or promotion of violence or discrimination, will never be tolerated.",
+ "CommunityStandardsModal.libraryItem4": "Kolibri Studio is for educational purposes only. It is not intended to be used for non-educational purposes such as recruitment, indoctrination, advertisement, file sharing, or personal media hosting.",
+ "CommunityStandardsModal.studioDescription": "Kolibri Studio gives you access to the Kolibri Library, a growing library of educational materials, which we encourage you to use as your own. We built Kolibri Studio to help you prepare educational materials in a variety of ways, including but not limited to:",
+ "CommunityStandardsModal.studioItem1": "Browsing. Selecting appropriate educational materials for your situation from the Kolibri Library, the listing of sources available on the public channels page in Kolibri Studio",
+ "CommunityStandardsModal.studioItem2": "Curating. Reorganizing the materials in these channels by selecting, deleting, and reordering appropriate items",
+ "CommunityStandardsModal.studioItem3": "Sharing. Creating and publishing new channels with what you find, either to share with your own implementations privately or to share with others on Kolibri Studio.",
+ "CommunityStandardsModal.studioItem4": "Modifying & Creating. Adding your own assessment exercises to any existing materials",
+ "CommunityStandardsModal.studioItem5": "Hosting. Uploading your own materials (limited to materials you know are appropriately licensed to do so) from a local hard drive or other locations on the internet",
+ "CompletionOptions.learnersCanMarkComplete": "Allow learners to mark as complete",
+ "CompletionOptions.referenceHint": "Progress will not be tracked on reference material unless learners mark it as complete",
+ "ConstantStrings.All Rights Reserved": "All Rights Reserved",
+ "ConstantStrings.All Rights Reserved_description": "The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty.",
+ "ConstantStrings.CC BY": "CC BY",
+ "ConstantStrings.CC BY-NC": "CC BY-NC",
+ "ConstantStrings.CC BY-NC-ND": "CC BY-NC-ND",
+ "ConstantStrings.CC BY-NC-ND_description": "The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially.",
+ "ConstantStrings.CC BY-NC-SA": "CC BY-NC-SA",
+ "ConstantStrings.CC BY-NC-SA_description": "The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms.",
+ "ConstantStrings.CC BY-NC_description": "The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms.",
+ "ConstantStrings.CC BY-ND": "CC BY-ND",
+ "ConstantStrings.CC BY-ND_description": "The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you.",
+ "ConstantStrings.CC BY-SA": "CC BY-SA",
+ "ConstantStrings.CC BY-SA_description": "The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to \"copyleft\" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects.",
+ "ConstantStrings.CC BY_description": "The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials.",
+ "ConstantStrings.Public Domain": "Public Domain",
+ "ConstantStrings.Public Domain_description": "Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights.",
+ "ConstantStrings.Special Permissions": "Special Permissions",
+ "ConstantStrings.Special Permissions_description": "Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails.",
+ "ConstantStrings.audio": "Audio",
+ "ConstantStrings.audio_thumbnail": "Thumbnail",
+ "ConstantStrings.bookmark": "Starred",
+ "ConstantStrings.coach": "Coaches",
+ "ConstantStrings.do_all": "Goal: 100% correct",
+ "ConstantStrings.do_all_description": "Learner must answer all questions in the exercise correctly (not recommended for long exercises)",
+ "ConstantStrings.document": "Document",
+ "ConstantStrings.document_thumbnail": "Thumbnail",
+ "ConstantStrings.edit": "My channels",
+ "ConstantStrings.epub": "EPub document",
+ "ConstantStrings.exercise": "Exercise",
+ "ConstantStrings.exercise_thumbnail": "Thumbnail",
+ "ConstantStrings.firstCopy": "Copy of {title}",
+ "ConstantStrings.gif": "GIF image",
+ "ConstantStrings.h5p": "H5P App",
+ "ConstantStrings.high_res_video": "High resolution",
+ "ConstantStrings.html5": "HTML5 App",
+ "ConstantStrings.html5_thumbnail": "Thumbnail",
+ "ConstantStrings.html5_zip": "HTML5 zip",
+ "ConstantStrings.input_question": "Numeric input",
+ "ConstantStrings.jpeg": "JPEG image",
+ "ConstantStrings.jpg": "JPG image",
+ "ConstantStrings.json": "JSON",
+ "ConstantStrings.learner": "Anyone",
+ "ConstantStrings.low_res_video": "Low resolution",
+ "ConstantStrings.m_of_n": "M of N...",
+ "ConstantStrings.m_of_n_description": "Learner must answer M questions correctly from the last N answered questions. For example, ā3 of 5ā means learners must answer 3 questions correctly out of the 5 most recently answered.",
+ "ConstantStrings.mp3": "MP3 audio",
+ "ConstantStrings.mp4": "MP4 video",
+ "ConstantStrings.multiple_selection": "Multiple choice",
+ "ConstantStrings.nthCopy": "Copy {n, number, integer} of {title}",
+ "ConstantStrings.num_correct_in_a_row_10": "Goal: 10 in a row",
+ "ConstantStrings.num_correct_in_a_row_10_description": "Learner must answer 10 questions in a row correctly",
+ "ConstantStrings.num_correct_in_a_row_2": "Goal: 2 in a row",
+ "ConstantStrings.num_correct_in_a_row_2_description": "Learner must answer 2 questions in a row correctly",
+ "ConstantStrings.num_correct_in_a_row_3": "Goal: 3 in a row",
+ "ConstantStrings.num_correct_in_a_row_3_description": "Learner must answer 3 questions in a row correctly",
+ "ConstantStrings.num_correct_in_a_row_5": "Goal: 5 in a row",
+ "ConstantStrings.num_correct_in_a_row_5_description": "Learner must answer 5 questions in a row correctly",
+ "ConstantStrings.pdf": "PDF document",
+ "ConstantStrings.perseus": "Perseus Exercise",
+ "ConstantStrings.perseus_question": "Khan Academy question",
+ "ConstantStrings.png": "PNG image",
+ "ConstantStrings.public": "Content library",
+ "ConstantStrings.single_selection": "Single choice",
+ "ConstantStrings.slideshow": "Slideshow",
+ "ConstantStrings.svg": "SVG image",
+ "ConstantStrings.topic": "Folder",
+ "ConstantStrings.topic_thumbnail": "Thumbnail",
+ "ConstantStrings.true_false": "True/False",
+ "ConstantStrings.unknown_question": "Unknown question type",
+ "ConstantStrings.video": "Video",
+ "ConstantStrings.video_subtitle": "Captions",
+ "ConstantStrings.video_thumbnail": "Thumbnail",
+ "ConstantStrings.view": "View-only",
+ "ConstantStrings.vtt": "VTT caption",
+ "ConstantStrings.webm": "WEBM video",
+ "ConstantStrings.zip": "HTML5 zip",
+ "ContentDefaults.aggregator": "Aggregator",
+ "ContentDefaults.author": "Author",
+ "ContentDefaults.copyrightHolder": "Copyright holder",
+ "ContentDefaults.defaultsSubTitle": "New resources will be automatically given these values",
+ "ContentDefaults.defaultsTitle": "Default copyright settings for new resources (optional)",
+ "ContentDefaults.documents": "Documents",
+ "ContentDefaults.html5": "HTML5 apps",
+ "ContentDefaults.license": "License",
+ "ContentDefaults.licenseDescription": "License description",
+ "ContentDefaults.noLicense": "No license selected",
+ "ContentDefaults.provider": "Provider",
+ "ContentDefaults.thumbnailsTitle": "Automatically generate thumbnails for the following resource types",
+ "ContentDefaults.videos": "Videos",
+ "ContentNodeChangedIcon.containsNew": "Contains unpublished resources",
+ "ContentNodeChangedIcon.containsNewAndUpdated": "Contains unpublished resources and changes",
+ "ContentNodeChangedIcon.containsUpdated": "Contains unpublished changes",
+ "ContentNodeChangedIcon.isNewResource": "Unpublished",
+ "ContentNodeChangedIcon.isNewTopic": "Unpublished folder",
+ "ContentNodeChangedIcon.isUpdatedResource": "Updated since last publish",
+ "ContentNodeChangedIcon.isUpdatedTopic": "Folder has been updated since last publish",
+ "ContentNodeCopyTaskProgress.copyErrorTopic": "Some resources failed to copy",
+ "ContentNodeEditListItem.copiedSnackbar": "Copy operation complete",
+ "ContentNodeEditListItem.creatingCopies": "Copying...",
+ "ContentNodeEditListItem.optionsTooltip": "Options",
+ "ContentNodeEditListItem.removeNode": "Remove",
+ "ContentNodeEditListItem.retryCopy": "Retry",
+ "ContentNodeEditListItem.undo": "Undo",
+ "ContentNodeIcon.audio": "Audio",
+ "ContentNodeIcon.document": "Document",
+ "ContentNodeIcon.exercise": "Exercise",
+ "ContentNodeIcon.html5": "HTML5 App",
+ "ContentNodeIcon.slideshow": "Slideshow",
+ "ContentNodeIcon.topic": "Folder",
+ "ContentNodeIcon.unsupported": "Unsupported",
+ "ContentNodeIcon.video": "Video",
+ "ContentNodeLearningActivityIcon.multipleLearningActivities": "Multiple learning activities",
+ "ContentNodeListItem.coachTooltip": "Resource for coaches",
+ "ContentNodeListItem.copyingError": "Copy failed.",
+ "ContentNodeListItem.copyingTask": "Copying",
+ "ContentNodeListItem.hasCoachTooltip": "{value, number, integer} {value, plural, one {resource for coaches} other {resources for coaches}}",
+ "ContentNodeListItem.openTopic": "Open folder",
+ "ContentNodeListItem.questions": "{value, number, integer} {value, plural, one {question} other {questions}}",
+ "ContentNodeListItem.resources": "{value, number, integer} {value, plural, one {resource} other {resources}}",
+ "ContentNodeOptions.copiedItemsToClipboard": "Copied in clipboard",
+ "ContentNodeOptions.copiedSnackbar": "Copy operation complete",
+ "ContentNodeOptions.copiedToClipboardSnackbar": "Copied to clipboard",
+ "ContentNodeOptions.copyToClipboard": "Copy to clipboard",
+ "ContentNodeOptions.creatingCopies": "Copying...",
+ "ContentNodeOptions.editDetails": "Edit details",
+ "ContentNodeOptions.editTopicDetails": "Edit folder details",
+ "ContentNodeOptions.goToOriginalLocation": "Go to original location",
+ "ContentNodeOptions.makeACopy": "Make a copy",
+ "ContentNodeOptions.move": "Move",
+ "ContentNodeOptions.moveTo": "Move to...",
+ "ContentNodeOptions.newSubtopic": "New folder",
+ "ContentNodeOptions.remove": "Delete",
+ "ContentNodeOptions.removedFromClipboard": "Deleted from clipboard",
+ "ContentNodeOptions.removedItems": "Sent to trash",
+ "ContentNodeOptions.undo": "Undo",
+ "ContentNodeOptions.viewDetails": "View details",
+ "ContentNodeStrings.untitled": "Untitled",
+ "ContentNodeThumbnail.cancel": "Cancel",
+ "ContentNodeThumbnail.crop": "Crop",
+ "ContentNodeThumbnail.croppingPrompt": "Drag image to reframe",
+ "ContentNodeThumbnail.defaultFilename": "File",
+ "ContentNodeThumbnail.generate": "Generate from file",
+ "ContentNodeThumbnail.generatingThumbnail": "Generating from file",
+ "ContentNodeThumbnail.noThumbnail": "No thumbnail",
+ "ContentNodeThumbnail.remove": "Remove",
+ "ContentNodeThumbnail.retryUpload": "Retry upload",
+ "ContentNodeThumbnail.save": "Save",
+ "ContentNodeThumbnail.upload": "Upload image",
+ "ContentNodeThumbnail.uploadFailed": "Upload failed",
+ "ContentNodeThumbnail.uploadingThumbnail": "Uploading",
+ "ContentNodeThumbnail.zoomIn": "Zoom in",
+ "ContentNodeThumbnail.zoomOut": "Zoom out",
+ "ContentNodeValidator.allIncompleteDescendantsText": "{count, plural, one {{count, number, integer} resource is incomplete and cannot be published} other {All {count, number, integer} resources are incomplete and cannot be published}}",
+ "ContentNodeValidator.incompleteDescendantsText": "{count, number, integer} {count, plural, one {resource is incomplete} other {resources are incomplete}}",
+ "ContentNodeValidator.incompleteText": "Incomplete",
+ "ContentNodeValidator.missingTitle": "Missing title",
+ "ContentRenderer.noFileText": "Select a file to preview",
+ "ContentRenderer.previewNotSupported": "Preview unavailable",
+ "ContentTreeList.allChannelsLabel": "Channels",
+ "ContentTreeList.noResourcesOrTopics": "There are no resources or folders here",
+ "ContentTreeList.selectAllAction": "Select all",
+ "CopyToken.copiedTokenId": "Token copied",
+ "CopyToken.copyFailed": "Copy failed",
+ "CopyToken.copyPrompt": "Copy token to import channel into Kolibri",
+ "CountryField.locationLabel": "Select all that apply",
+ "CountryField.locationRequiredMessage": "Field is required",
+ "CountryField.noCountriesFound": "No countries found",
+ "Create.ToSRequiredMessage": "Please accept our terms of service and policy",
+ "Create.agreement": "I have read and agree to terms of service and the privacy policy",
+ "Create.backToLoginButton": "Sign in",
+ "Create.basicInformationHeader": "Basic information",
+ "Create.conferenceSourceOption": "Conference",
+ "Create.conferenceSourcePlaceholder": "Name of conference",
+ "Create.confirmPasswordLabel": "Confirm password",
+ "Create.contactMessage": "Questions or concerns? Please email us at content@learningequality.org",
+ "Create.conversationSourceOption": "Conversation with Learning Equality",
+ "Create.createAnAccountTitle": "Create an account",
+ "Create.creatingExercisesUsageOption": "Creating exercises",
+ "Create.emailExistsMessage": "An account with this email already exists",
+ "Create.errorsMessage": "Please fix the errors below",
+ "Create.fieldRequiredMessage": "Field is required",
+ "Create.findingUsageOption": "Finding and adding additional content sources",
+ "Create.finishButton": "Finish",
+ "Create.firstNameLabel": "First name",
+ "Create.forumSourceOption": "Learning Equality community forum",
+ "Create.githubSourceOption": "Learning Equality GitHub",
+ "Create.lastNameLabel": "Last name",
+ "Create.locationLabel": "Where do you plan to use Kolibri Studio? (check all that apply)",
+ "Create.newsletterSourceOption": "Learning Equality newsletter",
+ "Create.organizationSourceOption": "Organization",
+ "Create.organizationSourcePlaceholder": "Name of organization",
+ "Create.organizingUsageOption": "Organizing or aligning existing materials",
+ "Create.otherSourceOption": "Other",
+ "Create.otherSourcePlaceholder": "Please describe",
+ "Create.otherUsageOption": "Other",
+ "Create.otherUsagePlaceholder": "Please describe",
+ "Create.passwordLabel": "Password",
+ "Create.passwordMatchMessage": "Passwords don't match",
+ "Create.personalDemoSourceOption": "Personal demo",
+ "Create.registrationFailed": "There was an error registering your account. Please try again",
+ "Create.registrationFailedOffline": "You seem to be offline. Please connect to the internet to create an account.",
+ "Create.sequencingUsageOption": "Using prerequisites to put materials in a sequence",
+ "Create.sharingUsageOption": "Sharing materials publicly",
+ "Create.socialMediaSourceOption": "Social media",
+ "Create.sourceLabel": "How did you hear about us?",
+ "Create.sourcePlaceholder": "Select one",
+ "Create.storingUsageExample": "e.g. 500MB",
+ "Create.storingUsageOption": "Storing materials for private or local use",
+ "Create.storingUsagePlaceholder": "How much storage do you need?",
+ "Create.taggingUsageOption": "Tagging content sources for discovery",
+ "Create.usageLabel": "How do you plan on using Kolibri Studio (check all that apply)",
+ "Create.viewPrivacyPolicyLink": "View Privacy Policy",
+ "Create.viewToSLink": "View Terms of Service",
+ "Create.websiteSourceOption": "Learning Equality website",
+ "CurrentTopicView.COMFORTABLE_VIEW": "Comfortable view",
+ "CurrentTopicView.COMPACT_VIEW": "Compact view",
+ "CurrentTopicView.DEFAULT_VIEW": "Default view",
+ "CurrentTopicView.addButton": "Add",
+ "CurrentTopicView.addExercise": "New exercise",
+ "CurrentTopicView.addTopic": "New folder",
+ "CurrentTopicView.copiedItems": "Copy operation complete",
+ "CurrentTopicView.copiedItemsToClipboard": "Copied to clipboard",
+ "CurrentTopicView.copySelectedButton": "Copy to clipboard",
+ "CurrentTopicView.copyToClipboardButton": "Copy to clipboard",
+ "CurrentTopicView.creatingCopies": "Copying...",
+ "CurrentTopicView.deleteSelectedButton": "Delete",
+ "CurrentTopicView.duplicateSelectedButton": "Make a copy",
+ "CurrentTopicView.editButton": "Edit",
+ "CurrentTopicView.editSelectedButton": "Edit",
+ "CurrentTopicView.importFromChannels": "Import from channels",
+ "CurrentTopicView.moveSelectedButton": "Move",
+ "CurrentTopicView.optionsButton": "Options",
+ "CurrentTopicView.removedItems": "Sent to trash",
+ "CurrentTopicView.selectAllLabel": "Select all",
+ "CurrentTopicView.selectionCount": "{topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}",
+ "CurrentTopicView.undo": "Undo",
+ "CurrentTopicView.uploadFiles": "Upload files",
+ "CurrentTopicView.viewModeTooltip": "View",
+ "DeleteAccountForm.cancelButton": "Cancel",
+ "DeleteAccountForm.deleteAccountConfirmationPrompt": "Are you sure you want to permanently delete your account? This cannot be undone",
+ "DeleteAccountForm.deleteAccountEnterEmail": "Enter your email address to continue",
+ "DeleteAccountForm.deleteAccountLabel": "Delete account",
+ "DeleteAccountForm.deletionFailed": "Failed to delete account",
+ "DeleteAccountForm.deletionFailedText": "Failed to delete your account. Please contact us here: https://community.learningequality.org.",
+ "DeleteAccountForm.emailAddressLabel": "Email address",
+ "DeleteAccountForm.emailInvalidText": "Email does not match your account email",
+ "DeleteAccountForm.fieldRequired": "Field is required",
+ "Details.AVERAGE": "Average",
+ "Details.LARGE": "Large",
+ "Details.SMALL": "Small",
+ "Details.VERY_LARGE": "Very large",
+ "Details.VERY_SMALL": "Very small",
+ "Details.aggregatorToolTip": "Website or organization hosting the content collection but not necessarily the creator or copyright holder",
+ "Details.aggregatorsLabel": "Aggregators",
+ "Details.assessmentsIncludedText": "Assessments",
+ "Details.authorToolTip": "Person or organization who created this content",
+ "Details.authorsLabel": "Authors",
+ "Details.categoriesHeading": "Categories",
+ "Details.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
+ "Details.coachHeading": "Resources for coaches",
+ "Details.containsContentHeading": "Contains content from",
+ "Details.containsHeading": "Contains",
+ "Details.copyrightHoldersLabel": "Copyright holders",
+ "Details.creationHeading": "Created on",
+ "Details.currentVersionHeading": "Published version",
+ "Details.languagesHeading": "Languages",
+ "Details.levelsHeading": "Levels",
+ "Details.licensesLabel": "Licenses",
+ "Details.primaryLanguageHeading": "Primary language",
+ "Details.providerToolTip": "Organization that commissioned or is distributing the content",
+ "Details.providersLabel": "Providers",
+ "Details.publishedHeading": "Published on",
+ "Details.resourceHeading": "Total resources",
+ "Details.sampleFromChannelHeading": "Sample content from this channel",
+ "Details.sampleFromTopicHeading": "Sample content from this topic",
+ "Details.sizeHeading": "Channel size",
+ "Details.sizeText": "{text} ({size})",
+ "Details.subtitlesHeading": "Captions and subtitles",
+ "Details.tagsHeading": "Common tags",
+ "Details.tokenHeading": "Channel token",
+ "Details.unpublishedText": "Unpublished",
+ "DetailsTabView.aggregatorLabel": "Aggregator",
+ "DetailsTabView.aggregatorToolTip": "Website or org hosting the content collection but not necessarily the creator or copyright holder",
+ "DetailsTabView.assessmentOptionsLabel": "Assessment options",
+ "DetailsTabView.audienceHeader": "Audience",
+ "DetailsTabView.authorLabel": "Author",
+ "DetailsTabView.authorToolTip": "Person or organization who created this content",
+ "DetailsTabView.basicInfoHeader": "Basic information",
+ "DetailsTabView.completionLabel": "Completion",
+ "DetailsTabView.copyrightHolderLabel": "Copyright holder",
+ "DetailsTabView.descriptionLabel": "Description",
+ "DetailsTabView.detectedImportText": "{count, plural,\n =1 {# resource has view-only permission}\n other {# resources have view-only permission}}",
+ "DetailsTabView.importedFromButtonText": "Imported from {channel}",
+ "DetailsTabView.languageChannelHelpText": "Leave blank to use the channel language",
+ "DetailsTabView.languageHelpText": "Leave blank to use the folder language",
+ "DetailsTabView.noTagsFoundText": "No results found for \"{text}\". Press 'Enter' key to create a new tag",
+ "DetailsTabView.providerLabel": "Provider",
+ "DetailsTabView.providerToolTip": "Organization that commissioned or is distributing the content",
+ "DetailsTabView.randomizeQuestionLabel": "Randomize question order for learners",
+ "DetailsTabView.sourceHeader": "Source",
+ "DetailsTabView.tagsLabel": "Tags",
+ "DetailsTabView.thumbnailHeader": "Thumbnail",
+ "DetailsTabView.titleLabel": "Title",
+ "Diff.negativeSign": "-",
+ "Diff.positiveSign": "+",
+ "DiffTable.headerDiff": "Net changes",
+ "DiffTable.headerLive": "Live",
+ "DiffTable.headerStaged": "Staged",
+ "DiffTable.headerType": "Type",
+ "DiffTable.typeAudios": "Audios",
+ "DiffTable.typeDocuments": "Documents",
+ "DiffTable.typeExercises": "Exercises",
+ "DiffTable.typeFileSize": "File size",
+ "DiffTable.typeHtml5Apps": "HTML5 apps",
+ "DiffTable.typeSlideshows": "Slideshows",
+ "DiffTable.typeTopics": "Folders",
+ "DiffTable.typeVersion": "API version",
+ "DiffTable.typeVideos": "Videos",
+ "EditList.selectAllLabel": "Select all",
+ "EditListItem.questionCount": "{count, plural,\n =1 {# question}\n other {# questions}}",
+ "EditModal.addTopic": "Add new folder",
+ "EditModal.addTopicsHeader": "New folder",
+ "EditModal.cancelUploadsButton": "Exit",
+ "EditModal.closeWithoutSavingButton": "Close without saving",
+ "EditModal.createExerciseHeader": "New exercise",
+ "EditModal.dismissDialogButton": "Cancel",
+ "EditModal.editFilesHeader": "Edit files",
+ "EditModal.editingDetailsHeader": "Edit details",
+ "EditModal.finishButton": "Finish",
+ "EditModal.invalidNodesFound": "{count, plural,\n =1 {# incomplete resource found}\n other {# incomplete resources found}}",
+ "EditModal.invalidNodesFoundText": "Incomplete resources will not be published until these errors are resolved",
+ "EditModal.keepEditingButton": "Keep editing",
+ "EditModal.loadErrorText": "Failed to load content",
+ "EditModal.okButton": "OK",
+ "EditModal.saveAnywaysButton": "Exit anyway",
+ "EditModal.saveFailedHeader": "Save failed",
+ "EditModal.saveFailedText": "There was a problem saving your content",
+ "EditModal.uploadButton": "Upload more",
+ "EditModal.uploadFilesHeader": "Upload files",
+ "EditModal.uploadInProgressHeader": "Upload in progress",
+ "EditModal.uploadInProgressText": "Uploads that are in progress will be lost if you exit",
+ "EditSearchModal.cancelAction": "Cancel",
+ "EditSearchModal.changesSavedSnackbar": "Changes saved",
+ "EditSearchModal.editSavedSearchTitle": "Edit search title",
+ "EditSearchModal.fieldRequired": "Field is required",
+ "EditSearchModal.saveChangesAction": "Save",
+ "EditSearchModal.searchTitleLabel": "Search title",
+ "EditView.details": "Details",
+ "EditView.editingMultipleCount": "Editing details for {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}",
+ "EditView.errorBannerText": "Please provide the required information",
+ "EditView.invalidFieldsToolTip": "Some required information is missing",
+ "EditView.noItemsToEditText": "Please select resources or folders to edit",
+ "EditView.preview": "Preview",
+ "EditView.questions": "Questions",
+ "EditView.related": "Related",
+ "EmailField.emailLabel": "Email",
+ "EmailField.emailRequiredMessage": "Field is required",
+ "EmailField.validEmailMessage": "Please enter a valid email",
+ "ExpandableList.less": "Show less",
+ "ExpandableList.more": "Show more ({more})",
+ "FilePreview.exitFullscreen": "Exit fullscreen",
+ "FilePreview.fullscreenModeText": "Fullscreen mode",
+ "FilePreview.viewFullscreen": "View fullscreen",
+ "FileStatusText.selectFile": "Select file",
+ "FileStorage.requestStorage": "Request storage",
+ "FileStorage.storageFull": "Storage limit reached",
+ "FileStorage.storageFullWithSize": "Total storage limit reached: {used} of {total}",
+ "FileStorage.storageLow": "Storage is running low",
+ "FileStorage.storageLowWithSize": "Total storage is running low: {used} of {total}",
+ "FileStorage.storageUsed": "Total storage used: {used} of {total}",
+ "FileUpload.fileError": "Unsupported file type",
+ "FileUpload.filesHeader": "Preview files",
+ "FileUpload.noFileText": "Missing files",
+ "FileUploadDefault.acceptsHelp": "Supported file types: {extensions}",
+ "FileUploadDefault.chooseFilesButton": "Select files",
+ "FileUploadDefault.dropHereText": "Drag and drop your files here, or select your files manually",
+ "FileUploadDefault.uploadToText": "Upload to '{title}'",
+ "FileUploadItem.removeFileButton": "Remove",
+ "FileUploadItem.retryUpload": "Retry upload",
+ "FileUploadItem.unknownFile": "Unknown filename",
+ "FileUploadItem.uploadButton": "Select file",
+ "FileUploadItem.uploadFailed": "Upload failed",
+ "ForgotPassword.forgotPasswordFailed": "Failed to send a password reset link. Please try again.",
+ "ForgotPassword.forgotPasswordPrompt": "Please enter your email address to receive instructions for resetting your password",
+ "ForgotPassword.forgotPasswordTitle": "Reset your password",
+ "ForgotPassword.submitButton": "Submit",
+ "FormulasMenu.btnLabelInsert": "Insert",
+ "FormulasMenu.formulasMenuTitle": "Special characters",
+ "FullNameForm.cancelAction": "Cancel",
+ "FullNameForm.changesSavedMessage": "Changes saved",
+ "FullNameForm.editNameHeader": "Edit full name",
+ "FullNameForm.failedToSaveMessage": "Failed to save changes",
+ "FullNameForm.fieldRequired": "Field is required",
+ "FullNameForm.firstNameLabel": "First name",
+ "FullNameForm.lastNameLabel": "Last name",
+ "FullNameForm.saveChangesAction": "Save changes",
+ "GenericError.backToHomeAction": "Back to home",
+ "GenericError.genericErrorDetails": "Try refreshing this page or going back to the home page",
+ "GenericError.genericErrorHeader": "Sorry, something went wrong",
+ "GenericError.helpByReportingAction": "Help us by reporting this error",
+ "GenericError.refreshAction": "Refresh",
+ "HintsEditor.hintsLabel": "Hints",
+ "HintsEditor.newHintBtnLabel": "New hint",
+ "HintsEditor.noHintsPlaceholder": "Question has no hints",
+ "ImageOnlyThumbnail.thumbnail": "{title} thumbnail",
+ "ImagesMenu.acceptsText": "Supported file types: {acceptedFormats}",
+ "ImagesMenu.altTextHint": "The image description is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
+ "ImagesMenu.altTextLabel": "Image description",
+ "ImagesMenu.btnLabelCancel": "Cancel",
+ "ImagesMenu.btnLabelInsert": "Insert",
+ "ImagesMenu.currentImageDefaultText": "Current image",
+ "ImagesMenu.defaultDropText": "Drag and drop an image here, or upload manually",
+ "ImagesMenu.imageHeader": "Upload image",
+ "ImagesMenu.selectFile": "Select file",
+ "ImagesMenu.selectFileButton": "Select file",
+ "ImportFromChannelsModal.addButton": "Add",
+ "ImportFromChannelsModal.addedText": "Added",
+ "ImportFromChannelsModal.importAction": "Import",
+ "ImportFromChannelsModal.importTitle": "Import from other channels",
+ "ImportFromChannelsModal.removeButton": "Remove",
+ "ImportFromChannelsModal.resourcesAddedSnackbar": "{count, number} {count, plural, one {resource selected} other {resources selected}}",
+ "ImportFromChannelsModal.resourcesRemovedSnackbar": "{count, number} {count, plural, one {resource removed} other {resources removed}}",
+ "ImportFromChannelsModal.resourcesSelected": "{count, number} {count, plural, one {resource selected} other {resources selected}}",
+ "ImportFromChannelsModal.reviewAction": "Review",
+ "ImportFromChannelsModal.reviewTitle": "Resource selection",
+ "InfoModal.close": "Close",
+ "LanguageDropdown.labelText": "Language",
+ "LanguageDropdown.languageItemText": "{language} ({code})",
+ "LanguageDropdown.languageRequired": "Field is required",
+ "LanguageDropdown.noDataText": "Language not found",
+ "LanguageFilter.languageLabel": "Languages",
+ "LanguageFilter.noMatchingLanguageText": "No language matches the search",
+ "LanguageSwitcherList.showMoreLanguagesSelector": "More languages",
+ "LanguageSwitcherModal.cancelAction": "Cancel",
+ "LanguageSwitcherModal.changeLanguageModalHeader": "Change language",
+ "LanguageSwitcherModal.confirmAction": "Confirm",
+ "LicenseDropdown.learnMoreButton": "Learn More",
+ "LicenseDropdown.licenseDescriptionLabel": "License description",
+ "LicenseDropdown.licenseInfoHeader": "About licenses",
+ "LicenseDropdown.licenseLabel": "License",
+ "Main.TOSLink": "Terms of service",
+ "Main.copyright": "Ā© {year} Learning Equality",
+ "Main.createAccountButton": "Create an account",
+ "Main.forgotPasswordLink": "Forgot your password?",
+ "Main.guestModeLink": "Explore without an account",
+ "Main.kolibriStudio": "Kolibri Studio",
+ "Main.loginFailed": "Email or password is incorrect",
+ "Main.loginFailedOffline": "You seem to be offline. Please connect to the internet before signing in.",
+ "Main.loginToProceed": "You must sign in to view that page",
+ "Main.passwordLabel": "Password",
+ "Main.privacyPolicyLink": "Privacy policy",
+ "Main.signInButton": "Sign in",
+ "MainNavigationDrawer.administrationLink": "Administration",
+ "MainNavigationDrawer.changeLanguage": "Change language",
+ "MainNavigationDrawer.channelsLink": "Channels",
+ "MainNavigationDrawer.copyright": "Ā© {year} Learning Equality",
+ "MainNavigationDrawer.giveFeedback": "Give feedback",
+ "MainNavigationDrawer.helpLink": "Help and support",
+ "MainNavigationDrawer.logoutLink": "Sign out",
+ "MainNavigationDrawer.settingsLink": "Settings",
+ "MarkdownEditor.bold": "Bold (Ctrl+B)",
+ "MarkdownEditor.formulas": "Insert formula (Ctrl+F)",
+ "MarkdownEditor.image": "Insert image (Ctrl+P)",
+ "MarkdownEditor.italic": "Italic (Ctrl+I)",
+ "MarkdownEditor.minimize": "Minimize (Ctrl+M)",
+ "MarkdownImageField.editImageOption": "Edit",
+ "MarkdownImageField.removeImageOption": "Remove",
+ "MarkdownImageField.resizeImageOption": "Resize",
+ "MasteryCriteriaGoal.labelText": "Goal",
+ "MasteryCriteriaMofNFields.mHint": "Correct answers needed",
+ "MasteryCriteriaMofNFields.nHint": "Recent answers",
+ "MessageLayout.backToLogin": "Continue to sign-in page",
+ "MoveModal.addTopic": "Add new folder",
+ "MoveModal.cancel": "Cancel",
+ "MoveModal.emptyTopicText": "No resources found",
+ "MoveModal.goToLocationButton": "Go to location",
+ "MoveModal.moveHere": "Move here",
+ "MoveModal.moveItems": "Move {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}} into:",
+ "MoveModal.movedMessage": "Moved to {title}",
+ "MoveModal.resourcesCount": "{count, plural,\n =1 {# resource}\n other {# resources}}",
+ "MoveModal.topicCreatedMessage": "New folder created",
+ "MultiSelect.noItemsFound": "No items found",
+ "NewTopicModal.cancel": "Cancel",
+ "NewTopicModal.create": "Create",
+ "NewTopicModal.createTopic": "Create new folder",
+ "NewTopicModal.topicTitle": "Folder title",
+ "NewTopicModal.topicTitleRequired": "Folder title is required",
+ "NodePanel.emptyChannelSubText": "Create, upload, or import resources from other channels",
+ "NodePanel.emptyChannelText": "Click \"ADD\" to start building your channel",
+ "NodePanel.emptyTopicText": "Nothing in this folder yet",
+ "NodePanel.emptyViewOnlyChannelText": "Nothing in this channel yet",
+ "NodeTreeNavigation.noResourcesDefaultText": "No resources found",
+ "OfflineText.offlineIndicatorText": "Offline",
+ "OfflineText.offlineText": "You seem to be offline. Your changes will be saved once your connection is back.",
+ "PageNotFoundError.backToHomeAction": "Back to home",
+ "PageNotFoundError.pageNotFoundDetails": "Sorry, that page does not exist",
+ "PageNotFoundError.pageNotFoundHeader": "Page not found",
+ "PasswordField.fieldRequiredMessage": "Field is required",
+ "PasswordField.passwordLabel": "Password",
+ "PasswordInstructionsSent.passwordInstructionsHeader": "Instructions sent. Thank you!",
+ "PasswordInstructionsSent.passwordInstructionsText": "If there is already an account with the email address provided, you should receive the instructions shortly. If you don't see an email from us, please check your spam folder.",
+ "PermissionsError.goToHomePageAction": "Go to home page",
+ "PermissionsError.permissionDeniedHeader": "Did you forget to sign in?",
+ "PoliciesModal.checkboxText": "I have agreed to the above terms",
+ "PoliciesModal.closeButton": "Close",
+ "PoliciesModal.continueButton": "Continue",
+ "PoliciesModal.lastUpdated": "Last updated {date}",
+ "PrivacyPolicyModal.privacyHeader": "Privacy policy",
+ "PrivacyPolicyModal.updatedPrivacyHeader": "Updated privacy policy",
+ "ProgressBar.progressText": "{percent}%",
+ "ProgressModal.defaultErrorText": "Last attempt to publish failed",
+ "ProgressModal.lastPublished": "Published {last_published}",
+ "ProgressModal.publishHeader": "Publishing channel",
+ "ProgressModal.syncError": "Last attempt to sync failed",
+ "ProgressModal.syncHeader": "Syncing resources",
+ "ProgressModal.syncedSnackbar": "Resources synced",
+ "ProgressModal.unpublishedText": "Unpublished",
+ "PublishModal.cancelButton": "Cancel",
+ "PublishModal.descriptionDescriptionTooltip": "This description will be shown to Kolibri admins before they update channel versions",
+ "PublishModal.descriptionRequiredMessage": "Please describe what's new in this version before publishing",
+ "PublishModal.incompleteCount": "{count, plural, =1 {# incomplete resource} other {# incomplete resources}}",
+ "PublishModal.incompleteInstructions": "Click 'Continue' to confirm that you would like to publish anyway.",
+ "PublishModal.incompleteWarning": "Incomplete resources will not be published and made available for download in Kolibri.",
+ "PublishModal.nextButton": "Continue",
+ "PublishModal.publishButton": "Publish",
+ "PublishModal.publishMessageLabel": "Describe what's new in this channel version",
+ "PublishModal.versionDescriptionLabel": "Version description",
+ "RelatedResourcesList.removeBtnLabel": "Remove",
+ "RelatedResourcesTab.addNextStepBtnLabel": "Add next step",
+ "RelatedResourcesTab.addPreviousStepBtnLabel": "Add previous step",
+ "RelatedResourcesTab.dialogCloseBtnLabel": "Close",
+ "RelatedResourcesTab.nextStepsExplanation": "Recommended resources that build on skills or concepts learned in this resource",
+ "RelatedResourcesTab.nextStepsTitle": "Next steps",
+ "RelatedResourcesTab.previewHelpText": "Related resources are displayed as recommendations when learners engage with this resource",
+ "RelatedResourcesTab.previousStepsExplanation": "Recommended resources that introduce skills or concepts needed in order to use this resource",
+ "RelatedResourcesTab.previousStepsTitle": "Previous steps",
+ "RelatedResourcesTab.removeNextStepBtnLabel": "Remove next step",
+ "RelatedResourcesTab.removePreviousStepBtnLabel": "Remove previous step",
+ "RelatedResourcesTab.removedNextStepSnackbar": "Removed next step",
+ "RelatedResourcesTab.removedPreviousStepSnackbar": "Removed previous step",
+ "RelatedResourcesTab.resourcePreviewDialogHelpText": "Related resources in Kolibri display as recommendations alongside the resource that a learner is currently engaging with",
+ "RelatedResourcesTab.resourcePreviewDialogTitle": "Related resources",
+ "RelatedResourcesTab.showPreviewBtnLabel": "Show me",
+ "RelatedResourcesTab.tooManyNextStepsWarning": "Limit the number of next steps to create a more guided learning experience",
+ "RelatedResourcesTab.tooManyPreviousStepsWarning": "Limit the number of previous steps to create a more guided learning experience",
+ "ReportErrorModal.closeAction": "Close",
+ "ReportErrorModal.emailDescription": "Contact the support team with your error details and weāll do our best to help.",
+ "ReportErrorModal.emailPrompt": "Send an email to the developers",
+ "ReportErrorModal.errorDetailsHeader": "Error details",
+ "ReportErrorModal.forumPostingTips": "Include a description of what you were trying to do and what you clicked on when the error appeared.",
+ "ReportErrorModal.forumPrompt": "Visit the community forums",
+ "ReportErrorModal.forumUseTips": "Search the community forum to see if others encountered similar issues. If there are none reported, please open a new forum post and paste the error details below inside so we can rectify the error in a future version of Kolibri Studio.",
+ "ReportErrorModal.reportErrorHeader": "Report Error",
+ "RequestForm.approximatelyHowManyResourcesLabel": "Approximately how many individual resources are you planning to upload?",
+ "RequestForm.audiencePlaceholder": "In-school learners, adult learners, teachers, etc",
+ "RequestForm.authorLabel": "Who is the author (creator), curator (organizer), and/or aggregator (maintainer) of your content? Please specify",
+ "RequestForm.averageSizeOfResourceLabel": "Average size of each resource",
+ "RequestForm.coupleMonthsLabel": "1-2 months",
+ "RequestForm.explainNeedsInDetailLabel": "Please write a paragraph explaining your needs and use case for Kolibri Studio, and how it will integrate into your programs. Include information about who is curating, deploying, and using the content. Is this work being coordinated by an organization, as part of an educational program? Include justification for the additional space being requested and explanation of the time sensitive nature of your request.",
+ "RequestForm.fieldRequiredText": "Field is required",
+ "RequestForm.forProfitLabel": "For-profit or social enterprise company",
+ "RequestForm.grassrootsLabel": "Grassroots and/or volunteer initiative",
+ "RequestForm.howAreYouUsingYourContentLabel": "How are you using your content?",
+ "RequestForm.howOftenImportedToKolibriLabel": "How many times will this content be imported from Studio into new Kolibri installations per month, on average?",
+ "RequestForm.intendedAudienceLabel": "Who is the intended audience for your channel? How big is your audience?",
+ "RequestForm.kindOfContentQuestionLabel": "What types of resources do you plan to upload? Please specify",
+ "RequestForm.largeIntlNgoLabel": "Larger international NGOs or government agencies",
+ "RequestForm.learnMoreButton": "Learn More",
+ "RequestForm.licenseInfoHeader": "About licenses",
+ "RequestForm.licensingQuestionLabel": "What is the licensing of the content you are uploading? (Check all that apply)",
+ "RequestForm.mediumNgoLabel": "Medium-sized NGO with budget < $500k",
+ "RequestForm.natureOfYourContentLabel": "Nature of your content",
+ "RequestForm.notAffiliatedLabel": "I am not affiliated with an organization for this work",
+ "RequestForm.numberOfResourcesPlaceholder": "Number of resources",
+ "RequestForm.oneWeekLabel": "1 week",
+ "RequestForm.organizationNamePlaceholder": "Organization name",
+ "RequestForm.organizationalAffiliationLabel": "Organizational affiliation",
+ "RequestForm.otherLabel": "Other",
+ "RequestForm.pasteLinkPlaceholder": "Paste link here",
+ "RequestForm.provideSampleLinkLabel": "Please provide a link to a sample of your content (on Kolibri Studio or from source site)",
+ "RequestForm.requestFailed": "Unable to send request. Please try again.",
+ "RequestForm.requestSent": "Your storage request has been submitted for processing.",
+ "RequestForm.responsePlaceholder": "Response",
+ "RequestForm.selectAllThatApplyPlaceholder": "Select all that apply",
+ "RequestForm.sendRequestAction": "Send request",
+ "RequestForm.sixPlusMonthsLabel": "6+ months",
+ "RequestForm.sizePlaceholder": "Size",
+ "RequestForm.smallNgoLabel": "Small NGO with annual budget < $25k",
+ "RequestForm.storageAmountRequestedPlaceholder": "Amount requested (e.g. 10GB)",
+ "RequestForm.targetRegionsLabel": "Target region(s) for your content (if applicable)",
+ "RequestForm.threeToSixMonthsLabel": "3-6 months",
+ "RequestForm.timelineLabel": "To better understand the time sensitive nature of your request, please indicate an approximate timeline by when you need this additional storage:",
+ "RequestForm.twoToFourWeeksLabel": "2-4 weeks",
+ "RequestForm.typeOfContentPlaceholder": "Types of resources",
+ "RequestForm.typeOfOrganizationLabel": "What type of organization or group is coordinating the use of Kolibri (if applicable)?",
+ "RequestForm.unknownLabel": "Unknown",
+ "RequestForm.uploadingOnBehalfLabel": "I am uploading content on behalf of:",
+ "RequestForm.usageLabel": "Tell us more about your use of Kolibri",
+ "RequestForm.whoCanUseContentLabel": "Who can use your content?",
+ "RequestForm.willYouMakeYourChannelPublicLabel": "If the content is openly licensed, would you be willing to consider making your channels public to other Kolibri users if requested in the future?",
+ "RequestNewActivationLink.activationExpiredText": "This activation link has been used already or has expired.",
+ "RequestNewActivationLink.activationExpiredTitle": "Activation failed",
+ "RequestNewActivationLink.activationRequestFailed": "Failed to send a new activation link. Please try again.",
+ "RequestNewActivationLink.submitButton": "Submit",
+ "ResetLinkExpired.requestNewLink": "Request a new password reset link",
+ "ResetLinkExpired.resetExpiredText": "This password reset link has been used already or has expired.",
+ "ResetLinkExpired.resetExpiredTitle": "Reset link expired",
+ "ResetPassword.passwordConfirmLabel": "Confirm password",
+ "ResetPassword.passwordLabel": "New password",
+ "ResetPassword.passwordMatchMessage": "Passwords don't match",
+ "ResetPassword.resetPasswordFailed": "Failed to reset password. Please try again.",
+ "ResetPassword.resetPasswordPrompt": "Enter and confirm your new password",
+ "ResetPassword.resetPasswordTitle": "Reset your password",
+ "ResetPassword.submitButton": "Submit",
+ "ResetPasswordSuccess.header": "Password reset successfully",
+ "ResetPasswordSuccess.text": "Your password has been reset. You may sign in now.",
+ "ResourcePanel.aggregator": "Aggregator",
+ "ResourcePanel.audience": "Audience",
+ "ResourcePanel.author": "Author",
+ "ResourcePanel.availableFormats": "Available formats",
+ "ResourcePanel.coachResources": "Resources for coaches",
+ "ResourcePanel.copyrightHolder": "Copyright holder",
+ "ResourcePanel.description": "Description",
+ "ResourcePanel.details": "Details",
+ "ResourcePanel.fileSize": "Size",
+ "ResourcePanel.files": "Files",
+ "ResourcePanel.incompleteQuestionError": "{count, plural, one {# incomplete question} other {# incomplete questions}}",
+ "ResourcePanel.language": "Language",
+ "ResourcePanel.license": "License",
+ "ResourcePanel.nextSteps": "Next steps",
+ "ResourcePanel.noCompletionCriteriaError": "Completion criteria are required",
+ "ResourcePanel.noCopyrightHolderError": "Copyright holder is required",
+ "ResourcePanel.noDurationError": "Duration is required",
+ "ResourcePanel.noFilesError": "File is required",
+ "ResourcePanel.noLearningActivityError": "Learning activity is required",
+ "ResourcePanel.noLicenseDescriptionError": "License description is required",
+ "ResourcePanel.noLicenseError": "License is required",
+ "ResourcePanel.noMasteryModelError": "Mastery criteria are required",
+ "ResourcePanel.noQuestionsError": "Exercise is empty",
+ "ResourcePanel.originalChannel": "Imported from",
+ "ResourcePanel.previousSteps": "Previous steps",
+ "ResourcePanel.provider": "Provider",
+ "ResourcePanel.questionCount": "{value, number, integer} {value, plural, one {question} other {questions}}",
+ "ResourcePanel.questions": "Questions",
+ "ResourcePanel.relatedResources": "Related resources",
+ "ResourcePanel.resources": "Resources",
+ "ResourcePanel.showAnswers": "Show answers",
+ "ResourcePanel.source": "Source",
+ "ResourcePanel.subtitles": "Captions and subtitles",
+ "ResourcePanel.tags": "Tags",
+ "ResourcePanel.totalResources": "Total resources",
+ "ResourcePanel.visibleTo": "Visible to",
+ "ResourcesNeededOptions.furtherExplanation": "Please add to the 'Description' field any additional supplies learners will need in order to use this resource",
+ "ResourcesNeededOptions.resourcesNeededLabel": "Requirements",
+ "ReviewSelectionsPage.noResourcesSelected": "No resources selected",
+ "ReviewSelectionsPage.removeAction": "Remove",
+ "ReviewSelectionsPage.resourcesInTopic": "{count, number} {count, plural, one {resource} other {resources}}",
+ "ReviewSelectionsPage.reviewSelectionHeader": "Review selections",
+ "SavedSearchesModal.cancelAction": "Cancel",
+ "SavedSearchesModal.closeButtonLabel": "Close",
+ "SavedSearchesModal.deleteAction": "Delete",
+ "SavedSearchesModal.deleteConfirmation": "Are you sure you want to delete this saved search?",
+ "SavedSearchesModal.deleteSearchTitle": "Delete saved search",
+ "SavedSearchesModal.editAction": "Edit",
+ "SavedSearchesModal.filterCount": "{count, number} {count, plural, one {filter} other {filters}}",
+ "SavedSearchesModal.noSavedSearches": "You do not have any saved searches",
+ "SavedSearchesModal.savedSearchesTitle": "Saved searches",
+ "SavedSearchesModal.searchDeletedSnackbar": "Saved search deleted",
+ "SavingIndicator.lastSaved": "Saved {saved}",
+ "SavingIndicator.savedNow": "Saved just now",
+ "SavingIndicator.savingIndicator": "Saving...",
+ "SearchFilterBar.assessments": "Assessments",
+ "SearchFilterBar.clearAll": "Clear all",
+ "SearchFilterBar.coachContent": "Resources for coaches",
+ "SearchFilterBar.createdAfter": "Added after '{date}'",
+ "SearchFilterBar.topicsHidden": "Folders excluded",
+ "SearchFilters.addedAfterDateLabel": "Added after",
+ "SearchFilters.assessmentsLabel": "Show assessments only",
+ "SearchFilters.channelSourceLabel": "Channel/source",
+ "SearchFilters.channelTypeLabel": "Channel type",
+ "SearchFilters.channelsHeader": "Channels",
+ "SearchFilters.coachContentLabel": "Show resources for coaches",
+ "SearchFilters.filtersHeader": "Filter options",
+ "SearchFilters.hideTopicsLabel": "Hide folders",
+ "SearchFilters.kindLabel": "Format",
+ "SearchFilters.licensesLabel": "License",
+ "SearchOrBrowseWindow.backToBrowseAction": "Back to browse",
+ "SearchOrBrowseWindow.copiedToClipboard": "Copied to clipboard",
+ "SearchOrBrowseWindow.copyFailed": "Failed to copy to clipboard",
+ "SearchOrBrowseWindow.searchAction": "Search",
+ "SearchOrBrowseWindow.searchLabel": "Search for resourcesā¦",
+ "SearchResultsList.failedToLoad": "Failed to load search results",
+ "SearchResultsList.resultsPerPageLabel": "Results per page",
+ "SearchResultsList.saveSearchAction": "Save search",
+ "SearchResultsList.savedSearchesLabel": "View saved searches",
+ "SearchResultsList.searchResultsCount": "{count, number} {count, plural, one {result} other {results}} for '{searchTerm}'",
+ "SearchResultsList.searchSavedSnackbar": "Search saved",
+ "SettingsIndex.accountLabel": "Account",
+ "SettingsIndex.settingsTitle": "Settings",
+ "SettingsIndex.storageLabel": "Storage",
+ "SettingsIndex.usingStudioLabel": "About Studio",
+ "StagingTreePage.backToViewing": "Back to viewing",
+ "StagingTreePage.cancelDeployBtn": "Cancel",
+ "StagingTreePage.channelDeployed": "Channel has been deployed",
+ "StagingTreePage.closeSummaryDetailsDialogBtn": "Close",
+ "StagingTreePage.collapseAllButton": "Collapse all",
+ "StagingTreePage.confirmDeployBtn": "Deploy channel",
+ "StagingTreePage.deploy": "Deploy",
+ "StagingTreePage.deployChannel": "Deploy channel",
+ "StagingTreePage.deployDialogDescription": "You are about to replace all live resources with staged resources.",
+ "StagingTreePage.emptyChannelSubText": "No changes to review! The channel contains all the most recent folders and resources.",
+ "StagingTreePage.emptyChannelText": "No resources found",
+ "StagingTreePage.emptyTopicText": "This topic is empty",
+ "StagingTreePage.liveResources": "Live resources",
+ "StagingTreePage.openCurrentLocationButton": "Expand to current folder location",
+ "StagingTreePage.openSummaryDetailsDialogBtn": "View summary",
+ "StagingTreePage.resourcesCount": "{count, number} {count, plural, one { resource } other { resources }}",
+ "StagingTreePage.reviewMode": "Review mode",
+ "StagingTreePage.stagedResources": "Staged resources",
+ "StagingTreePage.summaryDetailsDialogTitle": "Summary details",
+ "StagingTreePage.topicsCount": "{count, number} {count, plural, one { folder } other { folders }}",
+ "StagingTreePage.totalResources": "Total resources",
+ "StagingTreePage.totalSize": "Total size",
+ "StagingTreePage.viewDetails": "View details",
+ "StatusStrings.noStorageError": "Not enough space",
+ "StatusStrings.uploadFailedError": "Upload failed",
+ "StatusStrings.uploadFileSize": "{uploaded} of {total}",
+ "Storage.hideFormAction": "Close form",
+ "Storage.learnMoreAboutImportingContentFromChannels": "Learn more about how to import resources from other channels",
+ "Storage.requestMoreSpaceHeading": "Request more space",
+ "Storage.requestMoreSpaceMessage": "Please use this form to request additional uploading storage for your Kolibri Studio account. The resources you import from our public library to your channels do not count towards your storage limit.",
+ "Storage.showFormAction": "Open form",
+ "Storage.spaceUsedOfMax": "{qty} of {max}",
+ "Storage.storagePercentageUsed": "{qty}% storage used",
+ "StudioTree.missingTitle": "Missing title",
+ "StudioTree.optionsTooltip": "Options",
+ "SubtitlesList.acceptedFormatsTooltip": "Supported formats: {extensions}",
+ "SubtitlesList.addSubtitleText": "Add captions",
+ "SubtitlesList.subtitlesHeader": "Captions and subtitles",
+ "SupplementaryItem.languageText": "{language} ({code})",
+ "SupplementaryItem.retryUpload": "Retry upload",
+ "SupplementaryItem.uploadFailed": "Upload failed",
+ "SupplementaryList.selectFileText": "Select file",
+ "SyncResourcesModal.backButtonLabel": "Back",
+ "SyncResourcesModal.cancelButtonLabel": "Cancel",
+ "SyncResourcesModal.confirmSyncModalExplainer": "You are about to sync and update the following:",
+ "SyncResourcesModal.confirmSyncModalTitle": "Confirm sync",
+ "SyncResourcesModal.confirmSyncModalWarningExplainer": "Warning: this will overwrite any changes you have made to copied or imported resources.",
+ "SyncResourcesModal.continueButtonLabel": "Continue",
+ "SyncResourcesModal.syncButtonLabel": "Sync",
+ "SyncResourcesModal.syncExercisesExplainer": "Update questions, answers, and hints in exercises and quizzes",
+ "SyncResourcesModal.syncExercisesTitle": "Assessment details",
+ "SyncResourcesModal.syncFilesExplainer": "Update all files, including: thumbnails, subtitles, and captions",
+ "SyncResourcesModal.syncFilesTitle": "Files",
+ "SyncResourcesModal.syncModalExplainer": "Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files.",
+ "SyncResourcesModal.syncModalSelectAttributes": "Select what you would like to sync:",
+ "SyncResourcesModal.syncModalTitle": "Sync resources",
+ "SyncResourcesModal.syncResourceDetailsExplainer": "Update information about the resource: learning activity, level, requirements, category, tags, audience, and source",
+ "SyncResourcesModal.syncResourceDetailsTitle": "Resource details",
+ "SyncResourcesModal.syncTitlesAndDescriptionsExplainer": "Update resource titles and descriptions",
+ "SyncResourcesModal.syncTitlesAndDescriptionsTitle": "Titles and descriptions",
+ "TechnicalTextBlock.copiedToClipboardConfirmation": "Copied to clipboard",
+ "TechnicalTextBlock.copiedToClipboardFailure": "Copy to clipboard failed",
+ "TechnicalTextBlock.copyToClipboardButtonPrompt": "Copy to clipboard",
+ "Template.templateString": "You have {count, plural,\n =1 {# node for testing}\n other {# nodes for testing}}",
+ "TermsOfServiceModal.ToSHeader": "Terms of Service",
+ "TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions",
+ "TermsOfServiceModal.acceptableUseItem1": "Will be in strict accordance with these Terms;",
+ "TermsOfServiceModal.acceptableUseItem10": "Will not interfere with, disrupt, or attack any service or network; and",
+ "TermsOfServiceModal.acceptableUseItem11": "Will not be used to create, distribute, or enable material that is - or that facilitates or operates in conjunction with - malware, spyware, adware, or other malicious programs or code.",
+ "TermsOfServiceModal.acceptableUseItem2": "Will comply with all applicable laws and regulations (including, without limitation, all applicable laws regarding online conduct and acceptable content, privacy, data protection, and the transmission of technical data exported from the United States or the country in which you reside);",
+ "TermsOfServiceModal.acceptableUseItem3": "Will not use the Services for any unlawful purposes, to publish illegal content, or in furtherance of illegal activities;",
+ "TermsOfServiceModal.acceptableUseItem4": "Will not transmit any material that is defamatory, offensive or otherwise objectionable in relation to your use of the Service;",
+ "TermsOfServiceModal.acceptableUseItem5": "Will not infringe or misappropriate the intellectual property rights of any third party;",
+ "TermsOfServiceModal.acceptableUseItem6": "Will not overburden Learning Equality's systems, as determined by us in our sole discretion, including but not limited to excessive bandwidth utilization or number of requests;",
+ "TermsOfServiceModal.acceptableUseItem7": "Will not attempt to circumvent your assigned storage quota or other account restrictions through technical or other means;",
+ "TermsOfServiceModal.acceptableUseItem8": "Will not disclose sensitive personal information of others;",
+ "TermsOfServiceModal.acceptableUseItem9": "Will not be used to send spam or bulk unsolicited messages;",
+ "TermsOfServiceModal.acceptableUseP1": "You represent and warrant that your use of the Service:",
+ "TermsOfServiceModal.accountTermsHeader": "Account Terms",
+ "TermsOfServiceModal.accountTermsP1": "When you register for an account on the Service, you agree to provide us with complete and accurate information. You will be solely responsible and liable for any activity that occurs under your username. You are responsible for keeping your account information up-to-date and for keeping your access credentials (password and API token) private and secure.",
+ "TermsOfServiceModal.accountTermsP2": "You are responsible for maintaining the security of your account and any Service-related content, and you are fully responsible for all activities that occur under your account and any other actions taken in connection with the Service. You shall not share or misuse your access credentials. You must immediately notify us of any unauthorized uses of your account, or of any other breach of security. We will not be liable for any acts or omissions by you, including any damages of any kind incurred as a result of such acts or omissions.",
+ "TermsOfServiceModal.accountTermsP3": "Access to and use of the Service is only for those over the age of 13 (or 16 in the European Union). If you are younger than this, you may not register for or use the Service. Any person who registers as a user or provides their personal information to the Service represents that they are 13 years of age or older (or 16 years or older in the European Union).",
+ "TermsOfServiceModal.arbitrationHeader": "Arbitration Agreement",
+ "TermsOfServiceModal.arbitrationP1": "Except for claims for injunctive or equitable relief or claims regarding intellectual property rights (which may be brought in any competent court without the posting of a bond), any dispute arising under the Agreement shall be finally settled in accordance with the Comprehensive Arbitration Rules of the Judicial Arbitration and Mediation Service, Inc. (\"JAMS\") by three arbitrators appointed in accordance with such Rules. The arbitration shall take place in San Diego, California, in the English language and the arbitral decision may be enforced in any court. The prevailing party in any action or proceeding to enforce the Agreement shall be entitled to costs and attorneys' fees.",
+ "TermsOfServiceModal.cancellationHeader": "Cancellation or Termination",
+ "TermsOfServiceModal.cancellationItem1": "You must stop all activities authorized by these Terms, including your use of the Service.",
+ "TermsOfServiceModal.cancellationItem2": "You must not register and create a new account under your name, a fake or borrowed name, or the name of any third party, even if you may be acting on behalf of the third party.",
+ "TermsOfServiceModal.cancellationP1": "We may terminate or restrict your access to all or any part of the Service at any time, with or without cause, with or without notice, effective immediately. We have the right (though not the obligation) to, in our sole discretion, (i) close down an account or remove content due to prolonged inactivity, (ii) refuse or remove any content that, in our reasonable opinion, violates any Learning Equality policy (including our Community Standards) or is in any way harmful or objectionable, or (iii) terminate or deny access to and use of the Service to any individual or entity for any reason. We will have no obligation to provide a refund of any amounts previously paid.",
+ "TermsOfServiceModal.cancellationP2": "If we end your rights to use the Service:",
+ "TermsOfServiceModal.cancellationP3": "In addition to terminating or suspending your account, we reserve the right to take appropriate legal action, including without limitation pursuing civil, criminal, and injunctive action for violating these Terms.",
+ "TermsOfServiceModal.cancellationP4": "All provisions of the Agreement which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity, and limitations of liability.",
+ "TermsOfServiceModal.changesToToSHeader": "Changes to these Terms of Service",
+ "TermsOfServiceModal.changesToToSP1": "We are constantly updating our Service and that means sometimes we have to change the legal terms under which our Service is offered. These Terms may only be modified by a written amendment signed by an authorized executive of Learning Equality, or by the posting by Learning Equality of a revised version. If we make changes that are material, we will let you know by posting on one of our blogs, or by sending you an email or other communication before the changes take effect. The notice will designate a reasonable period of time after which the new terms will take effect. If you disagree with our changes, then you should stop using the Service within the designated notice period, or once the changes become effective. Your continued use of the Service will be subject to the new terms. However, any dispute that arose before the changes shall be governed by the Terms (including the binding individual arbitration clause) that were in place when the dispute arose.",
+ "TermsOfServiceModal.communicationsHeader": "Communications with Learning Equality",
+ "TermsOfServiceModal.communicationsP1": "For contractual purposes, you (1) consent to receive communications from us in an electronic form via the email address you have submitted or via the Service; and (2) agree that all Terms of Service, agreements, notices, disclosures, and other communications that we provide to you electronically satisfy any legal requirement that those communications would satisfy if they were on paper. This section does not affect your non-waivable rights.",
+ "TermsOfServiceModal.communityStandardsHeader": "Community Standards",
+ "TermsOfServiceModal.communityStandardsLink": "Learn more about Studio's community standards",
+ "TermsOfServiceModal.communityStandardsP1": "For more information about the intended use of the Service, and standards around Content, please see our Community Standards page.",
+ "TermsOfServiceModal.definitionsHeader": "Definitions",
+ "TermsOfServiceModal.definitionsP1": "These are the Terms for the web application hosted at https://studio.learningequality.org/, along with any API's or other interfaces it provides (the \"Service\"), controlled and operated by Learning Equality (\"Learning Equality\", \"we\", \"us\" and \"our\"). We are registered as a nonprofit organization in California, USA under EIN 46-2676188, and have our registered office at 9700 Gilman Dr, PMB 323, La Jolla, CA 92093.",
+ "TermsOfServiceModal.definitionsP2": "These Terms describe our commitments to you, and your rights and responsibilities when using the Service. If you breach any of these Terms, your right to access and use of the Service and Service will be terminated. Please read them carefully and reach out to us if you have any questions.",
+ "TermsOfServiceModal.definitionsP3": "\"Content\" refers to media files (such as videos, audio files, HTML5 content, or other materials) that are hosted on the Service, along with their associated descriptive metadata.",
+ "TermsOfServiceModal.definitionsP4": "Throughout these Terms, \"you\" applies to both individuals and entities that access or use the Service. If you are an individual using the Service on behalf of an entity, you represent and warrant that you have the authority to bind that entity to the Agreement and that by using our Service, you are accepting the Agreement on behalf of that entity.",
+ "TermsOfServiceModal.disclaimerP1": "Before using this website, you should read the following important information relating to it. These Terms of Service (\"Terms\") govern your use of this website and form a legally binding agreement between you and us regarding your use of our website.",
+ "TermsOfServiceModal.disclaimerP2": "If, for any reason, you are unable or unwilling to agree to all of these Terms, please immediately discontinue using or attempting to use the service.",
+ "TermsOfServiceModal.disclaimerP3": "By continuing to use the Service you agree to these terms which will bind you.",
+ "TermsOfServiceModal.dmcaHeader": "DMCA Policy",
+ "TermsOfServiceModal.dmcaLink": "Report a violation",
+ "TermsOfServiceModal.dmcaP1": "As we ask others to respect our intellectual property rights, we respect the intellectual property rights of others. If you believe that material located on or associated with the Service violates your copyright, please notify us in accordance with our Digital Millennium Copyright Act (\"DMCA\") Policy. We will respond to all such notices, including as required or appropriate by removing the infringing material or disabling all links to the infringing material. We will terminate a visitor's access to and use of the website if, under appropriate circumstances, the visitor is determined to be a repeat infringer of copyrights or other intellectual property rights. In the case of such termination, we will have no obligation to provide a refund of any payments or other forms of restitution.",
+ "TermsOfServiceModal.indemnificationHeader": "Indemnification",
+ "TermsOfServiceModal.indemnificationP1": "You agree to indemnify and hold harmless Learning Equality, its contractors, and its licensors, and their respective directors, officers, employees, and agents from and against any and all losses, liabilities, demands, damages, costs, claims, and expenses, including attorneys' fees, arising out of or related to your use of the Service, including but not limited to your violation of the Agreement, Content that you upload or author, and any other activities conducted using the Service.",
+ "TermsOfServiceModal.intellectualPropertyHeader": "Intellectual Property Notice",
+ "TermsOfServiceModal.intellectualPropertyP1": "The Agreement does not transfer from Learning Equality to you any Learning Equality or third party intellectual property, and all right, title, and interest in and to such property will remain (as between the parties) solely with Learning Equality. \"Kolibri\", \"Kolibri Studio\", \"Learning Equality\", the Kolibri logo, and all other trademarks, service marks, graphics, and logos used in connection with learningequality.org or the Service, are trademarks or registered trademarks of Learning Equality or Learning Equality's licensors. Other trademarks, service marks, graphics, and logos used in connection with the Service may be the trademarks of other third parties. Your use of the Service grants you no right or license to reproduce or otherwise use any Learning Equality or third party trademarks and any such use may constitute an infringement of the holder's rights",
+ "TermsOfServiceModal.jurisdictionHeader": "Jurisdiction and Applicable Law",
+ "TermsOfServiceModal.jurisdictionP1": "Except to the extent any applicable law provides otherwise, the Agreement and any access to or use of the Service will be governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions. The proper venue for any disputes arising out of or relating to the Agreement and any access to or use of the Service will be the state and federal courts located in San Diego County, California.",
+ "TermsOfServiceModal.liabilityHeader": "Limitation of Liability",
+ "TermsOfServiceModal.liabilityP1": "To the extent legally permitted under the applicable law, Learning Equality shall not be responsible for any loss or damage to you, your customers or third parties caused by failure of the website to function. In no event will Learning Equality be liable for any special, consequential, incidental, or indirect damages (including, without limitation, those resulting from lost profits, cost of substitute goods or services, lost data or business interruption) in connection with the use of the website or Service of in connection with any other claim arising from these Terms of Service. The aggregate liability of Learning Equality arising from or relating to these Terms and the Service, regardless of the form of action or claim (contract, tort or otherwise) and even if you have been advised of the possibility of such damages shall not exceed the amount paid by you during the twelve (12) month period prior to the cause of action. Nothing in these Terms shall limit or exclude Learning Equality liability for gross negligence or for death or personal injury. Applicable law may not allow the exclusion or limitation of incidental or consequential damages, so the above limitation or exclusion may not apply to you.",
+ "TermsOfServiceModal.licensingHeader": "Licensing and Copyright",
+ "TermsOfServiceModal.licensingList1Item1": "Copyright ownership of the Content is retained by the original copyright holder and must be indicated, and license information must be marked so as to accurately reflect the copyright holder's intentions around the distribution and use of that Content.",
+ "TermsOfServiceModal.licensingList1Item2": "If you are not yourself the copyright holder, you must have the rights to distribute the uploaded Content, either through explicit written permission from the copyright holder, or as allowed by the terms of the license under which the Content has been released.",
+ "TermsOfServiceModal.licensingList1Item3": "If you are the copyright holder of the uploaded content, then by marking the Content you upload with a particular license, you are agreeing for the Content to be distributed and used under the terms of that license in perpetuity.",
+ "TermsOfServiceModal.licensingList2Item1": "Descriptive metadata: This includes primary metadata associated with a single piece of Content, for example, titles, descriptions, and other elements which constitute a definitive part of the Content regardless of which system it appears on. These metadata elements will fall under the same copyright and licensing as the Content itself.",
+ "TermsOfServiceModal.licensingList2Item2": "Organizational metadata: This defines how a piece of content may be used, aids with discovery, and places it within some broader structure of relations on the Service, for example, tags, curation into folders (including the titles of those folders), and other elements pertaining to the display and ordering of Content on the system itself. By using the Service, you agree that work you do to generate organizational metadata elements are released into the Public Domain, and may be made available for others to use, without any claim to copyright or restricted licensing. We may also share, leverage and distribute this organizational metadata. This is so that we can benefit others and improve the impact of our platforms.",
+ "TermsOfServiceModal.licensingP1": "The Service allows you to upload and distribute Content. When you do, the following terms apply:",
+ "TermsOfServiceModal.licensingP2": "We follow a policy of making content, including its associated metadata, as open as possible while following the appropriate copyright laws. With this in mind, we distinguish between:",
+ "TermsOfServiceModal.miscellaneousHeader": "Miscellaneous",
+ "TermsOfServiceModal.miscellaneousP1": "The Agreement constitutes the entire agreement between Learning Equality and you concerning the subject matter hereof. If any part of the Agreement is held invalid or unenforceable, that part will be construed to reflect the parties' original intent, and the remaining portions will remain in full force and effect. A waiver by either party of any term or condition of the Agreement or any breach thereof, in any one instance, will not waive such term or condition or any subsequent breach thereof.",
+ "TermsOfServiceModal.miscellaneousP2": "You may assign your rights under the Agreement to any party that consents to, and agrees to be bound by, its terms and conditions; Learning Equality may assign its rights under the Agreement without condition. The Agreement will be binding upon and will inure to the benefit of the parties, their successors and permitted assigns.",
+ "TermsOfServiceModal.miscellaneousP3": "If you have any questions about the Service or these Terms, please contact us at legal@learningequality.org.",
+ "TermsOfServiceModal.prompt": "Please read these terms and conditions carefully",
+ "TermsOfServiceModal.thirdPartyHeader": "Third Party Content and Third Party Applications",
+ "TermsOfServiceModal.thirdPartyP1": "The links to third party websites, any third party content, and any third party applications may be provided for your convenience and information only. The content on any linked website or in any third party application is not under our control and we are not responsible for the content of linked websites and/or third party applications, including any further links contained in a third party website. We make no representations or warranties in connection with any third party content or third party applications, which at all times and in each instance is provided \"as is.\" Third party applications may be subject to additional policies and conditions or agreements between you and the provider of such third party applications. You agree to fully comply with all such additional policies, conditions and agreements. If you decide to access any third party content, and/or any third party application, you do so entirely at your own risk.",
+ "TermsOfServiceModal.thirdPartyRightsHeader": "Third Party Rights",
+ "TermsOfServiceModal.thirdPartyRightsP1": "Nothing in our Terms is intended to confer on any third party any benefit or any right (under the Contracts (Rights of Third Parties) Act 1999 UK or otherwise) to enforce any provision of our Terms or any agreement entered into in connection with it.",
+ "TermsOfServiceModal.updatedToSHeader": "Updated terms of service",
+ "TermsOfServiceModal.userContentHeader": "User-Generated Content",
+ "TermsOfServiceModal.userContentList1Item1": "We do not endorse any uploaded Content or represent that Content is accurate, useful, or non-harmful. Content could be offensive, indecent, or objectionable; include technical inaccuracies, typographical mistakes, or other errors; or violate or infringe the privacy, publicity rights, intellectual property rights (see our Copyright Infringement and DMCA Policy section to submit copyright complaints), or other proprietary rights of third parties.",
+ "TermsOfServiceModal.userContentList1Item2": "If you upload or author Content, or otherwise make (or allow any third party to make) Content available on the Service, you are entirely responsible for the Content, and any harm resulting from, that Content or your conduct.",
+ "TermsOfServiceModal.userContentList1Item3": "You are responsible for ensuring that you have proper permissions to upload and distribute any and all uploaded Content and for ensuring that the copyright holder and licensing are properly evidenced on the uploaded Content.",
+ "TermsOfServiceModal.userContentList1Item4": "We disclaim any responsibility for any harm resulting from anyone's use or downloading of Content. If you access or use any Content, you are responsible for taking precautions as necessary to protect yourself and your computer systems from viruses, worms, Trojan horses, and other harmful or destructive content.",
+ "TermsOfServiceModal.userContentList1Item5": "We are not a party to, and will have no responsibility or liability for, any communications, transactions, interactions, or disputes between you and the provider of any Content.",
+ "TermsOfServiceModal.userContentList1Item6": "Please note that additional third party terms and conditions may apply to the downloading, copying, or use of Content.",
+ "TermsOfServiceModal.userContentList2Item1": "We do not have any control over those websites and are not responsible for their contents or their use.",
+ "TermsOfServiceModal.userContentList2Item2": "The existence of a link to or from the Service does not represent or imply that we endorse such website.",
+ "TermsOfServiceModal.userContentList2Item3": "You are responsible for taking precautions as necessary to protect yourself and your computer systems from viruses, worms, Trojan horses, and other harmful or destructive content.",
+ "TermsOfServiceModal.userContentList3Item1": "remove or force upgrades of copies of Content that have already been downloaded from the Service, except in cases in which the Kolibri Learning Application is running on a server that is under our control. This may mean that when we delete uploaded content not all copies will be removed.",
+ "TermsOfServiceModal.userContentList3Item2": "remove or change the licensing on old versions of Content that others have made copies of, should you change the licensing on your content and/or request a removal of the Content from us. When a Creative Commons license is applied to a specific version of a piece of Content, the rights conferred to others for distribution and use of that Content cannot be revoked. Whilst we cannot remove or force updates on copies of the Content, we would let you update the license on your own copy of the Content moving forward, and for future versions.",
+ "TermsOfServiceModal.userContentP1": "We have not reviewed, and cannot review, all of the Content (such as, but not limited to, text, photo, video, audio, code, computer software, or other materials) uploaded to or authored using the Service by users or anyone else and are not responsible for any use or effects of such Content. So, for example:",
+ "TermsOfServiceModal.userContentP2": "We also have not reviewed, and cannot review, all of the material made available through the websites and web pages that link to, or are linked from the Service. For example:",
+ "TermsOfServiceModal.userContentP3": "We reserve the right to remove any Content that violates our Terms or for any other reason.",
+ "TermsOfServiceModal.userContentP4": "Please note we cannot:",
+ "TermsOfServiceModal.warrantyHeader": "Disclaimer of Warranties",
+ "TermsOfServiceModal.warrantyHeaderP1": "You acknowledge that the website and the Service is provided \"as is\" and \"as available\", with all faults and without warranty of any kind, and we hereby disclaim all warranties and conditions with respect to the website and Service, either express, implied or statutory, including, but not limited to, any implied warranties and/or conditions of merchantability, of satisfactory quality, of fitness for a particular purpose, of accuracy, and non-infringement of third party rights. Any use of the Service and website is at your own risk. Some jurisdictions do not allow the exclusion of implied warranties, so the above limitations may not apply to you.",
+ "TermsOfServiceModal.yourPrivacyHeader": "Your Privacy",
+ "TermsOfServiceModal.yourPrivacyLink": "Learn more about Studio's privacy policy",
+ "TermsOfServiceModal.yourPrivacyP1": "We take your privacy seriously. Please read our Privacy Policy to see how we collect, use and protect your personal data.",
+ "TextArea.fieldRequiredMessage": "Field is required",
+ "TextField.fieldRequiredMessage": "Field is required",
+ "Thumbnail.thumbnail": "{title} thumbnail",
+ "ThumbnailGenerator.generatedDefaultFilename": "Generated thumbnail",
+ "ThumbnailGenerator.thumbnailGenerationFailedHeader": "Unable to generate thumbnail",
+ "ThumbnailGenerator.thumbnailGenerationFailedText": "There was a problem generating a thumbnail",
+ "TitleStrings.catalogTitle": "Kolibri Content Library Catalog",
+ "TitleStrings.defaultTitle": "Kolibri Studio",
+ "TitleStrings.tabTitle": "{title} - {site}",
+ "ToggleText.less": "Show less",
+ "ToggleText.more": "Show more",
+ "TrashModal.deleteButton": "Delete",
+ "TrashModal.deleteConfirmationCancelButton": "Cancel",
+ "TrashModal.deleteConfirmationDeleteButton": "Delete permanently",
+ "TrashModal.deleteConfirmationHeader": "Permanently delete {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}?",
+ "TrashModal.deleteConfirmationText": "You cannot undo this action. Are you sure you want to continue?",
+ "TrashModal.deleteSuccessMessage": "Permanently deleted",
+ "TrashModal.deletedHeader": "Removed",
+ "TrashModal.restoreButton": "Restore",
+ "TrashModal.selectAllHeader": "Select all",
+ "TrashModal.selectedCountText": "{topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}",
+ "TrashModal.trashEmptySubtext": "Resources removed from this channel will appear here",
+ "TrashModal.trashEmptyText": "Trash is empty",
+ "TrashModal.trashModalTitle": "Trash",
+ "TreeView.closeDrawer": "Close",
+ "TreeView.collapseAllButton": "Collapse all",
+ "TreeView.openCurrentLocationButton": "Expand to current folder location",
+ "TreeView.showSidebar": "Show sidebar",
+ "TreeView.updatedResourcesReadyForReview": "Updated resources are ready for review",
+ "TreeViewBase.apiGenerated": "Generated by API",
+ "TreeViewBase.cancel": "Cancel",
+ "TreeViewBase.channelDeletedSnackbar": "Channel deleted",
+ "TreeViewBase.channelDetails": "View channel details",
+ "TreeViewBase.deleteChannel": "Delete channel",
+ "TreeViewBase.deleteChannelButton": "Delete channel",
+ "TreeViewBase.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
+ "TreeViewBase.deleteTitle": "Delete this channel",
+ "TreeViewBase.editChannel": "Edit channel details",
+ "TreeViewBase.emptyChannelTooltip": "You cannot publish an empty channel",
+ "TreeViewBase.getToken": "Get token",
+ "TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {resource is incomplete and cannot be published} other {resources are incomplete and cannot be published}}",
+ "TreeViewBase.noChangesText": "No changes found in channel",
+ "TreeViewBase.noLanguageSetError": "Channel language is required",
+ "TreeViewBase.openTrash": "Open trash",
+ "TreeViewBase.publishButton": "Publish",
+ "TreeViewBase.publishButtonTitle": "Make this channel available for import into Kolibri",
+ "TreeViewBase.shareChannel": "Share channel",
+ "TreeViewBase.syncChannel": "Sync resources",
+ "TreeViewBase.viewOnly": "View-only",
+ "Uploader.listDelimiter": ", ",
+ "Uploader.maxFileSizeText": "{count, plural,\n =1 {# file will not be uploaded.}\n other {# files will not be uploaded.}} File size must be under {size}",
+ "Uploader.noStorageHeader": "Not enough space",
+ "Uploader.remainingStorage": "Remaining storage: {size}",
+ "Uploader.tooLargeFilesHeader": "Max file size exceeded",
+ "Uploader.unsupportedFilesHeader": "Unsupported files",
+ "Uploader.unsupportedFilesText": "{count, plural,\n =1 {# file will not be uploaded.}\n other {# files will not be uploaded.}} \n {extensionCount, plural,\n =1 {Supported file type is}\n other {Supported file types are}} {extensions}",
+ "Uploader.uploadSize": "Upload is too large: {size}",
+ "UsingStudio.aboutStudio": "About Kolibri Studio",
+ "UsingStudio.aboutStudioText": "Kolibri Studio is undergoing active development, and as such, some changes could cause unexpected behavior or challenges (also known as \"issues\"). If you encounter an issue, please notify us as soon as they occur to help us resolve them. (See below for instructions on how to report issues.)",
+ "UsingStudio.bestPractice1": "When using import and clipboard operations, work with small subsets of folders instead of whole channels at once (especially for large channels).",
+ "UsingStudio.bestPractice2": "It is preferable to create multiple small channels rather than one giant channel with many layers of folders.",
+ "UsingStudio.bestPractice3": "Reload the page to confirm your work has been saved to the server. Use CTRL+R on Linux/Windows or ā+R on Mac.",
+ "UsingStudio.bestPractice5": "It is possible that you will encounter timeout errors in your browser when performing operations like import and sync, on large channels. Don't be alarmed by this error message and do not repeat the same operation again right away. It doesn't mean the operation has failed- Kolibri Studio is still working in the background. Wait a few minutes and reload the page before continuing your edits.",
+ "UsingStudio.bestPractice6": "Compress videos before uploading them (see these instructions).",
+ "UsingStudio.bestPractice7": "PUBLISH periodically and import your channel into Kolibri to preview the content and obtain a local backup copy of your channel.",
+ "UsingStudio.bestPractice9": "Report issues as you encounter them.",
+ "UsingStudio.bestPractices": "Best practices",
+ "UsingStudio.communityStandardsLink": "Community standards",
+ "UsingStudio.issue1": "There have been reports where users have observed the disappearance of changes they've recently made to their channels. The issue seems related to opening multiple tabs of Kolibri Studio, and eventually signing out. We advise that you disable any āMemory Saver/Sleepingā tab browser feature for Kolibri Studio, and reload each tab before signing out. We're actively investigating this issue, so if you run into it, please contact us with as much information as possible.",
+ "UsingStudio.issueLink1": "Reports of disappearing content",
+ "UsingStudio.issuesPageLink": "View all issues",
+ "UsingStudio.notableIssues": "Notable issues",
+ "UsingStudio.policiesLink": "Privacy policy",
+ "UsingStudio.resourcesHeader": "Kolibri Studio resources",
+ "UsingStudio.termsOfServiceLink": "Terms of service",
+ "UsingStudio.userDocsLink": "User guide",
+ "VisibilityDropdown.coach": "Resources are visible only to coaches (teachers, facilitators, administrators)",
+ "VisibilityDropdown.labelText": "Visible to",
+ "VisibilityDropdown.learner": "Resources are visible to anyone",
+ "VisibilityDropdown.visibilityDescription": "Visibility determines what type of Kolibri users can see resources.",
+ "VisibilityDropdown.visibilityHeader": "About resource visibility",
+ "VisibilityDropdown.visibilityRequired": "Field is required",
+ "channelEditVue.errorChooseAtLeastOneCorrectAnswer": "Choose at least one correct answer",
+ "channelEditVue.errorMissingAnswer": "Choose a correct answer",
+ "channelEditVue.errorProvideAtLeastOneCorrectAnswer": "Provide at least one correct answer",
+ "channelEditVue.errorQuestionRequired": "Question is required",
+ "channelEditVue.false": "False",
+ "channelEditVue.questionTypeInput": "Numeric input",
+ "channelEditVue.questionTypeMultipleSelection": "Multiple choice",
+ "channelEditVue.questionTypePerseus": "Perseus",
+ "channelEditVue.questionTypeSingleSelection": "Single choice",
+ "channelEditVue.questionTypeTrueFalse": "True/False",
+ "channelEditVue.true": "True",
+ "formStrings.errorText": "Please fix {count, plural,\n =1 {# error}\n other {# errors}} below",
+ "sharedVue.activityDurationGteOne": "Value must be equal to or greater than 1",
+ "sharedVue.activityDurationRequired": "This field is required",
+ "sharedVue.activityDurationTooLongWarning": "This value is very high. Please make sure this is how long learners should work on the resource for, in order to complete it.",
+ "sharedVue.confirmLogout": "Changes you made may not be saved. Are you sure you want to leave this page?",
+ "sharedVue.copyrightHolderRequired": "Copyright holder is required",
+ "sharedVue.durationRequired": "Duration is required",
+ "sharedVue.fieldRequired": "This field is required",
+ "sharedVue.learningActivityRequired": "Learning activity is required",
+ "sharedVue.licenseDescriptionRequired": "Special permissions license must have a description",
+ "sharedVue.licenseRequired": "License is required",
+ "sharedVue.longActivityGtThirty": "Value must be greater than 30",
+ "sharedVue.longActivityLteOneTwenty": "Value must be equal or less than 120",
+ "sharedVue.masteryModelMGtZero": "Must be at least 1",
+ "sharedVue.masteryModelMLteN": "Must be lesser than or equal to N",
+ "sharedVue.masteryModelMRequired": "Required",
+ "sharedVue.masteryModelMWholeNumber": "Must be a whole number",
+ "sharedVue.masteryModelNGtZero": "Must be at least 1",
+ "sharedVue.masteryModelNRequired": "Required",
+ "sharedVue.masteryModelNWholeNumber": "Must be a whole number",
+ "sharedVue.masteryModelRequired": "Mastery is required",
+ "sharedVue.shortActivityLteThirty": "Value must be equal or less than 30",
+ "sharedVue.titleRequired": "Title is required"
+}
diff --git a/contentcuration/locale/en/LC_MESSAGES/django.po b/contentcuration/locale/en/LC_MESSAGES/django.po
index 6183991018..ddcab55569 100644
--- a/contentcuration/locale/en/LC_MESSAGES/django.po
+++ b/contentcuration/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-16 00:55+0000\n"
+"POT-Creation-Date: 2023-05-10 21:55+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -18,36 +18,8 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: contentcuration/api.py:140
-msgid "Date/Time Created"
-msgstr ""
-
-#: contentcuration/api.py:141 contentcuration/api.py:142
-msgid "Not Available"
-msgstr ""
-
-#: contentcuration/api.py:145
-msgid "Ricecooker Version"
-msgstr ""
-
-#: contentcuration/api.py:150 contentcuration/utils/csv_writer.py:164
-msgid "File Size"
-msgstr ""
-
-#: contentcuration/api.py:161
-msgid "# of {}s"
-msgstr ""
-
-#: contentcuration/api.py:165
-msgid "# of Questions"
-msgstr ""
-
-#: contentcuration/api.py:175
-msgid "# of Subtitles"
-msgstr ""
-
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
-#: contentcuration/settings.py:287
+#: contentcuration/settings.py:290
msgid "Arabic"
msgstr ""
@@ -55,40 +27,40 @@ msgstr ""
msgid "The site is currently in read-only mode. Please try again later."
msgstr ""
-#: contentcuration/models.py:279
+#: contentcuration/models.py:295
msgid "Not enough space. Check your storage under Settings page."
msgstr ""
-#: contentcuration/models.py:294 contentcuration/models.py:301
+#: contentcuration/models.py:308 contentcuration/models.py:315
msgid "Out of storage! Request more space under Settings > Storage."
msgstr ""
-#: contentcuration/models.py:1410
+#: contentcuration/models.py:1730
msgid " (Original)"
msgstr ""
-#: contentcuration/settings.py:285
+#: contentcuration/settings.py:288
msgid "English"
msgstr ""
-#: contentcuration/settings.py:286
+#: contentcuration/settings.py:289
msgid "Spanish"
msgstr ""
-#: contentcuration/settings.py:288
+#: contentcuration/settings.py:291
msgid "French"
msgstr ""
-#: contentcuration/tasks.py:251
-msgid "Unknown error starting task. Please contact support."
+#: contentcuration/settings.py:292
+msgid "Portuguese"
msgstr ""
-#: contentcuration/templates/base.html:83
+#: contentcuration/templates/base.html:38
#: contentcuration/templates/channel_list.html:14
msgid "Kolibri Studio"
msgstr ""
-#: contentcuration/templates/base.html:166
+#: contentcuration/templates/base.html:129
msgid ""
"Contentworkshop.learningequality.org has been deprecated. Please go to studio.learningequality.org "
@@ -104,232 +76,16 @@ msgid "We're sorry, this channel was not found."
msgstr ""
#: contentcuration/templates/channel_not_found.html:24
-#: contentcuration/templates/permissions/open_channel_fail.html:14
#: contentcuration/templates/staging_not_found.html:23
#: contentcuration/templates/unauthorized.html:23
msgid "Go Home"
msgstr ""
-#: contentcuration/templates/exercise_list.html:67
-msgid "Previous"
-msgstr ""
-
-#: contentcuration/templates/exercise_list.html:73
-msgid "current"
-msgstr ""
-
-#: contentcuration/templates/exercise_list.html:77
-msgid "Next"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:206
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:165
-msgid "Language not set"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:212
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:171
-#, python-format
-msgid "This file was generated on %(date)s"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:231
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:189
-msgid "Created"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:232
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:190
-msgid "Last Published"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:233
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:191
-msgid "Unpublished"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:245
-msgid "USING THIS CHANNEL"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:247
-msgid "Copy one of the following into Kolibri to import this channel:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:252
-msgid "Tokens (Recommended):"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:260
-msgid "Channel ID:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:267
-msgid "Channel must be published to import into Kolibri"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:273
-msgid "WHAT'S INSIDE"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:283
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:213
-#, python-format
-msgid "%(count)s Resource"
-msgid_plural "%(count)s Resources"
-msgstr[0] ""
-msgstr[1] ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:304
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:234
-msgid "Includes"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:310
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:236
-msgid "Languages"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:316
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:245
-msgid "Subtitles"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:324
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:254
-msgid "For Educators"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:256
-msgid "Coach Content"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:257
-msgid "Assessments"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:334
-msgid "Content Tags"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:338
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:270
-msgid "No tags found"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:343
-#: contentcuration/templates/export/channel_detail_pdf.html:448
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:274
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:337
-msgid "This channel is empty"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:348
-msgid "SOURCE"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:360
-msgid "This channel features resources created by:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:361
-#: contentcuration/templates/export/channel_detail_pdf.html:379
-#: contentcuration/templates/export/channel_detail_pdf.html:398
-#: contentcuration/templates/export/channel_detail_pdf.html:420
-#: contentcuration/templates/export/channel_detail_pdf.html:440
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:293
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:305
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:317
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:333
-msgid "Information not available"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:378
-msgid "The material in this channel was provided by:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:397
-msgid "Material in this channel was originally hosted by:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:416
-msgid "This channel includes the following licenses:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:439
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:326
-msgid "Copyright Holders:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:192
-msgid "Token:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:203
-msgid "What's Inside"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:239
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:248
-#, python-format
-msgid ""
-"\n"
-" (+ "
-"%(count)s more) \n"
-" "
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:267
-msgid "Most Common Tags"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:280
-msgid "Source Information"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:284
-msgid "Authors:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:288
-#, python-format
-msgid ""
-"\n"
-" (+ %(count)s more) \n"
-" "
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:298
-msgid "Providers:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:301
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:313
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:329
-#, python-format
-msgid ""
-"\n"
-" (+ %(count)s more) \n"
-" "
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:310
-msgid "Aggregators:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-msgid "Licenses:"
-msgstr ""
-
-#: contentcuration/templates/export/csv_email.txt:4
#: contentcuration/templates/export/user_csv_email.txt:4
#: contentcuration/templates/permissions/permissions_email.txt:4
#: contentcuration/templates/registration/activation_email.txt:4
#: contentcuration/templates/registration/activation_needed_email.txt:4
-#: contentcuration/templates/registration/channel_published_email.txt:4
+#: contentcuration/templates/registration/channel_published_email.html:10
#: contentcuration/templates/registration/password_reset_email.txt:3
#: contentcuration/templates/registration/registration_needed_email.txt:4
#: contentcuration/templates/settings/account_deleted_user_email.txt:3
@@ -339,43 +95,6 @@ msgstr ""
msgid "Hello %(name)s,"
msgstr ""
-#: contentcuration/templates/export/csv_email.txt:6
-#, python-format
-msgid "Your csv for %(channel_name)s has finished generating (attached)."
-msgstr ""
-
-#: contentcuration/templates/export/csv_email.txt:8
-#: contentcuration/templates/export/user_csv_email.txt:29
-#: contentcuration/templates/permissions/permissions_email.txt:21
-#: contentcuration/templates/registration/activation_email.txt:12
-#: contentcuration/templates/registration/activation_needed_email.txt:14
-#: contentcuration/templates/registration/channel_published_email.txt:15
-#: contentcuration/templates/registration/password_reset_email.txt:14
-#: contentcuration/templates/registration/registration_needed_email.txt:12
-#: contentcuration/templates/settings/account_deleted_user_email.txt:10
-#: contentcuration/templates/settings/storage_request_email.txt:46
-msgid "Thanks for using Kolibri Studio!"
-msgstr ""
-
-#: contentcuration/templates/export/csv_email.txt:10
-#: contentcuration/templates/export/user_csv_email.txt:31
-#: contentcuration/templates/permissions/permissions_email.txt:23
-#: contentcuration/templates/registration/activation_email.txt:14
-#: contentcuration/templates/registration/activation_needed_email.txt:16
-#: contentcuration/templates/registration/channel_published_email.txt:17
-#: contentcuration/templates/registration/password_reset_email.txt:16
-#: contentcuration/templates/registration/registration_needed_email.txt:14
-#: contentcuration/templates/settings/account_deleted_user_email.txt:12
-#: contentcuration/templates/settings/issue_report_email.txt:26
-#: contentcuration/templates/settings/storage_request_email.txt:48
-msgid "The Learning Equality Team"
-msgstr ""
-
-#: contentcuration/templates/export/csv_email_subject.txt:1
-#, python-format
-msgid "CSV for %(channel_name)s"
-msgstr ""
-
#: contentcuration/templates/export/user_csv_email.txt:6
msgid "Here is the information for your Kolibri Studio account"
msgstr ""
@@ -425,44 +144,65 @@ msgid ""
"If you have any questions or concerns, please email us at %(legal_email)s."
msgstr ""
-#: contentcuration/templates/export/user_csv_email_subject.txt:1
-msgid "Your Kolibri Studio account information"
+#: contentcuration/templates/export/user_csv_email.txt:29
+#: contentcuration/templates/permissions/permissions_email.txt:21
+#: contentcuration/templates/registration/activation_email.txt:12
+#: contentcuration/templates/registration/activation_needed_email.txt:14
+#: contentcuration/templates/registration/channel_published_email.html:23
+#: contentcuration/templates/registration/password_reset_email.txt:14
+#: contentcuration/templates/registration/registration_needed_email.txt:12
+#: contentcuration/templates/settings/account_deleted_user_email.txt:10
+#: contentcuration/templates/settings/storage_request_email.txt:46
+msgid "Thanks for using Kolibri Studio!"
msgstr ""
-#: contentcuration/templates/permissions/open_channel_fail.html:12
-msgid "There was an error opening this channel."
+#: contentcuration/templates/export/user_csv_email.txt:31
+#: contentcuration/templates/permissions/permissions_email.html:118
+#: contentcuration/templates/permissions/permissions_email.txt:23
+#: contentcuration/templates/registration/activation_email.html:111
+#: contentcuration/templates/registration/activation_email.txt:14
+#: contentcuration/templates/registration/activation_needed_email.txt:16
+#: contentcuration/templates/registration/channel_published_email.html:25
+#: contentcuration/templates/registration/password_reset_email.html:111
+#: contentcuration/templates/registration/password_reset_email.txt:16
+#: contentcuration/templates/registration/registration_needed_email.txt:14
+#: contentcuration/templates/registration/welcome_new_user_email.html:172
+#: contentcuration/templates/settings/account_deleted_user_email.txt:12
+#: contentcuration/templates/settings/issue_report_email.txt:26
+#: contentcuration/templates/settings/storage_request_email.txt:48
+msgid "The Learning Equality Team"
msgstr ""
-#: contentcuration/templates/permissions/open_channel_fail.html:13
-msgid "Try running ricecooker again."
+#: contentcuration/templates/export/user_csv_email_subject.txt:1
+msgid "Your Kolibri Studio account information"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:93
-#: contentcuration/templates/registration/activation_email.html:91
-#: contentcuration/templates/registration/password_reset_email.html:91
+#: contentcuration/templates/permissions/permissions_email.html:92
+#: contentcuration/templates/registration/activation_email.html:90
+#: contentcuration/templates/registration/password_reset_email.html:90
msgid "Hello"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:94
+#: contentcuration/templates/permissions/permissions_email.html:93
msgid "has invited you to edit a channel at"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:100
+#: contentcuration/templates/permissions/permissions_email.html:99
#, python-format
msgid "Invititation to %(share_mode)s channel"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:104
+#: contentcuration/templates/permissions/permissions_email.html:103
msgid ""
"Click one of the following links to either accept or decline your invitation:"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:107
-#: contentcuration/templates/permissions/permissions_email.html:109
+#: contentcuration/templates/permissions/permissions_email.html:106
+#: contentcuration/templates/permissions/permissions_email.html:108
msgid "ACCEPT"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:112
+#: contentcuration/templates/permissions/permissions_email.html:111
msgid "DECLINE"
msgstr ""
@@ -502,24 +242,24 @@ msgstr ""
msgid "You've been invited to view %(channel)s"
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:92
-msgid "Welcome to Kolibri! Here is the link to activate your account:"
+#: contentcuration/templates/registration/activation_email.html:91
+msgid "Welcome to Kolibri Studio! Here is the link to activate your account:"
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:101
+#: contentcuration/templates/registration/activation_email.html:100
msgid "Click here to activate your account."
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
msgid "This link is valid for"
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
#, python-format
msgid "%(expiration_days)s days."
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:104
+#: contentcuration/templates/registration/activation_email.html:103
msgid "ACTIVATE"
msgstr ""
@@ -549,29 +289,33 @@ msgstr ""
msgid "Please activate your account by following the link below:"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:6
+#: contentcuration/templates/registration/channel_published_email.html:12
#, python-format
+msgid "%(channel_name)s"
+msgstr ""
+
+#: contentcuration/templates/registration/channel_published_email.html:12
msgid ""
-"%(channel_name)s has finished publishing! Here is the channel token (for "
-"importing it into Kolibri):"
+"has finished publishing! Here is the channel token (for importing it into "
+"Kolibri):"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:8
+#: contentcuration/templates/registration/channel_published_email.html:15
#, python-format
msgid "Token: %(channel_token)s"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:10
+#: contentcuration/templates/registration/channel_published_email.html:17
#, python-format
msgid "ID (for Kolibri version 0.6.0 and below): %(channel_id)s"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:12
+#: contentcuration/templates/registration/channel_published_email.html:20
#, python-format
msgid "Version notes: %(notes)s"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:21
+#: contentcuration/templates/registration/channel_published_email.html:28
msgid ""
"You are receiving this email because you are subscribed to this channel."
msgstr ""
@@ -603,25 +347,25 @@ msgstr ""
msgid "Request a new password reset."
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:92
+#: contentcuration/templates/registration/password_reset_email.html:91
msgid ""
"You are receiving this e-mail because you requested a password reset for "
"your user account at"
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:98
+#: contentcuration/templates/registration/password_reset_email.html:97
msgid "Reset my Password"
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:101
+#: contentcuration/templates/registration/password_reset_email.html:100
msgid "Please click the button below and choose a new password."
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:102
+#: contentcuration/templates/registration/password_reset_email.html:101
msgid "Your username is"
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:104
+#: contentcuration/templates/registration/password_reset_email.html:103
msgid "RESET"
msgstr ""
@@ -667,6 +411,143 @@ msgstr ""
msgid "Please create an account by following the link below:"
msgstr ""
+#: contentcuration/templates/registration/welcome_new_user_email.html:78
+msgid "Welcome to Kolibri Studio!"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:82
+#, python-format
+msgid ""
+"\n"
+" We're delighted to introduce you to Kolibri "
+"Studio , our curricular tool to add,\n"
+" organize, and manage your own resources or those from the Kolibri "
+"Content Library.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:87
+msgid "View the Kolibri Content Library"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:93
+msgid ""
+"\n"
+" Using Kolibri Studio, you can explore pre-organized collections of "
+"open educational resources (OER), and bundle,\n"
+" tag, differentiate, re-order, and distribute them into custom "
+"channels.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:99
+msgid ""
+"\n"
+" Using an admin account, you can then publish and import these custom "
+"channels--either your own or those shared\n"
+" with you -- into Kolibri with a unique \"token\" generated for each "
+"channel.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:107
+msgid ""
+"\n"
+" Browse through the list of resources below* to learn more about "
+"Kolibri Studio and to begin creating your own\n"
+" custom channels:\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:115
+msgid "Kolibri Studio User Guide"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:120
+msgid "Content integration guide:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:122
+msgid ""
+"\n"
+" Information on licensing, compatible formats, technical "
+"integration and more.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:125
+msgid ""
+"\n"
+" Note that if you are adding a small number of resources, "
+"technical integration is not necessary. \n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:130
+msgid "Step by step tutorials:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:133
+msgid "Video format:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:135
+msgid "Using Kolibri Studio: Your Content Workspace for Kolibri"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:137
+msgid "(*also available in French and Arabic)"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:140
+msgid "Slide gif format:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:144
+msgid "Step by step Studio tutorial"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:151
+msgid "Video compression instructions:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:153
+msgid ""
+"\n"
+" For optimal results, videos should be compressed in order to "
+"achieve small file sizes. Compression ensures\n"
+" that the videos are well suited for offline distribution and "
+"playback on all Kolibri devices.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:158
+msgid "View the guide to video compression"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:165
+msgid ""
+"If you need support with Kolibri Studio, please reach out to us on our "
+"Community Forum."
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:167
+msgid "Access the Community Forum"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:171
+msgid "Thank You!"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:178
+msgid "*resources are presented in English"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email_subject.txt:1
+msgid ""
+"Thank you for activating your Kolibri Studio account! Let's get started..."
+msgstr ""
+
#: contentcuration/templates/settings/account_deleted_user_email.txt:5
#, python-format
msgid "Your %(email)s account on %(site_name)s has been deleted."
@@ -741,7 +622,7 @@ msgstr ""
msgid "You can also try updating your current browser."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:10
+#: contentcuration/templatetags/license_tags.py:11
msgid ""
"The Attribution License lets others distribute, remix, tweak, and build upon "
"your work, even commercially, as long as they credit you for the original "
@@ -749,7 +630,7 @@ msgid ""
"for maximum dissemination and use of licensed materials."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:15
+#: contentcuration/templatetags/license_tags.py:16
msgid ""
"The Attribution-ShareAlike License lets others remix, tweak, and build upon "
"your work even for commercial purposes, as long as they credit you and "
@@ -761,14 +642,14 @@ msgid ""
"Wikipedia and similarly licensed projects."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:25
+#: contentcuration/templatetags/license_tags.py:26
msgid ""
"The Attribution-NoDerivs License allows for redistribution, commercial and "
"non-commercial, as long as it is passed along unchanged and in whole, with "
"credit to you."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:28
+#: contentcuration/templatetags/license_tags.py:29
msgid ""
"The Attribution-NonCommercial License lets others remix, tweak, and build "
"upon your work non-commercially, and although their new works must also "
@@ -776,14 +657,14 @@ msgid ""
"derivative works on the same terms."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:32
+#: contentcuration/templatetags/license_tags.py:33
msgid ""
"The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, "
"and build upon your work non-commercially, as long as they credit you and "
"license their new creations under the identical terms."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:36
+#: contentcuration/templatetags/license_tags.py:37
msgid ""
"The Attribution-NonCommercial-NoDerivs License is the most restrictive of "
"our six main licenses, only allowing others to download your works and share "
@@ -791,172 +672,91 @@ msgid ""
"any way or use them commercially."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:40
+#: contentcuration/templatetags/license_tags.py:41
msgid ""
"The All Rights Reserved License indicates that the copyright holder "
"reserves, or holds for their own use, all the rights provided by copyright "
"law under one specific copyright treaty."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:43
+#: contentcuration/templatetags/license_tags.py:44
msgid ""
"Public Domain work has been identified as being free of known restrictions "
"under copyright law, including all related and neighboring rights."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:46
+#: contentcuration/templatetags/license_tags.py:47
msgid ""
"Special Permissions is a custom license to use when the current licenses do "
"not apply to the content. The owner of this license is responsible for "
"creating a description of what this license entails."
msgstr ""
-#: contentcuration/templatetags/translation_tags.py:26
-msgid "100% Correct"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:27
-msgid "10 in a row"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:28
-msgid "2 in a row"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:29
-msgid "3 in a row"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:30
-msgid "5 in a row"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:31
-msgid "M of N..."
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:32
-msgid "CC BY"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:33
-msgid "CC BY-SA"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:34
-msgid "CC BY-ND"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:35
-msgid "CC BY-NC"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:36
-msgid "CC BY-NC-SA"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:37
-msgid "CC BY-NC-ND"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:38
-msgid "All Rights Reserved"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:39
-msgid "Public Domain"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:40
-msgid "Special Permissions"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:49
-#, python-format
-msgid "%(filesize)s %(unit)s"
-msgstr ""
-
-#: contentcuration/utils/csv_writer.py:138
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:45
+#: contentcuration/utils/csv_writer.py:108
msgid "No Channel"
msgstr ""
-#: contentcuration/utils/csv_writer.py:139
+#: contentcuration/utils/csv_writer.py:46
msgid "No resource"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Channel"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Title"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Kind"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Filename"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
+msgid "File Size"
+msgstr ""
+
+#: contentcuration/utils/csv_writer.py:71
msgid "URL"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Description"
msgstr ""
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Author"
msgstr ""
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Language"
msgstr ""
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License"
msgstr ""
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License Description"
msgstr ""
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Copyright Holder"
msgstr ""
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "No Resource"
msgstr ""
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "Staged File"
msgstr ""
-#: contentcuration/utils/format.py:15
-msgid "B"
-msgstr ""
-
-#: contentcuration/utils/format.py:17
-msgid "KB"
-msgstr ""
-
-#: contentcuration/utils/format.py:19
-msgid "MB"
-msgstr ""
-
-#: contentcuration/utils/format.py:21
-msgid "GB"
-msgstr ""
-
-#: contentcuration/utils/format.py:23
-msgid "TB"
-msgstr ""
-
#: contentcuration/utils/incidents.py:7
msgid ""
"There was a problem with a third-party service. This means certain "
@@ -1011,22 +811,26 @@ msgid ""
"com/'>here"
msgstr ""
-#: contentcuration/utils/publish.py:57
+#: contentcuration/utils/publish.py:96
msgid "Kolibri Studio Channel Published"
msgstr ""
-#: contentcuration/views/public.py:63 contentcuration/views/public.py:74
-msgid "Api endpoint {} is not available"
+#: contentcuration/views/settings.py:111
+msgid "Kolibri Studio issue report"
msgstr ""
-#: contentcuration/views/public.py:76
-msgid "No channel matching {} found"
+#: contentcuration/views/settings.py:143
+msgid "Kolibri Studio account deleted"
msgstr ""
-#: contentcuration/views/settings.py:110
-msgid "Kolibri Studio issue report"
+#: kolibri_public/views.py:220
+msgid "Resource"
msgstr ""
-#: contentcuration/views/settings.py:144
-msgid "Kolibri Studio account deleted"
+#: kolibri_public/views_v1.py:63 kolibri_public/views_v1.py:74
+msgid "Api endpoint {} is not available"
+msgstr ""
+
+#: kolibri_public/views_v1.py:76
+msgid "No channel matching {} found"
msgstr ""
diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/README.md b/contentcuration/locale/es_ES/LC_MESSAGES/README.md
new file mode 100644
index 0000000000..0f82b94d50
--- /dev/null
+++ b/contentcuration/locale/es_ES/LC_MESSAGES/README.md
@@ -0,0 +1 @@
+The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.csv b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.csv
index 198d0f6c38..2237ceefe1 100644
--- a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.csv
+++ b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.csv
@@ -74,13 +74,13 @@
"AccountCreated.accountCreatedTitle","Account successfully created","
-- CONTEXT --
","Cuenta creada con Ć©xito"
-"AccountCreated.continueToSignIn","Continue to sign-in","
+"AccountCreated.backToLogin","Continue to sign-in page","
-- CONTEXT --
","Continuar a la pĆ”gina de inicio de sesiĆ³n"
"AccountDeleted.accountDeletedTitle","Account successfully deleted","
-- CONTEXT --
","Cuenta eliminada con Ć©xito"
-"AccountDeleted.continueToSignIn","Continue to sign-in page","
+"AccountDeleted.backToLogin","Continue to sign-in page","
-- CONTEXT --
","Continuar a la pĆ”gina de inicio de sesiĆ³n"
"AccountNotActivated.requestNewLink","Request a new activation link","
@@ -287,9 +287,6 @@
"BrowsingCard.coach","Resource for coaches","
-- CONTEXT --
","Recurso para tutores"
-"BrowsingCard.goToPluralLocationsAction","In {count, number} {count, plural, one {location} other {locations}}","
--- CONTEXT --
-","En {count, number} {count, plural, one {ubicaciĆ³n} other {ubicaciones}}"
"BrowsingCard.goToSingleLocationAction","Go to location","
-- CONTEXT --
","Ir a la ubicaciĆ³n"
@@ -1272,6 +1269,9 @@ A type of math category. See https://en.wikipedia.org/wiki/Algebra","Ćlgebra"
"CommonMetadataStrings.all","All","
-- CONTEXT --
A label for everything in the group of activities.","Todo"
+"CommonMetadataStrings.allContent","Viewed in its entirety","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners studied it all, for example they saw all pages of a document.","Visto en su totalidad"
"CommonMetadataStrings.allLevelsBasicSkills","All levels -- basic skills","
-- CONTEXT --
Refers to a type of educational level.","Todos los niveles -- habilidades bƔsicas"
@@ -1323,6 +1323,9 @@ Science category type. See https://en.wikipedia.org/wiki/Chemistry","QuĆmica"
"CommonMetadataStrings.civicEducation","Civic education","
-- CONTEXT --
Category type. Civic education is the study of the rights and obligations of citizens in society. See https://en.wikipedia.org/wiki/Civics","EducaciĆ³n cĆvica"
+"CommonMetadataStrings.completeDuration","When time spent is equal to duration","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners spent given time studying it.","Cuando el tiempo empleado es igual a la duraciĆ³n"
"CommonMetadataStrings.completion","Completion","CommonMetadataStrings.completion
-- CONTEXT --
@@ -1342,6 +1345,9 @@ Category type. See https://en.wikipedia.org/wiki/Everyday_life","Vida diaria"
"CommonMetadataStrings.dance","Dance","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Dance","Danza"
+"CommonMetadataStrings.determinedByResource","Determined by the resource","
+-- CONTEXT --
+One of the completion criteria types. Typically used for embedded html5/h5p resources that contain their own completion criteria, for example reaching a score in an educational game.","Determinado por el recurso"
"CommonMetadataStrings.digitalLiteracy","Digital literacy","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Digital_literacy","AlfabetizaciĆ³n digital"
@@ -1363,6 +1369,9 @@ Category type. See https://en.wikipedia.org/wiki/Entrepreneurship","Emprendimien
"CommonMetadataStrings.environment","Environment","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Environmental_studies","Medio ambiente"
+"CommonMetadataStrings.exactTime","Time to complete","
+-- CONTEXT --
+One of the completion criteria types. A subset of ""When time spent is equal to duration"". For example, for an audio resource with this criteria, learnes need to hear the whole length of audio for the resource to be considered complete.","Tiempo para completar"
"CommonMetadataStrings.explore","Explore","
-- CONTEXT --
Resource and filter label for the type of learning activity. Translate as a VERB","Explorar"
@@ -1378,6 +1387,9 @@ Category type","Para profesores"
"CommonMetadataStrings.geometry","Geometry","
-- CONTEXT --
Category type.","GeometrĆa"
+"CommonMetadataStrings.goal","When goal is met","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners reached a given goal, for example 100% correct.","Cuando se alcanza el objetivo"
"CommonMetadataStrings.guides","Guides","
-- CONTEXT --
Category label in the Kolibri resources library; refers to any guide-type material for teacher professional development.","GuĆas"
@@ -1428,6 +1440,9 @@ Refers to a level of learning. Approximately corresponds to the first half of pr
"CommonMetadataStrings.lowerSecondary","Lower secondary","
-- CONTEXT --
Refers to a level of learning. Approximately corresponds to the first half of secondary school (high school).","Secundaria inferior"
+"CommonMetadataStrings.masteryMofN","Goal: {m} out of {n}","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners answered m questions out of n correctly.","Objetivo: {m} de {n}"
"CommonMetadataStrings.mathematics","Mathematics","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Mathematics","MatemƔticas"
@@ -1465,6 +1480,9 @@ Category type. See https://en.wikipedia.org/wiki/Political_science.","Ciencias p
"CommonMetadataStrings.practice","Practice","
-- CONTEXT --
Resource and filter label for the type of learning activity with questions and answers. Translate as a VERB","Practicar"
+"CommonMetadataStrings.practiceQuiz","Practice quiz","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria represents a quiz.","Prueba para practicar"
"CommonMetadataStrings.preschool","Preschool","
-- CONTEXT --
Refers to a level of education offered to children before they begin compulsory education at primary school.
@@ -1491,9 +1509,12 @@ School subject category","Lectura y escritura"
"CommonMetadataStrings.readingComprehension","Reading comprehension","
-- CONTEXT --
Category type.","ComprensiĆ³n lectora"
+"CommonMetadataStrings.reference","Reference material","
+-- CONTEXT --
+One of the completion criteria types. Progress made on a resource with this criteria is not tracked.","Material de referencia"
"CommonMetadataStrings.reflect","Reflect","
-- CONTEXT --
-Resource and filter label for the type of learning activity. Translate as a VERB","Reflejar"
+Resource and filter label for the type of learning activity. Translate as a VERB","Reflexionar"
"CommonMetadataStrings.school","School","
-- CONTEXT --
Category type.","Escuela"
@@ -1614,27 +1635,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escritura"
"CommunityStandardsModal.studioItem5","Hosting. Uploading your own materials (limited to materials you know are appropriately licensed to do so) from a local hard drive or other locations on the internet","
-- CONTEXT --
","Alojar. Subir sus propios materiales (limitado a materiales con la licencia adecuada para hacerlo confirmada) desde un disco duro local u otras ubicaciones en Internet."
-"CompletionOptions.allContent","Viewed in its entirety","
--- CONTEXT --
-","Visto en su totalidad"
-"CompletionOptions.completeDuration","When time spent is equal to duration","
--- CONTEXT --
-","Cuando el tiempo empleado es igual a la duraciĆ³n"
-"CompletionOptions.determinedByResource","Determined by the resource","
--- CONTEXT --
-","Determinado por el recurso"
-"CompletionOptions.exactTime","Time to complete","
+"CompletionOptions.learnersCanMarkComplete","Allow learners to mark as complete","
-- CONTEXT --
-","Tiempo para completar"
-"CompletionOptions.goal","When goal is met","
--- CONTEXT --
-","Cuando se alcanza el objetivo"
-"CompletionOptions.practiceQuiz","Practice quiz","
--- CONTEXT --
-","Prueba de prƔcticas"
-"CompletionOptions.reference","Reference material","
--- CONTEXT --
-","Material de referencia"
+","Permitir a los estudiantes marcar como completado"
"CompletionOptions.referenceHint","Progress will not be tracked on reference material unless learners mark it as complete","
-- CONTEXT --
","No se graba el seguimiento de progreso en el material de referencia a menos que los estudiantes lo marquen como completo"
@@ -1926,9 +1929,27 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escritura"
"ContentNodeChangedIcon.isUpdatedTopic","Folder has been updated since last publish","
-- CONTEXT --
","La carpeta ha sido actualizada desde la Ćŗltima publicaciĆ³n"
+"ContentNodeCopyTaskProgress.copyErrorTopic","Some resources failed to copy","
+-- CONTEXT --
+","Fallo al copiar algunos recursos"
+"ContentNodeEditListItem.copiedSnackbar","Copy operation complete","
+-- CONTEXT --
+","OperaciĆ³n de copiar finalizada"
+"ContentNodeEditListItem.creatingCopies","Copying...","
+-- CONTEXT --
+","Copiando..."
"ContentNodeEditListItem.optionsTooltip","Options","
-- CONTEXT --
","Opciones"
+"ContentNodeEditListItem.removeNode","Remove","
+-- CONTEXT --
+","Eliminar"
+"ContentNodeEditListItem.retryCopy","Retry","
+-- CONTEXT --
+","Reintentar"
+"ContentNodeEditListItem.undo","Undo","
+-- CONTEXT --
+","Deshacer"
"ContentNodeIcon.audio","Audio","
-- CONTEXT --
","Audio"
@@ -1956,12 +1977,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escritura"
"ContentNodeLearningActivityIcon.multipleLearningActivities","Multiple learning activities","
-- CONTEXT --
","MĆŗltiples actividades de aprendizaje"
-"ContentNodeLearningActivityIcon.topic","Folder","
--- CONTEXT --
-","Carpeta"
"ContentNodeListItem.coachTooltip","Resource for coaches","
-- CONTEXT --
","Recurso para tutores"
+"ContentNodeListItem.copyingError","Copy failed.","
+-- CONTEXT --
+","Hubo un fallo al copiar."
"ContentNodeListItem.copyingTask","Copying","
-- CONTEXT --
","Copiando"
@@ -2013,7 +2034,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escritura"
"ContentNodeOptions.newSubtopic","New folder","
-- CONTEXT --
","Nueva carpeta"
-"ContentNodeOptions.remove","Remove","
+"ContentNodeOptions.remove","Delete","
-- CONTEXT --
","Eliminar"
"ContentNodeOptions.removedFromClipboard","Deleted from clipboard","
@@ -2121,12 +2142,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escritura"
"CountryField.noCountriesFound","No countries found","
-- CONTEXT --
","No se han encontrado paĆses"
-"Create.ToSCheck","I have read and agree to the terms of service","
+"Create.ToSRequiredMessage","Please accept our terms of service and policy","
-- CONTEXT --
-","He leĆdo y estoy de acuerdo con los tĆ©rminos del servicio"
-"Create.ToSRequiredMessage","Please accept our terms of service","
+","Por favor, acepte nuestra polĆtica y tĆ©rminos de servicio"
+"Create.agreement","I have read and agree to terms of service and the privacy policy","
-- CONTEXT --
-","Por favor, acepte nuestros tƩrminos de servicio"
+","He leĆdo y estoy de acuerdo con los tĆ©rminos de servicio y la polĆtica de privacidad"
"Create.backToLoginButton","Sign in","
-- CONTEXT --
","Iniciar sesiĆ³n"
@@ -2217,12 +2238,6 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escritura"
"Create.personalDemoSourceOption","Personal demo","
-- CONTEXT --
","Demo personal"
-"Create.privacyPolicyCheck","I have read and agree to the privacy policy","
--- CONTEXT --
-","He leĆdo y estoy de acuerdo con la polĆtica de privacidad"
-"Create.privacyPolicyRequiredMessage","Please accept our privacy policy","
--- CONTEXT --
-","Por favor, acepte nuestra polĆtica de privacidad"
"Create.registrationFailed","There was an error registering your account. Please try again","
-- CONTEXT --
","Hubo un error al crear la cuenta. Por favor, intƩntelo nuevamente."
@@ -2259,10 +2274,10 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escritura"
"Create.usageLabel","How do you plan on using Kolibri Studio (check all that apply)","
-- CONTEXT --
","ĀæCĆ³mo piensa utilizar Kolibri Studio? (marcar todo que procede)"
-"Create.viewPrivacyPolicyLink","View privacy policy","
+"Create.viewPrivacyPolicyLink","View Privacy Policy","
-- CONTEXT --
","Ver polĆtica de privacidad"
-"Create.viewToSLink","View terms of service","
+"Create.viewToSLink","View Terms of Service","
-- CONTEXT --
","Ver tƩrminos de servicio"
"Create.websiteSourceOption","Learning Equality website","
@@ -2405,6 +2420,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escritura"
"Details.authorsLabel","Authors","
-- CONTEXT --
","Autores"
+"Details.categoriesHeading","Categories","
+-- CONTEXT --
+","CategorĆas"
"Details.coachDescription","Resources for coaches are only visible to coaches in Kolibri","
-- CONTEXT --
","Los recursos para tutores son visibles sĆ³lo a los tutores en Kolibri"
@@ -2429,6 +2447,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escritura"
"Details.languagesHeading","Languages","
-- CONTEXT --
","Idiomas"
+"Details.levelsHeading","Levels","
+-- CONTEXT --
+","Niveles"
"Details.licensesLabel","Licenses","
-- CONTEXT --
","Licencias"
@@ -2519,9 +2540,6 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"DetailsTabView.languageHelpText","Leave blank to use the folder language","
-- CONTEXT --
","Dejar en blanco para usar el idioma de la carpeta"
-"DetailsTabView.learnersCanMarkComplete","Allow learners to mark as complete","
--- CONTEXT --
-","Permitir a los estudiantes marcar como completado"
"DetailsTabView.noTagsFoundText","No results found for ""{text}"". Press 'Enter' key to create a new tag","
-- CONTEXT --
","No se han encontrado resultados para ""{text}"". Pulse la tecla 'Entrar' para crear una nueva etiqueta"
@@ -3012,6 +3030,9 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"MainNavigationDrawer.administrationLink","Administration","
-- CONTEXT --
","AdministraciĆ³n"
+"MainNavigationDrawer.changeLanguage","Change language","
+-- CONTEXT --
+","Cambiar idioma"
"MainNavigationDrawer.channelsLink","Channels","
-- CONTEXT --
","Canales"
@@ -3174,9 +3195,6 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"PoliciesModal.checkboxText","I have agreed to the above terms","
-- CONTEXT --
","He leĆdo y estoy de acuerdo con los tĆ©rminos anteriores"
-"PoliciesModal.checkboxValidationErrorMessage","Field is required","
--- CONTEXT --
-","Este campo es obligatorio"
"PoliciesModal.closeButton","Close","
-- CONTEXT --
","Cerrar"
@@ -3207,9 +3225,12 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"ProgressModal.syncError","Last attempt to sync failed","
-- CONTEXT --
","Ćltimo intento de sincronizaciĆ³n fallido"
-"ProgressModal.syncHeader","Syncing channel","
+"ProgressModal.syncHeader","Syncing resources","
+-- CONTEXT --
+","Sincronizando recursos"
+"ProgressModal.syncedSnackbar","Resources synced","
-- CONTEXT --
-","Sincronizando canal"
+","Recursos sincronizados"
"ProgressModal.unpublishedText","Unpublished","
-- CONTEXT --
","No publicado"
@@ -3525,9 +3546,6 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"ResourcePanel.coachResources","Resources for coaches","
-- CONTEXT --
","Recursos para tutores"
-"ResourcePanel.completion","Completion","
--- CONTEXT --
-","FinalizaciĆ³n"
"ResourcePanel.copyrightHolder","Copyright holder","
-- CONTEXT --
","Titular de derechos de autor"
@@ -3552,27 +3570,37 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"ResourcePanel.license","License","
-- CONTEXT --
","Licencia"
-"ResourcePanel.masteryMofN","Goal: {m} out of {n}","
--- CONTEXT --
-","Objetivo: {m} de {n}"
"ResourcePanel.nextSteps","Next steps","
-- CONTEXT --
","Pasos siguientes"
-"ResourcePanel.noCopyrightHolderError","Missing copyright holder","
+"ResourcePanel.noCompletionCriteriaError","Completion criteria are required","ResourcePanel.noCompletionCriteriaError
+
-- CONTEXT --
-","Falta titular de derechos de autor"
-"ResourcePanel.noFilesError","Missing files","
+Error message notification when a specific metadata is missing.","Los criterios de finalizaciĆ³n son obligatorios"
+"ResourcePanel.noCopyrightHolderError","Copyright holder is required","
-- CONTEXT --
-","Faltan ficheros"
-"ResourcePanel.noLicenseDescriptionError","Missing license description","
+","Hay que especificar el titular de los derechos de autor"
+"ResourcePanel.noDurationError","Duration is required","
-- CONTEXT --
-","Falta la descripciĆ³n de licencia"
-"ResourcePanel.noLicenseError","Missing license","
+","Este campo es obligatorio"
+"ResourcePanel.noFilesError","File is required","ResourcePanel.noFilesError
+
-- CONTEXT --
-","Falta licencia"
-"ResourcePanel.noMasteryModelError","Missing mastery criteria","
+Error message notification when a file is missing.","El archivo es requerido"
+"ResourcePanel.noLearningActivityError","Learning activity is required","
-- CONTEXT --
-","Faltan criterios de dominio"
+","Este campo es obligatorio"
+"ResourcePanel.noLicenseDescriptionError","License description is required","ResourcePanel.noLicenseDescriptionError
+
+-- CONTEXT --
+Error message notification when a specific metadata is missing.","La descripciĆ³n de la licencia es obligatoria"
+"ResourcePanel.noLicenseError","License is required","
+-- CONTEXT --
+","Se requiere licencia"
+"ResourcePanel.noMasteryModelError","Mastery criteria are required","ResourcePanel.noMasteryModelError
+
+-- CONTEXT --
+Error message notification when a specific metadata is missing.","Los criterios de dominio son obligatorios"
"ResourcePanel.noQuestionsError","Exercise is empty","
-- CONTEXT --
","Ejercicio no tiene preguntas"
@@ -3900,36 +3928,42 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"SyncResourcesModal.confirmSyncModalTitle","Confirm sync","
-- CONTEXT --
","Confirmar sincronizaciĆ³n"
+"SyncResourcesModal.confirmSyncModalWarningExplainer","Warning: this will overwrite any changes you have made to copied or imported resources.","
+-- CONTEXT --
+","Advertencia: esto sobrescribirĆ” cualquier cambio que haya hecho a los recursos copiados o importados."
"SyncResourcesModal.continueButtonLabel","Continue","
-- CONTEXT --
","Continuar"
"SyncResourcesModal.syncButtonLabel","Sync","
-- CONTEXT --
","Sincronizar"
-"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints","
+"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints in exercises and quizzes","
-- CONTEXT --
-","Actualizar preguntas, respuestas y pistas"
+","Actualizar preguntas, respuestas y sugerencias en ejercicios y cuestionarios"
"SyncResourcesModal.syncExercisesTitle","Assessment details","
-- CONTEXT --
","Detalles de los ejercicios"
-"SyncResourcesModal.syncFilesExplainer","Update all file information","
+"SyncResourcesModal.syncFilesExplainer","Update all files, including: thumbnails, subtitles, and captions","
-- CONTEXT --
-","Actualizar toda la informaciĆ³n del fichero"
+","Actualizar todos los archivos, incluyendo miniaturas y subtĆtulos"
"SyncResourcesModal.syncFilesTitle","Files","
-- CONTEXT --
","Archivos"
-"SyncResourcesModal.syncModalExplainer","Sync and update your resources with their original source.","
+"SyncResourcesModal.syncModalExplainer","Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files.","
+-- CONTEXT --
+","Sincronizar recursos en Kolibri Studio actualiza los recursos copiados o importados en este canal con cualquier cambio en los ficheros de recursos originales."
+"SyncResourcesModal.syncModalSelectAttributes","Select what you would like to sync:","
-- CONTEXT --
-","Sincroniza y actualiza los recursos con sus canales de origen."
+","Seleccionar lo que se va a sincronizar:"
"SyncResourcesModal.syncModalTitle","Sync resources","
-- CONTEXT --
","Sincronizar recursos"
-"SyncResourcesModal.syncTagsExplainer","Update all tags","
+"SyncResourcesModal.syncResourceDetailsExplainer","Update information about the resource: learning activity, level, requirements, category, tags, audience, and source","
-- CONTEXT --
-","Actualizar todas las etiquetas"
-"SyncResourcesModal.syncTagsTitle","Tags","
+","Actualizar informaciĆ³n sobre el recurso: actividad de aprendizaje, nivel, requisitos, categorĆa, etiquetas, pĆŗblico y fuente"
+"SyncResourcesModal.syncResourceDetailsTitle","Resource details","
-- CONTEXT --
-","Etiquetas"
+","Detalles del recurso"
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer","Update resource titles and descriptions","
-- CONTEXT --
","Actualizar tĆtulos y descripciones de recursos"
@@ -4370,9 +4404,10 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"TreeViewBase.noChangesText","No changes found in channel","
-- CONTEXT --
","No se encontraron cambios en el canal"
-"TreeViewBase.noLanguageSetError","Missing channel language","
+"TreeViewBase.noLanguageSetError","Channel language is required","TreeViewBase.noLanguageSetError
+
-- CONTEXT --
-","Falta el idioma del canal"
+Error message notification when a specific metadata is missing.","El idioma del canal es obligatorio"
"TreeViewBase.openTrash","Open trash","
-- CONTEXT --
","Abrir papelera"
@@ -4415,14 +4450,14 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
","Ficheros no permitidos"
"Uploader.unsupportedFilesText","{count, plural,
=1 {# file will not be uploaded.}
- other {# files will not be uploaded.}}
+ other {# files will not be uploaded.}}
{extensionCount, plural,
=1 {Supported file type is}
other {Supported file types are}} {extensions}","
-- CONTEXT --
","{count, plural,
=1 {# fichero no serĆ” subido.}
- other {# ficheros no serƔn subidos.}}
+ other {# ficheros no serƔn subidos.}}
{extensionCount, plural,
=1 {Tipo de fichero permitido es}
other {Tipos de fichero permitido son}} {extensions}"
@@ -4441,12 +4476,9 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"UsingStudio.bestPractice2","It is preferable to create multiple small channels rather than one giant channel with many layers of folders.","
-- CONTEXT --
","Es preferible crear mĆŗltiples canales pequeƱos en lugar de un canal gigante con muchas carpetas anidadas."
-"UsingStudio.bestPractice3","Reload the page often to ensure your work is saved to the server and no network errors have occurred. Use CTRL+R on Linux/Windows or ā+R on Mac.","
+"UsingStudio.bestPractice3","Reload the page to confirm your work has been saved to the server. Use CTRL+R on Linux/Windows or ā+R on Mac.","
-- CONTEXT --
-","Recargar la pĆ”gina a menudo para asegurarse de que su trabajo se guarda en el servidor y no se han producido errores de red. Utilice CTRL+R en Linux/Windows o ā+R en Mac."
-"UsingStudio.bestPractice4","Avoid simultaneous edits on the same channel. Channels should not be edited by multiple users at the same time or by the same user in multiple browser windows.","
--- CONTEXT --
-","Evitar ediciones simultĆ”neas del mismo canal. Los canales no deben ser editados por varios usuarios al mismo tiempo, o por el mismo usuario en mĆŗltiples ventanas del navegador."
+","Recargar la pĆ”gina para confirmar que los cambios han sido guardados en el servidor. Usar CTRL+R en Linux/Windows o ā+R en Mac."
"UsingStudio.bestPractice5","It is possible that you will encounter timeout errors in your browser when performing operations like import and sync, on large channels. Don't be alarmed by this error message and do not repeat the same operation again right away. It doesn't mean the operation has failed- Kolibri Studio is still working in the background. Wait a few minutes and reload the page before continuing your edits.","
-- CONTEXT --
","Es posible que encuentre errores de tiempo de espera en su navegador cuando realice operaciones como importar y sincronizar en canales grandes. No hay que alarmarse con este mensaje de error, ya que eso no significa que la operaciĆ³n haya fallado - Kolibri Studio todavĆa estĆ” trabajando en segundo plano. Por esta razĆ³n no hace falta repetir la misma operaciĆ³n de inmediato, sino esperar unos minutos y volver a cargar la pĆ”gina antes de continuar editando."
@@ -4456,9 +4488,6 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"UsingStudio.bestPractice7","PUBLISH periodically and import your channel into Kolibri to preview the content and obtain a local backup copy of your channel.","
-- CONTEXT --
","PUBLICAR periĆ³dicamente e importar el canal en Kolibri, para previsualizar el contenido y tener una copia de seguridad local del canal."
-"UsingStudio.bestPractice8","Do not edit the channel after you click PUBLISH. Wait for the notification email before resuming editing operations.","
--- CONTEXT --
-","No editar el canal despuĆ©s de hacer clic en PUBLICAR. Esperar el correo de notificaciĆ³n antes de volver a editar."
"UsingStudio.bestPractice9","Report issues as you encounter them.","
-- CONTEXT --
","Informar sobre problemas a medida que se vayan encontrando."
@@ -4468,18 +4497,13 @@ Heading for the section in the resource editing window","Tipo de finalizaciĆ³n"
"UsingStudio.communityStandardsLink","Community standards","
-- CONTEXT --
","Normas de la comunidad"
-"UsingStudio.issue1","Two users have reported isolated incidents where content they imported from another channel disappeared, leaving only empty folders and subfolders. In one report, the content later re-appeared. They did not experience these problems consistently, and the incidents may possibly involve issues with a slow or unstable internet connection. If you run into this issue, please contact us as soon as possible and let us know as much information as you can remember.","
--- CONTEXT --
-","Dos usuarios han informado de incidentes aislados donde desaparecieron los contenidos importados de otro canal, dejando sĆ³lo carpetas y subcarpetas vacĆas. En un reporte, el contenido mĆ”s tarde volviĆ³ a aparecer. No experimentaron estos problemas de forma consistente, y los incidentes podrĆan implicar problemas con una conexiĆ³n Internet lenta o inestable. Si encuentra este problema, por favor contacte lo antes posible e informe de todos los detalles."
-"UsingStudio.issue2","Some operations in Studio are currently very slow, and so it may appear that the change you attempted to make timed out or did not take effect. In many cases, the change is still being processed and will appear once it is complete. If, after 5-10 minutes, the change still has not taken effect even after a browser refresh, please file an issue. We are working on solutions to these issues.","
+"UsingStudio.issue1","There have been reports where users have observed the disappearance of changes they've recently made to their channels. The issue seems related to opening multiple tabs of Kolibri Studio, and eventually signing out. We advise that you disable any āMemory Saver/Sleepingā tab browser feature for Kolibri Studio, and reload each tab before signing out. We're actively investigating this issue, so if you run into it, please contact us with as much information as possible.","UsingStudio.issue1
+
-- CONTEXT --
-","Algunas operaciones en Studio son actualmente muy lentas, por lo que puede parecer que el cambio haya caducado o no tuvo efecto. En muchos casos, el cambio sigue siendo procesado y aparecerĆ” una vez que estĆ© completo. Si, despuĆ©s de 5-10 minutos el cambio aĆŗn no ha tenido efecto incluso despuĆ©s de volver a cargar la pĆ”gina del navegador, informe sobre lo ocurrido, por favor. Estamos trabajando en soluciones para estos problemas."
+A description of an issue that has been reported by users - the recommendation is to disable any memory saver feature in the browser while they are using Kolibri Studio.","Ha habido informes en los que usuarios han observado la desapariciĆ³n de cambios que han hecho recientemente en sus canales. El problema parece estar relacionado con la apertura de mĆŗltiples pestaƱas de Kolibri Studio, y luego cerrar la sesiĆ³n. Recomendamos deshabilitar cualquier funciĆ³n de navegador relacionada con 'Memory Saver/Sleep' (ahorro de memoria, suspensiĆ³n) para Kolibri Studio, y volver a cargar cada pestaƱa antes de salir y cerrar sesiĆ³n. Estamos investigando activamente este problema, por favor contĆ”ctanos con tanta informaciĆ³n como sea posible si lo encuentras."
"UsingStudio.issueLink1","Reports of disappearing content","
-- CONTEXT --
","Informes sobre el contenido que parece desaparecer"
-"UsingStudio.issueLink2","Slow performance can lead to unexpected errors in the interface","
--- CONTEXT --
-","El rendimiento lento puede conducir a errores inesperados en la interfaz"
"UsingStudio.issuesPageLink","View all issues","
-- CONTEXT --
","Ver todas las incidencias"
diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json
index c24f068db5..2302575751 100644
--- a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json
@@ -24,9 +24,9 @@
"Account.unableToDeleteAdminAccount": "No se puede eliminar una cuenta de administrador",
"Account.usernameLabel": "Nombre de usuario",
"AccountCreated.accountCreatedTitle": "Cuenta creada con Ć©xito",
- "AccountCreated.continueToSignIn": "Continuar a la pĆ”gina de inicio de sesiĆ³n",
+ "AccountCreated.backToLogin": "Continuar a la pĆ”gina de inicio de sesiĆ³n",
"AccountDeleted.accountDeletedTitle": "Cuenta eliminada con Ć©xito",
- "AccountDeleted.continueToSignIn": "Continuar a la pĆ”gina de inicio de sesiĆ³n",
+ "AccountDeleted.backToLogin": "Continuar a la pĆ”gina de inicio de sesiĆ³n",
"AccountNotActivated.requestNewLink": "Solicitar un nuevo enlace de activaciĆ³n",
"AccountNotActivated.text": "Por favor, compruebe su correo electrĆ³nico para un enlace de activaciĆ³n o solicite un nuevo enlace.",
"AccountNotActivated.title": "Su cuenta no ha sido activada",
@@ -95,7 +95,6 @@
"AssessmentTab.incompleteItemsCountMessage": "{invalidItemsCount} {invalidItemsCount, plural, one {pregunta no completada} other {preguntas no completadas}}",
"BrowsingCard.addToClipboardAction": "Copiar a portapapeles",
"BrowsingCard.coach": "Recurso para tutores",
- "BrowsingCard.goToPluralLocationsAction": "En {count, number} {count, plural, one {ubicaciĆ³n} other {ubicaciones}}",
"BrowsingCard.goToSingleLocationAction": "Ir a la ubicaciĆ³n",
"BrowsingCard.hasCoachTooltip": "{value, number, integer} {value, plural, one {recurso para tutores} other {recursos para tutores}}",
"BrowsingCard.previewAction": "Ver detalles",
@@ -414,6 +413,7 @@
"CommonMetadataStrings.accessibility": "Accesibilidad",
"CommonMetadataStrings.algebra": "Ćlgebra",
"CommonMetadataStrings.all": "Todo",
+ "CommonMetadataStrings.allContent": "Visto en su totalidad",
"CommonMetadataStrings.allLevelsBasicSkills": "Todos los niveles -- habilidades bƔsicas",
"CommonMetadataStrings.allLevelsWorkSkills": "Todos los niveles -- habilidades laborales",
"CommonMetadataStrings.altText": "Incluye descripciones de texto alternativas para las imƔgenes",
@@ -430,12 +430,14 @@
"CommonMetadataStrings.category": "CategorĆa",
"CommonMetadataStrings.chemistry": "QuĆmica",
"CommonMetadataStrings.civicEducation": "EducaciĆ³n cĆvica",
+ "CommonMetadataStrings.completeDuration": "Cuando el tiempo empleado es igual a la duraciĆ³n",
"CommonMetadataStrings.completion": "FinalizaciĆ³n",
"CommonMetadataStrings.computerScience": "InformƔtica",
"CommonMetadataStrings.create": "Crear",
"CommonMetadataStrings.currentEvents": "Eventos actuales",
"CommonMetadataStrings.dailyLife": "Vida diaria",
"CommonMetadataStrings.dance": "Danza",
+ "CommonMetadataStrings.determinedByResource": "Determinado por el recurso",
"CommonMetadataStrings.digitalLiteracy": "AlfabetizaciĆ³n digital",
"CommonMetadataStrings.diversity": "Diversidad",
"CommonMetadataStrings.drama": "Teatro",
@@ -443,11 +445,13 @@
"CommonMetadataStrings.earthScience": "Ciencias de la Tierra",
"CommonMetadataStrings.entrepreneurship": "Emprendimiento",
"CommonMetadataStrings.environment": "Medio ambiente",
+ "CommonMetadataStrings.exactTime": "Tiempo para completar",
"CommonMetadataStrings.explore": "Explorar",
"CommonMetadataStrings.financialLiteracy": "EducaciĆ³n financiera",
"CommonMetadataStrings.forBeginners": "Para principiantes",
"CommonMetadataStrings.forTeachers": "Para profesores",
"CommonMetadataStrings.geometry": "GeometrĆa",
+ "CommonMetadataStrings.goal": "Cuando se alcanza el objetivo",
"CommonMetadataStrings.guides": "GuĆas",
"CommonMetadataStrings.highContrast": "Incluye texto de alto contraste para los estudiantes con baja visiĆ³n",
"CommonMetadataStrings.history": "Historial",
@@ -464,6 +468,7 @@
"CommonMetadataStrings.longActivity": "Actividad larga",
"CommonMetadataStrings.lowerPrimary": "Primaria inferior",
"CommonMetadataStrings.lowerSecondary": "Secundaria inferior",
+ "CommonMetadataStrings.masteryMofN": "Objetivo: {m} de {n}",
"CommonMetadataStrings.mathematics": "MatemƔticas",
"CommonMetadataStrings.mechanicalEngineering": "IngenierĆa mecĆ”nica",
"CommonMetadataStrings.mediaLiteracy": "AlfabetizaciĆ³n mediĆ”tica",
@@ -476,6 +481,7 @@
"CommonMetadataStrings.physics": "FĆsica",
"CommonMetadataStrings.politicalScience": "Ciencias polĆticas",
"CommonMetadataStrings.practice": "Practicar",
+ "CommonMetadataStrings.practiceQuiz": "Prueba para practicar",
"CommonMetadataStrings.preschool": "Preescolar",
"CommonMetadataStrings.professionalSkills": "Habilidades profesionales",
"CommonMetadataStrings.programming": "ProgramaciĆ³n",
@@ -484,7 +490,8 @@
"CommonMetadataStrings.readReference": "Referencia",
"CommonMetadataStrings.readingAndWriting": "Lectura y escritura",
"CommonMetadataStrings.readingComprehension": "ComprensiĆ³n lectora",
- "CommonMetadataStrings.reflect": "Reflejar",
+ "CommonMetadataStrings.reference": "Material de referencia",
+ "CommonMetadataStrings.reflect": "Reflexionar",
"CommonMetadataStrings.school": "Escuela",
"CommonMetadataStrings.sciences": "Ciencias",
"CommonMetadataStrings.shortActivity": "Actividad corta",
@@ -523,13 +530,7 @@
"CommunityStandardsModal.studioItem3": "Compartir. Crear y publicar nuevos canales con lo que encuentra, ya sea para compartir con sus propias implementaciones de forma privada o para compartir con otros en Kolibri Studio.",
"CommunityStandardsModal.studioItem4": "Modificar y crear. AƱadir sus propios ejercicios de evaluaciĆ³n a cualquier material existente.",
"CommunityStandardsModal.studioItem5": "Alojar. Subir sus propios materiales (limitado a materiales con la licencia adecuada para hacerlo confirmada) desde un disco duro local u otras ubicaciones en Internet.",
- "CompletionOptions.allContent": "Visto en su totalidad",
- "CompletionOptions.completeDuration": "Cuando el tiempo empleado es igual a la duraciĆ³n",
- "CompletionOptions.determinedByResource": "Determinado por el recurso",
- "CompletionOptions.exactTime": "Tiempo para completar",
- "CompletionOptions.goal": "Cuando se alcanza el objetivo",
- "CompletionOptions.practiceQuiz": "Prueba de prƔcticas",
- "CompletionOptions.reference": "Material de referencia",
+ "CompletionOptions.learnersCanMarkComplete": "Permitir a los estudiantes marcar como completado",
"CompletionOptions.referenceHint": "No se graba el seguimiento de progreso en el material de referencia a menos que los estudiantes lo marquen como completo",
"ConstantStrings.All Rights Reserved": "Todos los derechos reservados",
"ConstantStrings.All Rights Reserved_description": "La Licencia Todos los Derechos Reservados, indica que el titular reserva, o mantiene para uso propio, todos los derechos previstos por la ley de derechos de autor bajo un solo tratado de derechos especĆfico.",
@@ -627,7 +628,13 @@
"ContentNodeChangedIcon.isNewTopic": "Carpeta sin publicar",
"ContentNodeChangedIcon.isUpdatedResource": "Actualizado desde la Ćŗltima publicaciĆ³n",
"ContentNodeChangedIcon.isUpdatedTopic": "La carpeta ha sido actualizada desde la Ćŗltima publicaciĆ³n",
+ "ContentNodeCopyTaskProgress.copyErrorTopic": "Fallo al copiar algunos recursos",
+ "ContentNodeEditListItem.copiedSnackbar": "OperaciĆ³n de copiar finalizada",
+ "ContentNodeEditListItem.creatingCopies": "Copiando...",
"ContentNodeEditListItem.optionsTooltip": "Opciones",
+ "ContentNodeEditListItem.removeNode": "Eliminar",
+ "ContentNodeEditListItem.retryCopy": "Reintentar",
+ "ContentNodeEditListItem.undo": "Deshacer",
"ContentNodeIcon.audio": "Audio",
"ContentNodeIcon.document": "Documento",
"ContentNodeIcon.exercise": "Ejercicio",
@@ -637,8 +644,8 @@
"ContentNodeIcon.unsupported": "No permitido",
"ContentNodeIcon.video": "VĆdeo",
"ContentNodeLearningActivityIcon.multipleLearningActivities": "MĆŗltiples actividades de aprendizaje",
- "ContentNodeLearningActivityIcon.topic": "Carpeta",
"ContentNodeListItem.coachTooltip": "Recurso para tutores",
+ "ContentNodeListItem.copyingError": "Hubo un fallo al copiar.",
"ContentNodeListItem.copyingTask": "Copiando",
"ContentNodeListItem.hasCoachTooltip": "{value, number, integer} {value, plural, one {recurso para tutores} other {recursos para tutores}}",
"ContentNodeListItem.openTopic": "Abrir carpeta",
@@ -692,8 +699,8 @@
"CountryField.locationLabel": "Seleccione todo lo que corresponda",
"CountryField.locationRequiredMessage": "Este campo es obligatorio",
"CountryField.noCountriesFound": "No se han encontrado paĆses",
- "Create.ToSCheck": "He leĆdo y estoy de acuerdo con los tĆ©rminos del servicio",
- "Create.ToSRequiredMessage": "Por favor, acepte nuestros tƩrminos de servicio",
+ "Create.ToSRequiredMessage": "Por favor, acepte nuestra polĆtica y tĆ©rminos de servicio",
+ "Create.agreement": "He leĆdo y estoy de acuerdo con los tĆ©rminos de servicio y la polĆtica de privacidad",
"Create.backToLoginButton": "Iniciar sesiĆ³n",
"Create.basicInformationHeader": "InformaciĆ³n bĆ”sica",
"Create.conferenceSourceOption": "Conferencia",
@@ -724,8 +731,6 @@
"Create.passwordLabel": "ContraseƱa",
"Create.passwordMatchMessage": "Las contraseƱas no coinciden",
"Create.personalDemoSourceOption": "Demo personal",
- "Create.privacyPolicyCheck": "He leĆdo y estoy de acuerdo con la polĆtica de privacidad",
- "Create.privacyPolicyRequiredMessage": "Por favor, acepte nuestra polĆtica de privacidad",
"Create.registrationFailed": "Hubo un error al crear la cuenta. Por favor, intƩntelo nuevamente.",
"Create.registrationFailedOffline": "Parece que no tiene conexiĆ³n. Por favor, conĆ©ctese a Internet para crear una cuenta.",
"Create.sequencingUsageOption": "Utilizar requisitos previos para poner materiales en una secuencia",
@@ -784,6 +789,7 @@
"Details.assessmentsIncludedText": "Evaluaciones",
"Details.authorToolTip": "Persona u organizaciĆ³n que ha creado este contenido",
"Details.authorsLabel": "Autores",
+ "Details.categoriesHeading": "CategorĆas",
"Details.coachDescription": "Los recursos para tutores son visibles sĆ³lo a los tutores en Kolibri",
"Details.coachHeading": "Recursos para tutores",
"Details.containsContentHeading": "Contiene contenidos de",
@@ -792,6 +798,7 @@
"Details.creationHeading": "Creado el ",
"Details.currentVersionHeading": "VersiĆ³n publicada",
"Details.languagesHeading": "Idiomas",
+ "Details.levelsHeading": "Niveles",
"Details.licensesLabel": "Licencias",
"Details.primaryLanguageHeading": "Idioma principal",
"Details.providerToolTip": "OrganizaciĆ³n que ha encargado o estĆ” distribuyendo el contenido",
@@ -820,7 +827,6 @@
"DetailsTabView.importedFromButtonText": "Importado desde {channel}",
"DetailsTabView.languageChannelHelpText": "Dejar en blanco para usar el idioma del canal",
"DetailsTabView.languageHelpText": "Dejar en blanco para usar el idioma de la carpeta",
- "CompletionOptions.learnersCanMarkComplete": "Permitir a los estudiantes marcar como completado",
"DetailsTabView.noTagsFoundText": "No se han encontrado resultados para \"{text}\". Pulse la tecla 'Entrar' para crear una nueva etiqueta",
"DetailsTabView.providerLabel": "Proveedor",
"DetailsTabView.providerToolTip": "OrganizaciĆ³n que ha encargado o estĆ” distribuyendo el contenido",
@@ -979,6 +985,7 @@
"Main.privacyPolicyLink": "PolĆtica de privacidad",
"Main.signInButton": "Iniciar sesiĆ³n",
"MainNavigationDrawer.administrationLink": "AdministraciĆ³n",
+ "MainNavigationDrawer.changeLanguage": "Cambiar idioma",
"MainNavigationDrawer.channelsLink": "Canales",
"MainNavigationDrawer.copyright": "Ā© {year} Learning Equality",
"MainNavigationDrawer.giveFeedback": "Dejar un comentario",
@@ -1029,7 +1036,6 @@
"PermissionsError.goToHomePageAction": "Ir a la pƔgina de inicio",
"PermissionsError.permissionDeniedHeader": "ĀæHa olvidado iniciar la sesiĆ³n?",
"PoliciesModal.checkboxText": "He leĆdo y estoy de acuerdo con los tĆ©rminos anteriores",
- "PoliciesModal.checkboxValidationErrorMessage": "Este campo es obligatorio",
"PoliciesModal.closeButton": "Cerrar",
"PoliciesModal.continueButton": "Continuar",
"PoliciesModal.lastUpdated": "Ćltima actualizaciĆ³n {date}",
@@ -1040,7 +1046,8 @@
"ProgressModal.lastPublished": "Publicado {last_published}",
"ProgressModal.publishHeader": "Publicando el canal",
"ProgressModal.syncError": "Ćltimo intento de sincronizaciĆ³n fallido",
- "ProgressModal.syncHeader": "Sincronizando canal",
+ "ProgressModal.syncHeader": "Sincronizando recursos",
+ "ProgressModal.syncedSnackbar": "Recursos sincronizados",
"ProgressModal.unpublishedText": "No publicado",
"PublishModal.cancelButton": "Cancelar",
"PublishModal.descriptionDescriptionTooltip": "Esta descripciĆ³n se mostrarĆ” a los administradores de Kolibri antes de que actualicen las versiones del canal",
@@ -1146,7 +1153,6 @@
"ResourcePanel.author": "Autor",
"ResourcePanel.availableFormats": "Formatos disponibles",
"ResourcePanel.coachResources": "Recursos para tutores",
- "ResourcePanel.completion": "FinalizaciĆ³n",
"ResourcePanel.copyrightHolder": "Titular de derechos de autor",
"ResourcePanel.description": "DescripciĆ³n",
"ResourcePanel.details": "Detalles",
@@ -1155,13 +1161,15 @@
"ResourcePanel.incompleteQuestionError": "{count, plural, one {# pregunta incompleta} other {# preguntas incompletas}}",
"ResourcePanel.language": "Idioma",
"ResourcePanel.license": "Licencia",
- "ResourcePanel.masteryMofN": "Objetivo: {m} de {n}",
"ResourcePanel.nextSteps": "Pasos siguientes",
- "ResourcePanel.noCopyrightHolderError": "Falta titular de derechos de autor",
- "ResourcePanel.noFilesError": "Faltan ficheros",
- "ResourcePanel.noLicenseDescriptionError": "Falta la descripciĆ³n de licencia",
- "ResourcePanel.noLicenseError": "Falta licencia",
- "ResourcePanel.noMasteryModelError": "Faltan criterios de dominio",
+ "ResourcePanel.noCompletionCriteriaError": "Los criterios de finalizaciĆ³n son obligatorios",
+ "ResourcePanel.noCopyrightHolderError": "Hay que especificar el titular de los derechos de autor",
+ "ResourcePanel.noDurationError": "Este campo es obligatorio",
+ "ResourcePanel.noFilesError": "El archivo es requerido",
+ "ResourcePanel.noLearningActivityError": "Este campo es obligatorio",
+ "ResourcePanel.noLicenseDescriptionError": "La descripciĆ³n de la licencia es obligatoria",
+ "ResourcePanel.noLicenseError": "Se requiere licencia",
+ "ResourcePanel.noMasteryModelError": "Los criterios de dominio son obligatorios",
"ResourcePanel.noQuestionsError": "Ejercicio no tiene preguntas",
"ResourcePanel.originalChannel": "Importado desde",
"ResourcePanel.previousSteps": "Pasos anteriores",
@@ -1271,16 +1279,18 @@
"SyncResourcesModal.cancelButtonLabel": "Cancelar",
"SyncResourcesModal.confirmSyncModalExplainer": "EstĆ” a punto de sincronizar y actualizar lo siguiente:",
"SyncResourcesModal.confirmSyncModalTitle": "Confirmar sincronizaciĆ³n",
+ "SyncResourcesModal.confirmSyncModalWarningExplainer": "Advertencia: esto sobrescribirĆ” cualquier cambio que haya hecho a los recursos copiados o importados.",
"SyncResourcesModal.continueButtonLabel": "Continuar",
"SyncResourcesModal.syncButtonLabel": "Sincronizar",
- "SyncResourcesModal.syncExercisesExplainer": "Actualizar preguntas, respuestas y pistas",
+ "SyncResourcesModal.syncExercisesExplainer": "Actualizar preguntas, respuestas y sugerencias en ejercicios y cuestionarios",
"SyncResourcesModal.syncExercisesTitle": "Detalles de los ejercicios",
- "SyncResourcesModal.syncFilesExplainer": "Actualizar toda la informaciĆ³n del fichero",
+ "SyncResourcesModal.syncFilesExplainer": "Actualizar todos los archivos, incluyendo miniaturas y subtĆtulos",
"SyncResourcesModal.syncFilesTitle": "Archivos",
- "SyncResourcesModal.syncModalExplainer": "Sincroniza y actualiza los recursos con sus canales de origen.",
+ "SyncResourcesModal.syncModalExplainer": "Sincronizar recursos en Kolibri Studio actualiza los recursos copiados o importados en este canal con cualquier cambio en los ficheros de recursos originales.",
+ "SyncResourcesModal.syncModalSelectAttributes": "Seleccionar lo que se va a sincronizar:",
"SyncResourcesModal.syncModalTitle": "Sincronizar recursos",
- "SyncResourcesModal.syncTagsExplainer": "Actualizar todas las etiquetas",
- "SyncResourcesModal.syncTagsTitle": "Etiquetas",
+ "SyncResourcesModal.syncResourceDetailsExplainer": "Actualizar informaciĆ³n sobre el recurso: actividad de aprendizaje, nivel, requisitos, categorĆa, etiquetas, pĆŗblico y fuente",
+ "SyncResourcesModal.syncResourceDetailsTitle": "Detalles del recurso",
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer": "Actualizar tĆtulos y descripciones de recursos",
"SyncResourcesModal.syncTitlesAndDescriptionsTitle": "TĆtulos y descripciones",
"TechnicalTextBlock.copiedToClipboardConfirmation": "Copiado a portapapeles",
@@ -1421,7 +1431,7 @@
"TreeViewBase.getToken": "Obtener el token",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {el recurso estƔ incompleto y no se puede publicar} other {los recursos estƔn incompletos y no se pueden publicar}}",
"TreeViewBase.noChangesText": "No se encontraron cambios en el canal",
- "TreeViewBase.noLanguageSetError": "Falta el idioma del canal",
+ "TreeViewBase.noLanguageSetError": "El idioma del canal es obligatorio",
"TreeViewBase.openTrash": "Abrir papelera",
"TreeViewBase.publishButton": "Publicar",
"TreeViewBase.publishButtonTitle": "Hacer este canal disponible para importar en Kolibri",
@@ -1440,19 +1450,15 @@
"UsingStudio.aboutStudioText": "Kolibri Studio estĆ” en desarrollo activo, y como tal, algunos cambios podrĆan causar un comportamientos inesperado, incidencias o problemas (tambiĆ©n conocidos como \"issues\" en inglĆ©s). Si encuentra algĆŗn problema, por favor notifĆquenos tan pronto como ocurra para ayudarnos a resolverlos. (Ver abajo las instrucciones sobre cĆ³mo informar de incidencias.)",
"UsingStudio.bestPractice1": "Al usar operaciones de importaciĆ³n y portapapeles, se recomienda trabajar con pequeƱos subconjuntos de carpetas en lugar de canales enteros a la vez (especialmente para canales grandes).",
"UsingStudio.bestPractice2": "Es preferible crear mĆŗltiples canales pequeƱos en lugar de un canal gigante con muchas carpetas anidadas.",
- "UsingStudio.bestPractice3": "Recargar la pĆ”gina a menudo para asegurarse de que su trabajo se guarda en el servidor y no se han producido errores de red. Utilice CTRL+R en Linux/Windows o ā+R en Mac.",
- "UsingStudio.bestPractice4": "Evitar ediciones simultĆ”neas del mismo canal. Los canales no deben ser editados por varios usuarios al mismo tiempo, o por el mismo usuario en mĆŗltiples ventanas del navegador.",
+ "UsingStudio.bestPractice3": "Recargar la pĆ”gina para confirmar que los cambios han sido guardados en el servidor. Usar CTRL+R en Linux/Windows o ā+R en Mac.",
"UsingStudio.bestPractice5": "Es posible que encuentre errores de tiempo de espera en su navegador cuando realice operaciones como importar y sincronizar en canales grandes. No hay que alarmarse con este mensaje de error, ya que eso no significa que la operaciĆ³n haya fallado - Kolibri Studio todavĆa estĆ” trabajando en segundo plano. Por esta razĆ³n no hace falta repetir la misma operaciĆ³n de inmediato, sino esperar unos minutos y volver a cargar la pĆ”gina antes de continuar editando.",
"UsingStudio.bestPractice6": "Comprimir vĆdeos antes de subirlos (ver estas instrucciones).",
"UsingStudio.bestPractice7": "PUBLICAR periĆ³dicamente e importar el canal en Kolibri, para previsualizar el contenido y tener una copia de seguridad local del canal.",
- "UsingStudio.bestPractice8": "No editar el canal despuĆ©s de hacer clic en PUBLICAR. Esperar el correo de notificaciĆ³n antes de volver a editar.",
"UsingStudio.bestPractice9": "Informar sobre problemas a medida que se vayan encontrando.",
"UsingStudio.bestPractices": "Mejores prƔcticas",
"UsingStudio.communityStandardsLink": "Normas de la comunidad",
- "UsingStudio.issue1": "Dos usuarios han informado de incidentes aislados donde desaparecieron los contenidos importados de otro canal, dejando sĆ³lo carpetas y subcarpetas vacĆas. En un reporte, el contenido mĆ”s tarde volviĆ³ a aparecer. No experimentaron estos problemas de forma consistente, y los incidentes podrĆan implicar problemas con una conexiĆ³n Internet lenta o inestable. Si encuentra este problema, por favor contacte lo antes posible e informe de todos los detalles.",
- "UsingStudio.issue2": "Algunas operaciones en Studio son actualmente muy lentas, por lo que puede parecer que el cambio haya caducado o no tuvo efecto. En muchos casos, el cambio sigue siendo procesado y aparecerĆ” una vez que estĆ© completo. Si, despuĆ©s de 5-10 minutos el cambio aĆŗn no ha tenido efecto incluso despuĆ©s de volver a cargar la pĆ”gina del navegador, informe sobre lo ocurrido, por favor. Estamos trabajando en soluciones para estos problemas.",
+ "UsingStudio.issue1": "Ha habido informes en los que usuarios han observado la desapariciĆ³n de cambios que han hecho recientemente en sus canales. El problema parece estar relacionado con la apertura de mĆŗltiples pestaƱas de Kolibri Studio, y luego cerrar la sesiĆ³n. Recomendamos deshabilitar cualquier funciĆ³n de navegador relacionada con 'Memory Saver/Sleep' (ahorro de memoria, suspensiĆ³n) para Kolibri Studio, y volver a cargar cada pestaƱa antes de salir y cerrar sesiĆ³n. Estamos investigando activamente este problema, por favor contĆ”ctanos con tanta informaciĆ³n como sea posible si lo encuentras.",
"UsingStudio.issueLink1": "Informes sobre el contenido que parece desaparecer",
- "UsingStudio.issueLink2": "El rendimiento lento puede conducir a errores inesperados en la interfaz",
"UsingStudio.issuesPageLink": "Ver todas las incidencias",
"UsingStudio.notableIssues": "Incidencias notables",
"UsingStudio.policiesLink": "PolĆtica de privacidad",
@@ -1498,4 +1504,5 @@
"sharedVue.masteryModelNWholeNumber": "Debe ser un nĆŗmero entero",
"sharedVue.masteryModelRequired": "Tiene que seleccionar el criterio de dominio",
"sharedVue.shortActivityLteThirty": "El valor debe ser igual o menor que 30",
- "sharedVue.titleRequired": "Este campo es obligatorio"}
+ "sharedVue.titleRequired": "Este campo es obligatorio"
+}
diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/django.po b/contentcuration/locale/es_ES/LC_MESSAGES/django.po
index f8646e3270..cabd314dbf 100644
--- a/contentcuration/locale/es_ES/LC_MESSAGES/django.po
+++ b/contentcuration/locale/es_ES/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-16 00:55+0000\n"
-"PO-Revision-Date: 2022-10-17 20:08\n"
+"POT-Creation-Date: 2023-05-10 21:55+0000\n"
+"PO-Revision-Date: 2023-05-24 17:17\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -17,36 +17,8 @@ msgstr ""
"X-Crowdin-File: /unstable/django.po\n"
"X-Crowdin-File-ID: 4322\n"
-#: contentcuration/api.py:140
-msgid "Date/Time Created"
-msgstr "Fecha/Hora creaciĆ³n"
-
-#: contentcuration/api.py:141 contentcuration/api.py:142
-msgid "Not Available"
-msgstr "No estĆ” disponible"
-
-#: contentcuration/api.py:145
-msgid "Ricecooker Version"
-msgstr "VersiĆ³n de Ricecooker"
-
-#: contentcuration/api.py:150 contentcuration/utils/csv_writer.py:164
-msgid "File Size"
-msgstr "TamaƱo del fichero"
-
-#: contentcuration/api.py:161
-msgid "# of {}s"
-msgstr "# de {}s"
-
-#: contentcuration/api.py:165
-msgid "# of Questions"
-msgstr "# de Preguntas"
-
-#: contentcuration/api.py:175
-msgid "# of Subtitles"
-msgstr "# de SubtĆtulos"
-
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
-#: contentcuration/settings.py:287
+#: contentcuration/settings.py:290
msgid "Arabic"
msgstr "Ćrabe"
@@ -54,40 +26,40 @@ msgstr "Ćrabe"
msgid "The site is currently in read-only mode. Please try again later."
msgstr "El sitio estƔ actualmente en modo de solo lectura. Por favor, intƩntelo mƔs tarde."
-#: contentcuration/models.py:279
+#: contentcuration/models.py:295
msgid "Not enough space. Check your storage under Settings page."
msgstr "No hay suficiente espacio. Compruebe el almacenamiento disponible en la pĆ”gina de ConfiguraciĆ³n."
-#: contentcuration/models.py:294 contentcuration/models.py:301
+#: contentcuration/models.py:308 contentcuration/models.py:315
msgid "Out of storage! Request more space under Settings > Storage."
msgstr "El espacio de almacenamiento es insuficiente. Solicite mĆ”s espacio en ConfiguraciĆ³n > Almacenamiento."
-#: contentcuration/models.py:1410
+#: contentcuration/models.py:1730
msgid " (Original)"
msgstr " (Original)"
-#: contentcuration/settings.py:285
+#: contentcuration/settings.py:288
msgid "English"
msgstr "InglƩs"
-#: contentcuration/settings.py:286
+#: contentcuration/settings.py:289
msgid "Spanish"
msgstr "EspaƱol"
-#: contentcuration/settings.py:288
+#: contentcuration/settings.py:291
msgid "French"
msgstr "FrancƩs"
-#: contentcuration/tasks.py:251
-msgid "Unknown error starting task. Please contact support."
-msgstr "Error desconocido. PĆ³ngase en contacto con el servicio tĆ©cnico."
+#: contentcuration/settings.py:292
+msgid "Portuguese"
+msgstr "PortuguƩs"
-#: contentcuration/templates/base.html:83
+#: contentcuration/templates/base.html:38
#: contentcuration/templates/channel_list.html:14
msgid "Kolibri Studio"
msgstr "Kolibri Studio"
-#: contentcuration/templates/base.html:166
+#: contentcuration/templates/base.html:129
msgid "Contentworkshop.learningequality.org has been deprecated. Please go to studio.learningequality.org for the latest version of Studio"
msgstr "contentworkshop.learningequality.org ya no es vĆ”lido. Por favor, vaya a studio.learningequality.org para acceder a la Ćŗltima versiĆ³n de Studio"
@@ -100,234 +72,16 @@ msgid "We're sorry, this channel was not found."
msgstr "Lo sentimos, este canal no fue encontrado."
#: contentcuration/templates/channel_not_found.html:24
-#: contentcuration/templates/permissions/open_channel_fail.html:14
#: contentcuration/templates/staging_not_found.html:23
#: contentcuration/templates/unauthorized.html:23
msgid "Go Home"
msgstr "Ir a la pƔgina principal"
-#: contentcuration/templates/exercise_list.html:67
-msgid "Previous"
-msgstr "Anterior"
-
-#: contentcuration/templates/exercise_list.html:73
-msgid "current"
-msgstr "actual"
-
-#: contentcuration/templates/exercise_list.html:77
-msgid "Next"
-msgstr "Siguiente"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:206
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:165
-msgid "Language not set"
-msgstr "Idioma no establecido"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:212
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:171
-#, python-format
-msgid "This file was generated on %(date)s"
-msgstr "Este fichero fue generado el %(date)s"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:231
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:189
-msgid "Created"
-msgstr "Creado"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:232
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:190
-msgid "Last Published"
-msgstr "Fecha Ćŗltima publicaciĆ³n"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:233
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:191
-msgid "Unpublished"
-msgstr "No publicado"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:245
-msgid "USING THIS CHANNEL"
-msgstr "USANDO ESTE CANAL"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:247
-msgid "Copy one of the following into Kolibri to import this channel:"
-msgstr "Copie uno de los siguientes en Kolibri para importar este canal:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:252
-msgid "Tokens (Recommended):"
-msgstr "Token (recomendado):"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:260
-msgid "Channel ID:"
-msgstr "ID del canal:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:267
-msgid "Channel must be published to import into Kolibri"
-msgstr "El canal debe haber sido publicado para poder importarse en Kolibri"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:273
-msgid "WHAT'S INSIDE"
-msgstr "QUĆ CONTIENE"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:283
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:213
-#, python-format
-msgid "%(count)s Resource"
-msgid_plural "%(count)s Resources"
-msgstr[0] "%(count)s Recurso"
-msgstr[1] "%(count)s Recursos"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:304
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:234
-msgid "Includes"
-msgstr "Incluye"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:310
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:236
-msgid "Languages"
-msgstr "Idiomas"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:316
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:245
-msgid "Subtitles"
-msgstr "SubtĆtulos"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:324
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:254
-msgid "For Educators"
-msgstr "Para los educadores"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:256
-msgid "Coach Content"
-msgstr "Contenido para tutores"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:257
-msgid "Assessments"
-msgstr "Evaluaciones"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:334
-msgid "Content Tags"
-msgstr "Etiquetas de contenidos"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:338
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:270
-msgid "No tags found"
-msgstr "No se encontraron etiquetas"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:343
-#: contentcuration/templates/export/channel_detail_pdf.html:448
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:274
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:337
-msgid "This channel is empty"
-msgstr "Este canal estĆ” vacĆo"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:348
-msgid "SOURCE"
-msgstr "FUENTE"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:360
-msgid "This channel features resources created by:"
-msgstr "Este canal tiene recursos creados por:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:361
-#: contentcuration/templates/export/channel_detail_pdf.html:379
-#: contentcuration/templates/export/channel_detail_pdf.html:398
-#: contentcuration/templates/export/channel_detail_pdf.html:420
-#: contentcuration/templates/export/channel_detail_pdf.html:440
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:293
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:305
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:317
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:333
-msgid "Information not available"
-msgstr "InformaciĆ³n no disponible"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:378
-msgid "The material in this channel was provided by:"
-msgstr "El material de este canal fue proporcionado por:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:397
-msgid "Material in this channel was originally hosted by:"
-msgstr "El material de este canal estaba originalmente en:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:416
-msgid "This channel includes the following licenses:"
-msgstr "Este canal incluye las siguientes licencias:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:439
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:326
-msgid "Copyright Holders:"
-msgstr "Titular de derechos de autor:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:192
-msgid "Token:"
-msgstr "Token:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:203
-msgid "What's Inside"
-msgstr "Contenidos"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:239
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:248
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s mƔs) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:267
-msgid "Most Common Tags"
-msgstr "Etiquetas mƔs comunes"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:280
-msgid "Source Information"
-msgstr "InformaciĆ³n sobre fuentes"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:284
-msgid "Authors:"
-msgstr "Autores:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:288
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s mƔs) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:298
-msgid "Providers:"
-msgstr "Proveedores:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:301
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:313
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:329
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s mƔs) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:310
-msgid "Aggregators:"
-msgstr "Agregadores:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-msgid "Licenses:"
-msgstr "Licencias:"
-
-#: contentcuration/templates/export/csv_email.txt:4
#: contentcuration/templates/export/user_csv_email.txt:4
#: contentcuration/templates/permissions/permissions_email.txt:4
#: contentcuration/templates/registration/activation_email.txt:4
#: contentcuration/templates/registration/activation_needed_email.txt:4
-#: contentcuration/templates/registration/channel_published_email.txt:4
+#: contentcuration/templates/registration/channel_published_email.html:10
#: contentcuration/templates/registration/password_reset_email.txt:3
#: contentcuration/templates/registration/registration_needed_email.txt:4
#: contentcuration/templates/settings/account_deleted_user_email.txt:3
@@ -337,43 +91,6 @@ msgstr "Licencias:"
msgid "Hello %(name)s,"
msgstr "Hola %(name)s,"
-#: contentcuration/templates/export/csv_email.txt:6
-#, python-format
-msgid "Your csv for %(channel_name)s has finished generating (attached)."
-msgstr "Archivo CSV para %(channel_name)s se ha generando (adjunto)."
-
-#: contentcuration/templates/export/csv_email.txt:8
-#: contentcuration/templates/export/user_csv_email.txt:29
-#: contentcuration/templates/permissions/permissions_email.txt:21
-#: contentcuration/templates/registration/activation_email.txt:12
-#: contentcuration/templates/registration/activation_needed_email.txt:14
-#: contentcuration/templates/registration/channel_published_email.txt:15
-#: contentcuration/templates/registration/password_reset_email.txt:14
-#: contentcuration/templates/registration/registration_needed_email.txt:12
-#: contentcuration/templates/settings/account_deleted_user_email.txt:10
-#: contentcuration/templates/settings/storage_request_email.txt:46
-msgid "Thanks for using Kolibri Studio!"
-msgstr "Ā”Gracias por usar Kolibri Studio!"
-
-#: contentcuration/templates/export/csv_email.txt:10
-#: contentcuration/templates/export/user_csv_email.txt:31
-#: contentcuration/templates/permissions/permissions_email.txt:23
-#: contentcuration/templates/registration/activation_email.txt:14
-#: contentcuration/templates/registration/activation_needed_email.txt:16
-#: contentcuration/templates/registration/channel_published_email.txt:17
-#: contentcuration/templates/registration/password_reset_email.txt:16
-#: contentcuration/templates/registration/registration_needed_email.txt:14
-#: contentcuration/templates/settings/account_deleted_user_email.txt:12
-#: contentcuration/templates/settings/issue_report_email.txt:26
-#: contentcuration/templates/settings/storage_request_email.txt:48
-msgid "The Learning Equality Team"
-msgstr "El equipo de Learning Equality"
-
-#: contentcuration/templates/export/csv_email_subject.txt:1
-#, python-format
-msgid "CSV for %(channel_name)s"
-msgstr "CSV para %(channel_name)s"
-
#: contentcuration/templates/export/user_csv_email.txt:6
msgid "Here is the information for your Kolibri Studio account"
msgstr "AquĆ tiene la informaciĆ³n de su cuenta de usuario en Kolibri Studio"
@@ -420,43 +137,64 @@ msgstr "La informaciĆ³n sobre los recursos que ha subido ha sido adjuntada como
msgid "If you have any questions or concerns, please email us at %(legal_email)s."
msgstr "Si tiene alguna pregunta, por favor envĆenos un correo electrĆ³nico a %(legal_email)s."
+#: contentcuration/templates/export/user_csv_email.txt:29
+#: contentcuration/templates/permissions/permissions_email.txt:21
+#: contentcuration/templates/registration/activation_email.txt:12
+#: contentcuration/templates/registration/activation_needed_email.txt:14
+#: contentcuration/templates/registration/channel_published_email.html:23
+#: contentcuration/templates/registration/password_reset_email.txt:14
+#: contentcuration/templates/registration/registration_needed_email.txt:12
+#: contentcuration/templates/settings/account_deleted_user_email.txt:10
+#: contentcuration/templates/settings/storage_request_email.txt:46
+msgid "Thanks for using Kolibri Studio!"
+msgstr "Ā”Gracias por usar Kolibri Studio!"
+
+#: contentcuration/templates/export/user_csv_email.txt:31
+#: contentcuration/templates/permissions/permissions_email.html:118
+#: contentcuration/templates/permissions/permissions_email.txt:23
+#: contentcuration/templates/registration/activation_email.html:111
+#: contentcuration/templates/registration/activation_email.txt:14
+#: contentcuration/templates/registration/activation_needed_email.txt:16
+#: contentcuration/templates/registration/channel_published_email.html:25
+#: contentcuration/templates/registration/password_reset_email.html:111
+#: contentcuration/templates/registration/password_reset_email.txt:16
+#: contentcuration/templates/registration/registration_needed_email.txt:14
+#: contentcuration/templates/registration/welcome_new_user_email.html:172
+#: contentcuration/templates/settings/account_deleted_user_email.txt:12
+#: contentcuration/templates/settings/issue_report_email.txt:26
+#: contentcuration/templates/settings/storage_request_email.txt:48
+msgid "The Learning Equality Team"
+msgstr "El equipo de Learning Equality"
+
#: contentcuration/templates/export/user_csv_email_subject.txt:1
msgid "Your Kolibri Studio account information"
msgstr "InformaciĆ³n de su cuenta en Kolibri Studio"
-#: contentcuration/templates/permissions/open_channel_fail.html:12
-msgid "There was an error opening this channel."
-msgstr "Hubo un error al abrir este canal."
-
-#: contentcuration/templates/permissions/open_channel_fail.html:13
-msgid "Try running ricecooker again."
-msgstr "Intentar ejecutar el Ricecooker otra vez."
-
-#: contentcuration/templates/permissions/permissions_email.html:93
-#: contentcuration/templates/registration/activation_email.html:91
-#: contentcuration/templates/registration/password_reset_email.html:91
+#: contentcuration/templates/permissions/permissions_email.html:92
+#: contentcuration/templates/registration/activation_email.html:90
+#: contentcuration/templates/registration/password_reset_email.html:90
msgid "Hello"
msgstr "Hola"
-#: contentcuration/templates/permissions/permissions_email.html:94
+#: contentcuration/templates/permissions/permissions_email.html:93
msgid "has invited you to edit a channel at"
msgstr "le invita a editar un canal en"
-#: contentcuration/templates/permissions/permissions_email.html:100
+#: contentcuration/templates/permissions/permissions_email.html:99
#, python-format
msgid "Invititation to %(share_mode)s channel"
msgstr "InvitaciĆ³n al canal de %(share_mode)s"
-#: contentcuration/templates/permissions/permissions_email.html:104
+#: contentcuration/templates/permissions/permissions_email.html:103
msgid "Click one of the following links to either accept or decline your invitation:"
msgstr "Haga clic en uno de los siguientes enlaces para aceptar o rechazar la invitaciĆ³n:"
-#: contentcuration/templates/permissions/permissions_email.html:107
-#: contentcuration/templates/permissions/permissions_email.html:109
+#: contentcuration/templates/permissions/permissions_email.html:106
+#: contentcuration/templates/permissions/permissions_email.html:108
msgid "ACCEPT"
msgstr "ACEPTAR"
-#: contentcuration/templates/permissions/permissions_email.html:112
+#: contentcuration/templates/permissions/permissions_email.html:111
msgid "DECLINE"
msgstr "DECLINAR"
@@ -494,24 +232,24 @@ msgstr "Le han invitado a editar %(channel)s"
msgid "You've been invited to view %(channel)s"
msgstr "Le han invitado a ver %(channel)s"
-#: contentcuration/templates/registration/activation_email.html:92
-msgid "Welcome to Kolibri! Here is the link to activate your account:"
-msgstr "Ā”Bienvenidos a Kolibri! AquĆ estĆ” el vĆnculo para activar la cuenta:"
+#: contentcuration/templates/registration/activation_email.html:91
+msgid "Welcome to Kolibri Studio! Here is the link to activate your account:"
+msgstr "Ā”Le damos la bienvenida a Kolibri Studio! AquĆ estĆ” el enlace para activar la cuenta:"
-#: contentcuration/templates/registration/activation_email.html:101
+#: contentcuration/templates/registration/activation_email.html:100
msgid "Click here to activate your account."
msgstr "Haga clic aquĆ para activar la cuenta."
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
msgid "This link is valid for"
msgstr "Este enlace es vƔlido por"
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
#, python-format
msgid "%(expiration_days)s days."
msgstr "%(expiration_days)s dĆas."
-#: contentcuration/templates/registration/activation_email.html:104
+#: contentcuration/templates/registration/activation_email.html:103
msgid "ACTIVATE"
msgstr "ACTIVAR"
@@ -539,27 +277,31 @@ msgstr "Ha solicitado un enlace para restablecer la contraseƱa en %(site_name)s
msgid "Please activate your account by following the link below:"
msgstr "Por favor, active su cuenta a travƩs del siguiente enlace:"
-#: contentcuration/templates/registration/channel_published_email.txt:6
+#: contentcuration/templates/registration/channel_published_email.html:12
#, python-format
-msgid "%(channel_name)s has finished publishing! Here is the channel token (for importing it into Kolibri):"
-msgstr "%(channel_name)s se ha terminado de publicar. AquĆ estĆ” el token del canal (para importarlo en Kolibri):"
+msgid "%(channel_name)s"
+msgstr "%(channel_name)s"
+
+#: contentcuration/templates/registration/channel_published_email.html:12
+msgid "has finished publishing! Here is the channel token (for importing it into Kolibri):"
+msgstr "se ha terminado de publicar. AquĆ estĆ” el token del canal (para importarlo en Kolibri):"
-#: contentcuration/templates/registration/channel_published_email.txt:8
+#: contentcuration/templates/registration/channel_published_email.html:15
#, python-format
msgid "Token: %(channel_token)s"
msgstr "Token: %(channel_token)s"
-#: contentcuration/templates/registration/channel_published_email.txt:10
+#: contentcuration/templates/registration/channel_published_email.html:17
#, python-format
msgid "ID (for Kolibri version 0.6.0 and below): %(channel_id)s"
msgstr "ID (para Kolibri versiĆ³n 0.6.0 e inferior): %(channel_id)s"
-#: contentcuration/templates/registration/channel_published_email.txt:12
+#: contentcuration/templates/registration/channel_published_email.html:20
#, python-format
msgid "Version notes: %(notes)s"
msgstr "Notas de la versiĆ³n: %(notes)s"
-#: contentcuration/templates/registration/channel_published_email.txt:21
+#: contentcuration/templates/registration/channel_published_email.html:28
msgid "You are receiving this email because you are subscribed to this channel."
msgstr "EstĆ” recibiendo este mensaje porque tiene la suscripciĆ³n activa para este canal."
@@ -588,23 +330,23 @@ msgstr "El enlace de restablecimiento de contraseƱa no es vƔlido, posiblemente
msgid "Request a new password reset."
msgstr "Solicitar un restablecimiento de contraseƱa nuevo."
-#: contentcuration/templates/registration/password_reset_email.html:92
+#: contentcuration/templates/registration/password_reset_email.html:91
msgid "You are receiving this e-mail because you requested a password reset for your user account at"
msgstr "EstĆ” recibiendo este correo electrĆ³nico porque ha pedido restablecer la contraseƱa para su cuenta en"
-#: contentcuration/templates/registration/password_reset_email.html:98
+#: contentcuration/templates/registration/password_reset_email.html:97
msgid "Reset my Password"
msgstr "Restablecer mi contraseƱa"
-#: contentcuration/templates/registration/password_reset_email.html:101
+#: contentcuration/templates/registration/password_reset_email.html:100
msgid "Please click the button below and choose a new password."
msgstr "Por favor, haga clic en el botĆ³n de abajo y escriba una contraseƱa nueva."
-#: contentcuration/templates/registration/password_reset_email.html:102
+#: contentcuration/templates/registration/password_reset_email.html:101
msgid "Your username is"
msgstr "El nombre de usuario es"
-#: contentcuration/templates/registration/password_reset_email.html:104
+#: contentcuration/templates/registration/password_reset_email.html:103
msgid "RESET"
msgstr "RESTABLECER"
@@ -647,6 +389,141 @@ msgstr "Ha solicitado un restablecimiento de contraseƱa en %(site_name)s sin pr
msgid "Please create an account by following the link below:"
msgstr "Por favor, cree una cuenta usando el siguiente enlace:"
+#: contentcuration/templates/registration/welcome_new_user_email.html:78
+msgid "Welcome to Kolibri Studio!"
+msgstr "Ā”Le damos la bienvenida a Kolibri Studio!"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:82
+#, python-format
+msgid "\n"
+" We're delighted to introduce you to Kolibri Studio , our curricular tool to add,\n"
+" organize, and manage your own resources or those from the Kolibri Content Library.\n"
+" "
+msgstr "\n"
+" Estamos encantados de presentarle Kolibri Studio , nuestra herramienta curricular para aƱadir,\n"
+" organizar y gestionar sus propios recursos, o los de la Biblioteca de Contenido de Kolibri.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:87
+msgid "View the Kolibri Content Library"
+msgstr "Ver la Biblioteca de Contenido de Kolibri"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:93
+msgid "\n"
+" Using Kolibri Studio, you can explore pre-organized collections of open educational resources (OER), and bundle,\n"
+" tag, differentiate, re-order, and distribute them into custom channels.\n"
+" "
+msgstr "\n"
+" Utilizando Kolibri Studio, puede explorar colecciones pre-organizadas de recursos educativos abiertos (OER), y empaquetar,\n"
+" etiquetar, y reorganizarlos en canales personalizados.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:99
+msgid "\n"
+" Using an admin account, you can then publish and import these custom channels--either your own or those shared\n"
+" with you -- into Kolibri with a unique \"token\" generated for each channel.\n"
+" "
+msgstr "\n"
+" Usando la cuenta de administrador puede publicar e importar estos canales personalizados (propios o compartidos)\n"
+" en Kolibri, utilizando el \"token\" Ćŗnico generado para cada canal.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:107
+msgid "\n"
+" Browse through the list of resources below* to learn more about Kolibri Studio and to begin creating your own\n"
+" custom channels:\n"
+" "
+msgstr "\n"
+" Puede navegar a travƩs de la lista de recursos listados abajo* para aprender mƔs sobre Kolibri Studio y empezar a crear sus propios\n"
+" canales personalizados:\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:115
+msgid "Kolibri Studio User Guide"
+msgstr "GuĆa de usuario de Kolibri Studio"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:120
+msgid "Content integration guide:"
+msgstr "GuĆa de integraciĆ³n de contenido:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:122
+msgid "\n"
+" Information on licensing, compatible formats, technical integration and more.\n"
+" "
+msgstr "\n"
+" InformaciĆ³n sobre licencias, formatos compatibles, integraciĆ³n tĆ©cnica y mĆ”s.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:125
+msgid "\n"
+" Note that if you are adding a small number of resources, technical integration is not necessary. \n"
+" "
+msgstr "\n"
+" Tener en cuenta que si estĆ” trabajando con un pequeƱo nĆŗmero de recursos, la integraciĆ³n tĆ©cnica no es necesaria. \n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:130
+msgid "Step by step tutorials:"
+msgstr "Tutoriales paso a paso:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:133
+msgid "Video format:"
+msgstr "Formato de vĆdeo:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:135
+msgid "Using Kolibri Studio: Your Content Workspace for Kolibri"
+msgstr "Utilizando Kolibri Studio: Su espacio de trabajo para el contenido de Kolibri"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:137
+msgid "(*also available in French and Arabic)"
+msgstr "(*tambiƩn disponible en francƩs y Ɣrabe)"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:140
+msgid "Slide gif format:"
+msgstr "Formato de diapositiva GIF:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:144
+msgid "Step by step Studio tutorial"
+msgstr "Tutorial paso a paso de Studio "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:151
+msgid "Video compression instructions:"
+msgstr "Instrucciones de compresiĆ³n de vĆdeo:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:153
+msgid "\n"
+" For optimal results, videos should be compressed in order to achieve small file sizes. Compression ensures\n"
+" that the videos are well suited for offline distribution and playback on all Kolibri devices.\n"
+" "
+msgstr "\n"
+" Hay que comprimir videos para lograr pequeƱos tamaƱos de archivo para el mejor resultado. La compresiĆ³n asegura\n"
+" que los vĆdeos son apropiados para la distribuciĆ³n sin conexiĆ³n y la reproducciĆ³n en todos los dispositivos Kolibri.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:158
+msgid "View the guide to video compression"
+msgstr "Ver la guĆa de la compresiĆ³n de vĆdeo"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:165
+msgid "If you need support with Kolibri Studio, please reach out to us on our Community Forum."
+msgstr "Si necesita soporte para trabajar con Kolibri Studio, por favor contƔctenos en nuestro Foro de la Comunidad."
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:167
+msgid "Access the Community Forum"
+msgstr "Acceder al Foro de la Comunidad"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:171
+msgid "Thank You!"
+msgstr "Ā”Gracias!"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:178
+msgid "*resources are presented in English"
+msgstr "*los recursos estƔn en inglƩs"
+
+#: contentcuration/templates/registration/welcome_new_user_email_subject.txt:1
+msgid "Thank you for activating your Kolibri Studio account! Let's get started..."
+msgstr "Ā”Gracias por activar la cuenta de Kolibri Studio! Empecemos..."
+
#: contentcuration/templates/settings/account_deleted_user_email.txt:5
#, python-format
msgid "Your %(email)s account on %(site_name)s has been deleted."
@@ -709,188 +586,107 @@ msgstr "Para usar Kolibri Studio, recomendamos usar Firefox o Chrome."
msgid "You can also try updating your current browser."
msgstr "TambiƩn puedes actualizar tu navegador actual."
-#: contentcuration/templatetags/license_tags.py:10
+#: contentcuration/templatetags/license_tags.py:11
msgid "The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials."
msgstr "La Licencia de AtribuciĆ³n permite a otros distribuir, remezclar, retocar, y crear a partir de tu obra, incluso con fines comerciales, siempre y cuando te den crĆ©dito por la creaciĆ³n original. Esta es la mĆ”s flexible de las licencias ofrecidas. Se recomienda para la mĆ”xima difusiĆ³n y utilizaciĆ³n de los materiales licenciados."
-#: contentcuration/templatetags/license_tags.py:15
+#: contentcuration/templatetags/license_tags.py:16
msgid "The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to \"copyleft\" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects."
msgstr "La Licencia de AtribuciĆ³n-CompartirIgual permite a otros remezclar, retocar, y crear a partir de tu obra, incluso con fines comerciales, siempre y cuando te den crĆ©dito y licencien sus nuevas creaciones bajo las mismas condiciones. Esta licencia suele ser comparada con las licencias \"copyleft\" de software libre y de cĆ³digo abierto. Todas las nuevas obras basadas en la tuya portarĆ”n la misma licencia, asĆ que cualesquiera obras derivadas permitirĆ”n tambiĆ©n el uso comercial. Esa es la licencia que usa Wikipedia, y se recomienda para materiales que se beneficiarĆan de la incorporaciĆ³n de contenido de Wikipedia y de proyectos con licencias similares."
-#: contentcuration/templatetags/license_tags.py:25
+#: contentcuration/templatetags/license_tags.py:26
msgid "The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you."
msgstr "La Licencia de AtribuciĆ³n-SinDerivadas permite la redistribuciĆ³n, comercial o no comercial, siempre y cuando la obra circule Ćntegra y sin cambios, dĆ”ndote crĆ©dito."
-#: contentcuration/templatetags/license_tags.py:28
+#: contentcuration/templatetags/license_tags.py:29
msgid "The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms."
msgstr "La Licencia de AtribuciĆ³n-NoComercial permite a otros distribuir, remezclar, retocar, y crear a partir de tu obra de manera no comercial y, a pesar de que sus nuevas obras deben siempre mencionarte y mantenerse sin fines comerciales, no estĆ”n obligados a licenciar sus obras derivadas bajo las mismas condiciones."
-#: contentcuration/templatetags/license_tags.py:32
+#: contentcuration/templatetags/license_tags.py:33
msgid "The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms."
msgstr "La Licencia de AtribuciĆ³n-NoComercial-CompartirIgual permite a otros distribuir, remezclar, retocar, y crear a partir de tu obra de modo no comercial, siempre y cuando te den crĆ©dito y licencien sus nuevas creaciones bajo las mismas condiciones."
-#: contentcuration/templatetags/license_tags.py:36
+#: contentcuration/templatetags/license_tags.py:37
msgid "The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially."
msgstr "La Licencia de AtribuciĆ³n-NoComercial-SinDerivadas es la mĆ”s restrictiva de nuestras seis licencias principales, permitiendo a otros solo descargar tu obra y compartirla con otros siempre y cuando te den crĆ©dito, pero no permiten cambiarlas de forma alguna ni usarlas comercialmente."
-#: contentcuration/templatetags/license_tags.py:40
+#: contentcuration/templatetags/license_tags.py:41
msgid "The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty."
msgstr "La Licencia Todos los Derechos Reservados, indica que el titular reserva, o mantiene para uso propio, todos los derechos previstos por la ley de derechos de autor bajo un solo tratado de derechos especĆfico."
-#: contentcuration/templatetags/license_tags.py:43
+#: contentcuration/templatetags/license_tags.py:44
msgid "Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights."
msgstr "Una obra de Dominio PĆŗblico ha sido identificada como libre de restricciones conocidas en virtud del derecho de autor, incluyendo todos los derechos conexos."
-#: contentcuration/templatetags/license_tags.py:46
+#: contentcuration/templatetags/license_tags.py:47
msgid "Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails."
msgstr "Permisos Especiales es una licencia personalizada para utilizar cuando las licencias actuales no se aplican al contenido. El titular de esta licencia es responsable de crear una descripciĆ³n de lo que implica esta licencia."
-#: contentcuration/templatetags/translation_tags.py:26
-msgid "100% Correct"
-msgstr "100% correcto"
-
-#: contentcuration/templatetags/translation_tags.py:27
-msgid "10 in a row"
-msgstr "10 consecutivas"
-
-#: contentcuration/templatetags/translation_tags.py:28
-msgid "2 in a row"
-msgstr "2 consecutivas"
-
-#: contentcuration/templatetags/translation_tags.py:29
-msgid "3 in a row"
-msgstr "3 consecutivas"
-
-#: contentcuration/templatetags/translation_tags.py:30
-msgid "5 in a row"
-msgstr "5 consecutivas"
-
-#: contentcuration/templatetags/translation_tags.py:31
-msgid "M of N..."
-msgstr "M de N..."
-
-#: contentcuration/templatetags/translation_tags.py:32
-msgid "CC BY"
-msgstr "CC BY"
-
-#: contentcuration/templatetags/translation_tags.py:33
-msgid "CC BY-SA"
-msgstr "CC BY-SA"
-
-#: contentcuration/templatetags/translation_tags.py:34
-msgid "CC BY-ND"
-msgstr "CC BY-ND"
-
-#: contentcuration/templatetags/translation_tags.py:35
-msgid "CC BY-NC"
-msgstr "CC BY-NC"
-
-#: contentcuration/templatetags/translation_tags.py:36
-msgid "CC BY-NC-SA"
-msgstr "CC BY-NC-SA"
-
-#: contentcuration/templatetags/translation_tags.py:37
-msgid "CC BY-NC-ND"
-msgstr "CC BY-NC-ND"
-
-#: contentcuration/templatetags/translation_tags.py:38
-msgid "All Rights Reserved"
-msgstr "Todos los derechos reservados"
-
-#: contentcuration/templatetags/translation_tags.py:39
-msgid "Public Domain"
-msgstr "Dominio PĆŗblico"
-
-#: contentcuration/templatetags/translation_tags.py:40
-msgid "Special Permissions"
-msgstr "Permisos Especiales"
-
-#: contentcuration/templatetags/translation_tags.py:49
-#, python-format
-msgid "%(filesize)s %(unit)s"
-msgstr "%(filesize)s %(unit)s"
-
-#: contentcuration/utils/csv_writer.py:138
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:45
+#: contentcuration/utils/csv_writer.py:108
msgid "No Channel"
msgstr "No hay canales"
-#: contentcuration/utils/csv_writer.py:139
+#: contentcuration/utils/csv_writer.py:46
msgid "No resource"
msgstr "No hay recursos"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Channel"
msgstr "Canal"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Title"
msgstr "TĆtulo"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Kind"
msgstr "Tipo"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Filename"
msgstr "Nombre del fichero"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
+msgid "File Size"
+msgstr "TamaƱo del fichero"
+
+#: contentcuration/utils/csv_writer.py:71
msgid "URL"
msgstr "URL"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Description"
msgstr "DescripciĆ³n"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Author"
msgstr "Autor"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Language"
msgstr "Idioma"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License"
msgstr "Licencia"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License Description"
msgstr "DescripciĆ³n de la licencia"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Copyright Holder"
msgstr "Titular de derechos de autor"
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "No Resource"
msgstr "No hay recursos"
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "Staged File"
msgstr "Archivo enviado"
-#: contentcuration/utils/format.py:15
-msgid "B"
-msgstr "B"
-
-#: contentcuration/utils/format.py:17
-msgid "KB"
-msgstr "KB"
-
-#: contentcuration/utils/format.py:19
-msgid "MB"
-msgstr "MB"
-
-#: contentcuration/utils/format.py:21
-msgid "GB"
-msgstr "GB"
-
-#: contentcuration/utils/format.py:23
-msgid "TB"
-msgstr "TB"
-
#: contentcuration/utils/incidents.py:7
msgid "There was a problem with a third-party service. This means certain operations might be blocked. We appreciate your patience while these issues are being resolved."
msgstr "Hubo un problema con un servicio externo. Esto significa que ciertas operaciones podrĆan ser bloqueadas. Agradecemos su paciencia mientras se resuelven estos problemas."
@@ -915,23 +711,26 @@ msgstr "Hubo un problema con un servicio externo. Esto significa que no es posib
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here "
msgstr "Estamos encontrando problemas con nuestro centro de datos. Esto significa que puede encontrar problemas de red mientras utiliza Studio. Agradecemos su paciencia mientras se resuelvenn estos problemas. Para comprobar el estado de este servicio visite este enlace "
-#: contentcuration/utils/publish.py:57
+#: contentcuration/utils/publish.py:96
msgid "Kolibri Studio Channel Published"
msgstr "Canal publicado en Kolibri Studio"
-#: contentcuration/views/public.py:63 contentcuration/views/public.py:74
-msgid "Api endpoint {} is not available"
-msgstr "API endpoint {} no disponible"
-
-#: contentcuration/views/public.py:76
-msgid "No channel matching {} found"
-msgstr "NingĆŗn canal con {} encontrado"
-
-#: contentcuration/views/settings.py:110
+#: contentcuration/views/settings.py:111
msgid "Kolibri Studio issue report"
msgstr "Informe de incidencias de Kolibri Studio"
-#: contentcuration/views/settings.py:144
+#: contentcuration/views/settings.py:143
msgid "Kolibri Studio account deleted"
msgstr "Cuenta de Kolibri Studio eliminada"
+#: kolibri_public/views.py:220
+msgid "Resource"
+msgstr "Recurso"
+
+#: kolibri_public/views_v1.py:63 kolibri_public/views_v1.py:74
+msgid "Api endpoint {} is not available"
+msgstr "API endpoint {} no disponible"
+
+#: kolibri_public/views_v1.py:76
+msgid "No channel matching {} found"
+msgstr "NingĆŗn canal con {} encontrado"
diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/README.md b/contentcuration/locale/fr_FR/LC_MESSAGES/README.md
new file mode 100644
index 0000000000..0f82b94d50
--- /dev/null
+++ b/contentcuration/locale/fr_FR/LC_MESSAGES/README.md
@@ -0,0 +1 @@
+The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.csv b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.csv
index 227998b024..f70b71ff70 100644
--- a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.csv
+++ b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.csv
@@ -74,13 +74,13 @@
"AccountCreated.accountCreatedTitle","Account successfully created","
-- CONTEXT --
","Compte crĆ©Ć© avec succĆØs"
-"AccountCreated.continueToSignIn","Continue to sign-in","
+"AccountCreated.backToLogin","Continue to sign-in page","
-- CONTEXT --
-","Continuer pour vous connecter"
+","Continuer vers la page de connexion"
"AccountDeleted.accountDeletedTitle","Account successfully deleted","
-- CONTEXT --
","Compte supprimĆ© avec succĆØs"
-"AccountDeleted.continueToSignIn","Continue to sign-in page","
+"AccountDeleted.backToLogin","Continue to sign-in page","
-- CONTEXT --
","Continuer vers la page de connexion"
"AccountNotActivated.requestNewLink","Request a new activation link","
@@ -287,9 +287,6 @@
"BrowsingCard.coach","Resource for coaches","
-- CONTEXT --
","Ressource pour les Ć©ducateurs"
-"BrowsingCard.goToPluralLocationsAction","In {count, number} {count, plural, one {location} other {locations}}","
--- CONTEXT --
-","Dans {count, number} {count, plural, one {emplacement} other {emplacements}}"
"BrowsingCard.goToSingleLocationAction","Go to location","
-- CONTEXT --
","Aller Ć l'emplacement"
@@ -1272,6 +1269,9 @@ A type of math category. See https://en.wikipedia.org/wiki/Algebra","AlgĆØbre"
"CommonMetadataStrings.all","All","
-- CONTEXT --
A label for everything in the group of activities.","Tous"
+"CommonMetadataStrings.allContent","Viewed in its entirety","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners studied it all, for example they saw all pages of a document.","Vu en totalitƩ"
"CommonMetadataStrings.allLevelsBasicSkills","All levels -- basic skills","
-- CONTEXT --
Refers to a type of educational level.","Tous les niveaux -- compƩtences de base"
@@ -1323,6 +1323,9 @@ Science category type. See https://en.wikipedia.org/wiki/Chemistry","Chimie"
"CommonMetadataStrings.civicEducation","Civic education","
-- CONTEXT --
Category type. Civic education is the study of the rights and obligations of citizens in society. See https://en.wikipedia.org/wiki/Civics","Ćducation civique"
+"CommonMetadataStrings.completeDuration","When time spent is equal to duration","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners spent given time studying it.","Lorsque le temps passĆ© est Ć©gal Ć la durĆ©e"
"CommonMetadataStrings.completion","Completion","CommonMetadataStrings.completion
-- CONTEXT --
@@ -1342,6 +1345,9 @@ Category type. See https://en.wikipedia.org/wiki/Everyday_life","Vie quotidienne
"CommonMetadataStrings.dance","Dance","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Dance","Danse"
+"CommonMetadataStrings.determinedByResource","Determined by the resource","
+-- CONTEXT --
+One of the completion criteria types. Typically used for embedded html5/h5p resources that contain their own completion criteria, for example reaching a score in an educational game.","DƩterminƩ par la ressource"
"CommonMetadataStrings.digitalLiteracy","Digital literacy","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Digital_literacy","AlphabƩtisation numƩrique"
@@ -1363,6 +1369,9 @@ Category type. See https://en.wikipedia.org/wiki/Entrepreneurship","Entreprenari
"CommonMetadataStrings.environment","Environment","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Environmental_studies","Environnement"
+"CommonMetadataStrings.exactTime","Time to complete","
+-- CONTEXT --
+One of the completion criteria types. A subset of ""When time spent is equal to duration"". For example, for an audio resource with this criteria, learnes need to hear the whole length of audio for the resource to be considered complete.","DƩlai d'exƩcution"
"CommonMetadataStrings.explore","Explore","
-- CONTEXT --
Resource and filter label for the type of learning activity. Translate as a VERB","Explorer"
@@ -1378,6 +1387,9 @@ Category type","Pour les enseignants"
"CommonMetadataStrings.geometry","Geometry","
-- CONTEXT --
Category type.","GƩomƩtrie"
+"CommonMetadataStrings.goal","When goal is met","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners reached a given goal, for example 100% correct.","Lorsque l'objectif est atteint"
"CommonMetadataStrings.guides","Guides","
-- CONTEXT --
Category label in the Kolibri resources library; refers to any guide-type material for teacher professional development.","Guides"
@@ -1428,6 +1440,9 @@ Refers to a level of learning. Approximately corresponds to the first half of pr
"CommonMetadataStrings.lowerSecondary","Lower secondary","
-- CONTEXT --
Refers to a level of learning. Approximately corresponds to the first half of secondary school (high school).","Secondaire infƩrieur"
+"CommonMetadataStrings.masteryMofN","Goal: {m} out of {n}","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners answered m questions out of n correctly.","Objectif : {m} sur {n}"
"CommonMetadataStrings.mathematics","Mathematics","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Mathematics","MathƩmatiques"
@@ -1465,6 +1480,9 @@ Category type. See https://en.wikipedia.org/wiki/Political_science.","Science po
"CommonMetadataStrings.practice","Practice","
-- CONTEXT --
Resource and filter label for the type of learning activity with questions and answers. Translate as a VERB","S'entraƮner"
+"CommonMetadataStrings.practiceQuiz","Practice quiz","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria represents a quiz.","Quiz pratique"
"CommonMetadataStrings.preschool","Preschool","
-- CONTEXT --
Refers to a level of education offered to children before they begin compulsory education at primary school.
@@ -1491,6 +1509,9 @@ School subject category","Lire et Ć©crire"
"CommonMetadataStrings.readingComprehension","Reading comprehension","
-- CONTEXT --
Category type.","ComprƩhension de texte"
+"CommonMetadataStrings.reference","Reference material","
+-- CONTEXT --
+One of the completion criteria types. Progress made on a resource with this criteria is not tracked.","Document de rƩfƩrence"
"CommonMetadataStrings.reflect","Reflect","
-- CONTEXT --
Resource and filter label for the type of learning activity. Translate as a VERB","SymƩtrie axiale"
@@ -1614,27 +1635,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ćcriture"
"CommunityStandardsModal.studioItem5","Hosting. Uploading your own materials (limited to materials you know are appropriately licensed to do so) from a local hard drive or other locations on the internet","
-- CONTEXT --
","L'hĆ©bergement. TĆ©lĆ©versez vos propres supports (limitĆ© aux supports dont la licence appropriĆ©e vous a Ć©tĆ© accordĆ©e) Ć partir d'un disque dur local ou d'autres emplacements sur Internet"
-"CompletionOptions.allContent","Viewed in its entirety","
--- CONTEXT --
-","Vu en totalitƩ"
-"CompletionOptions.completeDuration","When time spent is equal to duration","
--- CONTEXT --
-","Lorsque le temps passĆ© est Ć©gal Ć la durĆ©e"
-"CompletionOptions.determinedByResource","Determined by the resource","
--- CONTEXT --
-","DƩterminƩ par la ressource"
-"CompletionOptions.exactTime","Time to complete","
--- CONTEXT --
-","DƩlai d'exƩcution"
-"CompletionOptions.goal","When goal is met","
--- CONTEXT --
-","Lorsque l'objectif est atteint"
-"CompletionOptions.practiceQuiz","Practice quiz","
--- CONTEXT --
-","Quiz d'entraƮnement"
-"CompletionOptions.reference","Reference material","
+"CompletionOptions.learnersCanMarkComplete","Allow learners to mark as complete","
-- CONTEXT --
-","Document de rƩfƩrence"
+","Autoriser les apprenants Ć marquer le contenu comme Ć©tant terminĆ©"
"CompletionOptions.referenceHint","Progress will not be tracked on reference material unless learners mark it as complete","
-- CONTEXT --
","Les progrĆØs ne seront pas suivis sur le document de rĆ©fĆ©rence Ć moins que les apprenants ne le marquent comme Ć©tant terminĆ©"
@@ -1926,9 +1929,27 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ćcriture"
"ContentNodeChangedIcon.isUpdatedTopic","Folder has been updated since last publish","
-- CONTEXT --
","Le dossier a Ć©tĆ© mis Ć jour depuis la derniĆØre publication"
+"ContentNodeCopyTaskProgress.copyErrorTopic","Some resources failed to copy","
+-- CONTEXT --
+","Certaines ressources n'ont pu ĆŖtre copiĆ©es"
+"ContentNodeEditListItem.copiedSnackbar","Copy operation complete","
+-- CONTEXT --
+","Copie terminƩe"
+"ContentNodeEditListItem.creatingCopies","Copying...","
+-- CONTEXT --
+","Copie en cours..."
"ContentNodeEditListItem.optionsTooltip","Options","
-- CONTEXT --
","Options"
+"ContentNodeEditListItem.removeNode","Remove","
+-- CONTEXT --
+","Supprimer"
+"ContentNodeEditListItem.retryCopy","Retry","
+-- CONTEXT --
+","RĆ©essayer"
+"ContentNodeEditListItem.undo","Undo","
+-- CONTEXT --
+","Annuler"
"ContentNodeIcon.audio","Audio","
-- CONTEXT --
","Audio"
@@ -1956,12 +1977,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ćcriture"
"ContentNodeLearningActivityIcon.multipleLearningActivities","Multiple learning activities","
-- CONTEXT --
","ActivitƩs d'apprentissage multiples"
-"ContentNodeLearningActivityIcon.topic","Folder","
--- CONTEXT --
-","Dossier"
"ContentNodeListItem.coachTooltip","Resource for coaches","
-- CONTEXT --
","Ressource pour les Ć©ducateurs"
+"ContentNodeListItem.copyingError","Copy failed.","
+-- CONTEXT --
+","Ćchec de la copie."
"ContentNodeListItem.copyingTask","Copying","
-- CONTEXT --
","Copie en cours"
@@ -2013,7 +2034,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ćcriture"
"ContentNodeOptions.newSubtopic","New folder","
-- CONTEXT --
","Nouveau dossier"
-"ContentNodeOptions.remove","Remove","
+"ContentNodeOptions.remove","Delete","
-- CONTEXT --
","Supprimer"
"ContentNodeOptions.removedFromClipboard","Deleted from clipboard","
@@ -2121,12 +2142,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ćcriture"
"CountryField.noCountriesFound","No countries found","
-- CONTEXT --
","Aucun pays trouvƩ"
-"Create.ToSCheck","I have read and agree to the terms of service","
+"Create.ToSRequiredMessage","Please accept our terms of service and policy","
-- CONTEXT --
-","J'ai lu et j'accepte les conditions d'utilisation"
-"Create.ToSRequiredMessage","Please accept our terms of service","
+","Veuillez accepter nos conditions de service et notre politique"
+"Create.agreement","I have read and agree to terms of service and the privacy policy","
-- CONTEXT --
-","Veuillez accepter nos conditions d'utilisation"
+","J'ai lu et j'accepte ainsi les conditions de service et la politique de confidentialitƩ"
"Create.backToLoginButton","Sign in","
-- CONTEXT --
","Se connecter"
@@ -2217,12 +2238,6 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ćcriture"
"Create.personalDemoSourceOption","Personal demo","
-- CONTEXT --
","DĆ©mo personnelle"
-"Create.privacyPolicyCheck","I have read and agree to the privacy policy","
--- CONTEXT --
-","J'ai lu et j'accepte la politique de confidentialitƩ"
-"Create.privacyPolicyRequiredMessage","Please accept our privacy policy","
--- CONTEXT --
-","Veuillez accepter notre politique de confidentialitƩ"
"Create.registrationFailed","There was an error registering your account. Please try again","
-- CONTEXT --
","Une erreur s'est produite lors de l'enregistrement de votre compte. Veuillez rƩessayer"
@@ -2259,12 +2274,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ćcriture"
"Create.usageLabel","How do you plan on using Kolibri Studio (check all that apply)","
-- CONTEXT --
","Comment comptez-vous utiliser Kolibri Studio (cochez tout ce qui s'applique)"
-"Create.viewPrivacyPolicyLink","View privacy policy","
+"Create.viewPrivacyPolicyLink","View Privacy Policy","
-- CONTEXT --
-","Voir la politique de confidentialitƩ"
-"Create.viewToSLink","View terms of service","
+","Voir la Politique de ConfidentialitƩ"
+"Create.viewToSLink","View Terms of Service","
-- CONTEXT --
-","Voir les conditions d'utilisation"
+","Voir les Conditions de service"
"Create.websiteSourceOption","Learning Equality website","
-- CONTEXT --
","Site Web de Learning Equality "
@@ -2405,6 +2420,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ćcriture"
"Details.authorsLabel","Authors","
-- CONTEXT --
","Auteurs"
+"Details.categoriesHeading","Categories","
+-- CONTEXT --
+","CatƩgories"
"Details.coachDescription","Resources for coaches are only visible to coaches in Kolibri","
-- CONTEXT --
","Sur Kolibri, les ressources destinƩes aux Ʃducateurs ne sont visibles que par eux"
@@ -2429,6 +2447,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Ćcriture"
"Details.languagesHeading","Languages","
-- CONTEXT --
","Langues"
+"Details.levelsHeading","Levels","
+-- CONTEXT --
+","Niveaux"
"Details.licensesLabel","Licenses","
-- CONTEXT --
","Licences"
@@ -2519,9 +2540,6 @@ Heading for the section in the resource editing window","AchĆØvement"
"DetailsTabView.languageHelpText","Leave blank to use the folder language","
-- CONTEXT --
","Laisser vide pour utiliser la langue du dossier"
-"DetailsTabView.learnersCanMarkComplete","Allow learners to mark as complete","
--- CONTEXT --
-","Autoriser les apprenants Ć marquer le contenu comme Ć©tant terminĆ©"
"DetailsTabView.noTagsFoundText","No results found for ""{text}"". Press 'Enter' key to create a new tag","
-- CONTEXT --
","Aucun rƩsultat trouvƩ pour ""{text}"". Appuyez sur la touche 'EntrƩe' pour crƩer une nouvelle Ʃtiquette"
@@ -3012,6 +3030,9 @@ Heading for the section in the resource editing window","AchĆØvement"
"MainNavigationDrawer.administrationLink","Administration","
-- CONTEXT --
","Administration"
+"MainNavigationDrawer.changeLanguage","Change language","
+-- CONTEXT --
+","Changer la langue"
"MainNavigationDrawer.channelsLink","Channels","
-- CONTEXT --
","ChaƮnes"
@@ -3174,9 +3195,6 @@ Heading for the section in the resource editing window","AchĆØvement"
"PoliciesModal.checkboxText","I have agreed to the above terms","
-- CONTEXT --
","J'ai acceptƩ les conditions ci-dessus"
-"PoliciesModal.checkboxValidationErrorMessage","Field is required","
--- CONTEXT --
-","Ce champ est obligatoire"
"PoliciesModal.closeButton","Close","
-- CONTEXT --
","Fermer"
@@ -3207,9 +3225,12 @@ Heading for the section in the resource editing window","AchĆØvement"
"ProgressModal.syncError","Last attempt to sync failed","
-- CONTEXT --
","Ćchec de la derniĆØre tentative de synchronisation"
-"ProgressModal.syncHeader","Syncing channel","
+"ProgressModal.syncHeader","Syncing resources","
+-- CONTEXT --
+","Synchronisation des ressources"
+"ProgressModal.syncedSnackbar","Resources synced","
-- CONTEXT --
-","Synchronisation de la chaƮne"
+","Ressources synchronisƩes"
"ProgressModal.unpublishedText","Unpublished","
-- CONTEXT --
","Non publiƩe"
@@ -3525,9 +3546,6 @@ Heading for the section in the resource editing window","AchĆØvement"
"ResourcePanel.coachResources","Resources for coaches","
-- CONTEXT --
","Ressources pour les Ć©ducateurs"
-"ResourcePanel.completion","Completion","
--- CONTEXT --
-","AchĆØvement"
"ResourcePanel.copyrightHolder","Copyright holder","
-- CONTEXT --
","Titulaire des droits d'auteur"
@@ -3552,27 +3570,37 @@ Heading for the section in the resource editing window","AchĆØvement"
"ResourcePanel.license","License","
-- CONTEXT --
","Licence"
-"ResourcePanel.masteryMofN","Goal: {m} out of {n}","
--- CONTEXT --
-","Objectif : {m} sur {n}"
"ResourcePanel.nextSteps","Next steps","
-- CONTEXT --
","Ćtapes suivantes"
-"ResourcePanel.noCopyrightHolderError","Missing copyright holder","
+"ResourcePanel.noCompletionCriteriaError","Completion criteria are required","ResourcePanel.noCompletionCriteriaError
+
-- CONTEXT --
-","Titulaire des droits d'auteur manquant"
-"ResourcePanel.noFilesError","Missing files","
+Error message notification when a specific metadata is missing.","Les critĆØres d'achĆØvement sont obligatoires"
+"ResourcePanel.noCopyrightHolderError","Copyright holder is required","
-- CONTEXT --
-","Fichiers manquants"
-"ResourcePanel.noLicenseDescriptionError","Missing license description","
+","Le propriƩtaire des droits d'auteur est obligatoire"
+"ResourcePanel.noDurationError","Duration is required","
+-- CONTEXT --
+","La durƩe est requise"
+"ResourcePanel.noFilesError","File is required","ResourcePanel.noFilesError
+
+-- CONTEXT --
+Error message notification when a file is missing.","Le fichier est obligatoire"
+"ResourcePanel.noLearningActivityError","Learning activity is required","
+-- CONTEXT --
+","L'activitƩ d'apprentissage est requise"
+"ResourcePanel.noLicenseDescriptionError","License description is required","ResourcePanel.noLicenseDescriptionError
+
-- CONTEXT --
-","Description de licence manquante"
-"ResourcePanel.noLicenseError","Missing license","
+Error message notification when a specific metadata is missing.","La description de la licence est obligatoire"
+"ResourcePanel.noLicenseError","License is required","
-- CONTEXT --
-","Licence manquante"
-"ResourcePanel.noMasteryModelError","Missing mastery criteria","
+","La licence est obligatoire"
+"ResourcePanel.noMasteryModelError","Mastery criteria are required","ResourcePanel.noMasteryModelError
+
-- CONTEXT --
-","CritĆØres de maĆ®trise manquants"
+Error message notification when a specific metadata is missing.","Les critĆØres de maĆ®trise sont obligatoires"
"ResourcePanel.noQuestionsError","Exercise is empty","
-- CONTEXT --
","L'exercice est vide"
@@ -3900,36 +3928,42 @@ Heading for the section in the resource editing window","AchĆØvement"
"SyncResourcesModal.confirmSyncModalTitle","Confirm sync","
-- CONTEXT --
","Confirmer la synchronisation"
+"SyncResourcesModal.confirmSyncModalWarningExplainer","Warning: this will overwrite any changes you have made to copied or imported resources.","
+-- CONTEXT --
+","Attention : cette opƩration Ʃcrasera toutes les modifications que vous avez apportƩes aux ressources copiƩes ou importƩes."
"SyncResourcesModal.continueButtonLabel","Continue","
-- CONTEXT --
","Continuer"
"SyncResourcesModal.syncButtonLabel","Sync","
-- CONTEXT --
","Synchroniser"
-"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints","
+"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints in exercises and quizzes","
-- CONTEXT --
-","Mettre Ć jour les questions, les rĆ©ponses et les indices"
+","Mettre Ć jour les questions, les rĆ©ponses et les indices dans les exercices et les quiz"
"SyncResourcesModal.syncExercisesTitle","Assessment details","
-- CONTEXT --
","DĆ©tails de l'Ć©valuation"
-"SyncResourcesModal.syncFilesExplainer","Update all file information","
+"SyncResourcesModal.syncFilesExplainer","Update all files, including: thumbnails, subtitles, and captions","
-- CONTEXT --
-","Mettre Ć jour toutes les informations du fichier"
+","Mettre Ć jour tous les fichiers, y compris les vignettes, les sous-titres et les lĆ©gendes"
"SyncResourcesModal.syncFilesTitle","Files","
-- CONTEXT --
","Fichiers"
-"SyncResourcesModal.syncModalExplainer","Sync and update your resources with their original source.","
+"SyncResourcesModal.syncModalExplainer","Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files.","
+-- CONTEXT --
+","La synchronisation des ressources dans Kolibri Studio met Ć jour les ressources copiĆ©es ou importĆ©es dans cette chaĆ®ne, y compris toutes les modifications apportĆ©es aux fichiers de ressources d'origine."
+"SyncResourcesModal.syncModalSelectAttributes","Select what you would like to sync:","
-- CONTEXT --
-","Synchronisez et mettez Ć jour vos ressources Ć partir de leur source d'origine."
+","SĆ©lectionnez ce que vous souhaitez synchroniser :"
"SyncResourcesModal.syncModalTitle","Sync resources","
-- CONTEXT --
","Synchroniser les ressources"
-"SyncResourcesModal.syncTagsExplainer","Update all tags","
+"SyncResourcesModal.syncResourceDetailsExplainer","Update information about the resource: learning activity, level, requirements, category, tags, audience, and source","
-- CONTEXT --
-","Mettre Ć jour toutes les Ć©tiquettes"
-"SyncResourcesModal.syncTagsTitle","Tags","
+","Mettre Ć jour les informations sur la ressource : activitĆ© d'apprentissage, niveau, exigences, catĆ©gorie, Ć©tiquettes, public et source"
+"SyncResourcesModal.syncResourceDetailsTitle","Resource details","
-- CONTEXT --
-","Ćtiquettes"
+","DĆ©tail de la ressource"
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer","Update resource titles and descriptions","
-- CONTEXT --
","Mettre Ć jour les titres et les descriptions des ressources"
@@ -4370,9 +4404,10 @@ Heading for the section in the resource editing window","AchĆØvement"
"TreeViewBase.noChangesText","No changes found in channel","
-- CONTEXT --
","Aucune modification trouvƩe dans la chaƮne"
-"TreeViewBase.noLanguageSetError","Missing channel language","
+"TreeViewBase.noLanguageSetError","Channel language is required","TreeViewBase.noLanguageSetError
+
-- CONTEXT --
-","Il manque la langue de la chaƮne"
+Error message notification when a specific metadata is missing.","La langue de la chaƮne est obligatoire"
"TreeViewBase.openTrash","Open trash","
-- CONTEXT --
","Ouvrir la corbeille"
@@ -4415,14 +4450,14 @@ Heading for the section in the resource editing window","AchĆØvement"
","Fichiers non supportƩs"
"Uploader.unsupportedFilesText","{count, plural,
=1 {# file will not be uploaded.}
- other {# files will not be uploaded.}}
+ other {# files will not be uploaded.}}
{extensionCount, plural,
=1 {Supported file type is}
other {Supported file types are}} {extensions}","
-- CONTEXT --
","{count, plural, one {}
=1 {# fichier ne sera pas tƩlƩversƩ.}
- other {# fichiers ne seront pas tƩlƩversƩs.}}
+ other {# fichiers ne seront pas tƩlƩversƩs.}}
{extensionCount, plural,
=1 {Le type de fichier supportƩ est}
other {Les types de fichiers supportƩs sont}} {extensions}"
@@ -4441,12 +4476,9 @@ Heading for the section in the resource editing window","AchĆØvement"
"UsingStudio.bestPractice2","It is preferable to create multiple small channels rather than one giant channel with many layers of folders.","
-- CONTEXT --
","Il est prƩfƩrable de crƩer plusieurs petites chaƮnes plutƓt qu'une chaƮne gƩante avec de nombreuses couches de dossiers."
-"UsingStudio.bestPractice3","Reload the page often to ensure your work is saved to the server and no network errors have occurred. Use CTRL+R on Linux/Windows or ā+R on Mac.","
+"UsingStudio.bestPractice3","Reload the page to confirm your work has been saved to the server. Use CTRL+R on Linux/Windows or ā+R on Mac.","
-- CONTEXT --
-","Rechargez souvent la page pour vous assurer que votre travail est enregistrĆ© sur le serveur et qu'aucune erreur de rĆ©seau ne s'est manifestĆ©e. Tapez CTRL+R sur Linux/Windows ou ā+R sur Mac."
-"UsingStudio.bestPractice4","Avoid simultaneous edits on the same channel. Channels should not be edited by multiple users at the same time or by the same user in multiple browser windows.","
--- CONTEXT --
-","Ćvitez de modifier simultanĆ©ment une mĆŖme chaĆ®ne. Les chaĆ®nes ne devraient pas ĆŖtre modifiĆ©es en mĆŖme temps par deux utilisateurs ou un mĆŖme utilisateur dans plusieurs fenĆŖtres de navigateur."
+","Rechargez la page pour confirmer que votre travail a Ć©tĆ© enregistrĆ© sur le serveur. Pour ce faire, utilisez CTRL+R sous Linux/Windows ou ā+R sous Mac."
"UsingStudio.bestPractice5","It is possible that you will encounter timeout errors in your browser when performing operations like import and sync, on large channels. Don't be alarmed by this error message and do not repeat the same operation again right away. It doesn't mean the operation has failed- Kolibri Studio is still working in the background. Wait a few minutes and reload the page before continuing your edits.","
-- CONTEXT --
","Certaines opĆ©rations comme l'importation et la synchronisation pourraient entraĆ®ner des erreurs de timeout dans votre navigateur lorsque vous travaillez sur des chaĆ®nes volumineuses. Ne faites pas attention Ć ces messages d'erreur et ne rĆ©pĆ©tez pas immĆ©diatement la mĆŖme opĆ©ration. Cela ne signifie pas que l'opĆ©ration a Ć©chouĆ©. En effet, Kolibri Studio travaille toujours en arriĆØre-plan. Attendez quelques minutes et rechargez la page avant de continuer vos modifications."
@@ -4456,9 +4488,6 @@ Heading for the section in the resource editing window","AchĆØvement"
"UsingStudio.bestPractice7","PUBLISH periodically and import your channel into Kolibri to preview the content and obtain a local backup copy of your channel.","
-- CONTEXT --
","PUBLIEZ et importez rĆ©guliĆØrement votre chaĆ®ne dans Kolibri pour en prĆ©visualiser le contenu et obtenir une copie de sauvegarde locale."
-"UsingStudio.bestPractice8","Do not edit the channel after you click PUBLISH. Wait for the notification email before resuming editing operations.","
--- CONTEXT --
-","Ne modifiez pas la chaĆ®ne aprĆØs avoir cliquĆ© sur PUBLIER. Attendez d'avoir reƧu l'e-mail de notification avant de reprendre les opĆ©rations de modification."
"UsingStudio.bestPractice9","Report issues as you encounter them.","
-- CONTEXT --
","Signalez les problĆØmes dĆØs que vous les rencontrez."
@@ -4468,18 +4497,13 @@ Heading for the section in the resource editing window","AchĆØvement"
"UsingStudio.communityStandardsLink","Community standards","
-- CONTEXT --
","Normes communautaires"
-"UsingStudio.issue1","Two users have reported isolated incidents where content they imported from another channel disappeared, leaving only empty folders and subfolders. In one report, the content later re-appeared. They did not experience these problems consistently, and the incidents may possibly involve issues with a slow or unstable internet connection. If you run into this issue, please contact us as soon as possible and let us know as much information as you can remember.","
--- CONTEXT --
-","Deux utilisateurs ont signalĆ© des incidents isolĆ©s oĆ¹ le contenu qu'ils avaient importĆ© d'une autre chaĆ®ne avait disparu, ne laissant que des dossiers et sous-dossiers vides. Selon un rapport, le contenu est rĆ©apparu par la suite. Ces problĆØmes ne sont pas rĆ©currents et les incidents peuvent ĆŖtre liĆ©s Ć une connexion Internet lente ou instable. Si vous rencontrez ce problĆØme, veuillez nous contacter dĆØs que possible et nous faire part du maximum d'informations dont vous vous souvenez."
-"UsingStudio.issue2","Some operations in Studio are currently very slow, and so it may appear that the change you attempted to make timed out or did not take effect. In many cases, the change is still being processed and will appear once it is complete. If, after 5-10 minutes, the change still has not taken effect even after a browser refresh, please file an issue. We are working on solutions to these issues.","
+"UsingStudio.issue1","There have been reports where users have observed the disappearance of changes they've recently made to their channels. The issue seems related to opening multiple tabs of Kolibri Studio, and eventually signing out. We advise that you disable any āMemory Saver/Sleepingā tab browser feature for Kolibri Studio, and reload each tab before signing out. We're actively investigating this issue, so if you run into it, please contact us with as much information as possible.","UsingStudio.issue1
+
-- CONTEXT --
-","Certaines opĆ©rations de Studio sont actuellement trĆØs lentes. La modification que vous avez tentĆ© d'apporter pourrait ainsi sembler interrompue ou sans effet. Dans de nombreux cas, elle est en rĆ©alitĆ© encore en cours de traitement et apparaĆ®tra dĆØs qu'elle sera terminĆ©e. Si, aprĆØs 5 Ć 10 minutes, la modification n'a toujours pas pris effet, mĆŖme aprĆØs un rafraĆ®chissement du navigateur, merci de nous signaler le problĆØme. Nous travaillons actuellement Ć rĆ©soudre ces difficultĆ©s."
+A description of an issue that has been reported by users - the recommendation is to disable any memory saver feature in the browser while they are using Kolibri Studio.","Des utilisateurs ont constatĆ© la disparition des modifications qu'ils avaient rĆ©cemment apportĆ©es Ć leurs chaĆ®nes. Le problĆØme semble liĆ© Ć l'ouverture de plusieurs onglets de Kolibri Studio, puis Ć la dĆ©connexion. Nous vous conseillons de dĆ©sactiver la fonction 'Economiseur de mĆ©moire/Hybernation' dans Kolibri Studio, et de recharger chaque onglet avant de vous dĆ©connecter. Nous enquĆŖtons activement sur ce problĆØme, donc si vous le rencontrez, veuillez nous contacter en nous apportant le plus d'informations possible."
"UsingStudio.issueLink1","Reports of disappearing content","
-- CONTEXT --
","Signalements de contenus qui semblent disparaƮtre"
-"UsingStudio.issueLink2","Slow performance can lead to unexpected errors in the interface","
--- CONTEXT --
-","La lenteur du systĆØme peut entraĆ®ner des erreurs inattendues dans l'interface"
"UsingStudio.issuesPageLink","View all issues","
-- CONTEXT --
","Afficher tous les problĆØmes"
diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json
index ae87e9d86c..d0c1eb1fac 100644
--- a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json
@@ -24,9 +24,9 @@
"Account.unableToDeleteAdminAccount": "Impossible de supprimer un compte administrateur",
"Account.usernameLabel": "Nom d'utilisateur",
"AccountCreated.accountCreatedTitle": "Compte crĆ©Ć© avec succĆØs",
- "AccountCreated.continueToSignIn": "Continuer pour vous connecter",
+ "AccountCreated.backToLogin": "Continuer vers la page de connexion",
"AccountDeleted.accountDeletedTitle": "Compte supprimĆ© avec succĆØs",
- "AccountDeleted.continueToSignIn": "Continuer vers la page de connexion",
+ "AccountDeleted.backToLogin": "Continuer vers la page de connexion",
"AccountNotActivated.requestNewLink": "Demander un nouveau lien d'activation",
"AccountNotActivated.text": "Merci de vƩrifier votre messagerie pour un lien d'activation ou d'en demander un nouveau.",
"AccountNotActivated.title": "Compte non activƩ",
@@ -95,7 +95,6 @@
"AssessmentTab.incompleteItemsCountMessage": "{invalidItemsCount} {invalidItemsCount, plural, one {question incomplĆØte} other {questions incomplĆØtes}}",
"BrowsingCard.addToClipboardAction": "Copier dans le presse-papier",
"BrowsingCard.coach": "Ressource pour les Ć©ducateurs",
- "BrowsingCard.goToPluralLocationsAction": "Dans {count, number} {count, plural, one {emplacement} other {emplacements}}",
"BrowsingCard.goToSingleLocationAction": "Aller Ć l'emplacement",
"BrowsingCard.hasCoachTooltip": "{value, number, integer} {value, plural, one {ressource pour les Ć©ducateurs} other {ressources pour les Ć©ducateurs}}",
"BrowsingCard.previewAction": "Afficher les dƩtails",
@@ -414,6 +413,7 @@
"CommonMetadataStrings.accessibility": "AccessibilitƩ",
"CommonMetadataStrings.algebra": "AlgĆØbre",
"CommonMetadataStrings.all": "Tous",
+ "CommonMetadataStrings.allContent": "Vu en totalitƩ",
"CommonMetadataStrings.allLevelsBasicSkills": "Tous les niveaux -- compƩtences de base",
"CommonMetadataStrings.allLevelsWorkSkills": "Tous les niveaux -- compƩtences professionnelles",
"CommonMetadataStrings.altText": "Comprend des descriptions de texte alternatives pour les images",
@@ -430,12 +430,14 @@
"CommonMetadataStrings.category": "CatƩgorie",
"CommonMetadataStrings.chemistry": "Chimie",
"CommonMetadataStrings.civicEducation": "Ćducation civique",
+ "CommonMetadataStrings.completeDuration": "Lorsque le temps passĆ© est Ć©gal Ć la durĆ©e",
"CommonMetadataStrings.completion": "AchĆØvement",
"CommonMetadataStrings.computerScience": "Informatique",
"CommonMetadataStrings.create": "CrƩer",
"CommonMetadataStrings.currentEvents": "EvĆØnements en cours",
"CommonMetadataStrings.dailyLife": "Vie quotidienne",
"CommonMetadataStrings.dance": "Danse",
+ "CommonMetadataStrings.determinedByResource": "DƩterminƩ par la ressource",
"CommonMetadataStrings.digitalLiteracy": "AlphabƩtisation numƩrique",
"CommonMetadataStrings.diversity": "DiversitƩ",
"CommonMetadataStrings.drama": "Drame",
@@ -443,11 +445,13 @@
"CommonMetadataStrings.earthScience": "Sciences de la Terre",
"CommonMetadataStrings.entrepreneurship": "Entreprenariat",
"CommonMetadataStrings.environment": "Environnement",
+ "CommonMetadataStrings.exactTime": "DƩlai d'exƩcution",
"CommonMetadataStrings.explore": "Explorer",
"CommonMetadataStrings.financialLiteracy": "Ćducation financiĆØre",
"CommonMetadataStrings.forBeginners": "Pour les dƩbutants",
"CommonMetadataStrings.forTeachers": "Pour les enseignants",
"CommonMetadataStrings.geometry": "GƩomƩtrie",
+ "CommonMetadataStrings.goal": "Lorsque l'objectif est atteint",
"CommonMetadataStrings.guides": "Guides",
"CommonMetadataStrings.highContrast": "Comprend un texte fortement contrastƩ pour les apprenants malvoyants",
"CommonMetadataStrings.history": "Histoire",
@@ -464,6 +468,7 @@
"CommonMetadataStrings.longActivity": "ActivitƩ longue",
"CommonMetadataStrings.lowerPrimary": "Primaire infƩrieur",
"CommonMetadataStrings.lowerSecondary": "Secondaire infƩrieur",
+ "CommonMetadataStrings.masteryMofN": "Objectif : {m} sur {n}",
"CommonMetadataStrings.mathematics": "MathƩmatiques",
"CommonMetadataStrings.mechanicalEngineering": "IngƩnierie mƩcanique",
"CommonMetadataStrings.mediaLiteracy": "LittƩratie aux mƩdias",
@@ -476,6 +481,7 @@
"CommonMetadataStrings.physics": "Physique",
"CommonMetadataStrings.politicalScience": "Science politique",
"CommonMetadataStrings.practice": "S'entraƮner",
+ "CommonMetadataStrings.practiceQuiz": "Quiz pratique",
"CommonMetadataStrings.preschool": "Maternelle",
"CommonMetadataStrings.professionalSkills": "CompƩtences professionnelles",
"CommonMetadataStrings.programming": "Programmation",
@@ -484,6 +490,7 @@
"CommonMetadataStrings.readReference": "RƩfƩrence",
"CommonMetadataStrings.readingAndWriting": "Lire et Ć©crire",
"CommonMetadataStrings.readingComprehension": "ComprƩhension de texte",
+ "CommonMetadataStrings.reference": "Document de rƩfƩrence",
"CommonMetadataStrings.reflect": "SymƩtrie axiale",
"CommonMetadataStrings.school": "Ćcole",
"CommonMetadataStrings.sciences": "Sciences",
@@ -523,13 +530,7 @@
"CommunityStandardsModal.studioItem3": "Le partage. CrƩez et publiez de nouvelles chaƮnes avec ce que vous trouvez, soit pour les partager en privƩ avec vos propres rƩalisations, soit pour les partager avec d'autres sur Kolibri Studio.",
"CommunityStandardsModal.studioItem4": "La modification et la crĆ©ation. Ajoutez vos propres exercices d'Ć©valuation Ć tout support existant",
"CommunityStandardsModal.studioItem5": "L'hĆ©bergement. TĆ©lĆ©versez vos propres supports (limitĆ© aux supports dont la licence appropriĆ©e vous a Ć©tĆ© accordĆ©e) Ć partir d'un disque dur local ou d'autres emplacements sur Internet",
- "CompletionOptions.allContent": "Vu en totalitƩ",
- "CompletionOptions.completeDuration": "Lorsque le temps passĆ© est Ć©gal Ć la durĆ©e",
- "CompletionOptions.determinedByResource": "DƩterminƩ par la ressource",
- "CompletionOptions.exactTime": "DƩlai d'exƩcution",
- "CompletionOptions.goal": "Lorsque l'objectif est atteint",
- "CompletionOptions.practiceQuiz": "Quiz d'entraƮnement",
- "CompletionOptions.reference": "Document de rƩfƩrence",
+ "CompletionOptions.learnersCanMarkComplete": "Autoriser les apprenants Ć marquer le contenu comme Ć©tant terminĆ©",
"CompletionOptions.referenceHint": "Les progrĆØs ne seront pas suivis sur le document de rĆ©fĆ©rence Ć moins que les apprenants ne le marquent comme Ć©tant terminĆ©",
"ConstantStrings.All Rights Reserved": "Tous droits rƩservƩs",
"ConstantStrings.All Rights Reserved_description": "La Licence Tous droits rĆ©servĆ©s indique que le titulaire du droit d'auteur rĆ©serve, ou dĆ©tient pour son propre usage, tous les droits prĆ©vus par la loi sur le droit d'auteur en vertu d'un traitĆ© spĆ©cifique en la matiĆØre.",
@@ -627,7 +628,13 @@
"ContentNodeChangedIcon.isNewTopic": "Dossier non publiƩ",
"ContentNodeChangedIcon.isUpdatedResource": "Mise Ć jour depuis la derniĆØre publication",
"ContentNodeChangedIcon.isUpdatedTopic": "Le dossier a Ć©tĆ© mis Ć jour depuis la derniĆØre publication",
+ "ContentNodeCopyTaskProgress.copyErrorTopic": "Certaines ressources n'ont pu ĆŖtre copiĆ©es",
+ "ContentNodeEditListItem.copiedSnackbar": "Copie terminƩe",
+ "ContentNodeEditListItem.creatingCopies": "Copie en cours...",
"ContentNodeEditListItem.optionsTooltip": "Options",
+ "ContentNodeEditListItem.removeNode": "Supprimer",
+ "ContentNodeEditListItem.retryCopy": "RĆ©essayer",
+ "ContentNodeEditListItem.undo": "Annuler",
"ContentNodeIcon.audio": "Audio",
"ContentNodeIcon.document": "Document",
"ContentNodeIcon.exercise": "Exercice",
@@ -637,8 +644,8 @@
"ContentNodeIcon.unsupported": "Non supportƩ",
"ContentNodeIcon.video": "VidƩo",
"ContentNodeLearningActivityIcon.multipleLearningActivities": "ActivitƩs d'apprentissage multiples",
- "ContentNodeLearningActivityIcon.topic": "Dossier",
"ContentNodeListItem.coachTooltip": "Ressource pour les Ć©ducateurs",
+ "ContentNodeListItem.copyingError": "Ćchec de la copie.",
"ContentNodeListItem.copyingTask": "Copie en cours",
"ContentNodeListItem.hasCoachTooltip": "{value, number, integer} {value, plural, one {ressource pour les Ć©ducateurs} other {ressources pour les Ć©ducateurs}}",
"ContentNodeListItem.openTopic": "Dossier ouvert",
@@ -692,8 +699,8 @@
"CountryField.locationLabel": "SĆ©lectionner tout ce qui s'applique",
"CountryField.locationRequiredMessage": "Ce champ est obligatoire",
"CountryField.noCountriesFound": "Aucun pays trouvƩ",
- "Create.ToSCheck": "J'ai lu et j'accepte les conditions d'utilisation",
- "Create.ToSRequiredMessage": "Veuillez accepter nos conditions d'utilisation",
+ "Create.ToSRequiredMessage": "Veuillez accepter nos conditions de service et notre politique",
+ "Create.agreement": "J'ai lu et j'accepte ainsi les conditions de service et la politique de confidentialitƩ",
"Create.backToLoginButton": "Se connecter",
"Create.basicInformationHeader": "Informations de base",
"Create.conferenceSourceOption": "ConfƩrence",
@@ -724,8 +731,6 @@
"Create.passwordLabel": "Mot de passe",
"Create.passwordMatchMessage": "Les mots de passe ne sont pas identiques",
"Create.personalDemoSourceOption": "DĆ©mo personnelle",
- "Create.privacyPolicyCheck": "J'ai lu et j'accepte la politique de confidentialitƩ",
- "Create.privacyPolicyRequiredMessage": "Veuillez accepter notre politique de confidentialitƩ",
"Create.registrationFailed": "Une erreur s'est produite lors de l'enregistrement de votre compte. Veuillez rƩessayer",
"Create.registrationFailedOffline": "Il semble que vous ne soyez pas en ligne. Veuillez vous connecter Ć Internet pour crĆ©er un compte.",
"Create.sequencingUsageOption": "Utiliser des prƩrequis pour crƩer une suite de supports",
@@ -738,8 +743,8 @@
"Create.storingUsagePlaceholder": "De combien d'espace de stockage avez-vous besoin ?",
"Create.taggingUsageOption": "Ćtiqueter des sources de contenus pour les retrouver",
"Create.usageLabel": "Comment comptez-vous utiliser Kolibri Studio (cochez tout ce qui s'applique)",
- "Create.viewPrivacyPolicyLink": "Voir la politique de confidentialitƩ",
- "Create.viewToSLink": "Voir les conditions d'utilisation",
+ "Create.viewPrivacyPolicyLink": "Voir la Politique de ConfidentialitƩ",
+ "Create.viewToSLink": "Voir les Conditions de service",
"Create.websiteSourceOption": "Site Web de Learning Equality ",
"CurrentTopicView.COMFORTABLE_VIEW": "Affichage confortable",
"CurrentTopicView.COMPACT_VIEW": "Affichage compact",
@@ -784,6 +789,7 @@
"Details.assessmentsIncludedText": "Ćvaluations",
"Details.authorToolTip": "Personne ou organisation ayant crƩƩ ce contenu",
"Details.authorsLabel": "Auteurs",
+ "Details.categoriesHeading": "CatƩgories",
"Details.coachDescription": "Sur Kolibri, les ressources destinƩes aux Ʃducateurs ne sont visibles que par eux",
"Details.coachHeading": "Ressources pour les Ć©ducateurs",
"Details.containsContentHeading": "Contient du contenu provenant de",
@@ -792,6 +798,7 @@
"Details.creationHeading": "CrƩƩ le",
"Details.currentVersionHeading": "Version publiƩe",
"Details.languagesHeading": "Langues",
+ "Details.levelsHeading": "Niveaux",
"Details.licensesLabel": "Licences",
"Details.primaryLanguageHeading": "Langue principale",
"Details.providerToolTip": "Organisation ayant commandƩ le contenu ou qui le distribue",
@@ -820,7 +827,6 @@
"DetailsTabView.importedFromButtonText": "Importer Ć partir de {channel}",
"DetailsTabView.languageChannelHelpText": "Laisser vide pour utiliser la langue de la chaƮne",
"DetailsTabView.languageHelpText": "Laisser vide pour utiliser la langue du dossier",
- "CompletionOptions.learnersCanMarkComplete": "Autoriser les apprenants Ć marquer le contenu comme Ć©tant terminĆ©",
"DetailsTabView.noTagsFoundText": "Aucun rƩsultat trouvƩ pour \"{text}\". Appuyez sur la touche 'EntrƩe' pour crƩer une nouvelle Ʃtiquette",
"DetailsTabView.providerLabel": "Fournisseur",
"DetailsTabView.providerToolTip": "Organisation ayant commandƩ le contenu ou qui le distribue",
@@ -979,6 +985,7 @@
"Main.privacyPolicyLink": "Politique de confidentialitƩ",
"Main.signInButton": "Se connecter",
"MainNavigationDrawer.administrationLink": "Administration",
+ "MainNavigationDrawer.changeLanguage": "Changer la langue",
"MainNavigationDrawer.channelsLink": "ChaƮnes",
"MainNavigationDrawer.copyright": "Ā© {year} Learning Equality",
"MainNavigationDrawer.giveFeedback": "Envoyer un commentaire",
@@ -1029,7 +1036,6 @@
"PermissionsError.goToHomePageAction": "Aller sur la page d'accueil",
"PermissionsError.permissionDeniedHeader": "Avez-vous oubliĆ© de vous connecterĀ ?",
"PoliciesModal.checkboxText": "J'ai acceptƩ les conditions ci-dessus",
- "PoliciesModal.checkboxValidationErrorMessage": "Ce champ est obligatoire",
"PoliciesModal.closeButton": "Fermer",
"PoliciesModal.continueButton": "Continuer",
"PoliciesModal.lastUpdated": "DerniĆØre mise Ć jour datĆ©e du {date}",
@@ -1040,7 +1046,8 @@
"ProgressModal.lastPublished": "PubliƩ le {last_published}",
"ProgressModal.publishHeader": "Publication de la chaƮne",
"ProgressModal.syncError": "Ćchec de la derniĆØre tentative de synchronisation",
- "ProgressModal.syncHeader": "Synchronisation de la chaƮne",
+ "ProgressModal.syncHeader": "Synchronisation des ressources",
+ "ProgressModal.syncedSnackbar": "Ressources synchronisƩes",
"ProgressModal.unpublishedText": "Non publiƩe",
"PublishModal.cancelButton": "Annuler",
"PublishModal.descriptionDescriptionTooltip": "Cette description sera prĆ©sentĆ©e aux administrateurs de Kolibri avant qu'ils ne mettent Ć jour les versions des chaĆ®nes",
@@ -1146,7 +1153,6 @@
"ResourcePanel.author": "Auteur",
"ResourcePanel.availableFormats": "Formats disponibles",
"ResourcePanel.coachResources": "Ressources pour les Ć©ducateurs",
- "ResourcePanel.completion": "AchĆØvement",
"ResourcePanel.copyrightHolder": "Titulaire des droits d'auteur",
"ResourcePanel.description": "Description",
"ResourcePanel.details": "DĆ©tails",
@@ -1155,13 +1161,15 @@
"ResourcePanel.incompleteQuestionError": "{count, plural, one {# question incomplĆØte} other {# questions incomplĆØtes}}",
"ResourcePanel.language": "Langue",
"ResourcePanel.license": "Licence",
- "ResourcePanel.masteryMofN": "Objectif : {m} sur {n}",
"ResourcePanel.nextSteps": "Ćtapes suivantes",
- "ResourcePanel.noCopyrightHolderError": "Titulaire des droits d'auteur manquant",
- "ResourcePanel.noFilesError": "Fichiers manquants",
- "ResourcePanel.noLicenseDescriptionError": "Description de licence manquante",
- "ResourcePanel.noLicenseError": "Licence manquante",
- "ResourcePanel.noMasteryModelError": "CritĆØres de maĆ®trise manquants",
+ "ResourcePanel.noCompletionCriteriaError": "Les critĆØres d'achĆØvement sont obligatoires",
+ "ResourcePanel.noCopyrightHolderError": "Le propriƩtaire des droits d'auteur est obligatoire",
+ "ResourcePanel.noDurationError": "La durƩe est requise",
+ "ResourcePanel.noFilesError": "Le fichier est obligatoire",
+ "ResourcePanel.noLearningActivityError": "L'activitƩ d'apprentissage est requise",
+ "ResourcePanel.noLicenseDescriptionError": "La description de la licence est obligatoire",
+ "ResourcePanel.noLicenseError": "La licence est obligatoire",
+ "ResourcePanel.noMasteryModelError": "Les critĆØres de maĆ®trise sont obligatoires",
"ResourcePanel.noQuestionsError": "L'exercice est vide",
"ResourcePanel.originalChannel": "ImportĆ© Ć partir de",
"ResourcePanel.previousSteps": "Ćtapes antĆ©rieures",
@@ -1271,16 +1279,18 @@
"SyncResourcesModal.cancelButtonLabel": "Annuler",
"SyncResourcesModal.confirmSyncModalExplainer": "Vous ĆŖtes sur le point de synchroniser et de mettre Ć jour les Ć©lĆ©ments suivants :",
"SyncResourcesModal.confirmSyncModalTitle": "Confirmer la synchronisation",
+ "SyncResourcesModal.confirmSyncModalWarningExplainer": "Attention : cette opƩration Ʃcrasera toutes les modifications que vous avez apportƩes aux ressources copiƩes ou importƩes.",
"SyncResourcesModal.continueButtonLabel": "Continuer",
"SyncResourcesModal.syncButtonLabel": "Synchroniser",
- "SyncResourcesModal.syncExercisesExplainer": "Mettre Ć jour les questions, les rĆ©ponses et les indices",
+ "SyncResourcesModal.syncExercisesExplainer": "Mettre Ć jour les questions, les rĆ©ponses et les indices dans les exercices et les quiz",
"SyncResourcesModal.syncExercisesTitle": "DĆ©tails de l'Ć©valuation",
- "SyncResourcesModal.syncFilesExplainer": "Mettre Ć jour toutes les informations du fichier",
+ "SyncResourcesModal.syncFilesExplainer": "Mettre Ć jour tous les fichiers, y compris les vignettes, les sous-titres et les lĆ©gendes",
"SyncResourcesModal.syncFilesTitle": "Fichiers",
- "SyncResourcesModal.syncModalExplainer": "Synchronisez et mettez Ć jour vos ressources Ć partir de leur source d'origine.",
+ "SyncResourcesModal.syncModalExplainer": "La synchronisation des ressources dans Kolibri Studio met Ć jour les ressources copiĆ©es ou importĆ©es dans cette chaĆ®ne, y compris toutes les modifications apportĆ©es aux fichiers de ressources d'origine.",
+ "SyncResourcesModal.syncModalSelectAttributes": "SĆ©lectionnez ce que vous souhaitez synchroniser :",
"SyncResourcesModal.syncModalTitle": "Synchroniser les ressources",
- "SyncResourcesModal.syncTagsExplainer": "Mettre Ć jour toutes les Ć©tiquettes",
- "SyncResourcesModal.syncTagsTitle": "Ćtiquettes",
+ "SyncResourcesModal.syncResourceDetailsExplainer": "Mettre Ć jour les informations sur la ressource : activitĆ© d'apprentissage, niveau, exigences, catĆ©gorie, Ć©tiquettes, public et source",
+ "SyncResourcesModal.syncResourceDetailsTitle": "DĆ©tail de la ressource",
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer": "Mettre Ć jour les titres et les descriptions des ressources",
"SyncResourcesModal.syncTitlesAndDescriptionsTitle": "Titres et descriptions",
"TechnicalTextBlock.copiedToClipboardConfirmation": "CopiƩ dans le presse-papier",
@@ -1421,7 +1431,7 @@
"TreeViewBase.getToken": "Obtenir un jeton",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {la ressource est incomplĆØte et ne peut pas ĆŖtre publiĆ©e} other {les ressources sont incomplĆØtes et ne peuvent pas ĆŖtre publiĆ©es}}",
"TreeViewBase.noChangesText": "Aucune modification trouvƩe dans la chaƮne",
- "TreeViewBase.noLanguageSetError": "Il manque la langue de la chaƮne",
+ "TreeViewBase.noLanguageSetError": "La langue de la chaƮne est obligatoire",
"TreeViewBase.openTrash": "Ouvrir la corbeille",
"TreeViewBase.publishButton": "Publier",
"TreeViewBase.publishButtonTitle": "Rendre cette chaƮne disponible pour importation dans Kolibri",
@@ -1440,19 +1450,15 @@
"UsingStudio.aboutStudioText": "Kolibri Studio Ć©tant en cours de dĆ©veloppement actif, certaines modifications pourraient entraĆ®ner des comportements ou des difficultĆ©s inattendus (Ć©galement appelĆ©s Ā« problĆØmes Ā»). Si vous en rencontrez un, merci de nous en faire part dĆØs que possible pour nous aider Ć le rĆ©soudre. (Voir ci-dessous pour dĆ©couvrir comment signaler un problĆØme).",
"UsingStudio.bestPractice1": "Lors des opĆ©rations d'importation et d'utilisation du presse-papiers, veillez Ć travailler avec de petits sous-ensembles de dossiers plutĆ“t qu'avec des chaĆ®nes entiĆØres (surtout en cas de chaĆ®nes volumineuses).",
"UsingStudio.bestPractice2": "Il est prƩfƩrable de crƩer plusieurs petites chaƮnes plutƓt qu'une chaƮne gƩante avec de nombreuses couches de dossiers.",
- "UsingStudio.bestPractice3": "Rechargez souvent la page pour vous assurer que votre travail est enregistrĆ© sur le serveur et qu'aucune erreur de rĆ©seau ne s'est manifestĆ©e. Tapez CTRL+R sur Linux/Windows ou ā+R sur Mac.",
- "UsingStudio.bestPractice4": "Ćvitez de modifier simultanĆ©ment une mĆŖme chaĆ®ne. Les chaĆ®nes ne devraient pas ĆŖtre modifiĆ©es en mĆŖme temps par deux utilisateurs ou un mĆŖme utilisateur dans plusieurs fenĆŖtres de navigateur.",
+ "UsingStudio.bestPractice3": "Rechargez la page pour confirmer que votre travail a Ć©tĆ© enregistrĆ© sur le serveur. Pour ce faire, utilisez CTRL+R sous Linux/Windows ou ā+R sous Mac.",
"UsingStudio.bestPractice5": "Certaines opĆ©rations comme l'importation et la synchronisation pourraient entraĆ®ner des erreurs de timeout dans votre navigateur lorsque vous travaillez sur des chaĆ®nes volumineuses. Ne faites pas attention Ć ces messages d'erreur et ne rĆ©pĆ©tez pas immĆ©diatement la mĆŖme opĆ©ration. Cela ne signifie pas que l'opĆ©ration a Ć©chouĆ©. En effet, Kolibri Studio travaille toujours en arriĆØre-plan. Attendez quelques minutes et rechargez la page avant de continuer vos modifications.",
"UsingStudio.bestPractice6": "Compressez les vidƩos avant de les tƩlƩverser (voir les prƩsentes consignes).",
"UsingStudio.bestPractice7": "PUBLIEZ et importez rĆ©guliĆØrement votre chaĆ®ne dans Kolibri pour en prĆ©visualiser le contenu et obtenir une copie de sauvegarde locale.",
- "UsingStudio.bestPractice8": "Ne modifiez pas la chaĆ®ne aprĆØs avoir cliquĆ© sur PUBLIER. Attendez d'avoir reƧu l'e-mail de notification avant de reprendre les opĆ©rations de modification.",
"UsingStudio.bestPractice9": "Signalez les problĆØmes dĆØs que vous les rencontrez.",
"UsingStudio.bestPractices": "Meilleures pratiques",
"UsingStudio.communityStandardsLink": "Normes communautaires",
- "UsingStudio.issue1": "Deux utilisateurs ont signalĆ© des incidents isolĆ©s oĆ¹ le contenu qu'ils avaient importĆ© d'une autre chaĆ®ne avait disparu, ne laissant que des dossiers et sous-dossiers vides. Selon un rapport, le contenu est rĆ©apparu par la suite. Ces problĆØmes ne sont pas rĆ©currents et les incidents peuvent ĆŖtre liĆ©s Ć une connexion Internet lente ou instable. Si vous rencontrez ce problĆØme, veuillez nous contacter dĆØs que possible et nous faire part du maximum d'informations dont vous vous souvenez.",
- "UsingStudio.issue2": "Certaines opĆ©rations de Studio sont actuellement trĆØs lentes. La modification que vous avez tentĆ© d'apporter pourrait ainsi sembler interrompue ou sans effet. Dans de nombreux cas, elle est en rĆ©alitĆ© encore en cours de traitement et apparaĆ®tra dĆØs qu'elle sera terminĆ©e. Si, aprĆØs 5 Ć 10 minutes, la modification n'a toujours pas pris effet, mĆŖme aprĆØs un rafraĆ®chissement du navigateur, merci de nous signaler le problĆØme. Nous travaillons actuellement Ć rĆ©soudre ces difficultĆ©s.",
+ "UsingStudio.issue1": "Des utilisateurs ont constatĆ© la disparition des modifications qu'ils avaient rĆ©cemment apportĆ©es Ć leurs chaĆ®nes. Le problĆØme semble liĆ© Ć l'ouverture de plusieurs onglets de Kolibri Studio, puis Ć la dĆ©connexion. Nous vous conseillons de dĆ©sactiver la fonction 'Economiseur de mĆ©moire/Hybernation' dans Kolibri Studio, et de recharger chaque onglet avant de vous dĆ©connecter. Nous enquĆŖtons activement sur ce problĆØme, donc si vous le rencontrez, veuillez nous contacter en nous apportant le plus d'informations possible.",
"UsingStudio.issueLink1": "Signalements de contenus qui semblent disparaƮtre",
- "UsingStudio.issueLink2": "La lenteur du systĆØme peut entraĆ®ner des erreurs inattendues dans l'interface",
"UsingStudio.issuesPageLink": "Afficher tous les problĆØmes",
"UsingStudio.notableIssues": "ProblĆØmes notables",
"UsingStudio.policiesLink": "Politique de confidentialitƩ",
@@ -1498,4 +1504,5 @@
"sharedVue.masteryModelNWholeNumber": "Doit ĆŖtre un nombre entier",
"sharedVue.masteryModelRequired": "Le critĆØre de maĆ®trise est obligatoire",
"sharedVue.shortActivityLteThirty": "La valeur doit ĆŖtre Ć©gale ou infĆ©rieure Ć 30",
- "sharedVue.titleRequired": "Le titre est obligatoire"}
+ "sharedVue.titleRequired": "Le titre est obligatoire"
+}
diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po
index 32e421da65..3768902bec 100644
--- a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po
+++ b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-16 00:55+0000\n"
-"PO-Revision-Date: 2022-10-17 20:08\n"
+"POT-Creation-Date: 2023-05-10 21:55+0000\n"
+"PO-Revision-Date: 2023-05-24 17:17\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -17,36 +17,8 @@ msgstr ""
"X-Crowdin-File: /unstable/django.po\n"
"X-Crowdin-File-ID: 4322\n"
-#: contentcuration/api.py:140
-msgid "Date/Time Created"
-msgstr "Date/heure de crƩation"
-
-#: contentcuration/api.py:141 contentcuration/api.py:142
-msgid "Not Available"
-msgstr "Non disponible"
-
-#: contentcuration/api.py:145
-msgid "Ricecooker Version"
-msgstr "Version Ricecooker"
-
-#: contentcuration/api.py:150 contentcuration/utils/csv_writer.py:164
-msgid "File Size"
-msgstr "Taille du fichier"
-
-#: contentcuration/api.py:161
-msgid "# of {}s"
-msgstr "Nbre de {}s"
-
-#: contentcuration/api.py:165
-msgid "# of Questions"
-msgstr "Nbre de questions"
-
-#: contentcuration/api.py:175
-msgid "# of Subtitles"
-msgstr "Nbre de sous-titres"
-
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
-#: contentcuration/settings.py:287
+#: contentcuration/settings.py:290
msgid "Arabic"
msgstr "Arabe"
@@ -54,40 +26,40 @@ msgstr "Arabe"
msgid "The site is currently in read-only mode. Please try again later."
msgstr "Le site est actuellement en mode lecture seule. Veuillez rƩessayer plus tard."
-#: contentcuration/models.py:279
+#: contentcuration/models.py:295
msgid "Not enough space. Check your storage under Settings page."
msgstr "Espace insuffisant. VĆ©rifiez votre espace de stockage disponible sur la page ParamĆØtres."
-#: contentcuration/models.py:294 contentcuration/models.py:301
+#: contentcuration/models.py:308 contentcuration/models.py:315
msgid "Out of storage! Request more space under Settings > Storage."
msgstr "Espace de stockage insuffisant ! Demandez plus d'espace dans ParamĆØtres > Stockage."
-#: contentcuration/models.py:1410
+#: contentcuration/models.py:1730
msgid " (Original)"
msgstr " (Original)"
-#: contentcuration/settings.py:285
+#: contentcuration/settings.py:288
msgid "English"
msgstr "Anglais"
-#: contentcuration/settings.py:286
+#: contentcuration/settings.py:289
msgid "Spanish"
msgstr "Espagnol"
-#: contentcuration/settings.py:288
+#: contentcuration/settings.py:291
msgid "French"
msgstr "FranƧais"
-#: contentcuration/tasks.py:251
-msgid "Unknown error starting task. Please contact support."
-msgstr "Erreur inconnue lors du dĆ©marrage de la tĆ¢che. Veuillez contacter l'Ć©quipe support."
+#: contentcuration/settings.py:292
+msgid "Portuguese"
+msgstr "Portugais"
-#: contentcuration/templates/base.html:83
+#: contentcuration/templates/base.html:38
#: contentcuration/templates/channel_list.html:14
msgid "Kolibri Studio"
msgstr "Kolibri Studio"
-#: contentcuration/templates/base.html:166
+#: contentcuration/templates/base.html:129
msgid "Contentworkshop.learningequality.org has been deprecated. Please go to studio.learningequality.org for the latest version of Studio"
msgstr "Contentworkshop.learningequality.org n'est plus valide. Veuillez vous rendre sur studio.learningequality.org pour obtenir la derniĆØre version de Studio"
@@ -100,234 +72,16 @@ msgid "We're sorry, this channel was not found."
msgstr "DƩsolƩ, cette chaƮne est introuvable."
#: contentcuration/templates/channel_not_found.html:24
-#: contentcuration/templates/permissions/open_channel_fail.html:14
#: contentcuration/templates/staging_not_found.html:23
#: contentcuration/templates/unauthorized.html:23
msgid "Go Home"
msgstr "Aller Ć la page d'accueil"
-#: contentcuration/templates/exercise_list.html:67
-msgid "Previous"
-msgstr "PrƩcƩdent"
-
-#: contentcuration/templates/exercise_list.html:73
-msgid "current"
-msgstr "actuel"
-
-#: contentcuration/templates/exercise_list.html:77
-msgid "Next"
-msgstr "Suivant"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:206
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:165
-msgid "Language not set"
-msgstr "Langue non dƩfinie"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:212
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:171
-#, python-format
-msgid "This file was generated on %(date)s"
-msgstr "Ce fichier a ƩtƩ gƩnƩrƩ le %(date)s"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:231
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:189
-msgid "Created"
-msgstr "CrƩƩe"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:232
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:190
-msgid "Last Published"
-msgstr "DerniĆØre publication"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:233
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:191
-msgid "Unpublished"
-msgstr "Non publiƩe"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:245
-msgid "USING THIS CHANNEL"
-msgstr "CHAĆNE EN COURS D'UTILISATION"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:247
-msgid "Copy one of the following into Kolibri to import this channel:"
-msgstr "Pour importer cette chaƮne, copiez l'un des ƩlƩments suivants dans Kolibri :"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:252
-msgid "Tokens (Recommended):"
-msgstr "Jetons (recommandƩs) :"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:260
-msgid "Channel ID:"
-msgstr "ID de la chaƮne :"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:267
-msgid "Channel must be published to import into Kolibri"
-msgstr "La chaĆ®ne doit ĆŖtre publiĆ©e afin d'ĆŖtre importĆ©e dans Kolibri"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:273
-msgid "WHAT'S INSIDE"
-msgstr "CONTENU"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:283
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:213
-#, python-format
-msgid "%(count)s Resource"
-msgid_plural "%(count)s Resources"
-msgstr[0] "%(count)s Ressource"
-msgstr[1] "%(count)s Ressources"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:304
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:234
-msgid "Includes"
-msgstr "Comprend"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:310
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:236
-msgid "Languages"
-msgstr "Langues"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:316
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:245
-msgid "Subtitles"
-msgstr "Sous-titres"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:324
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:254
-msgid "For Educators"
-msgstr "Pour les Ć©ducateurs"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:256
-msgid "Coach Content"
-msgstr "Contenu pour les Ć©ducateurs"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:257
-msgid "Assessments"
-msgstr "Ćvaluations"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:334
-msgid "Content Tags"
-msgstr "Ćtiquettes de contenu"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:338
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:270
-msgid "No tags found"
-msgstr "Aucune Ʃtiquette trouvƩe"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:343
-#: contentcuration/templates/export/channel_detail_pdf.html:448
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:274
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:337
-msgid "This channel is empty"
-msgstr "Cette chaƮne est vide"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:348
-msgid "SOURCE"
-msgstr "SOURCE"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:360
-msgid "This channel features resources created by:"
-msgstr "Cette chaƮne comprend des ressources crƩƩes par :"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:361
-#: contentcuration/templates/export/channel_detail_pdf.html:379
-#: contentcuration/templates/export/channel_detail_pdf.html:398
-#: contentcuration/templates/export/channel_detail_pdf.html:420
-#: contentcuration/templates/export/channel_detail_pdf.html:440
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:293
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:305
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:317
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:333
-msgid "Information not available"
-msgstr "Information non disponible"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:378
-msgid "The material in this channel was provided by:"
-msgstr "Le contenu de cette chaƮne a ƩtƩ fourni par :"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:397
-msgid "Material in this channel was originally hosted by:"
-msgstr "Le contenu de cette chaĆ®ne Ć©tait Ć l'origine hĆ©bergĆ© par :"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:416
-msgid "This channel includes the following licenses:"
-msgstr "Cette chaƮne comprend les licences suivantes :"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:439
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:326
-msgid "Copyright Holders:"
-msgstr "Titulaires des droits d'auteur :"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:192
-msgid "Token:"
-msgstr "Jeton :"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:203
-msgid "What's Inside"
-msgstr "Contenu"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:239
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:248
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s de plus) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:267
-msgid "Most Common Tags"
-msgstr "Ćtiquettes les plus courantes"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:280
-msgid "Source Information"
-msgstr "Information sur la source "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:284
-msgid "Authors:"
-msgstr "Auteurs :"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:288
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s de plus) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:298
-msgid "Providers:"
-msgstr "Fournisseurs :"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:301
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:313
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:329
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s de plus) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:310
-msgid "Aggregators:"
-msgstr "AgrƩgateurs :"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-msgid "Licenses:"
-msgstr "Licences :"
-
-#: contentcuration/templates/export/csv_email.txt:4
#: contentcuration/templates/export/user_csv_email.txt:4
#: contentcuration/templates/permissions/permissions_email.txt:4
#: contentcuration/templates/registration/activation_email.txt:4
#: contentcuration/templates/registration/activation_needed_email.txt:4
-#: contentcuration/templates/registration/channel_published_email.txt:4
+#: contentcuration/templates/registration/channel_published_email.html:10
#: contentcuration/templates/registration/password_reset_email.txt:3
#: contentcuration/templates/registration/registration_needed_email.txt:4
#: contentcuration/templates/settings/account_deleted_user_email.txt:3
@@ -337,43 +91,6 @@ msgstr "Licences :"
msgid "Hello %(name)s,"
msgstr "Bonjour %(name)s,"
-#: contentcuration/templates/export/csv_email.txt:6
-#, python-format
-msgid "Your csv for %(channel_name)s has finished generating (attached)."
-msgstr "La gƩnƩration de votre fichier csv pour %(channel_name)s est terminƩe (ci-joint)."
-
-#: contentcuration/templates/export/csv_email.txt:8
-#: contentcuration/templates/export/user_csv_email.txt:29
-#: contentcuration/templates/permissions/permissions_email.txt:21
-#: contentcuration/templates/registration/activation_email.txt:12
-#: contentcuration/templates/registration/activation_needed_email.txt:14
-#: contentcuration/templates/registration/channel_published_email.txt:15
-#: contentcuration/templates/registration/password_reset_email.txt:14
-#: contentcuration/templates/registration/registration_needed_email.txt:12
-#: contentcuration/templates/settings/account_deleted_user_email.txt:10
-#: contentcuration/templates/settings/storage_request_email.txt:46
-msgid "Thanks for using Kolibri Studio!"
-msgstr "Merci d'utiliser Kolibri Studio !"
-
-#: contentcuration/templates/export/csv_email.txt:10
-#: contentcuration/templates/export/user_csv_email.txt:31
-#: contentcuration/templates/permissions/permissions_email.txt:23
-#: contentcuration/templates/registration/activation_email.txt:14
-#: contentcuration/templates/registration/activation_needed_email.txt:16
-#: contentcuration/templates/registration/channel_published_email.txt:17
-#: contentcuration/templates/registration/password_reset_email.txt:16
-#: contentcuration/templates/registration/registration_needed_email.txt:14
-#: contentcuration/templates/settings/account_deleted_user_email.txt:12
-#: contentcuration/templates/settings/issue_report_email.txt:26
-#: contentcuration/templates/settings/storage_request_email.txt:48
-msgid "The Learning Equality Team"
-msgstr "LāĆ©quipe de Learning Equality"
-
-#: contentcuration/templates/export/csv_email_subject.txt:1
-#, python-format
-msgid "CSV for %(channel_name)s"
-msgstr "CSV pour %(channel_name)s"
-
#: contentcuration/templates/export/user_csv_email.txt:6
msgid "Here is the information for your Kolibri Studio account"
msgstr "Voici les informations relatives Ć votre compte Kolibri Studio"
@@ -420,43 +137,64 @@ msgstr "Les informations sur les ressources que vous avez tƩlƩversƩes sont jo
msgid "If you have any questions or concerns, please email us at %(legal_email)s."
msgstr "En cas de questions ou de prĆ©occupations, merci de nous envoyer un e-mail Ć l'adresse : %(legal_email)s."
+#: contentcuration/templates/export/user_csv_email.txt:29
+#: contentcuration/templates/permissions/permissions_email.txt:21
+#: contentcuration/templates/registration/activation_email.txt:12
+#: contentcuration/templates/registration/activation_needed_email.txt:14
+#: contentcuration/templates/registration/channel_published_email.html:23
+#: contentcuration/templates/registration/password_reset_email.txt:14
+#: contentcuration/templates/registration/registration_needed_email.txt:12
+#: contentcuration/templates/settings/account_deleted_user_email.txt:10
+#: contentcuration/templates/settings/storage_request_email.txt:46
+msgid "Thanks for using Kolibri Studio!"
+msgstr "Merci d'utiliser Kolibri Studio !"
+
+#: contentcuration/templates/export/user_csv_email.txt:31
+#: contentcuration/templates/permissions/permissions_email.html:118
+#: contentcuration/templates/permissions/permissions_email.txt:23
+#: contentcuration/templates/registration/activation_email.html:111
+#: contentcuration/templates/registration/activation_email.txt:14
+#: contentcuration/templates/registration/activation_needed_email.txt:16
+#: contentcuration/templates/registration/channel_published_email.html:25
+#: contentcuration/templates/registration/password_reset_email.html:111
+#: contentcuration/templates/registration/password_reset_email.txt:16
+#: contentcuration/templates/registration/registration_needed_email.txt:14
+#: contentcuration/templates/registration/welcome_new_user_email.html:172
+#: contentcuration/templates/settings/account_deleted_user_email.txt:12
+#: contentcuration/templates/settings/issue_report_email.txt:26
+#: contentcuration/templates/settings/storage_request_email.txt:48
+msgid "The Learning Equality Team"
+msgstr "LāĆ©quipe de Learning Equality"
+
#: contentcuration/templates/export/user_csv_email_subject.txt:1
msgid "Your Kolibri Studio account information"
msgstr "Informations sur votre compte Kolibri Studio"
-#: contentcuration/templates/permissions/open_channel_fail.html:12
-msgid "There was an error opening this channel."
-msgstr "Une erreur s'est produite lors de l'ouverture de cette chaƮne."
-
-#: contentcuration/templates/permissions/open_channel_fail.html:13
-msgid "Try running ricecooker again."
-msgstr "Essayez d'exƩcuter de nouveau ricecooker."
-
-#: contentcuration/templates/permissions/permissions_email.html:93
-#: contentcuration/templates/registration/activation_email.html:91
-#: contentcuration/templates/registration/password_reset_email.html:91
+#: contentcuration/templates/permissions/permissions_email.html:92
+#: contentcuration/templates/registration/activation_email.html:90
+#: contentcuration/templates/registration/password_reset_email.html:90
msgid "Hello"
msgstr "Bonjour"
-#: contentcuration/templates/permissions/permissions_email.html:94
+#: contentcuration/templates/permissions/permissions_email.html:93
msgid "has invited you to edit a channel at"
msgstr "vous a invitĆ© Ć modifier une chaĆ®ne sur"
-#: contentcuration/templates/permissions/permissions_email.html:100
+#: contentcuration/templates/permissions/permissions_email.html:99
#, python-format
msgid "Invititation to %(share_mode)s channel"
msgstr "Invitation Ć la chaĆ®ne %(share_mode)s"
-#: contentcuration/templates/permissions/permissions_email.html:104
+#: contentcuration/templates/permissions/permissions_email.html:103
msgid "Click one of the following links to either accept or decline your invitation:"
msgstr "Cliquez sur l'un des liens suivants pour accepter ou refuser votre invitation :"
-#: contentcuration/templates/permissions/permissions_email.html:107
-#: contentcuration/templates/permissions/permissions_email.html:109
+#: contentcuration/templates/permissions/permissions_email.html:106
+#: contentcuration/templates/permissions/permissions_email.html:108
msgid "ACCEPT"
msgstr "ACCEPTER"
-#: contentcuration/templates/permissions/permissions_email.html:112
+#: contentcuration/templates/permissions/permissions_email.html:111
msgid "DECLINE"
msgstr "REFUSER"
@@ -494,24 +232,24 @@ msgstr "Vous avez Ć©tĆ© invitĆ© Ć modifier %(channel)s"
msgid "You've been invited to view %(channel)s"
msgstr "Vous avez Ć©tĆ© invitĆ© Ć visualiser %(channel)s"
-#: contentcuration/templates/registration/activation_email.html:92
-msgid "Welcome to Kolibri! Here is the link to activate your account:"
-msgstr "Bienvenue Ć Kolibri ! Voici le lien permettant d'activer votre compte :"
+#: contentcuration/templates/registration/activation_email.html:91
+msgid "Welcome to Kolibri Studio! Here is the link to activate your account:"
+msgstr "Bienvenue dans Kolibri Studio ! Voici le lien pour activer votre compte :"
-#: contentcuration/templates/registration/activation_email.html:101
+#: contentcuration/templates/registration/activation_email.html:100
msgid "Click here to activate your account."
msgstr "Cliquez ici pour activer votre compte."
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
msgid "This link is valid for"
msgstr "Ce lien reste valable pendant"
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
#, python-format
msgid "%(expiration_days)s days."
msgstr "%(expiration_days)s jours."
-#: contentcuration/templates/registration/activation_email.html:104
+#: contentcuration/templates/registration/activation_email.html:103
msgid "ACTIVATE"
msgstr "ACTIVER"
@@ -539,27 +277,31 @@ msgstr "Vous avez demandĆ© Ć rĆ©initialiser votre mot de passe sur %(site_name)
msgid "Please activate your account by following the link below:"
msgstr "Veuillez activer votre compte en suivant le lien ci-dessous :"
-#: contentcuration/templates/registration/channel_published_email.txt:6
+#: contentcuration/templates/registration/channel_published_email.html:12
#, python-format
-msgid "%(channel_name)s has finished publishing! Here is the channel token (for importing it into Kolibri):"
-msgstr "La publication de %(channel_name)s est terminƩe ! Voici son jeton (pour l'importer dans Kolibri) :"
+msgid "%(channel_name)s"
+msgstr "%(channel_name)s"
+
+#: contentcuration/templates/registration/channel_published_email.html:12
+msgid "has finished publishing! Here is the channel token (for importing it into Kolibri):"
+msgstr "a fini la publication ! Voici le jeton de la chaƮne (pour l'importer dans Kolibri) :"
-#: contentcuration/templates/registration/channel_published_email.txt:8
+#: contentcuration/templates/registration/channel_published_email.html:15
#, python-format
msgid "Token: %(channel_token)s"
msgstr "Jeton : %(channel_token)s"
-#: contentcuration/templates/registration/channel_published_email.txt:10
+#: contentcuration/templates/registration/channel_published_email.html:17
#, python-format
msgid "ID (for Kolibri version 0.6.0 and below): %(channel_id)s"
msgstr "ID (pour les versions de Kolibri infĆ©rieures ou Ć©gales Ć 0.6.0) : %(channel_id)s"
-#: contentcuration/templates/registration/channel_published_email.txt:12
+#: contentcuration/templates/registration/channel_published_email.html:20
#, python-format
msgid "Version notes: %(notes)s"
msgstr "Notes de version : %(notes)s"
-#: contentcuration/templates/registration/channel_published_email.txt:21
+#: contentcuration/templates/registration/channel_published_email.html:28
msgid "You are receiving this email because you are subscribed to this channel."
msgstr "Vous recevez cet e-mail parce que vous ĆŖtes abonnĆ© Ć cette chaĆ®ne."
@@ -588,23 +330,23 @@ msgstr "Le lien de rĆ©initialisation du mot de passe Ć©tait invalide, peut-ĆŖtre
msgid "Request a new password reset."
msgstr "Demander une nouvelle rƩinitialisation de mot de passe."
-#: contentcuration/templates/registration/password_reset_email.html:92
+#: contentcuration/templates/registration/password_reset_email.html:91
msgid "You are receiving this e-mail because you requested a password reset for your user account at"
msgstr "Vous recevez cet e-mail parce que vous avez demandĆ© Ć rĆ©initialiser votre mot de passe pour votre compte utilisateur sur"
-#: contentcuration/templates/registration/password_reset_email.html:98
+#: contentcuration/templates/registration/password_reset_email.html:97
msgid "Reset my Password"
msgstr "RĆ©initialiser mon mot de passe"
-#: contentcuration/templates/registration/password_reset_email.html:101
+#: contentcuration/templates/registration/password_reset_email.html:100
msgid "Please click the button below and choose a new password."
msgstr "Veuillez cliquer sur le bouton ci-dessous pour choisir un nouveau mot de passe."
-#: contentcuration/templates/registration/password_reset_email.html:102
+#: contentcuration/templates/registration/password_reset_email.html:101
msgid "Your username is"
msgstr "Votre nom d'utilisateur est"
-#: contentcuration/templates/registration/password_reset_email.html:104
+#: contentcuration/templates/registration/password_reset_email.html:103
msgid "RESET"
msgstr "RĆINITIALISER"
@@ -647,6 +389,139 @@ msgstr "Vous avez demandĆ© Ć rĆ©initialiser votre mot de passe pour %(site_name
msgid "Please create an account by following the link below:"
msgstr "Veuillez crƩer un compte en suivant le lien ci-dessous :"
+#: contentcuration/templates/registration/welcome_new_user_email.html:78
+msgid "Welcome to Kolibri Studio!"
+msgstr "Bienvenue dans Kolibri Studio !"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:82
+#, python-format
+msgid "\n"
+" We're delighted to introduce you to Kolibri Studio , our curricular tool to add,\n"
+" organize, and manage your own resources or those from the Kolibri Content Library.\n"
+" "
+msgstr "\n"
+" Nous sommes ravis de vous prƩsenter Kolibri Studio , notre outil pƩdagogique pour ajouter,\n"
+" organiser et gĆ©rer vos propres ressources ou celles de la bibliothĆØque de contenu Kolibri. "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:87
+msgid "View the Kolibri Content Library"
+msgstr "Voir la bibliothĆØque de contenu Kolibri"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:93
+msgid "\n"
+" Using Kolibri Studio, you can explore pre-organized collections of open educational resources (OER), and bundle,\n"
+" tag, differentiate, re-order, and distribute them into custom channels.\n"
+" "
+msgstr "\n"
+" Avec Kolibri Studio, vous pouvez explorer des collections prƩ-organisƩes de ressources pƩdagogiques libres (OER), et les regrouper,\n"
+" les Ʃtiqueter, les diffƩrencier, les rƩordonner et les distribuer dans des chaƮnes personnalisƩes.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:99
+msgid "\n"
+" Using an admin account, you can then publish and import these custom channels--either your own or those shared\n"
+" with you -- into Kolibri with a unique \"token\" generated for each channel.\n"
+" "
+msgstr "\n"
+" GrĆ¢ce Ć un compte administrateur, vous pouvez alors publier et importer ces chaĆ®nes personnalisĆ©es - les vĆ“tres ou celles qui sont partagĆ©es \n"
+" avec vous - dans Kolibri, avec un \"jeton\" unique gƩnƩrƩ pour chaque chaƮne. "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:107
+msgid "\n"
+" Browse through the list of resources below* to learn more about Kolibri Studio and to begin creating your own\n"
+" custom channels:\n"
+" "
+msgstr "\n"
+" Parcourez la liste des ressources ci-dessous* pour en savoir plus sur Kolibri Studio et commencer Ć crĆ©er vos \n"
+" propres chaƮnes personnalisƩes :\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:115
+msgid "Kolibri Studio User Guide"
+msgstr "Guide de l'utilisateur de Kolibri Studio"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:120
+msgid "Content integration guide:"
+msgstr "Guide d'intƩgration de contenu :"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:122
+msgid "\n"
+" Information on licensing, compatible formats, technical integration and more.\n"
+" "
+msgstr "\n"
+" Informations sur les licences, les formats compatibles, l'intƩgration technique et autres.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:125
+msgid "\n"
+" Note that if you are adding a small number of resources, technical integration is not necessary. \n"
+" "
+msgstr "\n"
+" Notez que si vous ajoutez un petit nombre de ressources, l'intƩgration technique ne sera pas nƩcessaire. \n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:130
+msgid "Step by step tutorials:"
+msgstr "Tutoriels Ć©tape par Ć©tape :"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:133
+msgid "Video format:"
+msgstr "Format vidƩo :"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:135
+msgid "Using Kolibri Studio: Your Content Workspace for Kolibri"
+msgstr "Utilisation de Kolibri Studio : Votre espace de travail dans Kolibri"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:137
+msgid "(*also available in French and Arabic)"
+msgstr "(*Ʃgalement disponible en franƧais et en arabe)"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:140
+msgid "Slide gif format:"
+msgstr "Diapositive au format gif :"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:144
+msgid "Step by step Studio tutorial"
+msgstr "Tutoriel Studio Ć©tape par Ć©tape"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:151
+msgid "Video compression instructions:"
+msgstr "Instructions sur la compression vidƩo :"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:153
+msgid "\n"
+" For optimal results, videos should be compressed in order to achieve small file sizes. Compression ensures\n"
+" that the videos are well suited for offline distribution and playback on all Kolibri devices.\n"
+" "
+msgstr "\n"
+" Pour des rĆ©sultats optimaux, les vidĆ©os doivent ĆŖtre compressĆ©es afin d'obtenir des fichiers de petite taille. La compression \n"
+" permet Ć ce que les vidĆ©os soient bien adaptĆ©es Ć la distribution hors ligne et Ć la lecture sur tous les appareils Kolibri.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:158
+msgid "View the guide to video compression"
+msgstr "Voir le guide sur la compression vidƩo"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:165
+msgid "If you need support with Kolibri Studio, please reach out to us on our Community Forum."
+msgstr "Si vous avez besoin d'aide Ć propos de Kolibri Studio, n'hĆ©sitez pas Ć nous contacter sur notre forum communautaire."
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:167
+msgid "Access the Community Forum"
+msgstr "AccƩder au forum communautaire"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:171
+msgid "Thank You!"
+msgstr "Merci !"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:178
+msgid "*resources are presented in English"
+msgstr "*Les ressources sont prƩsentƩes en anglais"
+
+#: contentcuration/templates/registration/welcome_new_user_email_subject.txt:1
+msgid "Thank you for activating your Kolibri Studio account! Let's get started..."
+msgstr "Merci d'avoir activƩ votre compte Kolibri Studio ! C'est parti..."
+
#: contentcuration/templates/settings/account_deleted_user_email.txt:5
#, python-format
msgid "Your %(email)s account on %(site_name)s has been deleted."
@@ -709,188 +584,107 @@ msgstr "Nous vous recommandons d'utiliser Firefox ou Chrome pour utiliser Kolibr
msgid "You can also try updating your current browser."
msgstr "Vous pouvez aussi essayer de mettre Ć jour votre navigateur actuel."
-#: contentcuration/templatetags/license_tags.py:10
+#: contentcuration/templatetags/license_tags.py:11
msgid "The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials."
msgstr "La Licence d'Attribution permet aux autres de distribuer, remixer, modifier et dĆ©velopper votre Åuvre, y compris Ć des fins commerciales, Ć condition qu'ils vous attribuent la paternitĆ© de la crĆ©ation originale. Parmi les licences proposĆ©es, elle est la plus ouverte. Elle est recommandĆ©e pour la diffusion et l'utilisation maximales des supports sous licence."
-#: contentcuration/templatetags/license_tags.py:15
+#: contentcuration/templatetags/license_tags.py:16
msgid "The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to \"copyleft\" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects."
msgstr "La Licence d'Attribution-Partage dans les mĆŖmes conditions permet aux autres de remixer, modifier et dĆ©velopper votre Åuvre, y compris Ć des fins commerciales, Ć condition qu'ils vous attribuent la paternitĆ© de la crĆ©ation originale et qu'ils diffusent leurs nouvelles crĆ©ations selon les mĆŖmes conditions. Cette licence est souvent comparĆ©e aux licences de logiciels libres et open source de type Ā« copyleft Ā». Toutes les nouvelles Åuvres basĆ©es sur la vĆ“tre porteront la mĆŖme licence, de sorte que tous les travaux dĆ©rivĆ©s autoriseront Ć©galement une utilisation commerciale. Licence utilisĆ©e par WikipĆ©dia, elle est recommandĆ©e pour les supports qui bĆ©nĆ©ficieraient de l'incorporation de contenus de WikipĆ©dia et de projets sous licences similaires."
-#: contentcuration/templatetags/license_tags.py:25
+#: contentcuration/templatetags/license_tags.py:26
msgid "The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you."
msgstr "La Licence d'Attribution-Pas de travaux dĆ©rivĆ©s autorise la redistribution de l'Åuvre, Ć des fins commerciales et non commerciales, Ć condition qu'elle soit transmise intacte et en totalitĆ© et que sa paternitĆ© vous soit attribuĆ©e."
-#: contentcuration/templatetags/license_tags.py:28
+#: contentcuration/templatetags/license_tags.py:29
msgid "The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms."
msgstr "La Licence d'Attribution-Pas d'utilisation commerciale permet aux autres de remixer, de modifier et de dĆ©velopper votre Åuvre Ć des fins non commerciales, et si leurs nouvelles Åuvres doivent vous citer et demeurer non commerciales, leurs Åuvres dĆ©rivĆ©es ne sont pas tenues d'ĆŖtre diffusĆ©es sous les mĆŖmes conditions."
-#: contentcuration/templatetags/license_tags.py:32
+#: contentcuration/templatetags/license_tags.py:33
msgid "The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms."
msgstr "La Licence d'Attribution-Pas d'utilisation commerciale-Partage dans les mĆŖmes conditions permet aux autres de remixer, modifier et dĆ©velopper votre Åuvre Ć des fins non commerciales, Ć condition qu'ils vous en attribuent la paternitĆ© et diffusent leurs nouvelles crĆ©ations sous les mĆŖmes conditions."
-#: contentcuration/templatetags/license_tags.py:36
+#: contentcuration/templatetags/license_tags.py:37
msgid "The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially."
msgstr "La Licence d'Attribution-Pas d'utilisation commerciale-Pas de travaux dĆ©rivĆ©s est la plus restrictive de nos six licences principales. Elle n'autorise aux autres que le tĆ©lĆ©chargement et le partage de vos Åuvres Ć condition qu'ils vous en attribuent la paternitĆ©, et interdit toute modification de quelque nature que ce soit et toute utilisation Ć des fins commerciales."
-#: contentcuration/templatetags/license_tags.py:40
+#: contentcuration/templatetags/license_tags.py:41
msgid "The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty."
msgstr "La Licence Tous droits rĆ©servĆ©s indique que le titulaire du droit d'auteur rĆ©serve, ou dĆ©tient pour son propre usage, tous les droits prĆ©vus par la loi sur le droit d'auteur en vertu d'un traitĆ© spĆ©cifique en la matiĆØre."
-#: contentcuration/templatetags/license_tags.py:43
+#: contentcuration/templatetags/license_tags.py:44
msgid "Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights."
msgstr "Les Åuvres du domaine public ont Ć©tĆ© identifiĆ©es comme libres de toute restriction connue en vertu de la loi sur le droit d'auteur, y compris de tous droits connexes et voisins."
-#: contentcuration/templatetags/license_tags.py:46
+#: contentcuration/templatetags/license_tags.py:47
msgid "Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails."
msgstr "Les autorisations spĆ©ciales dĆ©signent des licences personnalisĆ©es Ć utiliser lorsque les licences actuelles ne s'appliquent pas au contenu. Il revient au propriĆ©taire d'une telle licence de dĆ©crire ce que la licence en question implique."
-#: contentcuration/templatetags/translation_tags.py:26
-msgid "100% Correct"
-msgstr "100% correct"
-
-#: contentcuration/templatetags/translation_tags.py:27
-msgid "10 in a row"
-msgstr "10 d'affilƩe"
-
-#: contentcuration/templatetags/translation_tags.py:28
-msgid "2 in a row"
-msgstr "2 d'affilƩe"
-
-#: contentcuration/templatetags/translation_tags.py:29
-msgid "3 in a row"
-msgstr "3 d'affilƩe"
-
-#: contentcuration/templatetags/translation_tags.py:30
-msgid "5 in a row"
-msgstr "5 d'affilƩe"
-
-#: contentcuration/templatetags/translation_tags.py:31
-msgid "M of N..."
-msgstr "M sur N..."
-
-#: contentcuration/templatetags/translation_tags.py:32
-msgid "CC BY"
-msgstr "CC BY"
-
-#: contentcuration/templatetags/translation_tags.py:33
-msgid "CC BY-SA"
-msgstr "CC BY-SA"
-
-#: contentcuration/templatetags/translation_tags.py:34
-msgid "CC BY-ND"
-msgstr "CC BY-ND"
-
-#: contentcuration/templatetags/translation_tags.py:35
-msgid "CC BY-NC"
-msgstr "CC BY-NC"
-
-#: contentcuration/templatetags/translation_tags.py:36
-msgid "CC BY-NC-SA"
-msgstr "CC BY-NC-SA"
-
-#: contentcuration/templatetags/translation_tags.py:37
-msgid "CC BY-NC-ND"
-msgstr "CC BY-NC-ND"
-
-#: contentcuration/templatetags/translation_tags.py:38
-msgid "All Rights Reserved"
-msgstr "Tous droits rƩservƩs"
-
-#: contentcuration/templatetags/translation_tags.py:39
-msgid "Public Domain"
-msgstr "Domaine public"
-
-#: contentcuration/templatetags/translation_tags.py:40
-msgid "Special Permissions"
-msgstr "Autorisations spƩciales"
-
-#: contentcuration/templatetags/translation_tags.py:49
-#, python-format
-msgid "%(filesize)s %(unit)s"
-msgstr "%(filesize)s %(unit)s"
-
-#: contentcuration/utils/csv_writer.py:138
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:45
+#: contentcuration/utils/csv_writer.py:108
msgid "No Channel"
msgstr "Aucune chaƮne"
-#: contentcuration/utils/csv_writer.py:139
+#: contentcuration/utils/csv_writer.py:46
msgid "No resource"
msgstr "Aucune ressource"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Channel"
msgstr "ChaƮne"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Title"
msgstr "Titre"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Kind"
msgstr "Type"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Filename"
msgstr "Nom du fichier"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
+msgid "File Size"
+msgstr "Taille du fichier"
+
+#: contentcuration/utils/csv_writer.py:71
msgid "URL"
msgstr "URL"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Description"
msgstr "Description"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Author"
msgstr "Auteur"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Language"
msgstr "Langue"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License"
msgstr "Licence"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License Description"
msgstr "Description de la licence"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Copyright Holder"
msgstr "Titulaire des droits d'auteur"
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "No Resource"
msgstr "Aucune ressource"
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "Staged File"
msgstr "Fichier indexƩ"
-#: contentcuration/utils/format.py:15
-msgid "B"
-msgstr "o"
-
-#: contentcuration/utils/format.py:17
-msgid "KB"
-msgstr "Ko"
-
-#: contentcuration/utils/format.py:19
-msgid "MB"
-msgstr "Mo"
-
-#: contentcuration/utils/format.py:21
-msgid "GB"
-msgstr "Go"
-
-#: contentcuration/utils/format.py:23
-msgid "TB"
-msgstr "To"
-
#: contentcuration/utils/incidents.py:7
msgid "There was a problem with a third-party service. This means certain operations might be blocked. We appreciate your patience while these issues are being resolved."
msgstr "Nous rencontrons un problĆØme avec un service tiers. Certaines opĆ©rations pourraient s'en retrouver bloquĆ©es. Nous vous remercions de votre patience pendant que nous cherchons Ć rĆ©soudre la question."
@@ -915,23 +709,26 @@ msgstr "Nous rencontrons des problĆØmes avec un service tiers. Toute opĆ©ration
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here "
msgstr "Nous rencontrons des problĆØmes avec notre centre de donnĆ©es. Vous pourriez par consĆ©quent rencontrer des problĆØmes de rĆ©seau lors de l'utilisation de Studio. Nous vous remercions de votre patience pendant la rĆ©solution de ces problĆØmes. Pour vĆ©rifier l'Ć©tat de ce service, veuillez visiter ici "
-#: contentcuration/utils/publish.py:57
+#: contentcuration/utils/publish.py:96
msgid "Kolibri Studio Channel Published"
msgstr "ChaƮne Kolibri Studio publiƩe"
-#: contentcuration/views/public.py:63 contentcuration/views/public.py:74
-msgid "Api endpoint {} is not available"
-msgstr "Le point de terminaison {} de l'API n'est pas disponible"
-
-#: contentcuration/views/public.py:76
-msgid "No channel matching {} found"
-msgstr "Aucune chaĆ®ne correspondant Ć {} trouvĆ©e"
-
-#: contentcuration/views/settings.py:110
+#: contentcuration/views/settings.py:111
msgid "Kolibri Studio issue report"
msgstr "Rapport d'erreurs de Kolibri Studio"
-#: contentcuration/views/settings.py:144
+#: contentcuration/views/settings.py:143
msgid "Kolibri Studio account deleted"
msgstr "Compte Kolibri Studio supprimƩ"
+#: kolibri_public/views.py:220
+msgid "Resource"
+msgstr "Ressource"
+
+#: kolibri_public/views_v1.py:63 kolibri_public/views_v1.py:74
+msgid "Api endpoint {} is not available"
+msgstr "Le point de terminaison {} de l'API n'est pas disponible"
+
+#: kolibri_public/views_v1.py:76
+msgid "No channel matching {} found"
+msgstr "Aucune chaĆ®ne correspondant Ć {} trouvĆ©e"
diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/README.md b/contentcuration/locale/hi_IN/LC_MESSAGES/README.md
new file mode 100644
index 0000000000..0f82b94d50
--- /dev/null
+++ b/contentcuration/locale/hi_IN/LC_MESSAGES/README.md
@@ -0,0 +1 @@
+The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.csv b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.csv
index 12a9bfef65..cb2a7f3b2a 100644
--- a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.csv
+++ b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.csv
@@ -28,7 +28,7 @@
","Basic Information"
"Account.changePasswordAction","Change password","
-- CONTEXT --
-","Change password"
+","ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤” ą¤¬ą¤¦ą¤²ą„ą¤"
"Account.completelyDeleteAccountLabel","Completely remove your account from Kolibri Studio","
-- CONTEXT --
","Completely remove your account from Kolibri Studio"
@@ -58,29 +58,29 @@
","Data export started"
"Account.fullNameLabel","Full name","
-- CONTEXT --
-","Full name"
+","ą¤Ŗą„ą¤°ą¤¾ ą¤Øą¤¾ą¤®"
"Account.handleChannelsBeforeAccount","You must delete these channels manually or invite others to edit them before you can delete your account.","
-- CONTEXT --
","You must delete these channels manually or invite others to edit them before you can delete your account."
"Account.passwordLabel","Password","
-- CONTEXT --
-","Password"
+","ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤”"
"Account.unableToDeleteAdminAccount","Unable to delete an admin account","
-- CONTEXT --
","Unable to delete an admin account"
"Account.usernameLabel","Username","
-- CONTEXT --
-","Username"
+","ą¤Æą„ą¤ą¤°ą¤Øą„ą¤®"
"AccountCreated.accountCreatedTitle","Account successfully created","
-- CONTEXT --
","Account successfully created"
-"AccountCreated.continueToSignIn","Continue to sign-in","
+"AccountCreated.backToLogin","Continue to sign-in page","
-- CONTEXT --
-","Continue to sign-in"
+","Continue to sign-in page"
"AccountDeleted.accountDeletedTitle","Account successfully deleted","
-- CONTEXT --
","Account successfully deleted"
-"AccountDeleted.continueToSignIn","Continue to sign-in page","
+"AccountDeleted.backToLogin","Continue to sign-in page","
-- CONTEXT --
","Continue to sign-in page"
"AccountNotActivated.requestNewLink","Request a new activation link","
@@ -136,13 +136,13 @@
","Add previous step"
"AddRelatedResourcesModal.addStepBtnLabel","Add","
-- CONTEXT --
-","Add"
+","ą¤ą„ą¤”ą¤¼ą„ą¤"
"AddRelatedResourcesModal.cancelBtnLabel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"AddRelatedResourcesModal.previewStepBtnLabel","Preview","
-- CONTEXT --
-","Preview"
+","ą¤Ŗą„ą¤°ą„ą¤µą¤¾ą¤µą¤²ą„ą¤ą¤Ø"
"AddRelatedResourcesModal.resourcesDisplayedText","Only showing available resources for","
-- CONTEXT --
","Only showing available resources for"
@@ -160,16 +160,16 @@
","You need to be an administrator of Studio to view this page"
"AdministrationIndex.channelsLabel","Channels","
-- CONTEXT --
-","Channels"
+","ą¤ą„ą¤Øą¤²"
"AdministrationIndex.usersLabel","Users","
-- CONTEXT --
-","Users"
+","ą¤ą¤Ŗą¤Æą„ą¤ą¤ą¤°ą„ą¤¤ą¤¾"
"Alert.closeButtonLabel","OK","
-- CONTEXT --
","OK"
"Alert.dontShowAgain","Don't show this message again","
-- CONTEXT --
-","Don't show this message again"
+","ą¤Æą¤¹ ą¤øą¤ą¤¦ą„ą¤¶ ą¤¦ą„ą¤¬ą¤¾ą¤°ą¤¾ ą¤Ø ą¤¦ą¤æą¤ą¤¾ą¤ą¤"
"AnswersEditor.answersLabel","Answers","
-- CONTEXT --
","Answers"
@@ -187,25 +187,25 @@
","Administration"
"AppBar.changeLanguage","Change language","
-- CONTEXT --
-","Change language"
+","ą¤ą¤¾ą¤·ą¤¾ ą¤¬ą¤¦ą¤²ą„ą¤"
"AppBar.help","Help and support","
-- CONTEXT --
","Help and support"
"AppBar.logIn","Sign in","
-- CONTEXT --
-","Sign in"
+","ą¤øą¤¾ą¤ą¤Ø ą¤ą¤Ø ą¤ą¤°ą„ą¤"
"AppBar.logOut","Sign out","
-- CONTEXT --
-","Sign out"
+","ą¤øą¤¾ą¤ą¤Ø ą¤ą¤ą¤ ą¤ą¤°ą„ą¤"
"AppBar.settings","Settings","
-- CONTEXT --
-","Settings"
+","ą¤øą„ą¤ą¤æą¤ą¤"
"AppBar.title","Kolibri Studio","
-- CONTEXT --
-","Kolibri Studio"
+","Kolibri ą¤øą„ą¤ą„ą¤”ą¤æą¤Æą„"
"AssessmentEditor.closeBtnLabel","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"AssessmentEditor.incompleteItemIndicatorLabel","Incomplete","
-- CONTEXT --
","Incomplete"
@@ -220,7 +220,7 @@
","Show answers"
"AssessmentEditor.toolbarItemLabel","question","
-- CONTEXT --
-","question"
+","ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø"
"AssessmentItemEditor.dialogMessageChangeToInput","Switching to 'numeric input' will set all answers as correct and remove all non-numeric answers. Continue?","
-- CONTEXT --
","Switching to 'numeric input' will set all answers as correct and remove all non-numeric answers. Continue?"
@@ -232,13 +232,13 @@
","Switching to 'true or false' will remove all current answers. Continue?"
"AssessmentItemEditor.dialogSubmitBtnLabel","Change","
-- CONTEXT --
-","Change"
+","ą¤¬ą¤¦ą¤²ą„ą¤"
"AssessmentItemEditor.dialogTitle","Changing question type","
-- CONTEXT --
","Changing question type"
"AssessmentItemEditor.questionLabel","Question","
-- CONTEXT --
-","Question"
+","ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø"
"AssessmentItemEditor.questionTypeLabel","Response type","
-- CONTEXT --
","Response type"
@@ -262,10 +262,10 @@
","Add {itemLabel} below"
"AssessmentItemToolbar.toolbarLabelDelete","Delete","
-- CONTEXT --
-","Delete"
+","ą¤¹ą¤ą¤¾ą¤ą¤"
"AssessmentItemToolbar.toolbarLabelEdit","Edit","
-- CONTEXT --
-","Edit"
+","ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)"
"AssessmentItemToolbar.toolbarLabelMoveDown","Move down","
-- CONTEXT --
","Move down"
@@ -274,7 +274,7 @@
","Move up"
"AssessmentTab.dialogCancelBtnLabel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"AssessmentTab.dialogSubmitBtnLabel","Submit","
-- CONTEXT --
","Submit"
@@ -283,13 +283,10 @@
","{invalidItemsCount} incomplete {invalidItemsCount, plural, one {question} other {questions}}"
"BrowsingCard.addToClipboardAction","Copy to clipboard","
-- CONTEXT --
-","Copy to clipboard"
+","ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤"
"BrowsingCard.coach","Resource for coaches","
-- CONTEXT --
","Resource for coaches"
-"BrowsingCard.goToPluralLocationsAction","In {count, number} {count, plural, one {location} other {locations}}","
--- CONTEXT --
-","In {count, number} {count, plural, one {location} other {locations}}"
"BrowsingCard.goToSingleLocationAction","Go to location","
-- CONTEXT --
","Go to location"
@@ -301,7 +298,7 @@
","View details"
"BrowsingCard.resourcesCount","{count, number} {count, plural, one {resource} other {resources}}","
-- CONTEXT --
-","{count, number} {count, plural, one {resource} other {resources}}"
+","{count, number} {count, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø}}"
"BrowsingCard.tagsList","Tags: {tags}","
-- CONTEXT --
","Tags: {tags}"
@@ -340,7 +337,7 @@
","Welcome to the Kolibri Content Library Catalog! "
"CatalogFAQ.aboutKolibriHeader","About Kolibri","
-- CONTEXT --
-","About Kolibri"
+","Kolibri ą¤ą„ ą¤¬ą¤¾ą¤°ą„ ą¤®ą„ą¤"
"CatalogFAQ.aboutLibraryHeader","About the Kolibri Content Library","
-- CONTEXT --
","About the Kolibri Content Library"
@@ -379,7 +376,7 @@
","Download Kolibri"
"CatalogFAQ.downloadLink","Download","
-- CONTEXT --
-","Download"
+","ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤ą¤°ą„ą¤"
"CatalogFAQ.endoresementQuestion","Have these sources been vetted or endorsed as classroom-safe and ready?","
-- CONTEXT --
","Have these sources been vetted or endorsed as classroom-safe and ready?"
@@ -509,10 +506,10 @@
other {# channels}}"
"CatalogFilterBar.clearAll","Clear all","
-- CONTEXT --
-","Clear all"
+","ą¤øą¤ą„ ą¤øą¤¾ą¤«ą¤¼ ą¤ą¤°ą„ą¤"
"CatalogFilterBar.close","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"CatalogFilterBar.coachContent","Coach content","
-- CONTEXT --
","Coach content"
@@ -533,7 +530,7 @@
","Starred"
"CatalogFilterBar.subtitles","Subtitles","
-- CONTEXT --
-","Subtitles"
+","ą¤ą¤Ŗą¤¶ą„ą¤°ą„ą¤·ą¤"
"CatalogFilters.coachDescription","Resources for coaches are only visible to coaches in Kolibri","
-- CONTEXT --
","Resources for coaches are only visible to coaches in Kolibri"
@@ -557,10 +554,10 @@
","Licenses"
"CatalogFilters.searchLabel","Keywords","
-- CONTEXT --
-","Keywords"
+","ą¤ą„ą¤¬ą„ą¤°ą„ą¤”"
"CatalogFilters.searchText","Search","
-- CONTEXT --
-","Search"
+","ą¤ą„ą¤"
"CatalogFilters.starredLabel","Starred","
-- CONTEXT --
","Starred"
@@ -569,7 +566,7 @@
","Captions or subtitles"
"CatalogList.cancelButton","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"CatalogList.channelSelectionCount","{count, plural,
=1 {# channel selected}
other {# channels selected}}","
@@ -579,16 +576,16 @@
other {# channels selected}}"
"CatalogList.downloadButton","Download","
-- CONTEXT --
-","Download"
+","ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤ą¤°ą„ą¤"
"CatalogList.downloadCSV","Download CSV","
-- CONTEXT --
-","Download CSV"
+","CSV ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤ą¤°ą„ą¤"
"CatalogList.downloadPDF","Download PDF","
-- CONTEXT --
","Download PDF"
"CatalogList.downloadingMessage","Download started","
-- CONTEXT --
-","Download started"
+","ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤¶ą„ą¤°ą„"
"CatalogList.resultsText","{count, plural,
=1 {# result found}
other {# results found}}","
@@ -598,7 +595,7 @@
other {# results found}}"
"CatalogList.selectAll","Select all","
-- CONTEXT --
-","Select all"
+","ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤"
"CatalogList.selectChannels","Download a summary of selected channels","
-- CONTEXT --
","Download a summary of selected channels"
@@ -607,10 +604,10 @@
","Category not found"
"ChangePasswordForm.cancelAction","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"ChangePasswordForm.changePasswordHeader","Change password","
-- CONTEXT --
-","Change password"
+","ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤” ą¤¬ą¤¦ą¤²ą„ą¤"
"ChangePasswordForm.confirmNewPasswordLabel","Confirm new password","
-- CONTEXT --
","Confirm new password"
@@ -628,7 +625,7 @@
","Password updated"
"ChangePasswordForm.saveChangesAction","Save changes","
-- CONTEXT --
-","Save changes"
+","ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Øą„ą¤ ą¤ą„ ą¤øą„ą¤µ ą¤ą¤°ą„ą¤"
"ChannelCatalogFrontPage.assessmentsIncludedText","Assessments","
-- CONTEXT --
","Assessments"
@@ -652,7 +649,7 @@
","Formats"
"ChannelCatalogFrontPage.languagesHeading","Languages","
-- CONTEXT --
-","Languages"
+","ą¤ą¤¾ą¤·ą¤¾ą¤ą¤"
"ChannelCatalogFrontPage.numberOfChannels","{ num } channels","
-- CONTEXT --
","{ num } channels"
@@ -661,19 +658,19 @@
","Captions or subtitles"
"ChannelDeletedError.backToHomeAction","Back to home","
-- CONTEXT --
-","Back to home"
+","ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤æą¤ø ą¤ą¤¾ą¤ą¤"
"ChannelDeletedError.channelDeletedDetails","This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.","
-- CONTEXT --
","This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake."
"ChannelDeletedError.channelDeletedHeader","Channel not found","
-- CONTEXT --
-","Channel not found"
+","ą¤ą„ą¤Øą¤² ą¤Øą¤¹ą„ą¤ ą¤®ą¤æą¤²ą¤¾"
"ChannelDetailsModal.downloadButton","Download channel summary","
-- CONTEXT --
","Download channel summary"
"ChannelDetailsModal.downloadCSV","Download CSV","
-- CONTEXT --
-","Download CSV"
+","CSV ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤ą¤°ą„ą¤"
"ChannelDetailsModal.downloadPDF","Download PDF","
-- CONTEXT --
","Download PDF"
@@ -694,7 +691,7 @@
","Copyright holders"
"ChannelExportStrings.description","Description","
-- CONTEXT --
-","Description"
+","ą¤µą¤æą¤µą¤°ą¤£"
"ChannelExportStrings.downloadFilename","{year}_{month}_Kolibri_Content_Library","
-- CONTEXT --
","{year}_{month}_Kolibri_Content_Library"
@@ -703,7 +700,7 @@
","Channel ID"
"ChannelExportStrings.language","Language","
-- CONTEXT --
-","Language"
+","ą¤ą¤¾ą¤·ą¤¾Ā "
"ChannelExportStrings.languages","Included languages","
-- CONTEXT --
","Included languages"
@@ -712,16 +709,16 @@
","Licenses"
"ChannelExportStrings.name","Name","
-- CONTEXT --
-","Name"
+","ą¤Øą¤¾ą¤®"
"ChannelExportStrings.no","No","
-- CONTEXT --
-","No"
+","ą¤Øą¤¹ą„ą¤"
"ChannelExportStrings.providers","Providers","
-- CONTEXT --
","Providers"
"ChannelExportStrings.resources","Resources","
-- CONTEXT --
-","Resources"
+","ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø"
"ChannelExportStrings.size","Total resources","
-- CONTEXT --
","Total resources"
@@ -739,22 +736,22 @@
","Token"
"ChannelExportStrings.yes","Yes","
-- CONTEXT --
-","Yes"
+","ą¤¹ą¤¾ą¤"
"ChannelInfoCard.resourceCount","{count, number} {count, plural, one {resource} other {resources}}","
-- CONTEXT --
-","{count, number} {count, plural, one {resource} other {resources}}"
+","{count, number} {count, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø}}"
"ChannelInvitation.accept","Accept","
-- CONTEXT --
-","Accept"
+","ą¤øą„ą¤µą„ą¤ą¤¾ą¤° ą¤ą¤°ą„ą¤"
"ChannelInvitation.acceptedSnackbar","Accepted invitation","
-- CONTEXT --
","Accepted invitation"
"ChannelInvitation.cancel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"ChannelInvitation.decline","Decline","
-- CONTEXT --
-","Decline"
+","ą¤
ą¤øą„ą¤µą„ą¤ą¤¾ą¤° ą¤ą¤°ą„ą¤"
"ChannelInvitation.declinedSnackbar","Declined invitation","
-- CONTEXT --
","Declined invitation"
@@ -775,7 +772,7 @@
","{sender} has invited you to view {channel}"
"ChannelItem.cancel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"ChannelItem.channelDeletedSnackbar","Channel deleted","
-- CONTEXT --
","Channel deleted"
@@ -787,7 +784,7 @@
","Copy channel token"
"ChannelItem.deleteChannel","Delete channel","
-- CONTEXT --
-","Delete channel"
+","ą¤ą„ą¤Øą¤² ą¤¹ą¤ą¤¾ą¤ą¤"
"ChannelItem.deletePrompt","This channel will be permanently deleted. This cannot be undone.","
-- CONTEXT --
","This channel will be permanently deleted. This cannot be undone."
@@ -796,7 +793,7 @@
","Delete this channel"
"ChannelItem.details","Details","
-- CONTEXT --
-","Details"
+","ą¤µą¤æą¤µą¤°ą¤£"
"ChannelItem.editChannel","Edit channel details","
-- CONTEXT --
","Edit channel details"
@@ -821,7 +818,7 @@
","Unpublished"
"ChannelItem.versionText","Version {version}","
-- CONTEXT --
-","Version {version}"
+","ą¤øą¤ą¤øą„ą¤ą¤°ą¤£ {version}"
"ChannelItem.viewContent","View channel on Kolibri","
-- CONTEXT --
","View channel on Kolibri"
@@ -830,7 +827,7 @@
","New channel"
"ChannelList.channelFilterLabel","Channels","
-- CONTEXT --
-","Channels"
+","ą¤ą„ą¤Øą¤²"
"ChannelList.noChannelsFound","No channels found","
-- CONTEXT --
","No channels found"
@@ -864,7 +861,7 @@
","Channels generated automatically are not editable."
"ChannelModal.changesSaved","Changes saved","
-- CONTEXT --
-","Changes saved"
+","ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Ø ą¤øą¤¹ą„ą¤ą„ ą¤ą¤"
"ChannelModal.channelDescription","Channel description","
-- CONTEXT --
","Channel description"
@@ -873,13 +870,13 @@
","Field is required"
"ChannelModal.channelName","Channel name","
-- CONTEXT --
-","Channel name"
+","ą¤ą„ą¤Øą¤² ą¤ą¤¾ ą¤Øą¤¾ą¤®"
"ChannelModal.closeButton","Exit without saving","
-- CONTEXT --
","Exit without saving"
"ChannelModal.createButton","Create","
-- CONTEXT --
-","Create"
+","ą¤¬ą¤Øą¤¾ą¤ą¤"
"ChannelModal.creatingHeader","New channel","
-- CONTEXT --
","New channel"
@@ -888,7 +885,7 @@
","Channel details"
"ChannelModal.editTab","Details","
-- CONTEXT --
-","Details"
+","ą¤µą¤æą¤µą¤°ą¤£"
"ChannelModal.keepEditingButton","Keep editing","
-- CONTEXT --
","Keep editing"
@@ -897,7 +894,7 @@
","Channel does not exist"
"ChannelModal.saveChangesButton","Save changes","
-- CONTEXT --
-","Save changes"
+","ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Øą„ą¤ ą¤ą„ ą¤øą„ą¤µ ą¤ą¤°ą„ą¤"
"ChannelModal.shareTab","Sharing","
-- CONTEXT --
","Sharing"
@@ -912,22 +909,22 @@
","You will lose any unsaved changes. Are you sure you want to exit?"
"ChannelNotFoundError.backToHomeAction","Back to home","
-- CONTEXT --
-","Back to home"
+","ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤æą¤ø ą¤ą¤¾ą¤ą¤"
"ChannelNotFoundError.channelNotFoundDetails","This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.","
-- CONTEXT --
","This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake."
"ChannelNotFoundError.channelNotFoundHeader","Channel not found","
-- CONTEXT --
-","Channel not found"
+","ą¤ą„ą¤Øą¤² ą¤Øą¤¹ą„ą¤ ą¤®ą¤æą¤²ą¤¾"
"ChannelSelectionList.noChannelsFound","No channels found","
-- CONTEXT --
","No channels found"
"ChannelSelectionList.searchText","Search for a channel","
-- CONTEXT --
-","Search for a channel"
+","ą¤ą¤æą¤øą„ ą¤ą„ą¤Øą¤² ą¤ą„ ą¤ą„ą¤ą„ą¤"
"ChannelSetItem.cancel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"ChannelSetItem.delete","Delete collection","
-- CONTEXT --
","Delete collection"
@@ -942,7 +939,7 @@
","Edit collection"
"ChannelSetItem.options","Options","
-- CONTEXT --
-","Options"
+","ą¤µą¤æą¤ą¤²ą„ą¤Ŗ"
"ChannelSetItem.saving","Saving","
-- CONTEXT --
","Saving"
@@ -957,7 +954,7 @@
","New collection"
"ChannelSetList.cancelButtonLabel","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"ChannelSetList.channelNumber","Number of channels","
-- CONTEXT --
","Number of channels"
@@ -975,7 +972,7 @@
","You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token."
"ChannelSetList.options","Options","
-- CONTEXT --
-","Options"
+","ą¤µą¤æą¤ą¤²ą„ą¤Ŗ"
"ChannelSetList.title","Collection name","
-- CONTEXT --
","Collection name"
@@ -1008,7 +1005,7 @@
","This collection does not exist"
"ChannelSetModal.createButton","Create","
-- CONTEXT --
-","Create"
+","ą¤¬ą¤Øą¤¾ą¤ą¤"
"ChannelSetModal.creatingChannelSet","New collection","
-- CONTEXT --
","New collection"
@@ -1017,7 +1014,7 @@
","My Channels"
"ChannelSetModal.finish","Finish","
-- CONTEXT --
-","Finish"
+","ą¤øą¤®ą¤¾ą¤Ŗą„ą¤¤"
"ChannelSetModal.public","Public","
-- CONTEXT --
","Public"
@@ -1026,7 +1023,7 @@
","Only published channels are available for selection"
"ChannelSetModal.removeText","Remove","
-- CONTEXT --
-","Remove"
+","ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤"
"ChannelSetModal.saveButton","Save and close","
-- CONTEXT --
","Save and close"
@@ -1089,7 +1086,7 @@
","Please enter a valid email"
"ChannelSharingTable.cancelButton","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"ChannelSharingTable.currentUserText","{first_name} {last_name} (you)","
-- CONTEXT --
","{first_name} {last_name} (you)"
@@ -1147,7 +1144,7 @@
","No users found"
"ChannelSharingTable.optionsDropdown","Options","
-- CONTEXT --
-","Options"
+","ą¤µą¤æą¤ą¤²ą„ą¤Ŗ"
"ChannelSharingTable.removeViewer","Revoke view permissions","
-- CONTEXT --
","Revoke view permissions"
@@ -1187,7 +1184,7 @@
","Removed from starred channels"
"ChannelThumbnail.cancel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"ChannelThumbnail.crop","Crop","
-- CONTEXT --
","Crop"
@@ -1202,13 +1199,13 @@
","No thumbnail"
"ChannelThumbnail.remove","Remove","
-- CONTEXT --
-","Remove"
+","ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤"
"ChannelThumbnail.retryUpload","Retry upload","
-- CONTEXT --
","Retry upload"
"ChannelThumbnail.save","Save","
-- CONTEXT --
-","Save"
+","ą¤øą„ą¤µ ą¤ą¤°ą„ą¤"
"ChannelThumbnail.upload","Upload image","
-- CONTEXT --
","Upload image"
@@ -1220,13 +1217,13 @@
","Uploading"
"ChannelThumbnail.zoomIn","Zoom in","
-- CONTEXT --
-","Zoom in"
+","ą¤ą¤¼ą„ą¤® ą¤ą¤Ø ą¤ą¤°ą„ą¤"
"ChannelThumbnail.zoomOut","Zoom out","
-- CONTEXT --
-","Zoom out"
+","ą¤ą¤¼ą„ą¤® ą¤ą¤ą¤ ą¤ą¤°ą„ą¤"
"ChannelTokenModal.close","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"ChannelTokenModal.copyTitle","Copy channel token","
-- CONTEXT --
","Copy channel token"
@@ -1238,13 +1235,13 @@
","Clipboard"
"Clipboard.close","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"Clipboard.copiedItemsToClipboard","Copied in clipboard","
-- CONTEXT --
","Copied in clipboard"
"Clipboard.deleteSelectedButton","Delete","
-- CONTEXT --
-","Delete"
+","ą¤¹ą¤ą¤¾ą¤ą¤"
"Clipboard.duplicateSelectedButton","Make a copy","
-- CONTEXT --
","Make a copy"
@@ -1262,54 +1259,57 @@
","Deleted from clipboard"
"Clipboard.selectAll","Select all","
-- CONTEXT --
-","Select all"
+","ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤"
"CommonMetadataStrings.accessibility","Accessibility","
-- CONTEXT --
-Allows the user to filter for all the resources with accessibility features for learners with disabilities.","Accessibility"
+Allows the user to filter for all the resources with accessibility features for learners with disabilities.","ą¤Ŗą¤¹ą„ą¤ą¤ ą¤ą„ą¤·ą¤®ą¤¤ą¤¾"
"CommonMetadataStrings.algebra","Algebra","
-- CONTEXT --
-A type of math category. See https://en.wikipedia.org/wiki/Algebra","Algebra"
+A type of math category. See https://en.wikipedia.org/wiki/Algebra","ą¤¬ą„ą¤ą¤ą¤£ą¤æą¤¤"
"CommonMetadataStrings.all","All","
-- CONTEXT --
-A label for everything in the group of activities.","All"
+A label for everything in the group of activities.","ą¤øą¤ą„"
+"CommonMetadataStrings.allContent","Viewed in its entirety","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners studied it all, for example they saw all pages of a document.","Viewed in its entirety"
"CommonMetadataStrings.allLevelsBasicSkills","All levels -- basic skills","
-- CONTEXT --
-Refers to a type of educational level.","All levels -- basic skills"
+Refers to a type of educational level.","ą¤øą¤ą„ ą¤øą„ą¤¤ą¤° -- ą¤®ą„ą¤²ą¤ą„ą¤¤ ą¤ą„ą¤¶ą¤²"
"CommonMetadataStrings.allLevelsWorkSkills","All levels -- work skills","
-- CONTEXT --
-Refers to a type of educational level.","All levels -- work skills"
+Refers to a type of educational level.","ą¤øą¤ą„ ą¤øą„ą¤¤ą¤° -- ą¤ą¤¾ą¤°ą„ą¤Æ ą¤ą„ą¤¶ą¤²"
"CommonMetadataStrings.altText","Includes alternative text descriptions for images","
-- CONTEXT --
Alternative text, or alt text, is a written substitute for an image. It is used to describe information being provided by an image, graph, or any other visual element on a web page. It provides information about the context and function of an image for people with varying degrees of visual and cognitive impairments. When a screen reader encounters an image, it will read aloud the alternative text.
https://www.med.unc.edu/webguide/accessibility/alt-text/","Includes alternative text descriptions for images"
"CommonMetadataStrings.anthropology","Anthropology","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Anthropology","Anthropology"
+Category type. See https://en.wikipedia.org/wiki/Anthropology","ą¤ą¤ą¤„ą„ą¤°ą„ą¤Ŗą„ą¤²ą„ą¤ą„"
"CommonMetadataStrings.arithmetic","Arithmetic","
-- CONTEXT --
-Math category type. See https://en.wikipedia.org/wiki/Arithmetic","Arithmetic"
+Math category type. See https://en.wikipedia.org/wiki/Arithmetic","ą¤
ą¤ą¤ą¤ą¤£ą¤æą¤¤"
"CommonMetadataStrings.arts","Arts","
-- CONTEXT --
-Refers to a category group type. See https://en.wikipedia.org/wiki/The_arts","Arts"
+Refers to a category group type. See https://en.wikipedia.org/wiki/The_arts","ą¤ą¤²ą¤¾"
"CommonMetadataStrings.astronomy","Astronomy","
-- CONTEXT --
-Science category type. See https://en.wikipedia.org/wiki/Astronomy","Astronomy"
+Science category type. See https://en.wikipedia.org/wiki/Astronomy","ą¤ą¤ą„ą¤² ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø"
"CommonMetadataStrings.audioDescription","Includes audio descriptions","
-- CONTEXT --
Content has narration used to provide information surrounding key visual elements for the benefit of blind and visually impaired users.
https://en.wikipedia.org/wiki/Audio_description","Includes audio descriptions"
"CommonMetadataStrings.basicSkills","Basic skills","
-- CONTEXT --
-Category type. Basic skills refer to learning resources focused on aspects like literacy, numeracy and digital literacy.","Basic skills"
+Category type. Basic skills refer to learning resources focused on aspects like literacy, numeracy and digital literacy.","ą¤®ą„ą¤²ą¤ą„ą¤¤ ą¤ą„ą¤¶ą¤²"
"CommonMetadataStrings.biology","Biology","
-- CONTEXT --
-Science category type. See https://en.wikipedia.org/wiki/Biology","Biology"
+Science category type. See https://en.wikipedia.org/wiki/Biology","ą¤ą„ą¤µ ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø"
"CommonMetadataStrings.browseChannel","Browse channel","
-- CONTEXT --
-Heading on page where a user can browse the content within a channel","Browse channel"
+Heading on page where a user can browse the content within a channel","ą¤ą„ą¤Øą¤² ą¤¬ą„ą¤°ą¤¾ą¤ą¤ą¤¼ ą¤ą¤°ą„ą¤"
"CommonMetadataStrings.calculus","Calculus","
-- CONTEXT --
-Math category type. https://en.wikipedia.org/wiki/Calculus","Calculus"
+Math category type. https://en.wikipedia.org/wiki/Calculus","ą¤ą„ą¤²ą„ą¤ą„ą¤²ą¤ø"
"CommonMetadataStrings.captionsSubtitles","Includes captions or subtitles","
-- CONTEXT --
Accessibility filter to search for video and audio resources that have text captions for users who are deaf or hard of hearing.
@@ -1319,130 +1319,145 @@ https://www.w3.org/WAI/media/av/captions/","Includes captions or subtitles"
A title for the metadata that explains the subject matter of an activity","Category"
"CommonMetadataStrings.chemistry","Chemistry","
-- CONTEXT --
-Science category type. See https://en.wikipedia.org/wiki/Chemistry","Chemistry"
+Science category type. See https://en.wikipedia.org/wiki/Chemistry","ą¤°ą¤øą¤¾ą¤Æą¤Ø ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø"
"CommonMetadataStrings.civicEducation","Civic education","
-- CONTEXT --
-Category type. Civic education is the study of the rights and obligations of citizens in society. See https://en.wikipedia.org/wiki/Civics","Civic education"
+Category type. Civic education is the study of the rights and obligations of citizens in society. See https://en.wikipedia.org/wiki/Civics","ą¤Øą¤¾ą¤ą¤°ą¤æą¤ ą¤¶ą¤¾ą¤øą„ą¤¤ą„ą¤°"
+"CommonMetadataStrings.completeDuration","When time spent is equal to duration","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners spent given time studying it.","When time spent is equal to duration"
"CommonMetadataStrings.completion","Completion","CommonMetadataStrings.completion
-- CONTEXT --
A title for the metadata that explains when an activity is considered completed","Completion"
"CommonMetadataStrings.computerScience","Computer science","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Computer_science","Computer science"
+Category type. See https://en.wikipedia.org/wiki/Computer_science","ą¤ą¤®ą„ą¤Ŗą„ą¤Æą„ą¤ą¤° ą¤øą¤¾ą¤ą¤ą¤ø"
"CommonMetadataStrings.create","Create","
-- CONTEXT --
-Resource and filter label for the type of learning activity. Translate as a VERB","Create"
+Resource and filter label for the type of learning activity. Translate as a VERB","ą¤¬ą¤Øą¤¾ą¤ą¤"
"CommonMetadataStrings.currentEvents","Current events","
-- CONTEXT --
-Category type. Could also be translated as 'News'. See https://en.wikipedia.org/wiki/News","Current events"
+Category type. Could also be translated as 'News'. See https://en.wikipedia.org/wiki/News","ą¤µą¤°ą„ą¤¤ą¤®ą¤¾ą¤Ø ą¤ą¤ą¤Øą¤¾ą¤ą¤"
"CommonMetadataStrings.dailyLife","Daily life","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Everyday_life","Daily life"
+Category type. See https://en.wikipedia.org/wiki/Everyday_life","ą¤¦ą„ą¤Øą¤æą¤ ą¤ą„ą¤µą¤Ø"
"CommonMetadataStrings.dance","Dance","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Dance","Dance"
+Category type. See https://en.wikipedia.org/wiki/Dance","ą¤Øą„ą¤¤ą„ą¤Æ"
+"CommonMetadataStrings.determinedByResource","Determined by the resource","
+-- CONTEXT --
+One of the completion criteria types. Typically used for embedded html5/h5p resources that contain their own completion criteria, for example reaching a score in an educational game.","Determined by the resource"
"CommonMetadataStrings.digitalLiteracy","Digital literacy","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Digital_literacy","Digital literacy"
+Category type. See https://en.wikipedia.org/wiki/Digital_literacy","ą¤”ą¤æą¤ą¤æą¤ą¤² ą¤øą¤¾ą¤ą„ą¤·ą¤°ą¤¤ą¤¾"
"CommonMetadataStrings.diversity","Diversity","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Diversity_(politics)","Diversity"
+Category type. See https://en.wikipedia.org/wiki/Diversity_(politics)","ą¤µą¤æą¤µą¤æą¤§ą¤¤ą¤¾"
"CommonMetadataStrings.drama","Drama","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Drama","Drama"
+Category type. See https://en.wikipedia.org/wiki/Drama","ą¤Øą¤¾ą¤ą¤"
"CommonMetadataStrings.duration","Duration","
-- CONTEXT --
A title for the metadata that explains how long an activity will take","Duration"
"CommonMetadataStrings.earthScience","Earth science","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Earth_science","Earth science"
+Category type. See https://en.wikipedia.org/wiki/Earth_science","ą¤Ŗą„ą¤„ą„ą¤µą„ ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø"
"CommonMetadataStrings.entrepreneurship","Entrepreneurship","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Entrepreneurship","Entrepreneurship"
+Category type. See https://en.wikipedia.org/wiki/Entrepreneurship","ą¤ą¤¦ą„ą¤Æą¤®ą¤æą¤¤ą¤¾"
"CommonMetadataStrings.environment","Environment","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Environmental_studies","Environment"
+Category type. See https://en.wikipedia.org/wiki/Environmental_studies","ą¤Ŗą¤°ą„ą¤Æą¤¾ą¤µą¤°ą¤£"
+"CommonMetadataStrings.exactTime","Time to complete","
+-- CONTEXT --
+One of the completion criteria types. A subset of ""When time spent is equal to duration"". For example, for an audio resource with this criteria, learnes need to hear the whole length of audio for the resource to be considered complete.","Time to complete"
"CommonMetadataStrings.explore","Explore","
-- CONTEXT --
-Resource and filter label for the type of learning activity. Translate as a VERB","Explore"
+Resource and filter label for the type of learning activity. Translate as a VERB","ą¤
ą¤Øą„ą¤µą„ą¤·ą¤£ ą¤ą¤°ą„ą¤"
"CommonMetadataStrings.financialLiteracy","Financial literacy","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Financial_literacy","Financial literacy"
+Category type. See https://en.wikipedia.org/wiki/Financial_literacy","ą¤µą¤æą¤¤ą„ą¤¤ą„ą¤Æ ą¤øą¤¾ą¤ą„ą¤·ą¤°ą¤¤ą¤¾"
"CommonMetadataStrings.forBeginners","For beginners","
-- CONTEXT --
-Filter option and a label for the resources in the Kolibri Library.","For beginners"
+Filter option and a label for the resources in the Kolibri Library.","ą¤Øą„ą¤øą¤æą¤ą¤æą¤Æą„ą¤ ą¤ą„ ą¤²ą¤æą¤"
"CommonMetadataStrings.forTeachers","For teachers","
-- CONTEXT --
-Category type","For teachers"
+Category type","ą¤¶ą¤æą¤ą„ą¤·ą¤ą„ą¤ ą¤ą„ ą¤²ą¤æą¤"
"CommonMetadataStrings.geometry","Geometry","
-- CONTEXT --
-Category type.","Geometry"
+Category type.","ą¤°ą„ą¤ą¤¾ą¤ą¤£ą¤æą¤¤"
+"CommonMetadataStrings.goal","When goal is met","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners reached a given goal, for example 100% correct.","When goal is met"
"CommonMetadataStrings.guides","Guides","
-- CONTEXT --
-Category label in the Kolibri resources library; refers to any guide-type material for teacher professional development.","Guides"
+Category label in the Kolibri resources library; refers to any guide-type material for teacher professional development.","ą¤ą¤¾ą¤ą¤”ą„ą¤ø"
"CommonMetadataStrings.highContrast","Includes high contrast text for learners with low vision","
-- CONTEXT --
Accessibility filter used to search for resources that have high contrast color themes for users with low vision ('display' refers to digital content, not the hardware like screens or monitors).
https://veroniiiica.com/2019/10/25/high-contrast-color-schemes-low-vision/","Includes high contrast text for learners with low vision"
"CommonMetadataStrings.history","History","
-- CONTEXT --
-Category type.","History"
+Category type.","ą¤ą¤¤ą¤æą¤¹ą¤¾ą¤ø"
"CommonMetadataStrings.industryAndSectorSpecific","Industry and sector specific","
-- CONTEXT --
-Subcategory type for technical and vocational training.","Industry and sector specific"
+Subcategory type for technical and vocational training.","ą¤ą¤¦ą„ą¤Æą„ą¤ ą¤ą¤° ą¤ą„ą¤·ą„ą¤¤ą„ą¤° ą¤µą¤æą¤¶ą¤æą¤·ą„ą¤"
"CommonMetadataStrings.languageLearning","Language learning","
-- CONTEXT --
-Category type.","Language learning"
+Category type.","ą¤ą¤¾ą¤·ą¤¾ ą¤øą„ą¤ą¤Øą¤¾"
"CommonMetadataStrings.learningActivity","Learning Activity","
-- CONTEXT --
A title for the category of education material interaction, i.e. watch, read, listen","Learning Activity"
"CommonMetadataStrings.learningSkills","Learning skills","
-- CONTEXT --
A category label and type of basic skill.
-https://en.wikipedia.org/wiki/Study_skills","Learning skills"
+https://en.wikipedia.org/wiki/Study_skills","ą¤øą„ą¤ą¤Øą„ ą¤ą„ ą¤ą„ą¤¶ą¤²"
"CommonMetadataStrings.lessonPlans","Lesson plans","
-- CONTEXT --
-Category label in the Kolibri resources library; refers to lesson planning materials for teachers.","Lesson plans"
+Category label in the Kolibri resources library; refers to lesson planning materials for teachers.","ą¤Ŗą¤¾ą¤ ą¤Æą„ą¤ą¤Øą¤¾ą¤ą¤"
"CommonMetadataStrings.level","Level","
-- CONTEXT --
-Refers to the educational learning level, such a preschool, primary, secondary, etc.","Level"
+Refers to the educational learning level, such a preschool, primary, secondary, etc.","ą¤øą„ą¤¤ą¤°"
"CommonMetadataStrings.listen","Listen","
-- CONTEXT --
-Resource and filter label for the type of learning activity with audio. Translate as a VERB","Listen"
+Resource and filter label for the type of learning activity with audio. Translate as a VERB","ą¤øą„ą¤Øą„ą¤"
"CommonMetadataStrings.literacy","Literacy","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Literacy","Literacy"
+Category type. See https://en.wikipedia.org/wiki/Literacy","ą¤øą¤¾ą¤ą„ą¤·ą¤°ą¤¤ą¤¾"
"CommonMetadataStrings.literature","Literature","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Literature","Literature"
+Category type. See https://en.wikipedia.org/wiki/Literature","ą¤øą¤¾ą¤¹ą¤æą¤¤ą„ą¤Æ"
"CommonMetadataStrings.logicAndCriticalThinking","Logic and critical thinking","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Critical_thinking","Logic and critical thinking"
+Category type. See https://en.wikipedia.org/wiki/Critical_thinking","ą¤¤ą¤°ą„ą¤ ą¤ą¤° ą¤ą¤²ą„ą¤ą¤Øą¤¾ą¤¤ą„ą¤®ą¤ ą¤øą„ą¤"
"CommonMetadataStrings.longActivity","Long activity","
-- CONTEXT --
-Label with time estimation for learning activities that take more than 30 minutes.","Long activity"
+Label with time estimation for learning activities that take more than 30 minutes.","ą¤²ą¤ą¤¬ą„ ą¤ą¤¤ą¤æą¤µą¤æą¤§ą¤æ"
"CommonMetadataStrings.lowerPrimary","Lower primary","
-- CONTEXT --
-Refers to a level of learning. Approximately corresponds to the first half of primary school.","Lower primary"
+Refers to a level of learning. Approximately corresponds to the first half of primary school.","ą¤Ŗą„ą¤°ą„ą¤µ ą¤Ŗą„ą¤°ą¤¾ą¤„ą¤®ą¤æą¤"
"CommonMetadataStrings.lowerSecondary","Lower secondary","
-- CONTEXT --
-Refers to a level of learning. Approximately corresponds to the first half of secondary school (high school).","Lower secondary"
+Refers to a level of learning. Approximately corresponds to the first half of secondary school (high school).","ą¤Ŗą„ą¤°ą„ą¤µ ą¤®ą¤¾ą¤§ą„ą¤Æą¤®ą¤æą¤"
+"CommonMetadataStrings.masteryMofN","Goal: {m} out of {n}","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners answered m questions out of n correctly.","Goal: {m} out of {n}"
"CommonMetadataStrings.mathematics","Mathematics","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Mathematics","Mathematics"
+Category type. See https://en.wikipedia.org/wiki/Mathematics","ą¤ą¤£ą¤æą¤¤"
"CommonMetadataStrings.mechanicalEngineering","Mechanical engineering","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Mechanical_engineering.","Mechanical engineering"
+Category type. See https://en.wikipedia.org/wiki/Mechanical_engineering.","ą¤®ą„ą¤ą„ą¤Øą¤æą¤ą¤² ą¤ą¤ą¤ą„ą¤Øą¤æą¤Æą¤°ą¤æą¤ą¤"
"CommonMetadataStrings.mediaLiteracy","Media literacy","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Media_literacy","Media literacy"
+Category type. See https://en.wikipedia.org/wiki/Media_literacy","ą¤®ą„ą¤”ą¤æą¤Æą¤¾ ą¤øą¤¾ą¤ą„ą¤·ą¤°ą¤¤ą¤¾"
"CommonMetadataStrings.mentalHealth","Mental health","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Mental_health","Mental health"
+Category type. See https://en.wikipedia.org/wiki/Mental_health","ą¤®ą¤¾ą¤Øą¤øą¤æą¤ ą¤øą„ą¤µą¤¾ą¤øą„ą¤„ą„ą¤Æ"
"CommonMetadataStrings.music","Music","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Music","Music"
+Category type. See https://en.wikipedia.org/wiki/Music","ą¤øą¤ą¤ą„ą¤¤"
"CommonMetadataStrings.needsInternet","Internet connection","
-- CONTEXT --
Refers to a filter for resources.","Internet connection"
@@ -1452,57 +1467,63 @@ Refers to a filter for resources.
","Other supplies"
"CommonMetadataStrings.numeracy","Numeracy","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Numeracy","Numeracy"
+Category type. See https://en.wikipedia.org/wiki/Numeracy","ą¤
ą¤ą¤ą„ą¤ ą¤ą¤¾ ą¤ą„ą¤ą¤¾ą¤Ø"
"CommonMetadataStrings.peers","Working with peers","
-- CONTEXT --
Refers to a filter for resources that require a learner to work with other learners to be used.","Working with peers"
"CommonMetadataStrings.physics","Physics","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Physics.","Physics"
+Category type. See https://en.wikipedia.org/wiki/Physics.","ą¤ą„ą¤¤ą¤æą¤ ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø"
"CommonMetadataStrings.politicalScience","Political science","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Political_science.","Political science"
+Category type. See https://en.wikipedia.org/wiki/Political_science.","ą¤°ą¤¾ą¤ą¤Øą„ą¤¤ą¤æą¤¶ą¤¾ą¤øą„ą¤¤ą„ą¤°"
"CommonMetadataStrings.practice","Practice","
-- CONTEXT --
-Resource and filter label for the type of learning activity with questions and answers. Translate as a VERB","Practice"
+Resource and filter label for the type of learning activity with questions and answers. Translate as a VERB","ą¤
ą¤ą„ą¤Æą¤¾ą¤ø"
+"CommonMetadataStrings.practiceQuiz","Practice quiz","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria represents a quiz.","Practice quiz"
"CommonMetadataStrings.preschool","Preschool","
-- CONTEXT --
Refers to a level of education offered to children before they begin compulsory education at primary school.
-See https://en.wikipedia.org/wiki/Preschool","Preschool"
+See https://en.wikipedia.org/wiki/Preschool","ą¤¬ą¤¾ą¤²ą¤µą¤¾ą¤”ą¤¼ą„"
"CommonMetadataStrings.professionalSkills","Professional skills","
-- CONTEXT --
-Category type. Refers to skills that are related to a profession or a job.","Professional skills"
+Category type. Refers to skills that are related to a profession or a job.","ą¤Ŗą„ą¤¶ą„ą¤µą¤° ą¤ą„ą¤¶ą¤²"
"CommonMetadataStrings.programming","Programming","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Computer_programming","Programming"
+Category type. See https://en.wikipedia.org/wiki/Computer_programming","ą¤Ŗą„ą¤°ą„ą¤ą„ą¤°ą¤¾ą¤®ą¤æą¤ą¤"
"CommonMetadataStrings.publicHealth","Public health","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Public_health.","Public health"
+Category type. See https://en.wikipedia.org/wiki/Public_health.","ą¤²ą„ą¤ ą¤øą„ą¤µą¤¾ą¤øą„ą¤„ą„ą¤Æ"
"CommonMetadataStrings.read","Read","
-- CONTEXT --
-Resource and filter label for the type of learning activity with documents. Translate as a VERB","Read"
+Resource and filter label for the type of learning activity with documents. Translate as a VERB","ą¤Ŗą¤¢ą¤¼ą„ą¤"
"CommonMetadataStrings.readReference","Reference","
-- CONTEXT --
-Label displayed for the 'Read' learning activity, used instead of the time duration information, to indicate a resource that may not need sequential reading from the beginning to the end. Similar concept as the 'reference' books in the traditional library, that the user just 'consults', and does not read from cover to cover.","Reference"
+Label displayed for the 'Read' learning activity, used instead of the time duration information, to indicate a resource that may not need sequential reading from the beginning to the end. Similar concept as the 'reference' books in the traditional library, that the user just 'consults', and does not read from cover to cover.","ą¤øą¤ą¤¦ą¤°ą„ą¤"
"CommonMetadataStrings.readingAndWriting","Reading and writing","
-- CONTEXT --
-School subject category","Reading and writing"
+School subject category","ą¤Ŗą¤¢ą¤¼ą¤Øą¤¾ ą¤ą¤° ą¤²ą¤æą¤ą¤Øą¤¾"
"CommonMetadataStrings.readingComprehension","Reading comprehension","
-- CONTEXT --
-Category type.","Reading comprehension"
+Category type.","ą¤Øą¤æą¤¬ą¤ą¤§ ą¤Ŗą¤¢ą¤¼ą¤Øą¤¾"
+"CommonMetadataStrings.reference","Reference material","
+-- CONTEXT --
+One of the completion criteria types. Progress made on a resource with this criteria is not tracked.","Reference material"
"CommonMetadataStrings.reflect","Reflect","
-- CONTEXT --
-Resource and filter label for the type of learning activity. Translate as a VERB","Reflect"
+Resource and filter label for the type of learning activity. Translate as a VERB","ą¤Ŗą„ą¤°ą¤¤ą¤æą¤¬ą¤æą¤ą¤¬ą¤æą¤¤"
"CommonMetadataStrings.school","School","
-- CONTEXT --
-Category type.","School"
+Category type.","ą¤µą¤æą¤¦ą„ą¤Æą¤¾ą¤²ą¤Æ"
"CommonMetadataStrings.sciences","Sciences","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Science","Sciences"
+Category type. See https://en.wikipedia.org/wiki/Science","ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø"
"CommonMetadataStrings.shortActivity","Short activity","
-- CONTEXT --
-Label with time estimation for learning activities that take less than 30 minutes.","Short activity"
+Label with time estimation for learning activities that take less than 30 minutes.","ą¤ą„ą¤ą„ ą¤ą¤¤ą¤æą¤µą¤æą¤§ą¤æ"
"CommonMetadataStrings.signLanguage","Includes sign language captions","
-- CONTEXT --
https://en.wikipedia.org/wiki/Sign_language
@@ -1510,68 +1531,68 @@ https://en.wikipedia.org/wiki/List_of_sign_languages
Wherever communities of deaf people exist, sign languages have developed as useful means of communication, and they form the core of local Deaf cultures. Although signing is used primarily by the deaf and hard of hearing, it is also used by hearing individuals, such as those unable to physically speak, those who have trouble with spoken language due to a disability or condition (augmentative and alternative communication), or those with deaf family members, such as children of deaf adults. ","Includes sign language captions"
"CommonMetadataStrings.skillsTraining","Skills training","
-- CONTEXT --
-Subcategory type for technical and vocational training.","Skills training"
+Subcategory type for technical and vocational training.","ą¤ą„ą¤¶ą¤² ą¤Ŗą„ą¤°ą¤¶ą¤æą¤ą„ą¤·ą¤£"
"CommonMetadataStrings.socialSciences","Social sciences","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Social_science","Social sciences"
+Category type. See https://en.wikipedia.org/wiki/Social_science","ą¤øą¤¾ą¤®ą¤¾ą¤ą¤æą¤ ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø"
"CommonMetadataStrings.sociology","Sociology","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Sociology","Sociology"
+Category type. See https://en.wikipedia.org/wiki/Sociology","ą¤øą¤®ą¤¾ą¤ ą¤¶ą¤¾ą¤øą„ą¤¤ą„ą¤°"
"CommonMetadataStrings.softwareTools","Other software tools","
-- CONTEXT --
Refers to a filter for resources that need additional software to be used.","Other software tools"
"CommonMetadataStrings.softwareToolsAndTraining","Software tools and training","
-- CONTEXT --
-Subcategory type for technical and vocational training.","Software tools and training"
+Subcategory type for technical and vocational training.","ą¤øą„ą¤«ą„ą¤ą¤µą„ą¤Æą¤° ą¤øą¤¾ą¤§ą¤Ø ą¤ą¤° ą¤Ŗą„ą¤°ą¤¶ą¤æą¤ą„ą¤·ą¤£"
"CommonMetadataStrings.specializedProfessionalTraining","Specialized professional training","
-- CONTEXT --
-Level of education that refers to training for a profession (job).","Specialized professional training"
+Level of education that refers to training for a profession (job).","ą¤µą¤æą¤¶ą„ą¤·ą¤ą„ą¤ą¤¤ą¤¾ ą¤Ŗą„ą¤¶ą„ą¤µą¤° ą¤Ŗą„ą¤°ą¤¶ą¤æą¤ą„ą¤·ą¤£"
"CommonMetadataStrings.statistics","Statistics","
-- CONTEXT --
-A math category. See https://en.wikipedia.org/wiki/Statistics","Statistics"
+A math category. See https://en.wikipedia.org/wiki/Statistics","ą¤øą¤¾ą¤ą¤ą„ą¤Æą¤æą¤ą„"
"CommonMetadataStrings.taggedPdf","Tagged PDF","
-- CONTEXT --
A tagged PDF includes hidden accessibility markups (tags) that make the document accessible to those who use screen readers and other assistive technology (AT).
-https://taggedpdf.com/what-is-a-tagged-pdf/","Tagged PDF"
+https://taggedpdf.com/what-is-a-tagged-pdf/","ą¤ą„ą¤ ą¤ą„ ą¤ą¤Æą„ PDF ą¤«ą¤¾ą¤ą¤²"
"CommonMetadataStrings.teacher","Working with a teacher","
-- CONTEXT --
Refers to a filter for resources that require a learner to work with a teacher to be used.","Working with a teacher"
"CommonMetadataStrings.technicalAndVocationalTraining","Technical and vocational training","
-- CONTEXT --
-A level of education. See https://en.wikipedia.org/wiki/TVET_(Technical_and_Vocational_Education_and_Training)","Technical and vocational training"
+A level of education. See https://en.wikipedia.org/wiki/TVET_(Technical_and_Vocational_Education_and_Training)","ą¤¤ą¤ą¤Øą„ą¤ą„ ą¤ą¤° ą¤µą„ą¤Æą¤¾ą¤µą¤øą¤¾ą¤Æą¤æą¤ ą¤Ŗą„ą¤°ą¤¶ą¤æą¤ą„ą¤·ą¤£"
"CommonMetadataStrings.tertiary","Tertiary","
-- CONTEXT --
-A level of education. See https://en.wikipedia.org/wiki/Tertiary_education","Tertiary"
+A level of education. See https://en.wikipedia.org/wiki/Tertiary_education","ą¤¤ą„ą¤¤ą„ą¤Æ"
"CommonMetadataStrings.toUseWithPaperAndPencil","Paper and pencil","
-- CONTEXT --
Refers to a filter for resources.
","Paper and pencil"
"CommonMetadataStrings.topicLabel","Folder","
-- CONTEXT --
-A collection of resources and other subfolders within a channel. Nested folders allow a channel to be organized as a tree or hierarchy.","Folder"
+A collection of resources and other subfolders within a channel. Nested folders allow a channel to be organized as a tree or hierarchy.","ą¤«ą„ą¤²ą„ą¤”ą¤°"
"CommonMetadataStrings.upperPrimary","Upper primary","
-- CONTEXT --
Refers to a level of education. Approximately corresponds to the second half of primary school.
-","Upper primary"
+","ą¤ą¤ą„ą¤ ą¤Ŗą„ą¤°ą¤¾ą¤„ą¤®ą¤æą¤"
"CommonMetadataStrings.upperSecondary","Upper secondary","
-- CONTEXT --
-Refers to a level of education. Approximately corresponds to the second half of secondary school.","Upper secondary"
+Refers to a level of education. Approximately corresponds to the second half of secondary school.","ą¤ą¤ą„ą¤ ą¤®ą¤¾ą¤§ą„ą¤Æą¤®ą¤æą¤"
"CommonMetadataStrings.visualArt","Visual art","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Visual_arts","Visual art"
+Category type. See https://en.wikipedia.org/wiki/Visual_arts","ą¤¦ą„ą¤¶ą„ą¤Æ ą¤ą¤²ą¤¾"
"CommonMetadataStrings.watch","Watch","
-- CONTEXT --
-Resource and filter label for the type of learning activity with video. Translate as a VERB","Watch"
+Resource and filter label for the type of learning activity with video. Translate as a VERB","ą¤¦ą„ą¤ą„ą¤"
"CommonMetadataStrings.webDesign","Web design","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Web_design","Web design"
+Category type. See https://en.wikipedia.org/wiki/Web_design","ą¤µą„ą¤¬ ą¤”ą¤æą¤ą¤¾ą¤ą¤Ø"
"CommonMetadataStrings.work","Work","
-- CONTEXT --
-Top level category group that contains resources for acquisition of professional skills.","Work"
+Top level category group that contains resources for acquisition of professional skills.","ą¤ą¤¾ą¤°ą„ą¤Æ"
"CommonMetadataStrings.writing","Writing","
-- CONTEXT --
-Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
+Category type. See https://en.wikipedia.org/wiki/Writing","ą¤²ą„ą¤ą¤Ø"
"CommunityStandardsModal.communityStandardsHeader","Community Standards","
-- CONTEXT --
","Community Standards"
@@ -1614,27 +1635,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
"CommunityStandardsModal.studioItem5","Hosting. Uploading your own materials (limited to materials you know are appropriately licensed to do so) from a local hard drive or other locations on the internet","
-- CONTEXT --
","Hosting. Uploading your own materials (limited to materials you know are appropriately licensed to do so) from a local hard drive or other locations on the internet"
-"CompletionOptions.allContent","Viewed in its entirety","
--- CONTEXT --
-","Viewed in its entirety"
-"CompletionOptions.completeDuration","When time spent is equal to duration","
--- CONTEXT --
-","When time spent is equal to duration"
-"CompletionOptions.determinedByResource","Determined by the resource","
--- CONTEXT --
-","Determined by the resource"
-"CompletionOptions.exactTime","Time to complete","
--- CONTEXT --
-","Time to complete"
-"CompletionOptions.goal","When goal is met","
--- CONTEXT --
-","When goal is met"
-"CompletionOptions.practiceQuiz","Practice quiz","
--- CONTEXT --
-","Practice quiz"
-"CompletionOptions.reference","Reference material","
+"CompletionOptions.learnersCanMarkComplete","Allow learners to mark as complete","
-- CONTEXT --
-","Reference material"
+","Allow learners to mark as complete"
"CompletionOptions.referenceHint","Progress will not be tracked on reference material unless learners mark it as complete","
-- CONTEXT --
","Progress will not be tracked on reference material unless learners mark it as complete"
@@ -1646,19 +1649,19 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty."
"ConstantStrings.CC BY","CC BY","
-- CONTEXT --
-","CC BY"
+","ą¤Øą¤æą¤®ą„ą¤Ø ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC"
"ConstantStrings.CC BY-NC","CC BY-NC","
-- CONTEXT --
-","CC BY-NC"
+","NC ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC"
"ConstantStrings.CC BY-NC-ND","CC BY-NC-ND","
-- CONTEXT --
-","CC BY-NC-ND"
+","NC-ND ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC"
"ConstantStrings.CC BY-NC-ND_description","The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially.","
-- CONTEXT --
","The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially."
"ConstantStrings.CC BY-NC-SA","CC BY-NC-SA","
-- CONTEXT --
-","CC BY-NC-SA"
+","NC-SA ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC"
"ConstantStrings.CC BY-NC-SA_description","The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms.","
-- CONTEXT --
","The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms."
@@ -1667,13 +1670,13 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms."
"ConstantStrings.CC BY-ND","CC BY-ND","
-- CONTEXT --
-","CC BY-ND"
+","ND ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC"
"ConstantStrings.CC BY-ND_description","The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you.","
-- CONTEXT --
","The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you."
"ConstantStrings.CC BY-SA","CC BY-SA","
-- CONTEXT --
-","CC BY-SA"
+","SA ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC"
"ConstantStrings.CC BY-SA_description","The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to ""copyleft"" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects.","
-- CONTEXT --
","The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to ""copyleft"" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects."
@@ -1694,7 +1697,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails."
"ConstantStrings.audio","Audio","
-- CONTEXT --
-","Audio"
+","ą¤ą¤”ą¤æą¤Æą„"
"ConstantStrings.audio_thumbnail","Thumbnail","
-- CONTEXT --
","Thumbnail"
@@ -1703,7 +1706,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Starred"
"ConstantStrings.coach","Coaches","
-- CONTEXT --
-","Coaches"
+","ą¤ą„ą¤"
"ConstantStrings.do_all","Goal: 100% correct","
-- CONTEXT --
","Goal: 100% correct"
@@ -1712,7 +1715,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Learner must answer all questions in the exercise correctly (not recommended for long exercises)"
"ConstantStrings.document","Document","
-- CONTEXT --
-","Document"
+","ą¤¦ą¤øą„ą¤¤ą¤¾ą¤µą„ą¤ą¤¼"
"ConstantStrings.document_thumbnail","Thumbnail","
-- CONTEXT --
","Thumbnail"
@@ -1724,7 +1727,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","EPub document"
"ConstantStrings.exercise","Exercise","
-- CONTEXT --
-","Exercise"
+","ą¤
ą¤ą„ą¤Æą¤¾ą¤ø"
"ConstantStrings.exercise_thumbnail","Thumbnail","
-- CONTEXT --
","Thumbnail"
@@ -1829,13 +1832,13 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Single choice"
"ConstantStrings.slideshow","Slideshow","
-- CONTEXT --
-","Slideshow"
+","ą¤øą„ą¤²ą¤¾ą¤ą¤”ą¤¶ą„"
"ConstantStrings.svg","SVG image","
-- CONTEXT --
","SVG image"
"ConstantStrings.topic","Folder","
-- CONTEXT --
-","Folder"
+","ą¤«ą„ą¤²ą„ą¤”ą¤°"
"ConstantStrings.topic_thumbnail","Thumbnail","
-- CONTEXT --
","Thumbnail"
@@ -1847,10 +1850,10 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Unknown question type"
"ConstantStrings.video","Video","
-- CONTEXT --
-","Video"
+","ą¤µą„ą¤”ą¤æą¤Æą„"
"ConstantStrings.video_subtitle","Captions","
-- CONTEXT --
-","Captions"
+","ą¤ą„ą¤Ŗą„ą¤¶ą¤Ø"
"ConstantStrings.video_thumbnail","Thumbnail","
-- CONTEXT --
","Thumbnail"
@@ -1871,10 +1874,10 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Aggregator"
"ContentDefaults.author","Author","
-- CONTEXT --
-","Author"
+","ą¤²ą„ą¤ą¤"
"ContentDefaults.copyrightHolder","Copyright holder","
-- CONTEXT --
-","Copyright holder"
+","ą¤ą„ą¤Ŗą„ą¤°ą¤¾ą¤ą¤ ą¤§ą¤¾ą¤°ą¤"
"ContentDefaults.defaultsSubTitle","New resources will be automatically given these values","
-- CONTEXT --
","New resources will be automatically given these values"
@@ -1883,13 +1886,13 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Default copyright settings for new resources (optional)"
"ContentDefaults.documents","Documents","
-- CONTEXT --
-","Documents"
+","ą¤¦ą¤øą„ą¤¤ą¤¾ą¤µą„ą¤ą¤¼"
"ContentDefaults.html5","HTML5 apps","
-- CONTEXT --
","HTML5 apps"
"ContentDefaults.license","License","
-- CONTEXT --
-","License"
+","ą¤²ą¤¾ą¤ą¤øą„ą¤ą¤ø"
"ContentDefaults.licenseDescription","License description","
-- CONTEXT --
","License description"
@@ -1904,7 +1907,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Automatically generate thumbnails for the following resource types"
"ContentDefaults.videos","Videos","
-- CONTEXT --
-","Videos"
+","ą¤µą„ą¤”ą¤æą¤Æą„"
"ContentNodeChangedIcon.containsNew","Contains unpublished resources","
-- CONTEXT --
","Contains unpublished resources"
@@ -1926,42 +1929,60 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
"ContentNodeChangedIcon.isUpdatedTopic","Folder has been updated since last publish","
-- CONTEXT --
","Folder has been updated since last publish"
+"ContentNodeCopyTaskProgress.copyErrorTopic","Some resources failed to copy","
+-- CONTEXT --
+","Some resources failed to copy"
+"ContentNodeEditListItem.copiedSnackbar","Copy operation complete","
+-- CONTEXT --
+","Copy operation complete"
+"ContentNodeEditListItem.creatingCopies","Copying...","
+-- CONTEXT --
+","Copying..."
"ContentNodeEditListItem.optionsTooltip","Options","
-- CONTEXT --
-","Options"
+","ą¤µą¤æą¤ą¤²ą„ą¤Ŗ"
+"ContentNodeEditListItem.removeNode","Remove","
+-- CONTEXT --
+","Remove"
+"ContentNodeEditListItem.retryCopy","Retry","
+-- CONTEXT --
+","Retry"
+"ContentNodeEditListItem.undo","Undo","
+-- CONTEXT --
+","Undo"
"ContentNodeIcon.audio","Audio","
-- CONTEXT --
-","Audio"
+","ą¤ą¤”ą¤æą¤Æą„"
"ContentNodeIcon.document","Document","
-- CONTEXT --
-","Document"
+","ą¤¦ą¤øą„ą¤¤ą¤¾ą¤µą„ą¤ą¤¼"
"ContentNodeIcon.exercise","Exercise","
-- CONTEXT --
-","Exercise"
+","ą¤
ą¤ą„ą¤Æą¤¾ą¤ø"
"ContentNodeIcon.html5","HTML5 App","
-- CONTEXT --
","HTML5 App"
"ContentNodeIcon.slideshow","Slideshow","
-- CONTEXT --
-","Slideshow"
+","ą¤øą„ą¤²ą¤¾ą¤ą¤”ą¤¶ą„"
"ContentNodeIcon.topic","Folder","
-- CONTEXT --
-","Folder"
+","ą¤«ą„ą¤²ą„ą¤”ą¤°"
"ContentNodeIcon.unsupported","Unsupported","
-- CONTEXT --
","Unsupported"
"ContentNodeIcon.video","Video","
-- CONTEXT --
-","Video"
+","ą¤µą„ą¤”ą¤æą¤Æą„"
"ContentNodeLearningActivityIcon.multipleLearningActivities","Multiple learning activities","
-- CONTEXT --
-","Multiple learning activities"
-"ContentNodeLearningActivityIcon.topic","Folder","
--- CONTEXT --
-","Folder"
+","ą¤øą„ą¤ą¤Øą„ ą¤ą„ ą¤
ą¤Øą„ą¤ ą¤ą¤¤ą¤æą¤µą¤æą¤§ą¤æą¤Æą¤¾ą¤"
"ContentNodeListItem.coachTooltip","Resource for coaches","
-- CONTEXT --
","Resource for coaches"
+"ContentNodeListItem.copyingError","Copy failed.","
+-- CONTEXT --
+","Copy failed."
"ContentNodeListItem.copyingTask","Copying","
-- CONTEXT --
","Copying"
@@ -1973,10 +1994,10 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Open folder"
"ContentNodeListItem.questions","{value, number, integer} {value, plural, one {question} other {questions}}","
-- CONTEXT --
-","{value, number, integer} {value, plural, one {question} other {questions}}"
+","{value, number, integer} {value, plural, one {ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø} other {ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø}}"
"ContentNodeListItem.resources","{value, number, integer} {value, plural, one {resource} other {resources}}","
-- CONTEXT --
-","{value, number, integer} {value, plural, one {resource} other {resources}}"
+","{value, number, integer} {value, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø}}"
"ContentNodeOptions.copiedItemsToClipboard","Copied in clipboard","
-- CONTEXT --
","Copied in clipboard"
@@ -1985,16 +2006,16 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Copy operation complete"
"ContentNodeOptions.copiedToClipboardSnackbar","Copied to clipboard","
-- CONTEXT --
-","Copied to clipboard"
+","ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤° ą¤¦ą¤æą¤Æą¤¾ ą¤ą¤Æą¤¾"
"ContentNodeOptions.copyToClipboard","Copy to clipboard","
-- CONTEXT --
-","Copy to clipboard"
+","ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤"
"ContentNodeOptions.creatingCopies","Copying...","
-- CONTEXT --
","Copying..."
"ContentNodeOptions.editDetails","Edit details","
-- CONTEXT --
-","Edit details"
+","ą¤µą¤æą¤µą¤°ą¤£ ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤"
"ContentNodeOptions.editTopicDetails","Edit folder details","
-- CONTEXT --
","Edit folder details"
@@ -2013,9 +2034,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
"ContentNodeOptions.newSubtopic","New folder","
-- CONTEXT --
","New folder"
-"ContentNodeOptions.remove","Remove","
+"ContentNodeOptions.remove","Delete","
-- CONTEXT --
-","Remove"
+","Delete"
"ContentNodeOptions.removedFromClipboard","Deleted from clipboard","
-- CONTEXT --
","Deleted from clipboard"
@@ -2024,7 +2045,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Sent to trash"
"ContentNodeOptions.undo","Undo","
-- CONTEXT --
-","Undo"
+","ą¤
ą¤ą„ą¤¤ ą¤ą¤°ą„ą¤"
"ContentNodeOptions.viewDetails","View details","
-- CONTEXT --
","View details"
@@ -2033,7 +2054,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Untitled"
"ContentNodeThumbnail.cancel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"ContentNodeThumbnail.crop","Crop","
-- CONTEXT --
","Crop"
@@ -2054,13 +2075,13 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","No thumbnail"
"ContentNodeThumbnail.remove","Remove","
-- CONTEXT --
-","Remove"
+","ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤"
"ContentNodeThumbnail.retryUpload","Retry upload","
-- CONTEXT --
","Retry upload"
"ContentNodeThumbnail.save","Save","
-- CONTEXT --
-","Save"
+","ą¤øą„ą¤µ ą¤ą¤°ą„ą¤"
"ContentNodeThumbnail.upload","Upload image","
-- CONTEXT --
","Upload image"
@@ -2072,10 +2093,10 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Uploading"
"ContentNodeThumbnail.zoomIn","Zoom in","
-- CONTEXT --
-","Zoom in"
+","ą¤ą¤¼ą„ą¤® ą¤ą¤Ø ą¤ą¤°ą„ą¤"
"ContentNodeThumbnail.zoomOut","Zoom out","
-- CONTEXT --
-","Zoom out"
+","ą¤ą¤¼ą„ą¤® ą¤ą¤ą¤ ą¤ą¤°ą„ą¤"
"ContentNodeValidator.allIncompleteDescendantsText","{count, plural, one {{count, number, integer} resource is incomplete and cannot be published} other {All {count, number, integer} resources are incomplete and cannot be published}}","
-- CONTEXT --
","{count, plural, one {{count, number, integer} resource is incomplete and cannot be published} other {All {count, number, integer} resources are incomplete and cannot be published}}"
@@ -2096,13 +2117,13 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Preview unavailable"
"ContentTreeList.allChannelsLabel","Channels","
-- CONTEXT --
-","Channels"
+","ą¤ą„ą¤Øą¤²"
"ContentTreeList.noResourcesOrTopics","There are no resources or folders here","
-- CONTEXT --
","There are no resources or folders here"
"ContentTreeList.selectAllAction","Select all","
-- CONTEXT --
-","Select all"
+","ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤"
"CopyToken.copiedTokenId","Token copied","
-- CONTEXT --
","Token copied"
@@ -2121,15 +2142,15 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
"CountryField.noCountriesFound","No countries found","
-- CONTEXT --
","No countries found"
-"Create.ToSCheck","I have read and agree to the terms of service","
+"Create.ToSRequiredMessage","Please accept our terms of service and policy","
-- CONTEXT --
-","I have read and agree to the terms of service"
-"Create.ToSRequiredMessage","Please accept our terms of service","
+","Please accept our terms of service and policy"
+"Create.agreement","I have read and agree to terms of service and the privacy policy","
-- CONTEXT --
-","Please accept our terms of service"
+","I have read and agree to terms of service and the privacy policy"
"Create.backToLoginButton","Sign in","
-- CONTEXT --
-","Sign in"
+","ą¤øą¤¾ą¤ą¤Ø ą¤ą¤Ø ą¤ą¤°ą„ą¤"
"Create.basicInformationHeader","Basic information","
-- CONTEXT --
","Basic information"
@@ -2150,7 +2171,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Conversation with Learning Equality"
"Create.createAnAccountTitle","Create an account","
-- CONTEXT --
-","Create an account"
+","ą¤
ą¤ą¤¾ą¤ą¤ą¤ ą¤¬ą¤Øą¤¾ą¤ą¤"
"Create.creatingExercisesUsageOption","Creating exercises","
-- CONTEXT --
","Creating exercises"
@@ -2168,7 +2189,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Finding and adding additional content sources"
"Create.finishButton","Finish","
-- CONTEXT --
-","Finish"
+","ą¤øą¤®ą¤¾ą¤Ŗą„ą¤¤"
"Create.firstNameLabel","First name","
-- CONTEXT --
","First name"
@@ -2210,19 +2231,13 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Please describe"
"Create.passwordLabel","Password","
-- CONTEXT --
-","Password"
+","ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤”"
"Create.passwordMatchMessage","Passwords don't match","
-- CONTEXT --
","Passwords don't match"
"Create.personalDemoSourceOption","Personal demo","
-- CONTEXT --
","Personal demo"
-"Create.privacyPolicyCheck","I have read and agree to the privacy policy","
--- CONTEXT --
-","I have read and agree to the privacy policy"
-"Create.privacyPolicyRequiredMessage","Please accept our privacy policy","
--- CONTEXT --
-","Please accept our privacy policy"
"Create.registrationFailed","There was an error registering your account. Please try again","
-- CONTEXT --
","There was an error registering your account. Please try again"
@@ -2259,12 +2274,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
"Create.usageLabel","How do you plan on using Kolibri Studio (check all that apply)","
-- CONTEXT --
","How do you plan on using Kolibri Studio (check all that apply)"
-"Create.viewPrivacyPolicyLink","View privacy policy","
+"Create.viewPrivacyPolicyLink","View Privacy Policy","
-- CONTEXT --
-","View privacy policy"
-"Create.viewToSLink","View terms of service","
+","View Privacy Policy"
+"Create.viewToSLink","View Terms of Service","
-- CONTEXT --
-","View terms of service"
+","View Terms of Service"
"Create.websiteSourceOption","Learning Equality website","
-- CONTEXT --
","Learning Equality website"
@@ -2279,7 +2294,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Default view"
"CurrentTopicView.addButton","Add","
-- CONTEXT --
-","Add"
+","ą¤ą„ą¤”ą¤¼ą„ą¤"
"CurrentTopicView.addExercise","New exercise","
-- CONTEXT --
","New exercise"
@@ -2291,28 +2306,28 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Copy operation complete"
"CurrentTopicView.copiedItemsToClipboard","Copied to clipboard","
-- CONTEXT --
-","Copied to clipboard"
+","ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤° ą¤¦ą¤æą¤Æą¤¾ ą¤ą¤Æą¤¾"
"CurrentTopicView.copySelectedButton","Copy to clipboard","
-- CONTEXT --
-","Copy to clipboard"
+","ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤"
"CurrentTopicView.copyToClipboardButton","Copy to clipboard","
-- CONTEXT --
-","Copy to clipboard"
+","ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤"
"CurrentTopicView.creatingCopies","Copying...","
-- CONTEXT --
","Copying..."
"CurrentTopicView.deleteSelectedButton","Delete","
-- CONTEXT --
-","Delete"
+","ą¤¹ą¤ą¤¾ą¤ą¤"
"CurrentTopicView.duplicateSelectedButton","Make a copy","
-- CONTEXT --
","Make a copy"
"CurrentTopicView.editButton","Edit","
-- CONTEXT --
-","Edit"
+","ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)"
"CurrentTopicView.editSelectedButton","Edit","
-- CONTEXT --
-","Edit"
+","ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)"
"CurrentTopicView.importFromChannels","Import from channels","
-- CONTEXT --
","Import from channels"
@@ -2321,13 +2336,13 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Move"
"CurrentTopicView.optionsButton","Options","
-- CONTEXT --
-","Options"
+","ą¤µą¤æą¤ą¤²ą„ą¤Ŗ"
"CurrentTopicView.removedItems","Sent to trash","
-- CONTEXT --
","Sent to trash"
"CurrentTopicView.selectAllLabel","Select all","
-- CONTEXT --
-","Select all"
+","ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤"
"CurrentTopicView.selectionCount","{topicCount, plural,
=1 {# folder}
other {# folders}}, {resourceCount, plural,
@@ -2341,16 +2356,16 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
other {# resources}}"
"CurrentTopicView.undo","Undo","
-- CONTEXT --
-","Undo"
+","ą¤
ą¤ą„ą¤¤ ą¤ą¤°ą„ą¤"
"CurrentTopicView.uploadFiles","Upload files","
-- CONTEXT --
","Upload files"
"CurrentTopicView.viewModeTooltip","View","
-- CONTEXT --
-","View"
+","ą¤¦ą„ą¤ą„ą¤"
"DeleteAccountForm.cancelButton","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"DeleteAccountForm.deleteAccountConfirmationPrompt","Are you sure you want to permanently delete your account? This cannot be undone","
-- CONTEXT --
","Are you sure you want to permanently delete your account? This cannot be undone"
@@ -2405,6 +2420,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
"Details.authorsLabel","Authors","
-- CONTEXT --
","Authors"
+"Details.categoriesHeading","Categories","
+-- CONTEXT --
+","Categories"
"Details.coachDescription","Resources for coaches are only visible to coaches in Kolibri","
-- CONTEXT --
","Resources for coaches are only visible to coaches in Kolibri"
@@ -2428,7 +2446,10 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Published version"
"Details.languagesHeading","Languages","
-- CONTEXT --
-","Languages"
+","ą¤ą¤¾ą¤·ą¤¾ą¤ą¤"
+"Details.levelsHeading","Levels","
+-- CONTEXT --
+","Levels"
"Details.licensesLabel","Licenses","
-- CONTEXT --
","Licenses"
@@ -2467,7 +2488,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Common tags"
"Details.tokenHeading","Channel token","
-- CONTEXT --
-","Channel token"
+","ą¤ą„ą¤Øą¤² ą¤ą„ą¤ą¤Ø"
"Details.unpublishedText","Unpublished","
-- CONTEXT --
","Unpublished"
@@ -2485,7 +2506,7 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
","Audience"
"DetailsTabView.authorLabel","Author","
-- CONTEXT --
-","Author"
+","ą¤²ą„ą¤ą¤"
"DetailsTabView.authorToolTip","Person or organization who created this content","
-- CONTEXT --
","Person or organization who created this content"
@@ -2499,10 +2520,10 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Writing"
Heading for the section in the resource editing window","Completion"
"DetailsTabView.copyrightHolderLabel","Copyright holder","
-- CONTEXT --
-","Copyright holder"
+","ą¤ą„ą¤Ŗą„ą¤°ą¤¾ą¤ą¤ ą¤§ą¤¾ą¤°ą¤"
"DetailsTabView.descriptionLabel","Description","
-- CONTEXT --
-","Description"
+","ą¤µą¤æą¤µą¤°ą¤£"
"DetailsTabView.detectedImportText","{count, plural,
=1 {# resource has view-only permission}
other {# resources have view-only permission}}","
@@ -2519,9 +2540,6 @@ Heading for the section in the resource editing window","Completion"
"DetailsTabView.languageHelpText","Leave blank to use the folder language","
-- CONTEXT --
","Leave blank to use the folder language"
-"DetailsTabView.learnersCanMarkComplete","Allow learners to mark as complete","
--- CONTEXT --
-","Allow learners to mark as complete"
"DetailsTabView.noTagsFoundText","No results found for ""{text}"". Press 'Enter' key to create a new tag","
-- CONTEXT --
","No results found for ""{text}"". Press 'Enter' key to create a new tag"
@@ -2545,7 +2563,7 @@ Heading for the section in the resource editing window","Completion"
","Thumbnail"
"DetailsTabView.titleLabel","Title","
-- CONTEXT --
-","Title"
+","ą¤¶ą„ą¤°ą„ą¤·ą¤"
"Diff.negativeSign","-","
-- CONTEXT --
","-"
@@ -2563,19 +2581,19 @@ Heading for the section in the resource editing window","Completion"
","Staged"
"DiffTable.headerType","Type","
-- CONTEXT --
-","Type"
+","ą¤Ŗą„ą¤°ą¤ą¤¾ą¤°"
"DiffTable.typeAudios","Audios","
-- CONTEXT --
","Audios"
"DiffTable.typeDocuments","Documents","
-- CONTEXT --
-","Documents"
+","ą¤¦ą¤øą„ą¤¤ą¤¾ą¤µą„ą¤ą¤¼"
"DiffTable.typeExercises","Exercises","
-- CONTEXT --
-","Exercises"
+","ą¤
ą¤ą„ą¤Æą¤¾ą¤ø"
"DiffTable.typeFileSize","File size","
-- CONTEXT --
-","File size"
+","ą¤«ą¤¼ą¤¾ą¤ą¤² ą¤ą¤¾ ą¤øą¤¾ą¤ą¤ą¤¼"
"DiffTable.typeHtml5Apps","HTML5 apps","
-- CONTEXT --
","HTML5 apps"
@@ -2584,16 +2602,16 @@ Heading for the section in the resource editing window","Completion"
","Slideshows"
"DiffTable.typeTopics","Folders","
-- CONTEXT --
-","Folders"
+","ą¤«ą¤¼ą„ą¤²ą„ą¤”ą¤°ą„ą¤ø"
"DiffTable.typeVersion","API version","
-- CONTEXT --
","API version"
"DiffTable.typeVideos","Videos","
-- CONTEXT --
-","Videos"
+","ą¤µą„ą¤”ą¤æą¤Æą„"
"EditList.selectAllLabel","Select all","
-- CONTEXT --
-","Select all"
+","ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤"
"EditListItem.questionCount","{count, plural,
=1 {# question}
other {# questions}}","
@@ -2618,16 +2636,16 @@ Heading for the section in the resource editing window","Completion"
","New exercise"
"EditModal.dismissDialogButton","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"EditModal.editFilesHeader","Edit files","
-- CONTEXT --
","Edit files"
"EditModal.editingDetailsHeader","Edit details","
-- CONTEXT --
-","Edit details"
+","ą¤µą¤æą¤µą¤°ą¤£ ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤"
"EditModal.finishButton","Finish","
-- CONTEXT --
-","Finish"
+","ą¤øą¤®ą¤¾ą¤Ŗą„ą¤¤"
"EditModal.invalidNodesFound","{count, plural,
=1 {# incomplete resource found}
other {# incomplete resources found}}","
@@ -2670,10 +2688,10 @@ Heading for the section in the resource editing window","Completion"
","Uploads that are in progress will be lost if you exit"
"EditSearchModal.cancelAction","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"EditSearchModal.changesSavedSnackbar","Changes saved","
-- CONTEXT --
-","Changes saved"
+","ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Ø ą¤øą¤¹ą„ą¤ą„ ą¤ą¤"
"EditSearchModal.editSavedSearchTitle","Edit search title","
-- CONTEXT --
","Edit search title"
@@ -2682,13 +2700,13 @@ Heading for the section in the resource editing window","Completion"
","Field is required"
"EditSearchModal.saveChangesAction","Save","
-- CONTEXT --
-","Save"
+","ą¤øą„ą¤µ ą¤ą¤°ą„ą¤"
"EditSearchModal.searchTitleLabel","Search title","
-- CONTEXT --
","Search title"
"EditView.details","Details","
-- CONTEXT --
-","Details"
+","ą¤µą¤æą¤µą¤°ą¤£"
"EditView.editingMultipleCount","Editing details for {topicCount, plural,
=1 {# folder}
other {# folders}}, {resourceCount, plural,
@@ -2711,13 +2729,13 @@ Heading for the section in the resource editing window","Completion"
","Please select resources or folders to edit"
"EditView.preview","Preview","
-- CONTEXT --
-","Preview"
+","ą¤Ŗą„ą¤°ą„ą¤µą¤¾ą¤µą¤²ą„ą¤ą¤Ø"
"EditView.questions","Questions","
-- CONTEXT --
-","Questions"
+","ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø"
"EditView.related","Related","
-- CONTEXT --
-","Related"
+","ą¤øą¤ą¤¬ą¤ą¤§ą¤æą¤¤"
"EmailField.emailLabel","Email","
-- CONTEXT --
","Email"
@@ -2729,13 +2747,13 @@ Heading for the section in the resource editing window","Completion"
","Please enter a valid email"
"ExpandableList.less","Show less","
-- CONTEXT --
-","Show less"
+","ą¤ą¤® ą¤¦ą¤æą¤ą¤¾ą¤ą¤"
"ExpandableList.more","Show more ({more})","
-- CONTEXT --
","Show more ({more})"
"FilePreview.exitFullscreen","Exit fullscreen","
-- CONTEXT --
-","Exit fullscreen"
+","ą¤«ą¤¼ą„ą¤² ą¤øą„ą¤ą„ą¤°ą„ą¤Ø ą¤øą„ ą¤¬ą¤¾ą¤¹ą¤° ą¤ą¤¾ą¤ą¤"
"FilePreview.fullscreenModeText","Fullscreen mode","
-- CONTEXT --
","Fullscreen mode"
@@ -2786,7 +2804,7 @@ Heading for the section in the resource editing window","Completion"
","Upload to '{title}'"
"FileUploadItem.removeFileButton","Remove","
-- CONTEXT --
-","Remove"
+","ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤"
"FileUploadItem.retryUpload","Retry upload","
-- CONTEXT --
","Retry upload"
@@ -2819,10 +2837,10 @@ Heading for the section in the resource editing window","Completion"
","Special characters"
"FullNameForm.cancelAction","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"FullNameForm.changesSavedMessage","Changes saved","
-- CONTEXT --
-","Changes saved"
+","ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Ø ą¤øą¤¹ą„ą¤ą„ ą¤ą¤"
"FullNameForm.editNameHeader","Edit full name","
-- CONTEXT --
","Edit full name"
@@ -2840,25 +2858,25 @@ Heading for the section in the resource editing window","Completion"
","Last name"
"FullNameForm.saveChangesAction","Save changes","
-- CONTEXT --
-","Save changes"
+","ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Øą„ą¤ ą¤ą„ ą¤øą„ą¤µ ą¤ą¤°ą„ą¤"
"GenericError.backToHomeAction","Back to home","
-- CONTEXT --
-","Back to home"
+","ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤æą¤ø ą¤ą¤¾ą¤ą¤"
"GenericError.genericErrorDetails","Try refreshing this page or going back to the home page","
-- CONTEXT --
-","Try refreshing this page or going back to the home page"
+","ą¤ą¤ø ą¤Ŗą„ą¤ ą¤ą„ ą¤°ą„ą¤«ą„ą¤°ą„ą¤¶ ą¤ą¤°ą¤Øą„ ą¤Æą¤¾ ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤ø ą¤ą¤¾ą¤Øą„ ą¤ą„ ą¤ą„ą¤¶ą¤æą¤¶ ą¤ą¤°ą„ą¤"
"GenericError.genericErrorHeader","Sorry, something went wrong","
-- CONTEXT --
","Sorry, something went wrong"
"GenericError.helpByReportingAction","Help us by reporting this error","
-- CONTEXT --
-","Help us by reporting this error"
+","ą¤ą¤ø ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤ą„ ą¤°ą¤æą¤Ŗą„ą¤°ą„ą¤ ą¤ą¤°ą¤ą„ ą¤¹ą¤®ą¤¾ą¤°ą„ ą¤®ą¤¦ą¤¦ ą¤ą¤°ą„ą¤"
"GenericError.refreshAction","Refresh","
-- CONTEXT --
-","Refresh"
+","ą¤°ą„ą¤«ą¤¼ą„ą¤°ą„ą¤¶ ą¤ą¤°ą„ą¤"
"HintsEditor.hintsLabel","Hints","
-- CONTEXT --
-","Hints"
+","ą¤øą¤ą¤ą„ą¤¤"
"HintsEditor.newHintBtnLabel","New hint","
-- CONTEXT --
","New hint"
@@ -2879,7 +2897,7 @@ Heading for the section in the resource editing window","Completion"
","Image description"
"ImagesMenu.btnLabelCancel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"ImagesMenu.btnLabelInsert","Insert","
-- CONTEXT --
","Insert"
@@ -2900,25 +2918,25 @@ Heading for the section in the resource editing window","Completion"
","Select file"
"ImportFromChannelsModal.addButton","Add","
-- CONTEXT --
-","Add"
+","ą¤ą„ą¤”ą¤¼ą„ą¤"
"ImportFromChannelsModal.addedText","Added","
-- CONTEXT --
-","Added"
+","ą¤ą„ą¤”ą¤¼ą¤¾ ą¤ą¤Æą¤¾"
"ImportFromChannelsModal.importAction","Import","
-- CONTEXT --
-","Import"
+","ą¤ą¤Æą¤¾ą¤¤ ą¤ą¤°ą„ą¤"
"ImportFromChannelsModal.importTitle","Import from other channels","
-- CONTEXT --
","Import from other channels"
"ImportFromChannelsModal.removeButton","Remove","
-- CONTEXT --
-","Remove"
+","ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤"
"ImportFromChannelsModal.resourcesAddedSnackbar","{count, number} {count, plural, one {resource selected} other {resources selected}}","
-- CONTEXT --
","{count, number} {count, plural, one {resource selected} other {resources selected}}"
"ImportFromChannelsModal.resourcesRemovedSnackbar","{count, number} {count, plural, one {resource removed} other {resources removed}}","
-- CONTEXT --
-","{count, number} {count, plural, one {resource removed} other {resources removed}}"
+","{count, number} {count, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø ą¤¹ą¤ą¤¾ą¤Æą¤¾ ą¤ą¤Æą¤¾} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø ą¤¹ą¤ą¤¾ą¤ ą¤ą¤}} "
"ImportFromChannelsModal.resourcesSelected","{count, number} {count, plural, one {resource selected} other {resources selected}}","
-- CONTEXT --
","{count, number} {count, plural, one {resource selected} other {resources selected}}"
@@ -2930,10 +2948,10 @@ Heading for the section in the resource editing window","Completion"
","Resource selection"
"InfoModal.close","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"LanguageDropdown.labelText","Language","
-- CONTEXT --
-","Language"
+","ą¤ą¤¾ą¤·ą¤¾Ā "
"LanguageDropdown.languageItemText","{language} ({code})","
-- CONTEXT --
","{language} ({code})"
@@ -2945,22 +2963,22 @@ Heading for the section in the resource editing window","Completion"
","Language not found"
"LanguageFilter.languageLabel","Languages","
-- CONTEXT --
-","Languages"
+","ą¤ą¤¾ą¤·ą¤¾ą¤ą¤"
"LanguageFilter.noMatchingLanguageText","No language matches the search","
-- CONTEXT --
","No language matches the search"
"LanguageSwitcherList.showMoreLanguagesSelector","More languages","
-- CONTEXT --
-","More languages"
+","ą¤
ą¤Øą„ą¤Æ ą¤ą¤¾ą¤·ą¤¾ą¤ą¤"
"LanguageSwitcherModal.cancelAction","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"LanguageSwitcherModal.changeLanguageModalHeader","Change language","
-- CONTEXT --
-","Change language"
+","ą¤ą¤¾ą¤·ą¤¾ ą¤¬ą¤¦ą¤²ą„ą¤"
"LanguageSwitcherModal.confirmAction","Confirm","
-- CONTEXT --
-","Confirm"
+","ą¤Ŗą„ą¤·ą„ą¤ą¤æ ą¤ą¤°ą„ą¤"
"LicenseDropdown.learnMoreButton","Learn More","
-- CONTEXT --
","Learn More"
@@ -2972,7 +2990,7 @@ Heading for the section in the resource editing window","Completion"
","About licenses"
"LicenseDropdown.licenseLabel","License","
-- CONTEXT --
-","License"
+","ą¤²ą¤¾ą¤ą¤øą„ą¤ą¤ø"
"Main.TOSLink","Terms of service","
-- CONTEXT --
","Terms of service"
@@ -2981,7 +2999,7 @@ Heading for the section in the resource editing window","Completion"
","Ā© {year} Learning Equality"
"Main.createAccountButton","Create an account","
-- CONTEXT --
-","Create an account"
+","ą¤
ą¤ą¤¾ą¤ą¤ą¤ ą¤¬ą¤Øą¤¾ą¤ą¤"
"Main.forgotPasswordLink","Forgot your password?","
-- CONTEXT --
","Forgot your password?"
@@ -2990,7 +3008,7 @@ Heading for the section in the resource editing window","Completion"
","Explore without an account"
"Main.kolibriStudio","Kolibri Studio","
-- CONTEXT --
-","Kolibri Studio"
+","Kolibri ą¤øą„ą¤ą„ą¤”ą¤æą¤Æą„"
"Main.loginFailed","Email or password is incorrect","
-- CONTEXT --
","Email or password is incorrect"
@@ -3002,19 +3020,22 @@ Heading for the section in the resource editing window","Completion"
","You must sign in to view that page"
"Main.passwordLabel","Password","
-- CONTEXT --
-","Password"
+","ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤”"
"Main.privacyPolicyLink","Privacy policy","
-- CONTEXT --
","Privacy policy"
"Main.signInButton","Sign in","
-- CONTEXT --
-","Sign in"
+","ą¤øą¤¾ą¤ą¤Ø ą¤ą¤Ø ą¤ą¤°ą„ą¤"
"MainNavigationDrawer.administrationLink","Administration","
-- CONTEXT --
","Administration"
+"MainNavigationDrawer.changeLanguage","Change language","
+-- CONTEXT --
+","Change language"
"MainNavigationDrawer.channelsLink","Channels","
-- CONTEXT --
-","Channels"
+","ą¤ą„ą¤Øą¤²"
"MainNavigationDrawer.copyright","Ā© {year} Learning Equality","
-- CONTEXT --
","Ā© {year} Learning Equality"
@@ -3026,10 +3047,10 @@ Heading for the section in the resource editing window","Completion"
","Help and support"
"MainNavigationDrawer.logoutLink","Sign out","
-- CONTEXT --
-","Sign out"
+","ą¤øą¤¾ą¤ą¤Ø ą¤ą¤ą¤ ą¤ą¤°ą„ą¤"
"MainNavigationDrawer.settingsLink","Settings","
-- CONTEXT --
-","Settings"
+","ą¤øą„ą¤ą¤æą¤ą¤"
"MarkdownEditor.bold","Bold (Ctrl+B)","
-- CONTEXT --
","Bold (Ctrl+B)"
@@ -3047,10 +3068,10 @@ Heading for the section in the resource editing window","Completion"
","Minimize (Ctrl+M)"
"MarkdownImageField.editImageOption","Edit","
-- CONTEXT --
-","Edit"
+","ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)"
"MarkdownImageField.removeImageOption","Remove","
-- CONTEXT --
-","Remove"
+","ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤"
"MarkdownImageField.resizeImageOption","Resize","
-- CONTEXT --
","Resize"
@@ -3071,7 +3092,7 @@ Heading for the section in the resource editing window","Completion"
","Add new folder"
"MoveModal.cancel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"MoveModal.emptyTopicText","No resources found","
-- CONTEXT --
","No resources found"
@@ -3110,10 +3131,10 @@ Heading for the section in the resource editing window","Completion"
","No items found"
"NewTopicModal.cancel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"NewTopicModal.create","Create","
-- CONTEXT --
-","Create"
+","ą¤¬ą¤Øą¤¾ą¤ą¤"
"NewTopicModal.createTopic","Create new folder","
-- CONTEXT --
","Create new folder"
@@ -3146,7 +3167,7 @@ Heading for the section in the resource editing window","Completion"
","You seem to be offline. Your changes will be saved once your connection is back."
"PageNotFoundError.backToHomeAction","Back to home","
-- CONTEXT --
-","Back to home"
+","ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤æą¤ø ą¤ą¤¾ą¤ą¤"
"PageNotFoundError.pageNotFoundDetails","Sorry, that page does not exist","
-- CONTEXT --
","Sorry, that page does not exist"
@@ -3158,7 +3179,7 @@ Heading for the section in the resource editing window","Completion"
","Field is required"
"PasswordField.passwordLabel","Password","
-- CONTEXT --
-","Password"
+","ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤”"
"PasswordInstructionsSent.passwordInstructionsHeader","Instructions sent. Thank you!","
-- CONTEXT --
","Instructions sent. Thank you!"
@@ -3167,22 +3188,19 @@ Heading for the section in the resource editing window","Completion"
","If there is already an account with the email address provided, you should receive the instructions shortly. If you don't see an email from us, please check your spam folder."
"PermissionsError.goToHomePageAction","Go to home page","
-- CONTEXT --
-","Go to home page"
+","ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤ą¤¾ą¤ą¤"
"PermissionsError.permissionDeniedHeader","Did you forget to sign in?","
-- CONTEXT --
-","Did you forget to sign in?"
+","ą¤ą„ą¤Æą¤¾ ą¤ą¤Ŗ ą¤øą¤¾ą¤ą¤Ø ą¤ą¤Ø ą¤ą¤°ą¤Øą¤¾ ą¤ą„ą¤² ą¤ą¤?"
"PoliciesModal.checkboxText","I have agreed to the above terms","
-- CONTEXT --
","I have agreed to the above terms"
-"PoliciesModal.checkboxValidationErrorMessage","Field is required","
--- CONTEXT --
-","Field is required"
"PoliciesModal.closeButton","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"PoliciesModal.continueButton","Continue","
-- CONTEXT --
-","Continue"
+","ą¤ą¤¾ą¤°ą„ ą¤°ą¤ą„ą¤"
"PoliciesModal.lastUpdated","Last updated {date}","
-- CONTEXT --
","Last updated {date}"
@@ -3207,15 +3225,18 @@ Heading for the section in the resource editing window","Completion"
"ProgressModal.syncError","Last attempt to sync failed","
-- CONTEXT --
","Last attempt to sync failed"
-"ProgressModal.syncHeader","Syncing channel","
+"ProgressModal.syncHeader","Syncing resources","
+-- CONTEXT --
+","Syncing resources"
+"ProgressModal.syncedSnackbar","Resources synced","
-- CONTEXT --
-","Syncing channel"
+","Resources synced"
"ProgressModal.unpublishedText","Unpublished","
-- CONTEXT --
","Unpublished"
"PublishModal.cancelButton","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"PublishModal.descriptionDescriptionTooltip","This description will be shown to Kolibri admins before they update channel versions","
-- CONTEXT --
","This description will be shown to Kolibri admins before they update channel versions"
@@ -3233,7 +3254,7 @@ Heading for the section in the resource editing window","Completion"
","Incomplete resources will not be published and made available for download in Kolibri."
"PublishModal.nextButton","Continue","
-- CONTEXT --
-","Continue"
+","ą¤ą¤¾ą¤°ą„ ą¤°ą¤ą„ą¤"
"PublishModal.publishButton","Publish","
-- CONTEXT --
","Publish"
@@ -3245,7 +3266,7 @@ Heading for the section in the resource editing window","Completion"
","Version description"
"RelatedResourcesList.removeBtnLabel","Remove","
-- CONTEXT --
-","Remove"
+","ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤"
"RelatedResourcesTab.addNextStepBtnLabel","Add next step","
-- CONTEXT --
","Add next step"
@@ -3254,13 +3275,13 @@ Heading for the section in the resource editing window","Completion"
","Add previous step"
"RelatedResourcesTab.dialogCloseBtnLabel","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"RelatedResourcesTab.nextStepsExplanation","Recommended resources that build on skills or concepts learned in this resource","
-- CONTEXT --
","Recommended resources that build on skills or concepts learned in this resource"
"RelatedResourcesTab.nextStepsTitle","Next steps","
-- CONTEXT --
-","Next steps"
+","ą¤
ą¤ą¤²ą„ ą¤ą¤¦ą¤®"
"RelatedResourcesTab.previewHelpText","Related resources are displayed as recommendations when learners engage with this resource","
-- CONTEXT --
","Related resources are displayed as recommendations when learners engage with this resource"
@@ -3299,28 +3320,28 @@ Heading for the section in the resource editing window","Completion"
","Limit the number of previous steps to create a more guided learning experience"
"ReportErrorModal.closeAction","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"ReportErrorModal.emailDescription","Contact the support team with your error details and weāll do our best to help.","
-- CONTEXT --
-","Contact the support team with your error details and weāll do our best to help."
+","ą¤
ą¤Ŗą¤Øą„ ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤µą¤æą¤µą¤°ą¤£ ą¤ą„ ą¤øą¤¾ą¤„ ą¤øą¤®ą¤°ą„ą¤„ą¤Ø ą¤ą„ą¤® ą¤øą„ ą¤øą¤ą¤Ŗą¤°ą„ą¤ ą¤ą¤°ą„ą¤ ą¤ą¤° ą¤¹ą¤® ą¤®ą¤¦ą¤¦ ą¤ą¤°ą¤Øą„ ą¤ą„ ą¤Ŗą„ą¤°ą„ ą¤ą„ą¤¶ą¤æą¤¶ ą¤ą¤°ą„ą¤ą¤ą„ ą„¤"
"ReportErrorModal.emailPrompt","Send an email to the developers","
-- CONTEXT --
-","Send an email to the developers"
+","ą¤”ą„ą¤µą¤²ą¤Ŗą¤° ą¤ą„ ą¤-ą¤®ą„ą¤² ą¤ą¤°ą„ą¤"
"ReportErrorModal.errorDetailsHeader","Error details","
-- CONTEXT --
-","Error details"
+","ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤µą¤æą¤µą¤°ą¤£"
"ReportErrorModal.forumPostingTips","Include a description of what you were trying to do and what you clicked on when the error appeared.","
-- CONTEXT --
-","Include a description of what you were trying to do and what you clicked on when the error appeared."
+","ą¤ą¤Ŗ ą¤ą„ą¤Æą¤¾ ą¤ą¤°ą¤Øą„ ą¤ą„ ą¤ą„ą¤¶ą¤æą¤¶ ą¤ą¤° ą¤°ą¤¹ą„ ą¤„ą„ ą¤ą¤° ą¤ą¤æą¤ø ą¤Ŗą¤° ą¤ą„ą¤²ą¤æą¤ ą¤ą¤æą¤Æą¤¾ ą¤ą¤¬ ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤¦ą¤æą¤ą¤¾ą¤ ą¤¦ą„, ą¤ą¤ø ą¤øą¤¬ą¤ą¤¾ ą¤µą¤°ą„ą¤£ą¤Ø ą¤¶ą¤¾ą¤®ą¤æą¤² ą¤ą¤°ą„ą¤ą„¤"
"ReportErrorModal.forumPrompt","Visit the community forums","
-- CONTEXT --
-","Visit the community forums"
+","ą¤øą¤®ą„ą¤¦ą¤¾ą¤Æ ą¤«ą¤¼ą„ą¤°ą¤® ą¤Ŗą¤° ą¤ą¤¾ą¤ą¤"
"ReportErrorModal.forumUseTips","Search the community forum to see if others encountered similar issues. If there are none reported, please open a new forum post and paste the error details below inside so we can rectify the error in a future version of Kolibri Studio.","
-- CONTEXT --
","Search the community forum to see if others encountered similar issues. If there are none reported, please open a new forum post and paste the error details below inside so we can rectify the error in a future version of Kolibri Studio."
"ReportErrorModal.reportErrorHeader","Report Error","
-- CONTEXT --
-","Report Error"
+","ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤ą„ ą¤øą„ą¤ą¤Øą¤¾ ą¤¦ą„ą¤"
"RequestForm.approximatelyHowManyResourcesLabel","Approximately how many individual resources are you planning to upload?","
-- CONTEXT --
","Approximately how many individual resources are you planning to upload?"
@@ -3422,7 +3443,7 @@ Heading for the section in the resource editing window","Completion"
","6+ months"
"RequestForm.sizePlaceholder","Size","
-- CONTEXT --
-","Size"
+","ą¤øą¤¾ą¤ą¤ą¤¼"
"RequestForm.smallNgoLabel","Small NGO with annual budget < $25k","
-- CONTEXT --
","Small NGO with annual budget < $25k"
@@ -3449,7 +3470,7 @@ Heading for the section in the resource editing window","Completion"
","What type of organization or group is coordinating the use of Kolibri (if applicable)?"
"RequestForm.unknownLabel","Unknown","
-- CONTEXT --
-","Unknown"
+","ą¤
ą¤ą„ą¤ą¤¾ą¤¤"
"RequestForm.uploadingOnBehalfLabel","I am uploading content on behalf of:","
-- CONTEXT --
","I am uploading content on behalf of:"
@@ -3518,28 +3539,25 @@ Heading for the section in the resource editing window","Completion"
","Audience"
"ResourcePanel.author","Author","
-- CONTEXT --
-","Author"
+","ą¤²ą„ą¤ą¤"
"ResourcePanel.availableFormats","Available formats","
-- CONTEXT --
","Available formats"
"ResourcePanel.coachResources","Resources for coaches","
-- CONTEXT --
","Resources for coaches"
-"ResourcePanel.completion","Completion","
--- CONTEXT --
-","Completion"
"ResourcePanel.copyrightHolder","Copyright holder","
-- CONTEXT --
-","Copyright holder"
+","ą¤ą„ą¤Ŗą„ą¤°ą¤¾ą¤ą¤ ą¤§ą¤¾ą¤°ą¤"
"ResourcePanel.description","Description","
-- CONTEXT --
-","Description"
+","ą¤µą¤æą¤µą¤°ą¤£"
"ResourcePanel.details","Details","
-- CONTEXT --
-","Details"
+","ą¤µą¤æą¤µą¤°ą¤£"
"ResourcePanel.fileSize","Size","
-- CONTEXT --
-","Size"
+","ą¤øą¤¾ą¤ą¤ą¤¼"
"ResourcePanel.files","Files","
-- CONTEXT --
","Files"
@@ -3548,31 +3566,41 @@ Heading for the section in the resource editing window","Completion"
","{count, plural, one {# incomplete question} other {# incomplete questions}}"
"ResourcePanel.language","Language","
-- CONTEXT --
-","Language"
+","ą¤ą¤¾ą¤·ą¤¾Ā "
"ResourcePanel.license","License","
-- CONTEXT --
-","License"
-"ResourcePanel.masteryMofN","Goal: {m} out of {n}","
--- CONTEXT --
-","Goal: {m} out of {n}"
+","ą¤²ą¤¾ą¤ą¤øą„ą¤ą¤ø"
"ResourcePanel.nextSteps","Next steps","
-- CONTEXT --
-","Next steps"
-"ResourcePanel.noCopyrightHolderError","Missing copyright holder","
+","ą¤
ą¤ą¤²ą„ ą¤ą¤¦ą¤®"
+"ResourcePanel.noCompletionCriteriaError","Completion criteria are required","ResourcePanel.noCompletionCriteriaError
+
-- CONTEXT --
-","Missing copyright holder"
-"ResourcePanel.noFilesError","Missing files","
+Error message notification when a specific metadata is missing.","Completion criteria are required"
+"ResourcePanel.noCopyrightHolderError","Copyright holder is required","
-- CONTEXT --
-","Missing files"
-"ResourcePanel.noLicenseDescriptionError","Missing license description","
+","Copyright holder is required"
+"ResourcePanel.noDurationError","Duration is required","
-- CONTEXT --
-","Missing license description"
-"ResourcePanel.noLicenseError","Missing license","
+","Duration is required"
+"ResourcePanel.noFilesError","File is required","ResourcePanel.noFilesError
+
+-- CONTEXT --
+Error message notification when a file is missing.","File is required"
+"ResourcePanel.noLearningActivityError","Learning activity is required","
+-- CONTEXT --
+","Learning activity is required"
+"ResourcePanel.noLicenseDescriptionError","License description is required","ResourcePanel.noLicenseDescriptionError
+
-- CONTEXT --
-","Missing license"
-"ResourcePanel.noMasteryModelError","Missing mastery criteria","
+Error message notification when a specific metadata is missing.","License description is required"
+"ResourcePanel.noLicenseError","License is required","
-- CONTEXT --
-","Missing mastery criteria"
+","License is required"
+"ResourcePanel.noMasteryModelError","Mastery criteria are required","ResourcePanel.noMasteryModelError
+
+-- CONTEXT --
+Error message notification when a specific metadata is missing.","Mastery criteria are required"
"ResourcePanel.noQuestionsError","Exercise is empty","
-- CONTEXT --
","Exercise is empty"
@@ -3587,16 +3615,16 @@ Heading for the section in the resource editing window","Completion"
","Provider"
"ResourcePanel.questionCount","{value, number, integer} {value, plural, one {question} other {questions}}","
-- CONTEXT --
-","{value, number, integer} {value, plural, one {question} other {questions}}"
+","{value, number, integer} {value, plural, one {ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø} other {ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø}}"
"ResourcePanel.questions","Questions","
-- CONTEXT --
-","Questions"
+","ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø"
"ResourcePanel.relatedResources","Related resources","
-- CONTEXT --
","Related resources"
"ResourcePanel.resources","Resources","
-- CONTEXT --
-","Resources"
+","ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø"
"ResourcePanel.showAnswers","Show answers","
-- CONTEXT --
","Show answers"
@@ -3623,25 +3651,25 @@ Heading for the section in the resource editing window","Completion"
","Requirements"
"ReviewSelectionsPage.noResourcesSelected","No resources selected","
-- CONTEXT --
-","No resources selected"
+","ą¤ą„ą¤ ą¤ą„ ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø ą¤Øą¤¹ą„ą¤ ą¤ą„ą¤Øą„ ą¤ą¤ ą¤¹ą„"
"ReviewSelectionsPage.removeAction","Remove","
-- CONTEXT --
-","Remove"
+","ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤"
"ReviewSelectionsPage.resourcesInTopic","{count, number} {count, plural, one {resource} other {resources}}","
-- CONTEXT --
-","{count, number} {count, plural, one {resource} other {resources}}"
+","{count, number} {count, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø}}"
"ReviewSelectionsPage.reviewSelectionHeader","Review selections","
-- CONTEXT --
","Review selections"
"SavedSearchesModal.cancelAction","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"SavedSearchesModal.closeButtonLabel","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"SavedSearchesModal.deleteAction","Delete","
-- CONTEXT --
-","Delete"
+","ą¤¹ą¤ą¤¾ą¤ą¤"
"SavedSearchesModal.deleteConfirmation","Are you sure you want to delete this saved search?","
-- CONTEXT --
","Are you sure you want to delete this saved search?"
@@ -3650,7 +3678,7 @@ Heading for the section in the resource editing window","Completion"
","Delete saved search"
"SavedSearchesModal.editAction","Edit","
-- CONTEXT --
-","Edit"
+","ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)"
"SavedSearchesModal.filterCount","{count, number} {count, plural, one {filter} other {filters}}","
-- CONTEXT --
","{count, number} {count, plural, one {filter} other {filters}}"
@@ -3677,7 +3705,7 @@ Heading for the section in the resource editing window","Completion"
","Assessments"
"SearchFilterBar.clearAll","Clear all","
-- CONTEXT --
-","Clear all"
+","ą¤øą¤ą„ ą¤øą¤¾ą¤«ą¤¼ ą¤ą¤°ą„ą¤"
"SearchFilterBar.coachContent","Resources for coaches","
-- CONTEXT --
","Resources for coaches"
@@ -3701,7 +3729,7 @@ Heading for the section in the resource editing window","Completion"
","Channel type"
"SearchFilters.channelsHeader","Channels","
-- CONTEXT --
-","Channels"
+","ą¤ą„ą¤Øą¤²"
"SearchFilters.coachContentLabel","Show resources for coaches","
-- CONTEXT --
","Show resources for coaches"
@@ -3716,19 +3744,19 @@ Heading for the section in the resource editing window","Completion"
","Format"
"SearchFilters.licensesLabel","License","
-- CONTEXT --
-","License"
+","ą¤²ą¤¾ą¤ą¤øą„ą¤ą¤ø"
"SearchOrBrowseWindow.backToBrowseAction","Back to browse","
-- CONTEXT --
","Back to browse"
"SearchOrBrowseWindow.copiedToClipboard","Copied to clipboard","
-- CONTEXT --
-","Copied to clipboard"
+","ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤° ą¤¦ą¤æą¤Æą¤¾ ą¤ą¤Æą¤¾"
"SearchOrBrowseWindow.copyFailed","Failed to copy to clipboard","
-- CONTEXT --
","Failed to copy to clipboard"
"SearchOrBrowseWindow.searchAction","Search","
-- CONTEXT --
-","Search"
+","ą¤ą„ą¤"
"SearchOrBrowseWindow.searchLabel","Search for resourcesā¦","
-- CONTEXT --
","Search for resourcesā¦"
@@ -3755,7 +3783,7 @@ Heading for the section in the resource editing window","Completion"
","Account"
"SettingsIndex.settingsTitle","Settings","
-- CONTEXT --
-","Settings"
+","ą¤øą„ą¤ą¤æą¤ą¤"
"SettingsIndex.storageLabel","Storage","
-- CONTEXT --
","Storage"
@@ -3767,13 +3795,13 @@ Heading for the section in the resource editing window","Completion"
","Back to viewing"
"StagingTreePage.cancelDeployBtn","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"StagingTreePage.channelDeployed","Channel has been deployed","
-- CONTEXT --
","Channel has been deployed"
"StagingTreePage.closeSummaryDetailsDialogBtn","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"StagingTreePage.collapseAllButton","Collapse all","
-- CONTEXT --
","Collapse all"
@@ -3827,7 +3855,7 @@ Heading for the section in the resource editing window","Completion"
","Total resources"
"StagingTreePage.totalSize","Total size","
-- CONTEXT --
-","Total size"
+","ą¤ą„ą¤² ą¤øą¤¾ą¤ą¤ą¤¼"
"StagingTreePage.viewDetails","View details","
-- CONTEXT --
","View details"
@@ -3866,7 +3894,7 @@ Heading for the section in the resource editing window","Completion"
","Missing title"
"StudioTree.optionsTooltip","Options","
-- CONTEXT --
-","Options"
+","ą¤µą¤æą¤ą¤²ą„ą¤Ŗ"
"SubtitlesList.acceptedFormatsTooltip","Supported formats: {extensions}","
-- CONTEXT --
","Supported formats: {extensions}"
@@ -3890,46 +3918,52 @@ Heading for the section in the resource editing window","Completion"
","Select file"
"SyncResourcesModal.backButtonLabel","Back","
-- CONTEXT --
-","Back"
+","ą¤µą¤¾ą¤Ŗą¤ø ą¤ą¤¾ą¤ą¤"
"SyncResourcesModal.cancelButtonLabel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"SyncResourcesModal.confirmSyncModalExplainer","You are about to sync and update the following:","
-- CONTEXT --
","You are about to sync and update the following:"
"SyncResourcesModal.confirmSyncModalTitle","Confirm sync","
-- CONTEXT --
","Confirm sync"
+"SyncResourcesModal.confirmSyncModalWarningExplainer","Warning: this will overwrite any changes you have made to copied or imported resources.","
+-- CONTEXT --
+","Warning: this will overwrite any changes you have made to copied or imported resources."
"SyncResourcesModal.continueButtonLabel","Continue","
-- CONTEXT --
-","Continue"
+","ą¤ą¤¾ą¤°ą„ ą¤°ą¤ą„ą¤"
"SyncResourcesModal.syncButtonLabel","Sync","
-- CONTEXT --
-","Sync"
-"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints","
+","ą¤øą¤æą¤ą¤"
+"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints in exercises and quizzes","
-- CONTEXT --
-","Update questions, answers, and hints"
+","Update questions, answers, and hints in exercises and quizzes"
"SyncResourcesModal.syncExercisesTitle","Assessment details","
-- CONTEXT --
","Assessment details"
-"SyncResourcesModal.syncFilesExplainer","Update all file information","
+"SyncResourcesModal.syncFilesExplainer","Update all files, including: thumbnails, subtitles, and captions","
-- CONTEXT --
-","Update all file information"
+","Update all files, including: thumbnails, subtitles, and captions"
"SyncResourcesModal.syncFilesTitle","Files","
-- CONTEXT --
","Files"
-"SyncResourcesModal.syncModalExplainer","Sync and update your resources with their original source.","
+"SyncResourcesModal.syncModalExplainer","Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files.","
+-- CONTEXT --
+","Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files."
+"SyncResourcesModal.syncModalSelectAttributes","Select what you would like to sync:","
-- CONTEXT --
-","Sync and update your resources with their original source."
+","Select what you would like to sync:"
"SyncResourcesModal.syncModalTitle","Sync resources","
-- CONTEXT --
","Sync resources"
-"SyncResourcesModal.syncTagsExplainer","Update all tags","
+"SyncResourcesModal.syncResourceDetailsExplainer","Update information about the resource: learning activity, level, requirements, category, tags, audience, and source","
-- CONTEXT --
-","Update all tags"
-"SyncResourcesModal.syncTagsTitle","Tags","
+","Update information about the resource: learning activity, level, requirements, category, tags, audience, and source"
+"SyncResourcesModal.syncResourceDetailsTitle","Resource details","
-- CONTEXT --
-","Tags"
+","Resource details"
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer","Update resource titles and descriptions","
-- CONTEXT --
","Update resource titles and descriptions"
@@ -3938,13 +3972,13 @@ Heading for the section in the resource editing window","Completion"
","Titles and descriptions"
"TechnicalTextBlock.copiedToClipboardConfirmation","Copied to clipboard","
-- CONTEXT --
-","Copied to clipboard"
+","ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤° ą¤¦ą¤æą¤Æą¤¾ ą¤ą¤Æą¤¾"
"TechnicalTextBlock.copiedToClipboardFailure","Copy to clipboard failed","
-- CONTEXT --
","Copy to clipboard failed"
"TechnicalTextBlock.copyToClipboardButtonPrompt","Copy to clipboard","
-- CONTEXT --
-","Copy to clipboard"
+","ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤"
"Template.templateString","You have {count, plural,
=1 {# node for testing}
other {# nodes for testing}}","
@@ -4251,22 +4285,22 @@ Heading for the section in the resource editing window","Completion"
","Kolibri Content Library Catalog"
"TitleStrings.defaultTitle","Kolibri Studio","
-- CONTEXT --
-","Kolibri Studio"
+","Kolibri ą¤øą„ą¤ą„ą¤”ą¤æą¤Æą„"
"TitleStrings.tabTitle","{title} - {site}","
-- CONTEXT --
","{title} - {site}"
"ToggleText.less","Show less","
-- CONTEXT --
-","Show less"
+","ą¤ą¤® ą¤¦ą¤æą¤ą¤¾ą¤ą¤"
"ToggleText.more","Show more","
-- CONTEXT --
-","Show more"
+","ą¤ą¤° ą¤¦ą¤æą¤ą¤¾ą¤ą¤"
"TrashModal.deleteButton","Delete","
-- CONTEXT --
-","Delete"
+","ą¤¹ą¤ą¤¾ą¤ą¤"
"TrashModal.deleteConfirmationCancelButton","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"TrashModal.deleteConfirmationDeleteButton","Delete permanently","
-- CONTEXT --
","Delete permanently"
@@ -4295,7 +4329,7 @@ Heading for the section in the resource editing window","Completion"
","Restore"
"TrashModal.selectAllHeader","Select all","
-- CONTEXT --
-","Select all"
+","ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤"
"TrashModal.selectedCountText","{topicCount, plural,
=1 {# folder}
other {# folders}}, {resourceCount, plural,
@@ -4318,7 +4352,7 @@ Heading for the section in the resource editing window","Completion"
","Trash"
"TreeView.closeDrawer","Close","
-- CONTEXT --
-","Close"
+","ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤"
"TreeView.collapseAllButton","Collapse all","
-- CONTEXT --
","Collapse all"
@@ -4336,7 +4370,7 @@ Heading for the section in the resource editing window","Completion"
","Generated by API"
"TreeViewBase.cancel","Cancel","
-- CONTEXT --
-","Cancel"
+","ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤"
"TreeViewBase.channelDeletedSnackbar","Channel deleted","
-- CONTEXT --
","Channel deleted"
@@ -4345,10 +4379,10 @@ Heading for the section in the resource editing window","Completion"
","View channel details"
"TreeViewBase.deleteChannel","Delete channel","
-- CONTEXT --
-","Delete channel"
+","ą¤ą„ą¤Øą¤² ą¤¹ą¤ą¤¾ą¤ą¤"
"TreeViewBase.deleteChannelButton","Delete channel","
-- CONTEXT --
-","Delete channel"
+","ą¤ą„ą¤Øą¤² ą¤¹ą¤ą¤¾ą¤ą¤"
"TreeViewBase.deletePrompt","This channel will be permanently deleted. This cannot be undone.","
-- CONTEXT --
","This channel will be permanently deleted. This cannot be undone."
@@ -4370,9 +4404,10 @@ Heading for the section in the resource editing window","Completion"
"TreeViewBase.noChangesText","No changes found in channel","
-- CONTEXT --
","No changes found in channel"
-"TreeViewBase.noLanguageSetError","Missing channel language","
+"TreeViewBase.noLanguageSetError","Channel language is required","TreeViewBase.noLanguageSetError
+
-- CONTEXT --
-","Missing channel language"
+Error message notification when a specific metadata is missing.","Channel language is required"
"TreeViewBase.openTrash","Open trash","
-- CONTEXT --
","Open trash"
@@ -4415,14 +4450,14 @@ Heading for the section in the resource editing window","Completion"
","Unsupported files"
"Uploader.unsupportedFilesText","{count, plural,
=1 {# file will not be uploaded.}
- other {# files will not be uploaded.}}
+ other {# files will not be uploaded.}}
{extensionCount, plural,
=1 {Supported file type is}
other {Supported file types are}} {extensions}","
-- CONTEXT --
","{count, plural,
=1 {# file will not be uploaded.}
- other {# files will not be uploaded.}}
+ other {# files will not be uploaded.}}
{extensionCount, plural,
=1 {Supported file type is}
other {Supported file types are}} {extensions}"
@@ -4441,12 +4476,9 @@ Heading for the section in the resource editing window","Completion"
"UsingStudio.bestPractice2","It is preferable to create multiple small channels rather than one giant channel with many layers of folders.","
-- CONTEXT --
","It is preferable to create multiple small channels rather than one giant channel with many layers of folders."
-"UsingStudio.bestPractice3","Reload the page often to ensure your work is saved to the server and no network errors have occurred. Use CTRL+R on Linux/Windows or ā+R on Mac.","
+"UsingStudio.bestPractice3","Reload the page to confirm your work has been saved to the server. Use CTRL+R on Linux/Windows or ā+R on Mac.","
-- CONTEXT --
-","Reload the page often to ensure your work is saved to the server and no network errors have occurred. Use CTRL+R on Linux/Windows or ā+R on Mac."
-"UsingStudio.bestPractice4","Avoid simultaneous edits on the same channel. Channels should not be edited by multiple users at the same time or by the same user in multiple browser windows.","
--- CONTEXT --
-","Avoid simultaneous edits on the same channel. Channels should not be edited by multiple users at the same time or by the same user in multiple browser windows."
+","Reload the page to confirm your work has been saved to the server. Use CTRL+R on Linux/Windows or ā+R on Mac."
"UsingStudio.bestPractice5","It is possible that you will encounter timeout errors in your browser when performing operations like import and sync, on large channels. Don't be alarmed by this error message and do not repeat the same operation again right away. It doesn't mean the operation has failed- Kolibri Studio is still working in the background. Wait a few minutes and reload the page before continuing your edits.","
-- CONTEXT --
","It is possible that you will encounter timeout errors in your browser when performing operations like import and sync, on large channels. Don't be alarmed by this error message and do not repeat the same operation again right away. It doesn't mean the operation has failed- Kolibri Studio is still working in the background. Wait a few minutes and reload the page before continuing your edits."
@@ -4456,9 +4488,6 @@ Heading for the section in the resource editing window","Completion"
"UsingStudio.bestPractice7","PUBLISH periodically and import your channel into Kolibri to preview the content and obtain a local backup copy of your channel.","
-- CONTEXT --
","PUBLISH periodically and import your channel into Kolibri to preview the content and obtain a local backup copy of your channel."
-"UsingStudio.bestPractice8","Do not edit the channel after you click PUBLISH. Wait for the notification email before resuming editing operations.","
--- CONTEXT --
-","Do not edit the channel after you click PUBLISH. Wait for the notification email before resuming editing operations."
"UsingStudio.bestPractice9","Report issues as you encounter them.","
-- CONTEXT --
","Report issues as you encounter them."
@@ -4468,18 +4497,13 @@ Heading for the section in the resource editing window","Completion"
"UsingStudio.communityStandardsLink","Community standards","
-- CONTEXT --
","Community standards"
-"UsingStudio.issue1","Two users have reported isolated incidents where content they imported from another channel disappeared, leaving only empty folders and subfolders. In one report, the content later re-appeared. They did not experience these problems consistently, and the incidents may possibly involve issues with a slow or unstable internet connection. If you run into this issue, please contact us as soon as possible and let us know as much information as you can remember.","
--- CONTEXT --
-","Two users have reported isolated incidents where content they imported from another channel disappeared, leaving only empty folders and subfolders. In one report, the content later re-appeared. They did not experience these problems consistently, and the incidents may possibly involve issues with a slow or unstable internet connection. If you run into this issue, please contact us as soon as possible and let us know as much information as you can remember."
-"UsingStudio.issue2","Some operations in Studio are currently very slow, and so it may appear that the change you attempted to make timed out or did not take effect. In many cases, the change is still being processed and will appear once it is complete. If, after 5-10 minutes, the change still has not taken effect even after a browser refresh, please file an issue. We are working on solutions to these issues.","
+"UsingStudio.issue1","There have been reports where users have observed the disappearance of changes they've recently made to their channels. The issue seems related to opening multiple tabs of Kolibri Studio, and eventually signing out. We advise that you disable any āMemory Saver/Sleepingā tab browser feature for Kolibri Studio, and reload each tab before signing out. We're actively investigating this issue, so if you run into it, please contact us with as much information as possible.","UsingStudio.issue1
+
-- CONTEXT --
-","Some operations in Studio are currently very slow, and so it may appear that the change you attempted to make timed out or did not take effect. In many cases, the change is still being processed and will appear once it is complete. If, after 5-10 minutes, the change still has not taken effect even after a browser refresh, please file an issue. We are working on solutions to these issues."
+A description of an issue that has been reported by users - the recommendation is to disable any memory saver feature in the browser while they are using Kolibri Studio.","There have been reports where users have observed the disappearance of changes they've recently made to their channels. The issue seems related to opening multiple tabs of Kolibri Studio, and eventually signing out. We advise that you disable any āMemory Saver/Sleepingā tab browser feature for Kolibri Studio, and reload each tab before signing out. We're actively investigating this issue, so if you run into it, please contact us with as much information as possible."
"UsingStudio.issueLink1","Reports of disappearing content","
-- CONTEXT --
","Reports of disappearing content"
-"UsingStudio.issueLink2","Slow performance can lead to unexpected errors in the interface","
--- CONTEXT --
-","Slow performance can lead to unexpected errors in the interface"
"UsingStudio.issuesPageLink","View all issues","
-- CONTEXT --
","View all issues"
@@ -4530,7 +4554,7 @@ Heading for the section in the resource editing window","Completion"
","Question is required"
"channelEditVue.false","False","
-- CONTEXT --
-","False"
+","ą¤ą¤²ą¤¤"
"channelEditVue.questionTypeInput","Numeric input","
-- CONTEXT --
","Numeric input"
@@ -4548,7 +4572,7 @@ Heading for the section in the resource editing window","Completion"
","True/False"
"channelEditVue.true","True","
-- CONTEXT --
-","True"
+","ą¤øą¤¹ą„"
"formStrings.errorText","Please fix {count, plural,
=1 {# error}
other {# errors}} below","
@@ -4561,7 +4585,7 @@ Heading for the section in the resource editing window","Completion"
","Value must be equal to or greater than 1"
"sharedVue.activityDurationRequired","This field is required","
-- CONTEXT --
-","This field is required"
+","ą¤Æą¤¹ ą¤ą¤¾ą¤Øą¤ą¤¾ą¤°ą„ ą¤ą¤¼ą¤°ą„ą¤°ą„ ą¤¹ą„"
"sharedVue.activityDurationTooLongWarning","This value is very high. Please make sure this is how long learners should work on the resource for, in order to complete it.","
-- CONTEXT --
","This value is very high. Please make sure this is how long learners should work on the resource for, in order to complete it."
@@ -4576,7 +4600,7 @@ Heading for the section in the resource editing window","Completion"
","Duration is required"
"sharedVue.fieldRequired","This field is required","
-- CONTEXT --
-","This field is required"
+","ą¤Æą¤¹ ą¤ą¤¾ą¤Øą¤ą¤¾ą¤°ą„ ą¤ą¤¼ą¤°ą„ą¤°ą„ ą¤¹ą„"
"sharedVue.learningActivityRequired","Learning activity is required","
-- CONTEXT --
","Learning activity is required"
@@ -4600,7 +4624,7 @@ Heading for the section in the resource editing window","Completion"
","Must be lesser than or equal to N"
"sharedVue.masteryModelMRequired","Required","
-- CONTEXT --
-","Required"
+","ą¤ą¤µą¤¶ą„ą¤Æą¤"
"sharedVue.masteryModelMWholeNumber","Must be a whole number","
-- CONTEXT --
","Must be a whole number"
@@ -4609,7 +4633,7 @@ Heading for the section in the resource editing window","Completion"
","Must be at least 1"
"sharedVue.masteryModelNRequired","Required","
-- CONTEXT --
-","Required"
+","ą¤ą¤µą¤¶ą„ą¤Æą¤"
"sharedVue.masteryModelNWholeNumber","Must be a whole number","
-- CONTEXT --
","Must be a whole number"
diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json
index a9d58c8bc2..5b7c69fee3 100644
--- a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json
@@ -8,7 +8,7 @@
"Account.apiTokenHeading": "API Token",
"Account.apiTokenMessage": "You will need this access token to run content integration scripts for bulk-uploading materials through the Kolibri Studio API.",
"Account.basicInfoHeader": "Basic Information",
- "Account.changePasswordAction": "Change password",
+ "Account.changePasswordAction": "ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤” ą¤¬ą¤¦ą¤²ą„ą¤",
"Account.completelyDeleteAccountLabel": "Completely remove your account from Kolibri Studio",
"Account.deleteAccountLabel": "Delete account",
"Account.editFullNameAction": "Edit full name",
@@ -18,15 +18,15 @@
"Account.exportDataButton": "Export data",
"Account.exportFailed": "Unable to export data. Please try again.",
"Account.exportStartedHeader": "Data export started",
- "Account.fullNameLabel": "Full name",
+ "Account.fullNameLabel": "ą¤Ŗą„ą¤°ą¤¾ ą¤Øą¤¾ą¤®",
"Account.handleChannelsBeforeAccount": "You must delete these channels manually or invite others to edit them before you can delete your account.",
- "Account.passwordLabel": "Password",
+ "Account.passwordLabel": "ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤”",
"Account.unableToDeleteAdminAccount": "Unable to delete an admin account",
- "Account.usernameLabel": "Username",
+ "Account.usernameLabel": "ą¤Æą„ą¤ą¤°ą¤Øą„ą¤®",
"AccountCreated.accountCreatedTitle": "Account successfully created",
- "AccountCreated.continueToSignIn": "Continue to sign-in",
+ "AccountCreated.backToLogin": "Continue to sign-in page",
"AccountDeleted.accountDeletedTitle": "Account successfully deleted",
- "AccountDeleted.continueToSignIn": "Continue to sign-in page",
+ "AccountDeleted.backToLogin": "Continue to sign-in page",
"AccountNotActivated.requestNewLink": "Request a new activation link",
"AccountNotActivated.text": "Please check your email for an activation link or request a new link.",
"AccountNotActivated.title": "Account has not been activated",
@@ -44,41 +44,41 @@
"AddNextStepsPage.toolbarTitle": "Add next step",
"AddPreviousStepsPage.addedPreviousStepSnackbar": "Added previous step",
"AddPreviousStepsPage.toolbarTitle": "Add previous step",
- "AddRelatedResourcesModal.addStepBtnLabel": "Add",
- "AddRelatedResourcesModal.cancelBtnLabel": "Cancel",
- "AddRelatedResourcesModal.previewStepBtnLabel": "Preview",
+ "AddRelatedResourcesModal.addStepBtnLabel": "ą¤ą„ą¤”ą¤¼ą„ą¤",
+ "AddRelatedResourcesModal.cancelBtnLabel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
+ "AddRelatedResourcesModal.previewStepBtnLabel": "ą¤Ŗą„ą¤°ą„ą¤µą¤¾ą¤µą¤²ą„ą¤ą¤Ø",
"AddRelatedResourcesModal.resourcesDisplayedText": "Only showing available resources for",
"AddRelatedResourcesModal.selectedAsCurrentResource": "This is the current resource",
"AddRelatedResourcesModal.selectedAsNextStep": "Already selected as a next step",
"AddRelatedResourcesModal.selectedAsPreviousStep": "Already selected as a previous step",
"AdministrationAppError.unauthorizedDetails": "You need to be an administrator of Studio to view this page",
- "AdministrationIndex.channelsLabel": "Channels",
- "AdministrationIndex.usersLabel": "Users",
+ "AdministrationIndex.channelsLabel": "ą¤ą„ą¤Øą¤²",
+ "AdministrationIndex.usersLabel": "ą¤ą¤Ŗą¤Æą„ą¤ą¤ą¤°ą„ą¤¤ą¤¾",
"Alert.closeButtonLabel": "OK",
- "Alert.dontShowAgain": "Don't show this message again",
+ "Alert.dontShowAgain": "ą¤Æą¤¹ ą¤øą¤ą¤¦ą„ą¤¶ ą¤¦ą„ą¤¬ą¤¾ą¤°ą¤¾ ą¤Ø ą¤¦ą¤æą¤ą¤¾ą¤ą¤",
"AnswersEditor.answersLabel": "Answers",
"AnswersEditor.newAnswerBtnLabel": "New answer",
"AnswersEditor.noAnswersPlaceholder": "Question has no answer options",
"AnswersEditor.numberFieldErrorLabel": "Answer must be a number",
"AppBar.administration": "Administration",
- "AppBar.changeLanguage": "Change language",
+ "AppBar.changeLanguage": "ą¤ą¤¾ą¤·ą¤¾ ą¤¬ą¤¦ą¤²ą„ą¤",
"AppBar.help": "Help and support",
- "AppBar.logIn": "Sign in",
- "AppBar.logOut": "Sign out",
- "AppBar.settings": "Settings",
- "AppBar.title": "Kolibri Studio",
- "AssessmentEditor.closeBtnLabel": "Close",
+ "AppBar.logIn": "ą¤øą¤¾ą¤ą¤Ø ą¤ą¤Ø ą¤ą¤°ą„ą¤",
+ "AppBar.logOut": "ą¤øą¤¾ą¤ą¤Ø ą¤ą¤ą¤ ą¤ą¤°ą„ą¤",
+ "AppBar.settings": "ą¤øą„ą¤ą¤æą¤ą¤",
+ "AppBar.title": "Kolibri ą¤øą„ą¤ą„ą¤”ą¤æą¤Æą„",
+ "AssessmentEditor.closeBtnLabel": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
"AssessmentEditor.incompleteItemIndicatorLabel": "Incomplete",
"AssessmentEditor.newQuestionBtnLabel": "New question",
"AssessmentEditor.noQuestionsPlaceholder": "Exercise has no questions",
"AssessmentEditor.showAnswers": "Show answers",
- "AssessmentEditor.toolbarItemLabel": "question",
+ "AssessmentEditor.toolbarItemLabel": "ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø",
"AssessmentItemEditor.dialogMessageChangeToInput": "Switching to 'numeric input' will set all answers as correct and remove all non-numeric answers. Continue?",
"AssessmentItemEditor.dialogMessageChangeToSingleSelection": "Switching to 'single choice' will set only one answer as correct. Continue?",
"AssessmentItemEditor.dialogMessageChangeToTrueFalse": "Switching to 'true or false' will remove all current answers. Continue?",
- "AssessmentItemEditor.dialogSubmitBtnLabel": "Change",
+ "AssessmentItemEditor.dialogSubmitBtnLabel": "ą¤¬ą¤¦ą¤²ą„ą¤",
"AssessmentItemEditor.dialogTitle": "Changing question type",
- "AssessmentItemEditor.questionLabel": "Question",
+ "AssessmentItemEditor.questionLabel": "ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø",
"AssessmentItemEditor.questionTypeLabel": "Response type",
"AssessmentItemPreview.answersLabel": "Answers",
"AssessmentItemPreview.hintsToggleLabelHide": "Hide hints",
@@ -86,20 +86,19 @@
"AssessmentItemPreview.noAnswersPlaceholder": "Question has no answer options",
"AssessmentItemToolbar.toolbarLabelAddAbove": "Add {itemLabel} above",
"AssessmentItemToolbar.toolbarLabelAddBelow": "Add {itemLabel} below",
- "AssessmentItemToolbar.toolbarLabelDelete": "Delete",
- "AssessmentItemToolbar.toolbarLabelEdit": "Edit",
+ "AssessmentItemToolbar.toolbarLabelDelete": "ą¤¹ą¤ą¤¾ą¤ą¤",
+ "AssessmentItemToolbar.toolbarLabelEdit": "ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)",
"AssessmentItemToolbar.toolbarLabelMoveDown": "Move down",
"AssessmentItemToolbar.toolbarLabelMoveUp": "Move up",
- "AssessmentTab.dialogCancelBtnLabel": "Cancel",
+ "AssessmentTab.dialogCancelBtnLabel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"AssessmentTab.dialogSubmitBtnLabel": "Submit",
"AssessmentTab.incompleteItemsCountMessage": "{invalidItemsCount} incomplete {invalidItemsCount, plural, one {question} other {questions}}",
- "BrowsingCard.addToClipboardAction": "Copy to clipboard",
+ "BrowsingCard.addToClipboardAction": "ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤",
"BrowsingCard.coach": "Resource for coaches",
- "BrowsingCard.goToPluralLocationsAction": "In {count, number} {count, plural, one {location} other {locations}}",
"BrowsingCard.goToSingleLocationAction": "Go to location",
"BrowsingCard.hasCoachTooltip": "{value, number, integer} {value, plural, one {resource for coaches} other {resources for coaches}}",
"BrowsingCard.previewAction": "View details",
- "BrowsingCard.resourcesCount": "{count, number} {count, plural, one {resource} other {resources}}",
+ "BrowsingCard.resourcesCount": "{count, number} {count, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø}}",
"BrowsingCard.tagsList": "Tags: {tags}",
"BytesForHumansStrings.fileSizeInBytes": "{n, number, integer} B",
"BytesForHumansStrings.fileSizeInGigabytes": "{n, number, integer} GB",
@@ -112,7 +111,7 @@
"CatalogFAQ.KolibriAnswerItem3": "Fostering innovative pedagogy and effective learning outcomes",
"CatalogFAQ.KolibriQuestion": "What is Kolibri?",
"CatalogFAQ.aboutHeader": "Welcome to the Kolibri Content Library Catalog! ",
- "CatalogFAQ.aboutKolibriHeader": "About Kolibri",
+ "CatalogFAQ.aboutKolibriHeader": "Kolibri ą¤ą„ ą¤¬ą¤¾ą¤°ą„ ą¤®ą„ą¤",
"CatalogFAQ.aboutLibraryHeader": "About the Kolibri Content Library",
"CatalogFAQ.channelAnswer": "A channel is Kolibriās unit of organization for digital content. It's a collection of resources organized by single institutions or creators, each of which may contain a set of books, games, textbooks, articles, simulations, exercises, and many more types of educational materials, all made available for use in Kolibri without the need for internet access. A channel isn't necessarily a course or a sequence, it's simply a collection of materials published or gathered together by one organization, as close to the provider's original layout as possible, while still organized for the best possible navigation in Kolibri.",
"CatalogFAQ.channelLink": "What is a channel?",
@@ -125,7 +124,7 @@
"CatalogFAQ.descriptionP2": "Click on a channel to get a preview of what subjects and topics it covers, learn more about its creator, see how many resources the channel contains, and learn how to import it into Kolibri. You can also find coach-specific content (lesson plans, teacher professional guides, and other supplementary facilitation material), assessments and exercises, and captions for accessibility.",
"CatalogFAQ.descriptionP3": "Sharing the work of these resource creators is what inspires Learning Equality's efforts. We hope you find something that excites you about the potential of digital learning, online or off!",
"CatalogFAQ.downloadKolibriLink": "Download Kolibri",
- "CatalogFAQ.downloadLink": "Download",
+ "CatalogFAQ.downloadLink": "ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤ą¤°ą„ą¤",
"CatalogFAQ.endoresementQuestion": "Have these sources been vetted or endorsed as classroom-safe and ready?",
"CatalogFAQ.endorsementAnswer": "We select sources with an educational affiliation or mandate, so you can trust that most resources in the Kolibri Content Library were designed for learning purposes. However, we are not able to guarantee the appropriateness of each individual item within any particular source. We recommend that educators and administrators conduct a thorough review of any digital content using their own criteria - including reorganization and re-curation, if necessary - before using it with learners. Since we recognize that there may be many different standards across situations for criteria like preferred levels of interactivity, subject/age appropriateness, cultural sensitivity and tone, among others, we have intentionally offered a wide range of materials to help meet the needs of all learners whatever they may be.",
"CatalogFAQ.faqHeader": "Frequently asked questions",
@@ -167,15 +166,15 @@
"CatalogFAQ.visitWebsiteLink": "Visit website",
"CatalogFilterBar.assessments": "Assessments",
"CatalogFilterBar.channelCount": "{count, plural,\n =1 {# channel}\n other {# channels}}",
- "CatalogFilterBar.clearAll": "Clear all",
- "CatalogFilterBar.close": "Close",
+ "CatalogFilterBar.clearAll": "ą¤øą¤ą„ ą¤øą¤¾ą¤«ą¤¼ ą¤ą¤°ą„ą¤",
+ "CatalogFilterBar.close": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
"CatalogFilterBar.coachContent": "Coach content",
"CatalogFilterBar.copyTitle": "Copy collection token",
"CatalogFilterBar.copyToken": "Copy collection token",
"CatalogFilterBar.copyTokenInstructions": "Paste this token into Kolibri to import the channels contained in this collection",
"CatalogFilterBar.keywords": "\"{text}\"",
"CatalogFilterBar.starred": "Starred",
- "CatalogFilterBar.subtitles": "Subtitles",
+ "CatalogFilterBar.subtitles": "ą¤ą¤Ŗą¤¶ą„ą¤°ą„ą¤·ą¤",
"CatalogFilters.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
"CatalogFilters.coachLabel": "Resources for coaches",
"CatalogFilters.copyright": "Ā© {year} Learning Equality",
@@ -183,28 +182,28 @@
"CatalogFilters.frequentlyAskedQuestionsLink": "Frequently asked questions",
"CatalogFilters.includesLabel": "Display only channels with",
"CatalogFilters.licenseLabel": "Licenses",
- "CatalogFilters.searchLabel": "Keywords",
- "CatalogFilters.searchText": "Search",
+ "CatalogFilters.searchLabel": "ą¤ą„ą¤¬ą„ą¤°ą„ą¤”",
+ "CatalogFilters.searchText": "ą¤ą„ą¤",
"CatalogFilters.starredLabel": "Starred",
"CatalogFilters.subtitlesLabel": "Captions or subtitles",
- "CatalogList.cancelButton": "Cancel",
+ "CatalogList.cancelButton": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"CatalogList.channelSelectionCount": "{count, plural,\n =1 {# channel selected}\n other {# channels selected}}",
- "CatalogList.downloadButton": "Download",
- "CatalogList.downloadCSV": "Download CSV",
+ "CatalogList.downloadButton": "ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤ą¤°ą„ą¤",
+ "CatalogList.downloadCSV": "CSV ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤ą¤°ą„ą¤",
"CatalogList.downloadPDF": "Download PDF",
- "CatalogList.downloadingMessage": "Download started",
+ "CatalogList.downloadingMessage": "ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤¶ą„ą¤°ą„",
"CatalogList.resultsText": "{count, plural,\n =1 {# result found}\n other {# results found}}",
- "CatalogList.selectAll": "Select all",
+ "CatalogList.selectAll": "ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤",
"CatalogList.selectChannels": "Download a summary of selected channels",
"CategoryOptions.noCategoryFoundText": "Category not found",
- "ChangePasswordForm.cancelAction": "Cancel",
- "ChangePasswordForm.changePasswordHeader": "Change password",
+ "ChangePasswordForm.cancelAction": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
+ "ChangePasswordForm.changePasswordHeader": "ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤” ą¤¬ą¤¦ą¤²ą„ą¤",
"ChangePasswordForm.confirmNewPasswordLabel": "Confirm new password",
"ChangePasswordForm.formInvalidText": "Passwords don't match",
"ChangePasswordForm.newPasswordLabel": "New password",
"ChangePasswordForm.passwordChangeFailed": "Failed to save new password",
"ChangePasswordForm.paswordChangeSuccess": "Password updated",
- "ChangePasswordForm.saveChangesAction": "Save changes",
+ "ChangePasswordForm.saveChangesAction": "ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Øą„ą¤ ą¤ą„ ą¤øą„ą¤µ ą¤ą¤°ą„ą¤",
"ChannelCatalogFrontPage.assessmentsIncludedText": "Assessments",
"ChannelCatalogFrontPage.catalogHeader": "Kolibri Content Library channels",
"ChannelCatalogFrontPage.coachHeading": "Resources for coaches",
@@ -212,65 +211,65 @@
"ChannelCatalogFrontPage.defaultNoItemsText": "---",
"ChannelCatalogFrontPage.exported": "Exported",
"ChannelCatalogFrontPage.formatsHeading": "Formats",
- "ChannelCatalogFrontPage.languagesHeading": "Languages",
+ "ChannelCatalogFrontPage.languagesHeading": "ą¤ą¤¾ą¤·ą¤¾ą¤ą¤",
"ChannelCatalogFrontPage.numberOfChannels": "{ num } channels",
"ChannelCatalogFrontPage.subtitlesIncludedText": "Captions or subtitles",
- "ChannelDeletedError.backToHomeAction": "Back to home",
+ "ChannelDeletedError.backToHomeAction": "ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤æą¤ø ą¤ą¤¾ą¤ą¤",
"ChannelDeletedError.channelDeletedDetails": "This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.",
- "ChannelDeletedError.channelDeletedHeader": "Channel not found",
+ "ChannelDeletedError.channelDeletedHeader": "ą¤ą„ą¤Øą¤² ą¤Øą¤¹ą„ą¤ ą¤®ą¤æą¤²ą¤¾",
"ChannelDetailsModal.downloadButton": "Download channel summary",
- "ChannelDetailsModal.downloadCSV": "Download CSV",
+ "ChannelDetailsModal.downloadCSV": "CSV ą¤”ą¤¾ą¤ą¤Øą¤²ą„ą¤” ą¤ą¤°ą„ą¤",
"ChannelDetailsModal.downloadPDF": "Download PDF",
"ChannelExportStrings.aggregators": "Aggregators",
"ChannelExportStrings.assessments": "Assessments",
"ChannelExportStrings.authors": "Authors",
"ChannelExportStrings.coachContent": "Resources for coaches",
"ChannelExportStrings.copyrightHolders": "Copyright holders",
- "ChannelExportStrings.description": "Description",
+ "ChannelExportStrings.description": "ą¤µą¤æą¤µą¤°ą¤£",
"ChannelExportStrings.downloadFilename": "{year}_{month}_Kolibri_Content_Library",
"ChannelExportStrings.id": "Channel ID",
- "ChannelExportStrings.language": "Language",
+ "ChannelExportStrings.language": "ą¤ą¤¾ą¤·ą¤¾Ā ",
"ChannelExportStrings.languages": "Included languages",
"ChannelExportStrings.licenses": "Licenses",
- "ChannelExportStrings.name": "Name",
- "ChannelExportStrings.no": "No",
+ "ChannelExportStrings.name": "ą¤Øą¤¾ą¤®",
+ "ChannelExportStrings.no": "ą¤Øą¤¹ą„ą¤",
"ChannelExportStrings.providers": "Providers",
- "ChannelExportStrings.resources": "Resources",
+ "ChannelExportStrings.resources": "ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø",
"ChannelExportStrings.size": "Total resources",
"ChannelExportStrings.storage": "Storage",
"ChannelExportStrings.subtitles": "Captions or subtitles",
"ChannelExportStrings.tags": "Tags",
"ChannelExportStrings.token": "Token",
- "ChannelExportStrings.yes": "Yes",
- "ChannelInfoCard.resourceCount": "{count, number} {count, plural, one {resource} other {resources}}",
- "ChannelInvitation.accept": "Accept",
+ "ChannelExportStrings.yes": "ą¤¹ą¤¾ą¤",
+ "ChannelInfoCard.resourceCount": "{count, number} {count, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø}}",
+ "ChannelInvitation.accept": "ą¤øą„ą¤µą„ą¤ą¤¾ą¤° ą¤ą¤°ą„ą¤",
"ChannelInvitation.acceptedSnackbar": "Accepted invitation",
- "ChannelInvitation.cancel": "Cancel",
- "ChannelInvitation.decline": "Decline",
+ "ChannelInvitation.cancel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
+ "ChannelInvitation.decline": "ą¤
ą¤øą„ą¤µą„ą¤ą¤¾ą¤° ą¤ą¤°ą„ą¤",
"ChannelInvitation.declinedSnackbar": "Declined invitation",
"ChannelInvitation.decliningInvitation": "Declining Invitation",
"ChannelInvitation.decliningInvitationMessage": "Are you sure you want to decline this invitation?",
"ChannelInvitation.editText": "{sender} has invited you to edit {channel}",
"ChannelInvitation.goToChannelSnackbarAction": "Go to channel",
"ChannelInvitation.viewText": "{sender} has invited you to view {channel}",
- "ChannelItem.cancel": "Cancel",
+ "ChannelItem.cancel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"ChannelItem.channelDeletedSnackbar": "Channel deleted",
"ChannelItem.channelLanguageNotSetIndicator": "No language set",
"ChannelItem.copyToken": "Copy channel token",
- "ChannelItem.deleteChannel": "Delete channel",
+ "ChannelItem.deleteChannel": "ą¤ą„ą¤Øą¤² ą¤¹ą¤ą¤¾ą¤ą¤",
"ChannelItem.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
"ChannelItem.deleteTitle": "Delete this channel",
- "ChannelItem.details": "Details",
+ "ChannelItem.details": "ą¤µą¤æą¤µą¤°ą¤£",
"ChannelItem.editChannel": "Edit channel details",
"ChannelItem.goToWebsite": "Go to source website",
"ChannelItem.lastPublished": "Published {last_published}",
"ChannelItem.lastUpdated": "Updated {updated}",
"ChannelItem.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}",
"ChannelItem.unpublishedText": "Unpublished",
- "ChannelItem.versionText": "Version {version}",
+ "ChannelItem.versionText": "ą¤øą¤ą¤øą„ą¤ą¤°ą¤£ {version}",
"ChannelItem.viewContent": "View channel on Kolibri",
"ChannelList.channel": "New channel",
- "ChannelList.channelFilterLabel": "Channels",
+ "ChannelList.channelFilterLabel": "ą¤ą„ą¤Øą¤²",
"ChannelList.noChannelsFound": "No channels found",
"ChannelList.noMatchingChannels": "There are no matching channels",
"ChannelListAppError.channelPermissionsErrorDetails": "Sign in or ask the owner of this channel to give you permission to edit or view",
@@ -280,44 +279,44 @@
"ChannelListIndex.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}",
"ChannelListIndex.libraryTitle": "Kolibri Content Library Catalog",
"ChannelModal.APIText": "Channels generated automatically are not editable.",
- "ChannelModal.changesSaved": "Changes saved",
+ "ChannelModal.changesSaved": "ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Ø ą¤øą¤¹ą„ą¤ą„ ą¤ą¤",
"ChannelModal.channelDescription": "Channel description",
"ChannelModal.channelError": "Field is required",
- "ChannelModal.channelName": "Channel name",
+ "ChannelModal.channelName": "ą¤ą„ą¤Øą¤² ą¤ą¤¾ ą¤Øą¤¾ą¤®",
"ChannelModal.closeButton": "Exit without saving",
- "ChannelModal.createButton": "Create",
+ "ChannelModal.createButton": "ą¤¬ą¤Øą¤¾ą¤ą¤",
"ChannelModal.creatingHeader": "New channel",
"ChannelModal.details": "Channel details",
- "ChannelModal.editTab": "Details",
+ "ChannelModal.editTab": "ą¤µą¤æą¤µą¤°ą¤£",
"ChannelModal.keepEditingButton": "Keep editing",
"ChannelModal.notFoundError": "Channel does not exist",
- "ChannelModal.saveChangesButton": "Save changes",
+ "ChannelModal.saveChangesButton": "ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Øą„ą¤ ą¤ą„ ą¤øą„ą¤µ ą¤ą¤°ą„ą¤",
"ChannelModal.shareTab": "Sharing",
"ChannelModal.unauthorizedError": "You cannot edit this channel",
"ChannelModal.unsavedChangesHeader": "Unsaved changes",
"ChannelModal.unsavedChangesText": "You will lose any unsaved changes. Are you sure you want to exit?",
- "ChannelNotFoundError.backToHomeAction": "Back to home",
+ "ChannelNotFoundError.backToHomeAction": "ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤æą¤ø ą¤ą¤¾ą¤ą¤",
"ChannelNotFoundError.channelNotFoundDetails": "This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.",
- "ChannelNotFoundError.channelNotFoundHeader": "Channel not found",
+ "ChannelNotFoundError.channelNotFoundHeader": "ą¤ą„ą¤Øą¤² ą¤Øą¤¹ą„ą¤ ą¤®ą¤æą¤²ą¤¾",
"ChannelSelectionList.noChannelsFound": "No channels found",
- "ChannelSelectionList.searchText": "Search for a channel",
- "ChannelSetItem.cancel": "Cancel",
+ "ChannelSelectionList.searchText": "ą¤ą¤æą¤øą„ ą¤ą„ą¤Øą¤² ą¤ą„ ą¤ą„ą¤ą„ą¤",
+ "ChannelSetItem.cancel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"ChannelSetItem.delete": "Delete collection",
"ChannelSetItem.deleteChannelSetText": "Are you sure you want to delete this collection?",
"ChannelSetItem.deleteChannelSetTitle": "Delete collection",
"ChannelSetItem.edit": "Edit collection",
- "ChannelSetItem.options": "Options",
+ "ChannelSetItem.options": "ą¤µą¤æą¤ą¤²ą„ą¤Ŗ",
"ChannelSetItem.saving": "Saving",
"ChannelSetList.aboutChannelSets": "About collections",
"ChannelSetList.aboutChannelSetsLink": "Learn about collections",
"ChannelSetList.addChannelSetTitle": "New collection",
- "ChannelSetList.cancelButtonLabel": "Close",
+ "ChannelSetList.cancelButtonLabel": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
"ChannelSetList.channelNumber": "Number of channels",
"ChannelSetList.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.",
"ChannelSetList.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections",
"ChannelSetList.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.",
"ChannelSetList.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.",
- "ChannelSetList.options": "Options",
+ "ChannelSetList.options": "ą¤µą¤æą¤ą¤²ą„ą¤Ŗ",
"ChannelSetList.title": "Collection name",
"ChannelSetList.token": "Token ID",
"ChannelSetModal.bookmark": "Starred",
@@ -328,13 +327,13 @@
"ChannelSetModal.channels": "Collection channels",
"ChannelSetModal.closeButton": "Exit without saving",
"ChannelSetModal.collectionErrorText": "This collection does not exist",
- "ChannelSetModal.createButton": "Create",
+ "ChannelSetModal.createButton": "ą¤¬ą¤Øą¤¾ą¤ą¤",
"ChannelSetModal.creatingChannelSet": "New collection",
"ChannelSetModal.edit": "My Channels",
- "ChannelSetModal.finish": "Finish",
+ "ChannelSetModal.finish": "ą¤øą¤®ą¤¾ą¤Ŗą„ą¤¤",
"ChannelSetModal.public": "Public",
"ChannelSetModal.publishedChannelsOnlyText": "Only published channels are available for selection",
- "ChannelSetModal.removeText": "Remove",
+ "ChannelSetModal.removeText": "ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤",
"ChannelSetModal.saveButton": "Save and close",
"ChannelSetModal.selectChannelsHeader": "Select channels",
"ChannelSetModal.titleLabel": "Collection name",
@@ -355,7 +354,7 @@
"ChannelSharing.inviteButton": "Send invitation",
"ChannelSharing.inviteSubheading": "Invite collaborators",
"ChannelSharing.validEmailMessage": "Please enter a valid email",
- "ChannelSharingTable.cancelButton": "Cancel",
+ "ChannelSharingTable.cancelButton": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"ChannelSharingTable.currentUserText": "{first_name} {last_name} (you)",
"ChannelSharingTable.deleteInvitation": "Delete invitation",
"ChannelSharingTable.deleteInvitationConfirm": "Delete invitation",
@@ -373,7 +372,7 @@
"ChannelSharingTable.makeEditorHeader": "Grant edit permissions",
"ChannelSharingTable.makeEditorText": "Are you sure you would like to grant edit permissions to {first_name} {last_name}?",
"ChannelSharingTable.noUsersText": "No users found",
- "ChannelSharingTable.optionsDropdown": "Options",
+ "ChannelSharingTable.optionsDropdown": "ą¤µą¤æą¤ą¤²ą„ą¤Ŗ",
"ChannelSharingTable.removeViewer": "Revoke view permissions",
"ChannelSharingTable.removeViewerConfirm": "Yes, revoke",
"ChannelSharingTable.removeViewerHeader": "Revoke view permissions",
@@ -385,130 +384,138 @@
"ChannelStar.starred": "Added to starred channels",
"ChannelStar.unstar": "Remove from starred channels",
"ChannelStar.unstarred": "Removed from starred channels",
- "ChannelThumbnail.cancel": "Cancel",
+ "ChannelThumbnail.cancel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"ChannelThumbnail.crop": "Crop",
"ChannelThumbnail.croppingPrompt": "Drag image to reframe",
"ChannelThumbnail.defaultFilename": "File",
"ChannelThumbnail.noThumbnail": "No thumbnail",
- "ChannelThumbnail.remove": "Remove",
+ "ChannelThumbnail.remove": "ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤",
"ChannelThumbnail.retryUpload": "Retry upload",
- "ChannelThumbnail.save": "Save",
+ "ChannelThumbnail.save": "ą¤øą„ą¤µ ą¤ą¤°ą„ą¤",
"ChannelThumbnail.upload": "Upload image",
"ChannelThumbnail.uploadFailed": "Upload failed",
"ChannelThumbnail.uploadingThumbnail": "Uploading",
- "ChannelThumbnail.zoomIn": "Zoom in",
- "ChannelThumbnail.zoomOut": "Zoom out",
- "ChannelTokenModal.close": "Close",
+ "ChannelThumbnail.zoomIn": "ą¤ą¤¼ą„ą¤® ą¤ą¤Ø ą¤ą¤°ą„ą¤",
+ "ChannelThumbnail.zoomOut": "ą¤ą¤¼ą„ą¤® ą¤ą¤ą¤ ą¤ą¤°ą„ą¤",
+ "ChannelTokenModal.close": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
"ChannelTokenModal.copyTitle": "Copy channel token",
"ChannelTokenModal.copyTokenInstructions": "Paste this token into Kolibri to import this channel",
"Clipboard.backToClipboard": "Clipboard",
- "Clipboard.close": "Close",
+ "Clipboard.close": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
"Clipboard.copiedItemsToClipboard": "Copied in clipboard",
- "Clipboard.deleteSelectedButton": "Delete",
+ "Clipboard.deleteSelectedButton": "ą¤¹ą¤ą¤¾ą¤ą¤",
"Clipboard.duplicateSelectedButton": "Make a copy",
"Clipboard.emptyDefaultText": "Use the clipboard to copy resources and move them to other folders and channels",
"Clipboard.emptyDefaultTitle": "No resources in your clipboard",
"Clipboard.moveSelectedButton": "Move",
"Clipboard.removedFromClipboard": "Deleted from clipboard",
- "Clipboard.selectAll": "Select all",
- "CommonMetadataStrings.accessibility": "Accessibility",
- "CommonMetadataStrings.algebra": "Algebra",
- "CommonMetadataStrings.all": "All",
- "CommonMetadataStrings.allLevelsBasicSkills": "All levels -- basic skills",
- "CommonMetadataStrings.allLevelsWorkSkills": "All levels -- work skills",
+ "Clipboard.selectAll": "ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤",
+ "CommonMetadataStrings.accessibility": "ą¤Ŗą¤¹ą„ą¤ą¤ ą¤ą„ą¤·ą¤®ą¤¤ą¤¾",
+ "CommonMetadataStrings.algebra": "ą¤¬ą„ą¤ą¤ą¤£ą¤æą¤¤",
+ "CommonMetadataStrings.all": "ą¤øą¤ą„",
+ "CommonMetadataStrings.allContent": "Viewed in its entirety",
+ "CommonMetadataStrings.allLevelsBasicSkills": "ą¤øą¤ą„ ą¤øą„ą¤¤ą¤° -- ą¤®ą„ą¤²ą¤ą„ą¤¤ ą¤ą„ą¤¶ą¤²",
+ "CommonMetadataStrings.allLevelsWorkSkills": "ą¤øą¤ą„ ą¤øą„ą¤¤ą¤° -- ą¤ą¤¾ą¤°ą„ą¤Æ ą¤ą„ą¤¶ą¤²",
"CommonMetadataStrings.altText": "Includes alternative text descriptions for images",
- "CommonMetadataStrings.anthropology": "Anthropology",
- "CommonMetadataStrings.arithmetic": "Arithmetic",
- "CommonMetadataStrings.arts": "Arts",
- "CommonMetadataStrings.astronomy": "Astronomy",
+ "CommonMetadataStrings.anthropology": "ą¤ą¤ą¤„ą„ą¤°ą„ą¤Ŗą„ą¤²ą„ą¤ą„",
+ "CommonMetadataStrings.arithmetic": "ą¤
ą¤ą¤ą¤ą¤£ą¤æą¤¤",
+ "CommonMetadataStrings.arts": "ą¤ą¤²ą¤¾",
+ "CommonMetadataStrings.astronomy": "ą¤ą¤ą„ą¤² ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø",
"CommonMetadataStrings.audioDescription": "Includes audio descriptions",
- "CommonMetadataStrings.basicSkills": "Basic skills",
- "CommonMetadataStrings.biology": "Biology",
- "CommonMetadataStrings.browseChannel": "Browse channel",
- "CommonMetadataStrings.calculus": "Calculus",
+ "CommonMetadataStrings.basicSkills": "ą¤®ą„ą¤²ą¤ą„ą¤¤ ą¤ą„ą¤¶ą¤²",
+ "CommonMetadataStrings.biology": "ą¤ą„ą¤µ ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø",
+ "CommonMetadataStrings.browseChannel": "ą¤ą„ą¤Øą¤² ą¤¬ą„ą¤°ą¤¾ą¤ą¤ą¤¼ ą¤ą¤°ą„ą¤",
+ "CommonMetadataStrings.calculus": "ą¤ą„ą¤²ą„ą¤ą„ą¤²ą¤ø",
"CommonMetadataStrings.captionsSubtitles": "Includes captions or subtitles",
"CommonMetadataStrings.category": "Category",
- "CommonMetadataStrings.chemistry": "Chemistry",
- "CommonMetadataStrings.civicEducation": "Civic education",
+ "CommonMetadataStrings.chemistry": "ą¤°ą¤øą¤¾ą¤Æą¤Ø ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø",
+ "CommonMetadataStrings.civicEducation": "ą¤Øą¤¾ą¤ą¤°ą¤æą¤ ą¤¶ą¤¾ą¤øą„ą¤¤ą„ą¤°",
+ "CommonMetadataStrings.completeDuration": "When time spent is equal to duration",
"CommonMetadataStrings.completion": "Completion",
- "CommonMetadataStrings.computerScience": "Computer science",
- "CommonMetadataStrings.create": "Create",
- "CommonMetadataStrings.currentEvents": "Current events",
- "CommonMetadataStrings.dailyLife": "Daily life",
- "CommonMetadataStrings.dance": "Dance",
- "CommonMetadataStrings.digitalLiteracy": "Digital literacy",
- "CommonMetadataStrings.diversity": "Diversity",
- "CommonMetadataStrings.drama": "Drama",
+ "CommonMetadataStrings.computerScience": "ą¤ą¤®ą„ą¤Ŗą„ą¤Æą„ą¤ą¤° ą¤øą¤¾ą¤ą¤ą¤ø",
+ "CommonMetadataStrings.create": "ą¤¬ą¤Øą¤¾ą¤ą¤",
+ "CommonMetadataStrings.currentEvents": "ą¤µą¤°ą„ą¤¤ą¤®ą¤¾ą¤Ø ą¤ą¤ą¤Øą¤¾ą¤ą¤",
+ "CommonMetadataStrings.dailyLife": "ą¤¦ą„ą¤Øą¤æą¤ ą¤ą„ą¤µą¤Ø",
+ "CommonMetadataStrings.dance": "ą¤Øą„ą¤¤ą„ą¤Æ",
+ "CommonMetadataStrings.determinedByResource": "Determined by the resource",
+ "CommonMetadataStrings.digitalLiteracy": "ą¤”ą¤æą¤ą¤æą¤ą¤² ą¤øą¤¾ą¤ą„ą¤·ą¤°ą¤¤ą¤¾",
+ "CommonMetadataStrings.diversity": "ą¤µą¤æą¤µą¤æą¤§ą¤¤ą¤¾",
+ "CommonMetadataStrings.drama": "ą¤Øą¤¾ą¤ą¤",
"CommonMetadataStrings.duration": "Duration",
- "CommonMetadataStrings.earthScience": "Earth science",
- "CommonMetadataStrings.entrepreneurship": "Entrepreneurship",
- "CommonMetadataStrings.environment": "Environment",
- "CommonMetadataStrings.explore": "Explore",
- "CommonMetadataStrings.financialLiteracy": "Financial literacy",
- "CommonMetadataStrings.forBeginners": "For beginners",
- "CommonMetadataStrings.forTeachers": "For teachers",
- "CommonMetadataStrings.geometry": "Geometry",
- "CommonMetadataStrings.guides": "Guides",
+ "CommonMetadataStrings.earthScience": "ą¤Ŗą„ą¤„ą„ą¤µą„ ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø",
+ "CommonMetadataStrings.entrepreneurship": "ą¤ą¤¦ą„ą¤Æą¤®ą¤æą¤¤ą¤¾",
+ "CommonMetadataStrings.environment": "ą¤Ŗą¤°ą„ą¤Æą¤¾ą¤µą¤°ą¤£",
+ "CommonMetadataStrings.exactTime": "Time to complete",
+ "CommonMetadataStrings.explore": "ą¤
ą¤Øą„ą¤µą„ą¤·ą¤£ ą¤ą¤°ą„ą¤",
+ "CommonMetadataStrings.financialLiteracy": "ą¤µą¤æą¤¤ą„ą¤¤ą„ą¤Æ ą¤øą¤¾ą¤ą„ą¤·ą¤°ą¤¤ą¤¾",
+ "CommonMetadataStrings.forBeginners": "ą¤Øą„ą¤øą¤æą¤ą¤æą¤Æą„ą¤ ą¤ą„ ą¤²ą¤æą¤",
+ "CommonMetadataStrings.forTeachers": "ą¤¶ą¤æą¤ą„ą¤·ą¤ą„ą¤ ą¤ą„ ą¤²ą¤æą¤",
+ "CommonMetadataStrings.geometry": "ą¤°ą„ą¤ą¤¾ą¤ą¤£ą¤æą¤¤",
+ "CommonMetadataStrings.goal": "When goal is met",
+ "CommonMetadataStrings.guides": "ą¤ą¤¾ą¤ą¤”ą„ą¤ø",
"CommonMetadataStrings.highContrast": "Includes high contrast text for learners with low vision",
- "CommonMetadataStrings.history": "History",
- "CommonMetadataStrings.industryAndSectorSpecific": "Industry and sector specific",
- "CommonMetadataStrings.languageLearning": "Language learning",
+ "CommonMetadataStrings.history": "ą¤ą¤¤ą¤æą¤¹ą¤¾ą¤ø",
+ "CommonMetadataStrings.industryAndSectorSpecific": "ą¤ą¤¦ą„ą¤Æą„ą¤ ą¤ą¤° ą¤ą„ą¤·ą„ą¤¤ą„ą¤° ą¤µą¤æą¤¶ą¤æą¤·ą„ą¤",
+ "CommonMetadataStrings.languageLearning": "ą¤ą¤¾ą¤·ą¤¾ ą¤øą„ą¤ą¤Øą¤¾",
"CommonMetadataStrings.learningActivity": "Learning Activity",
- "CommonMetadataStrings.learningSkills": "Learning skills",
- "CommonMetadataStrings.lessonPlans": "Lesson plans",
- "CommonMetadataStrings.level": "Level",
- "CommonMetadataStrings.listen": "Listen",
- "CommonMetadataStrings.literacy": "Literacy",
- "CommonMetadataStrings.literature": "Literature",
- "CommonMetadataStrings.logicAndCriticalThinking": "Logic and critical thinking",
- "CommonMetadataStrings.longActivity": "Long activity",
- "CommonMetadataStrings.lowerPrimary": "Lower primary",
- "CommonMetadataStrings.lowerSecondary": "Lower secondary",
- "CommonMetadataStrings.mathematics": "Mathematics",
- "CommonMetadataStrings.mechanicalEngineering": "Mechanical engineering",
- "CommonMetadataStrings.mediaLiteracy": "Media literacy",
- "CommonMetadataStrings.mentalHealth": "Mental health",
- "CommonMetadataStrings.music": "Music",
+ "CommonMetadataStrings.learningSkills": "ą¤øą„ą¤ą¤Øą„ ą¤ą„ ą¤ą„ą¤¶ą¤²",
+ "CommonMetadataStrings.lessonPlans": "ą¤Ŗą¤¾ą¤ ą¤Æą„ą¤ą¤Øą¤¾ą¤ą¤",
+ "CommonMetadataStrings.level": "ą¤øą„ą¤¤ą¤°",
+ "CommonMetadataStrings.listen": "ą¤øą„ą¤Øą„ą¤",
+ "CommonMetadataStrings.literacy": "ą¤øą¤¾ą¤ą„ą¤·ą¤°ą¤¤ą¤¾",
+ "CommonMetadataStrings.literature": "ą¤øą¤¾ą¤¹ą¤æą¤¤ą„ą¤Æ",
+ "CommonMetadataStrings.logicAndCriticalThinking": "ą¤¤ą¤°ą„ą¤ ą¤ą¤° ą¤ą¤²ą„ą¤ą¤Øą¤¾ą¤¤ą„ą¤®ą¤ ą¤øą„ą¤",
+ "CommonMetadataStrings.longActivity": "ą¤²ą¤ą¤¬ą„ ą¤ą¤¤ą¤æą¤µą¤æą¤§ą¤æ",
+ "CommonMetadataStrings.lowerPrimary": "ą¤Ŗą„ą¤°ą„ą¤µ ą¤Ŗą„ą¤°ą¤¾ą¤„ą¤®ą¤æą¤",
+ "CommonMetadataStrings.lowerSecondary": "ą¤Ŗą„ą¤°ą„ą¤µ ą¤®ą¤¾ą¤§ą„ą¤Æą¤®ą¤æą¤",
+ "CommonMetadataStrings.masteryMofN": "Goal: {m} out of {n}",
+ "CommonMetadataStrings.mathematics": "ą¤ą¤£ą¤æą¤¤",
+ "CommonMetadataStrings.mechanicalEngineering": "ą¤®ą„ą¤ą„ą¤Øą¤æą¤ą¤² ą¤ą¤ą¤ą„ą¤Øą¤æą¤Æą¤°ą¤æą¤ą¤",
+ "CommonMetadataStrings.mediaLiteracy": "ą¤®ą„ą¤”ą¤æą¤Æą¤¾ ą¤øą¤¾ą¤ą„ą¤·ą¤°ą¤¤ą¤¾",
+ "CommonMetadataStrings.mentalHealth": "ą¤®ą¤¾ą¤Øą¤øą¤æą¤ ą¤øą„ą¤µą¤¾ą¤øą„ą¤„ą„ą¤Æ",
+ "CommonMetadataStrings.music": "ą¤øą¤ą¤ą„ą¤¤",
"CommonMetadataStrings.needsInternet": "Internet connection",
"CommonMetadataStrings.needsMaterials": "Other supplies",
- "CommonMetadataStrings.numeracy": "Numeracy",
+ "CommonMetadataStrings.numeracy": "ą¤
ą¤ą¤ą„ą¤ ą¤ą¤¾ ą¤ą„ą¤ą¤¾ą¤Ø",
"CommonMetadataStrings.peers": "Working with peers",
- "CommonMetadataStrings.physics": "Physics",
- "CommonMetadataStrings.politicalScience": "Political science",
- "CommonMetadataStrings.practice": "Practice",
- "CommonMetadataStrings.preschool": "Preschool",
- "CommonMetadataStrings.professionalSkills": "Professional skills",
- "CommonMetadataStrings.programming": "Programming",
- "CommonMetadataStrings.publicHealth": "Public health",
- "CommonMetadataStrings.read": "Read",
- "CommonMetadataStrings.readReference": "Reference",
- "CommonMetadataStrings.readingAndWriting": "Reading and writing",
- "CommonMetadataStrings.readingComprehension": "Reading comprehension",
- "CommonMetadataStrings.reflect": "Reflect",
- "CommonMetadataStrings.school": "School",
- "CommonMetadataStrings.sciences": "Sciences",
- "CommonMetadataStrings.shortActivity": "Short activity",
+ "CommonMetadataStrings.physics": "ą¤ą„ą¤¤ą¤æą¤ ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø",
+ "CommonMetadataStrings.politicalScience": "ą¤°ą¤¾ą¤ą¤Øą„ą¤¤ą¤æą¤¶ą¤¾ą¤øą„ą¤¤ą„ą¤°",
+ "CommonMetadataStrings.practice": "ą¤
ą¤ą„ą¤Æą¤¾ą¤ø",
+ "CommonMetadataStrings.practiceQuiz": "Practice quiz",
+ "CommonMetadataStrings.preschool": "ą¤¬ą¤¾ą¤²ą¤µą¤¾ą¤”ą¤¼ą„",
+ "CommonMetadataStrings.professionalSkills": "ą¤Ŗą„ą¤¶ą„ą¤µą¤° ą¤ą„ą¤¶ą¤²",
+ "CommonMetadataStrings.programming": "ą¤Ŗą„ą¤°ą„ą¤ą„ą¤°ą¤¾ą¤®ą¤æą¤ą¤",
+ "CommonMetadataStrings.publicHealth": "ą¤²ą„ą¤ ą¤øą„ą¤µą¤¾ą¤øą„ą¤„ą„ą¤Æ",
+ "CommonMetadataStrings.read": "ą¤Ŗą¤¢ą¤¼ą„ą¤",
+ "CommonMetadataStrings.readReference": "ą¤øą¤ą¤¦ą¤°ą„ą¤",
+ "CommonMetadataStrings.readingAndWriting": "ą¤Ŗą¤¢ą¤¼ą¤Øą¤¾ ą¤ą¤° ą¤²ą¤æą¤ą¤Øą¤¾",
+ "CommonMetadataStrings.readingComprehension": "ą¤Øą¤æą¤¬ą¤ą¤§ ą¤Ŗą¤¢ą¤¼ą¤Øą¤¾",
+ "CommonMetadataStrings.reference": "Reference material",
+ "CommonMetadataStrings.reflect": "ą¤Ŗą„ą¤°ą¤¤ą¤æą¤¬ą¤æą¤ą¤¬ą¤æą¤¤",
+ "CommonMetadataStrings.school": "ą¤µą¤æą¤¦ą„ą¤Æą¤¾ą¤²ą¤Æ",
+ "CommonMetadataStrings.sciences": "ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø",
+ "CommonMetadataStrings.shortActivity": "ą¤ą„ą¤ą„ ą¤ą¤¤ą¤æą¤µą¤æą¤§ą¤æ",
"CommonMetadataStrings.signLanguage": "Includes sign language captions",
- "CommonMetadataStrings.skillsTraining": "Skills training",
- "CommonMetadataStrings.socialSciences": "Social sciences",
- "CommonMetadataStrings.sociology": "Sociology",
+ "CommonMetadataStrings.skillsTraining": "ą¤ą„ą¤¶ą¤² ą¤Ŗą„ą¤°ą¤¶ą¤æą¤ą„ą¤·ą¤£",
+ "CommonMetadataStrings.socialSciences": "ą¤øą¤¾ą¤®ą¤¾ą¤ą¤æą¤ ą¤µą¤æą¤ą„ą¤ą¤¾ą¤Ø",
+ "CommonMetadataStrings.sociology": "ą¤øą¤®ą¤¾ą¤ ą¤¶ą¤¾ą¤øą„ą¤¤ą„ą¤°",
"CommonMetadataStrings.softwareTools": "Other software tools",
- "CommonMetadataStrings.softwareToolsAndTraining": "Software tools and training",
- "CommonMetadataStrings.specializedProfessionalTraining": "Specialized professional training",
- "CommonMetadataStrings.statistics": "Statistics",
- "CommonMetadataStrings.taggedPdf": "Tagged PDF",
+ "CommonMetadataStrings.softwareToolsAndTraining": "ą¤øą„ą¤«ą„ą¤ą¤µą„ą¤Æą¤° ą¤øą¤¾ą¤§ą¤Ø ą¤ą¤° ą¤Ŗą„ą¤°ą¤¶ą¤æą¤ą„ą¤·ą¤£",
+ "CommonMetadataStrings.specializedProfessionalTraining": "ą¤µą¤æą¤¶ą„ą¤·ą¤ą„ą¤ą¤¤ą¤¾ ą¤Ŗą„ą¤¶ą„ą¤µą¤° ą¤Ŗą„ą¤°ą¤¶ą¤æą¤ą„ą¤·ą¤£",
+ "CommonMetadataStrings.statistics": "ą¤øą¤¾ą¤ą¤ą„ą¤Æą¤æą¤ą„",
+ "CommonMetadataStrings.taggedPdf": "ą¤ą„ą¤ ą¤ą„ ą¤ą¤Æą„ PDF ą¤«ą¤¾ą¤ą¤²",
"CommonMetadataStrings.teacher": "Working with a teacher",
- "CommonMetadataStrings.technicalAndVocationalTraining": "Technical and vocational training",
- "CommonMetadataStrings.tertiary": "Tertiary",
+ "CommonMetadataStrings.technicalAndVocationalTraining": "ą¤¤ą¤ą¤Øą„ą¤ą„ ą¤ą¤° ą¤µą„ą¤Æą¤¾ą¤µą¤øą¤¾ą¤Æą¤æą¤ ą¤Ŗą„ą¤°ą¤¶ą¤æą¤ą„ą¤·ą¤£",
+ "CommonMetadataStrings.tertiary": "ą¤¤ą„ą¤¤ą„ą¤Æ",
"CommonMetadataStrings.toUseWithPaperAndPencil": "Paper and pencil",
- "CommonMetadataStrings.topicLabel": "Folder",
- "CommonMetadataStrings.upperPrimary": "Upper primary",
- "CommonMetadataStrings.upperSecondary": "Upper secondary",
- "CommonMetadataStrings.visualArt": "Visual art",
- "CommonMetadataStrings.watch": "Watch",
- "CommonMetadataStrings.webDesign": "Web design",
- "CommonMetadataStrings.work": "Work",
- "CommonMetadataStrings.writing": "Writing",
+ "CommonMetadataStrings.topicLabel": "ą¤«ą„ą¤²ą„ą¤”ą¤°",
+ "CommonMetadataStrings.upperPrimary": "ą¤ą¤ą„ą¤ ą¤Ŗą„ą¤°ą¤¾ą¤„ą¤®ą¤æą¤",
+ "CommonMetadataStrings.upperSecondary": "ą¤ą¤ą„ą¤ ą¤®ą¤¾ą¤§ą„ą¤Æą¤®ą¤æą¤",
+ "CommonMetadataStrings.visualArt": "ą¤¦ą„ą¤¶ą„ą¤Æ ą¤ą¤²ą¤¾",
+ "CommonMetadataStrings.watch": "ą¤¦ą„ą¤ą„ą¤",
+ "CommonMetadataStrings.webDesign": "ą¤µą„ą¤¬ ą¤”ą¤æą¤ą¤¾ą¤ą¤Ø",
+ "CommonMetadataStrings.work": "ą¤ą¤¾ą¤°ą„ą¤Æ",
+ "CommonMetadataStrings.writing": "ą¤²ą„ą¤ą¤Ø",
"CommunityStandardsModal.communityStandardsHeader": "Community Standards",
"CommunityStandardsModal.coreValuesLink": "Learn more about Learning Equality's core values",
"CommunityStandardsModal.description": "Learning Equality is a nonprofit organization dedicated to enabling equitable access to quality educational experiences. Along with our statement of Core Values, these Community Standards are intended to foster a supportive and inclusive environment for our users.",
@@ -523,43 +530,37 @@
"CommunityStandardsModal.studioItem3": "Sharing. Creating and publishing new channels with what you find, either to share with your own implementations privately or to share with others on Kolibri Studio.",
"CommunityStandardsModal.studioItem4": "Modifying & Creating. Adding your own assessment exercises to any existing materials",
"CommunityStandardsModal.studioItem5": "Hosting. Uploading your own materials (limited to materials you know are appropriately licensed to do so) from a local hard drive or other locations on the internet",
- "CompletionOptions.allContent": "Viewed in its entirety",
- "CompletionOptions.completeDuration": "When time spent is equal to duration",
- "CompletionOptions.determinedByResource": "Determined by the resource",
- "CompletionOptions.exactTime": "Time to complete",
- "CompletionOptions.goal": "When goal is met",
- "CompletionOptions.practiceQuiz": "Practice quiz",
- "CompletionOptions.reference": "Reference material",
+ "CompletionOptions.learnersCanMarkComplete": "Allow learners to mark as complete",
"CompletionOptions.referenceHint": "Progress will not be tracked on reference material unless learners mark it as complete",
"ConstantStrings.All Rights Reserved": "All Rights Reserved",
"ConstantStrings.All Rights Reserved_description": "The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty.",
- "ConstantStrings.CC BY": "CC BY",
- "ConstantStrings.CC BY-NC": "CC BY-NC",
- "ConstantStrings.CC BY-NC-ND": "CC BY-NC-ND",
+ "ConstantStrings.CC BY": "ą¤Øą¤æą¤®ą„ą¤Ø ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC",
+ "ConstantStrings.CC BY-NC": "NC ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC",
+ "ConstantStrings.CC BY-NC-ND": "NC-ND ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC",
"ConstantStrings.CC BY-NC-ND_description": "The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially.",
- "ConstantStrings.CC BY-NC-SA": "CC BY-NC-SA",
+ "ConstantStrings.CC BY-NC-SA": "NC-SA ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC",
"ConstantStrings.CC BY-NC-SA_description": "The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms.",
"ConstantStrings.CC BY-NC_description": "The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms.",
- "ConstantStrings.CC BY-ND": "CC BY-ND",
+ "ConstantStrings.CC BY-ND": "ND ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC",
"ConstantStrings.CC BY-ND_description": "The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you.",
- "ConstantStrings.CC BY-SA": "CC BY-SA",
+ "ConstantStrings.CC BY-SA": "SA ą¤¦ą„ą¤µą¤¾ą¤°ą¤¾ CC",
"ConstantStrings.CC BY-SA_description": "The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to \"copyleft\" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects.",
"ConstantStrings.CC BY_description": "The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials.",
"ConstantStrings.Public Domain": "Public Domain",
"ConstantStrings.Public Domain_description": "Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights.",
"ConstantStrings.Special Permissions": "Special Permissions",
"ConstantStrings.Special Permissions_description": "Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails.",
- "ConstantStrings.audio": "Audio",
+ "ConstantStrings.audio": "ą¤ą¤”ą¤æą¤Æą„",
"ConstantStrings.audio_thumbnail": "Thumbnail",
"ConstantStrings.bookmark": "Starred",
- "ConstantStrings.coach": "Coaches",
+ "ConstantStrings.coach": "ą¤ą„ą¤",
"ConstantStrings.do_all": "Goal: 100% correct",
"ConstantStrings.do_all_description": "Learner must answer all questions in the exercise correctly (not recommended for long exercises)",
- "ConstantStrings.document": "Document",
+ "ConstantStrings.document": "ą¤¦ą¤øą„ą¤¤ą¤¾ą¤µą„ą¤ą¤¼",
"ConstantStrings.document_thumbnail": "Thumbnail",
"ConstantStrings.edit": "My channels",
"ConstantStrings.epub": "EPub document",
- "ConstantStrings.exercise": "Exercise",
+ "ConstantStrings.exercise": "ą¤
ą¤ą„ą¤Æą¤¾ą¤ø",
"ConstantStrings.exercise_thumbnail": "Thumbnail",
"ConstantStrings.firstCopy": "Copy of {title}",
"ConstantStrings.gif": "GIF image",
@@ -594,32 +595,32 @@
"ConstantStrings.png": "PNG image",
"ConstantStrings.public": "Content library",
"ConstantStrings.single_selection": "Single choice",
- "ConstantStrings.slideshow": "Slideshow",
+ "ConstantStrings.slideshow": "ą¤øą„ą¤²ą¤¾ą¤ą¤”ą¤¶ą„",
"ConstantStrings.svg": "SVG image",
- "ConstantStrings.topic": "Folder",
+ "ConstantStrings.topic": "ą¤«ą„ą¤²ą„ą¤”ą¤°",
"ConstantStrings.topic_thumbnail": "Thumbnail",
"ConstantStrings.true_false": "True/False",
"ConstantStrings.unknown_question": "Unknown question type",
- "ConstantStrings.video": "Video",
- "ConstantStrings.video_subtitle": "Captions",
+ "ConstantStrings.video": "ą¤µą„ą¤”ą¤æą¤Æą„",
+ "ConstantStrings.video_subtitle": "ą¤ą„ą¤Ŗą„ą¤¶ą¤Ø",
"ConstantStrings.video_thumbnail": "Thumbnail",
"ConstantStrings.view": "View-only",
"ConstantStrings.vtt": "VTT caption",
"ConstantStrings.webm": "WEBM video",
"ConstantStrings.zip": "HTML5 zip",
"ContentDefaults.aggregator": "Aggregator",
- "ContentDefaults.author": "Author",
- "ContentDefaults.copyrightHolder": "Copyright holder",
+ "ContentDefaults.author": "ą¤²ą„ą¤ą¤",
+ "ContentDefaults.copyrightHolder": "ą¤ą„ą¤Ŗą„ą¤°ą¤¾ą¤ą¤ ą¤§ą¤¾ą¤°ą¤",
"ContentDefaults.defaultsSubTitle": "New resources will be automatically given these values",
"ContentDefaults.defaultsTitle": "Default copyright settings for new resources (optional)",
- "ContentDefaults.documents": "Documents",
+ "ContentDefaults.documents": "ą¤¦ą¤øą„ą¤¤ą¤¾ą¤µą„ą¤ą¤¼",
"ContentDefaults.html5": "HTML5 apps",
- "ContentDefaults.license": "License",
+ "ContentDefaults.license": "ą¤²ą¤¾ą¤ą¤øą„ą¤ą¤ø",
"ContentDefaults.licenseDescription": "License description",
"ContentDefaults.noLicense": "No license selected",
"ContentDefaults.provider": "Provider",
"ContentDefaults.thumbnailsTitle": "Automatically generate thumbnails for the following resource types",
- "ContentDefaults.videos": "Videos",
+ "ContentDefaults.videos": "ą¤µą„ą¤”ą¤æą¤Æą„",
"ContentNodeChangedIcon.containsNew": "Contains unpublished resources",
"ContentNodeChangedIcon.containsNewAndUpdated": "Contains unpublished resources and changes",
"ContentNodeChangedIcon.containsUpdated": "Contains unpublished changes",
@@ -627,87 +628,93 @@
"ContentNodeChangedIcon.isNewTopic": "Unpublished folder",
"ContentNodeChangedIcon.isUpdatedResource": "Updated since last publish",
"ContentNodeChangedIcon.isUpdatedTopic": "Folder has been updated since last publish",
- "ContentNodeEditListItem.optionsTooltip": "Options",
- "ContentNodeIcon.audio": "Audio",
- "ContentNodeIcon.document": "Document",
- "ContentNodeIcon.exercise": "Exercise",
+ "ContentNodeCopyTaskProgress.copyErrorTopic": "Some resources failed to copy",
+ "ContentNodeEditListItem.copiedSnackbar": "Copy operation complete",
+ "ContentNodeEditListItem.creatingCopies": "Copying...",
+ "ContentNodeEditListItem.optionsTooltip": "ą¤µą¤æą¤ą¤²ą„ą¤Ŗ",
+ "ContentNodeEditListItem.removeNode": "Remove",
+ "ContentNodeEditListItem.retryCopy": "Retry",
+ "ContentNodeEditListItem.undo": "Undo",
+ "ContentNodeIcon.audio": "ą¤ą¤”ą¤æą¤Æą„",
+ "ContentNodeIcon.document": "ą¤¦ą¤øą„ą¤¤ą¤¾ą¤µą„ą¤ą¤¼",
+ "ContentNodeIcon.exercise": "ą¤
ą¤ą„ą¤Æą¤¾ą¤ø",
"ContentNodeIcon.html5": "HTML5 App",
- "ContentNodeIcon.slideshow": "Slideshow",
- "ContentNodeIcon.topic": "Folder",
+ "ContentNodeIcon.slideshow": "ą¤øą„ą¤²ą¤¾ą¤ą¤”ą¤¶ą„",
+ "ContentNodeIcon.topic": "ą¤«ą„ą¤²ą„ą¤”ą¤°",
"ContentNodeIcon.unsupported": "Unsupported",
- "ContentNodeIcon.video": "Video",
- "ContentNodeLearningActivityIcon.multipleLearningActivities": "Multiple learning activities",
- "ContentNodeLearningActivityIcon.topic": "Folder",
+ "ContentNodeIcon.video": "ą¤µą„ą¤”ą¤æą¤Æą„",
+ "ContentNodeLearningActivityIcon.multipleLearningActivities": "ą¤øą„ą¤ą¤Øą„ ą¤ą„ ą¤
ą¤Øą„ą¤ ą¤ą¤¤ą¤æą¤µą¤æą¤§ą¤æą¤Æą¤¾ą¤",
"ContentNodeListItem.coachTooltip": "Resource for coaches",
+ "ContentNodeListItem.copyingError": "Copy failed.",
"ContentNodeListItem.copyingTask": "Copying",
"ContentNodeListItem.hasCoachTooltip": "{value, number, integer} {value, plural, one {resource for coaches} other {resources for coaches}}",
"ContentNodeListItem.openTopic": "Open folder",
- "ContentNodeListItem.questions": "{value, number, integer} {value, plural, one {question} other {questions}}",
- "ContentNodeListItem.resources": "{value, number, integer} {value, plural, one {resource} other {resources}}",
+ "ContentNodeListItem.questions": "{value, number, integer} {value, plural, one {ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø} other {ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø}}",
+ "ContentNodeListItem.resources": "{value, number, integer} {value, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø}}",
"ContentNodeOptions.copiedItemsToClipboard": "Copied in clipboard",
"ContentNodeOptions.copiedSnackbar": "Copy operation complete",
- "ContentNodeOptions.copiedToClipboardSnackbar": "Copied to clipboard",
- "ContentNodeOptions.copyToClipboard": "Copy to clipboard",
+ "ContentNodeOptions.copiedToClipboardSnackbar": "ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤° ą¤¦ą¤æą¤Æą¤¾ ą¤ą¤Æą¤¾",
+ "ContentNodeOptions.copyToClipboard": "ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤",
"ContentNodeOptions.creatingCopies": "Copying...",
- "ContentNodeOptions.editDetails": "Edit details",
+ "ContentNodeOptions.editDetails": "ą¤µą¤æą¤µą¤°ą¤£ ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤",
"ContentNodeOptions.editTopicDetails": "Edit folder details",
"ContentNodeOptions.goToOriginalLocation": "Go to original location",
"ContentNodeOptions.makeACopy": "Make a copy",
"ContentNodeOptions.move": "Move",
"ContentNodeOptions.moveTo": "Move to...",
"ContentNodeOptions.newSubtopic": "New folder",
- "ContentNodeOptions.remove": "Remove",
+ "ContentNodeOptions.remove": "Delete",
"ContentNodeOptions.removedFromClipboard": "Deleted from clipboard",
"ContentNodeOptions.removedItems": "Sent to trash",
- "ContentNodeOptions.undo": "Undo",
+ "ContentNodeOptions.undo": "ą¤
ą¤ą„ą¤¤ ą¤ą¤°ą„ą¤",
"ContentNodeOptions.viewDetails": "View details",
"ContentNodeStrings.untitled": "Untitled",
- "ContentNodeThumbnail.cancel": "Cancel",
+ "ContentNodeThumbnail.cancel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"ContentNodeThumbnail.crop": "Crop",
"ContentNodeThumbnail.croppingPrompt": "Drag image to reframe",
"ContentNodeThumbnail.defaultFilename": "File",
"ContentNodeThumbnail.generate": "Generate from file",
"ContentNodeThumbnail.generatingThumbnail": "Generating from file",
"ContentNodeThumbnail.noThumbnail": "No thumbnail",
- "ContentNodeThumbnail.remove": "Remove",
+ "ContentNodeThumbnail.remove": "ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤",
"ContentNodeThumbnail.retryUpload": "Retry upload",
- "ContentNodeThumbnail.save": "Save",
+ "ContentNodeThumbnail.save": "ą¤øą„ą¤µ ą¤ą¤°ą„ą¤",
"ContentNodeThumbnail.upload": "Upload image",
"ContentNodeThumbnail.uploadFailed": "Upload failed",
"ContentNodeThumbnail.uploadingThumbnail": "Uploading",
- "ContentNodeThumbnail.zoomIn": "Zoom in",
- "ContentNodeThumbnail.zoomOut": "Zoom out",
+ "ContentNodeThumbnail.zoomIn": "ą¤ą¤¼ą„ą¤® ą¤ą¤Ø ą¤ą¤°ą„ą¤",
+ "ContentNodeThumbnail.zoomOut": "ą¤ą¤¼ą„ą¤® ą¤ą¤ą¤ ą¤ą¤°ą„ą¤",
"ContentNodeValidator.allIncompleteDescendantsText": "{count, plural, one {{count, number, integer} resource is incomplete and cannot be published} other {All {count, number, integer} resources are incomplete and cannot be published}}",
"ContentNodeValidator.incompleteDescendantsText": "{count, number, integer} {count, plural, one {resource is incomplete} other {resources are incomplete}}",
"ContentNodeValidator.incompleteText": "Incomplete",
"ContentNodeValidator.missingTitle": "Missing title",
"ContentRenderer.noFileText": "Select a file to preview",
"ContentRenderer.previewNotSupported": "Preview unavailable",
- "ContentTreeList.allChannelsLabel": "Channels",
+ "ContentTreeList.allChannelsLabel": "ą¤ą„ą¤Øą¤²",
"ContentTreeList.noResourcesOrTopics": "There are no resources or folders here",
- "ContentTreeList.selectAllAction": "Select all",
+ "ContentTreeList.selectAllAction": "ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤",
"CopyToken.copiedTokenId": "Token copied",
"CopyToken.copyFailed": "Copy failed",
"CopyToken.copyPrompt": "Copy token to import channel into Kolibri",
"CountryField.locationLabel": "Select all that apply",
"CountryField.locationRequiredMessage": "Field is required",
"CountryField.noCountriesFound": "No countries found",
- "Create.ToSCheck": "I have read and agree to the terms of service",
- "Create.ToSRequiredMessage": "Please accept our terms of service",
- "Create.backToLoginButton": "Sign in",
+ "Create.ToSRequiredMessage": "Please accept our terms of service and policy",
+ "Create.agreement": "I have read and agree to terms of service and the privacy policy",
+ "Create.backToLoginButton": "ą¤øą¤¾ą¤ą¤Ø ą¤ą¤Ø ą¤ą¤°ą„ą¤",
"Create.basicInformationHeader": "Basic information",
"Create.conferenceSourceOption": "Conference",
"Create.conferenceSourcePlaceholder": "Name of conference",
"Create.confirmPasswordLabel": "Confirm password",
"Create.contactMessage": "Questions or concerns? Please email us at content@learningequality.org",
"Create.conversationSourceOption": "Conversation with Learning Equality",
- "Create.createAnAccountTitle": "Create an account",
+ "Create.createAnAccountTitle": "ą¤
ą¤ą¤¾ą¤ą¤ą¤ ą¤¬ą¤Øą¤¾ą¤ą¤",
"Create.creatingExercisesUsageOption": "Creating exercises",
"Create.emailExistsMessage": "An account with this email already exists",
"Create.errorsMessage": "Please fix the errors below",
"Create.fieldRequiredMessage": "Field is required",
"Create.findingUsageOption": "Finding and adding additional content sources",
- "Create.finishButton": "Finish",
+ "Create.finishButton": "ą¤øą¤®ą¤¾ą¤Ŗą„ą¤¤",
"Create.firstNameLabel": "First name",
"Create.forumSourceOption": "Learning Equality community forum",
"Create.githubSourceOption": "Learning Equality GitHub",
@@ -721,11 +728,9 @@
"Create.otherSourcePlaceholder": "Please describe",
"Create.otherUsageOption": "Other",
"Create.otherUsagePlaceholder": "Please describe",
- "Create.passwordLabel": "Password",
+ "Create.passwordLabel": "ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤”",
"Create.passwordMatchMessage": "Passwords don't match",
"Create.personalDemoSourceOption": "Personal demo",
- "Create.privacyPolicyCheck": "I have read and agree to the privacy policy",
- "Create.privacyPolicyRequiredMessage": "Please accept our privacy policy",
"Create.registrationFailed": "There was an error registering your account. Please try again",
"Create.registrationFailedOffline": "You seem to be offline. Please connect to the internet to create an account.",
"Create.sequencingUsageOption": "Using prerequisites to put materials in a sequence",
@@ -738,34 +743,34 @@
"Create.storingUsagePlaceholder": "How much storage do you need?",
"Create.taggingUsageOption": "Tagging content sources for discovery",
"Create.usageLabel": "How do you plan on using Kolibri Studio (check all that apply)",
- "Create.viewPrivacyPolicyLink": "View privacy policy",
- "Create.viewToSLink": "View terms of service",
+ "Create.viewPrivacyPolicyLink": "View Privacy Policy",
+ "Create.viewToSLink": "View Terms of Service",
"Create.websiteSourceOption": "Learning Equality website",
"CurrentTopicView.COMFORTABLE_VIEW": "Comfortable view",
"CurrentTopicView.COMPACT_VIEW": "Compact view",
"CurrentTopicView.DEFAULT_VIEW": "Default view",
- "CurrentTopicView.addButton": "Add",
+ "CurrentTopicView.addButton": "ą¤ą„ą¤”ą¤¼ą„ą¤",
"CurrentTopicView.addExercise": "New exercise",
"CurrentTopicView.addTopic": "New folder",
"CurrentTopicView.copiedItems": "Copy operation complete",
- "CurrentTopicView.copiedItemsToClipboard": "Copied to clipboard",
- "CurrentTopicView.copySelectedButton": "Copy to clipboard",
- "CurrentTopicView.copyToClipboardButton": "Copy to clipboard",
+ "CurrentTopicView.copiedItemsToClipboard": "ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤° ą¤¦ą¤æą¤Æą¤¾ ą¤ą¤Æą¤¾",
+ "CurrentTopicView.copySelectedButton": "ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤",
+ "CurrentTopicView.copyToClipboardButton": "ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤",
"CurrentTopicView.creatingCopies": "Copying...",
- "CurrentTopicView.deleteSelectedButton": "Delete",
+ "CurrentTopicView.deleteSelectedButton": "ą¤¹ą¤ą¤¾ą¤ą¤",
"CurrentTopicView.duplicateSelectedButton": "Make a copy",
- "CurrentTopicView.editButton": "Edit",
- "CurrentTopicView.editSelectedButton": "Edit",
+ "CurrentTopicView.editButton": "ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)",
+ "CurrentTopicView.editSelectedButton": "ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)",
"CurrentTopicView.importFromChannels": "Import from channels",
"CurrentTopicView.moveSelectedButton": "Move",
- "CurrentTopicView.optionsButton": "Options",
+ "CurrentTopicView.optionsButton": "ą¤µą¤æą¤ą¤²ą„ą¤Ŗ",
"CurrentTopicView.removedItems": "Sent to trash",
- "CurrentTopicView.selectAllLabel": "Select all",
+ "CurrentTopicView.selectAllLabel": "ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤",
"CurrentTopicView.selectionCount": "{topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}",
- "CurrentTopicView.undo": "Undo",
+ "CurrentTopicView.undo": "ą¤
ą¤ą„ą¤¤ ą¤ą¤°ą„ą¤",
"CurrentTopicView.uploadFiles": "Upload files",
- "CurrentTopicView.viewModeTooltip": "View",
- "DeleteAccountForm.cancelButton": "Cancel",
+ "CurrentTopicView.viewModeTooltip": "ą¤¦ą„ą¤ą„ą¤",
+ "DeleteAccountForm.cancelButton": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"DeleteAccountForm.deleteAccountConfirmationPrompt": "Are you sure you want to permanently delete your account? This cannot be undone",
"DeleteAccountForm.deleteAccountEnterEmail": "Enter your email address to continue",
"DeleteAccountForm.deleteAccountLabel": "Delete account",
@@ -784,6 +789,7 @@
"Details.assessmentsIncludedText": "Assessments",
"Details.authorToolTip": "Person or organization who created this content",
"Details.authorsLabel": "Authors",
+ "Details.categoriesHeading": "Categories",
"Details.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
"Details.coachHeading": "Resources for coaches",
"Details.containsContentHeading": "Contains content from",
@@ -791,7 +797,8 @@
"Details.copyrightHoldersLabel": "Copyright holders",
"Details.creationHeading": "Created on",
"Details.currentVersionHeading": "Published version",
- "Details.languagesHeading": "Languages",
+ "Details.languagesHeading": "ą¤ą¤¾ą¤·ą¤¾ą¤ą¤",
+ "Details.levelsHeading": "Levels",
"Details.licensesLabel": "Licenses",
"Details.primaryLanguageHeading": "Primary language",
"Details.providerToolTip": "Organization that commissioned or is distributing the content",
@@ -804,23 +811,22 @@
"Details.sizeText": "{text} ({size})",
"Details.subtitlesHeading": "Captions and subtitles",
"Details.tagsHeading": "Common tags",
- "Details.tokenHeading": "Channel token",
+ "Details.tokenHeading": "ą¤ą„ą¤Øą¤² ą¤ą„ą¤ą¤Ø",
"Details.unpublishedText": "Unpublished",
"DetailsTabView.aggregatorLabel": "Aggregator",
"DetailsTabView.aggregatorToolTip": "Website or org hosting the content collection but not necessarily the creator or copyright holder",
"DetailsTabView.assessmentOptionsLabel": "Assessment options",
"DetailsTabView.audienceHeader": "Audience",
- "DetailsTabView.authorLabel": "Author",
+ "DetailsTabView.authorLabel": "ą¤²ą„ą¤ą¤",
"DetailsTabView.authorToolTip": "Person or organization who created this content",
"DetailsTabView.basicInfoHeader": "Basic information",
"DetailsTabView.completionLabel": "Completion",
- "DetailsTabView.copyrightHolderLabel": "Copyright holder",
- "DetailsTabView.descriptionLabel": "Description",
+ "DetailsTabView.copyrightHolderLabel": "ą¤ą„ą¤Ŗą„ą¤°ą¤¾ą¤ą¤ ą¤§ą¤¾ą¤°ą¤",
+ "DetailsTabView.descriptionLabel": "ą¤µą¤æą¤µą¤°ą¤£",
"DetailsTabView.detectedImportText": "{count, plural,\n =1 {# resource has view-only permission}\n other {# resources have view-only permission}}",
"DetailsTabView.importedFromButtonText": "Imported from {channel}",
"DetailsTabView.languageChannelHelpText": "Leave blank to use the channel language",
"DetailsTabView.languageHelpText": "Leave blank to use the folder language",
- "CompletionOptions.learnersCanMarkComplete": "Allow learners to mark as complete",
"DetailsTabView.noTagsFoundText": "No results found for \"{text}\". Press 'Enter' key to create a new tag",
"DetailsTabView.providerLabel": "Provider",
"DetailsTabView.providerToolTip": "Organization that commissioned or is distributing the content",
@@ -828,33 +834,33 @@
"DetailsTabView.sourceHeader": "Source",
"DetailsTabView.tagsLabel": "Tags",
"DetailsTabView.thumbnailHeader": "Thumbnail",
- "DetailsTabView.titleLabel": "Title",
+ "DetailsTabView.titleLabel": "ą¤¶ą„ą¤°ą„ą¤·ą¤",
"Diff.negativeSign": "-",
"Diff.positiveSign": "+",
"DiffTable.headerDiff": "Net changes",
"DiffTable.headerLive": "Live",
"DiffTable.headerStaged": "Staged",
- "DiffTable.headerType": "Type",
+ "DiffTable.headerType": "ą¤Ŗą„ą¤°ą¤ą¤¾ą¤°",
"DiffTable.typeAudios": "Audios",
- "DiffTable.typeDocuments": "Documents",
- "DiffTable.typeExercises": "Exercises",
- "DiffTable.typeFileSize": "File size",
+ "DiffTable.typeDocuments": "ą¤¦ą¤øą„ą¤¤ą¤¾ą¤µą„ą¤ą¤¼",
+ "DiffTable.typeExercises": "ą¤
ą¤ą„ą¤Æą¤¾ą¤ø",
+ "DiffTable.typeFileSize": "ą¤«ą¤¼ą¤¾ą¤ą¤² ą¤ą¤¾ ą¤øą¤¾ą¤ą¤ą¤¼",
"DiffTable.typeHtml5Apps": "HTML5 apps",
"DiffTable.typeSlideshows": "Slideshows",
- "DiffTable.typeTopics": "Folders",
+ "DiffTable.typeTopics": "ą¤«ą¤¼ą„ą¤²ą„ą¤”ą¤°ą„ą¤ø",
"DiffTable.typeVersion": "API version",
- "DiffTable.typeVideos": "Videos",
- "EditList.selectAllLabel": "Select all",
+ "DiffTable.typeVideos": "ą¤µą„ą¤”ą¤æą¤Æą„",
+ "EditList.selectAllLabel": "ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤",
"EditListItem.questionCount": "{count, plural,\n =1 {# question}\n other {# questions}}",
"EditModal.addTopic": "Add new folder",
"EditModal.addTopicsHeader": "New folder",
"EditModal.cancelUploadsButton": "Exit",
"EditModal.closeWithoutSavingButton": "Close without saving",
"EditModal.createExerciseHeader": "New exercise",
- "EditModal.dismissDialogButton": "Cancel",
+ "EditModal.dismissDialogButton": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"EditModal.editFilesHeader": "Edit files",
- "EditModal.editingDetailsHeader": "Edit details",
- "EditModal.finishButton": "Finish",
+ "EditModal.editingDetailsHeader": "ą¤µą¤æą¤µą¤°ą¤£ ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤",
+ "EditModal.finishButton": "ą¤øą¤®ą¤¾ą¤Ŗą„ą¤¤",
"EditModal.invalidNodesFound": "{count, plural,\n =1 {# incomplete resource found}\n other {# incomplete resources found}}",
"EditModal.invalidNodesFoundText": "Incomplete resources will not be published until these errors are resolved",
"EditModal.keepEditingButton": "Keep editing",
@@ -867,26 +873,26 @@
"EditModal.uploadFilesHeader": "Upload files",
"EditModal.uploadInProgressHeader": "Upload in progress",
"EditModal.uploadInProgressText": "Uploads that are in progress will be lost if you exit",
- "EditSearchModal.cancelAction": "Cancel",
- "EditSearchModal.changesSavedSnackbar": "Changes saved",
+ "EditSearchModal.cancelAction": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
+ "EditSearchModal.changesSavedSnackbar": "ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Ø ą¤øą¤¹ą„ą¤ą„ ą¤ą¤",
"EditSearchModal.editSavedSearchTitle": "Edit search title",
"EditSearchModal.fieldRequired": "Field is required",
- "EditSearchModal.saveChangesAction": "Save",
+ "EditSearchModal.saveChangesAction": "ą¤øą„ą¤µ ą¤ą¤°ą„ą¤",
"EditSearchModal.searchTitleLabel": "Search title",
- "EditView.details": "Details",
+ "EditView.details": "ą¤µą¤æą¤µą¤°ą¤£",
"EditView.editingMultipleCount": "Editing details for {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}",
"EditView.errorBannerText": "Please provide the required information",
"EditView.invalidFieldsToolTip": "Some required information is missing",
"EditView.noItemsToEditText": "Please select resources or folders to edit",
- "EditView.preview": "Preview",
- "EditView.questions": "Questions",
- "EditView.related": "Related",
+ "EditView.preview": "ą¤Ŗą„ą¤°ą„ą¤µą¤¾ą¤µą¤²ą„ą¤ą¤Ø",
+ "EditView.questions": "ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø",
+ "EditView.related": "ą¤øą¤ą¤¬ą¤ą¤§ą¤æą¤¤",
"EmailField.emailLabel": "Email",
"EmailField.emailRequiredMessage": "Field is required",
"EmailField.validEmailMessage": "Please enter a valid email",
- "ExpandableList.less": "Show less",
+ "ExpandableList.less": "ą¤ą¤® ą¤¦ą¤æą¤ą¤¾ą¤ą¤",
"ExpandableList.more": "Show more ({more})",
- "FilePreview.exitFullscreen": "Exit fullscreen",
+ "FilePreview.exitFullscreen": "ą¤«ą¤¼ą„ą¤² ą¤øą„ą¤ą„ą¤°ą„ą¤Ø ą¤øą„ ą¤¬ą¤¾ą¤¹ą¤° ą¤ą¤¾ą¤ą¤",
"FilePreview.fullscreenModeText": "Fullscreen mode",
"FilePreview.viewFullscreen": "View fullscreen",
"FileStatusText.selectFile": "Select file",
@@ -903,7 +909,7 @@
"FileUploadDefault.chooseFilesButton": "Select files",
"FileUploadDefault.dropHereText": "Drag and drop your files here, or select your files manually",
"FileUploadDefault.uploadToText": "Upload to '{title}'",
- "FileUploadItem.removeFileButton": "Remove",
+ "FileUploadItem.removeFileButton": "ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤",
"FileUploadItem.retryUpload": "Retry upload",
"FileUploadItem.unknownFile": "Unknown filename",
"FileUploadItem.uploadButton": "Select file",
@@ -914,91 +920,92 @@
"ForgotPassword.submitButton": "Submit",
"FormulasMenu.btnLabelInsert": "Insert",
"FormulasMenu.formulasMenuTitle": "Special characters",
- "FullNameForm.cancelAction": "Cancel",
- "FullNameForm.changesSavedMessage": "Changes saved",
+ "FullNameForm.cancelAction": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
+ "FullNameForm.changesSavedMessage": "ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Ø ą¤øą¤¹ą„ą¤ą„ ą¤ą¤",
"FullNameForm.editNameHeader": "Edit full name",
"FullNameForm.failedToSaveMessage": "Failed to save changes",
"FullNameForm.fieldRequired": "Field is required",
"FullNameForm.firstNameLabel": "First name",
"FullNameForm.lastNameLabel": "Last name",
- "FullNameForm.saveChangesAction": "Save changes",
- "GenericError.backToHomeAction": "Back to home",
- "GenericError.genericErrorDetails": "Try refreshing this page or going back to the home page",
+ "FullNameForm.saveChangesAction": "ą¤Ŗą¤°ą¤æą¤µą¤°ą„ą¤¤ą¤Øą„ą¤ ą¤ą„ ą¤øą„ą¤µ ą¤ą¤°ą„ą¤",
+ "GenericError.backToHomeAction": "ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤æą¤ø ą¤ą¤¾ą¤ą¤",
+ "GenericError.genericErrorDetails": "ą¤ą¤ø ą¤Ŗą„ą¤ ą¤ą„ ą¤°ą„ą¤«ą„ą¤°ą„ą¤¶ ą¤ą¤°ą¤Øą„ ą¤Æą¤¾ ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤ø ą¤ą¤¾ą¤Øą„ ą¤ą„ ą¤ą„ą¤¶ą¤æą¤¶ ą¤ą¤°ą„ą¤",
"GenericError.genericErrorHeader": "Sorry, something went wrong",
- "GenericError.helpByReportingAction": "Help us by reporting this error",
- "GenericError.refreshAction": "Refresh",
- "HintsEditor.hintsLabel": "Hints",
+ "GenericError.helpByReportingAction": "ą¤ą¤ø ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤ą„ ą¤°ą¤æą¤Ŗą„ą¤°ą„ą¤ ą¤ą¤°ą¤ą„ ą¤¹ą¤®ą¤¾ą¤°ą„ ą¤®ą¤¦ą¤¦ ą¤ą¤°ą„ą¤",
+ "GenericError.refreshAction": "ą¤°ą„ą¤«ą¤¼ą„ą¤°ą„ą¤¶ ą¤ą¤°ą„ą¤",
+ "HintsEditor.hintsLabel": "ą¤øą¤ą¤ą„ą¤¤",
"HintsEditor.newHintBtnLabel": "New hint",
"HintsEditor.noHintsPlaceholder": "Question has no hints",
"ImageOnlyThumbnail.thumbnail": "{title} thumbnail",
"ImagesMenu.acceptsText": "Supported file types: {acceptedFormats}",
"ImagesMenu.altTextHint": "The image description is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
"ImagesMenu.altTextLabel": "Image description",
- "ImagesMenu.btnLabelCancel": "Cancel",
+ "ImagesMenu.btnLabelCancel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"ImagesMenu.btnLabelInsert": "Insert",
"ImagesMenu.currentImageDefaultText": "Current image",
"ImagesMenu.defaultDropText": "Drag and drop an image here, or upload manually",
"ImagesMenu.imageHeader": "Upload image",
"ImagesMenu.selectFile": "Select file",
"ImagesMenu.selectFileButton": "Select file",
- "ImportFromChannelsModal.addButton": "Add",
- "ImportFromChannelsModal.addedText": "Added",
- "ImportFromChannelsModal.importAction": "Import",
+ "ImportFromChannelsModal.addButton": "ą¤ą„ą¤”ą¤¼ą„ą¤",
+ "ImportFromChannelsModal.addedText": "ą¤ą„ą¤”ą¤¼ą¤¾ ą¤ą¤Æą¤¾",
+ "ImportFromChannelsModal.importAction": "ą¤ą¤Æą¤¾ą¤¤ ą¤ą¤°ą„ą¤",
"ImportFromChannelsModal.importTitle": "Import from other channels",
- "ImportFromChannelsModal.removeButton": "Remove",
+ "ImportFromChannelsModal.removeButton": "ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤",
"ImportFromChannelsModal.resourcesAddedSnackbar": "{count, number} {count, plural, one {resource selected} other {resources selected}}",
- "ImportFromChannelsModal.resourcesRemovedSnackbar": "{count, number} {count, plural, one {resource removed} other {resources removed}}",
+ "ImportFromChannelsModal.resourcesRemovedSnackbar": "{count, number} {count, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø ą¤¹ą¤ą¤¾ą¤Æą¤¾ ą¤ą¤Æą¤¾} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø ą¤¹ą¤ą¤¾ą¤ ą¤ą¤}} ",
"ImportFromChannelsModal.resourcesSelected": "{count, number} {count, plural, one {resource selected} other {resources selected}}",
"ImportFromChannelsModal.reviewAction": "Review",
"ImportFromChannelsModal.reviewTitle": "Resource selection",
- "InfoModal.close": "Close",
- "LanguageDropdown.labelText": "Language",
+ "InfoModal.close": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
+ "LanguageDropdown.labelText": "ą¤ą¤¾ą¤·ą¤¾Ā ",
"LanguageDropdown.languageItemText": "{language} ({code})",
"LanguageDropdown.languageRequired": "Field is required",
"LanguageDropdown.noDataText": "Language not found",
- "LanguageFilter.languageLabel": "Languages",
+ "LanguageFilter.languageLabel": "ą¤ą¤¾ą¤·ą¤¾ą¤ą¤",
"LanguageFilter.noMatchingLanguageText": "No language matches the search",
- "LanguageSwitcherList.showMoreLanguagesSelector": "More languages",
- "LanguageSwitcherModal.cancelAction": "Cancel",
- "LanguageSwitcherModal.changeLanguageModalHeader": "Change language",
- "LanguageSwitcherModal.confirmAction": "Confirm",
+ "LanguageSwitcherList.showMoreLanguagesSelector": "ą¤
ą¤Øą„ą¤Æ ą¤ą¤¾ą¤·ą¤¾ą¤ą¤",
+ "LanguageSwitcherModal.cancelAction": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
+ "LanguageSwitcherModal.changeLanguageModalHeader": "ą¤ą¤¾ą¤·ą¤¾ ą¤¬ą¤¦ą¤²ą„ą¤",
+ "LanguageSwitcherModal.confirmAction": "ą¤Ŗą„ą¤·ą„ą¤ą¤æ ą¤ą¤°ą„ą¤",
"LicenseDropdown.learnMoreButton": "Learn More",
"LicenseDropdown.licenseDescriptionLabel": "License description",
"LicenseDropdown.licenseInfoHeader": "About licenses",
- "LicenseDropdown.licenseLabel": "License",
+ "LicenseDropdown.licenseLabel": "ą¤²ą¤¾ą¤ą¤øą„ą¤ą¤ø",
"Main.TOSLink": "Terms of service",
"Main.copyright": "Ā© {year} Learning Equality",
- "Main.createAccountButton": "Create an account",
+ "Main.createAccountButton": "ą¤
ą¤ą¤¾ą¤ą¤ą¤ ą¤¬ą¤Øą¤¾ą¤ą¤",
"Main.forgotPasswordLink": "Forgot your password?",
"Main.guestModeLink": "Explore without an account",
- "Main.kolibriStudio": "Kolibri Studio",
+ "Main.kolibriStudio": "Kolibri ą¤øą„ą¤ą„ą¤”ą¤æą¤Æą„",
"Main.loginFailed": "Email or password is incorrect",
"Main.loginFailedOffline": "You seem to be offline. Please connect to the internet before signing in.",
"Main.loginToProceed": "You must sign in to view that page",
- "Main.passwordLabel": "Password",
+ "Main.passwordLabel": "ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤”",
"Main.privacyPolicyLink": "Privacy policy",
- "Main.signInButton": "Sign in",
+ "Main.signInButton": "ą¤øą¤¾ą¤ą¤Ø ą¤ą¤Ø ą¤ą¤°ą„ą¤",
"MainNavigationDrawer.administrationLink": "Administration",
- "MainNavigationDrawer.channelsLink": "Channels",
+ "MainNavigationDrawer.changeLanguage": "Change language",
+ "MainNavigationDrawer.channelsLink": "ą¤ą„ą¤Øą¤²",
"MainNavigationDrawer.copyright": "Ā© {year} Learning Equality",
"MainNavigationDrawer.giveFeedback": "Give feedback",
"MainNavigationDrawer.helpLink": "Help and support",
- "MainNavigationDrawer.logoutLink": "Sign out",
- "MainNavigationDrawer.settingsLink": "Settings",
+ "MainNavigationDrawer.logoutLink": "ą¤øą¤¾ą¤ą¤Ø ą¤ą¤ą¤ ą¤ą¤°ą„ą¤",
+ "MainNavigationDrawer.settingsLink": "ą¤øą„ą¤ą¤æą¤ą¤",
"MarkdownEditor.bold": "Bold (Ctrl+B)",
"MarkdownEditor.formulas": "Insert formula (Ctrl+F)",
"MarkdownEditor.image": "Insert image (Ctrl+P)",
"MarkdownEditor.italic": "Italic (Ctrl+I)",
"MarkdownEditor.minimize": "Minimize (Ctrl+M)",
- "MarkdownImageField.editImageOption": "Edit",
- "MarkdownImageField.removeImageOption": "Remove",
+ "MarkdownImageField.editImageOption": "ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)",
+ "MarkdownImageField.removeImageOption": "ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤",
"MarkdownImageField.resizeImageOption": "Resize",
"MasteryCriteriaGoal.labelText": "Goal",
"MasteryCriteriaMofNFields.mHint": "Correct answers needed",
"MasteryCriteriaMofNFields.nHint": "Recent answers",
"MessageLayout.backToLogin": "Continue to sign-in page",
"MoveModal.addTopic": "Add new folder",
- "MoveModal.cancel": "Cancel",
+ "MoveModal.cancel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"MoveModal.emptyTopicText": "No resources found",
"MoveModal.goToLocationButton": "Go to location",
"MoveModal.moveHere": "Move here",
@@ -1007,8 +1014,8 @@
"MoveModal.resourcesCount": "{count, plural,\n =1 {# resource}\n other {# resources}}",
"MoveModal.topicCreatedMessage": "New folder created",
"MultiSelect.noItemsFound": "No items found",
- "NewTopicModal.cancel": "Cancel",
- "NewTopicModal.create": "Create",
+ "NewTopicModal.cancel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
+ "NewTopicModal.create": "ą¤¬ą¤Øą¤¾ą¤ą¤",
"NewTopicModal.createTopic": "Create new folder",
"NewTopicModal.topicTitle": "Folder title",
"NewTopicModal.topicTitleRequired": "Folder title is required",
@@ -1019,19 +1026,18 @@
"NodeTreeNavigation.noResourcesDefaultText": "No resources found",
"OfflineText.offlineIndicatorText": "Offline",
"OfflineText.offlineText": "You seem to be offline. Your changes will be saved once your connection is back.",
- "PageNotFoundError.backToHomeAction": "Back to home",
+ "PageNotFoundError.backToHomeAction": "ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤µą¤¾ą¤Ŗą¤æą¤ø ą¤ą¤¾ą¤ą¤",
"PageNotFoundError.pageNotFoundDetails": "Sorry, that page does not exist",
"PageNotFoundError.pageNotFoundHeader": "Page not found",
"PasswordField.fieldRequiredMessage": "Field is required",
- "PasswordField.passwordLabel": "Password",
+ "PasswordField.passwordLabel": "ą¤Ŗą¤¾ą¤øą¤µą¤°ą„ą¤”",
"PasswordInstructionsSent.passwordInstructionsHeader": "Instructions sent. Thank you!",
"PasswordInstructionsSent.passwordInstructionsText": "If there is already an account with the email address provided, you should receive the instructions shortly. If you don't see an email from us, please check your spam folder.",
- "PermissionsError.goToHomePageAction": "Go to home page",
- "PermissionsError.permissionDeniedHeader": "Did you forget to sign in?",
+ "PermissionsError.goToHomePageAction": "ą¤¹ą„ą¤®ą¤Ŗą„ą¤ ą¤Ŗą¤° ą¤ą¤¾ą¤ą¤",
+ "PermissionsError.permissionDeniedHeader": "ą¤ą„ą¤Æą¤¾ ą¤ą¤Ŗ ą¤øą¤¾ą¤ą¤Ø ą¤ą¤Ø ą¤ą¤°ą¤Øą¤¾ ą¤ą„ą¤² ą¤ą¤?",
"PoliciesModal.checkboxText": "I have agreed to the above terms",
- "PoliciesModal.checkboxValidationErrorMessage": "Field is required",
- "PoliciesModal.closeButton": "Close",
- "PoliciesModal.continueButton": "Continue",
+ "PoliciesModal.closeButton": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
+ "PoliciesModal.continueButton": "ą¤ą¤¾ą¤°ą„ ą¤°ą¤ą„ą¤",
"PoliciesModal.lastUpdated": "Last updated {date}",
"PrivacyPolicyModal.privacyHeader": "Privacy policy",
"PrivacyPolicyModal.updatedPrivacyHeader": "Updated privacy policy",
@@ -1040,24 +1046,25 @@
"ProgressModal.lastPublished": "Published {last_published}",
"ProgressModal.publishHeader": "Publishing channel",
"ProgressModal.syncError": "Last attempt to sync failed",
- "ProgressModal.syncHeader": "Syncing channel",
+ "ProgressModal.syncHeader": "Syncing resources",
+ "ProgressModal.syncedSnackbar": "Resources synced",
"ProgressModal.unpublishedText": "Unpublished",
- "PublishModal.cancelButton": "Cancel",
+ "PublishModal.cancelButton": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"PublishModal.descriptionDescriptionTooltip": "This description will be shown to Kolibri admins before they update channel versions",
"PublishModal.descriptionRequiredMessage": "Please describe what's new in this version before publishing",
"PublishModal.incompleteCount": "{count, plural, =1 {# incomplete resource} other {# incomplete resources}}",
"PublishModal.incompleteInstructions": "Click 'Continue' to confirm that you would like to publish anyway.",
"PublishModal.incompleteWarning": "Incomplete resources will not be published and made available for download in Kolibri.",
- "PublishModal.nextButton": "Continue",
+ "PublishModal.nextButton": "ą¤ą¤¾ą¤°ą„ ą¤°ą¤ą„ą¤",
"PublishModal.publishButton": "Publish",
"PublishModal.publishMessageLabel": "Describe what's new in this channel version",
"PublishModal.versionDescriptionLabel": "Version description",
- "RelatedResourcesList.removeBtnLabel": "Remove",
+ "RelatedResourcesList.removeBtnLabel": "ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤",
"RelatedResourcesTab.addNextStepBtnLabel": "Add next step",
"RelatedResourcesTab.addPreviousStepBtnLabel": "Add previous step",
- "RelatedResourcesTab.dialogCloseBtnLabel": "Close",
+ "RelatedResourcesTab.dialogCloseBtnLabel": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
"RelatedResourcesTab.nextStepsExplanation": "Recommended resources that build on skills or concepts learned in this resource",
- "RelatedResourcesTab.nextStepsTitle": "Next steps",
+ "RelatedResourcesTab.nextStepsTitle": "ą¤
ą¤ą¤²ą„ ą¤ą¤¦ą¤®",
"RelatedResourcesTab.previewHelpText": "Related resources are displayed as recommendations when learners engage with this resource",
"RelatedResourcesTab.previousStepsExplanation": "Recommended resources that introduce skills or concepts needed in order to use this resource",
"RelatedResourcesTab.previousStepsTitle": "Previous steps",
@@ -1070,14 +1077,14 @@
"RelatedResourcesTab.showPreviewBtnLabel": "Show me",
"RelatedResourcesTab.tooManyNextStepsWarning": "Limit the number of next steps to create a more guided learning experience",
"RelatedResourcesTab.tooManyPreviousStepsWarning": "Limit the number of previous steps to create a more guided learning experience",
- "ReportErrorModal.closeAction": "Close",
- "ReportErrorModal.emailDescription": "Contact the support team with your error details and weāll do our best to help.",
- "ReportErrorModal.emailPrompt": "Send an email to the developers",
- "ReportErrorModal.errorDetailsHeader": "Error details",
- "ReportErrorModal.forumPostingTips": "Include a description of what you were trying to do and what you clicked on when the error appeared.",
- "ReportErrorModal.forumPrompt": "Visit the community forums",
+ "ReportErrorModal.closeAction": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
+ "ReportErrorModal.emailDescription": "ą¤
ą¤Ŗą¤Øą„ ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤µą¤æą¤µą¤°ą¤£ ą¤ą„ ą¤øą¤¾ą¤„ ą¤øą¤®ą¤°ą„ą¤„ą¤Ø ą¤ą„ą¤® ą¤øą„ ą¤øą¤ą¤Ŗą¤°ą„ą¤ ą¤ą¤°ą„ą¤ ą¤ą¤° ą¤¹ą¤® ą¤®ą¤¦ą¤¦ ą¤ą¤°ą¤Øą„ ą¤ą„ ą¤Ŗą„ą¤°ą„ ą¤ą„ą¤¶ą¤æą¤¶ ą¤ą¤°ą„ą¤ą¤ą„ ą„¤",
+ "ReportErrorModal.emailPrompt": "ą¤”ą„ą¤µą¤²ą¤Ŗą¤° ą¤ą„ ą¤-ą¤®ą„ą¤² ą¤ą¤°ą„ą¤",
+ "ReportErrorModal.errorDetailsHeader": "ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤µą¤æą¤µą¤°ą¤£",
+ "ReportErrorModal.forumPostingTips": "ą¤ą¤Ŗ ą¤ą„ą¤Æą¤¾ ą¤ą¤°ą¤Øą„ ą¤ą„ ą¤ą„ą¤¶ą¤æą¤¶ ą¤ą¤° ą¤°ą¤¹ą„ ą¤„ą„ ą¤ą¤° ą¤ą¤æą¤ø ą¤Ŗą¤° ą¤ą„ą¤²ą¤æą¤ ą¤ą¤æą¤Æą¤¾ ą¤ą¤¬ ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤¦ą¤æą¤ą¤¾ą¤ ą¤¦ą„, ą¤ą¤ø ą¤øą¤¬ą¤ą¤¾ ą¤µą¤°ą„ą¤£ą¤Ø ą¤¶ą¤¾ą¤®ą¤æą¤² ą¤ą¤°ą„ą¤ą„¤",
+ "ReportErrorModal.forumPrompt": "ą¤øą¤®ą„ą¤¦ą¤¾ą¤Æ ą¤«ą¤¼ą„ą¤°ą¤® ą¤Ŗą¤° ą¤ą¤¾ą¤ą¤",
"ReportErrorModal.forumUseTips": "Search the community forum to see if others encountered similar issues. If there are none reported, please open a new forum post and paste the error details below inside so we can rectify the error in a future version of Kolibri Studio.",
- "ReportErrorModal.reportErrorHeader": "Report Error",
+ "ReportErrorModal.reportErrorHeader": "ą¤¤ą„ą¤°ą„ą¤ą¤æ ą¤ą„ ą¤øą„ą¤ą¤Øą¤¾ ą¤¦ą„ą¤",
"RequestForm.approximatelyHowManyResourcesLabel": "Approximately how many individual resources are you planning to upload?",
"RequestForm.audiencePlaceholder": "In-school learners, adult learners, teachers, etc",
"RequestForm.authorLabel": "Who is the author (creator), curator (organizer), and/or aggregator (maintainer) of your content? Please specify",
@@ -1111,7 +1118,7 @@
"RequestForm.selectAllThatApplyPlaceholder": "Select all that apply",
"RequestForm.sendRequestAction": "Send request",
"RequestForm.sixPlusMonthsLabel": "6+ months",
- "RequestForm.sizePlaceholder": "Size",
+ "RequestForm.sizePlaceholder": "ą¤øą¤¾ą¤ą¤ą¤¼",
"RequestForm.smallNgoLabel": "Small NGO with annual budget < $25k",
"RequestForm.storageAmountRequestedPlaceholder": "Amount requested (e.g. 10GB)",
"RequestForm.targetRegionsLabel": "Target region(s) for your content (if applicable)",
@@ -1120,7 +1127,7 @@
"RequestForm.twoToFourWeeksLabel": "2-4 weeks",
"RequestForm.typeOfContentPlaceholder": "Types of resources",
"RequestForm.typeOfOrganizationLabel": "What type of organization or group is coordinating the use of Kolibri (if applicable)?",
- "RequestForm.unknownLabel": "Unknown",
+ "RequestForm.unknownLabel": "ą¤
ą¤ą„ą¤ą¤¾ą¤¤",
"RequestForm.uploadingOnBehalfLabel": "I am uploading content on behalf of:",
"RequestForm.usageLabel": "Tell us more about your use of Kolibri",
"RequestForm.whoCanUseContentLabel": "Who can use your content?",
@@ -1143,33 +1150,34 @@
"ResetPasswordSuccess.text": "Your password has been reset. You may sign in now.",
"ResourcePanel.aggregator": "Aggregator",
"ResourcePanel.audience": "Audience",
- "ResourcePanel.author": "Author",
+ "ResourcePanel.author": "ą¤²ą„ą¤ą¤",
"ResourcePanel.availableFormats": "Available formats",
"ResourcePanel.coachResources": "Resources for coaches",
- "ResourcePanel.completion": "Completion",
- "ResourcePanel.copyrightHolder": "Copyright holder",
- "ResourcePanel.description": "Description",
- "ResourcePanel.details": "Details",
- "ResourcePanel.fileSize": "Size",
+ "ResourcePanel.copyrightHolder": "ą¤ą„ą¤Ŗą„ą¤°ą¤¾ą¤ą¤ ą¤§ą¤¾ą¤°ą¤",
+ "ResourcePanel.description": "ą¤µą¤æą¤µą¤°ą¤£",
+ "ResourcePanel.details": "ą¤µą¤æą¤µą¤°ą¤£",
+ "ResourcePanel.fileSize": "ą¤øą¤¾ą¤ą¤ą¤¼",
"ResourcePanel.files": "Files",
"ResourcePanel.incompleteQuestionError": "{count, plural, one {# incomplete question} other {# incomplete questions}}",
- "ResourcePanel.language": "Language",
- "ResourcePanel.license": "License",
- "ResourcePanel.masteryMofN": "Goal: {m} out of {n}",
- "ResourcePanel.nextSteps": "Next steps",
- "ResourcePanel.noCopyrightHolderError": "Missing copyright holder",
- "ResourcePanel.noFilesError": "Missing files",
- "ResourcePanel.noLicenseDescriptionError": "Missing license description",
- "ResourcePanel.noLicenseError": "Missing license",
- "ResourcePanel.noMasteryModelError": "Missing mastery criteria",
+ "ResourcePanel.language": "ą¤ą¤¾ą¤·ą¤¾Ā ",
+ "ResourcePanel.license": "ą¤²ą¤¾ą¤ą¤øą„ą¤ą¤ø",
+ "ResourcePanel.nextSteps": "ą¤
ą¤ą¤²ą„ ą¤ą¤¦ą¤®",
+ "ResourcePanel.noCompletionCriteriaError": "Completion criteria are required",
+ "ResourcePanel.noCopyrightHolderError": "Copyright holder is required",
+ "ResourcePanel.noDurationError": "Duration is required",
+ "ResourcePanel.noFilesError": "File is required",
+ "ResourcePanel.noLearningActivityError": "Learning activity is required",
+ "ResourcePanel.noLicenseDescriptionError": "License description is required",
+ "ResourcePanel.noLicenseError": "License is required",
+ "ResourcePanel.noMasteryModelError": "Mastery criteria are required",
"ResourcePanel.noQuestionsError": "Exercise is empty",
"ResourcePanel.originalChannel": "Imported from",
"ResourcePanel.previousSteps": "Previous steps",
"ResourcePanel.provider": "Provider",
- "ResourcePanel.questionCount": "{value, number, integer} {value, plural, one {question} other {questions}}",
- "ResourcePanel.questions": "Questions",
+ "ResourcePanel.questionCount": "{value, number, integer} {value, plural, one {ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø} other {ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø}}",
+ "ResourcePanel.questions": "ą¤Ŗą„ą¤°ą¤¶ą„ą¤Ø",
"ResourcePanel.relatedResources": "Related resources",
- "ResourcePanel.resources": "Resources",
+ "ResourcePanel.resources": "ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø",
"ResourcePanel.showAnswers": "Show answers",
"ResourcePanel.source": "Source",
"ResourcePanel.subtitles": "Captions and subtitles",
@@ -1178,16 +1186,16 @@
"ResourcePanel.visibleTo": "Visible to",
"ResourcesNeededOptions.furtherExplanation": "Please add to the 'Description' field any additional supplies learners will need in order to use this resource",
"ResourcesNeededOptions.resourcesNeededLabel": "Requirements",
- "ReviewSelectionsPage.noResourcesSelected": "No resources selected",
- "ReviewSelectionsPage.removeAction": "Remove",
- "ReviewSelectionsPage.resourcesInTopic": "{count, number} {count, plural, one {resource} other {resources}}",
+ "ReviewSelectionsPage.noResourcesSelected": "ą¤ą„ą¤ ą¤ą„ ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø ą¤Øą¤¹ą„ą¤ ą¤ą„ą¤Øą„ ą¤ą¤ ą¤¹ą„",
+ "ReviewSelectionsPage.removeAction": "ą¤¹ą¤ą¤¾ ą¤¦ą„ą¤",
+ "ReviewSelectionsPage.resourcesInTopic": "{count, number} {count, plural, one {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø} other {ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø}}",
"ReviewSelectionsPage.reviewSelectionHeader": "Review selections",
- "SavedSearchesModal.cancelAction": "Cancel",
- "SavedSearchesModal.closeButtonLabel": "Close",
- "SavedSearchesModal.deleteAction": "Delete",
+ "SavedSearchesModal.cancelAction": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
+ "SavedSearchesModal.closeButtonLabel": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
+ "SavedSearchesModal.deleteAction": "ą¤¹ą¤ą¤¾ą¤ą¤",
"SavedSearchesModal.deleteConfirmation": "Are you sure you want to delete this saved search?",
"SavedSearchesModal.deleteSearchTitle": "Delete saved search",
- "SavedSearchesModal.editAction": "Edit",
+ "SavedSearchesModal.editAction": "ą¤øą¤ą¤Ŗą¤¾ą¤¦ą¤æą¤¤ ą¤ą¤°ą„ą¤ (ą¤ą¤”ą¤æą¤)",
"SavedSearchesModal.filterCount": "{count, number} {count, plural, one {filter} other {filters}}",
"SavedSearchesModal.noSavedSearches": "You do not have any saved searches",
"SavedSearchesModal.savedSearchesTitle": "Saved searches",
@@ -1196,7 +1204,7 @@
"SavingIndicator.savedNow": "Saved just now",
"SavingIndicator.savingIndicator": "Saving...",
"SearchFilterBar.assessments": "Assessments",
- "SearchFilterBar.clearAll": "Clear all",
+ "SearchFilterBar.clearAll": "ą¤øą¤ą„ ą¤øą¤¾ą¤«ą¤¼ ą¤ą¤°ą„ą¤",
"SearchFilterBar.coachContent": "Resources for coaches",
"SearchFilterBar.createdAfter": "Added after '{date}'",
"SearchFilterBar.topicsHidden": "Folders excluded",
@@ -1204,16 +1212,16 @@
"SearchFilters.assessmentsLabel": "Show assessments only",
"SearchFilters.channelSourceLabel": "Channel/source",
"SearchFilters.channelTypeLabel": "Channel type",
- "SearchFilters.channelsHeader": "Channels",
+ "SearchFilters.channelsHeader": "ą¤ą„ą¤Øą¤²",
"SearchFilters.coachContentLabel": "Show resources for coaches",
"SearchFilters.filtersHeader": "Filter options",
"SearchFilters.hideTopicsLabel": "Hide folders",
"SearchFilters.kindLabel": "Format",
- "SearchFilters.licensesLabel": "License",
+ "SearchFilters.licensesLabel": "ą¤²ą¤¾ą¤ą¤øą„ą¤ą¤ø",
"SearchOrBrowseWindow.backToBrowseAction": "Back to browse",
- "SearchOrBrowseWindow.copiedToClipboard": "Copied to clipboard",
+ "SearchOrBrowseWindow.copiedToClipboard": "ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤° ą¤¦ą¤æą¤Æą¤¾ ą¤ą¤Æą¤¾",
"SearchOrBrowseWindow.copyFailed": "Failed to copy to clipboard",
- "SearchOrBrowseWindow.searchAction": "Search",
+ "SearchOrBrowseWindow.searchAction": "ą¤ą„ą¤",
"SearchOrBrowseWindow.searchLabel": "Search for resourcesā¦",
"SearchResultsList.failedToLoad": "Failed to load search results",
"SearchResultsList.resultsPerPageLabel": "Results per page",
@@ -1222,13 +1230,13 @@
"SearchResultsList.searchResultsCount": "{count, number} {count, plural, one {result} other {results}} for '{searchTerm}'",
"SearchResultsList.searchSavedSnackbar": "Search saved",
"SettingsIndex.accountLabel": "Account",
- "SettingsIndex.settingsTitle": "Settings",
+ "SettingsIndex.settingsTitle": "ą¤øą„ą¤ą¤æą¤ą¤",
"SettingsIndex.storageLabel": "Storage",
"SettingsIndex.usingStudioLabel": "About Studio",
"StagingTreePage.backToViewing": "Back to viewing",
- "StagingTreePage.cancelDeployBtn": "Cancel",
+ "StagingTreePage.cancelDeployBtn": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"StagingTreePage.channelDeployed": "Channel has been deployed",
- "StagingTreePage.closeSummaryDetailsDialogBtn": "Close",
+ "StagingTreePage.closeSummaryDetailsDialogBtn": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
"StagingTreePage.collapseAllButton": "Collapse all",
"StagingTreePage.confirmDeployBtn": "Deploy channel",
"StagingTreePage.deploy": "Deploy",
@@ -1246,7 +1254,7 @@
"StagingTreePage.summaryDetailsDialogTitle": "Summary details",
"StagingTreePage.topicsCount": "{count, number} {count, plural, one { folder } other { folders }}",
"StagingTreePage.totalResources": "Total resources",
- "StagingTreePage.totalSize": "Total size",
+ "StagingTreePage.totalSize": "ą¤ą„ą¤² ą¤øą¤¾ą¤ą¤ą¤¼",
"StagingTreePage.viewDetails": "View details",
"StatusStrings.noStorageError": "Not enough space",
"StatusStrings.uploadFailedError": "Upload failed",
@@ -1259,7 +1267,7 @@
"Storage.spaceUsedOfMax": "{qty} of {max}",
"Storage.storagePercentageUsed": "{qty}% storage used",
"StudioTree.missingTitle": "Missing title",
- "StudioTree.optionsTooltip": "Options",
+ "StudioTree.optionsTooltip": "ą¤µą¤æą¤ą¤²ą„ą¤Ŗ",
"SubtitlesList.acceptedFormatsTooltip": "Supported formats: {extensions}",
"SubtitlesList.addSubtitleText": "Add captions",
"SubtitlesList.subtitlesHeader": "Captions and subtitles",
@@ -1267,25 +1275,27 @@
"SupplementaryItem.retryUpload": "Retry upload",
"SupplementaryItem.uploadFailed": "Upload failed",
"SupplementaryList.selectFileText": "Select file",
- "SyncResourcesModal.backButtonLabel": "Back",
- "SyncResourcesModal.cancelButtonLabel": "Cancel",
+ "SyncResourcesModal.backButtonLabel": "ą¤µą¤¾ą¤Ŗą¤ø ą¤ą¤¾ą¤ą¤",
+ "SyncResourcesModal.cancelButtonLabel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"SyncResourcesModal.confirmSyncModalExplainer": "You are about to sync and update the following:",
"SyncResourcesModal.confirmSyncModalTitle": "Confirm sync",
- "SyncResourcesModal.continueButtonLabel": "Continue",
- "SyncResourcesModal.syncButtonLabel": "Sync",
- "SyncResourcesModal.syncExercisesExplainer": "Update questions, answers, and hints",
+ "SyncResourcesModal.confirmSyncModalWarningExplainer": "Warning: this will overwrite any changes you have made to copied or imported resources.",
+ "SyncResourcesModal.continueButtonLabel": "ą¤ą¤¾ą¤°ą„ ą¤°ą¤ą„ą¤",
+ "SyncResourcesModal.syncButtonLabel": "ą¤øą¤æą¤ą¤",
+ "SyncResourcesModal.syncExercisesExplainer": "Update questions, answers, and hints in exercises and quizzes",
"SyncResourcesModal.syncExercisesTitle": "Assessment details",
- "SyncResourcesModal.syncFilesExplainer": "Update all file information",
+ "SyncResourcesModal.syncFilesExplainer": "Update all files, including: thumbnails, subtitles, and captions",
"SyncResourcesModal.syncFilesTitle": "Files",
- "SyncResourcesModal.syncModalExplainer": "Sync and update your resources with their original source.",
+ "SyncResourcesModal.syncModalExplainer": "Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files.",
+ "SyncResourcesModal.syncModalSelectAttributes": "Select what you would like to sync:",
"SyncResourcesModal.syncModalTitle": "Sync resources",
- "SyncResourcesModal.syncTagsExplainer": "Update all tags",
- "SyncResourcesModal.syncTagsTitle": "Tags",
+ "SyncResourcesModal.syncResourceDetailsExplainer": "Update information about the resource: learning activity, level, requirements, category, tags, audience, and source",
+ "SyncResourcesModal.syncResourceDetailsTitle": "Resource details",
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer": "Update resource titles and descriptions",
"SyncResourcesModal.syncTitlesAndDescriptionsTitle": "Titles and descriptions",
- "TechnicalTextBlock.copiedToClipboardConfirmation": "Copied to clipboard",
+ "TechnicalTextBlock.copiedToClipboardConfirmation": "ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤° ą¤¦ą¤æą¤Æą¤¾ ą¤ą¤Æą¤¾",
"TechnicalTextBlock.copiedToClipboardFailure": "Copy to clipboard failed",
- "TechnicalTextBlock.copyToClipboardButtonPrompt": "Copy to clipboard",
+ "TechnicalTextBlock.copyToClipboardButtonPrompt": "ą¤ą„ą¤²ą¤æą¤Ŗą¤¬ą„ą¤°ą„ą¤” ą¤Ŗą¤° ą¤ą„ą¤Ŗą„ ą¤ą¤°ą„ą¤",
"Template.templateString": "You have {count, plural,\n =1 {# node for testing}\n other {# nodes for testing}}",
"TermsOfServiceModal.ToSHeader": "Terms of Service",
"TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions",
@@ -1386,34 +1396,34 @@
"ThumbnailGenerator.thumbnailGenerationFailedHeader": "Unable to generate thumbnail",
"ThumbnailGenerator.thumbnailGenerationFailedText": "There was a problem generating a thumbnail",
"TitleStrings.catalogTitle": "Kolibri Content Library Catalog",
- "TitleStrings.defaultTitle": "Kolibri Studio",
+ "TitleStrings.defaultTitle": "Kolibri ą¤øą„ą¤ą„ą¤”ą¤æą¤Æą„",
"TitleStrings.tabTitle": "{title} - {site}",
- "ToggleText.less": "Show less",
- "ToggleText.more": "Show more",
- "TrashModal.deleteButton": "Delete",
- "TrashModal.deleteConfirmationCancelButton": "Cancel",
+ "ToggleText.less": "ą¤ą¤® ą¤¦ą¤æą¤ą¤¾ą¤ą¤",
+ "ToggleText.more": "ą¤ą¤° ą¤¦ą¤æą¤ą¤¾ą¤ą¤",
+ "TrashModal.deleteButton": "ą¤¹ą¤ą¤¾ą¤ą¤",
+ "TrashModal.deleteConfirmationCancelButton": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"TrashModal.deleteConfirmationDeleteButton": "Delete permanently",
"TrashModal.deleteConfirmationHeader": "Permanently delete {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}?",
"TrashModal.deleteConfirmationText": "You cannot undo this action. Are you sure you want to continue?",
"TrashModal.deleteSuccessMessage": "Permanently deleted",
"TrashModal.deletedHeader": "Removed",
"TrashModal.restoreButton": "Restore",
- "TrashModal.selectAllHeader": "Select all",
+ "TrashModal.selectAllHeader": "ą¤øą¤ą„ ą¤ą¤¾ ą¤ą¤Æą¤Ø ą¤ą¤°ą„ą¤",
"TrashModal.selectedCountText": "{topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}",
"TrashModal.trashEmptySubtext": "Resources removed from this channel will appear here",
"TrashModal.trashEmptyText": "Trash is empty",
"TrashModal.trashModalTitle": "Trash",
- "TreeView.closeDrawer": "Close",
+ "TreeView.closeDrawer": "ą¤¬ą¤ą¤¦ ą¤ą¤°ą„ą¤",
"TreeView.collapseAllButton": "Collapse all",
"TreeView.openCurrentLocationButton": "Expand to current folder location",
"TreeView.showSidebar": "Show sidebar",
"TreeView.updatedResourcesReadyForReview": "Updated resources are ready for review",
"TreeViewBase.apiGenerated": "Generated by API",
- "TreeViewBase.cancel": "Cancel",
+ "TreeViewBase.cancel": "ą¤°ą¤¦ą„ą¤¦ ą¤ą¤°ą„ą¤",
"TreeViewBase.channelDeletedSnackbar": "Channel deleted",
"TreeViewBase.channelDetails": "View channel details",
- "TreeViewBase.deleteChannel": "Delete channel",
- "TreeViewBase.deleteChannelButton": "Delete channel",
+ "TreeViewBase.deleteChannel": "ą¤ą„ą¤Øą¤² ą¤¹ą¤ą¤¾ą¤ą¤",
+ "TreeViewBase.deleteChannelButton": "ą¤ą„ą¤Øą¤² ą¤¹ą¤ą¤¾ą¤ą¤",
"TreeViewBase.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
"TreeViewBase.deleteTitle": "Delete this channel",
"TreeViewBase.editChannel": "Edit channel details",
@@ -1421,7 +1431,7 @@
"TreeViewBase.getToken": "Get token",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {resource is incomplete and cannot be published} other {resources are incomplete and cannot be published}}",
"TreeViewBase.noChangesText": "No changes found in channel",
- "TreeViewBase.noLanguageSetError": "Missing channel language",
+ "TreeViewBase.noLanguageSetError": "Channel language is required",
"TreeViewBase.openTrash": "Open trash",
"TreeViewBase.publishButton": "Publish",
"TreeViewBase.publishButtonTitle": "Make this channel available for import into Kolibri",
@@ -1440,19 +1450,15 @@
"UsingStudio.aboutStudioText": "Kolibri Studio is undergoing active development, and as such, some changes could cause unexpected behavior or challenges (also known as \"issues\"). If you encounter an issue, please notify us as soon as they occur to help us resolve them. (See below for instructions on how to report issues.)",
"UsingStudio.bestPractice1": "When using import and clipboard operations, work with small subsets of folders instead of whole channels at once (especially for large channels).",
"UsingStudio.bestPractice2": "It is preferable to create multiple small channels rather than one giant channel with many layers of folders.",
- "UsingStudio.bestPractice3": "Reload the page often to ensure your work is saved to the server and no network errors have occurred. Use CTRL+R on Linux/Windows or ā+R on Mac.",
- "UsingStudio.bestPractice4": "Avoid simultaneous edits on the same channel. Channels should not be edited by multiple users at the same time or by the same user in multiple browser windows.",
+ "UsingStudio.bestPractice3": "Reload the page to confirm your work has been saved to the server. Use CTRL+R on Linux/Windows or ā+R on Mac.",
"UsingStudio.bestPractice5": "It is possible that you will encounter timeout errors in your browser when performing operations like import and sync, on large channels. Don't be alarmed by this error message and do not repeat the same operation again right away. It doesn't mean the operation has failed- Kolibri Studio is still working in the background. Wait a few minutes and reload the page before continuing your edits.",
"UsingStudio.bestPractice6": "Compress videos before uploading them (see these instructions).",
"UsingStudio.bestPractice7": "PUBLISH periodically and import your channel into Kolibri to preview the content and obtain a local backup copy of your channel.",
- "UsingStudio.bestPractice8": "Do not edit the channel after you click PUBLISH. Wait for the notification email before resuming editing operations.",
"UsingStudio.bestPractice9": "Report issues as you encounter them.",
"UsingStudio.bestPractices": "Best practices",
"UsingStudio.communityStandardsLink": "Community standards",
- "UsingStudio.issue1": "Two users have reported isolated incidents where content they imported from another channel disappeared, leaving only empty folders and subfolders. In one report, the content later re-appeared. They did not experience these problems consistently, and the incidents may possibly involve issues with a slow or unstable internet connection. If you run into this issue, please contact us as soon as possible and let us know as much information as you can remember.",
- "UsingStudio.issue2": "Some operations in Studio are currently very slow, and so it may appear that the change you attempted to make timed out or did not take effect. In many cases, the change is still being processed and will appear once it is complete. If, after 5-10 minutes, the change still has not taken effect even after a browser refresh, please file an issue. We are working on solutions to these issues.",
+ "UsingStudio.issue1": "There have been reports where users have observed the disappearance of changes they've recently made to their channels. The issue seems related to opening multiple tabs of Kolibri Studio, and eventually signing out. We advise that you disable any āMemory Saver/Sleepingā tab browser feature for Kolibri Studio, and reload each tab before signing out. We're actively investigating this issue, so if you run into it, please contact us with as much information as possible.",
"UsingStudio.issueLink1": "Reports of disappearing content",
- "UsingStudio.issueLink2": "Slow performance can lead to unexpected errors in the interface",
"UsingStudio.issuesPageLink": "View all issues",
"UsingStudio.notableIssues": "Notable issues",
"UsingStudio.policiesLink": "Privacy policy",
@@ -1469,21 +1475,21 @@
"channelEditVue.errorMissingAnswer": "Choose a correct answer",
"channelEditVue.errorProvideAtLeastOneCorrectAnswer": "Provide at least one correct answer",
"channelEditVue.errorQuestionRequired": "Question is required",
- "channelEditVue.false": "False",
+ "channelEditVue.false": "ą¤ą¤²ą¤¤",
"channelEditVue.questionTypeInput": "Numeric input",
"channelEditVue.questionTypeMultipleSelection": "Multiple choice",
"channelEditVue.questionTypePerseus": "Perseus",
"channelEditVue.questionTypeSingleSelection": "Single choice",
"channelEditVue.questionTypeTrueFalse": "True/False",
- "channelEditVue.true": "True",
+ "channelEditVue.true": "ą¤øą¤¹ą„",
"formStrings.errorText": "Please fix {count, plural,\n =1 {# error}\n other {# errors}} below",
"sharedVue.activityDurationGteOne": "Value must be equal to or greater than 1",
- "sharedVue.activityDurationRequired": "This field is required",
+ "sharedVue.activityDurationRequired": "ą¤Æą¤¹ ą¤ą¤¾ą¤Øą¤ą¤¾ą¤°ą„ ą¤ą¤¼ą¤°ą„ą¤°ą„ ą¤¹ą„",
"sharedVue.activityDurationTooLongWarning": "This value is very high. Please make sure this is how long learners should work on the resource for, in order to complete it.",
"sharedVue.confirmLogout": "Changes you made may not be saved. Are you sure you want to leave this page?",
"sharedVue.copyrightHolderRequired": "Copyright holder is required",
"sharedVue.durationRequired": "Duration is required",
- "sharedVue.fieldRequired": "This field is required",
+ "sharedVue.fieldRequired": "ą¤Æą¤¹ ą¤ą¤¾ą¤Øą¤ą¤¾ą¤°ą„ ą¤ą¤¼ą¤°ą„ą¤°ą„ ą¤¹ą„",
"sharedVue.learningActivityRequired": "Learning activity is required",
"sharedVue.licenseDescriptionRequired": "Special permissions license must have a description",
"sharedVue.licenseRequired": "License is required",
@@ -1491,11 +1497,12 @@
"sharedVue.longActivityLteOneTwenty": "Value must be equal or less than 120",
"sharedVue.masteryModelMGtZero": "Must be at least 1",
"sharedVue.masteryModelMLteN": "Must be lesser than or equal to N",
- "sharedVue.masteryModelMRequired": "Required",
+ "sharedVue.masteryModelMRequired": "ą¤ą¤µą¤¶ą„ą¤Æą¤",
"sharedVue.masteryModelMWholeNumber": "Must be a whole number",
"sharedVue.masteryModelNGtZero": "Must be at least 1",
- "sharedVue.masteryModelNRequired": "Required",
+ "sharedVue.masteryModelNRequired": "ą¤ą¤µą¤¶ą„ą¤Æą¤",
"sharedVue.masteryModelNWholeNumber": "Must be a whole number",
"sharedVue.masteryModelRequired": "Mastery is required",
"sharedVue.shortActivityLteThirty": "Value must be equal or less than 30",
- "sharedVue.titleRequired": "Title is required"}
+ "sharedVue.titleRequired": "Title is required"
+}
diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po
index 213a2436b5..072b213ed6 100644
--- a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po
+++ b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-16 00:55+0000\n"
-"PO-Revision-Date: 2022-10-17 20:08\n"
+"POT-Creation-Date: 2023-05-10 21:55+0000\n"
+"PO-Revision-Date: 2023-05-24 17:17\n"
"Last-Translator: \n"
"Language-Team: Hindi\n"
"Language: hi_IN\n"
@@ -17,36 +17,8 @@ msgstr ""
"X-Crowdin-File: /unstable/django.po\n"
"X-Crowdin-File-ID: 4322\n"
-#: contentcuration/api.py:140
-msgid "Date/Time Created"
-msgstr ""
-
-#: contentcuration/api.py:141 contentcuration/api.py:142
-msgid "Not Available"
-msgstr ""
-
-#: contentcuration/api.py:145
-msgid "Ricecooker Version"
-msgstr ""
-
-#: contentcuration/api.py:150 contentcuration/utils/csv_writer.py:164
-msgid "File Size"
-msgstr ""
-
-#: contentcuration/api.py:161
-msgid "# of {}s"
-msgstr ""
-
-#: contentcuration/api.py:165
-msgid "# of Questions"
-msgstr ""
-
-#: contentcuration/api.py:175
-msgid "# of Subtitles"
-msgstr ""
-
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
-#: contentcuration/settings.py:287
+#: contentcuration/settings.py:290
msgid "Arabic"
msgstr ""
@@ -54,40 +26,40 @@ msgstr ""
msgid "The site is currently in read-only mode. Please try again later."
msgstr ""
-#: contentcuration/models.py:279
+#: contentcuration/models.py:295
msgid "Not enough space. Check your storage under Settings page."
msgstr ""
-#: contentcuration/models.py:294 contentcuration/models.py:301
+#: contentcuration/models.py:308 contentcuration/models.py:315
msgid "Out of storage! Request more space under Settings > Storage."
msgstr ""
-#: contentcuration/models.py:1410
+#: contentcuration/models.py:1730
msgid " (Original)"
msgstr ""
-#: contentcuration/settings.py:285
+#: contentcuration/settings.py:288
msgid "English"
msgstr ""
-#: contentcuration/settings.py:286
+#: contentcuration/settings.py:289
msgid "Spanish"
msgstr ""
-#: contentcuration/settings.py:288
+#: contentcuration/settings.py:291
msgid "French"
msgstr ""
-#: contentcuration/tasks.py:251
-msgid "Unknown error starting task. Please contact support."
+#: contentcuration/settings.py:292
+msgid "Portuguese"
msgstr ""
-#: contentcuration/templates/base.html:83
+#: contentcuration/templates/base.html:38
#: contentcuration/templates/channel_list.html:14
msgid "Kolibri Studio"
-msgstr ""
+msgstr "Kolibri ą¤øą„ą¤ą„ą¤”ą¤æą¤Æą„"
-#: contentcuration/templates/base.html:166
+#: contentcuration/templates/base.html:129
msgid "Contentworkshop.learningequality.org has been deprecated. Please go to studio.learningequality.org for the latest version of Studio"
msgstr ""
@@ -100,228 +72,16 @@ msgid "We're sorry, this channel was not found."
msgstr ""
#: contentcuration/templates/channel_not_found.html:24
-#: contentcuration/templates/permissions/open_channel_fail.html:14
#: contentcuration/templates/staging_not_found.html:23
#: contentcuration/templates/unauthorized.html:23
msgid "Go Home"
msgstr ""
-#: contentcuration/templates/exercise_list.html:67
-msgid "Previous"
-msgstr ""
-
-#: contentcuration/templates/exercise_list.html:73
-msgid "current"
-msgstr ""
-
-#: contentcuration/templates/exercise_list.html:77
-msgid "Next"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:206
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:165
-msgid "Language not set"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:212
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:171
-#, python-format
-msgid "This file was generated on %(date)s"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:231
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:189
-msgid "Created"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:232
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:190
-msgid "Last Published"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:233
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:191
-msgid "Unpublished"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:245
-msgid "USING THIS CHANNEL"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:247
-msgid "Copy one of the following into Kolibri to import this channel:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:252
-msgid "Tokens (Recommended):"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:260
-msgid "Channel ID:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:267
-msgid "Channel must be published to import into Kolibri"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:273
-msgid "WHAT'S INSIDE"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:283
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:213
-#, python-format
-msgid "%(count)s Resource"
-msgid_plural "%(count)s Resources"
-msgstr[0] ""
-msgstr[1] ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:304
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:234
-msgid "Includes"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:310
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:236
-msgid "Languages"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:316
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:245
-msgid "Subtitles"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:324
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:254
-msgid "For Educators"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:256
-msgid "Coach Content"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:257
-msgid "Assessments"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:334
-msgid "Content Tags"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:338
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:270
-msgid "No tags found"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:343
-#: contentcuration/templates/export/channel_detail_pdf.html:448
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:274
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:337
-msgid "This channel is empty"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:348
-msgid "SOURCE"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:360
-msgid "This channel features resources created by:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:361
-#: contentcuration/templates/export/channel_detail_pdf.html:379
-#: contentcuration/templates/export/channel_detail_pdf.html:398
-#: contentcuration/templates/export/channel_detail_pdf.html:420
-#: contentcuration/templates/export/channel_detail_pdf.html:440
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:293
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:305
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:317
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:333
-msgid "Information not available"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:378
-msgid "The material in this channel was provided by:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:397
-msgid "Material in this channel was originally hosted by:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:416
-msgid "This channel includes the following licenses:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf.html:439
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:326
-msgid "Copyright Holders:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:192
-msgid "Token:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:203
-msgid "What's Inside"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:239
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:248
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:267
-msgid "Most Common Tags"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:280
-msgid "Source Information"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:284
-msgid "Authors:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:288
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:298
-msgid "Providers:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:301
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:313
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:329
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:310
-msgid "Aggregators:"
-msgstr ""
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-msgid "Licenses:"
-msgstr ""
-
-#: contentcuration/templates/export/csv_email.txt:4
#: contentcuration/templates/export/user_csv_email.txt:4
#: contentcuration/templates/permissions/permissions_email.txt:4
#: contentcuration/templates/registration/activation_email.txt:4
#: contentcuration/templates/registration/activation_needed_email.txt:4
-#: contentcuration/templates/registration/channel_published_email.txt:4
+#: contentcuration/templates/registration/channel_published_email.html:10
#: contentcuration/templates/registration/password_reset_email.txt:3
#: contentcuration/templates/registration/registration_needed_email.txt:4
#: contentcuration/templates/settings/account_deleted_user_email.txt:3
@@ -331,50 +91,13 @@ msgstr ""
msgid "Hello %(name)s,"
msgstr ""
-#: contentcuration/templates/export/csv_email.txt:6
-#, python-format
-msgid "Your csv for %(channel_name)s has finished generating (attached)."
-msgstr ""
-
-#: contentcuration/templates/export/csv_email.txt:8
-#: contentcuration/templates/export/user_csv_email.txt:29
-#: contentcuration/templates/permissions/permissions_email.txt:21
-#: contentcuration/templates/registration/activation_email.txt:12
-#: contentcuration/templates/registration/activation_needed_email.txt:14
-#: contentcuration/templates/registration/channel_published_email.txt:15
-#: contentcuration/templates/registration/password_reset_email.txt:14
-#: contentcuration/templates/registration/registration_needed_email.txt:12
-#: contentcuration/templates/settings/account_deleted_user_email.txt:10
-#: contentcuration/templates/settings/storage_request_email.txt:46
-msgid "Thanks for using Kolibri Studio!"
-msgstr ""
-
-#: contentcuration/templates/export/csv_email.txt:10
-#: contentcuration/templates/export/user_csv_email.txt:31
-#: contentcuration/templates/permissions/permissions_email.txt:23
-#: contentcuration/templates/registration/activation_email.txt:14
-#: contentcuration/templates/registration/activation_needed_email.txt:16
-#: contentcuration/templates/registration/channel_published_email.txt:17
-#: contentcuration/templates/registration/password_reset_email.txt:16
-#: contentcuration/templates/registration/registration_needed_email.txt:14
-#: contentcuration/templates/settings/account_deleted_user_email.txt:12
-#: contentcuration/templates/settings/issue_report_email.txt:26
-#: contentcuration/templates/settings/storage_request_email.txt:48
-msgid "The Learning Equality Team"
-msgstr ""
-
-#: contentcuration/templates/export/csv_email_subject.txt:1
-#, python-format
-msgid "CSV for %(channel_name)s"
-msgstr ""
-
#: contentcuration/templates/export/user_csv_email.txt:6
msgid "Here is the information for your Kolibri Studio account"
msgstr ""
#: contentcuration/templates/export/user_csv_email.txt:8
msgid "Name:"
-msgstr ""
+msgstr "ą¤Øą¤¾ą¤®:"
#: contentcuration/templates/export/user_csv_email.txt:9
#: contentcuration/templates/registration/password_reset_form.html:23
@@ -414,43 +137,64 @@ msgstr ""
msgid "If you have any questions or concerns, please email us at %(legal_email)s."
msgstr ""
-#: contentcuration/templates/export/user_csv_email_subject.txt:1
-msgid "Your Kolibri Studio account information"
+#: contentcuration/templates/export/user_csv_email.txt:29
+#: contentcuration/templates/permissions/permissions_email.txt:21
+#: contentcuration/templates/registration/activation_email.txt:12
+#: contentcuration/templates/registration/activation_needed_email.txt:14
+#: contentcuration/templates/registration/channel_published_email.html:23
+#: contentcuration/templates/registration/password_reset_email.txt:14
+#: contentcuration/templates/registration/registration_needed_email.txt:12
+#: contentcuration/templates/settings/account_deleted_user_email.txt:10
+#: contentcuration/templates/settings/storage_request_email.txt:46
+msgid "Thanks for using Kolibri Studio!"
msgstr ""
-#: contentcuration/templates/permissions/open_channel_fail.html:12
-msgid "There was an error opening this channel."
+#: contentcuration/templates/export/user_csv_email.txt:31
+#: contentcuration/templates/permissions/permissions_email.html:118
+#: contentcuration/templates/permissions/permissions_email.txt:23
+#: contentcuration/templates/registration/activation_email.html:111
+#: contentcuration/templates/registration/activation_email.txt:14
+#: contentcuration/templates/registration/activation_needed_email.txt:16
+#: contentcuration/templates/registration/channel_published_email.html:25
+#: contentcuration/templates/registration/password_reset_email.html:111
+#: contentcuration/templates/registration/password_reset_email.txt:16
+#: contentcuration/templates/registration/registration_needed_email.txt:14
+#: contentcuration/templates/registration/welcome_new_user_email.html:172
+#: contentcuration/templates/settings/account_deleted_user_email.txt:12
+#: contentcuration/templates/settings/issue_report_email.txt:26
+#: contentcuration/templates/settings/storage_request_email.txt:48
+msgid "The Learning Equality Team"
msgstr ""
-#: contentcuration/templates/permissions/open_channel_fail.html:13
-msgid "Try running ricecooker again."
+#: contentcuration/templates/export/user_csv_email_subject.txt:1
+msgid "Your Kolibri Studio account information"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:93
-#: contentcuration/templates/registration/activation_email.html:91
-#: contentcuration/templates/registration/password_reset_email.html:91
+#: contentcuration/templates/permissions/permissions_email.html:92
+#: contentcuration/templates/registration/activation_email.html:90
+#: contentcuration/templates/registration/password_reset_email.html:90
msgid "Hello"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:94
+#: contentcuration/templates/permissions/permissions_email.html:93
msgid "has invited you to edit a channel at"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:100
+#: contentcuration/templates/permissions/permissions_email.html:99
#, python-format
msgid "Invititation to %(share_mode)s channel"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:104
+#: contentcuration/templates/permissions/permissions_email.html:103
msgid "Click one of the following links to either accept or decline your invitation:"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:107
-#: contentcuration/templates/permissions/permissions_email.html:109
+#: contentcuration/templates/permissions/permissions_email.html:106
+#: contentcuration/templates/permissions/permissions_email.html:108
msgid "ACCEPT"
msgstr ""
-#: contentcuration/templates/permissions/permissions_email.html:112
+#: contentcuration/templates/permissions/permissions_email.html:111
msgid "DECLINE"
msgstr ""
@@ -488,24 +232,24 @@ msgstr ""
msgid "You've been invited to view %(channel)s"
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:92
-msgid "Welcome to Kolibri! Here is the link to activate your account:"
+#: contentcuration/templates/registration/activation_email.html:91
+msgid "Welcome to Kolibri Studio! Here is the link to activate your account:"
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:101
+#: contentcuration/templates/registration/activation_email.html:100
msgid "Click here to activate your account."
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
msgid "This link is valid for"
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
#, python-format
msgid "%(expiration_days)s days."
msgstr ""
-#: contentcuration/templates/registration/activation_email.html:104
+#: contentcuration/templates/registration/activation_email.html:103
msgid "ACTIVATE"
msgstr ""
@@ -533,27 +277,31 @@ msgstr ""
msgid "Please activate your account by following the link below:"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:6
+#: contentcuration/templates/registration/channel_published_email.html:12
#, python-format
-msgid "%(channel_name)s has finished publishing! Here is the channel token (for importing it into Kolibri):"
+msgid "%(channel_name)s"
+msgstr ""
+
+#: contentcuration/templates/registration/channel_published_email.html:12
+msgid "has finished publishing! Here is the channel token (for importing it into Kolibri):"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:8
+#: contentcuration/templates/registration/channel_published_email.html:15
#, python-format
msgid "Token: %(channel_token)s"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:10
+#: contentcuration/templates/registration/channel_published_email.html:17
#, python-format
msgid "ID (for Kolibri version 0.6.0 and below): %(channel_id)s"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:12
+#: contentcuration/templates/registration/channel_published_email.html:20
#, python-format
msgid "Version notes: %(notes)s"
msgstr ""
-#: contentcuration/templates/registration/channel_published_email.txt:21
+#: contentcuration/templates/registration/channel_published_email.html:28
msgid "You are receiving this email because you are subscribed to this channel."
msgstr ""
@@ -582,23 +330,23 @@ msgstr ""
msgid "Request a new password reset."
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:92
+#: contentcuration/templates/registration/password_reset_email.html:91
msgid "You are receiving this e-mail because you requested a password reset for your user account at"
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:98
+#: contentcuration/templates/registration/password_reset_email.html:97
msgid "Reset my Password"
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:101
+#: contentcuration/templates/registration/password_reset_email.html:100
msgid "Please click the button below and choose a new password."
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:102
+#: contentcuration/templates/registration/password_reset_email.html:101
msgid "Your username is"
msgstr ""
-#: contentcuration/templates/registration/password_reset_email.html:104
+#: contentcuration/templates/registration/password_reset_email.html:103
msgid "RESET"
msgstr ""
@@ -641,6 +389,122 @@ msgstr ""
msgid "Please create an account by following the link below:"
msgstr ""
+#: contentcuration/templates/registration/welcome_new_user_email.html:78
+msgid "Welcome to Kolibri Studio!"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:82
+#, python-format
+msgid "\n"
+" We're delighted to introduce you to Kolibri Studio , our curricular tool to add,\n"
+" organize, and manage your own resources or those from the Kolibri Content Library.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:87
+msgid "View the Kolibri Content Library"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:93
+msgid "\n"
+" Using Kolibri Studio, you can explore pre-organized collections of open educational resources (OER), and bundle,\n"
+" tag, differentiate, re-order, and distribute them into custom channels.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:99
+msgid "\n"
+" Using an admin account, you can then publish and import these custom channels--either your own or those shared\n"
+" with you -- into Kolibri with a unique \"token\" generated for each channel.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:107
+msgid "\n"
+" Browse through the list of resources below* to learn more about Kolibri Studio and to begin creating your own\n"
+" custom channels:\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:115
+msgid "Kolibri Studio User Guide"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:120
+msgid "Content integration guide:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:122
+msgid "\n"
+" Information on licensing, compatible formats, technical integration and more.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:125
+msgid "\n"
+" Note that if you are adding a small number of resources, technical integration is not necessary. \n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:130
+msgid "Step by step tutorials:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:133
+msgid "Video format:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:135
+msgid "Using Kolibri Studio: Your Content Workspace for Kolibri"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:137
+msgid "(*also available in French and Arabic)"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:140
+msgid "Slide gif format:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:144
+msgid "Step by step Studio tutorial"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:151
+msgid "Video compression instructions:"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:153
+msgid "\n"
+" For optimal results, videos should be compressed in order to achieve small file sizes. Compression ensures\n"
+" that the videos are well suited for offline distribution and playback on all Kolibri devices.\n"
+" "
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:158
+msgid "View the guide to video compression"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:165
+msgid "If you need support with Kolibri Studio, please reach out to us on our Community Forum."
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:167
+msgid "Access the Community Forum"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:171
+msgid "Thank You!"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:178
+msgid "*resources are presented in English"
+msgstr ""
+
+#: contentcuration/templates/registration/welcome_new_user_email_subject.txt:1
+msgid "Thank you for activating your Kolibri Studio account! Let's get started..."
+msgstr ""
+
#: contentcuration/templates/settings/account_deleted_user_email.txt:5
#, python-format
msgid "Your %(email)s account on %(site_name)s has been deleted."
@@ -693,7 +557,7 @@ msgstr ""
#: contentcuration/templates/unsupported_browser.html:49
msgid "Sorry, your browser version is not supported."
-msgstr ""
+msgstr "ą¤ą„ą¤·ą¤®ą¤¾ ą¤ą¤°ą„ą¤, ą¤ą¤Ŗą¤ą¤¾ ą¤¬ą„ą¤°ą¤¾ą¤ą¤ą¤¼ą¤° ą¤øą¤ą¤øą„ą¤ą¤°ą¤£ ą¤øą¤®ą¤°ą„ą¤„ą¤æą¤¤ ą¤Øą¤¹ą„ą¤ ą¤¹ą„ą„¤"
#: contentcuration/templates/unsupported_browser.html:51
msgid "To use Kolibri Studio, we recommend using Firefox or Chrome."
@@ -701,190 +565,109 @@ msgstr ""
#: contentcuration/templates/unsupported_browser.html:53
msgid "You can also try updating your current browser."
-msgstr ""
+msgstr "ą¤ą¤Ŗ ą¤
ą¤Ŗą¤Øą„ ą¤µą¤°ą„ą¤¤ą¤®ą¤¾ą¤Ø ą¤¬ą„ą¤°ą¤¾ą¤ą¤ą¤¼ą¤° ą¤ą„ ą¤
ą¤Ŗą¤”ą„ą¤ ą¤ą¤°ą¤Øą„ ą¤ą¤¾ ą¤Ŗą„ą¤°ą¤Æą¤¾ą¤ø ą¤ą„ ą¤ą¤° ą¤øą¤ą¤¤ą„ ą¤¹ą„ą¤ ą„¤"
-#: contentcuration/templatetags/license_tags.py:10
+#: contentcuration/templatetags/license_tags.py:11
msgid "The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:15
+#: contentcuration/templatetags/license_tags.py:16
msgid "The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to \"copyleft\" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:25
+#: contentcuration/templatetags/license_tags.py:26
msgid "The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:28
+#: contentcuration/templatetags/license_tags.py:29
msgid "The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:32
+#: contentcuration/templatetags/license_tags.py:33
msgid "The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:36
+#: contentcuration/templatetags/license_tags.py:37
msgid "The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:40
+#: contentcuration/templatetags/license_tags.py:41
msgid "The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:43
+#: contentcuration/templatetags/license_tags.py:44
msgid "Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:46
+#: contentcuration/templatetags/license_tags.py:47
msgid "Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails."
msgstr ""
-#: contentcuration/templatetags/translation_tags.py:26
-msgid "100% Correct"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:27
-msgid "10 in a row"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:28
-msgid "2 in a row"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:29
-msgid "3 in a row"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:30
-msgid "5 in a row"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:31
-msgid "M of N..."
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:32
-msgid "CC BY"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:33
-msgid "CC BY-SA"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:34
-msgid "CC BY-ND"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:35
-msgid "CC BY-NC"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:36
-msgid "CC BY-NC-SA"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:37
-msgid "CC BY-NC-ND"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:38
-msgid "All Rights Reserved"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:39
-msgid "Public Domain"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:40
-msgid "Special Permissions"
-msgstr ""
-
-#: contentcuration/templatetags/translation_tags.py:49
-#, python-format
-msgid "%(filesize)s %(unit)s"
-msgstr ""
-
-#: contentcuration/utils/csv_writer.py:138
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:45
+#: contentcuration/utils/csv_writer.py:108
msgid "No Channel"
msgstr ""
-#: contentcuration/utils/csv_writer.py:139
+#: contentcuration/utils/csv_writer.py:46
msgid "No resource"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Channel"
-msgstr ""
+msgstr "ą¤ą„ą¤Øą¤²"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Title"
-msgstr ""
+msgstr "ą¤¶ą„ą¤°ą„ą¤·ą¤"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Kind"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Filename"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
+msgid "File Size"
+msgstr ""
+
+#: contentcuration/utils/csv_writer.py:71
msgid "URL"
msgstr ""
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Description"
-msgstr ""
+msgstr "ą¤µą¤æą¤µą¤°ą¤£"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Author"
-msgstr ""
+msgstr "ą¤²ą„ą¤ą¤"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Language"
-msgstr ""
+msgstr "ą¤ą¤¾ą¤·ą¤¾Ā "
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License"
-msgstr ""
+msgstr "ą¤²ą¤¾ą¤ą¤øą„ą¤ą¤ø"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License Description"
msgstr ""
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Copyright Holder"
msgstr ""
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "No Resource"
msgstr ""
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "Staged File"
msgstr ""
-#: contentcuration/utils/format.py:15
-msgid "B"
-msgstr ""
-
-#: contentcuration/utils/format.py:17
-msgid "KB"
-msgstr ""
-
-#: contentcuration/utils/format.py:19
-msgid "MB"
-msgstr ""
-
-#: contentcuration/utils/format.py:21
-msgid "GB"
-msgstr ""
-
-#: contentcuration/utils/format.py:23
-msgid "TB"
-msgstr ""
-
#: contentcuration/utils/incidents.py:7
msgid "There was a problem with a third-party service. This means certain operations might be blocked. We appreciate your patience while these issues are being resolved."
msgstr ""
@@ -909,23 +692,26 @@ msgstr ""
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here "
msgstr ""
-#: contentcuration/utils/publish.py:57
+#: contentcuration/utils/publish.py:96
msgid "Kolibri Studio Channel Published"
msgstr ""
-#: contentcuration/views/public.py:63 contentcuration/views/public.py:74
-msgid "Api endpoint {} is not available"
+#: contentcuration/views/settings.py:111
+msgid "Kolibri Studio issue report"
msgstr ""
-#: contentcuration/views/public.py:76
-msgid "No channel matching {} found"
+#: contentcuration/views/settings.py:143
+msgid "Kolibri Studio account deleted"
msgstr ""
-#: contentcuration/views/settings.py:110
-msgid "Kolibri Studio issue report"
-msgstr ""
+#: kolibri_public/views.py:220
+msgid "Resource"
+msgstr "ą¤øą¤ą¤øą¤¾ą¤§ą¤Ø"
-#: contentcuration/views/settings.py:144
-msgid "Kolibri Studio account deleted"
+#: kolibri_public/views_v1.py:63 kolibri_public/views_v1.py:74
+msgid "Api endpoint {} is not available"
msgstr ""
+#: kolibri_public/views_v1.py:76
+msgid "No channel matching {} found"
+msgstr ""
diff --git a/contentcuration/locale/language_info.json b/contentcuration/locale/language_info.json
index 6b65bf6127..6c52242da3 100644
--- a/contentcuration/locale/language_info.json
+++ b/contentcuration/locale/language_info.json
@@ -1,11 +1,4 @@
[
- {
- "crowdin_code": "ach",
- "intl_code": "ach-ug",
- "language_name": "In-context translation",
- "english_name": "In-context translation",
- "default_font": "NotoSans"
- },
{
"crowdin_code": "ar",
"intl_code": "ar",
@@ -13,27 +6,6 @@
"english_name": "Arabic",
"default_font": "NotoSansArabic"
},
- {
- "crowdin_code": "bg",
- "intl_code": "bg-bg",
- "language_name": "ŠŃŠ»Š³Š°ŃŃŠŗŠø",
- "english_name": "Bulgarian",
- "default_font": "NotoSans"
- },
- {
- "crowdin_code": "bn",
- "intl_code": "bn-bd",
- "language_name": "ą¦¬ą¦¾ą¦ą¦²ą¦¾",
- "english_name": "Bengali",
- "default_font": "NotoSansBengali"
- },
- {
- "crowdin_code": "de",
- "intl_code": "de",
- "language_name": "Deutsch",
- "english_name": "German",
- "default_font": "NotoSans"
- },
{
"crowdin_code": "en",
"intl_code": "en",
@@ -48,20 +20,6 @@
"english_name": "Spanish (Spain)",
"default_font": "NotoSans"
},
- {
- "crowdin_code": "la",
- "intl_code": "es-419",
- "language_name": "EspaƱol",
- "english_name": "Spanish (Latin America)",
- "default_font": "NotoSans"
- },
- {
- "crowdin_code": "fa",
- "intl_code": "fa",
- "language_name": "ŁŲ§Ų±Ų³Ū",
- "english_name": "Farsi",
- "default_font": "NotoSansArabic"
- },
{
"crowdin_code": "fr",
"intl_code": "fr-fr",
@@ -69,20 +27,6 @@
"english_name": "French",
"default_font": "NotoSans"
},
- {
- "crowdin_code": "fv",
- "intl_code": "ff-cm",
- "language_name": "Fulfulde Mbororoore",
- "english_name": "Fulfulde (Cameroon)",
- "default_font": "NotoSans"
- },
- {
- "crowdin_code": "gu-IN",
- "intl_code": "gu-in",
- "language_name": "ąŖą«ąŖąŖ°ąŖ¾ąŖ¤ą«",
- "english_name": "Gujarati",
- "default_font": "NotoSansGujarati"
- },
{
"crowdin_code": "hi",
"intl_code": "hi-in",
@@ -90,95 +34,11 @@
"english_name": "Hindi (India)",
"default_font": "NotoSansDevanagari"
},
- {
- "crowdin_code": "it",
- "intl_code": "it",
- "language_name": "Italiano",
- "english_name": "Italian",
- "default_font": "NotoSans"
- },
- {
- "crowdin_code": "km",
- "intl_code": "km",
- "language_name": "įį¶įį¶įįįįį",
- "english_name": "Khmer",
- "default_font": "NotoSansKhmer"
- },
- {
- "crowdin_code": "ko",
- "intl_code": "ko",
- "language_name": "ķźµģ“",
- "english_name": "Korean",
- "default_font": "NotoSans"
- },
- {
- "crowdin_code": "mr",
- "intl_code": "mr",
- "language_name": "ą¤®ą¤°ą¤¾ą¤ ą„",
- "english_name": "Marathi",
- "default_font": "NotoSansDevanagari"
- },
- {
- "crowdin_code": "my",
- "intl_code": "my",
- "language_name": "įįį¬į
į¬",
- "english_name": "Burmese",
- "default_font": "NotoSansMyanmar"
- },
- {
- "crowdin_code": "ny",
- "intl_code": "nyn",
- "language_name": "Chinyanja",
- "english_name": "Chichewa, Chewa, Nyanja",
- "default_font": "NotoSans"
- },
{
"crowdin_code": "pt-BR",
"intl_code": "pt-br",
"language_name": "PortuguĆŖs",
"english_name": "Portuguese (Brazil)",
"default_font": "NotoSans"
- },
- {
- "crowdin_code": "sw-TZ",
- "intl_code": "sw-tz",
- "language_name": "Kiswahili",
- "english_name": "Swahili",
- "default_font": "NotoSans"
- },
- {
- "crowdin_code": "te",
- "intl_code": "te",
- "language_name": "ą°¤ą±ą°²ą±ą°ą±",
- "english_name": "Telugu",
- "default_font": "NotoSansTelugu"
- },
- {
- "crowdin_code": "ur-PK",
- "intl_code": "ur-pk",
- "language_name": "Ų§ŁŲ±ŲÆŁ (Ł¾Ų§Ś©Ų³ŲŖŲ§Ł)ā",
- "english_name": "Urdu (Pakistan)",
- "default_font": "NotoSansArabic"
- },
- {
- "crowdin_code": "vi",
- "intl_code": "vi",
- "language_name": "Tiįŗæng Viį»t",
- "english_name": "Vietnamese",
- "default_font": "NotoSans"
- },
- {
- "crowdin_code": "yo",
- "intl_code": "yo",
- "language_name": "YorĆ¹bĆ”",
- "english_name": "Yoruba",
- "default_font": "NotoSans"
- },
- {
- "crowdin_code": "zh-CN",
- "intl_code": "zh-hans",
- "language_name": "ē®ä½äøę",
- "english_name": "Simplified Chinese",
- "default_font": "NotoSans"
}
]
diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/README.md b/contentcuration/locale/pt_BR/LC_MESSAGES/README.md
new file mode 100644
index 0000000000..0f82b94d50
--- /dev/null
+++ b/contentcuration/locale/pt_BR/LC_MESSAGES/README.md
@@ -0,0 +1 @@
+The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.csv b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.csv
index 36e910ecd1..9935240ffb 100644
--- a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.csv
+++ b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.csv
@@ -74,13 +74,13 @@
"AccountCreated.accountCreatedTitle","Account successfully created","
-- CONTEXT --
","Conta criada com sucesso"
-"AccountCreated.continueToSignIn","Continue to sign-in","
+"AccountCreated.backToLogin","Continue to sign-in page","
-- CONTEXT --
-","Continuar para iniciar sessĆ£o"
+","Continuar para a pƔgina de login"
"AccountDeleted.accountDeletedTitle","Account successfully deleted","
-- CONTEXT --
","Conta excluĆda com sucesso"
-"AccountDeleted.continueToSignIn","Continue to sign-in page","
+"AccountDeleted.backToLogin","Continue to sign-in page","
-- CONTEXT --
","Continuar para a pƔgina de login"
"AccountNotActivated.requestNewLink","Request a new activation link","
@@ -287,9 +287,6 @@
"BrowsingCard.coach","Resource for coaches","
-- CONTEXT --
","Material para professores"
-"BrowsingCard.goToPluralLocationsAction","In {count, number} {count, plural, one {location} other {locations}}","
--- CONTEXT --
-","Em {count, number} {count, plural, one {local} other {locais}}"
"BrowsingCard.goToSingleLocationAction","Go to location","
-- CONTEXT --
","Ir para o local"
@@ -1272,6 +1269,9 @@ A type of math category. See https://en.wikipedia.org/wiki/Algebra","Ćlgebra"
"CommonMetadataStrings.all","All","
-- CONTEXT --
A label for everything in the group of activities.","Todas"
+"CommonMetadataStrings.allContent","Viewed in its entirety","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners studied it all, for example they saw all pages of a document.","Visualizado na sua totalidade"
"CommonMetadataStrings.allLevelsBasicSkills","All levels -- basic skills","
-- CONTEXT --
Refers to a type of educational level.","Todos os nĆveis -- habilidades bĆ”sicas"
@@ -1323,6 +1323,9 @@ Science category type. See https://en.wikipedia.org/wiki/Chemistry","QuĆmica"
"CommonMetadataStrings.civicEducation","Civic education","
-- CONTEXT --
Category type. Civic education is the study of the rights and obligations of citizens in society. See https://en.wikipedia.org/wiki/Civics","EducaĆ§Ć£o CĆvica"
+"CommonMetadataStrings.completeDuration","When time spent is equal to duration","
+-- CONTEXT --
+One of the completion criteria types. A resource with this criteria is considered complete when learners spent given time studying it.","Quando o tempo gasto no conteĆŗdo Ć© igual Ć duraĆ§Ć£o do mesmo"
"CommonMetadataStrings.completion","Completion","CommonMetadataStrings.completion
-- CONTEXT --
@@ -1342,6 +1345,9 @@ Category type. See https://en.wikipedia.org/wiki/Everyday_life","Cotidiano"
"CommonMetadataStrings.dance","Dance","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Dance","DanƧa"
+"CommonMetadataStrings.determinedByResource","Determined by the resource","
+-- CONTEXT --
+One of the completion criteria types. Typically used for embedded html5/h5p resources that contain their own completion criteria, for example reaching a score in an educational game.","PrĆ©-determinado pelo prĆ³prio conteĆŗdo"
"CommonMetadataStrings.digitalLiteracy","Digital literacy","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Digital_literacy","AlfabetizaĆ§Ć£o Digital"
@@ -1363,6 +1369,9 @@ Category type. See https://en.wikipedia.org/wiki/Entrepreneurship","Empreendedor
"CommonMetadataStrings.environment","Environment","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Environmental_studies","Meio Ambiente"
+"CommonMetadataStrings.exactTime","Time to complete","
+-- CONTEXT --
+One of the completion criteria types. A subset of ""When time spent is equal to duration"". For example, for an audio resource with this criteria, learnes need to hear the whole length of audio for the resource to be considered complete.","Tempo necessĆ”rio para a conclusĆ£o do conteĆŗdo"
"CommonMetadataStrings.explore","Explore","
-- CONTEXT --
Resource and filter label for the type of learning activity. Translate as a VERB","Explorar"
@@ -1378,6 +1387,9 @@ Category type","Para Educadores"
"CommonMetadataStrings.geometry","Geometry","
-- CONTEXT --
Category type.","Geometria"
+"CommonMetadataStrings.goal","When goal is met","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners reached a given goal, for example 100% correct.","Quando o objetivo for alcanƧado"
"CommonMetadataStrings.guides","Guides","
-- CONTEXT --
Category label in the Kolibri resources library; refers to any guide-type material for teacher professional development.","Guias"
@@ -1428,6 +1440,9 @@ Refers to a level of learning. Approximately corresponds to the first half of pr
"CommonMetadataStrings.lowerSecondary","Lower secondary","
-- CONTEXT --
Refers to a level of learning. Approximately corresponds to the first half of secondary school (high school).","Ensino MĆ©dio"
+"CommonMetadataStrings.masteryMofN","Goal: {m} out of {n}","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria is considered complete when learners answered m questions out of n correctly.","Objetivo: {m} de {n}"
"CommonMetadataStrings.mathematics","Mathematics","
-- CONTEXT --
Category type. See https://en.wikipedia.org/wiki/Mathematics","MatemƔtica"
@@ -1465,6 +1480,9 @@ Category type. See https://en.wikipedia.org/wiki/Political_science.","CiĆŖncias
"CommonMetadataStrings.practice","Practice","
-- CONTEXT --
Resource and filter label for the type of learning activity with questions and answers. Translate as a VERB","Praticar"
+"CommonMetadataStrings.practiceQuiz","Practice quiz","
+-- CONTEXT --
+One of the completion criteria types specific to exercises. An exercise with this criteria represents a quiz.","Teste prƔtico"
"CommonMetadataStrings.preschool","Preschool","
-- CONTEXT --
Refers to a level of education offered to children before they begin compulsory education at primary school.
@@ -1491,6 +1509,9 @@ School subject category","Leitura e escrita"
"CommonMetadataStrings.readingComprehension","Reading comprehension","
-- CONTEXT --
Category type.","CompreensĆ£o de texto"
+"CommonMetadataStrings.reference","Reference material","
+-- CONTEXT --
+One of the completion criteria types. Progress made on a resource with this criteria is not tracked.","Materiais de referĆŖncia"
"CommonMetadataStrings.reflect","Reflect","
-- CONTEXT --
Resource and filter label for the type of learning activity. Translate as a VERB","Refletir"
@@ -1614,27 +1635,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escrita"
"CommunityStandardsModal.studioItem5","Hosting. Uploading your own materials (limited to materials you know are appropriately licensed to do so) from a local hard drive or other locations on the internet","
-- CONTEXT --
","Hospedagem. Enviar seus prĆ³prios materiais (limitado a materiais que vocĆŖ sabe serem licenciados de forma apropriada) para um disco rĆgido local ou outras localidades na internet"
-"CompletionOptions.allContent","Viewed in its entirety","
--- CONTEXT --
-","Visualizado na sua totalidade"
-"CompletionOptions.completeDuration","When time spent is equal to duration","
--- CONTEXT --
-","Quando o tempo gasto no conteĆŗdo Ć© igual Ć duraĆ§Ć£o do mesmo"
-"CompletionOptions.determinedByResource","Determined by the resource","
--- CONTEXT --
-","PrĆ©-determinado pelo prĆ³prio conteĆŗdo"
-"CompletionOptions.exactTime","Time to complete","
+"CompletionOptions.learnersCanMarkComplete","Allow learners to mark as complete","
-- CONTEXT --
-","Tempo necessĆ”rio para a conclusĆ£o do conteĆŗdo"
-"CompletionOptions.goal","When goal is met","
--- CONTEXT --
-","Quando o objetivo for alcanƧado"
-"CompletionOptions.practiceQuiz","Practice quiz","
--- CONTEXT --
-","Teste para praticar"
-"CompletionOptions.reference","Reference material","
--- CONTEXT --
-","Materiais de referĆŖncia"
+","Permitir que os alunos marquem como 'concluĆdo'"
"CompletionOptions.referenceHint","Progress will not be tracked on reference material unless learners mark it as complete","
-- CONTEXT --
","O progresso do aluno sobre um material de referĆŖncia sĆ³ serĆ” registrado se o aluno o marcar como 'concluĆdo'"
@@ -1926,9 +1929,27 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escrita"
"ContentNodeChangedIcon.isUpdatedTopic","Folder has been updated since last publish","
-- CONTEXT --
","A pasta foi atualizada desde sua Ćŗltima publicaĆ§Ć£o"
+"ContentNodeCopyTaskProgress.copyErrorTopic","Some resources failed to copy","
+-- CONTEXT --
+","Alguns conteĆŗdos nĆ£o foram copiados"
+"ContentNodeEditListItem.copiedSnackbar","Copy operation complete","
+-- CONTEXT --
+","CĆ³pia concluĆda"
+"ContentNodeEditListItem.creatingCopies","Copying...","
+-- CONTEXT --
+","Copiando..."
"ContentNodeEditListItem.optionsTooltip","Options","
-- CONTEXT --
","OpƧƵes"
+"ContentNodeEditListItem.removeNode","Remove","
+-- CONTEXT --
+","Remover"
+"ContentNodeEditListItem.retryCopy","Retry","
+-- CONTEXT --
+","Tentar novamente"
+"ContentNodeEditListItem.undo","Undo","
+-- CONTEXT --
+","Desfazer"
"ContentNodeIcon.audio","Audio","
-- CONTEXT --
","Ćudio"
@@ -1956,12 +1977,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escrita"
"ContentNodeLearningActivityIcon.multipleLearningActivities","Multiple learning activities","
-- CONTEXT --
","ConteĆŗdo com mĆŗltiplas atividades"
-"ContentNodeLearningActivityIcon.topic","Folder","
--- CONTEXT --
-","Pasta"
"ContentNodeListItem.coachTooltip","Resource for coaches","
-- CONTEXT --
","Material para professores"
+"ContentNodeListItem.copyingError","Copy failed.","
+-- CONTEXT --
+","Falha ao copiar."
"ContentNodeListItem.copyingTask","Copying","
-- CONTEXT --
","Copiando"
@@ -2013,9 +2034,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escrita"
"ContentNodeOptions.newSubtopic","New folder","
-- CONTEXT --
","Nova pasta"
-"ContentNodeOptions.remove","Remove","
+"ContentNodeOptions.remove","Delete","
-- CONTEXT --
-","Remover"
+","Excluir"
"ContentNodeOptions.removedFromClipboard","Deleted from clipboard","
-- CONTEXT --
","ExcluĆdo da Ć”rea de transferĆŖncia"
@@ -2121,12 +2142,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escrita"
"CountryField.noCountriesFound","No countries found","
-- CONTEXT --
","Nenhum paĆs encontrado"
-"Create.ToSCheck","I have read and agree to the terms of service","
+"Create.ToSRequiredMessage","Please accept our terms of service and policy","
-- CONTEXT --
-","Eu li e concordo com os termos de serviƧo"
-"Create.ToSRequiredMessage","Please accept our terms of service","
+","Pedimos que aceite nossos Termos de ServiƧo e PolĆtica de Privacidade"
+"Create.agreement","I have read and agree to terms of service and the privacy policy","
-- CONTEXT --
-","Por favor, aceite os nossos termos de serviƧo"
+","Eu li e concordo com os Termos de ServiƧo e a PolĆtica de Privacidade"
"Create.backToLoginButton","Sign in","
-- CONTEXT --
","Iniciar sessĆ£o"
@@ -2217,12 +2238,6 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escrita"
"Create.personalDemoSourceOption","Personal demo","
-- CONTEXT --
","DemonstraĆ§Ć£o pessoal"
-"Create.privacyPolicyCheck","I have read and agree to the privacy policy","
--- CONTEXT --
-","Eu li e concordo com a polĆtica de privacidade"
-"Create.privacyPolicyRequiredMessage","Please accept our privacy policy","
--- CONTEXT --
-","Por favor, aceite a nossa polĆtica de privacidade"
"Create.registrationFailed","There was an error registering your account. Please try again","
-- CONTEXT --
","Ocorreu um erro ao registrar a sua conta. Por favor, tente novamente"
@@ -2259,12 +2274,12 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escrita"
"Create.usageLabel","How do you plan on using Kolibri Studio (check all that apply)","
-- CONTEXT --
","Como voce pretende utilizar o Kolibri Studio? (Selecione todas que se aplicam)"
-"Create.viewPrivacyPolicyLink","View privacy policy","
+"Create.viewPrivacyPolicyLink","View Privacy Policy","
-- CONTEXT --
-","Ver polĆtica de privacidade"
-"Create.viewToSLink","View terms of service","
+","Visualizar a PolĆtica de Privacidade"
+"Create.viewToSLink","View Terms of Service","
-- CONTEXT --
-","Ver termos de serviƧo"
+","Visualizar os Termos de ServiƧo"
"Create.websiteSourceOption","Learning Equality website","
-- CONTEXT --
","Website da Learning Equality"
@@ -2405,6 +2420,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escrita"
"Details.authorsLabel","Authors","
-- CONTEXT --
","Autores"
+"Details.categoriesHeading","Categories","
+-- CONTEXT --
+","Categorias"
"Details.coachDescription","Resources for coaches are only visible to coaches in Kolibri","
-- CONTEXT --
","Os materiais para professores sĆ£o visĆveis apenas para eles no Kolibri"
@@ -2429,6 +2447,9 @@ Category type. See https://en.wikipedia.org/wiki/Writing","Escrita"
"Details.languagesHeading","Languages","
-- CONTEXT --
","Idiomas"
+"Details.levelsHeading","Levels","
+-- CONTEXT --
+","NĆveis"
"Details.licensesLabel","Licenses","
-- CONTEXT --
","LicenƧas"
@@ -2519,9 +2540,6 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"DetailsTabView.languageHelpText","Leave blank to use the folder language","
-- CONTEXT --
","Deixar em branco para que o idioma da pasta seja utilizado"
-"DetailsTabView.learnersCanMarkComplete","Allow learners to mark as complete","
--- CONTEXT --
-","Permitir que os alunos marquem como 'concluĆdo'"
"DetailsTabView.noTagsFoundText","No results found for ""{text}"". Press 'Enter' key to create a new tag","
-- CONTEXT --
","Nenhum resultado encontrado para ""{text}"". Pressione 'Enter' para criar um novo marcador"
@@ -3012,6 +3030,9 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"MainNavigationDrawer.administrationLink","Administration","
-- CONTEXT --
","AdministraĆ§Ć£o"
+"MainNavigationDrawer.changeLanguage","Change language","
+-- CONTEXT --
+","Trocar de idioma"
"MainNavigationDrawer.channelsLink","Channels","
-- CONTEXT --
","Canais"
@@ -3174,9 +3195,6 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"PoliciesModal.checkboxText","I have agreed to the above terms","
-- CONTEXT --
","Eu concordo com os termos acima"
-"PoliciesModal.checkboxValidationErrorMessage","Field is required","
--- CONTEXT --
-","Campo obrigatĆ³rio"
"PoliciesModal.closeButton","Close","
-- CONTEXT --
","Fechar"
@@ -3207,9 +3225,12 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"ProgressModal.syncError","Last attempt to sync failed","
-- CONTEXT --
","Falha na Ćŗltima tentativa de sincronizaĆ§Ć£o"
-"ProgressModal.syncHeader","Syncing channel","
+"ProgressModal.syncHeader","Syncing resources","
-- CONTEXT --
-","Sincronizando canal"
+","Sincronizar conteĆŗdos"
+"ProgressModal.syncedSnackbar","Resources synced","
+-- CONTEXT --
+","ConteĆŗdos sincronizados"
"ProgressModal.unpublishedText","Unpublished","
-- CONTEXT --
","NĆ£o publicado"
@@ -3525,9 +3546,6 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"ResourcePanel.coachResources","Resources for coaches","
-- CONTEXT --
","Materiais para professores"
-"ResourcePanel.completion","Completion","
--- CONTEXT --
-","ConclusĆ£o"
"ResourcePanel.copyrightHolder","Copyright holder","
-- CONTEXT --
","Detentor dos direitos autorais"
@@ -3552,27 +3570,37 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"ResourcePanel.license","License","
-- CONTEXT --
","LicenƧa"
-"ResourcePanel.masteryMofN","Goal: {m} out of {n}","
--- CONTEXT --
-","Objetivo: {m} de {n}"
"ResourcePanel.nextSteps","Next steps","
-- CONTEXT --
","PrĆ³ximos passos"
-"ResourcePanel.noCopyrightHolderError","Missing copyright holder","
+"ResourcePanel.noCompletionCriteriaError","Completion criteria are required","ResourcePanel.noCompletionCriteriaError
+
-- CONTEXT --
-","Falta o detentor dos direitos autorais"
-"ResourcePanel.noFilesError","Missing files","
+Error message notification when a specific metadata is missing.","Campo obrigatĆ³rio"
+"ResourcePanel.noCopyrightHolderError","Copyright holder is required","
-- CONTEXT --
-","Faltam arquivos"
-"ResourcePanel.noLicenseDescriptionError","Missing license description","
+","O detentor dos direitos autorais Ć© obrigatĆ³rio"
+"ResourcePanel.noDurationError","Duration is required","
+-- CONTEXT --
+","Este campo Ć© obrigatĆ³rio"
+"ResourcePanel.noFilesError","File is required","ResourcePanel.noFilesError
+
-- CONTEXT --
-","Falta a descriĆ§Ć£o da licenƧa"
-"ResourcePanel.noLicenseError","Missing license","
+Error message notification when a file is missing.","Campo obrigatĆ³rio"
+"ResourcePanel.noLearningActivityError","Learning activity is required","
-- CONTEXT --
-","Falta a licenƧa"
-"ResourcePanel.noMasteryModelError","Missing mastery criteria","
+","Este campo Ć© obrigatĆ³rio"
+"ResourcePanel.noLicenseDescriptionError","License description is required","ResourcePanel.noLicenseDescriptionError
+
-- CONTEXT --
-","Faltam critĆ©rios de domĆnio"
+Error message notification when a specific metadata is missing.","Campo obrigatĆ³rio"
+"ResourcePanel.noLicenseError","License is required","
+-- CONTEXT --
+","A licenƧa Ć© obrigatĆ³ria"
+"ResourcePanel.noMasteryModelError","Mastery criteria are required","ResourcePanel.noMasteryModelError
+
+-- CONTEXT --
+Error message notification when a specific metadata is missing.","Campo obrigatĆ³rio"
"ResourcePanel.noQuestionsError","Exercise is empty","
-- CONTEXT --
","O exercĆcio estĆ” vazio"
@@ -3900,36 +3928,42 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"SyncResourcesModal.confirmSyncModalTitle","Confirm sync","
-- CONTEXT --
","Confirmar sincronizaĆ§Ć£o"
+"SyncResourcesModal.confirmSyncModalWarningExplainer","Warning: this will overwrite any changes you have made to copied or imported resources.","
+-- CONTEXT --
+","Aviso: isto irĆ” alterar quaisquer mudanƧas que tenham sido feitas previamente em conteĆŗdos copiados ou importados."
"SyncResourcesModal.continueButtonLabel","Continue","
-- CONTEXT --
","Continuar"
"SyncResourcesModal.syncButtonLabel","Sync","
-- CONTEXT --
","Sincronizar"
-"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints","
+"SyncResourcesModal.syncExercisesExplainer","Update questions, answers, and hints in exercises and quizzes","
-- CONTEXT --
-","Atualizar perguntas, respostas e dicas"
+","Atualizar perguntas, respostas e dicas nos exercĆcios e questionĆ”rios"
"SyncResourcesModal.syncExercisesTitle","Assessment details","
-- CONTEXT --
","Detalhes da avaliaĆ§Ć£o"
-"SyncResourcesModal.syncFilesExplainer","Update all file information","
+"SyncResourcesModal.syncFilesExplainer","Update all files, including: thumbnails, subtitles, and captions","
-- CONTEXT --
-","Atualizar todas as informaƧƵes do arquivo"
+","Atualizar todos os arquivos, incluindo: miniaturas, captions e legendas"
"SyncResourcesModal.syncFilesTitle","Files","
-- CONTEXT --
","Arquivos"
-"SyncResourcesModal.syncModalExplainer","Sync and update your resources with their original source.","
+"SyncResourcesModal.syncModalExplainer","Syncing resources in Kolibri Studio updates copied or imported resources in this channel with any changes made to the original resource files.","
+-- CONTEXT --
+","Sincronizar conteĆŗdos atualiza os materiais copiados ou importados desde Kolibri Studio para este canal. Isso significa que quaisquer mudanƧas que tenham sido feitas no conteĆŗdo original, aparecerĆ£o nos conteĆŗdos deste canal, mesmo que jĆ” importados, copiados ou editados."
+"SyncResourcesModal.syncModalSelectAttributes","Select what you would like to sync:","
-- CONTEXT --
-","Sincronize e atualize seus conteĆŗdos com sua fonte original."
+","Selecione o que vocĆŖ gostaria de copiar:"
"SyncResourcesModal.syncModalTitle","Sync resources","
-- CONTEXT --
","Sincronizar conteĆŗdos"
-"SyncResourcesModal.syncTagsExplainer","Update all tags","
+"SyncResourcesModal.syncResourceDetailsExplainer","Update information about the resource: learning activity, level, requirements, category, tags, audience, and source","
-- CONTEXT --
-","Atualizar todos os marcadores"
-"SyncResourcesModal.syncTagsTitle","Tags","
+","Atualizar as informaƧƵes do conteĆŗdo: atividade de aprendizagem, nĆvel, requisitos, categoria, tags, pĆŗblico e fonte"
+"SyncResourcesModal.syncResourceDetailsTitle","Resource details","
-- CONTEXT --
-","Marcadores"
+","Detalhes do conteĆŗdo"
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer","Update resource titles and descriptions","
-- CONTEXT --
","Atualizar tĆtulos e descriƧƵes dos conteĆŗdos"
@@ -4279,7 +4313,7 @@ Heading for the section in the resource editing window","ConclusĆ£o"
","ExcluĆr {topicCount, plural,
one {}=1 {# pasta}
other {# pastas}} e {resourceCount, plural,
- one {}=1 {# conteĆŗdo}
+ one {}=1 {# conteĆŗdo}
other {# conteĆŗdos}}?"
"TrashModal.deleteConfirmationText","You cannot undo this action. Are you sure you want to continue?","
-- CONTEXT --
@@ -4370,9 +4404,10 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"TreeViewBase.noChangesText","No changes found in channel","
-- CONTEXT --
","Nenhuma alteraĆ§Ć£o encontrada no canal"
-"TreeViewBase.noLanguageSetError","Missing channel language","
+"TreeViewBase.noLanguageSetError","Channel language is required","TreeViewBase.noLanguageSetError
+
-- CONTEXT --
-","Falta o idioma do canal"
+Error message notification when a specific metadata is missing.","Campo obrigatĆ³rio"
"TreeViewBase.openTrash","Open trash","
-- CONTEXT --
","Abrir lixeira"
@@ -4415,14 +4450,14 @@ Heading for the section in the resource editing window","ConclusĆ£o"
","Arquivos nĆ£o suportados"
"Uploader.unsupportedFilesText","{count, plural,
=1 {# file will not be uploaded.}
- other {# files will not be uploaded.}}
+ other {# files will not be uploaded.}}
{extensionCount, plural,
=1 {Supported file type is}
other {Supported file types are}} {extensions}","
-- CONTEXT --
","{count, plural, one {}
=1 {# arquivo nĆ£o serĆ” enviado.}
- other {# arquivos nĆ£o serĆ£o enviados.}}
+ other {# arquivos nĆ£o serĆ£o enviados.}}
{extensionCount, plural,
=1 {O tipo suportado de arquivo Ć©}
other {Os tipos suportados de arquivo sĆ£o}} {extensions}"
@@ -4441,12 +4476,9 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"UsingStudio.bestPractice2","It is preferable to create multiple small channels rather than one giant channel with many layers of folders.","
-- CONTEXT --
","Ć preferĆvel criar mĆŗltiplos canais menores ao invĆ©s de um canal gigante com muitos nĆveis e pastas."
-"UsingStudio.bestPractice3","Reload the page often to ensure your work is saved to the server and no network errors have occurred. Use CTRL+R on Linux/Windows or ā+R on Mac.","
--- CONTEXT --
-","Recarregue a pĆ”gina com frequĆŖncia para ter certeza que seu trabalho seja salvo no servidor e que nenhum erro de conexĆ£o tenha ocorrido. Use CTRL+R no Linux/Windows ou ā+R no Mac."
-"UsingStudio.bestPractice4","Avoid simultaneous edits on the same channel. Channels should not be edited by multiple users at the same time or by the same user in multiple browser windows.","
+"UsingStudio.bestPractice3","Reload the page to confirm your work has been saved to the server. Use CTRL+R on Linux/Windows or ā+R on Mac.","
-- CONTEXT --
-","Evite ediƧƵes simultĆ¢neas no mesmo canal. Os canais nĆ£o devem ser editados por vĆ”rios usuĆ”rios ao mesmo tempo ou pelo mesmo usuĆ”rio em vĆ”rias janelas do navegador."
+","Recarregue a pĆ”gina para confirmar se seu trabalho foi salvo no servidor. Use CTRL+R para Linux/Windows ou ā+R para Mac."
"UsingStudio.bestPractice5","It is possible that you will encounter timeout errors in your browser when performing operations like import and sync, on large channels. Don't be alarmed by this error message and do not repeat the same operation again right away. It doesn't mean the operation has failed- Kolibri Studio is still working in the background. Wait a few minutes and reload the page before continuing your edits.","
-- CONTEXT --
","Ć possĆvel que vocĆŖ encontre erros de timeout no seu navegador quando estiver importando ou sincronizando canais pesados, com muitos conteĆŗdos. NĆ£o se preocupe com a mensagem de erro e nĆ£o repita a operaĆ§Ć£o logo em seguida: Kolibri Studio continua trabalhando em segundo plano, mesmo depois da mensagem de erro. Apenas espere alguns minutos e recarregue a pĆ”gina antes de continuar com suas ediƧƵes."
@@ -4456,9 +4488,6 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"UsingStudio.bestPractice7","PUBLISH periodically and import your channel into Kolibri to preview the content and obtain a local backup copy of your channel.","
-- CONTEXT --
","PUBLICAR periodicamente e importe seu canal para o Kolibri para prĆ©-visualizar o conteĆŗdo e obter uma cĆ³pia de backup local do seu canal."
-"UsingStudio.bestPractice8","Do not edit the channel after you click PUBLISH. Wait for the notification email before resuming editing operations.","
--- CONTEXT --
-","NĆ£o edite o canal apĆ³s clicar em PUBLICAR. Aguarde o e-mail de notificaĆ§Ć£o antes de retomar as operaƧƵes de ediĆ§Ć£o."
"UsingStudio.bestPractice9","Report issues as you encounter them.","
-- CONTEXT --
","Relate problemas na medida em que os encontra."
@@ -4468,18 +4497,13 @@ Heading for the section in the resource editing window","ConclusĆ£o"
"UsingStudio.communityStandardsLink","Community standards","
-- CONTEXT --
","Normas comunitƔrias"
-"UsingStudio.issue1","Two users have reported isolated incidents where content they imported from another channel disappeared, leaving only empty folders and subfolders. In one report, the content later re-appeared. They did not experience these problems consistently, and the incidents may possibly involve issues with a slow or unstable internet connection. If you run into this issue, please contact us as soon as possible and let us know as much information as you can remember.","
--- CONTEXT --
-","Dois usuĆ”rios relataram incidentes isolados onde conteĆŗdos que foram importados de outro canal desapareceram, deixando as pastas e subpastas vazias. Foi reportado posteriormente que os conteĆŗdos reapareceram, e que estes problemas nĆ£o ocorreram constantemente. Esses incidentes podem ter sido causados por conta de uma conexĆ£o lenta ou instĆ”vel com a internet. Se vocĆŖ se deparar com esse problema, por favor, contate-nos assim que possĆvel, enviando o mĆ”ximo de informaƧƵes sobre o que aconteceu."
-"UsingStudio.issue2","Some operations in Studio are currently very slow, and so it may appear that the change you attempted to make timed out or did not take effect. In many cases, the change is still being processed and will appear once it is complete. If, after 5-10 minutes, the change still has not taken effect even after a browser refresh, please file an issue. We are working on solutions to these issues.","
+"UsingStudio.issue1","There have been reports where users have observed the disappearance of changes they've recently made to their channels. The issue seems related to opening multiple tabs of Kolibri Studio, and eventually signing out. We advise that you disable any āMemory Saver/Sleepingā tab browser feature for Kolibri Studio, and reload each tab before signing out. We're actively investigating this issue, so if you run into it, please contact us with as much information as possible.","UsingStudio.issue1
+
-- CONTEXT --
-","Algumas operaƧƵes no Studio sĆ£o atualmente muito lentas e, sendo assim, podem parecer que as alteraƧƵes que vocĆŖ tentou fazer foram interrompidas ou nĆ£o tiveram efeito. Muitas vezes, a alteraĆ§Ć£o ainda estĆ” sendo processada e aparecerĆ” assim que estiver completa. Se apĆ³s 5 a 10 minutos a alteraĆ§Ć£o ainda nĆ£o teve efeito, mesmo apĆ³s recarregar o navegador, por favor, registre o seu problema. Estamos trabalhando em soluƧƵes para estes transtornos."
+A description of an issue that has been reported by users - the recommendation is to disable any memory saver feature in the browser while they are using Kolibri Studio.","Alguns usuĆ”rios relataram o desaparecimento repentino de alteraƧƵes feitas recentemente em seus canais. O problema parece estar relacionado ao fato de se abrir vĆ”rias abas com Kolibri Studio ao mesmo tempo, e, logo apĆ³s, encerrar a sessĆ£o. Aconselhamos que vocĆŖ desative quaisquer funƧƵes de 'economia de memĆ³ria' ou 'dormir' do seu navegador enquanto estiver usando Kolibri Studio e recarregue cada aba antes de sair. Continuamos investigando esse problema, portanto, se vocĆŖ passar por isso, por favor entre em contato conosco com o mĆ”ximo de informaƧƵes possĆvel. Obrigado."
"UsingStudio.issueLink1","Reports of disappearing content","
-- CONTEXT --
","RelatĆ³rios de conteĆŗdo desaparecendo"
-"UsingStudio.issueLink2","Slow performance can lead to unexpected errors in the interface","
--- CONTEXT --
-","O desempenho lento pode levar a erros inesperados na interface"
"UsingStudio.issuesPageLink","View all issues","
-- CONTEXT --
","Ver todos os problemas"
diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json
index 34ec773bf5..03046534b7 100644
--- a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json
@@ -24,9 +24,9 @@
"Account.unableToDeleteAdminAccount": "NĆ£o foi possĆvel excluir uma conta de administrador",
"Account.usernameLabel": "Nome de usuƔrio",
"AccountCreated.accountCreatedTitle": "Conta criada com sucesso",
- "AccountCreated.continueToSignIn": "Continuar para iniciar sessĆ£o",
+ "AccountCreated.backToLogin": "Continuar para a pƔgina de login",
"AccountDeleted.accountDeletedTitle": "Conta excluĆda com sucesso",
- "AccountDeleted.continueToSignIn": "Continuar para a pƔgina de login",
+ "AccountDeleted.backToLogin": "Continuar para a pƔgina de login",
"AccountNotActivated.requestNewLink": "Solicitar um novo link de ativaĆ§Ć£o",
"AccountNotActivated.text": "Por favor, verifique o seu e-mail para um link de ativaĆ§Ć£o ou solicite um novo link.",
"AccountNotActivated.title": "A conta nĆ£o foi ativada",
@@ -95,7 +95,6 @@
"AssessmentTab.incompleteItemsCountMessage": "{invalidItemsCount} {invalidItemsCount, plural, one {pergunta incompleta} other {perguntas incompletas}}",
"BrowsingCard.addToClipboardAction": "Copiar para Ć”rea de transferĆŖncia",
"BrowsingCard.coach": "Material para professores",
- "BrowsingCard.goToPluralLocationsAction": "Em {count, number} {count, plural, one {local} other {locais}}",
"BrowsingCard.goToSingleLocationAction": "Ir para o local",
"BrowsingCard.hasCoachTooltip": "{value, number, integer} {value, plural, one {material para professores} other {materiais para professores}}",
"BrowsingCard.previewAction": "Ver detalhes",
@@ -414,6 +413,7 @@
"CommonMetadataStrings.accessibility": "Acessibilidade",
"CommonMetadataStrings.algebra": "Ćlgebra",
"CommonMetadataStrings.all": "Todas",
+ "CommonMetadataStrings.allContent": "Visualizado na sua totalidade",
"CommonMetadataStrings.allLevelsBasicSkills": "Todos os nĆveis -- habilidades bĆ”sicas",
"CommonMetadataStrings.allLevelsWorkSkills": "Todos os nĆveis - habilidades profissionais",
"CommonMetadataStrings.altText": "ContĆ©m descriĆ§Ć£o de imagens ou 'alt text'",
@@ -430,12 +430,14 @@
"CommonMetadataStrings.category": "Categoria",
"CommonMetadataStrings.chemistry": "QuĆmica",
"CommonMetadataStrings.civicEducation": "EducaĆ§Ć£o CĆvica",
+ "CommonMetadataStrings.completeDuration": "Quando o tempo gasto no conteĆŗdo Ć© igual Ć duraĆ§Ć£o do mesmo",
"CommonMetadataStrings.completion": "ConclusĆ£o",
"CommonMetadataStrings.computerScience": "CiĆŖncias da ComputaĆ§Ć£o",
"CommonMetadataStrings.create": "Criar",
"CommonMetadataStrings.currentEvents": "Atualidades",
"CommonMetadataStrings.dailyLife": "Cotidiano",
"CommonMetadataStrings.dance": "DanƧa",
+ "CommonMetadataStrings.determinedByResource": "PrĆ©-determinado pelo prĆ³prio conteĆŗdo",
"CommonMetadataStrings.digitalLiteracy": "AlfabetizaĆ§Ć£o Digital",
"CommonMetadataStrings.diversity": "Diversidade",
"CommonMetadataStrings.drama": "Teatro",
@@ -443,11 +445,13 @@
"CommonMetadataStrings.earthScience": "CiĆŖncias",
"CommonMetadataStrings.entrepreneurship": "Empreendedorismo",
"CommonMetadataStrings.environment": "Meio Ambiente",
+ "CommonMetadataStrings.exactTime": "Tempo necessĆ”rio para a conclusĆ£o do conteĆŗdo",
"CommonMetadataStrings.explore": "Explorar",
"CommonMetadataStrings.financialLiteracy": "EducaĆ§Ć£o Financeira",
"CommonMetadataStrings.forBeginners": "Para iniciantes",
"CommonMetadataStrings.forTeachers": "Para Educadores",
"CommonMetadataStrings.geometry": "Geometria",
+ "CommonMetadataStrings.goal": "Quando o objetivo for alcanƧado",
"CommonMetadataStrings.guides": "Guias",
"CommonMetadataStrings.highContrast": "ContĆ©m texto em alto contraste para alunos com baixa visĆ£o",
"CommonMetadataStrings.history": "HistĆ³rico",
@@ -464,6 +468,7 @@
"CommonMetadataStrings.longActivity": "Atividade longa",
"CommonMetadataStrings.lowerPrimary": "Ensino Fundamental (1o - 5o)",
"CommonMetadataStrings.lowerSecondary": "Ensino MĆ©dio",
+ "CommonMetadataStrings.masteryMofN": "Objetivo: {m} de {n}",
"CommonMetadataStrings.mathematics": "MatemƔtica",
"CommonMetadataStrings.mechanicalEngineering": "Engenharia mecĆ¢nica",
"CommonMetadataStrings.mediaLiteracy": "MĆdia e ComunicaƧƵes",
@@ -476,6 +481,7 @@
"CommonMetadataStrings.physics": "FĆsica",
"CommonMetadataStrings.politicalScience": "CiĆŖncias PolĆticas",
"CommonMetadataStrings.practice": "Praticar",
+ "CommonMetadataStrings.practiceQuiz": "Teste prƔtico",
"CommonMetadataStrings.preschool": "PrƩ-escola",
"CommonMetadataStrings.professionalSkills": "CompetĆŖncias profissionais",
"CommonMetadataStrings.programming": "ProgramaĆ§Ć£o",
@@ -484,6 +490,7 @@
"CommonMetadataStrings.readReference": "Lista de referĆŖncias",
"CommonMetadataStrings.readingAndWriting": "Leitura e escrita",
"CommonMetadataStrings.readingComprehension": "CompreensĆ£o de texto",
+ "CommonMetadataStrings.reference": "Materiais de referĆŖncia",
"CommonMetadataStrings.reflect": "Refletir",
"CommonMetadataStrings.school": "Escolar",
"CommonMetadataStrings.sciences": "CiĆŖncias",
@@ -523,13 +530,7 @@
"CommunityStandardsModal.studioItem3": "Compartilhamento. Criar e publicar novos canais com o que vocĆŖ encontrar, seja para compartilhar com as suas prĆ³prias implementaƧƵes de forma privada ou compartilhar com outros no Kolibri Studio.",
"CommunityStandardsModal.studioItem4": "ModificaĆ§Ć£o e criaĆ§Ć£o. Adicionar seus prĆ³prios exercĆcios avaliativos a qualquer material existente",
"CommunityStandardsModal.studioItem5": "Hospedagem. Enviar seus prĆ³prios materiais (limitado a materiais que vocĆŖ sabe serem licenciados de forma apropriada) para um disco rĆgido local ou outras localidades na internet",
- "CompletionOptions.allContent": "Visualizado na sua totalidade",
- "CompletionOptions.completeDuration": "Quando o tempo gasto no conteĆŗdo Ć© igual Ć duraĆ§Ć£o do mesmo",
- "CompletionOptions.determinedByResource": "PrĆ©-determinado pelo prĆ³prio conteĆŗdo",
- "CompletionOptions.exactTime": "Tempo necessĆ”rio para a conclusĆ£o do conteĆŗdo",
- "CompletionOptions.goal": "Quando o objetivo for alcanƧado",
- "CompletionOptions.practiceQuiz": "Teste para praticar",
- "CompletionOptions.reference": "Materiais de referĆŖncia",
+ "CompletionOptions.learnersCanMarkComplete": "Permitir que os alunos marquem como 'concluĆdo'",
"CompletionOptions.referenceHint": "O progresso do aluno sobre um material de referĆŖncia sĆ³ serĆ” registrado se o aluno o marcar como 'concluĆdo'",
"ConstantStrings.All Rights Reserved": "Todos os direitos reservados",
"ConstantStrings.All Rights Reserved_description": "A licenƧa de Todos os Direitos Reservados indica que o detentor dos direitos autorais reserva-se, ou mantĆ©m para si o uso, de todos os direitos providos pela lei de direitos autorais sob um Ćŗnico acordo de direitos autorais.",
@@ -627,7 +628,13 @@
"ContentNodeChangedIcon.isNewTopic": "Pasta nĆ£o publicada",
"ContentNodeChangedIcon.isUpdatedResource": "Atualizado desde a Ćŗltima publicaĆ§Ć£o",
"ContentNodeChangedIcon.isUpdatedTopic": "A pasta foi atualizada desde sua Ćŗltima publicaĆ§Ć£o",
+ "ContentNodeCopyTaskProgress.copyErrorTopic": "Alguns conteĆŗdos nĆ£o foram copiados",
+ "ContentNodeEditListItem.copiedSnackbar": "CĆ³pia concluĆda",
+ "ContentNodeEditListItem.creatingCopies": "Copiando...",
"ContentNodeEditListItem.optionsTooltip": "OpƧƵes",
+ "ContentNodeEditListItem.removeNode": "Remover",
+ "ContentNodeEditListItem.retryCopy": "Tentar novamente",
+ "ContentNodeEditListItem.undo": "Desfazer",
"ContentNodeIcon.audio": "Ćudio",
"ContentNodeIcon.document": "Documento",
"ContentNodeIcon.exercise": "ExercĆcio",
@@ -637,8 +644,8 @@
"ContentNodeIcon.unsupported": "NĆ£o suportado",
"ContentNodeIcon.video": "VĆdeo",
"ContentNodeLearningActivityIcon.multipleLearningActivities": "ConteĆŗdo com mĆŗltiplas atividades",
- "ContentNodeLearningActivityIcon.topic": "Pasta",
"ContentNodeListItem.coachTooltip": "Material para professores",
+ "ContentNodeListItem.copyingError": "Falha ao copiar.",
"ContentNodeListItem.copyingTask": "Copiando",
"ContentNodeListItem.hasCoachTooltip": "{value, number, integer} {value, plural, one {material para professores} other {materiais para professores}}",
"ContentNodeListItem.openTopic": "Abrir pasta",
@@ -656,7 +663,7 @@
"ContentNodeOptions.move": "Mover",
"ContentNodeOptions.moveTo": "Mover para...",
"ContentNodeOptions.newSubtopic": "Nova pasta",
- "ContentNodeOptions.remove": "Remover",
+ "ContentNodeOptions.remove": "Excluir",
"ContentNodeOptions.removedFromClipboard": "ExcluĆdo da Ć”rea de transferĆŖncia",
"ContentNodeOptions.removedItems": "Enviado para a lixeira",
"ContentNodeOptions.undo": "Desfazer",
@@ -692,8 +699,8 @@
"CountryField.locationLabel": "Selecione tudo que se aplica",
"CountryField.locationRequiredMessage": "Campo obrigatĆ³rio",
"CountryField.noCountriesFound": "Nenhum paĆs encontrado",
- "Create.ToSCheck": "Eu li e concordo com os termos de serviƧo",
- "Create.ToSRequiredMessage": "Por favor, aceite os nossos termos de serviƧo",
+ "Create.ToSRequiredMessage": "Pedimos que aceite nossos Termos de ServiƧo e PolĆtica de Privacidade",
+ "Create.agreement": "Eu li e concordo com os Termos de ServiƧo e a PolĆtica de Privacidade",
"Create.backToLoginButton": "Iniciar sessĆ£o",
"Create.basicInformationHeader": "InformaƧƵes bƔsicas",
"Create.conferenceSourceOption": "ConferĆŖncia",
@@ -724,8 +731,6 @@
"Create.passwordLabel": "Senha",
"Create.passwordMatchMessage": "As senhas nĆ£o coincidem",
"Create.personalDemoSourceOption": "DemonstraĆ§Ć£o pessoal",
- "Create.privacyPolicyCheck": "Eu li e concordo com a polĆtica de privacidade",
- "Create.privacyPolicyRequiredMessage": "Por favor, aceite a nossa polĆtica de privacidade",
"Create.registrationFailed": "Ocorreu um erro ao registrar a sua conta. Por favor, tente novamente",
"Create.registrationFailedOffline": "VocĆŖ parece estar offline. Por favor, conecte-se Ć internet para criar uma conta.",
"Create.sequencingUsageOption": "Usar prĆ©-requisitos para colocar materiais em sequĆŖncia",
@@ -738,8 +743,8 @@
"Create.storingUsagePlaceholder": "Quanto armazenamento vocĆŖ precisa?",
"Create.taggingUsageOption": "Marcar fontes de conteĆŗdo para encontrĆ”-las",
"Create.usageLabel": "Como voce pretende utilizar o Kolibri Studio? (Selecione todas que se aplicam)",
- "Create.viewPrivacyPolicyLink": "Ver polĆtica de privacidade",
- "Create.viewToSLink": "Ver termos de serviƧo",
+ "Create.viewPrivacyPolicyLink": "Visualizar a PolĆtica de Privacidade",
+ "Create.viewToSLink": "Visualizar os Termos de ServiƧo",
"Create.websiteSourceOption": "Website da Learning Equality",
"CurrentTopicView.COMFORTABLE_VIEW": "Vista confortƔvel",
"CurrentTopicView.COMPACT_VIEW": "Vista compacta",
@@ -784,6 +789,7 @@
"Details.assessmentsIncludedText": "AvaliaƧƵes",
"Details.authorToolTip": "Pessoa ou organizaĆ§Ć£o que criou este conteĆŗdo",
"Details.authorsLabel": "Autores",
+ "Details.categoriesHeading": "Categorias",
"Details.coachDescription": "Os materiais para professores sĆ£o visĆveis apenas para eles no Kolibri",
"Details.coachHeading": "Materiais para professores",
"Details.containsContentHeading": "ContĆ©m conteĆŗdo de",
@@ -792,6 +798,7 @@
"Details.creationHeading": "Criado em",
"Details.currentVersionHeading": "VersĆ£o publicada",
"Details.languagesHeading": "Idiomas",
+ "Details.levelsHeading": "NĆveis",
"Details.licensesLabel": "LicenƧas",
"Details.primaryLanguageHeading": "Idioma principal",
"Details.providerToolTip": "OrganizaĆ§Ć£o que encomendou ou distribui o conteĆŗdo",
@@ -820,7 +827,6 @@
"DetailsTabView.importedFromButtonText": "Importado do {channel}",
"DetailsTabView.languageChannelHelpText": "Deixar em branco para usar o idioma do canal",
"DetailsTabView.languageHelpText": "Deixar em branco para que o idioma da pasta seja utilizado",
- "CompletionOptions.learnersCanMarkComplete": "Permitir que os alunos marquem como 'concluĆdo'",
"DetailsTabView.noTagsFoundText": "Nenhum resultado encontrado para \"{text}\". Pressione 'Enter' para criar um novo marcador",
"DetailsTabView.providerLabel": "Provedor",
"DetailsTabView.providerToolTip": "OrganizaĆ§Ć£o que encomendou ou distribui o conteĆŗdo",
@@ -979,6 +985,7 @@
"Main.privacyPolicyLink": "PolĆtica de privacidade",
"Main.signInButton": "Iniciar sessĆ£o",
"MainNavigationDrawer.administrationLink": "AdministraĆ§Ć£o",
+ "MainNavigationDrawer.changeLanguage": "Trocar de idioma",
"MainNavigationDrawer.channelsLink": "Canais",
"MainNavigationDrawer.copyright": "Ā© {year} Learning Equality",
"MainNavigationDrawer.giveFeedback": "Deixar um comentƔrio",
@@ -1029,7 +1036,6 @@
"PermissionsError.goToHomePageAction": "Ir para a pƔgina inicial",
"PermissionsError.permissionDeniedHeader": "VocĆŖ se esqueceu de fazer o login?",
"PoliciesModal.checkboxText": "Eu concordo com os termos acima",
- "PoliciesModal.checkboxValidationErrorMessage": "Campo obrigatĆ³rio",
"PoliciesModal.closeButton": "Fechar",
"PoliciesModal.continueButton": "Continuar",
"PoliciesModal.lastUpdated": "Data da Ćŗltima atualizaĆ§Ć£o {date}",
@@ -1040,7 +1046,8 @@
"ProgressModal.lastPublished": "Publicado em {last_published}",
"ProgressModal.publishHeader": "Publicando canal",
"ProgressModal.syncError": "Falha na Ćŗltima tentativa de sincronizaĆ§Ć£o",
- "ProgressModal.syncHeader": "Sincronizando canal",
+ "ProgressModal.syncHeader": "Sincronizar conteĆŗdos",
+ "ProgressModal.syncedSnackbar": "ConteĆŗdos sincronizados",
"ProgressModal.unpublishedText": "NĆ£o publicado",
"PublishModal.cancelButton": "Cancelar",
"PublishModal.descriptionDescriptionTooltip": "Esta descriĆ§Ć£o serĆ” mostrada aos administradores do Kolibri antes de atualizarem as versƵes dos canais",
@@ -1146,7 +1153,6 @@
"ResourcePanel.author": "Autor",
"ResourcePanel.availableFormats": "Formatos disponĆveis",
"ResourcePanel.coachResources": "Materiais para professores",
- "ResourcePanel.completion": "ConclusĆ£o",
"ResourcePanel.copyrightHolder": "Detentor dos direitos autorais",
"ResourcePanel.description": "DescriĆ§Ć£o",
"ResourcePanel.details": "Detalhes",
@@ -1155,13 +1161,15 @@
"ResourcePanel.incompleteQuestionError": "{count, plural, one {# pergunta incompleta} other {# perguntas incompletas}}",
"ResourcePanel.language": "Idioma",
"ResourcePanel.license": "LicenƧa",
- "ResourcePanel.masteryMofN": "Objetivo: {m} de {n}",
"ResourcePanel.nextSteps": "PrĆ³ximos passos",
- "ResourcePanel.noCopyrightHolderError": "Falta o detentor dos direitos autorais",
- "ResourcePanel.noFilesError": "Faltam arquivos",
- "ResourcePanel.noLicenseDescriptionError": "Falta a descriĆ§Ć£o da licenƧa",
- "ResourcePanel.noLicenseError": "Falta a licenƧa",
- "ResourcePanel.noMasteryModelError": "Faltam critĆ©rios de domĆnio",
+ "ResourcePanel.noCompletionCriteriaError": "Campo obrigatĆ³rio",
+ "ResourcePanel.noCopyrightHolderError": "O detentor dos direitos autorais Ć© obrigatĆ³rio",
+ "ResourcePanel.noDurationError": "Este campo Ć© obrigatĆ³rio",
+ "ResourcePanel.noFilesError": "Campo obrigatĆ³rio",
+ "ResourcePanel.noLearningActivityError": "Este campo Ć© obrigatĆ³rio",
+ "ResourcePanel.noLicenseDescriptionError": "Campo obrigatĆ³rio",
+ "ResourcePanel.noLicenseError": "A licenƧa Ć© obrigatĆ³ria",
+ "ResourcePanel.noMasteryModelError": "Campo obrigatĆ³rio",
"ResourcePanel.noQuestionsError": "O exercĆcio estĆ” vazio",
"ResourcePanel.originalChannel": "Importado de",
"ResourcePanel.previousSteps": "Passos anteriores",
@@ -1271,16 +1279,18 @@
"SyncResourcesModal.cancelButtonLabel": "Cancelar",
"SyncResourcesModal.confirmSyncModalExplainer": "VocĆŖ estĆ” prestes a sincronizar e atualizar o seguinte:",
"SyncResourcesModal.confirmSyncModalTitle": "Confirmar sincronizaĆ§Ć£o",
+ "SyncResourcesModal.confirmSyncModalWarningExplainer": "Aviso: isto irĆ” alterar quaisquer mudanƧas que tenham sido feitas previamente em conteĆŗdos copiados ou importados.",
"SyncResourcesModal.continueButtonLabel": "Continuar",
"SyncResourcesModal.syncButtonLabel": "Sincronizar",
- "SyncResourcesModal.syncExercisesExplainer": "Atualizar perguntas, respostas e dicas",
+ "SyncResourcesModal.syncExercisesExplainer": "Atualizar perguntas, respostas e dicas nos exercĆcios e questionĆ”rios",
"SyncResourcesModal.syncExercisesTitle": "Detalhes da avaliaĆ§Ć£o",
- "SyncResourcesModal.syncFilesExplainer": "Atualizar todas as informaƧƵes do arquivo",
+ "SyncResourcesModal.syncFilesExplainer": "Atualizar todos os arquivos, incluindo: miniaturas, captions e legendas",
"SyncResourcesModal.syncFilesTitle": "Arquivos",
- "SyncResourcesModal.syncModalExplainer": "Sincronize e atualize seus conteĆŗdos com sua fonte original.",
+ "SyncResourcesModal.syncModalExplainer": "Sincronizar conteĆŗdos atualiza os materiais copiados ou importados desde Kolibri Studio para este canal. Isso significa que quaisquer mudanƧas que tenham sido feitas no conteĆŗdo original, aparecerĆ£o nos conteĆŗdos deste canal, mesmo que jĆ” importados, copiados ou editados.",
+ "SyncResourcesModal.syncModalSelectAttributes": "Selecione o que vocĆŖ gostaria de copiar:",
"SyncResourcesModal.syncModalTitle": "Sincronizar conteĆŗdos",
- "SyncResourcesModal.syncTagsExplainer": "Atualizar todos os marcadores",
- "SyncResourcesModal.syncTagsTitle": "Marcadores",
+ "SyncResourcesModal.syncResourceDetailsExplainer": "Atualizar as informaƧƵes do conteĆŗdo: atividade de aprendizagem, nĆvel, requisitos, categoria, tags, pĆŗblico e fonte",
+ "SyncResourcesModal.syncResourceDetailsTitle": "Detalhes do conteĆŗdo",
"SyncResourcesModal.syncTitlesAndDescriptionsExplainer": "Atualizar tĆtulos e descriƧƵes dos conteĆŗdos",
"SyncResourcesModal.syncTitlesAndDescriptionsTitle": "TĆtulos e descriƧƵes",
"TechnicalTextBlock.copiedToClipboardConfirmation": "Copiado para a Ć”rea de transferĆŖncia",
@@ -1421,7 +1431,7 @@
"TreeViewBase.getToken": "Obter token",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {conteĆŗdo estĆ” incompleto e nĆ£o pode ser publicado} other {conteĆŗdos estĆ£o incompletos e nĆ£o podem ser publicados}}",
"TreeViewBase.noChangesText": "Nenhuma alteraĆ§Ć£o encontrada no canal",
- "TreeViewBase.noLanguageSetError": "Falta o idioma do canal",
+ "TreeViewBase.noLanguageSetError": "Campo obrigatĆ³rio",
"TreeViewBase.openTrash": "Abrir lixeira",
"TreeViewBase.publishButton": "Publicar",
"TreeViewBase.publishButtonTitle": "Tornar este canal disponĆvel para importaĆ§Ć£o no Kolibri",
@@ -1440,19 +1450,15 @@
"UsingStudio.aboutStudioText": "O Kolibri Studio estĆ” em desenvolvimento ativo e, sendo assim, algumas alteraƧƵes podem causar comportamentos inesperados ou desafios (tambĆ©m conhecidos como \"problemas\". Se vocĆŖ encontrar um problema, por favor, nos notifique assim que ocorrerem para nos ajudar a resolvĆŖ-los. (Veja as instruƧƵes abaixo de como relatar problemas).",
"UsingStudio.bestPractice1": "Ao importar conteĆŗdos e/ou utilizar a Ć”rea de transferĆŖncia para mover ou copiar, prefira trabalhar com um nĆŗmero pequeno de conteĆŗdos por vez, ao invĆ©s de tentar trazer um canal inteiro. ",
"UsingStudio.bestPractice2": "Ć preferĆvel criar mĆŗltiplos canais menores ao invĆ©s de um canal gigante com muitos nĆveis e pastas.",
- "UsingStudio.bestPractice3": "Recarregue a pĆ”gina com frequĆŖncia para ter certeza que seu trabalho seja salvo no servidor e que nenhum erro de conexĆ£o tenha ocorrido. Use CTRL+R no Linux/Windows ou ā+R no Mac.",
- "UsingStudio.bestPractice4": "Evite ediƧƵes simultĆ¢neas no mesmo canal. Os canais nĆ£o devem ser editados por vĆ”rios usuĆ”rios ao mesmo tempo ou pelo mesmo usuĆ”rio em vĆ”rias janelas do navegador.",
+ "UsingStudio.bestPractice3": "Recarregue a pĆ”gina para confirmar se seu trabalho foi salvo no servidor. Use CTRL+R para Linux/Windows ou ā+R para Mac.",
"UsingStudio.bestPractice5": "Ć possĆvel que vocĆŖ encontre erros de timeout no seu navegador quando estiver importando ou sincronizando canais pesados, com muitos conteĆŗdos. NĆ£o se preocupe com a mensagem de erro e nĆ£o repita a operaĆ§Ć£o logo em seguida: Kolibri Studio continua trabalhando em segundo plano, mesmo depois da mensagem de erro. Apenas espere alguns minutos e recarregue a pĆ”gina antes de continuar com suas ediƧƵes.",
"UsingStudio.bestPractice6": "Comprima vĆdeos antes de enviĆ”-los (veja estas instruƧƵes).",
"UsingStudio.bestPractice7": "PUBLICAR periodicamente e importe seu canal para o Kolibri para prĆ©-visualizar o conteĆŗdo e obter uma cĆ³pia de backup local do seu canal.",
- "UsingStudio.bestPractice8": "NĆ£o edite o canal apĆ³s clicar em PUBLICAR. Aguarde o e-mail de notificaĆ§Ć£o antes de retomar as operaƧƵes de ediĆ§Ć£o.",
"UsingStudio.bestPractice9": "Relate problemas na medida em que os encontra.",
"UsingStudio.bestPractices": "PrƔticas recomendadas",
"UsingStudio.communityStandardsLink": "Normas comunitƔrias",
- "UsingStudio.issue1": "Dois usuĆ”rios relataram incidentes isolados onde conteĆŗdos que foram importados de outro canal desapareceram, deixando as pastas e subpastas vazias. Foi reportado posteriormente que os conteĆŗdos reapareceram, e que estes problemas nĆ£o ocorreram constantemente. Esses incidentes podem ter sido causados por conta de uma conexĆ£o lenta ou instĆ”vel com a internet. Se vocĆŖ se deparar com esse problema, por favor, contate-nos assim que possĆvel, enviando o mĆ”ximo de informaƧƵes sobre o que aconteceu.",
- "UsingStudio.issue2": "Algumas operaƧƵes no Studio sĆ£o atualmente muito lentas e, sendo assim, podem parecer que as alteraƧƵes que vocĆŖ tentou fazer foram interrompidas ou nĆ£o tiveram efeito. Muitas vezes, a alteraĆ§Ć£o ainda estĆ” sendo processada e aparecerĆ” assim que estiver completa. Se apĆ³s 5 a 10 minutos a alteraĆ§Ć£o ainda nĆ£o teve efeito, mesmo apĆ³s recarregar o navegador, por favor, registre o seu problema. Estamos trabalhando em soluƧƵes para estes transtornos.",
+ "UsingStudio.issue1": "Alguns usuĆ”rios relataram o desaparecimento repentino de alteraƧƵes feitas recentemente em seus canais. O problema parece estar relacionado ao fato de se abrir vĆ”rias abas com Kolibri Studio ao mesmo tempo, e, logo apĆ³s, encerrar a sessĆ£o. Aconselhamos que vocĆŖ desative quaisquer funƧƵes de 'economia de memĆ³ria' ou 'dormir' do seu navegador enquanto estiver usando Kolibri Studio e recarregue cada aba antes de sair. Continuamos investigando esse problema, portanto, se vocĆŖ passar por isso, por favor entre em contato conosco com o mĆ”ximo de informaƧƵes possĆvel. Obrigado.",
"UsingStudio.issueLink1": "RelatĆ³rios de conteĆŗdo desaparecendo",
- "UsingStudio.issueLink2": "O desempenho lento pode levar a erros inesperados na interface",
"UsingStudio.issuesPageLink": "Ver todos os problemas",
"UsingStudio.notableIssues": "Problemas notƔveis",
"UsingStudio.policiesLink": "PolĆtica de privacidade",
@@ -1498,4 +1504,5 @@
"sharedVue.masteryModelNWholeNumber": "Deve ser um nĆŗmero inteiro",
"sharedVue.masteryModelRequired": "O critĆ©rio de domĆnio Ć© obrigatĆ³rio",
"sharedVue.shortActivityLteThirty": "O nĆŗmero deve ser igual ou menor que 30",
- "sharedVue.titleRequired": "O tĆtulo Ć© obrigatĆ³rio"}
+ "sharedVue.titleRequired": "O tĆtulo Ć© obrigatĆ³rio"
+}
diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po
index 9501c759a6..5030b3df7d 100644
--- a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po
+++ b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-16 00:55+0000\n"
-"PO-Revision-Date: 2022-10-17 20:08\n"
+"POT-Creation-Date: 2023-05-10 21:55+0000\n"
+"PO-Revision-Date: 2023-05-24 17:17\n"
"Last-Translator: \n"
"Language-Team: Portuguese, Brazilian\n"
"Language: pt_BR\n"
@@ -17,36 +17,8 @@ msgstr ""
"X-Crowdin-File: /unstable/django.po\n"
"X-Crowdin-File-ID: 4322\n"
-#: contentcuration/api.py:140
-msgid "Date/Time Created"
-msgstr "Data/HorƔrio em que foi criado"
-
-#: contentcuration/api.py:141 contentcuration/api.py:142
-msgid "Not Available"
-msgstr "IndisponĆvel"
-
-#: contentcuration/api.py:145
-msgid "Ricecooker Version"
-msgstr "VersĆ£o do Ricecooker"
-
-#: contentcuration/api.py:150 contentcuration/utils/csv_writer.py:164
-msgid "File Size"
-msgstr "Tamanho do arquivo"
-
-#: contentcuration/api.py:161
-msgid "# of {}s"
-msgstr "# de {}s"
-
-#: contentcuration/api.py:165
-msgid "# of Questions"
-msgstr "# de perguntas"
-
-#: contentcuration/api.py:175
-msgid "# of Subtitles"
-msgstr "# de legendas"
-
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
-#: contentcuration/settings.py:287
+#: contentcuration/settings.py:290
msgid "Arabic"
msgstr "Ćrabe"
@@ -54,40 +26,40 @@ msgstr "Ćrabe"
msgid "The site is currently in read-only mode. Please try again later."
msgstr "No momento, o site estĆ” em modo somente leitura. Por favor, tente novamente mais tarde."
-#: contentcuration/models.py:279
+#: contentcuration/models.py:295
msgid "Not enough space. Check your storage under Settings page."
msgstr "EspaƧo insuficiente. Verifique seu armazenamento na pƔgina ConfiguraƧƵes."
-#: contentcuration/models.py:294 contentcuration/models.py:301
+#: contentcuration/models.py:308 contentcuration/models.py:315
msgid "Out of storage! Request more space under Settings > Storage."
msgstr "Sem armazenamento! Solicite mais espaƧo em ConfiguraƧƵes > Armazenamento."
-#: contentcuration/models.py:1410
+#: contentcuration/models.py:1730
msgid " (Original)"
msgstr " (Original)"
-#: contentcuration/settings.py:285
+#: contentcuration/settings.py:288
msgid "English"
msgstr "InglĆŖs"
-#: contentcuration/settings.py:286
+#: contentcuration/settings.py:289
msgid "Spanish"
msgstr "Espanhol"
-#: contentcuration/settings.py:288
+#: contentcuration/settings.py:291
msgid "French"
msgstr "FrancĆŖs"
-#: contentcuration/tasks.py:251
-msgid "Unknown error starting task. Please contact support."
-msgstr "Erro desconhecido ao iniciar tarefa. Por favor, entre em contato com o suporte."
+#: contentcuration/settings.py:292
+msgid "Portuguese"
+msgstr "PortuguĆŖs"
-#: contentcuration/templates/base.html:83
+#: contentcuration/templates/base.html:38
#: contentcuration/templates/channel_list.html:14
msgid "Kolibri Studio"
msgstr "Kolibri Studio"
-#: contentcuration/templates/base.html:166
+#: contentcuration/templates/base.html:129
msgid "Contentworkshop.learningequality.org has been deprecated. Please go to studio.learningequality.org for the latest version of Studio"
msgstr "Contentworkshop.learningequality.org foi descontinuado. Por favor, acesse studio.learningequality.org para ver a Ćŗltima versĆ£o do Kolibri Studio"
@@ -100,234 +72,16 @@ msgid "We're sorry, this channel was not found."
msgstr "Infelizmente este canal nĆ£o foi encontrado."
#: contentcuration/templates/channel_not_found.html:24
-#: contentcuration/templates/permissions/open_channel_fail.html:14
#: contentcuration/templates/staging_not_found.html:23
#: contentcuration/templates/unauthorized.html:23
msgid "Go Home"
msgstr "Ir para a pƔgina inicial"
-#: contentcuration/templates/exercise_list.html:67
-msgid "Previous"
-msgstr "Pergunta anterior"
-
-#: contentcuration/templates/exercise_list.html:73
-msgid "current"
-msgstr "atual"
-
-#: contentcuration/templates/exercise_list.html:77
-msgid "Next"
-msgstr "PrĆ³ximo"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:206
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:165
-msgid "Language not set"
-msgstr "Idioma nĆ£o definido"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:212
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:171
-#, python-format
-msgid "This file was generated on %(date)s"
-msgstr "Este arquivo foi gerado em %(date)s"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:231
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:189
-msgid "Created"
-msgstr "Criado"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:232
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:190
-msgid "Last Published"
-msgstr "Ćltima publicaĆ§Ć£o"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:233
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:191
-msgid "Unpublished"
-msgstr "NĆ£o publicado"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:245
-msgid "USING THIS CHANNEL"
-msgstr "USANDO ESTE CANAL"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:247
-msgid "Copy one of the following into Kolibri to import this channel:"
-msgstr "Para importar este canal, copie um dos seguintes itens para a Plataforma de Aprendizagem Kolibri:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:252
-msgid "Tokens (Recommended):"
-msgstr "Tokens (recomendados):"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:260
-msgid "Channel ID:"
-msgstr "ID do canal:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:267
-msgid "Channel must be published to import into Kolibri"
-msgstr "O canal deve estar publicado para ser importado para a Plataforma de Aprendizagem Kolibri"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:273
-msgid "WHAT'S INSIDE"
-msgstr "O QUE ESTĆ INCLUSO"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:283
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:213
-#, python-format
-msgid "%(count)s Resource"
-msgid_plural "%(count)s Resources"
-msgstr[0] "%(count)s ConteĆŗdo"
-msgstr[1] "%(count)s ConteĆŗdos"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:304
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:234
-msgid "Includes"
-msgstr "Inclui"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:310
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:236
-msgid "Languages"
-msgstr "Idiomas"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:316
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:245
-msgid "Subtitles"
-msgstr "Legendas"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:324
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:254
-msgid "For Educators"
-msgstr "Para Educadores"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:256
-msgid "Coach Content"
-msgstr "ConteĆŗdo para educadores"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:325
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:257
-msgid "Assessments"
-msgstr "AvaliaƧƵes"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:334
-msgid "Content Tags"
-msgstr "Marcadores de conteĆŗdo"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:338
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:270
-msgid "No tags found"
-msgstr "Nenhum marcador encontrado"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:343
-#: contentcuration/templates/export/channel_detail_pdf.html:448
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:274
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:337
-msgid "This channel is empty"
-msgstr "Este canal estĆ” vazio"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:348
-msgid "SOURCE"
-msgstr "FONTE"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:360
-msgid "This channel features resources created by:"
-msgstr "Este canal contĆ©m conteĆŗdos criados por:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:361
-#: contentcuration/templates/export/channel_detail_pdf.html:379
-#: contentcuration/templates/export/channel_detail_pdf.html:398
-#: contentcuration/templates/export/channel_detail_pdf.html:420
-#: contentcuration/templates/export/channel_detail_pdf.html:440
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:293
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:305
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:317
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:333
-msgid "Information not available"
-msgstr "InformaĆ§Ć£o indisponĆvel"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:378
-msgid "The material in this channel was provided by:"
-msgstr "O material neste canal foi fornecido por:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:397
-msgid "Material in this channel was originally hosted by:"
-msgstr "O material neste canal foi hospedado originalmente por:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:416
-msgid "This channel includes the following licenses:"
-msgstr "Este canal contƩm as seguintes licenƧas:"
-
-#: contentcuration/templates/export/channel_detail_pdf.html:439
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:326
-msgid "Copyright Holders:"
-msgstr "Detentores dos direitos autorais:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:192
-msgid "Token:"
-msgstr "Token:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:203
-msgid "What's Inside"
-msgstr "ConteĆŗdo"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:239
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:248
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s mais) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:267
-msgid "Most Common Tags"
-msgstr "Etiquetas / marcadores mais comuns"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:280
-msgid "Source Information"
-msgstr "InformaƧƵes bƔsicas"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:284
-msgid "Authors:"
-msgstr "Autores:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:288
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s mais) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:298
-msgid "Providers:"
-msgstr "Provedores:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:301
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:313
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:329
-#, python-format
-msgid "\n"
-" (+ %(count)s more) \n"
-" "
-msgstr "\n"
-" (+ %(count)s mais) \n"
-" "
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:310
-msgid "Aggregators:"
-msgstr "Agregadores:"
-
-#: contentcuration/templates/export/channel_detail_pdf_condensed.html:322
-msgid "Licenses:"
-msgstr "LicenƧas:"
-
-#: contentcuration/templates/export/csv_email.txt:4
#: contentcuration/templates/export/user_csv_email.txt:4
#: contentcuration/templates/permissions/permissions_email.txt:4
#: contentcuration/templates/registration/activation_email.txt:4
#: contentcuration/templates/registration/activation_needed_email.txt:4
-#: contentcuration/templates/registration/channel_published_email.txt:4
+#: contentcuration/templates/registration/channel_published_email.html:10
#: contentcuration/templates/registration/password_reset_email.txt:3
#: contentcuration/templates/registration/registration_needed_email.txt:4
#: contentcuration/templates/settings/account_deleted_user_email.txt:3
@@ -337,43 +91,6 @@ msgstr "LicenƧas:"
msgid "Hello %(name)s,"
msgstr "OlĆ” %(name)s,"
-#: contentcuration/templates/export/csv_email.txt:6
-#, python-format
-msgid "Your csv for %(channel_name)s has finished generating (attached)."
-msgstr "Seu arquivo csv para %(channel_name)s foi gerado (em anexo)."
-
-#: contentcuration/templates/export/csv_email.txt:8
-#: contentcuration/templates/export/user_csv_email.txt:29
-#: contentcuration/templates/permissions/permissions_email.txt:21
-#: contentcuration/templates/registration/activation_email.txt:12
-#: contentcuration/templates/registration/activation_needed_email.txt:14
-#: contentcuration/templates/registration/channel_published_email.txt:15
-#: contentcuration/templates/registration/password_reset_email.txt:14
-#: contentcuration/templates/registration/registration_needed_email.txt:12
-#: contentcuration/templates/settings/account_deleted_user_email.txt:10
-#: contentcuration/templates/settings/storage_request_email.txt:46
-msgid "Thanks for using Kolibri Studio!"
-msgstr "Obrigado por usar o Kolibri Studio!"
-
-#: contentcuration/templates/export/csv_email.txt:10
-#: contentcuration/templates/export/user_csv_email.txt:31
-#: contentcuration/templates/permissions/permissions_email.txt:23
-#: contentcuration/templates/registration/activation_email.txt:14
-#: contentcuration/templates/registration/activation_needed_email.txt:16
-#: contentcuration/templates/registration/channel_published_email.txt:17
-#: contentcuration/templates/registration/password_reset_email.txt:16
-#: contentcuration/templates/registration/registration_needed_email.txt:14
-#: contentcuration/templates/settings/account_deleted_user_email.txt:12
-#: contentcuration/templates/settings/issue_report_email.txt:26
-#: contentcuration/templates/settings/storage_request_email.txt:48
-msgid "The Learning Equality Team"
-msgstr "A equipe Learning Equality"
-
-#: contentcuration/templates/export/csv_email_subject.txt:1
-#, python-format
-msgid "CSV for %(channel_name)s"
-msgstr "Arquivo csv para %(channel_name)s"
-
#: contentcuration/templates/export/user_csv_email.txt:6
msgid "Here is the information for your Kolibri Studio account"
msgstr "Aqui estĆ£o as informaƧƵes sobre a sua conta no Kolibri Studio"
@@ -420,43 +137,64 @@ msgstr "As informaƧƵes sobre os conteĆŗdos que vocĆŖ enviou foram anexadas com
msgid "If you have any questions or concerns, please email us at %(legal_email)s."
msgstr "Se vocĆŖ tiver perguntas ou dĆŗvidas, por favor envie um e-mail para %(legal_email)s."
+#: contentcuration/templates/export/user_csv_email.txt:29
+#: contentcuration/templates/permissions/permissions_email.txt:21
+#: contentcuration/templates/registration/activation_email.txt:12
+#: contentcuration/templates/registration/activation_needed_email.txt:14
+#: contentcuration/templates/registration/channel_published_email.html:23
+#: contentcuration/templates/registration/password_reset_email.txt:14
+#: contentcuration/templates/registration/registration_needed_email.txt:12
+#: contentcuration/templates/settings/account_deleted_user_email.txt:10
+#: contentcuration/templates/settings/storage_request_email.txt:46
+msgid "Thanks for using Kolibri Studio!"
+msgstr "Obrigado por usar o Kolibri Studio!"
+
+#: contentcuration/templates/export/user_csv_email.txt:31
+#: contentcuration/templates/permissions/permissions_email.html:118
+#: contentcuration/templates/permissions/permissions_email.txt:23
+#: contentcuration/templates/registration/activation_email.html:111
+#: contentcuration/templates/registration/activation_email.txt:14
+#: contentcuration/templates/registration/activation_needed_email.txt:16
+#: contentcuration/templates/registration/channel_published_email.html:25
+#: contentcuration/templates/registration/password_reset_email.html:111
+#: contentcuration/templates/registration/password_reset_email.txt:16
+#: contentcuration/templates/registration/registration_needed_email.txt:14
+#: contentcuration/templates/registration/welcome_new_user_email.html:172
+#: contentcuration/templates/settings/account_deleted_user_email.txt:12
+#: contentcuration/templates/settings/issue_report_email.txt:26
+#: contentcuration/templates/settings/storage_request_email.txt:48
+msgid "The Learning Equality Team"
+msgstr "A equipe Learning Equality"
+
#: contentcuration/templates/export/user_csv_email_subject.txt:1
msgid "Your Kolibri Studio account information"
msgstr "InformaƧƵes sobre a sua conta no Kolibri Studio"
-#: contentcuration/templates/permissions/open_channel_fail.html:12
-msgid "There was an error opening this channel."
-msgstr "Ocorreu um erro ao abrir este canal."
-
-#: contentcuration/templates/permissions/open_channel_fail.html:13
-msgid "Try running ricecooker again."
-msgstr "Tente rodar o ricecooker novamente."
-
-#: contentcuration/templates/permissions/permissions_email.html:93
-#: contentcuration/templates/registration/activation_email.html:91
-#: contentcuration/templates/registration/password_reset_email.html:91
+#: contentcuration/templates/permissions/permissions_email.html:92
+#: contentcuration/templates/registration/activation_email.html:90
+#: contentcuration/templates/registration/password_reset_email.html:90
msgid "Hello"
msgstr "OlĆ”"
-#: contentcuration/templates/permissions/permissions_email.html:94
+#: contentcuration/templates/permissions/permissions_email.html:93
msgid "has invited you to edit a channel at"
msgstr "te convidou para editar um canal em"
-#: contentcuration/templates/permissions/permissions_email.html:100
+#: contentcuration/templates/permissions/permissions_email.html:99
#, python-format
msgid "Invititation to %(share_mode)s channel"
msgstr "Convite para o canal %(share_mode)s"
-#: contentcuration/templates/permissions/permissions_email.html:104
+#: contentcuration/templates/permissions/permissions_email.html:103
msgid "Click one of the following links to either accept or decline your invitation:"
msgstr "Clique num dos links a seguir para aceitar ou recusar o convite:"
-#: contentcuration/templates/permissions/permissions_email.html:107
-#: contentcuration/templates/permissions/permissions_email.html:109
+#: contentcuration/templates/permissions/permissions_email.html:106
+#: contentcuration/templates/permissions/permissions_email.html:108
msgid "ACCEPT"
msgstr "ACEITAR"
-#: contentcuration/templates/permissions/permissions_email.html:112
+#: contentcuration/templates/permissions/permissions_email.html:111
msgid "DECLINE"
msgstr "RECUSAR"
@@ -494,24 +232,24 @@ msgstr "VocĆŖ foi convidado para editar %(channel)s"
msgid "You've been invited to view %(channel)s"
msgstr "VocĆŖ foi convidado para visualizar %(channel)s"
-#: contentcuration/templates/registration/activation_email.html:92
-msgid "Welcome to Kolibri! Here is the link to activate your account:"
+#: contentcuration/templates/registration/activation_email.html:91
+msgid "Welcome to Kolibri Studio! Here is the link to activate your account:"
msgstr "Bem-vindo ao Kolibri Studio! Aqui estĆ” o link para ativar a sua conta:"
-#: contentcuration/templates/registration/activation_email.html:101
+#: contentcuration/templates/registration/activation_email.html:100
msgid "Click here to activate your account."
msgstr "Clique aqui para ativar a sua conta."
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
msgid "This link is valid for"
msgstr "Este link Ʃ vƔlido por"
-#: contentcuration/templates/registration/activation_email.html:102
+#: contentcuration/templates/registration/activation_email.html:101
#, python-format
msgid "%(expiration_days)s days."
msgstr "%(expiration_days)s dias."
-#: contentcuration/templates/registration/activation_email.html:104
+#: contentcuration/templates/registration/activation_email.html:103
msgid "ACTIVATE"
msgstr "ATIVAR"
@@ -539,27 +277,31 @@ msgstr "VocĆŖ solicitou um link para redefiniĆ§Ć£o de senha em %(site_name)s sem
msgid "Please activate your account by following the link below:"
msgstr "Por favor, ative a sua conta usando o link abaixo:"
-#: contentcuration/templates/registration/channel_published_email.txt:6
+#: contentcuration/templates/registration/channel_published_email.html:12
#, python-format
-msgid "%(channel_name)s has finished publishing! Here is the channel token (for importing it into Kolibri):"
-msgstr "%(channel_name)s acabout de ser publicado! Aqui estĆ” o token do canal (para importar para a Plataforma de Aprendizagem Kolibri):"
+msgid "%(channel_name)s"
+msgstr "%(channel_name)s"
+
+#: contentcuration/templates/registration/channel_published_email.html:12
+msgid "has finished publishing! Here is the channel token (for importing it into Kolibri):"
+msgstr "acabout de ser publicado! Aqui estĆ” o token do canal (para importar para a Plataforma de Aprendizagem Kolibri):"
-#: contentcuration/templates/registration/channel_published_email.txt:8
+#: contentcuration/templates/registration/channel_published_email.html:15
#, python-format
msgid "Token: %(channel_token)s"
msgstr "Token: %(channel_token)s"
-#: contentcuration/templates/registration/channel_published_email.txt:10
+#: contentcuration/templates/registration/channel_published_email.html:17
#, python-format
msgid "ID (for Kolibri version 0.6.0 and below): %(channel_id)s"
msgstr "ID (para uso no Kolibri versĆ£o 0.6.0 e inferior): %(channel_id)s"
-#: contentcuration/templates/registration/channel_published_email.txt:12
+#: contentcuration/templates/registration/channel_published_email.html:20
#, python-format
msgid "Version notes: %(notes)s"
msgstr "Notas da versĆ£o: %(notes)s"
-#: contentcuration/templates/registration/channel_published_email.txt:21
+#: contentcuration/templates/registration/channel_published_email.html:28
msgid "You are receiving this email because you are subscribed to this channel."
msgstr "VocĆŖ estĆ” recebendo este e-mail porque estĆ” inscrito neste canal."
@@ -588,23 +330,23 @@ msgstr "O link para redefiniĆ§Ć£o de senha estava invĆ”lido, pois provavelmente
msgid "Request a new password reset."
msgstr "Solicitar uma nova redefiniĆ§Ć£o de senha."
-#: contentcuration/templates/registration/password_reset_email.html:92
+#: contentcuration/templates/registration/password_reset_email.html:91
msgid "You are receiving this e-mail because you requested a password reset for your user account at"
msgstr "VocĆŖ estĆ” recebendo este e-mail porque pediu uma redefiniĆ§Ć£o de senha para a sua conta de usuĆ”rio em"
-#: contentcuration/templates/registration/password_reset_email.html:98
+#: contentcuration/templates/registration/password_reset_email.html:97
msgid "Reset my Password"
msgstr "Redefinir minha senha"
-#: contentcuration/templates/registration/password_reset_email.html:101
+#: contentcuration/templates/registration/password_reset_email.html:100
msgid "Please click the button below and choose a new password."
msgstr "Por favor, clique no botĆ£o abaixo e escolha uma nova senha."
-#: contentcuration/templates/registration/password_reset_email.html:102
+#: contentcuration/templates/registration/password_reset_email.html:101
msgid "Your username is"
msgstr "Seu nome de usuƔrio Ʃ"
-#: contentcuration/templates/registration/password_reset_email.html:104
+#: contentcuration/templates/registration/password_reset_email.html:103
msgid "RESET"
msgstr "REDEFINIR"
@@ -647,6 +389,141 @@ msgstr "VocĆŖ solicitou um link para redefiniĆ§Ć£o de senha em %(site_name)s sem
msgid "Please create an account by following the link below:"
msgstr "Por favor, crie uma conta usando o link abaixo:"
+#: contentcuration/templates/registration/welcome_new_user_email.html:78
+msgid "Welcome to Kolibri Studio!"
+msgstr "Bem-vindo ao Kolibri Studio!"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:82
+#, python-format
+msgid "\n"
+" We're delighted to introduce you to Kolibri Studio , our curricular tool to add,\n"
+" organize, and manage your own resources or those from the Kolibri Content Library.\n"
+" "
+msgstr "\n"
+" Ć com prazer que apresentamos Kolibri Studio , nossa ferramenta curricular para adicionar,\n"
+" organizar e gerenciar seus prĆ³prios conteĆŗdos ou os da Biblioteca Kolibri.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:87
+msgid "View the Kolibri Content Library"
+msgstr "Acessar a Biblioteca Kolibri"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:93
+msgid "\n"
+" Using Kolibri Studio, you can explore pre-organized collections of open educational resources (OER), and bundle,\n"
+" tag, differentiate, re-order, and distribute them into custom channels.\n"
+" "
+msgstr "\n"
+" Usando Kolibri Studio, vocĆŖ pode explorar coleƧƵes prĆ©-organizadas de Recursos Educativos Abertos (REAs), e agrupar, \n"
+" marcar, diferenciar, reordenar e distribuir em canais personalizados.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:99
+msgid "\n"
+" Using an admin account, you can then publish and import these custom channels--either your own or those shared\n"
+" with you -- into Kolibri with a unique \"token\" generated for each channel.\n"
+" "
+msgstr "\n"
+" Usando uma conta de administrador, vocĆŖ pode publicar e importar esses canais personalizados -- tanto os seus, quanto os que foram compartilhados\n"
+" com vocĆŖ -- para a Plataforma de Aprendizagem Kolibri, por meio de um \"token\" Ćŗnico gerado para cada canal.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:107
+msgid "\n"
+" Browse through the list of resources below* to learn more about Kolibri Studio and to begin creating your own\n"
+" custom channels:\n"
+" "
+msgstr "\n"
+" Confira os conteĆŗdos abaixo* para aprender mais sobre Kolibri Studio e comeƧar a criar seus prĆ³prios\n"
+" canais personalizados:\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:115
+msgid "Kolibri Studio User Guide"
+msgstr "Guia do UsuƔrio Kolibri Studio"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:120
+msgid "Content integration guide:"
+msgstr "Guia de integraĆ§Ć£o de conteĆŗdo:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:122
+msgid "\n"
+" Information on licensing, compatible formats, technical integration and more.\n"
+" "
+msgstr "\n"
+" InformaƧƵes sobre licenƧas, formatos compatĆveis, integraĆ§Ć£o tĆ©cnica e mais.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:125
+msgid "\n"
+" Note that if you are adding a small number of resources, technical integration is not necessary. \n"
+" "
+msgstr "\n"
+" AtenĆ§Ć£o: ao adicionar um nĆŗmero pequeno de conteĆŗdos, a integraĆ§Ć£o tĆ©cnica nĆ£o Ć© necessĆ”ria. \n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:130
+msgid "Step by step tutorials:"
+msgstr "Tutoriais passo a passo:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:133
+msgid "Video format:"
+msgstr "Formato do video:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:135
+msgid "Using Kolibri Studio: Your Content Workspace for Kolibri"
+msgstr "Utilizando Kolibri Studio: seu espaƧo de trabalho para conteĆŗdos em Kolibri"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:137
+msgid "(*also available in French and Arabic)"
+msgstr "(*tambĆ©m disponĆvel em francĆŖs e Ć”rabe)"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:140
+msgid "Slide gif format:"
+msgstr "Formato gif do slide:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:144
+msgid "Step by step Studio tutorial"
+msgstr "Tutorial passo a passo de Kolibri Studio"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:151
+msgid "Video compression instructions:"
+msgstr "InstruƧƵes para compressĆ£o de vĆdeo:"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:153
+msgid "\n"
+" For optimal results, videos should be compressed in order to achieve small file sizes. Compression ensures\n"
+" that the videos are well suited for offline distribution and playback on all Kolibri devices.\n"
+" "
+msgstr "\n"
+" Para melhores resultados, os vĆdeos em Kolibri devem ser compactados em arquivos pequenos. A compressĆ£o garante\n"
+" uma melhor distribuiĆ§Ć£o e reproduĆ§Ć£o offline em todos os dispositivos.\n"
+" "
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:158
+msgid "View the guide to video compression"
+msgstr "Acessar o guia de compactaĆ§Ć£o de vĆdeo"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:165
+msgid "If you need support with Kolibri Studio, please reach out to us on our Community Forum."
+msgstr "Se precisar de ajuda com Kolibri Studio, entre em contato conosco no nosso fĆ³rum da comunidade."
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:167
+msgid "Access the Community Forum"
+msgstr "Acessar o fĆ³rum da comunidade"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:171
+msgid "Thank You!"
+msgstr "Obrigada!"
+
+#: contentcuration/templates/registration/welcome_new_user_email.html:178
+msgid "*resources are presented in English"
+msgstr "*Os conteĆŗdos estĆ£o em inglĆŖs"
+
+#: contentcuration/templates/registration/welcome_new_user_email_subject.txt:1
+msgid "Thank you for activating your Kolibri Studio account! Let's get started..."
+msgstr "Obrigada por ativar sua conta em Kolibri Studio! Vamos comeƧar..."
+
#: contentcuration/templates/settings/account_deleted_user_email.txt:5
#, python-format
msgid "Your %(email)s account on %(site_name)s has been deleted."
@@ -709,188 +586,107 @@ msgstr "Para usar o Kolibri Studio, recomendamos os navegadores Firefox ou Chrom
msgid "You can also try updating your current browser."
msgstr "VocĆŖ tambĆ©m pode tentar atualizar seu navegador."
-#: contentcuration/templatetags/license_tags.py:10
+#: contentcuration/templatetags/license_tags.py:11
msgid "The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials."
msgstr "A licenƧa de AtribuiĆ§Ć£o permite que outros distribuam, remixem, ajustem e se baseiem no seu trabalho, atĆ© comercialmente, desde que te deem crĆ©dito pela criaĆ§Ć£o original. Ć o tipo de licenƧa mais flexĆvel oferecido. Recomendado para o mĆ”ximo de divulgaĆ§Ć£o e uso dos materiais licenciados."
-#: contentcuration/templatetags/license_tags.py:15
+#: contentcuration/templatetags/license_tags.py:16
msgid "The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to \"copyleft\" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects."
msgstr "A licenƧa de AtribuiĆ§Ć£o-CompartilhaIgual permite que outros remixem, ajustem e se baseiem no seu trabalho, mesmo que por motivos comerciais, desde que te deem crĆ©dito e licenciem suas novas criaƧƵes sob os mesmos termos. Essa licenƧa Ć© frequentemente comparada Ć s de software livre, como as de \"copyleft\" e de cĆ³digo aberto. Todos os novos trabalhos baseados no seu terĆ£o a mesma licenƧa, entĆ£o quaisquer derivativos tambĆ©m permitirĆ£o o uso comercial. Essa Ć© a licenƧa usada pelo Wikipedia e Ć© recomendada para materiais que se beneficiariam de incorporarem conteĆŗdo do Wikipedia e de outros projetos similarmente licenciados."
-#: contentcuration/templatetags/license_tags.py:25
+#: contentcuration/templatetags/license_tags.py:26
msgid "The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you."
msgstr "A licenƧa de AtribuiĆ§Ć£o-SemDerivaƧƵes permite redistribuiĆ§Ć£o, comercial ou nĆ£o comercial, desde que seja feita sem modificaƧƵes e por inteiro, com os crĆ©ditos a vocĆŖ."
-#: contentcuration/templatetags/license_tags.py:28
+#: contentcuration/templatetags/license_tags.py:29
msgid "The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms."
msgstr "A licenƧa de AtribuiĆ§Ć£o-NĆ£oComercial permite que outros remixem, ajustem e se baseiem no seu trabalho nĆ£o comercialmente, e embora os seus novos trabalhos devam tambĆ©m te reconhecer e serem nĆ£o comerciais, nĆ£o precisam licenciar os seus trabalhos derivados sob os mesmos termos."
-#: contentcuration/templatetags/license_tags.py:32
+#: contentcuration/templatetags/license_tags.py:33
msgid "The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms."
msgstr "A licenƧa de AtribuiĆ§Ć£o-NĆ£oComercial-CompartilhaIgual permite que outros remixem, ajustem e se baseiem no seu trabalho nĆ£o comercialmente, desde que te deem crĆ©dito e licenciem suas novas criaƧƵes sob os mesmos termos."
-#: contentcuration/templatetags/license_tags.py:36
+#: contentcuration/templatetags/license_tags.py:37
msgid "The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially."
msgstr "A licenƧa de AtribuiĆ§Ć£o-NĆ£oComercial-SemDerivaƧƵes Ć© a mais restritiva das nossas seis principais licenƧas, permitindo apenas que outros baixem seus trabalhos e os compartilhem com outros desde que te deem crĆ©dito, mas nĆ£o podem alterĆ”-los ou usĆ”-los comercialmente."
-#: contentcuration/templatetags/license_tags.py:40
+#: contentcuration/templatetags/license_tags.py:41
msgid "The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty."
msgstr "A licenƧa de Todos os Direitos Reservados indica que o detentor dos direitos autorais reserva-se, ou mantĆ©m para si o uso, de todos os direitos providos pela lei de direitos autorais sob um Ćŗnico acordo de direitos autorais."
-#: contentcuration/templatetags/license_tags.py:43
+#: contentcuration/templatetags/license_tags.py:44
msgid "Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights."
msgstr "Um trabalho de domĆnio pĆŗblico Ć© identificado como livre das restriƧƵes correntes sob a lei de direitos autorais, incluindo todos os direitos relacionados e afins."
-#: contentcuration/templatetags/license_tags.py:46
+#: contentcuration/templatetags/license_tags.py:47
msgid "Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails."
msgstr "A licenƧa personalizada de PermissƵes Especiais Ć© usada quando as licenƧas atuais nĆ£o se aplicarem ao conteĆŗdo. O detentor dessa licenƧa Ć© responsĆ”vel por criar a descriĆ§Ć£o do que ela pressupƵe."
-#: contentcuration/templatetags/translation_tags.py:26
-msgid "100% Correct"
-msgstr "100% de acerto"
-
-#: contentcuration/templatetags/translation_tags.py:27
-msgid "10 in a row"
-msgstr "10 seguidas"
-
-#: contentcuration/templatetags/translation_tags.py:28
-msgid "2 in a row"
-msgstr "2 seguidas"
-
-#: contentcuration/templatetags/translation_tags.py:29
-msgid "3 in a row"
-msgstr "3 seguidas"
-
-#: contentcuration/templatetags/translation_tags.py:30
-msgid "5 in a row"
-msgstr "5 seguidas"
-
-#: contentcuration/templatetags/translation_tags.py:31
-msgid "M of N..."
-msgstr "M de N..."
-
-#: contentcuration/templatetags/translation_tags.py:32
-msgid "CC BY"
-msgstr "CC BY"
-
-#: contentcuration/templatetags/translation_tags.py:33
-msgid "CC BY-SA"
-msgstr "CC BY-SA"
-
-#: contentcuration/templatetags/translation_tags.py:34
-msgid "CC BY-ND"
-msgstr "CC BY-ND"
-
-#: contentcuration/templatetags/translation_tags.py:35
-msgid "CC BY-NC"
-msgstr "CC BY-NC"
-
-#: contentcuration/templatetags/translation_tags.py:36
-msgid "CC BY-NC-SA"
-msgstr "CC BY-NC-SA"
-
-#: contentcuration/templatetags/translation_tags.py:37
-msgid "CC BY-NC-ND"
-msgstr "CC BY-NC-ND"
-
-#: contentcuration/templatetags/translation_tags.py:38
-msgid "All Rights Reserved"
-msgstr "Todos os direitos reservados"
-
-#: contentcuration/templatetags/translation_tags.py:39
-msgid "Public Domain"
-msgstr "DomĆnio pĆŗblico"
-
-#: contentcuration/templatetags/translation_tags.py:40
-msgid "Special Permissions"
-msgstr "PermissƵes especiais"
-
-#: contentcuration/templatetags/translation_tags.py:49
-#, python-format
-msgid "%(filesize)s %(unit)s"
-msgstr "%(filesize)s %(unit)s"
-
-#: contentcuration/utils/csv_writer.py:138
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:45
+#: contentcuration/utils/csv_writer.py:108
msgid "No Channel"
msgstr "Nenhum canal"
-#: contentcuration/utils/csv_writer.py:139
+#: contentcuration/utils/csv_writer.py:46
msgid "No resource"
msgstr "Nenhum conteĆŗdo"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Channel"
msgstr "Canal"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Title"
msgstr "TĆtulo"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Kind"
msgstr "Tipo"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Filename"
msgstr "Nome do arquivo"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
+msgid "File Size"
+msgstr "Tamanho do arquivo"
+
+#: contentcuration/utils/csv_writer.py:71
msgid "URL"
msgstr "URL"
-#: contentcuration/utils/csv_writer.py:164
+#: contentcuration/utils/csv_writer.py:71
msgid "Description"
msgstr "DescriĆ§Ć£o"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Author"
msgstr "Autor"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Language"
msgstr "Idioma"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License"
msgstr "LicenƧa"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "License Description"
msgstr "DescriĆ§Ć£o da licenƧa"
-#: contentcuration/utils/csv_writer.py:165
+#: contentcuration/utils/csv_writer.py:72
msgid "Copyright Holder"
msgstr "Detentor dos direitos autorais"
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "No Resource"
msgstr "Nenhum conteĆŗdo"
-#: contentcuration/utils/csv_writer.py:201
+#: contentcuration/utils/csv_writer.py:108
msgid "Staged File"
msgstr "Arquivo preparado"
-#: contentcuration/utils/format.py:15
-msgid "B"
-msgstr "B"
-
-#: contentcuration/utils/format.py:17
-msgid "KB"
-msgstr "KB"
-
-#: contentcuration/utils/format.py:19
-msgid "MB"
-msgstr "MB"
-
-#: contentcuration/utils/format.py:21
-msgid "GB"
-msgstr "GB"
-
-#: contentcuration/utils/format.py:23
-msgid "TB"
-msgstr "TB"
-
#: contentcuration/utils/incidents.py:7
msgid "There was a problem with a third-party service. This means certain operations might be blocked. We appreciate your patience while these issues are being resolved."
msgstr "Ocorreu um problema com um serviƧo de terceiros. Isso significa que algumas operaƧƵes podem estar bloqueadas. Agradecemos pela paciĆŖncia enquanto esses problemas sĆ£o resolvidos."
@@ -915,23 +711,26 @@ msgstr "Estamos com problemas com um serviƧo de terceiros. Isso significa que o
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here "
msgstr "Estamos com problemas com o nosso centro de dados. Isso significa que vocĆŖ pode ter problemas de conexĆ£o enquanto usa o Studio. Agradecemos pela paciĆŖncia enquanto esses problemas sĆ£o resolvidos. Para saber o status do serviƧo, por favor visite aqui "
-#: contentcuration/utils/publish.py:57
+#: contentcuration/utils/publish.py:96
msgid "Kolibri Studio Channel Published"
msgstr "Canal do Kolibri Studio publicado"
-#: contentcuration/views/public.py:63 contentcuration/views/public.py:74
-msgid "Api endpoint {} is not available"
-msgstr "Ponto de extremidade da API {} estĆ” indisponĆvel"
-
-#: contentcuration/views/public.py:76
-msgid "No channel matching {} found"
-msgstr "Nenhum canal correspondente a {} encontrado"
-
-#: contentcuration/views/settings.py:110
+#: contentcuration/views/settings.py:111
msgid "Kolibri Studio issue report"
msgstr "RelatĆ³rio de problemas do Kolibri Studio"
-#: contentcuration/views/settings.py:144
+#: contentcuration/views/settings.py:143
msgid "Kolibri Studio account deleted"
msgstr "Conta do Kolibri Studio excluĆda"
+#: kolibri_public/views.py:220
+msgid "Resource"
+msgstr "ConteĆŗdo"
+
+#: kolibri_public/views_v1.py:63 kolibri_public/views_v1.py:74
+msgid "Api endpoint {} is not available"
+msgstr "Ponto de extremidade da API {} estĆ” indisponĆvel"
+
+#: kolibri_public/views_v1.py:76
+msgid "No channel matching {} found"
+msgstr "Nenhum canal correspondente a {} encontrado"
diff --git a/contentcuration/search/management/commands/set_contentnode_tsvectors.py b/contentcuration/search/management/commands/set_contentnode_tsvectors.py
index c5e78ca8d1..067a956f62 100644
--- a/contentcuration/search/management/commands/set_contentnode_tsvectors.py
+++ b/contentcuration/search/management/commands/set_contentnode_tsvectors.py
@@ -10,9 +10,11 @@
from search.models import ContentNodeFullTextSearch
from search.utils import get_fts_annotated_contentnode_qs
+from contentcuration.models import Channel
+
logmodule.basicConfig(level=logmodule.INFO)
-logging = logmodule.getLogger("command")
+logging = logmodule.getLogger(__name__)
CHUNKSIZE = 10000
@@ -20,55 +22,53 @@
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("--channel-id", type=str, dest="channel_id",
- help="The channel_id to annotate to the nodes. If not specified then each node's channel_id is queried and then annotated.")
- parser.add_argument("--tree-id", type=int, dest="tree_id",
- help="Set tsvectors for a specific tree_id nodes only. If not specified then tsvectors for all nodes of ContentNode table are set.")
- parser.add_argument("--published", dest="published", action="store_true", help="Filters on whether node is published or not.")
- parser.add_argument("--complete", dest="complete", action="store_true", help="Filters on whether node is complete or not.")
-
- def get_tsvector_nodes_queryset(self, *args, **options):
- tsvector_nodes_queryset = get_fts_annotated_contentnode_qs(channel_id=options["channel_id"])
+ help="The channel_id for which tsvectors need to be generated.\
+ If not specified then tsvectors is generated for all published channels.")
+ parser.add_argument("--published", dest="published", action="store_true",
+ help="Filters on whether channel's contentnodes are published or not.")
- if options["tree_id"]:
- tsvector_nodes_queryset = tsvector_nodes_queryset.filter(tree_id=options["tree_id"])
+ def handle(self, *args, **options):
+ start = time.time()
- if options["complete"]:
- tsvector_nodes_queryset = tsvector_nodes_queryset.filter(complete=True)
+ if options["channel_id"]:
+ generate_tsv_for_channels = list(Channel.objects.filter(id=options["channel_id"]).values("id", "main_tree__tree_id"))
+ else:
+ generate_tsv_for_channels = list(Channel.objects.filter(main_tree__published=True, deleted=False).values("id", "main_tree__tree_id"))
if options["published"]:
- tsvector_nodes_queryset = tsvector_nodes_queryset.filter(published=True)
-
- tsvector_not_already_inserted_query = ~Exists(ContentNodeFullTextSearch.objects.filter(contentnode_id=OuterRef("id")))
- tsvector_nodes_queryset = (tsvector_nodes_queryset
- .filter(tsvector_not_already_inserted_query, channel_id__isnull=False)
- .values("id", "channel_id", "keywords_tsvector", "author_tsvector").order_by())
+ publish_filter_dict = dict(published=True)
+ else:
+ publish_filter_dict = dict()
- return tsvector_nodes_queryset
+ total_tsvectors_inserted = 0
- def handle(self, *args, **options):
- start = time.time()
+ for channel in generate_tsv_for_channels:
+ tsvector_not_already_inserted_query = ~Exists(ContentNodeFullTextSearch.objects.filter(contentnode_id=OuterRef("id")))
+ tsvector_nodes_query = (get_fts_annotated_contentnode_qs(channel["id"])
+ .filter(tsvector_not_already_inserted_query, tree_id=channel["main_tree__tree_id"], complete=True, **publish_filter_dict)
+ .values("id", "channel_id", "keywords_tsvector", "author_tsvector")
+ .order_by())
- tsvector_nodes_queryset = self.get_tsvector_nodes_queryset(*args, **options)
+ insertable_nodes_tsvector = list(tsvector_nodes_query[:CHUNKSIZE])
- insertable_nodes_tsvector = list(tsvector_nodes_queryset[:CHUNKSIZE])
- total_tsvectors_inserted = 0
+ logging.info("Inserting contentnode tsvectors of channel {}.".format(channel["id"]))
- while insertable_nodes_tsvector:
- logging.info("Inserting contentnode tsvectors.")
+ while insertable_nodes_tsvector:
+ insert_objs = list()
+ for node in insertable_nodes_tsvector:
+ obj = ContentNodeFullTextSearch(contentnode_id=node["id"], channel_id=node["channel_id"],
+ keywords_tsvector=node["keywords_tsvector"], author_tsvector=node["author_tsvector"])
+ insert_objs.append(obj)
- insert_objs = list()
- for node in insertable_nodes_tsvector:
- obj = ContentNodeFullTextSearch(contentnode_id=node["id"], channel_id=node["channel_id"],
- keywords_tsvector=node["keywords_tsvector"], author_tsvector=node["author_tsvector"])
- insert_objs.append(obj)
+ inserted_objs_list = ContentNodeFullTextSearch.objects.bulk_create(insert_objs)
- inserted_objs_list = ContentNodeFullTextSearch.objects.bulk_create(insert_objs)
+ current_inserts_count = len(inserted_objs_list)
+ total_tsvectors_inserted = total_tsvectors_inserted + current_inserts_count
- current_inserts_count = len(inserted_objs_list)
- total_tsvectors_inserted = total_tsvectors_inserted + current_inserts_count
+ logging.info("Inserted {} contentnode tsvectors of channel {}.".format(current_inserts_count, channel["id"]))
- logging.info("Inserted {} contentnode tsvectors.".format(current_inserts_count))
+ insertable_nodes_tsvector = list(tsvector_nodes_query[:CHUNKSIZE])
- insertable_nodes_tsvector = list(tsvector_nodes_queryset[:CHUNKSIZE])
+ logging.info("Insertion complete for channel {}.".format(channel["id"]))
logging.info("Completed! Successfully inserted total of {} contentnode tsvectors in {} seconds.".format(total_tsvectors_inserted, time.time() - start))
diff --git a/contentcuration/search/utils.py b/contentcuration/search/utils.py
index 4f6768f650..8519fde49d 100644
--- a/contentcuration/search/utils.py
+++ b/contentcuration/search/utils.py
@@ -14,28 +14,19 @@ def get_fts_search_query(value):
return SearchQuery(value=value, config=POSTGRES_FTS_CONFIG)
-def get_fts_annotated_contentnode_qs(channel_id=None):
+def get_fts_annotated_contentnode_qs(channel_id):
"""
Returns a `ContentNode` queryset annotated with fields required for full text search.
-
- If `channel_id` is provided, annotates that specific `channel_id` else annotates
- the `channel_id` to which the contentnode belongs.
"""
from contentcuration.models import ContentNode
- if channel_id:
- queryset = ContentNode.objects.annotate(channel_id=Value(channel_id))
- else:
- queryset = ContentNode._annotate_channel_id(ContentNode.objects)
-
- queryset = queryset.annotate(
+ return ContentNode.objects.annotate(
+ channel_id=Value(channel_id),
contentnode_tags=StringAgg("tags__tag_name", delimiter=" "),
keywords_tsvector=CONTENTNODE_KEYWORDS_TSVECTOR,
author_tsvector=CONTENTNODE_AUTHOR_TSVECTOR
)
- return queryset
-
def get_fts_annotated_channel_qs():
"""
diff --git a/contentcuration/search/viewsets/contentnode.py b/contentcuration/search/viewsets/contentnode.py
index 676698031d..0f88a037f6 100644
--- a/contentcuration/search/viewsets/contentnode.py
+++ b/contentcuration/search/viewsets/contentnode.py
@@ -16,11 +16,9 @@
from search.utils import get_fts_search_query
from contentcuration.models import Channel
-from contentcuration.models import File
from contentcuration.utils.pagination import ValuesViewsetPageNumberPagination
from contentcuration.viewsets.base import ReadOnlyValuesViewset
from contentcuration.viewsets.base import RequiredFilterSet
-from contentcuration.viewsets.common import NotNullMapArrayAgg
from contentcuration.viewsets.common import UUIDFilter
from contentcuration.viewsets.common import UUIDInFilter
@@ -104,48 +102,42 @@ class SearchContentNodeViewSet(ReadOnlyValuesViewset):
"id": "contentnode__id",
"content_id": "contentnode__content_id",
"node_id": "contentnode__node_id",
- "title": "contentnode__title",
- "description": "contentnode__description",
- "author": "contentnode__author",
- "provider": "contentnode__provider",
- "kind__kind": "contentnode__kind__kind",
- "thumbnail_encoding": "contentnode__thumbnail_encoding",
- "published": "contentnode__published",
- "modified": "contentnode__modified",
- "parent_id": "contentnode__parent_id",
- "changed": "contentnode__changed",
+ "root_id": "channel__main_tree_id",
+ "kind": "contentnode__kind__kind",
+ "parent": "contentnode__parent_id",
+ "public": "channel__public",
}
values = (
+ "channel__public",
+ "channel__main_tree_id",
"contentnode__id",
"contentnode__content_id",
"contentnode__node_id",
- "contentnode__title",
- "contentnode__description",
- "contentnode__author",
- "contentnode__provider",
"contentnode__kind__kind",
- "contentnode__thumbnail_encoding",
- "contentnode__published",
- "contentnode__modified",
"contentnode__parent_id",
- "contentnode__changed",
"channel_id",
"resource_count",
- "thumbnail_checksum",
- "thumbnail_extension",
- "content_tags",
"original_channel_name",
+
+ # TODO: currently loading nodes separately
+ # "thumbnail_checksum",
+ # "thumbnail_extension",
+ # "content_tags",
+ # "contentnode__title",
+ # "contentnode__description",
+ # "contentnode__author",
+ # "contentnode__provider",
+ # "contentnode__changed",
+ # "contentnode__thumbnail_encoding",
+ # "contentnode__published",
+ # "contentnode__modified",
)
def annotate_queryset(self, queryset):
"""
Annotates thumbnails, resources count and original channel name.
"""
- thumbnails = File.objects.filter(
- contentnode=OuterRef("contentnode__id"), preset__thumbnail=True
- )
-
descendant_resources_count = ExpressionWrapper(((F("contentnode__rght") - F("contentnode__lft") - Value(1)) / Value(2)), output_field=IntegerField())
original_channel_name = Coalesce(
@@ -163,11 +155,6 @@ def annotate_queryset(self, queryset):
queryset = queryset.annotate(
resource_count=descendant_resources_count,
- thumbnail_checksum=Subquery(thumbnails.values("checksum")[:1]),
- thumbnail_extension=Subquery(
- thumbnails.values("file_format__extension")[:1]
- ),
- content_tags=NotNullMapArrayAgg("contentnode__tags__tag_name"),
original_channel_name=original_channel_name,
)
diff --git a/deploy/cloudprober.cfg b/deploy/cloudprober.cfg
index 57bd0084db..c5a129455e 100644
--- a/deploy/cloudprober.cfg
+++ b/deploy/cloudprober.cfg
@@ -160,4 +160,28 @@ probe {
timeout_msec: 10000 # 10s
}
+probe {
+ name: "unapplied_changes_status"
+ type: EXTERNAL
+ targets { dummy_targets {} }
+ external_probe {
+ mode: ONCE
+ command: "./probers/unapplied_changes_probe.py"
+ }
+ interval_msec: 1800000 # 30 minutes
+ timeout_msec: 20000 # 20s
+}
+
+probe {
+ name: "task_queue_status"
+ type: EXTERNAL
+ targets { dummy_targets {} }
+ external_probe {
+ mode: ONCE
+ command: "./probers/task_queue_probe.py"
+ }
+ interval_msec: 600000 # 10 minutes
+ timeout_msec: 10000 # 10s
+}
+
# Note: When deploying on GKE, the error logs can be found under GCE VM instance.
diff --git a/deploy/generatejsconstantfiles.py b/deploy/generatejsconstantfiles.py
index a5135f2a2c..3ef554a5e4 100644
--- a/deploy/generatejsconstantfiles.py
+++ b/deploy/generatejsconstantfiles.py
@@ -127,7 +127,7 @@ def generate_constants_set_file(
# Don't generate this unless ids are strings
generate_names_constants = True
for constant in sorted(
- constant_list, key=lambda x: x if mapper is None else getattr(x, sort_by)
+ constant_list, key=lambda x: 0 if mapper is None else getattr(x, sort_by)
):
output += " "
value = mapper(constant) if mapper is not None else constant
@@ -177,7 +177,9 @@ def main():
)
generate_constants_set_file(
- [m[0] for m in exercises.MASTERY_MODELS if m[0] != exercises.SKILL_CHECK],
+ [m[0] for m in sorted(
+ exercises.MASTERY_MODELS, key=lambda x: int(x[0][21:]) if 'num_correct_in_a_row_' in x[0] else 0
+ ) if m[0] != exercises.SKILL_CHECK and m[0] != exercises.QUIZ],
"MasteryModels",
)
generate_constants_set_file([r[0] for r in roles.choices], "Roles")
diff --git a/deploy/nginx.conf.jinja2 b/deploy/nginx.conf.jinja2
index 8c10268884..b15851ca8d 100644
--- a/deploy/nginx.conf.jinja2
+++ b/deploy/nginx.conf.jinja2
@@ -87,6 +87,7 @@ http {
proxy_pass http://studio;
proxy_redirect off;
proxy_set_header Host $host;
+ proxy_read_timeout 100s;
}
location /ws/ {
diff --git a/deploy/probers/task_queue_probe.py b/deploy/probers/task_queue_probe.py
new file mode 100755
index 0000000000..3a3b02cfed
--- /dev/null
+++ b/deploy/probers/task_queue_probe.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+from base import BaseProbe
+
+
+class TaskQueueProbe(BaseProbe):
+
+ metric = "task_queue_ping_latency_msec"
+ threshold = 50
+
+ def do_probe(self):
+ r = self.request("api/probers/task_queue_status/")
+ r.raise_for_status()
+ results = r.json()
+
+ task_count = results.get('queued_task_count', 0)
+ if task_count >= self.threshold:
+ raise Exception("Task queue length is over threshold! {} > {}".format(task_count, self.threshold))
+
+
+if __name__ == "__main__":
+ TaskQueueProbe().run()
diff --git a/deploy/probers/unapplied_changes_probe.py b/deploy/probers/unapplied_changes_probe.py
new file mode 100755
index 0000000000..a3ceee915e
--- /dev/null
+++ b/deploy/probers/unapplied_changes_probe.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+from base import BaseProbe
+
+
+class UnappliedChangesProbe(BaseProbe):
+
+ metric = "unapplied__changes_ping_latency_msec"
+
+ def do_probe(self):
+ r = self.request("api/probers/unapplied_changes_status/")
+ r.raise_for_status()
+ results = r.json()
+
+ active_task_count = results.get('active_task_count', 0)
+ unapplied_changes_count = results.get('unapplied_changes_count', 0)
+
+ if active_task_count == 0 and unapplied_changes_count > 0:
+ raise Exception("There are unapplied changes and no active tasks! {} unapplied changes".format(unapplied_changes_count))
+
+
+if __name__ == "__main__":
+ UnappliedChangesProbe().run()
diff --git a/deploy/secretmanage b/deploy/secretmanage
deleted file mode 100755
index 1dfe4cccba..0000000000
--- a/deploy/secretmanage
+++ /dev/null
@@ -1,125 +0,0 @@
-#!/usr/bin/env python2
-"""
-./secretmanage -- manage production secrets!
-
-secretmanage allows you to upload new secrets. These secrets are then available
-in production (either in real production, staging or develop) by using the
-importsecret module.
-
-
-Usage:
- secretmanage upload (--dev|--staging|--prod)
-
- is the name of the secret. This will be used as a reference when you import
-the secret.
-
- is the content of the secret. These can be the actual DB credentials,
-the password to a message broker, or your life's nemesis.
-"""
-# NOTE to maintainers:
-# This encrypts the given plaintext using a keyring with the name of the environment,
-# and a key with the same name as the secret.
-# We will then upload this key to a bucket called '-secrets/', inside a folder in that bucket
-# with the same name as the environment. The file in that bucket is named the same as
-# the secret.
-#
-# This means that you need to ensure that a keyring with the same name as the environment is created.
-# You also need to make sure that the -secrets/ bucket is available.
-#
-# The persons running this script must also have th Encrypt permission for KMS, and has Edit and Get
-# permissions for the bucket.
-import logging
-import os
-
-from docopt import docopt
-from google.cloud import kms_v1
-from google.cloud.storage import Client
-
-logging.basicConfig(level=logging.INFO)
-
-# PROJECT_ID = "contentworkshop-159920"
-PROJECT_ID = "ops-central"
-LOCATION = "global"
-
-kms_client = kms_v1.KeyManagementServiceClient()
-
-
-def create_key(project_id, location, env, name):
- """
- Create the key. The keyring named after the env should already exist!
- """
- keyring = kms_client.key_ring_path(project_id, location, env)
- key_purpose = kms_v1.enums.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT
-
- response = kms_client.create_crypto_key(keyring, name, {"purpose": key_purpose})
-
-
-def get_key_url(project_id, location, env, secret_name):
- """
- Return the URL we can pass to google.cloud.kms to encrypt a string.
- """
- return kms_client.crypto_key_path_path(project_id, location, env, secret_name)
-
-
-def encrypt_string(key_url, plaintext):
- """
- Encrypt the plaintext string using the given GCloud KMS key.
-
- Returns the ciphertext.
- """
- response = kms_client.encrypt(key_url, plaintext)
- return response.ciphertext
-
-
-def upload_ciphertext(project_id, env, name, ciphertext):
- """
- Upload the given ciphertext to the secrets bucket.
-
- The secret is written as a file located in -secrets//.
-
- Note that a bucket with the name {project_id}-secrets should exist. If it doesn't,
- just create one. No special config needed.
- """
-
- bucket_name = "{id}-secrets".format(id=project_id)
- loc = "{env}/{name}".format(env=env, name=name)
-
- bucket = Client().get_bucket(bucket_name)
-
- # Write the ciphertext to the bucket!
- bucket.blob(loc).upload_from_string(ciphertext)
-
-
-def main():
- args = docopt(__doc__)
-
- if args["--dev"]:
- args["env"] = "dev"
- elif args["--staging"]:
- args["env"] = "staging"
- elif args["--prod"]:
- args["env"] = "prod"
-
- env = args["env"]
- name = args[""]
- plaintext = args[""]
- project_id = PROJECT_ID
- location = LOCATION
-
- logging.info("Creating key...")
- create_key(project_id, location, env, name)
-
- logging.info("Getting KMS key...")
- key = get_key_url(project_id, location, env, name)
-
- logging.info("Encrypting string...")
- ciphertext = encrypt_string(key, plaintext)
-
- # logging.info("Writing encrypted string...")
- logging.info("Uploading encrypted secret to storage...")
- upload_ciphertext(project_id, env, name, ciphertext)
-
- logging.info("Done!")
-
-
-main()
diff --git a/docker-compose.yml b/docker-compose.yml
index 7f21cc2844..037f1e5a03 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -30,6 +30,17 @@ x-studio-worker:
environment: *studio-environment
services:
+ studio-nginx:
+ platform: linux/amd64
+ build:
+ context: .
+ dockerfile: k8s/images/nginx/Dockerfile
+ ports:
+ - "8081:8080"
+ depends_on:
+ - studio-app
+ environment: *studio-environment
+
studio-app:
<<: *studio-worker
entrypoint: python docker/entrypoint.py
@@ -50,10 +61,13 @@ services:
MINIO_SECRET_KEY: development
MINIO_API_CORS_ALLOW_ORIGIN: 'http://localhost:8080,http://127.0.0.1:8080'
volumes:
- - minio_data:/data
+ - .docker/minio:/data
postgres:
- image: postgres:12
+ image: ghcr.io/learningequality/postgres
+ build:
+ context: ./docker
+ dockerfile: Dockerfile.postgres.dev
environment:
PGDATA: /var/lib/postgresql/data/pgdata
POSTGRES_USER: learningequality
@@ -61,6 +75,7 @@ services:
POSTGRES_DB: kolibri-studio
volumes:
- pgdata:/var/lib/postgresql/data/pgdata
+ - .docker/postgres:/docker-entrypoint-initdb.d
redis:
image: redis:6.0.9
diff --git a/docker/Dockerfile.demo b/docker/Dockerfile.demo
index de612c57ac..d9798827f4 100644
--- a/docker/Dockerfile.demo
+++ b/docker/Dockerfile.demo
@@ -1,4 +1,4 @@
-FROM python:3.9-slim-buster
+FROM python:3.10-slim-bookworm
# Set the timezone
RUN ln -fs /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
@@ -6,11 +6,19 @@ RUN ln -fs /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
ENV DEBIAN_FRONTEND noninteractive
# Default Python file.open file encoding to UTF-8 instead of ASCII, workaround for le-utils setup.py issue
ENV LANG C.UTF-8
-RUN apt-get update && apt-get -y install python3-pip python3-dev gcc libpq-dev make git curl libjpeg-dev ffmpeg
-
-# install node
-RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
-
+RUN apt-get update && apt-get -y install python3-pip python3-dev gcc libpq-dev make git curl libjpeg-dev libssl-dev libffi-dev ffmpeg
+
+# Pin, Download and install node 16.x
+RUN apt-get update \
+ && apt-get install -y ca-certificates curl gnupg \
+ && mkdir -p /etc/apt/keyrings \
+ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+ && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
+ && echo "Package: nodejs" >> /etc/apt/preferences.d/preferences \
+ && echo "Pin: origin deb.nodesource.com" >> /etc/apt/preferences.d/preferences \
+ && echo "Pin-Priority: 1001" >> /etc/apt/preferences.d/preferences\
+ && apt-get update \
+ && apt-get install -y nodejs
RUN npm install --location=global yarn && npm cache clean --force
COPY ./package.json .
diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev
index e6f44ee93f..fdbbebe820 100644
--- a/docker/Dockerfile.dev
+++ b/docker/Dockerfile.dev
@@ -1,4 +1,4 @@
-FROM python:3.9-slim-buster
+FROM python:3.10-slim-bookworm
# Set the timezone
RUN ln -fs /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
@@ -17,11 +17,19 @@ RUN apt-get update && \
apt-get -y install \
curl fish man \
python3-pip python3-dev \
- gcc libpq-dev make git gettext libjpeg-dev ffmpeg
-
-# Download then install node
-RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - &&\
- apt-get install -y nodejs
+ gcc libpq-dev libssl-dev libffi-dev make git gettext libjpeg-dev ffmpeg
+
+# Pin, Download and install node 16.x
+RUN apt-get update \
+ && apt-get install -y ca-certificates curl gnupg \
+ && mkdir -p /etc/apt/keyrings \
+ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+ && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
+ && echo "Package: nodejs" >> /etc/apt/preferences.d/preferences \
+ && echo "Pin: origin deb.nodesource.com" >> /etc/apt/preferences.d/preferences \
+ && echo "Pin-Priority: 1001" >> /etc/apt/preferences.d/preferences\
+ && apt-get update \
+ && apt-get install -y nodejs
################################################################################
diff --git a/docker/Dockerfile.postgres.dev b/docker/Dockerfile.postgres.dev
new file mode 100644
index 0000000000..0a38a49719
--- /dev/null
+++ b/docker/Dockerfile.postgres.dev
@@ -0,0 +1,9 @@
+# Installs pgvector to postgres base image.
+ARG PG_MAJOR=12
+FROM postgres:$PG_MAJOR
+
+LABEL org.opencontainers.image.source=https://github.com/learningequality/studio
+LABEL org.opencontainers.image.description="Postgres with PG Vector extension"
+LABEL org.opencontainers.image.licenses=MIT
+
+RUN apt-get update && apt-get install -y postgresql-$PG_MAJOR-pgvector
diff --git a/docs/_index.md b/docs/_index.md
new file mode 100644
index 0000000000..a7ceea42b9
--- /dev/null
+++ b/docs/_index.md
@@ -0,0 +1,26 @@
+# Studio Developer Documentation
+
+## Local development guides
+
+- [Local development instructions: With Docker (recommended)](./local_dev_docker.md)
+- [Local development instructions: Run everything on your host machine](./local_dev_host.md)
+- [Local development tools](./dev_tools.md)
+- [Running tests](./running_tests.md)
+- [Adding or updating dependencies](./dependencies.md)
+
+## Additional development tools
+
+- [Storybook](./storybook.md)
+
+## Deployment
+
+- [Docker + Kubernetes Studio Instance Setup](./docker_kubernetes_setup.md)
+
+## API
+
+- [API Endpoints](./api_endpoints.md)
+
+## Architecture and components documentation
+
+- [Studio Architecture](./architecture.md)
+- [Markdown Editor/Viewer](./markdown_editor_viewer.md)
diff --git a/docs/api_endpoints.md b/docs/api_endpoints.md
index 094a018f5f..9d2de4a9f4 100644
--- a/docs/api_endpoints.md
+++ b/docs/api_endpoints.md
@@ -199,24 +199,6 @@ Response:
}
-
-### api/internal/activate_channel_internal
-_Method: contentcuration.views.internal.activate_channel_internal_
-Deploys a staged channel to the live channel
-
- POST api/internal/activate_channel_internal
- Header: ---
- Body:
- {"channel_id": "{uuid.hex}"}
-
-Response:
-
- {
- "success": True
- }
-
-
-
### api/internal/get_tree_data
_Method: contentcuration.views.internal.get_tree_data_
Returns the complete tree hierarchy information (for tree specified in `tree`).
@@ -309,18 +291,6 @@ Response:
Channel endpoints
--------------------------
-### api/activate_channel
-_Method: contentcuration.views.base.activate_channel_endpoint_
-Moves the channel's staging tree to the main tree
-
- POST api/activate_channel
- Body: {"channel_id": "{uuid.hex}"}
- Response:
-
- {
- "success": true
- }
-
### api/get_staged_diff_endpoint
_Method: contentcuration.views.base.get_staged_diff_endpoint_
diff --git a/docs/dependencies.md b/docs/dependencies.md
new file mode 100644
index 0000000000..9b27056ce4
--- /dev/null
+++ b/docs/dependencies.md
@@ -0,0 +1,11 @@
+# Adding or updating dependencies
+
+We use `pip-tools` to ensure all our dependencies use the same versions on all deployments.
+
+To add a dependency, add it to either `requirements.in` or `requirements-dev.in`, then
+run `pip-compile requirements[-dev|-docs].in` to generate the .txt file. Please make sure that
+both the `.in` and `.txt` file changes are part of the commit when updating dependencies.
+
+To update a dependency, use `pip-compile --upgrade-package [package-name] requirements[-dev|-docs].in`
+
+For more details, please see the [pip-tools docs on Github](https://github.com/jazzband/pip-tools).
diff --git a/docs/dev_tools.md b/docs/dev_tools.md
new file mode 100644
index 0000000000..7d23039f25
--- /dev/null
+++ b/docs/dev_tools.md
@@ -0,0 +1,86 @@
+# Local development tools
+
+## Running tests
+
+With Studio's services running, you may run tests with the following commands:
+
+```bash
+# backend
+make test
+# frontend
+yarn run test
+```
+
+View [more testing tips](./running_tests.md)
+
+## Linting
+
+Front-end linting is run using:
+
+```bash
+yarn run lint-frontend
+```
+
+Some linting errors can be fixed automatically by running:
+
+```bash
+yarn run lint-frontend:format
+```
+
+Make sure you've set up pre-commit hooks as described above. This will ensure that linting is automatically run on staged changes before every commit.
+
+## Profiling and local production testing
+
+If you want to test the performance of your changes, you can start up a local server with settings closer to a production environment like so:
+
+```bash
+# build frontend dependencies
+yarn run build
+# run the server (no webpack)
+yarn run runserver
+# or for profiling production more closely
+yarn run runserver:prod-profiling
+```
+
+Once the local production server is running, you can also use Locust to test your changes under scenarios of high demand like so:
+
+```bash
+cd deploy/chaos/loadtest
+make timed_run
+make stop_slaves # mac: killall python
+```
+
+### Profiling
+
+In case you need to profile the application to know which part of the code are more time consuming, there are two different profilers available to work in two different modes. Both will store the profiling output in a directory that's determined by the `PROFILE_DIR` env variable. If this variable is not set, the output files will be store in a folder called profiler inside the OS temp folder (`/tmp/profile` usually)
+Note that both profiling modes are incompatible: you can either use one or the other, but not both at the same time. In case the env variables are set for both modes, _All request profiling mode_ will be used.
+
+#### All requests profiling mode
+
+This mode will create interactive html files with all the profiling information for every request the Studio server receives. The name of the files will contain the total execution time, the endpoint name and a timestamp.
+
+To activate it an env variable called `PROFILE_STUDIO_FULL` must be set.
+
+Example of use:
+
+`PROFILE_STUDIO_FULL=y yarn runserver`
+
+Afterwards no further treatment of the generated files is needed. You can open directly the html files in your browser.
+
+#### Endpoint profiling mode
+
+When using the _all requests mode_ it's usual that the profile folder is soon full of information for requests that are not interesting for the developer, obscuring the files for specific endpoints.
+
+If an env variable called `PROFILE_STUDIO_FILTER` is used, the profiler will be executed only on the http requests containing the text stated by the variable.
+
+Example of use:
+
+`PROFILE_STUDIO_FILTER=edit yarn localprodserver`
+
+For this case, only html requests having the text _edit_ in their request path will be profiled. The profile folder will not have html files, but binary dump files (with the timestamp as filename) of the profiler information that can be later seen by different profiling tools (`snakeviz` that can be installed using pip is recommended). Also while the server is running, the ten most time consuming lines of code of the filtered request will be shown in the console where Studio has been launched.
+
+Example of snakeviz use:
+
+`snakeviz /tmp/profile/studio\:20200909161405011678.prof`
+
+will open the browser with an interactive diagram with all the profiling information
diff --git a/docs/host_services_setup.md b/docs/host_services_setup.md
index 7ded077aa7..bd21c52b35 100644
--- a/docs/host_services_setup.md
+++ b/docs/host_services_setup.md
@@ -82,13 +82,13 @@ $ cat runtime.txt
# inside Ubuntu Bionic, which is used to build images for Studio.
# We encode it here so that it can be picked up by Github's dependabot
# to manage automated package upgrades.
-python-3.9.13
+python-3.10.13
```
-So to install python 3.9.13 through `pyenv` and set up a virtual environment:
+So to install python 3.10.13 through `pyenv` and set up a virtual environment:
```bash
-pyenv install 3.9.13
-pyenv virtualenv 3.9.13 studio-py3.9
-pyenv activate studio-py3.9
+pyenv install 3.10.13
+pyenv virtualenv 3.10.13 studio-py3.10
+pyenv activate studio-py3.10
```
Now you may install Studio's Python dependencies:
```bash
diff --git a/docs/local_dev_docker.md b/docs/local_dev_docker.md
new file mode 100644
index 0000000000..9234330ff4
--- /dev/null
+++ b/docs/local_dev_docker.md
@@ -0,0 +1,121 @@
+# Local development instructions: With Docker (recommended)
+
+The following guide utilizes docker and docker-compose to run select services required for Studio to function. It's our recommended setup. However, if you would rather install these services on your host, please follow the [host-setup guide](./local_dev_host.md).
+
+## Prerequisites
+Please install these prerequisites, or alternatives for setting up your local development environment:
+- [volta](https://docs.volta.sh/guide/getting-started) or a different node.js manager
+- [pyenv](https://kolibri-dev.readthedocs.io/en/develop/howtos/installing_pyenv.html) and [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv#installation)
+- [docker](https://docs.docker.com/install/) and [docker-compose](https://docs.docker.com/compose/install/)
+
+
+## Build your python virtual environment
+To determine the preferred version of Python, you can check the `runtime.txt` file:
+```bash
+$ cat runtime.txt
+# This is the required version of Python to run Studio currently.
+# This is determined by the default Python 3 version that is installed
+# inside Ubuntu Bionic, which is used to build images for Studio.
+# We encode it here so that it can be picked up by Github's dependabot
+# to manage automated package upgrades.
+python-3.10.13
+```
+Use `pyenv` to install the version of Python listed in that file, and to also set up a virtual environment:
+```bash
+pyenv install 3.10.13
+pyenv virtualenv 3.10.13 studio-py3.10
+pyenv activate studio-py3.10
+```
+Now you may install Studio's Python dependencies:
+```bash
+pip install -r requirements.txt -r requirements-dev.txt
+```
+To deactivate the virtual environment, when you're finished developing on Studio for the time being:
+```bash
+pyenv deactivate
+```
+
+### A note about dependencies on Apple Silicon M1+
+If you run into an error with `pip install` related to the `grcpio` package, it is because it currently [does not support M1 with the version for `grcpio` Studio uses](https://github.com/grpc/grpc/issues/25082). In order to fix it, you will need to add the following environmental variables before running `pip install`:
+```bash
+export GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1
+export GRPC_PYTHON_BUILD_SYSTEM_ZLIB=1
+export CFLAGS="-I/opt/homebrew/opt/openssl/include"
+export LDFLAGS="-L/opt/homebrew/opt/openssl/lib"
+```
+
+## Install frontend dependencies
+Install the version of node.js supported by Studio, and install `yarn` version 1.x:
+```bash
+volta install node@16
+volta install yarn@1
+```
+After installing `yarn`, you may now install frontend dependencies:
+```bash
+yarn install
+```
+
+## Install and run services
+
+Studio requires some background services to be running:
+
+* Minio - a local S3 storage emulation
+* PostgreSQL (postgres) - a relational database
+* Redis - a fast key/value store useful for caching
+* Celery - the task manager and executor, which relies on the Studio codebase
+
+Generally speaking, you'll want to open a separate terminal/terminal-tab to run the services. With docker and docker-compose installed, running the above services is as easy as:
+```bash
+make run-services
+```
+
+The above command may take longer the first time it's run. It includes starting the `celery` workers, and the other dependent services through docker, which can be done separately with the following two commands:
+
+```bash
+make dcservicesup
+make devceleryworkers
+```
+
+To confirm that docker-based services are running, you should see three containers when executing `docker ps`. For example:
+
+```bash
+> docker ps
+CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
+e09c5c203b93 redis:6.0.9 "docker-entrypoint.sā¦" 51 seconds ago Up 49 seconds 0.0.0.0:6379->6379/tcp studio_vue-refactor_redis_1
+6164371efb6b minio/minio "minio server /data" 51 seconds ago Up 49 seconds 0.0.0.0:9000->9000/tcp studio_vue-refactor_minio_1
+c86bbfa3a59e postgres:12.10 "docker-entrypoint.sā¦" 51 seconds ago Up 49 seconds 0.0.0.0:5432->5432/tcp studio_vue-refactor_postgres_1
+```
+
+To stop the services, press Ctrl + C in the terminal where you ran `make run-services` (or `dcservicesup`). Once you've done that, you may run the following command to remove the docker containers (they will be recreated when you run `run-services` or `dcservicesup` again):
+```bash
+make dcservicesdown
+```
+
+## Initializing Studio
+With the services running, in a separate terminal/terminal-tab, we can now initialize the database for Studio development purposes. The command below will initialize the database tables, import constants, and a user account for development:
+```bash
+yarn run devsetup
+```
+
+## Running the development server
+With the services running, in a separate terminal/terminal-tab, and the database initialized, we can start the dev server:
+```bash
+yarn run devserver:hot # with Vue hot module reloading
+# or
+yarn run devserver # without hot module reloading
+```
+
+Either of the above commands will take a few moments to build the frontend. When it finishes, you can sign in with the account created by the `yarn run devsetup` command:
+- url: `http://localhost:8080/accounts/login/`
+- username: `a@a.com`
+- password: `a`
+
+## Running the celery service
+Studio uses `celery` for executing asynchronous tasks, which are integral to Studio's channel editing architecture. The celery service does not reload when there are Python changes like the Django devserver does, so it's often preferred to run it separately. If you are developing changes against a task or the celery configuration, you'll need to use `make dcservicesup` to run only the docker-based services.
+
+In a separate terminal/terminal-tab, run the following to start the service and press Ctrl + C to stop it:
+```bash
+make devceleryworkers
+```
+
+Stop and restart the above to reload your changes.
diff --git a/docs/local_dev_host.md b/docs/local_dev_host.md
new file mode 100644
index 0000000000..28a27679a6
--- /dev/null
+++ b/docs/local_dev_host.md
@@ -0,0 +1,149 @@
+# Local development instructions: Run everything on your host machine
+
+This guide will walk through setting up Kolibri Studio for local development, where you'll run Studio's Python apps and all of Studio's services on your host machine, without the need for docker.
+
+## Prerequisites
+- [volta](https://docs.volta.sh/guide/getting-started)
+- [pyenv](https://kolibri-dev.readthedocs.io/en/develop/howtos/installing_pyenv.html) and [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv#installation)
+
+## Install system dependencies and services
+Studio requires some background services to be running:
+
+* Minio - a local S3 storage emulation
+* PostgreSQL - a relational database
+* Redis - a fast key/value store useful for caching
+
+### Ubuntu or Debian
+```bash
+# Install packages
+sudo apt-get install -y python-tk \
+ postgresql-server-dev-all postgresql-contrib postgresql-client postgresql-12 \
+ ffmpeg libmagickwand-dev redis-server wkhtmltopdf
+
+# Install minio
+wget https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2020-06-01T17-28-03Z -O bin/minio
+sudo chmod +x bin/minio
+```
+
+### Mac OS
+```bash
+brew install postgresql@12 redis ffmpeg imagemagick@6 gs
+# note, this version of minio may not be compatible with Studio
+brew install minio/stable/minio
+brew link --force postgresql@12
+brew link --force imagemagick@6
+```
+
+### Windows
+
+Windows is no longer supported due to incompatibilities with some required packages.
+
+## Set up the database
+
+Make sure postgres is running:
+
+```bash
+service postgresql start
+# alternatively: pg_ctl -D /usr/local/var/postgresql@12 start
+```
+
+Start the client with:
+
+```bash
+sudo su postgres # switch to the postgres account
+psql # mac: psql postgres
+```
+
+Create a database user with username `learningequality` and password `kolibri`:
+
+```sql
+CREATE USER learningequality with NOSUPERUSER INHERIT NOCREATEROLE CREATEDB LOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'kolibri';
+ ```
+
+Create a database called `kolibri-studio`:
+
+```sql
+CREATE DATABASE "kolibri-studio" WITH TEMPLATE = template0 ENCODING = "UTF8" OWNER = "learningequality";
+```
+
+Press Ctrl +D to exit the `psql` client. Finally
+
+```bash
+exit # leave the postgres account
+```
+
+
+## Build your python virtual environment
+To determine what version of Python studio needs, you can check the `runtime.txt` file:
+```bash
+$ cat runtime.txt
+# This is the required version of Python to run Studio currently.
+# This is determined by the default Python 3 version that is installed
+# inside Ubuntu Bionic, which is used to build images for Studio.
+# We encode it here so that it can be picked up by Github's dependabot
+# to manage automated package upgrades.
+python-3.10.13
+```
+So to install python 3.10.13 through `pyenv` and set up a virtual environment:
+```bash
+pyenv install 3.10.13
+pyenv virtualenv 3.10.13 studio-py3.10
+pyenv activate studio-py3.10
+```
+Now you may install Studio's Python dependencies:
+```bash
+pip install -r requirements.txt -r requirements-dev.txt
+```
+To deactivate the virtual environment, when you're finished developing on Studio for the time being:
+```bash
+pyenv deactivate
+```
+
+### A note about `psycopg2`
+The packages `postgresql-12`, `postgresql-contrib`, and `postgresql-server-dev-all` are required to build `psycopg2` python driver.
+
+### A note about dependencies on Apple Silicon M1+
+If you run into an error with `pip install` related to the `grcpio` package, it is because it currently [does not support M1 with the version for `grcpio` Studio uses](https://github.com/grpc/grpc/issues/25082). In order to fix it, you will need to add the following environmental variables before running `pip install`:
+```bash
+export GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1
+export GRPC_PYTHON_BUILD_SYSTEM_ZLIB=1
+export CFLAGS="-I/opt/homebrew/opt/openssl/include"
+export LDFLAGS="-L/opt/homebrew/opt/openssl/lib"
+```
+
+## Install frontend dependencies
+Ready the version of node.js supported by Studio, and install yarn.
+```bash
+volta install node@16
+volta install yarn
+```
+Then you can install frontend dependencies
+```bash
+yarn install
+```
+
+## Run the services
+
+Having installed all the necessary services, initialized your python virtual environment, and installed `yarn`, you're now ready to start the services. Generally speaking, you'll want to open a separate terminal/terminal-tab to run the services. The following will ensure all services are started, in addition to starting the celery workers service:
+```bash
+yarn run services
+```
+
+## Initializing Studio
+With the services running, in a separate terminal/terminal-tab, we can now initialize the database for Studio development purposes. The command below will initialize the database, in addition to adding a user account for development:
+```bash
+yarn run devsetup
+```
+
+## Running the development server
+With the services running, in a separate terminal/terminal-tab, and the database initialized, we can start the dev server:
+```bash
+yarn run devserver:hot # with Vue hot module reloading
+# or
+yarn run devserver # without hot module reloading
+```
+
+Either of the above commands will take a few minutes to build the frontend. When it's done, you can sign in with the account created by the `yarn run devsetup` command:
+- url: `http://localhost:8080/accounts/login/`
+- username: `a@a.com`
+- password: `a`
diff --git a/docs/storybook.md b/docs/storybook.md
new file mode 100644
index 0000000000..78eb9d5658
--- /dev/null
+++ b/docs/storybook.md
@@ -0,0 +1,35 @@
+# Storybook
+
+Storybook is a development environment for UI components. If this is your first encounter with this tool, you can check [this presentation](https://docs.google.com/presentation/d/10JL4C9buygWsTbT62Ym149Yh9zSR9nY_ZqFumBKUY0o/edit?usp=sharing) or [its website](https://storybook.js.org/). You are encouraged to use it any time you need to develop a new UI component. It is especially suitable for smaller to middle size components that represent basic UI building blocks.
+
+An example is worth a thousand words so please have a look at these simple [stories of an example component](./contentcuration/contentcuration/frontend/shared/views/details/DetailsRow.stories.js) to see how to write yours. For detailed information on writing stories you can [go through this tutorial](https://www.learnstorybook.com/intro-to-storybook/).
+
+You can also check [official addons](https://storybook.js.org/addons/).
+
+**Run development server**
+
+```bash
+yarn run storybook
+```
+
+With detailed webpack information (useful when debugging loaders, addons and similar):
+
+```bash
+yarn run storybook:debug
+```
+
+**Bundle**
+
+```bash
+yarn run storybook:build
+```
+
+The output is saved to *storybook-static/*.
+
+## Current usage notes
+
+We've decided not to push our stories to the codebase and keep them locally in the near future. Although this limits the number of advantages Storybook provides, it allows us to start using it as soon as possible without the need to agree on all conventions and it also gives the whole team enough time to test the development workflow so we can decide later if we want to adopt this tool in a larger scale.
+
+Taking into account the above-mentioned, all stories except of example *DetailsRow.stories.js* will be ignored by git as long as you use a naming convention for Storybook source files: *\*.stories.js*.
+
+Although we don't share stories at this point, Storybook is installed and configured in the codebase to prevent the need for everyone to configure everything locally. If you update Storybook Webpack settings, install a new plugin and similar, you are welcome to share such updates with other members of the team.
diff --git a/integration_testing/features/access-kolibri-studio/create-account.feature b/integration_testing/features/access-kolibri-studio/create-account.feature
index 84038a2a02..78c6c085db 100755
--- a/integration_testing/features/access-kolibri-studio/create-account.feature
+++ b/integration_testing/features/access-kolibri-studio/create-account.feature
@@ -1,30 +1,33 @@
-Feature: Create account on Studio
+Feature: Create an account on Studio
- Background:
- Given I am signed in to Studio
- And I am on Studio home page
- And I do not have an account registered with my
+ Background:
+ Given I am not signed in to Studio
+ And I am at Studio's sign-in page
+ And I haven't previously registered with my email
- Scenario: Create account
+ Scenario: Create an account and sign in with the created account
+ When I click the *Create an account* button
+ Then I see the *Create an account* form
+ And I see the *Basic information* section containing the following fields: First name, Last name, Email, Password and Confirm password
+ And I see the *How do you plan on using Kolibri Studio (check all that apply)*, *Where do you plan to use Kolibri Studio? (check all that apply)*, *How did you hear about us?* and *I have read and agree to terms of service and the privacy policy* sections
+ And I see the *View Privacy Policy* and *View Terms of Service* links
When I input all the required fields
And I click the *Finish* button
Then I see the *Activation link sent* page
- And I see the email in my Inbox
- When I click the activation link
- And I go to Studio home page
- Then I am able to sign in
+ And I see *Thank you for creating an account! To complete the process, please check your email for the activation link we sent you.*
+ When I open the received email and click the activation link
+ Then I see the *Account successfully created* page
+ When I click the *Continue to sign-in page* button
+ Then I am at the sign-in page
+ When I fill in my email and password
+ And I click the *Sign in* button
+ Then I am able to sign in successfully
- Scenario: See error notification
+ Scenario: See an error notification if not all required information is provided
When I fail to provide all the required information
Then I see the *Please fix the errors below* alert under the main heading
And I see the *Field is required* error notification for each missing information
When I correct the error(s)
And I click the *Finish* button
Then I see the *Activation link sent* page
- And I see the email in my Inbox
- When I click the activation link
- And I go to Studio home page
- Then I am able to sign in
-
- Examples:
- | email |
\ No newline at end of file
+ And I see *Thank you for creating an account! To complete the process, please check your email for the activation link we sent you.*
diff --git a/integration_testing/features/access-kolibri-studio/explore-without-an-account.feature b/integration_testing/features/access-kolibri-studio/explore-without-an-account.feature
index 46e5572ac6..a1bd0c0457 100755
--- a/integration_testing/features/access-kolibri-studio/explore-without-an-account.feature
+++ b/integration_testing/features/access-kolibri-studio/explore-without-an-account.feature
@@ -8,4 +8,4 @@ Feature: Explore without an account
When I click the *Explore without an account* link
Then I see the *Content Library* page with available public channels
And I can filter the search results
- And I can view or download the channel summary
\ No newline at end of file
+ And I can view or download the channel summary
diff --git a/integration_testing/features/access-kolibri-studio/reset-your-password.feature b/integration_testing/features/access-kolibri-studio/reset-your-password.feature
index 4e8b9274df..46bb82b954 100755
--- a/integration_testing/features/access-kolibri-studio/reset-your-password.feature
+++ b/integration_testing/features/access-kolibri-studio/reset-your-password.feature
@@ -39,4 +39,3 @@ Feature: Reset your password
When I click the link in the email again
Then I see a page with the following message: Reset link expired
And I see a *Request a new password reset link* button
-
diff --git a/integration_testing/features/access-kolibri-studio/sign-in.feature b/integration_testing/features/access-kolibri-studio/sign-in.feature
new file mode 100755
index 0000000000..1678e03bac
--- /dev/null
+++ b/integration_testing/features/access-kolibri-studio/sign-in.feature
@@ -0,0 +1,24 @@
+Feature: Sign in to Studio
+
+ Background:
+ Given I am not signed in to Studio
+ And I am at Studio's sign-in page
+ And I have already registered with my email
+
+ Scenario: Sign in to Studio
+ When I fill in my email
+ And I fill in my password
+ And I click the *Sign in* button
+ Then I am signed in
+ And I am at *My channels* page
+
+ Scenario: See error notification for incorrect email or password
+ When I fill in an incorrect email or password
+ And I click the *Sign in* button
+ Then I see the following validation message above the form: *Email or password is incorrect*
+
+ Scenario: See validation messages for the required fields
+ When I leave one or both of the *Email* and *Password* fields empty
+ And I click the *Sign in* button
+ Then I see the fields colored in red
+ And I see a *Field is required* message under each empty field
diff --git a/integration_testing/features/access-kolibri-studio/url-updates.feature b/integration_testing/features/access-kolibri-studio/url-updates.feature
deleted file mode 100755
index 86f441dee6..0000000000
--- a/integration_testing/features/access-kolibri-studio/url-updates.feature
+++ /dev/null
@@ -1,25 +0,0 @@
-Feature: URL update when the user switches between channel list
-User can see changes at the URL when they switch between channel list
-
- Background:
- Given I am signed in to Studio
-
- Scenario: URL update at the *My Channels* nav
- When I click *My Channels* nav
- Then I see that the URL changes
-
- Scenario: URL update at the *Starred* nav
- When I click *Starred* nav
- Then I see that the URL changes
-
- Scenario: URL update at the *View-Only* nav
- When I click *View-Only* nav
- Then I see that the URL changes
-
- Scenario: URL update at the *Content Library* nav
- When I click *Content Library* nav
- Then I see that the URL changes
-
- Scenario: URL update at the *Collections* nav
- When I click *Collections* nav
- Then I see that the URL changes
\ No newline at end of file
diff --git a/integration_testing/features/manage-account/change-language.feature b/integration_testing/features/common/change-language.feature
similarity index 59%
rename from integration_testing/features/manage-account/change-language.feature
rename to integration_testing/features/common/change-language.feature
index e5eb75523a..f642029fde 100755
--- a/integration_testing/features/manage-account/change-language.feature
+++ b/integration_testing/features/common/change-language.feature
@@ -8,6 +8,16 @@ Feature: Change language
Then the language interface changes to the selected language
And the selected language is no longer clickable
+ Scenario: Change language when you are exploring without an account
+ Given I am not signed-in to Studio
+ And I have clicked the *Explore without an account link*
+ When I click on the user profile icon
+ And I click *Change language*
+ Then I see a *Change language* modal window displayed with several languages to choose from
+ When I click on a language which is not currently selected
+ And I click the *Confirm* button
+ Then the interface language changes to the selected language
+
Scenario: Change language as a signed-in user
Given I am signed-in to Studio
And I click the user profile icon
diff --git a/integration_testing/features/manage-account/open-sidebar-and-user-menus.feature b/integration_testing/features/common/open-sidebar-and-user-menus.feature
similarity index 90%
rename from integration_testing/features/manage-account/open-sidebar-and-user-menus.feature
rename to integration_testing/features/common/open-sidebar-and-user-menus.feature
index 9de7b076ee..f52112570f 100755
--- a/integration_testing/features/manage-account/open-sidebar-and-user-menus.feature
+++ b/integration_testing/features/common/open-sidebar-and-user-menus.feature
@@ -1,7 +1,7 @@
Feature: Open and close sidebar and user menus
-User needs to be able to open and close the sidebar menu and the user menu
+User needs to be able to open and close the sidebar menu and the user menu
- Background:
+ Background:
Given I am signed in to Studio
And I am on any of the tabs (*My Channels*, *Starred*, *View only*, *Content Library*, or *Collections*)
@@ -17,4 +17,4 @@ User needs to be able to open and close the sidebar menu and the user menu
Then I see the user menu
And I can select any of the options inside
When I click the user menu button again, or anywhere on the browser screen
- Then I don't see the user menu anymore
\ No newline at end of file
+ Then I don't see the user menu anymore
diff --git a/integration_testing/features/manage-account/delete-account.feature b/integration_testing/features/manage-account/delete-account.feature
index 917214450d..3ce1047fcb 100755
--- a/integration_testing/features/manage-account/delete-account.feature
+++ b/integration_testing/features/manage-account/delete-account.feature
@@ -1,6 +1,6 @@
Feature: Delete account
- Background:
+ Background:
Given I am signed in to Studio
And I am on the *Settings > Account* page
@@ -17,4 +17,4 @@ Feature: Delete account
Given my account still has active channels for which I am an admin
When I look under the *Delete account* heading
Then I don't see the *Delete account* button
- And I need to delete my active channels or invite other admins in order to see the *Delete account* button
\ No newline at end of file
+ And I need to delete my active channels or invite other admins in order to see the *Delete account* button
diff --git a/integration_testing/features/manage-account/edit-account-information.feature b/integration_testing/features/manage-account/edit-account-information.feature
index cfc8ce4bfe..fa9ecb46de 100755
--- a/integration_testing/features/manage-account/edit-account-information.feature
+++ b/integration_testing/features/manage-account/edit-account-information.feature
@@ -1,6 +1,6 @@
Feature: Edit account information
- Background:
+ Background:
Given I am signed in to Studio
And I am on Studio *Settings > Account* page
@@ -10,7 +10,7 @@ Feature: Edit account information
And I press *Save changes* button in the modal
Then I see a snackbar appears to confirm my password was updated
And the modal is dismissed
-
+
Scenario: Editing full name
When I click the *Edit* hyperlink near my username
And I make changes to my full name
@@ -20,4 +20,4 @@ Feature: Edit account information
Scenario: Copying API token
When I click the copy button in the token text field
- Then a snackbar appears to confirm the code is copied
\ No newline at end of file
+ Then a snackbar appears to confirm the code is copied
diff --git a/integration_testing/features/manage-account/export-account-information.feature b/integration_testing/features/manage-account/export-account-information.feature
index a497529897..95d3b9c2b4 100755
--- a/integration_testing/features/manage-account/export-account-information.feature
+++ b/integration_testing/features/manage-account/export-account-information.feature
@@ -1,6 +1,6 @@
Feature: Export account information
- Background:
+ Background:
Given I have a Studio account
And I am signed in to Studio
And I have interacted with various data and channels
@@ -14,4 +14,4 @@ Feature: Export account information
Scenario: Viewing the exported data
When I check the Inbox of my email account registered at Studio
- Then I see an email with an attachment that contains my account data
\ No newline at end of file
+ Then I see an email with an attachment that contains my account data
diff --git a/integration_testing/features/manage-account/report-issue.feature b/integration_testing/features/manage-account/report-issue.feature
index 91d7f95fe8..27ee5105bb 100755
--- a/integration_testing/features/manage-account/report-issue.feature
+++ b/integration_testing/features/manage-account/report-issue.feature
@@ -20,4 +20,4 @@ Feature: Report an issue
When I resolve those errors
And click the *Submit* button
Then I see the modal disappears
- And a snackbar appears to confirm the submission
\ No newline at end of file
+ And a snackbar appears to confirm the submission
diff --git a/integration_testing/features/manage-account/request-more-storage-space.feature b/integration_testing/features/manage-account/request-more-storage-space.feature
index 8e1f8c4bc3..bac9d32f63 100755
--- a/integration_testing/features/manage-account/request-more-storage-space.feature
+++ b/integration_testing/features/manage-account/request-more-storage-space.feature
@@ -1,7 +1,7 @@
Feature: Request more storage space
Background:
- Given I am signed in to Studio
+ Given I am signed in to Studio
And I am on Studio *Settings > Storage* page
And I click *Show form* on the page
@@ -16,4 +16,4 @@ Feature: Request more storage space
And I click the *Send request* submit button
Then I see a system error message above the form
And I see my text field inputs still intact
- And I see error validation text near fields that need input
\ No newline at end of file
+ And I see error validation text near fields that need input
diff --git a/integration_testing/features/manage-account/review-used-storage.feature b/integration_testing/features/manage-account/review-used-storage.feature
index ef0d9fccc8..743b36c1ac 100755
--- a/integration_testing/features/manage-account/review-used-storage.feature
+++ b/integration_testing/features/manage-account/review-used-storage.feature
@@ -1,6 +1,6 @@
Feature: Review used storage
- Background:
+ Background:
Given I am signed in to Studio
And I am on the *Settings > Storage* page
@@ -12,4 +12,4 @@ Feature: Review used storage
Scenario: No storage used
Given that I have not uploaded any resources in my channels
When I look under the *% storage used* heading
- Then I see that there is 0% storage used
\ No newline at end of file
+ Then I see that there is 0% storage used
diff --git a/integration_testing/features/manage-account/view-support-links.feature b/integration_testing/features/manage-account/view-support-links.feature
index 795e5c4c08..8013621f30 100755
--- a/integration_testing/features/manage-account/view-support-links.feature
+++ b/integration_testing/features/manage-account/view-support-links.feature
@@ -1,4 +1,4 @@
-Feature: View support links
+Feature: View support links
Background:
Given I am signed in to Studio
@@ -8,7 +8,7 @@ Feature: View support links
When I click the *Kolibri Studio User Guide* link
Then a new browser tab opens with the ReadTheDocs page
# Privacy Policy page not yet implemented
-
+
Scenario: View notable issues
When I click one of the notable issue hyperlinks
- Then a new browser tab opens with the GitHub issue in question
\ No newline at end of file
+ Then a new browser tab opens with the GitHub issue in question
diff --git a/integration_testing/features/manage-channels/add-channel-to-starred.feature b/integration_testing/features/manage-channels/add-channel-to-starred.feature
index 00b6c9aabd..91da946562 100755
--- a/integration_testing/features/manage-channels/add-channel-to-starred.feature
+++ b/integration_testing/features/manage-channels/add-channel-to-starred.feature
@@ -1,7 +1,7 @@
Feature: Add a channel to the *Starred* tab
A user needs to be able to mark channels with a star to label them as favorite for easy access
- Background:
+ Background:
Given I am signed in to Studio
And I am on any of the tabs (*My Channels*, *Starred*, *View-only*, or *Content Library*)
And I see a channel that is not starred (white star)
@@ -9,8 +9,8 @@ Feature: Add a channel to the *Starred* tab
Scenario: Add channel to the *Starred* tab
When I click the *Add to starred channels* button for the desired channel
Then I see that the channel's *Add to starred channels* button is now black
- And I see a message that the channel was added to the starred channels
- When I click and open the *Starred* tab
+ And I see a message that the channel was added to the starred channels
+ When I click and open the *Starred* tab
Then I see that the channel is displayed among the starred channels
Scenario: Unstar a channel
@@ -19,11 +19,11 @@ Feature: Add a channel to the *Starred* tab
When I click the *Remove from starred channels* button for the channel
Then I see that the channel's *Remove from starred channels* button is now white
And I see a message that the channel was removed from the starred channels
- When I click and open the *Starred* tab
+ When I click and open the *Starred* tab
Then I see the list of starred channels
But I don't see the unstarred channel on the list
- Scenario: Remove channel directly from the *Starred* tab
+ Scenario: Remove channel directly from the *Starred* tab
Given I am on the *Starred* tab
And I see a starred channel
When I click the *Remove from starred channels* button for the channel
@@ -34,4 +34,4 @@ Feature: Add a channel to the *Starred* tab
And I've selected the *Starred* checkbox
When I click on the star button of a starred channel.
Then I should see a message that the channel was removed from the starred channels
- And the channel should no longer be displayed in the list with the filtered channels
\ No newline at end of file
+ And the channel should no longer be displayed in the list with the filtered channels
diff --git a/integration_testing/features/manage-channels/create-a-channel.feature b/integration_testing/features/manage-channels/create-a-channel.feature
index a14a694bb3..5e4b357fd0 100755
--- a/integration_testing/features/manage-channels/create-a-channel.feature
+++ b/integration_testing/features/manage-channels/create-a-channel.feature
@@ -1,6 +1,6 @@
Feature: Create a channel
- Background:
+ Background:
Given I am signed in to Studio
And I am on the *My Channels* tab
@@ -16,4 +16,4 @@ Feature: Create a channel
Examples:
| channel_name | channel_description | language |
- | ck-12 | sample channel | english |
+ | ck-12 | sample channel | english |
diff --git a/integration_testing/features/manage-channels/delete-channel.feature b/integration_testing/features/manage-channels/delete-channel.feature
index 512eacc2fd..8e774a2cd7 100755
--- a/integration_testing/features/manage-channels/delete-channel.feature
+++ b/integration_testing/features/manage-channels/delete-channel.feature
@@ -1,6 +1,6 @@
-Feature: Delete channel
+Feature: Delete channel
- Background:
+ Background:
Given I am signed in to Studio
And I have permissions to edit
And I am on *My Channels* tab
@@ -30,4 +30,4 @@ Feature: Delete channel
And I click the *Delete channel* button
Then I see a message that the channel is deleted
And I am brought back on *My channels* tab
- And the deleted channel is no longer displayed on *My Channels* tab
\ No newline at end of file
+ And the deleted channel is no longer displayed on *My Channels* tab
diff --git a/integration_testing/features/manage-channels/deploy-channel.feature b/integration_testing/features/manage-channels/deploy-channel.feature
index fe2445616e..98753933b0 100755
--- a/integration_testing/features/manage-channels/deploy-channel.feature
+++ b/integration_testing/features/manage-channels/deploy-channel.feature
@@ -1,6 +1,6 @@
Feature: Deploy Channel
- Background:
+ Background:
Given I am signed in to Studio as
And I have uploaded content to the staging tree for
@@ -11,5 +11,5 @@ Feature: Deploy Channel
When I click the *Deploy* button
Then I get redirected to https://api.studio.learningequality.org/channels//edit/
- Examples:
- | username | channel_id |
\ No newline at end of file
+ Examples:
+ | username | channel_id |
diff --git a/integration_testing/features/manage-channels/edit-channel-details.feature b/integration_testing/features/manage-channels/edit-channel-details.feature
index b527f3ea28..53dbf8a136 100755
--- a/integration_testing/features/manage-channels/edit-channel-details.feature
+++ b/integration_testing/features/manage-channels/edit-channel-details.feature
@@ -1,6 +1,6 @@
-Feature: Edit channel details
+Feature: Edit channel details
- Background:
+ Background:
Given I am signed in to Studio
And I am on the channel editor view
diff --git a/integration_testing/features/manage-channels/get-channel-token-after-publishing.feature b/integration_testing/features/manage-channels/get-channel-token-after-publishing.feature
index 6f5fdd47fe..68701049fc 100755
--- a/integration_testing/features/manage-channels/get-channel-token-after-publishing.feature
+++ b/integration_testing/features/manage-channels/get-channel-token-after-publishing.feature
@@ -13,4 +13,4 @@ Feature: Get channel token after publishing the channel
Then a snackbar appears to confirm the code is copied to the clipboard
Examples:
- | channel |
\ No newline at end of file
+ | channel |
diff --git a/integration_testing/features/manage-channels/manage-additional-channel-options.feature b/integration_testing/features/manage-channels/manage-additional-channel-options.feature
index ab249a52d5..d0578dfc3c 100755
--- a/integration_testing/features/manage-channels/manage-additional-channel-options.feature
+++ b/integration_testing/features/manage-channels/manage-additional-channel-options.feature
@@ -1,7 +1,7 @@
Feature: Access and use additional options on channel list
User needs to be able to access additional options to manage channels from the channel list
- Background:
+ Background:
Given I am signed in to Studio
And I am at *My Channels* tab
diff --git a/integration_testing/features/manage-channels/open-a-channel-in-a-new-tab.feature b/integration_testing/features/manage-channels/open-a-channel-in-a-new-tab.feature
index cc566cf714..5afb28ba91 100755
--- a/integration_testing/features/manage-channels/open-a-channel-in-a-new-tab.feature
+++ b/integration_testing/features/manage-channels/open-a-channel-in-a-new-tab.feature
@@ -9,4 +9,4 @@ Feature: Open channel in new tab
Then the clipboard opens up
When I right click a channel item on the clipboard
And click *Open in new tab* on the dropdown menu that appears
- Then a tab opens with the view-only or editable channel in question
\ No newline at end of file
+ Then a tab opens with the view-only or editable channel in question
diff --git a/integration_testing/features/manage-channels/share-channels.feature b/integration_testing/features/manage-channels/share-channels.feature
index 9cc7f1dd39..7798d299f4 100755
--- a/integration_testing/features/manage-channels/share-channels.feature
+++ b/integration_testing/features/manage-channels/share-channels.feature
@@ -1,7 +1,7 @@
Feature: Share channels
A user needs to be able to invite collaborators to view or edit channels
- Background:
+ Background:
Given I am signed in to Studio
And I am on the channel editor page
When I click the *...* (options) button in the topbar
diff --git a/integration_testing/features/manage-channels/stop-channel-publish.feature b/integration_testing/features/manage-channels/stop-channel-publish.feature
index 87c35e3b89..5e68344c78 100755
--- a/integration_testing/features/manage-channels/stop-channel-publish.feature
+++ b/integration_testing/features/manage-channels/stop-channel-publish.feature
@@ -1,6 +1,6 @@
Feature: Stop the publish of a channel
- Background:
+ Background:
Given there's a channel publish task in-progress
Scenario: Stop the publish of a channel
@@ -8,4 +8,4 @@ Feature: Stop the publish of a channel
Then a confirmation message appears
When I click *Yes, stop task*
Then the publish task stops
- And I am back in the channel editor page
\ No newline at end of file
+ And I am back in the channel editor page
diff --git a/integration_testing/features/manage-channels/sync-channel.feature b/integration_testing/features/manage-channels/sync-channel.feature
index b607a9f97a..ec33919b39 100755
--- a/integration_testing/features/manage-channels/sync-channel.feature
+++ b/integration_testing/features/manage-channels/sync-channel.feature
@@ -1,10 +1,10 @@
-Feature: Sync resources
+Feature: Sync resources
Studio users need to be able to sync and update the resources in their channels that have been imported from other channels, but have been modified since the original import.
Background:
Given I am signed in to Studio
And I am on the editor page
- And there is a in the that has been imported from
+ And there is a in the that has been imported from
Scenario: Sync resource file information
Given there is new version of the file in the
@@ -21,7 +21,7 @@ Feature: Sync resources
When I see the *Operation complete!* message
And I click the *Refresh button*
Then I see the new file version of the
- Or I see the new thumbnail
+ Or I see the new thumbnail
Scenario: Sync resource tags
Given the in the has a new tag
@@ -84,5 +84,5 @@ Feature: Sync resources
And I click the *Refresh button*
Then I see that my edits of title, description or tags for the have been reverted to reflect those on the
- Examples:
- | channel_a | channel_b | resource |
\ No newline at end of file
+ Examples:
+ | channel_a | channel_b | resource |
diff --git a/integration_testing/features/manage-channels/use-channels-with-view-only-access.feature b/integration_testing/features/manage-channels/use-channels-with-view-only-access.feature
index d7415fd9b7..1d1c190a04 100755
--- a/integration_testing/features/manage-channels/use-channels-with-view-only-access.feature
+++ b/integration_testing/features/manage-channels/use-channels-with-view-only-access.feature
@@ -1,6 +1,6 @@
Feature: Use channels with view-only access
- Background:
+ Background:
Given I am signed in to Studio
And I am on the editor page
And I have view-only permissions for
@@ -23,14 +23,14 @@ Feature: Use channels with view-only access
And I click on a *Ā·Ā·Ā·* button for more options
Then I can see the *View details* option
When I select the *View details* option
- Then I can see the *Topic* pane open on the right
+ Then I can see the *Topic* pane open on the right
And I can see all the details for the topic
Scenario: View details for a resource
When I hover over a type of resource
And I click on a *Ā·Ā·Ā·* button for more options
And I select the *View details* option
- Then I can see the pane for the resource opens on the right
+ Then I can see the pane for the resource opens on the right
And I can see all the details for the resource
Scenario: Copy topic or resource to the clipboard from *Ā·Ā·Ā·* options
@@ -41,4 +41,4 @@ Feature: Use channels with view-only access
And I see the *Cancel* button
Examples:
- | topic | kind | resource |
\ No newline at end of file
+ | topic | kind | resource |
diff --git a/integration_testing/features/manage-channels/view-channel-details.feature b/integration_testing/features/manage-channels/view-channel-details.feature
index 3a3aeda641..1109f8f971 100755
--- a/integration_testing/features/manage-channels/view-channel-details.feature
+++ b/integration_testing/features/manage-channels/view-channel-details.feature
@@ -1,6 +1,6 @@
-Feature: View channel details
+Feature: View channel details
- Background:
+ Background:
Given I am signed in to Studio
And I am on any of the tabs (*My Channels*, *Starred*, *View only*, or *Content Library*)
@@ -11,7 +11,7 @@ Feature: View channel details
Scenario: Copy channel token
Given I am on channel details page
- And I see the channel token
+ And I see the channel token
And I see the token *Copy* button
When I click the *Copy* button
Then I see the snackbar notification that the token is copied in to the clipboard
@@ -30,7 +30,7 @@ Feature: View channel details
Scenario: Close channel details page
When I click the *X* button in the top bar
- Then I don't see the channel details page any more
+ Then I don't see the channel details page any more
And I see the channel list on the tab where I initially opened it
Examples:
diff --git a/integration_testing/features/manage-channels/create-a-collection.feature b/integration_testing/features/manage-collections/create-a-collection.feature
similarity index 96%
rename from integration_testing/features/manage-channels/create-a-collection.feature
rename to integration_testing/features/manage-collections/create-a-collection.feature
index 1c8a2b2b01..1c3096f61c 100755
--- a/integration_testing/features/manage-channels/create-a-collection.feature
+++ b/integration_testing/features/manage-collections/create-a-collection.feature
@@ -1,6 +1,6 @@
Feature: Create a collection
- Background:
+ Background:
Given I am signed in to Studio
And I am on the *Collections* tab
diff --git a/integration_testing/features/manage-channels/delete-a-collection.feature b/integration_testing/features/manage-collections/delete-a-collection.feature
similarity index 84%
rename from integration_testing/features/manage-channels/delete-a-collection.feature
rename to integration_testing/features/manage-collections/delete-a-collection.feature
index d506601d7d..361b2a907d 100755
--- a/integration_testing/features/manage-channels/delete-a-collection.feature
+++ b/integration_testing/features/manage-collections/delete-a-collection.feature
@@ -1,6 +1,6 @@
Feature: Delete a collection
- Background:
+ Background:
Given I am signed in to Studio
And I am on the *Collections* tab
And there is at least one collection
@@ -8,7 +8,7 @@ Feature: Delete a collection
Scenario: Delete a collection
When I click the *Options* drop-down for the collection I want to edit
And I select the *Delete collection* option
- Then I see the *Delete collection* modal window
+ Then I see the *Delete collection* modal window
When I click the *Delete collection* button
Then I see the *Collections* tab
And I see that the deleted collection is no longer displayed
diff --git a/integration_testing/features/manage-channels/edit-a-collection.feature b/integration_testing/features/manage-collections/edit-a-collection.feature
similarity index 96%
rename from integration_testing/features/manage-channels/edit-a-collection.feature
rename to integration_testing/features/manage-collections/edit-a-collection.feature
index 6991827989..b0ba1680ca 100755
--- a/integration_testing/features/manage-channels/edit-a-collection.feature
+++ b/integration_testing/features/manage-collections/edit-a-collection.feature
@@ -1,6 +1,6 @@
Feature: Edit a collection
- Background:
+ Background:
Given I am signed in to Studio
And I am on the *Collections* tab
And there is at least one collection
diff --git a/integration_testing/features/manage-resources/clipboard/expand-and-collapse-clipboard-folders.feature b/integration_testing/features/manage-resources/clipboard/expand-and-collapse-clipboard-folders.feature
new file mode 100755
index 0000000000..3dfa83ff2c
--- /dev/null
+++ b/integration_testing/features/manage-resources/clipboard/expand-and-collapse-clipboard-folders.feature
@@ -0,0 +1,15 @@
+Feature: Expand and collapse folders in the clipboard
+
+ Background:
+ Given I am signed in to Studio
+ And I am on the channel editor view
+
+ Scenario: Expand and collapse folders in the clipboard
+ When I click on clipboard button on the bottom-right of the screen
+ Then the clipboard opens up
+ When I expand a folder on the clipboard via the downward arrow button
+ Then I see the items within the folder appear
+ And I see the downward arrow button changes to an upward arrow button
+ When I collapse a folder on the clipboard via the upward arrow button
+ Then I see the items within that folder disappear
+ And I see the upward arrow button changes to an upward arrow button
diff --git a/integration_testing/features/manage-resources/go-to-resource-original-location.feature b/integration_testing/features/manage-resources/clipboard/go-to-resource-original-location.feature
similarity index 85%
rename from integration_testing/features/manage-resources/go-to-resource-original-location.feature
rename to integration_testing/features/manage-resources/clipboard/go-to-resource-original-location.feature
index cc605ead7c..e744f5f57f 100755
--- a/integration_testing/features/manage-resources/go-to-resource-original-location.feature
+++ b/integration_testing/features/manage-resources/clipboard/go-to-resource-original-location.feature
@@ -9,4 +9,4 @@ Feature: Go to resource original location
Then the clipboard opens up
When I right click a topic or resource in the clipboard
And I click *Go to original location*
- Then a new tab opens and navigates me to the channel and node location in question that I pulled that topic or resource from
\ No newline at end of file
+ Then a new tab opens and navigates me to the channel and node location in question that I pulled that topic or resource from
diff --git a/integration_testing/features/manage-resources/preview-resource-in-clipboard.feature b/integration_testing/features/manage-resources/clipboard/preview-resource-in-clipboard.feature
similarity index 96%
rename from integration_testing/features/manage-resources/preview-resource-in-clipboard.feature
rename to integration_testing/features/manage-resources/clipboard/preview-resource-in-clipboard.feature
index 26d43e3391..b86509850d 100755
--- a/integration_testing/features/manage-resources/preview-resource-in-clipboard.feature
+++ b/integration_testing/features/manage-resources/clipboard/preview-resource-in-clipboard.feature
@@ -8,4 +8,4 @@ Feature: Preview resource in the clipboard
When I click on clipboard button on the bottom-right of the screen
Then the clipboard opens up
When I click on a topic title or resource title
- Then I see the clipboard content transition to the preview panel
\ No newline at end of file
+ Then I see the clipboard content transition to the preview panel
diff --git a/integration_testing/features/manage-resources/remove-from-clipboard-via-right-click.feature b/integration_testing/features/manage-resources/clipboard/remove-from-clipboard-via-right-click.feature
similarity index 96%
rename from integration_testing/features/manage-resources/remove-from-clipboard-via-right-click.feature
rename to integration_testing/features/manage-resources/clipboard/remove-from-clipboard-via-right-click.feature
index 3f98a70842..3b258f6891 100755
--- a/integration_testing/features/manage-resources/remove-from-clipboard-via-right-click.feature
+++ b/integration_testing/features/manage-resources/clipboard/remove-from-clipboard-via-right-click.feature
@@ -10,4 +10,4 @@ Feature: Remove resource from the clipboard via right click
When I right click a topic or a resource
And I click *Delete* in the dropdown menu that appears
Then the dropdown menu disappears
- And I see that the resource in question is removed from the clipboard
\ No newline at end of file
+ And I see that the resource in question is removed from the clipboard
diff --git a/integration_testing/features/manage-resources/remove-from-clipboard-via-selection-bar.feature b/integration_testing/features/manage-resources/clipboard/remove-from-clipboard-via-selection-bar.feature
similarity index 97%
rename from integration_testing/features/manage-resources/remove-from-clipboard-via-selection-bar.feature
rename to integration_testing/features/manage-resources/clipboard/remove-from-clipboard-via-selection-bar.feature
index 168c54d865..9118298b0a 100755
--- a/integration_testing/features/manage-resources/remove-from-clipboard-via-selection-bar.feature
+++ b/integration_testing/features/manage-resources/clipboard/remove-from-clipboard-via-selection-bar.feature
@@ -11,4 +11,4 @@ Feature: Remove resource from the clipboard via selection bar
And I see that the top bar changes to an actions bar
And I click the *Delete* button in the actions bar
Then I see that the resource in question disappears from the clipboard
- And I see a snackbar appears to confirm the resource is removed
\ No newline at end of file
+ And I see a snackbar appears to confirm the resource is removed
diff --git a/integration_testing/features/manage-resources/remove-multiple-resources-from-the-clipboard.feature b/integration_testing/features/manage-resources/clipboard/remove-multiple-resources-from-the-clipboard.feature
similarity index 97%
rename from integration_testing/features/manage-resources/remove-multiple-resources-from-the-clipboard.feature
rename to integration_testing/features/manage-resources/clipboard/remove-multiple-resources-from-the-clipboard.feature
index 094c0a6ad4..6af63619ca 100755
--- a/integration_testing/features/manage-resources/remove-multiple-resources-from-the-clipboard.feature
+++ b/integration_testing/features/manage-resources/clipboard/remove-multiple-resources-from-the-clipboard.feature
@@ -10,4 +10,4 @@ Feature: Remove multiple resources from the clipboard
When I select multiple resource checkboxes
And I click the remove button in the actions bar
Then I see all my resources removed from the clipboard
- And I see a snackbar confirming that the resources were removed
\ No newline at end of file
+ And I see a snackbar confirming that the resources were removed
diff --git a/integration_testing/features/manage-resources/undo-removal-of-clipboard-resources.feature b/integration_testing/features/manage-resources/clipboard/undo-removal-of-clipboard-resources.feature
similarity index 91%
rename from integration_testing/features/manage-resources/undo-removal-of-clipboard-resources.feature
rename to integration_testing/features/manage-resources/clipboard/undo-removal-of-clipboard-resources.feature
index f919301f4b..d0f34ce3d4 100755
--- a/integration_testing/features/manage-resources/undo-removal-of-clipboard-resources.feature
+++ b/integration_testing/features/manage-resources/clipboard/undo-removal-of-clipboard-resources.feature
@@ -16,4 +16,4 @@ Feature: Undo the removal of a clipboard resources
When I have removed several resources from the clipboard
And I see the snackbar confirmation that the resources were removed
And I click the *Undo* button on the snackbar
- Then I see the resources are back to the clipboard
\ No newline at end of file
+ Then I see the resources are back to the clipboard
diff --git a/integration_testing/features/manage-resources/cancel-copy.feature b/integration_testing/features/manage-resources/copy-resources/cancel-copy.feature
similarity index 91%
rename from integration_testing/features/manage-resources/cancel-copy.feature
rename to integration_testing/features/manage-resources/copy-resources/cancel-copy.feature
index 99fce69b6d..0552020ba8 100755
--- a/integration_testing/features/manage-resources/cancel-copy.feature
+++ b/integration_testing/features/manage-resources/copy-resources/cancel-copy.feature
@@ -1,5 +1,5 @@
Feature: Cancel copy
-
+
Background:
Given I am signed in to Studio
And I am on the channel editor view
@@ -18,4 +18,4 @@ Feature: Cancel copy
When there is a copy creation of multiple selections in progress
And I click on the *Cancel* text on the snackbar
Then the operation ceases and the snackbar disappears
- And I don't see any copies made on the clipboard
\ No newline at end of file
+ And I don't see any copies made on the clipboard
diff --git a/integration_testing/features/manage-resources/cancel-sync.feature b/integration_testing/features/manage-resources/copy-resources/cancel-sync.feature
similarity index 87%
rename from integration_testing/features/manage-resources/cancel-sync.feature
rename to integration_testing/features/manage-resources/copy-resources/cancel-sync.feature
index 9d2981836d..f18d8b66cd 100755
--- a/integration_testing/features/manage-resources/cancel-sync.feature
+++ b/integration_testing/features/manage-resources/copy-resources/cancel-sync.feature
@@ -16,4 +16,4 @@ Feature: Cancel sync
When the sync progress bar finishes
Then the progress dialog disappears
And I am back in the channel editor page
- And a snackbar appears to confirm the sync completion
\ No newline at end of file
+ And a snackbar appears to confirm the sync completion
diff --git a/integration_testing/features/manage-resources/copy-resource-via-right-click.feature b/integration_testing/features/manage-resources/copy-resources/copy-resource-via-right-click.feature
similarity index 95%
rename from integration_testing/features/manage-resources/copy-resource-via-right-click.feature
rename to integration_testing/features/manage-resources/copy-resources/copy-resource-via-right-click.feature
index 63819337f8..851a97069b 100755
--- a/integration_testing/features/manage-resources/copy-resource-via-right-click.feature
+++ b/integration_testing/features/manage-resources/copy-resources/copy-resource-via-right-click.feature
@@ -1,5 +1,5 @@
Feature: Copy resource via right click
-
+
Background:
Given I am signed in to Studio
And I am on the channel editor view
@@ -13,4 +13,4 @@ Feature: Copy resource via right click
When the copy creation is finished
Then the *Creating copy* snackbar disappears
And I see another snackbar appears to confirm *Copy created*
- And I see the newly made copy appearss in the clipboard below the original
\ No newline at end of file
+ And I see the newly made copy appearss in the clipboard below the original
diff --git a/integration_testing/features/manage-resources/copy-resource-via-selection-bar.feature b/integration_testing/features/manage-resources/copy-resources/copy-resource-via-selection-bar.feature
similarity index 96%
rename from integration_testing/features/manage-resources/copy-resource-via-selection-bar.feature
rename to integration_testing/features/manage-resources/copy-resources/copy-resource-via-selection-bar.feature
index 7a8d8aabd4..c2fe376b92 100755
--- a/integration_testing/features/manage-resources/copy-resource-via-selection-bar.feature
+++ b/integration_testing/features/manage-resources/copy-resources/copy-resource-via-selection-bar.feature
@@ -14,4 +14,4 @@ Feature: Copy resource via selection bar
When the copy creation is finished
Then the *Copying* loader disappears
And I see another snackbar appears to confirm *Copy created*
- And I see the newly made copy appears in the clipboard below the original
\ No newline at end of file
+ And I see the newly made copy appears in the clipboard below the original
diff --git a/integration_testing/features/manage-resources/copy-to-clipboard.feature b/integration_testing/features/manage-resources/copy-resources/copy-to-clipboard.feature
similarity index 80%
rename from integration_testing/features/manage-resources/copy-to-clipboard.feature
rename to integration_testing/features/manage-resources/copy-resources/copy-to-clipboard.feature
index ec378942d5..e29157d74c 100755
--- a/integration_testing/features/manage-resources/copy-to-clipboard.feature
+++ b/integration_testing/features/manage-resources/copy-resources/copy-to-clipboard.feature
@@ -1,6 +1,6 @@
Feature: Copy to clipboard
- Background:
+ Background:
Given I am signed in to Studio
And I am on the editor page
And I have edit permissions for
@@ -10,10 +10,10 @@ Feature: Copy to clipboard
And I click on a *Ā·Ā·Ā·* button for more options
Then I can see the *Copy to clipboard* option
When I select the *Copy to clipboard* option
- Then I can see the snackbar notification
+ Then I can see the snackbar notification
And I see the *Undo* button
When I click the *Undo* button
- Then I don't see the snackbar notification any more
+ Then I don't see the snackbar notification any more
And I don't see the topic in the clipboard
Scenario: Copy a resource to clipboard from *Ā·Ā·Ā·* (more options)
@@ -21,27 +21,27 @@ Feature: Copy to clipboard
And I click on a *Ā·Ā·Ā·* button for more options
Then I can see the *Copy to clipboard* option
When I select the *Copy to clipboard* option
- Then I can see the snackbar notification
+ Then I can see the snackbar notification
And I see the *Undo* button
When I click the *Undo* button
- Then I don't see the snackbar notification any more
+ Then I don't see the snackbar notification any more
And I don't see the resource in the clipboard
Scenario: Copy a topic to clipboard from toolbar
When I check the topic checkbox
- Then I see the toolbar options for topic
+ Then I see the toolbar options for topic
When I click the *Copy selected items to clipboard* toolbar button
- Then I can see the snackbar notification
+ Then I can see the snackbar notification
And I see the *Undo* button
When I click the *Undo* button
- Then I don't see the snackbar notification any more
+ Then I don't see the snackbar notification any more
And I don't see the topic in the clipboard
Scenario: Copy a resource to clipboard from toolbar
When I check the resource checkbox
- Then I see the toolbar options for resource
+ Then I see the toolbar options for resource
When I click the *Copy selected items to clipboard* toolbar button
- Then I can see the snackbar notification
+ Then I can see the snackbar notification
And I see the *Undo* button
When I click the *Undo* buttons
Then I don't see the snackbar notification any more
diff --git a/integration_testing/features/manage-resources/delete-resources.feature b/integration_testing/features/manage-resources/delete-resources.feature
index 15ef0dc307..586ed11033 100755
--- a/integration_testing/features/manage-resources/delete-resources.feature
+++ b/integration_testing/features/manage-resources/delete-resources.feature
@@ -1,6 +1,6 @@
Feature: Delete resources permanently
- Background:
+ Background:
Given I am signed in to Studio
And I am on the channel editor page
And I have one removed resource from my channel editor tree
@@ -24,4 +24,4 @@ Feature: Delete resources permanently
Then I see a warning message appear
When I click the *Delete permanently* button
Then the selections disappear from the list
- And a snackbar appears to confirm the deletion of multiple resources
\ No newline at end of file
+ And a snackbar appears to confirm the deletion of multiple resources
diff --git a/integration_testing/features/wip-studio-edit-modal/allow-marking-as-complete.feature b/integration_testing/features/manage-resources/edit-modal/allow-marking-as-complete.feature
similarity index 68%
rename from integration_testing/features/wip-studio-edit-modal/allow-marking-as-complete.feature
rename to integration_testing/features/manage-resources/edit-modal/allow-marking-as-complete.feature
index 18540109c8..118aa25f49 100755
--- a/integration_testing/features/wip-studio-edit-modal/allow-marking-as-complete.feature
+++ b/integration_testing/features/manage-resources/edit-modal/allow-marking-as-complete.feature
@@ -1,7 +1,7 @@
Feature: Allow marking as complete
This feature allows learners to manually mark a resource as complete in the learning platform. This option is available on all file types.
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel with all resource types
And I see the *Edit details* modal for the
@@ -10,7 +10,13 @@ Feature: Allow marking as complete
Scenario: Toggle *Allow learners to mark as complete* setting
When I click the *Allow marking as complete* checkbox
Then I see the *Allow marking as complete* is selected
- When I click the selected *Allow marking as complete* checkbox
+ When I click *FINISH*
+ Then I see the in the topic tree
+ And I do not see an error icon
+
+ Scenario: Uncheck the *Allow learners to mark as complete* setting
+ Given the *Allow marking as complete* checkbox is checked
+ When I uncheck the selected *Allow marking as complete* checkbox
Then I see the *Allow marking as complete* is empty
When I click *FINISH*
Then I see the in the topic tree
diff --git a/integration_testing/features/manage-resources/edit-modal/edit-accessibility-options.feature b/integration_testing/features/manage-resources/edit-modal/edit-accessibility-options.feature
new file mode 100755
index 0000000000..848f1f7b98
--- /dev/null
+++ b/integration_testing/features/manage-resources/edit-modal/edit-accessibility-options.feature
@@ -0,0 +1,86 @@
+Feature: Edit *Accessibility* options
+ Across all file types
+
+# Comment here
+
+ Background:
+ Given I am signed into Studio
+ And I am in an editable channel
+ When I right click
+ When I click *Edit details*
+ Then I see the edit modal for the
+ And I see the *Accessibility* section underneath the *Audience* section
+
+ Scenario: View options for .MP4
+ Given I am viewing an .MP4 in the edit modal
+ Then I see a checkbox that says *Includes captions or subtitles*
+ And I see a checkbox that says *Includes audio descriptions*
+ And I see a checkbox that says *Includes sign language captions*
+
+ Scenario: View tooltips for .MP4
+ Given I am viewing an .MP4 in the edit modal
+ When I hover my mouse over the info icon for *Includes sign language captions*
+ Then I see *Synchronized sign language interpretation is available for audio and video content*
+ When I hover my mouse over the info icon for *Includes audio descriptions*
+ Then I see *The resource contains a second narration audio track that provides additional information for the benefit of blind users and those with low vision*
+
+ Scenario: View options for .PDF or .EPUB
+ Given I am viewing a .PDF or .EPUB in the edit modal
+ Then I see a checkbox that says *Includes alternative text description for images*
+ And I see a checkbox that says *Includes high contrast text for learners with low vision*
+ And I see a checkbox that says *Tagged PDF*
+
+ Scenario: View tooltips for .PDF or .EPUB
+ Given I am viewing a .PDF or .EPUB in the edit modal
+ When I hover my mouse over the info icon for *Includes alternative text descriptions for images*
+ Then I see *Visual elements in the resource have descriptions that can be accessed by screen readers for the benefit of blind learners*
+ When I hover my mouse over the info icon for *Includes high contrast text for learners with low vision*
+ Then I see *The resource text and visual elements are displayed with high contrast for the benefit of users with low vision*
+ When I hover my mouse over the info icon for *Tagged PDF*
+ Then I see *The document contains PDF tags that can be accessed by screen readers for the benefits of blind learners*
+
+ Scenario: View options for .MP3
+ Given I am viewing a .MP3 in the edit modal
+ Then I see a *Captions and subtitles* section underneath the *Source* section
+
+ Scenario: View tooltips for .MP3
+ Given I am viewing an .MP3 in the edit modal
+ When I hover my mouse over the info icon for *Captions and subtitles*
+ Then I see *Supported formats: vtt*
+
+ Scenario: View options for .ZIP
+ Given I am viewing a .ZIP in the edit modal
+ Then I see a checkbox that says *Includes alternative text description for images*
+ And I see a checkbox that says *Includes high contrast text for learners with low vision*
+
+ Scenario: View tooltips for .ZIP
+ Given I am viewing a .ZIP in the edit modal
+ When I hover my mouse over the info icon for *Includes alternative text description for images*
+ Then I see *Visual elements in the resource have descriptions that can be accessed by screen readers for the benefit of blind learners*
+ When I hover my mouse over the info icon for *Includes high contrast display for low vision*
+ Then I see *The resource text and visual elements are displayed with high contrast for the benefit of users with low vision*
+
+ Scenario: View options for Exercises
+ Given I am viewing an Exercise in the edit modal
+ Then I see a checkbox that says *Includes alternative text description for images*
+
+ Scenario: View tooltips for Practice resources
+ Given I am viewing an Exercise in the edit modal
+ When I hover my mouse over the info icon for *Includes alternative text description for images*
+ Then I see *Visual elements in the resource have descriptions that can be accessed by screen readers for the benefit of blind learners*
+
+ Scenario: Select and deselect accessibility options
+ Given that for any there are some checkbox options in the *Accessibility* section
+ When I select a checkbox for an under *Accessibility*
+ Then I see that the checkbox is selected
+ When I uncheck a selected
+ Then I see the checkbox is empty
+
+ Scenario: See that accessibility field is optional
+ Given the *Accessibility* field of a is empty
+ When I click *FINISH*
+ Then I see the in the topic tree
+ And I do not see an error icon
+ When I left-click on the
+ Then I see the previewer for the
+ And I see that the *Accessibility* field is empty
diff --git a/integration_testing/features/wip-studio-edit-modal/edit-category-field.feature b/integration_testing/features/manage-resources/edit-modal/edit-category-field.feature
similarity index 95%
rename from integration_testing/features/wip-studio-edit-modal/edit-category-field.feature
rename to integration_testing/features/manage-resources/edit-modal/edit-category-field.feature
index 5c7812bcc2..a4e188a00e 100755
--- a/integration_testing/features/wip-studio-edit-modal/edit-category-field.feature
+++ b/integration_testing/features/manage-resources/edit-modal/edit-category-field.feature
@@ -1,7 +1,7 @@
Feature: Edit *Category* field
- Across all file types.
+ Across all file types.
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel with all resource types
When I right click a
@@ -18,7 +18,7 @@ Feature: Edit *Category* field
Scenario: Select first-level category option
Given that checkbox is empty
- When I click
+ When I click
Then I see that is determinately selected
And I see that children categories are not selected
And I see a chip with in the *Category* text field
@@ -29,7 +29,7 @@ Feature: Edit *Category* field
Then I see that is determinately selected
And I see that is determinately selected
And I see that children categories are not selected
- And I see that is represented as a chip in the *Category* text field
+ And I see that is represented as a chip in the *Category* text field
And I do not see represented as a chip in the *Category* text field
When I hover my mouse over the chip
Then I see * - * in a tooltip
@@ -96,7 +96,7 @@ Feature: Edit *Category* field
Given I is selected in *Category*
When I click the selected checkbox for
Then I see that the checkbox for is empty
- And I do not see the chip in the textbox
+ And I do not see the chip in the textbox
Scenario: Clear all category selections
Given there is more than one option selected in *Category
diff --git a/integration_testing/features/wip-studio-edit-modal/edit-completion-field.feature b/integration_testing/features/manage-resources/edit-modal/edit-completion-field.feature
similarity index 75%
rename from integration_testing/features/wip-studio-edit-modal/edit-completion-field.feature
rename to integration_testing/features/manage-resources/edit-modal/edit-completion-field.feature
index 4ae96901ab..1d1965b688 100755
--- a/integration_testing/features/wip-studio-edit-modal/edit-completion-field.feature
+++ b/integration_testing/features/manage-resources/edit-modal/edit-completion-field.feature
@@ -47,45 +47,49 @@ Feature: Edit completion/duration field
Given I am viewing a .ZIP
When I click on the *Completion* dropdown
Then I see the options: *When time spent is equal to duration*, *Determined by the resource*, and *Reference material*
+ When I click on the *Duration* dropdown
+ Then I see the options: *Time to complete*, *Short activity*, and *Long activity*
Scenario: Default options for Exercise on content creation
Given I am viewing an exercise that I have just added
And I see the *Completion* section
Then I see the *Completion* is set by default to *When goal is met*
- And there is a *goal* dropdown with no default set
+ And there is a *Goal* dropdown with no default set
And *Duration* is not visible for exercises
+ When I click on the *Duration* dropdown
+ Then I see the options: *Goal: 100% correct*, *M of N*, *Goal: 10 in a row*, *Goal: 2 in a row*, *Goal: 3 in a row*, *Goal: 5 in a row*
Scenario: View options for Practice resources
Given I am viewing an exercise
When I click on the *Completion* dropdown
Then I see the options: *When goal is met* and *Practice quiz*
- Scenario: Set completion to *Short activity*
- When I click on the *Completion* dropdown
+ Scenario: Set duration to *Short activity* #not valid for exercises
+ When I click on the *Duration* dropdown
And I select *Short activity*
Then I see *Short activity* appear in the text input
- And I see a *Minutes* dropdown input field appear next to *Completion*
+ And I see a *Minutes* dropdown input field appear next to *Duration*
And I see *Minutes* is set to 10 by default
And I see the maximum option in the menu is 30 minutes
- And I see the caption *(Optional) Duration until resource is marked as complete. This value will not be shown to learners*
+ And I see the caption *(Optional) Time required for the resource to be marked as completed. This value will not be displayed to learners.*
Scenario: Adjust time for short activities
- Given *Completion* is set to *Short activity*
+ Given *Duration* is set to *Short activity*
When I adjust the value by selecting a different option in the dropdown
Then I see the value in the *Minutes* field update
- Scenario: Set completion to *Long activity*
- When I click on the *Completion* dropdown
+ Scenario: Set duration to *Long activity* #not valid for exercises
+ When I click on the *Duration* dropdown
And I select *Long activity*
Then I see *Long activity* appear in the text input
- And I see a *Minutes* dropdown input field appear next to *Completion*
+ And I see a *Minutes* dropdown input field appear next to *Duration*
And I see *Minutes* is set to 50 by default
And I see the maximum option in the menu is 120 minutes
And I see the maximum option in the menu is 40 minutes
- And I see the caption *(Optional) Duration until resource is marked as complete. This value will not be shown to learners*
+ And I see the caption *(Optional) Time required for the resource to be marked as completed. This value will not be displayed to learners.*
Scenario: Adjust time for long activities
- Given *Completion* is set to *Long activity*
+ Given *Duration* is set to *Long activity*
When I adjust the value by selecting a different option in the dropdown
Then I see the value in the *Minutes* field update
@@ -96,11 +100,11 @@ Feature: Edit completion/duration field
And I see the caption *Progress will not be tracked on reference material unless learners mark it as complete*
And the *Duration* input field is hidden
- Scenario: Set completion to *Time to complete* on .MP4 or .MOV
+ Scenario: Set duration to *Time to complete* on .MP4 or .WEBM
Given I am viewing an .MP4 or .MOV
- When I click the *Completion* dropdown
- When I select *Time to complete*
- Then I see the exact duration appear next to the *Duration* field as plain text
+ When I set the *Completion* dropdown to *When time spent is equal to duration*
+ Then the *Duration* dropdown is set by default to *Time to complete*
+ And I see the exact duration appear next to the *Duration* field as plain text
And I see that I cannot edit it
Scenario: Set completion to *Time to complete* for .PDF, .EPUB, or slides
@@ -111,30 +115,29 @@ Feature: Edit completion/duration field
Then I see a *Time to complete* appear as an input for *Duration*
And I see a *Minutes* text input field appear next to *Duration*
When I click the *Minutes* field
- And I enter a whole number
+ And I enter a whole number value
And I lose focus on the field
- Then I see the number has saved
+ Then I see the number was saved
Scenario: Enter whole numbers only to *Minutes* field
Given I can see the *Minutes* field in *Completion*
When I try to enter a non-numeric input or non-whole number
Then I see that I am blocked from entering that input
- Scenario: Check *Allow learners to mark as complete*
- Given I can see the *Allow learners to mark as complete* field in *Completion*
- When I click on the checkbox
- Then I see that it changes from its current state to the opposite
-
- Scenario: Set completion to *Practice until goal is met*
+ Scenario: Set exercise completion to *When goal is met*
Given I am viewing an exercise
When I click the *Completion* dropdown
And I click *When goal is met*
Then I see the *Completion* field is set to *When goal is met*
- And I see an empty *Goal* text field dropdown appears next to it
+ And I see the *Goal* dropdown is set to *Goal: 100% correct*
When I click the *Goal* dropdown
Then I see the options: *100% correct*, *M of N*, *10 in a row*, *2 in a row*, *3 in a row*, *5 in a row*
When I select *M of N*
Then I see an empty *Correct answers needed* text field, */*, and empty *Recent answers* text field appear
+ When I fill in the required fields
+ And I wait for the auto save to complete
+ Then I see the *Completion* set to the specified criteria
+ And I don't see any errors
Scenario: See that *Completion* field is required
Given that a required *Completion* or *Duration* field of is empty
diff --git a/integration_testing/features/wip-studio-edit-modal/edit-learning-activity-field.feature b/integration_testing/features/manage-resources/edit-modal/edit-learning-activity-field.feature
similarity index 96%
rename from integration_testing/features/wip-studio-edit-modal/edit-learning-activity-field.feature
rename to integration_testing/features/manage-resources/edit-modal/edit-learning-activity-field.feature
index 360212a796..a6089b79b3 100755
--- a/integration_testing/features/wip-studio-edit-modal/edit-learning-activity-field.feature
+++ b/integration_testing/features/manage-resources/edit-modal/edit-learning-activity-field.feature
@@ -1,7 +1,7 @@
Feature: Edit *Learning activity* field
Across all file types
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel with all resource types
When I right click a
@@ -23,7 +23,7 @@ Feature: Edit *Learning activity* field
Scenario: Remove an option
Given I see the options for *Learning activity*
- And is selected
+ And an is selected
When I click the selected checkbox for
Then I see that the checkbox for is empty
And I do not see the chip in the textbox
diff --git a/integration_testing/features/wip-studio-edit-modal/edit-learning-level-field.feature b/integration_testing/features/manage-resources/edit-modal/edit-learning-level-field.feature
similarity index 85%
rename from integration_testing/features/wip-studio-edit-modal/edit-learning-level-field.feature
rename to integration_testing/features/manage-resources/edit-modal/edit-learning-level-field.feature
index 7d3c2da940..5a7fb38753 100755
--- a/integration_testing/features/wip-studio-edit-modal/edit-learning-level-field.feature
+++ b/integration_testing/features/manage-resources/edit-modal/edit-learning-level-field.feature
@@ -1,7 +1,7 @@
Feature: Edit *Learning level* field
Across all file types
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel with all resource types
When I right click
@@ -12,7 +12,7 @@ Feature: Edit *Learning level* field
Scenario: View learning level options
When I click the *Level* dropdown
Then I see multi-select checkboxes
- And I see the options: *Preschool/Nursery*, *Lower primary*, *Upper primary*, *Lower secondary*, *Upper secondary*, *Tertiary*, *Specialized professional training*, *All levels -- basic skills*, *All levels -- work skills* #TO DO - we need a final list with the available checkboxes
+ And I see the options: *All levels -- basic skills*, *Lower primary*, *Lower secondary*, *Preschool*, *Specialized professional training*, *Tertiary*, *Upper primary*, *Upper secondary*, *All levels -- work skills*
Scenario: Select options
Given I see the options for *Level*
diff --git a/integration_testing/features/wip-studio-edit-modal/edit-multiple-resources-at-the-same-time.feature b/integration_testing/features/manage-resources/edit-modal/edit-multiple-resources-at-the-same-time.feature
similarity index 87%
rename from integration_testing/features/wip-studio-edit-modal/edit-multiple-resources-at-the-same-time.feature
rename to integration_testing/features/manage-resources/edit-modal/edit-multiple-resources-at-the-same-time.feature
index db8657662e..a13c8b1978 100755
--- a/integration_testing/features/wip-studio-edit-modal/edit-multiple-resources-at-the-same-time.feature
+++ b/integration_testing/features/manage-resources/edit-modal/edit-multiple-resources-at-the-same-time.feature
@@ -1,7 +1,7 @@
Feature: Edit multiple resources at the same time in the edit modal
Users can apply dropdown options to multiple resources at one time
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel with all resource types
When I select resource checkboxes
@@ -10,12 +10,12 @@ Feature: Edit multiple resources at the same time in the edit modal
Scenario: See which fields are hidden and shown
When I select more than one checkbox in the resource panel on the left
- Then I see *Editing details for resources*
+ Then I see *Editing details for folders, resources*
And I see text fields and dropdown options that are common to all selected resources
And I do not see text fields and dropdown options that are not common to all selected resources
And I do not see the resource renderer, *Title*, *Description*, *Preview files*, or *Thumbnail*
- Scenario: View mixed settings on a multi-select dropdown
+ Scenario: View mixed settings on a multi-select dropdown #not implemented, don't test
When I select checkboxes in the resource panel on the left
And those resources have mixed settings across different dropdown fields
Then I see a chip with the *Mixed* label for those dropdowns
@@ -23,7 +23,7 @@ Feature: Edit multiple resources at the same time in the edit modal
When I click a dropdown that has the *Mixed* label
Then I see an indeterminate checkbox for each option that partially applies to all selected resources
- Scenario: Select an indeterminate checkbox in a multi-select dropdown
+ Scenario: Select an indeterminate checkbox in a multi-select dropdown #not implemented, don't test
Given has a *Mixed* chip
When I click
Then I see is indeterminately selected
@@ -31,7 +31,7 @@ Feature: Edit multiple resources at the same time in the edit modal
Then I see is determinately selected
And I see is a chip in
- Scenario: View mixed settings on a single-select dropdown
+ Scenario: View mixed settings on a single-select dropdown #not implemented, don't test
Given says *Mixed*
When I click
Then I see all options
@@ -40,7 +40,7 @@ Feature: Edit multiple resources at the same time in the edit modal
Then I see in the dropdown
And I see is selected in the dropdown options
- Scenario: View mixed settings on a checkbox
+ Scenario: View mixed settings on a checkbox #not implemented, don't test
When I select checkboxes in the resource panel on the left
And those resources have mixed settings across checkbox settings
And I see that some checkboxes are indeterminate
diff --git a/integration_testing/features/wip-studio-edit-modal/edit-what-you-will-need-field.feature b/integration_testing/features/manage-resources/edit-modal/edit-requirements-field.feature
similarity index 55%
rename from integration_testing/features/wip-studio-edit-modal/edit-what-you-will-need-field.feature
rename to integration_testing/features/manage-resources/edit-modal/edit-requirements-field.feature
index f5922a4586..38d232e259 100755
--- a/integration_testing/features/wip-studio-edit-modal/edit-what-you-will-need-field.feature
+++ b/integration_testing/features/manage-resources/edit-modal/edit-requirements-field.feature
@@ -1,57 +1,57 @@
-Feature: Edit *What you will need* field
+Feature: Edit *Requirements* field
Across all file types
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel with all resource types
When I right click
When I click *Edit details*
Then I see the *Edit details* modal for the
- And I see the *What you will need* dropdown in the *Basic information* section
+ And I see the *Requirements* dropdown in the *Basic information* section
- Scenario: View *What you will need* options
- When I click the *What you will need* dropdown
+ Scenario: View *Requirements* options
+ When I click the *Requirements* dropdown
Then I see multi-select checkboxes
- And I see the options: *Teacher*, *Peers*, *Paper and pencil*, *Internet*, and *Other supplies*
+ And I see the options: *Working with peers*, *Working with a teacher*, *Internet connection*, *Other software tools*, *Paper and pencil*, and *Other supplies*
Scenario: Select options
- Given I see the options for *What you will need*
+ Given I see the options for *Requirements*
When I click on the checkbox for
Then I see has a determinate checkbox
And I see the chip for appear in the textbox
And I see an *X* in the text field
Scenario: Remove an option
- Given I see the options for *What you will need*
+ Given I see the options for *Requirements*
And is selected
When I click the selected checkbox for
Then I see that the checkbox for is empty
And I do not see the chip in the textbox
Scenario: Remove an option through chip
- Given I see the options for *What you will need*
+ Given I see the options for *Requirements*
And is selected
When I click the *X* in the chip
Then I do not see the chip for in the textbox
- And I do not see selected in the *What you will need* dropdown
+ And I do not see selected in the *Requirements* dropdown
Scenario: Clear all options in the text field
- Given I see that several options for *What you will need* are selected
+ Given I see that several options for *Requirements* are selected
When I click the *X* in the text field
- Then I do not see any *What you will need* options in the text field
+ Then I do not see any *Requirements* options in the text field
And I do not see the *X* in the text field
Scenario: Chips overflow in the text field
- When I click checkboxes in the *What you will need* dropdown
+ When I click checkboxes in the *Requirements* dropdown
And the length of the chips exceeds the width of the dropdown
Then I see the chips go to the next line
- And the height of the *What you will need* dropdown grows to contain it
+ And the height of the *Requirements* dropdown grows to contain it
- Scenario: See that *What you will need* field is optional
- Given the *What you will need* field of a is empty
+ Scenario: See that *Requirements* field is optional
+ Given the *Requirements* field of a is empty
When I click *FINISH*
Then I see in the topic tree
And I do not see an error icon
When I left-click the
Then I see the previewer for the
- And I see that the *What you will need* field is empty
+ And I see that the *Requirements* field is empty
diff --git a/integration_testing/features/wip-studio-edit-modal/new-layout-in-the-edit-modal.feature b/integration_testing/features/manage-resources/edit-modal/new-layout-in-the-edit-modal.feature
similarity index 71%
rename from integration_testing/features/wip-studio-edit-modal/new-layout-in-the-edit-modal.feature
rename to integration_testing/features/manage-resources/edit-modal/new-layout-in-the-edit-modal.feature
index 45abba073a..360a211e79 100755
--- a/integration_testing/features/wip-studio-edit-modal/new-layout-in-the-edit-modal.feature
+++ b/integration_testing/features/manage-resources/edit-modal/new-layout-in-the-edit-modal.feature
@@ -1,7 +1,7 @@
Feature: New layout in the edit modal
User can see new fields: learning level, learning activity, what you will need, duration, completion, for beginners, accessibility, and category
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel
And I have added resources of type .pdf, .epub, .mp3, .mp4, .mov, .zip to the channel
@@ -22,9 +22,9 @@ Feature: New layout in the edit modal
And I see the *Thumbnail* section
And I see the *Audience* section
And I see the *Language* and *Visible to* drop-downs
- And I see a *For beginners* checkbox
+ And I see a *For beginners* checkbox
And I see the *Accessibility* section
- And I see the following checkboxes there: *Has alternative text description for images*, *Has high contrast display for low vision*, *Tagged PDF*
+ And I see the following checkboxes there: *Includes alternative text description for images*, *Includes high contrast display for low vision*, *Tagged PDF*
And I see the *Source* section
And I see the following fields there: *Author*, *Provider*, *Aggregator*, *License*, *Copyright holder*
@@ -44,7 +44,7 @@ Feature: New layout in the edit modal
And I see the *Thumbnail* section
And I see the *Audience* section
And I see the *Language* and *Visible to* drop-downs
- And I see a *For beginners* checkbox
+ And I see a *For beginners* checkbox
And I see the *Source* section
And I see the following fields there: *Author*, *Provider*, *Aggregator*, *License*
And I see the *Captions and subtitles* section
@@ -66,8 +66,8 @@ Feature: New layout in the edit modal
And I see the *Thumbnail* section
And I see the *Audience* section
And I see the *Language* and *Visible to* drop-downs
- And I see a *For beginners* checkbox
- And I see the *Accessibility* section with the following checkboxes: *Has sign language captions*, *Has audio descriptions*
+ And I see a *For beginners* checkbox
+ And I see the *Accessibility* section with the following checkboxes: *Includes captions or subtitles*, *Includes audio descriptions*, *Includes sign language captions*
And I see the *Source* section
And I see the following fields there: *Author*, *Provider*, *Aggregator*, *License*, *Copyright holder*
@@ -83,12 +83,12 @@ Feature: New layout in the edit modal
And I see a *Randomize question order for learners*
And I see the *Completion* section
And I see the *Allow learner to mark as complete* checkbox
- And I see the following fields: *Completion*, *Goal*, *Correct answers needed*, *Recent answers*, *Duration* and a *Minutes* slider
+ And I see the following fields: *Completion*, *Goal*
And I see the *Thumbnail* section
And I see the *Audience* section
And I see the *Language* and *Visible to* drop-downs
- And I see a *For beginners* checkbox
- And I see the *Accessibility* section with the following checkbox: *Has alternative text description for images*
+ And I see a *For beginners* checkbox
+ And I see the *Accessibility* section with the following checkbox: *Includes alternative text descriptions for images*
And I see the *Source* section
And I see the following fields there: *Author*, *Provider*, *Aggregator*, *License*, *Copyright holder*
@@ -104,32 +104,12 @@ Feature: New layout in the edit modal
And I see the *Basic information* section with the following fields: *Title*, *Description*, *Learning activity*, *Level*, *What you will need*, *Tags*, and *Category*
And I see a *Completion* section under the *Basic information* section
And I see the *Allow learner to mark as complete* checkbox
- And I see a *Duration* drop-down and a *Minutes* slider
+ And I see the *Completion* drop-down with prefilled *When time spent is equal to duration* value
+ And I see the *Duration* drop-down and the *Minutes* drop-down
And I see the *Thumbnail* section
And I see the *Audience* section
And I see the *Language* and *Visible to* drop-downs
- And I see a *For beginners* checkbox
- And I see the *Accessibility* section with the following checkboxes: *Has alternative text descriptions*, *Has high contrast display for low vision*
- And I see the *Source* section
- And I see the following fields there: *Author*, *Provider*, *Aggregator*, *License*, *Copyright holder*
-
- Scenario: View multiple activities layout
- When I right click a resource #TO DO: How exactly do we get to the multiple activities edit details?
- Then I see an options menu for that resource
- When I click *Edit details*
- Then I see the *Edit details* modal
- And I am at the *Details* tab
- And I see a the icons for the activities
- And I see a thumbnail
- And I see a *Preview files* option
- And I see the *Basic information* section with the following fields: *Title*, *Description*, *Learning activity*, *Level*, *What you will need*, *Tags*, and *Category*
- And I see a *Completion* section under the *Basic information* section
- And I see the *Allow learner to mark as complete* checkbox
- And I see a *Duration* drop-down and a *Minutes* slider
- And I see the *Thumbnail* section
- And I see the *Audience* section
- And I see the *Language* and *Visible to* drop-downs
- And I see a *For beginners* checkbox
- And I see the *Accessibility* section with the following checkboxes: *Has alternative text descriptions*, *Has high contrast display for low vision*, *Tagged PDF*
+ And I see a *For beginners* checkbox
+ And I see the *Accessibility* section with the following checkboxes: *Includes alternative text descriptions for images*, *Includes high contrast text for learners with low vision*
And I see the *Source* section
And I see the following fields there: *Author*, *Provider*, *Aggregator*, *License*, *Copyright holder*
diff --git a/integration_testing/features/manage-resources/edit-modal/new-metadata-defaults-while-uploading-or-creating-new-files.feature b/integration_testing/features/manage-resources/edit-modal/new-metadata-defaults-while-uploading-or-creating-new-files.feature
new file mode 100755
index 0000000000..600712d6f4
--- /dev/null
+++ b/integration_testing/features/manage-resources/edit-modal/new-metadata-defaults-while-uploading-or-creating-new-files.feature
@@ -0,0 +1,106 @@
+Feature: New metadata defaults while uploading or creating new files
+ User uploads files and sees which new metadata fields are and aren't set by default
+
+ Background:
+ Given I am signed into Studio
+ And I am in an editable channel
+ When I click the *Add* button
+ And I select *Upload files*
+ Then I see the *Upload files* page
+
+ Scenario: Upload .PDF or .EPUB
+ When I upload a .PDF or an .EPUB file
+ Then I see the edit modal for <.PDF or .EPUB>
+ And the *Title* field is prefilled with the name of the file
+ And the *Description* field is empty
+ And *Learning activity* is set to *Read*
+ And *Level* is empty
+ And *Requirements* is empty
+ And *Tags* is empty
+ And *Category* is empty
+ And *Allow learners to mark as complete* is unchecked
+ And *Completion* is set to *Viewed in its entirety*
+ And there is no thumbnail
+ And *Language* is empty
+ And *Visible to* is set to *Anyone*
+ And *For beginners* is not checked
+ And all *Accessibility* checkboxes are unchecked
+
+ Scenario: Upload .MP3
+ When I upload <.MP3>
+ Then I see the edit modal for <.MP3>
+ And the *Title* field is prefilled with the name of the file
+ And the *Description* field is empty
+ And *Learning activity* is set to *Listen*
+ And *Level* is empty
+ And *Requirements* is empty
+ And *Tags* is empty
+ And *Category* is empty
+ And *Allow learners to mark as complete* is unchecked
+ And *Completion* is set to *When time spent is equal to duration*
+ And *Duration* is set to *Time to complete*
+ And there is no thumbnail
+ And *Language* is empty
+ And *Visible to* is set to *Anyone*
+ And *For beginners* is unchecked
+
+ Scenario: Upload .MP4 or .WEBM
+ When I upload <.MP4 or .WEBM>
+ Then I see the edit modal for <.MP4 or .WEBM>
+ And the *Title* field is prefilled with the name of the file
+ And the *Description* field is empty
+ And *Learning activity* is set to *Watch*
+ And *Level* is empty
+ And *Requirements* is empty
+ And *Tags* is empty
+ And *Category* is empty
+ And *Allow learners to mark as complete* is unchecked
+ And *Completion* is set to *When time spent is equal to duration*
+ And *Duration* is set to *Time to complete*
+ And there is no thumbnail
+ And *Language* is empty
+ And *Visible to* is set to *Anyone*
+ And *For beginners* is unchecked
+ And all *Accessibility* checkboxes are unchecked
+
+ Scenario: Upload .ZIP
+ When I upload <.ZIP>
+ Then I see the edit modal for <.ZIP>
+ And the *Title* field is prefilled with the name of the file
+ And the *Description* field is empty
+ And *Learning activity* is empty
+ And *Level* is empty
+ And *Requirements* is empty
+ And *Tags* is empty
+ And *Category* is empty
+ And *Allow learners to mark as complete* is unchecked
+ And *Completion* is set to *When time spent is equal to duration*
+ And *Duration* is set to *Time to complete*
+ And *Minutes* is set to *10*
+ And there is no thumbnail
+ And *Language* is empty
+ And *Visible to* is set to *Anyone*
+ And *For beginners* is unchecked
+ And all *Accessibility* checkboxes are unchecked
+
+ Scenario: Create new Practice resource
+ Given I am in an editable channel
+ When I click *Add*
+ And I click *New exercise*
+ Then I see the edit modal for the exercise
+ And the *Title* field is empty
+ And the *Description* field is empty
+ And *Learning activity* is is set to *Practice*
+ And *Level* is empty
+ And *Requirements* is empty
+ And *Tags* is empty
+ And *Category* is empty
+ And *Randomize question order for learners* is checked
+ And *Allow learners to mark as complete* is unchecked
+ And *Completion* is set to *When goal is met*
+ And *Goal* is empty
+ And there is no thumbnail
+ And *Language* is empty
+ And *Visible to* is set to *Anyone*
+ And *For beginners* is unchecked
+ And all *Accessibility* checkboxes are unchecked
diff --git a/integration_testing/features/manage-resources/edit-resource-details.feature b/integration_testing/features/manage-resources/edit-resource-details.feature
index 1c8624a494..da035bcb0c 100755
--- a/integration_testing/features/manage-resources/edit-resource-details.feature
+++ b/integration_testing/features/manage-resources/edit-resource-details.feature
@@ -1,6 +1,6 @@
-Feature: Edit resource details
+Feature: Edit resource details
- Background:
+ Background:
Given I am signed in to Studio
And I am on the channel editor page
@@ -13,4 +13,3 @@ Feature: Edit resource details
Then I see a message: *Changes saved*
When I click the *Finish* button
Then I am returned at the main topic tree view
-
\ No newline at end of file
diff --git a/integration_testing/features/manage-resources/expand-and-collapse-clipboard-topics.feature b/integration_testing/features/manage-resources/expand-and-collapse-clipboard-topics.feature
deleted file mode 100755
index 25bf522b02..0000000000
--- a/integration_testing/features/manage-resources/expand-and-collapse-clipboard-topics.feature
+++ /dev/null
@@ -1,15 +0,0 @@
-Feature: Expand and collapse topics in the clipboard
-
- Background:
- Given I am signed in to Studio
- And I am on the channel editor view
-
- Scenario: Expand and collapse topics in the clipboard
- When I click on clipboard button on the bottom-right of the screen
- Then the clipboard opens up
- When I expand a topic on the clipboard via the downward carot button
- Then I see the items within the topic appear
- And I see the downward carot button changes to an upward carot button
- When I collapse a topic on the clipboard via the upward carot button
- Then I see the items within that topic disappear
- And I see the upward carot button changes to an upward carot button
\ No newline at end of file
diff --git a/integration_testing/features/manage-resources/go-back-from-review-import-selections-to-channel-list.feature b/integration_testing/features/manage-resources/go-back-from-review-import-selections-to-channel-list.feature
index 9d4c7815a0..bbb5efbed6 100755
--- a/integration_testing/features/manage-resources/go-back-from-review-import-selections-to-channel-list.feature
+++ b/integration_testing/features/manage-resources/go-back-from-review-import-selections-to-channel-list.feature
@@ -1,6 +1,6 @@
Feature: Go back from Review selections for import to channel list
- Background:
+ Background:
Given I am signed in to Studio
And I am on *My Channels > edit* page
@@ -16,4 +16,4 @@ Feature: Go back from Review selections for import to channel list
And I see the *0 topics, 0 resource* at the bottom of the modal
Examples:
- | resource |
\ No newline at end of file
+ | resource |
diff --git a/integration_testing/features/manage-resources/import-content-from-another-channel.feature b/integration_testing/features/manage-resources/import/import-content-from-another-channel.feature
similarity index 88%
rename from integration_testing/features/manage-resources/import-content-from-another-channel.feature
rename to integration_testing/features/manage-resources/import/import-content-from-another-channel.feature
index 880e78c106..c872c8b796 100755
--- a/integration_testing/features/manage-resources/import-content-from-another-channel.feature
+++ b/integration_testing/features/manage-resources/import/import-content-from-another-channel.feature
@@ -1,6 +1,6 @@
Feature: Import content from another channel
- Background:
+ Background:
Given I am signed in to Studio
And I am on the editing page
@@ -17,9 +17,9 @@ Feature: Import content from another channel
And I press *Import* button
Then I see the *Copying content* modal which displays the import progress
And I don't see the *Import from Other channels* page
- And I see the editing page again
+ And I see the editing page again
#Import content from an available/accessible channel
Examples:
- | channel |
\ No newline at end of file
+ | channel |
diff --git a/integration_testing/features/manage-resources/import-from-search-results-at-my-channels-channel-2.feature b/integration_testing/features/manage-resources/import/import-from-search-results-at-my-channels-channel-2.feature
similarity index 92%
rename from integration_testing/features/manage-resources/import-from-search-results-at-my-channels-channel-2.feature
rename to integration_testing/features/manage-resources/import/import-from-search-results-at-my-channels-channel-2.feature
index 4ea2f3f37e..9ba661c465 100755
--- a/integration_testing/features/manage-resources/import-from-search-results-at-my-channels-channel-2.feature
+++ b/integration_testing/features/manage-resources/import/import-from-search-results-at-my-channels-channel-2.feature
@@ -1,6 +1,6 @@
Feature: Import content from the search results at *My Channels*
- Background:
+ Background:
Given I am already a registered user
And I am signed in to Studio
And I am on *My Channels > edit* page
@@ -18,4 +18,4 @@ Feature: Import content from the search results at *My Channels*
Then I see *Copying Content* modal
Examples:
- | channel | topic | resource |
\ No newline at end of file
+ | channel | topic | resource |
diff --git a/integration_testing/features/manage-resources/import-from-search-results-at-my-channels-channel.feature b/integration_testing/features/manage-resources/import/import-from-search-results-at-my-channels-channel.feature
similarity index 91%
rename from integration_testing/features/manage-resources/import-from-search-results-at-my-channels-channel.feature
rename to integration_testing/features/manage-resources/import/import-from-search-results-at-my-channels-channel.feature
index 6f74ecb217..221feafb66 100755
--- a/integration_testing/features/manage-resources/import-from-search-results-at-my-channels-channel.feature
+++ b/integration_testing/features/manage-resources/import/import-from-search-results-at-my-channels-channel.feature
@@ -1,6 +1,6 @@
Feature: Import content from the search results
-
- Background:
+
+ Background:
Given I am signed in to Studio
And I am on the editing page
@@ -21,4 +21,4 @@ Feature: Import content from the search results
Then I see the imported in the
Examples:
- | channel | search_term | resource |
\ No newline at end of file
+ | channel | search_term | resource |
diff --git a/integration_testing/features/manage-resources/create-a-topic.feature b/integration_testing/features/manage-resources/manage-folders/create-a-folder.feature
similarity index 67%
rename from integration_testing/features/manage-resources/create-a-topic.feature
rename to integration_testing/features/manage-resources/manage-folders/create-a-folder.feature
index 0f3939d738..18394b56c8 100755
--- a/integration_testing/features/manage-resources/create-a-topic.feature
+++ b/integration_testing/features/manage-resources/manage-folders/create-a-folder.feature
@@ -1,16 +1,16 @@
-Feature: Create a topic
+Feature: Create a folder
Background:
Given I am signed in to Studio
And I am on the channel editor page
- Scenario: Create a topic
+ Scenario: Create a folder
When I click the *Add* button in the top right corner
- And I click the *New topic* option
- Then I see the *New topic* modal
+ And I click the *New folder* option
+ Then I see the *New folder* modal
When I fill in the required field *Title*
And I fill in any of the other fields such as *Description*, *Tags* and *Language*
And I add a thumbnail image
And I click the *Finish* button
Then I am on the channel editor page
- And I can see the newly created topic
+ And I can see the newly created folder
diff --git a/integration_testing/features/manage-resources/manage-folders/set-up-empty-folder-tree.feature b/integration_testing/features/manage-resources/manage-folders/set-up-empty-folder-tree.feature
new file mode 100755
index 0000000000..769d76ecda
--- /dev/null
+++ b/integration_testing/features/manage-resources/manage-folders/set-up-empty-folder-tree.feature
@@ -0,0 +1,28 @@
+Feature: Set up empty folder tree
+
+ Background:
+ Given I am signed in to Studio
+ And I am on the channel editor page for an empty channel
+
+ Scenario: Create an empty folder tree
+ When I click the *Add* button in the top right corner
+ And I click the *New folder* option
+ Then I see the *New folder* modal
+ When I fill in the required fields
+ And I click the *Add new folder* button
+ Then I can fill in the required fields for a new folder #repeat this process for as many empty folders you need
+ When I click the *Finish* button
+ Then I am on the channel editor page
+ And I can see the empty folders
+
+ Scenario: Create sub-folders
+ Given I have created an empty folder tree
+ When I click *ā®* (Options) button for a folder
+ And I click the *New folder* option
+ Then I see the *New folder* modal
+ When I fill in the required fields
+ And I click the *Add new folder* button
+ Then I can fill in the required fields for a new folder #repeat this process for as many empty folders you need
+ When I click the *Finish* button
+ Then I am on the channel editor page
+ And I can click on the folder to see the created sub-folders
diff --git a/integration_testing/features/manage-resources/drag-drop-out-of-clipboard.feature b/integration_testing/features/manage-resources/move-resources/drag-drop-out-of-clipboard.feature
similarity index 95%
rename from integration_testing/features/manage-resources/drag-drop-out-of-clipboard.feature
rename to integration_testing/features/manage-resources/move-resources/drag-drop-out-of-clipboard.feature
index f5ea12c8b9..891a422093 100755
--- a/integration_testing/features/manage-resources/drag-drop-out-of-clipboard.feature
+++ b/integration_testing/features/manage-resources/move-resources/drag-drop-out-of-clipboard.feature
@@ -11,4 +11,4 @@ Feature: Drag-drop resources out of the clipboard
And I drop the items on the channel editor
Then clipboard stays open
And the items disappear from the clipboard
- And I see the items appear on the channel editor node at the very bottom
\ No newline at end of file
+ And I see the items appear on the channel editor node at the very bottom
diff --git a/integration_testing/features/manage-resources/drag-drop-to-clipboard.feature b/integration_testing/features/manage-resources/move-resources/drag-drop-to-clipboard.feature
similarity index 96%
rename from integration_testing/features/manage-resources/drag-drop-to-clipboard.feature
rename to integration_testing/features/manage-resources/move-resources/drag-drop-to-clipboard.feature
index f01b4402fb..13b5837219 100755
--- a/integration_testing/features/manage-resources/drag-drop-to-clipboard.feature
+++ b/integration_testing/features/manage-resources/move-resources/drag-drop-to-clipboard.feature
@@ -20,4 +20,4 @@ Feature: Drag-drop to clipboard
Then I see the clipboard button changes color styling and move slightly up and down
When I drop the content into the clipboard button
Then the clipboard button styling gets back to normal
- And I see a snackbar appears to confirm the resources were moved to clipboard
\ No newline at end of file
+ And I see a snackbar appears to confirm the resources were moved to clipboard
diff --git a/integration_testing/features/manage-resources/jump-to-new-location.feature b/integration_testing/features/manage-resources/move-resources/jump-to-new-location.feature
similarity index 83%
rename from integration_testing/features/manage-resources/jump-to-new-location.feature
rename to integration_testing/features/manage-resources/move-resources/jump-to-new-location.feature
index 98bd5bf3d8..d276b56cbe 100755
--- a/integration_testing/features/manage-resources/jump-to-new-location.feature
+++ b/integration_testing/features/manage-resources/move-resources/jump-to-new-location.feature
@@ -8,4 +8,4 @@ Feature: Jump to the new location
Scenario: Jump to the new location
When I click the *Go to location* action
- Then I jump to the new location the resources are moved to
\ No newline at end of file
+ Then I jump to the new location the resources are moved to
diff --git a/integration_testing/features/manage-resources/move-resources-from-clipboard.feature b/integration_testing/features/manage-resources/move-resources/move-resources-from-clipboard.feature
similarity index 88%
rename from integration_testing/features/manage-resources/move-resources-from-clipboard.feature
rename to integration_testing/features/manage-resources/move-resources/move-resources-from-clipboard.feature
index e65797d6fe..4cb3a75792 100755
--- a/integration_testing/features/manage-resources/move-resources-from-clipboard.feature
+++ b/integration_testing/features/manage-resources/move-resources/move-resources-from-clipboard.feature
@@ -3,11 +3,11 @@ Feature: Move multiple resources from Clipboard
Background:
Given I am signed in to Studio
And I am on the channel editor view
-
+
Scenario: Move multiple resources
When I click on clipboard button on the bottom-right of the screen
Then the clipboard opens up
When I select multiple items via checkboxes
And I see that the select bar changes to an actions bar
And I click the move button in the actions bar
- Then I am redirected to the movement interface where I can choose a new directory or channel to move the items to
\ No newline at end of file
+ Then I am redirected to the movement interface where I can choose a new directory or channel to move the items to
diff --git a/integration_testing/features/manage-resources/move-resources.feature b/integration_testing/features/manage-resources/move-resources/move-resources.feature
similarity index 84%
rename from integration_testing/features/manage-resources/move-resources.feature
rename to integration_testing/features/manage-resources/move-resources/move-resources.feature
index eb2fc6b360..7119db2bae 100755
--- a/integration_testing/features/manage-resources/move-resources.feature
+++ b/integration_testing/features/manage-resources/move-resources/move-resources.feature
@@ -10,7 +10,7 @@ Feature: Move resources to a new destination
And I click the move button
Then I am navigated to a screen that allows me to navigate and choose a destination to move the resource
When I navigate to an appropriate destination
- And click the *Move here* button
+ And click the *Move here* button
Then I am redirected to the channel editor
And I see a snackbar confirmation that my resources are moved
- And the resources are no longer in my original directory
\ No newline at end of file
+ And the resources are no longer in my original directory
diff --git a/integration_testing/features/manage-resources/move-to-clipboard-via-right-click.feature b/integration_testing/features/manage-resources/move-resources/move-to-clipboard-via-right-click.feature
similarity index 88%
rename from integration_testing/features/manage-resources/move-to-clipboard-via-right-click.feature
rename to integration_testing/features/manage-resources/move-resources/move-to-clipboard-via-right-click.feature
index 4424414187..e857d0369d 100755
--- a/integration_testing/features/manage-resources/move-to-clipboard-via-right-click.feature
+++ b/integration_testing/features/manage-resources/move-resources/move-to-clipboard-via-right-click.feature
@@ -9,4 +9,4 @@ Feature: Move resource in the clipboard via right click
Then the clipboard opens up
When I right click a topic or resource in the clipboard
And click *Move to...* in the dropdown menu that appears
- Then I am redirected to the movement interface where I can choose a new directory or channel to move the items to
\ No newline at end of file
+ Then I am redirected to the movement interface where I can choose a new directory or channel to move the items to
diff --git a/integration_testing/features/manage-resources/remove-resource-from-channel.feature b/integration_testing/features/manage-resources/remove-resource-from-channel.feature
index 9a31174087..220875a50e 100755
--- a/integration_testing/features/manage-resources/remove-resource-from-channel.feature
+++ b/integration_testing/features/manage-resources/remove-resource-from-channel.feature
@@ -1,6 +1,6 @@
Feature: Remove a topic or a resource from a channel
- Background:
+ Background:
Given I am signed in to Studio
And I am on the editor page
And I have edit permissions for
@@ -10,38 +10,38 @@ Feature: Remove a topic or a resource from a channel
And I click on a *Ā·Ā·Ā·* button for more options
Then I can see the *Remove* option
When I select the *Remove* option
- Then I can see the *Sent to trash* snackbar notification
+ Then I can see the *Sent to trash* snackbar notification
And I see the *Undo* button
And I don't see the topic anymore
When I click the *Undo* button
- Then I can see the topic again
+ Then I can see the topic again
Scenario: Remove a resource from *Ā·Ā·Ā·* (more options)
When I hover over a resource
And I click on a *Ā·Ā·Ā·* button for more options
Then I can see the *Remove* option
When I select the *Remove* option
- Then I can see the *Sent to trash* snackbar notification
+ Then I can see the *Sent to trash* snackbar notification
And I see the *Undo* button
And I don't see the resource anymore
When I click the *Undo* button
- Then I can see the resource again
+ Then I can see the resource again
Scenario: Remove a topic from toolbar
When I check the topic checkbox
- Then I see the toolbar options for topic
+ Then I see the toolbar options for topic
When I click the *Delete selected items* button
- Then I can see the *Sent to trash* snackbar notification
+ Then I can see the *Sent to trash* snackbar notification
And I see the *Undo* button
And I don't see the topic anymore
When I click the *Undo* button
- Then I can see the topic again
+ Then I can see the topic again
Scenario: Remove a resource from toolbar
When I hover over a resource checkbox
- Then I see the toolbar options for resource
+ Then I see the toolbar options for resource
When I click the *Delete selected items* button
- Then I can see the *Sent to trash* snackbar notification
+ Then I can see the *Sent to trash* snackbar notification
And I see the *Undo* button
And I don't see the resource anymore
When I click the *Undo* button
@@ -51,4 +51,4 @@ Feature: Remove a topic or a resource from a channel
# same as for single resources, just the snackbar notification indicates the number of items to remove
Examples:
- | channel | topic | resource |
\ No newline at end of file
+ | channel | topic | resource |
diff --git a/integration_testing/features/manage-resources/resource-count-updates-properly.feature b/integration_testing/features/manage-resources/resource-count-updates-properly.feature
index a8d047f0e8..e6193f0626 100755
--- a/integration_testing/features/manage-resources/resource-count-updates-properly.feature
+++ b/integration_testing/features/manage-resources/resource-count-updates-properly.feature
@@ -1,6 +1,6 @@
Feature: Resource count gets updated properly
- Background:
+ Background:
Given I am signed in to Studio
And I am on the editing page
@@ -16,4 +16,4 @@ Feature: Resource count gets updated properly
Then I see the *0 topics, 1 resource* notification at the bottom of the page
Examples:
- | channel | topic | import_channel | resource |
\ No newline at end of file
+ | channel | topic | import_channel | resource |
diff --git a/integration_testing/features/manage-resources/restore-resources-from-trash.feature b/integration_testing/features/manage-resources/restore-resources-from-trash.feature
index d6a1ee12f6..a4b42624b0 100755
--- a/integration_testing/features/manage-resources/restore-resources-from-trash.feature
+++ b/integration_testing/features/manage-resources/restore-resources-from-trash.feature
@@ -1,6 +1,6 @@
Feature: Restore resources from trash
- Background:
+ Background:
Given I am signed in to Studio
And I am on the editing page
@@ -22,7 +22,7 @@ Feature: Restore resources from trash
Then I see the *Managing deleted content* modal again
When I click the *Close* button
And I open the topic
- And I reload the page
+ And I reload the page
#not sure if this is a bug or a feature, but I had to do it
Then I see the resource restored to the inside the
@@ -44,9 +44,9 @@ Feature: Restore resources from trash
Then I see the *Managing deleted content* modal again
When I click the *Close* button
And I open the topic
- And I reload the page
+ And I reload the page
#not sure if this is a bug or a feature, but I had to do it
Then I see the resource restored to the inside the
Examples:
- | channel | resource | number | topic |
\ No newline at end of file
+ | channel | resource | number | topic |
diff --git a/integration_testing/features/manage-resources/review-import-selection-at-my-channels.feature b/integration_testing/features/manage-resources/review-import-selection-at-my-channels.feature
index 68629ca715..2f05db3f35 100755
--- a/integration_testing/features/manage-resources/review-import-selection-at-my-channels.feature
+++ b/integration_testing/features/manage-resources/review-import-selection-at-my-channels.feature
@@ -1,6 +1,6 @@
Feature: Review import selection at *My Channels*
- Background:
+ Background:
Given I am signed in to Studio
And I am on *My Channels > edit* page
@@ -14,4 +14,4 @@ Feature: Review import selection at *My Channels*
Then I see the list of contents I selected
Examples:
- | channel |
\ No newline at end of file
+ | channel |
diff --git a/integration_testing/features/manage-resources/set-up-empty-topic-tree.feature b/integration_testing/features/manage-resources/set-up-empty-topic-tree.feature
deleted file mode 100755
index 364d54a2ac..0000000000
--- a/integration_testing/features/manage-resources/set-up-empty-topic-tree.feature
+++ /dev/null
@@ -1,29 +0,0 @@
-Feature: Set up empty topic tree
-
- Background:
- Given I am signed in to Studio
- And I am on the channel editor page for an empty channel
-
- Scenario: Create an empty topic tree
- When I click the *Add* button in the top right corner
- And I click the *New topic* option
- Then I see the *New topic* modal
- When I fill in the required fields
- And I click the *Add new topic* button
- Then I can fill in the required fields for a new topic #repeat this process for as many empty topics you need
- When I click the *Finish* button
- Then I am on the channel editor page
- And I can see the empty topics
-
- Scenario: Create sub-topics
- Given I have created an empty topic tree
- When I click *ā®* (Options) button for a topic
- And I click the *New topic* option
- Then I see the *New topic* modal
- When I fill in the required fields
- And I click the *Add new topic* button
- Then I can fill in the required fields for a new topic #repeat this process for as many empty topics you need
- When I click the *Finish* button
- Then I am on the channel editor page
- And I can click on the topic to see the created sub-topics
-
\ No newline at end of file
diff --git a/integration_testing/features/wip-studio-previewers-cards-channel-details/view-new-metadata-in-previewers-across-studio.feature b/integration_testing/features/manage-resources/studio-previewers-cards-channel-details/view-metadata-in-previewers-across-studio.feature
similarity index 85%
rename from integration_testing/features/wip-studio-previewers-cards-channel-details/view-new-metadata-in-previewers-across-studio.feature
rename to integration_testing/features/manage-resources/studio-previewers-cards-channel-details/view-metadata-in-previewers-across-studio.feature
index 03f60b2a6c..0e304ad69d 100755
--- a/integration_testing/features/wip-studio-previewers-cards-channel-details/view-new-metadata-in-previewers-across-studio.feature
+++ b/integration_testing/features/manage-resources/studio-previewers-cards-channel-details/view-metadata-in-previewers-across-studio.feature
@@ -1,7 +1,7 @@
-Feature: View new metadata in previewers across Studio
+Feature: View metadata in previewers across Studio
How the new metadata is laid out on previewers
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel
@@ -21,12 +21,13 @@ Feature: View new metadata in previewers across Studio
When I left-click a .MP4 or .MOV
Then I see the previewer panel
And I see a learning activity label
- And I see text fields for: *Level*, *Learning activity*, *Completion*, *Category*, and *Accessibility*
+ And I see text fields for: *Level*, *Learning activity*, *Completion*, *Category*, *Accessibility*, , and *Captions and subtitles*
Scenario: View Practice previewer
When I left-click an exercise
Then I see the previewer panel
- And I see a learning activity label
+ When I click the *Details* tab
+ Then I see a learning activity label
And I see text fields for: *Level*, *Learning activity*, *Completion*, *Category*, and *Accessibility*
Scenario: View previewer with multiple activities
diff --git a/integration_testing/features/wip-studio-previewers-cards-channel-details/view-new-metadata-in-the-channel-details-modal.feature b/integration_testing/features/manage-resources/studio-previewers-cards-channel-details/view-new-metadata-in-the-channel-details-modal.feature
similarity index 96%
rename from integration_testing/features/wip-studio-previewers-cards-channel-details/view-new-metadata-in-the-channel-details-modal.feature
rename to integration_testing/features/manage-resources/studio-previewers-cards-channel-details/view-new-metadata-in-the-channel-details-modal.feature
index 524973558b..e66b3d33b2 100755
--- a/integration_testing/features/wip-studio-previewers-cards-channel-details/view-new-metadata-in-the-channel-details-modal.feature
+++ b/integration_testing/features/manage-resources/studio-previewers-cards-channel-details/view-new-metadata-in-the-channel-details-modal.feature
@@ -1,6 +1,6 @@
Feature: View new metadata in the channel details modal
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel
When I click on the info icon next to the channel label in the app bar
@@ -18,7 +18,7 @@ Feature: View new metadata in the channel details modal
Then I see a list of levels that appear in the channel
And I see that the list is comma-separated
And I see it is ordered from most frequent levels first to least frequent levels last
-
+
Scenario: View categories in the channel
Given there are resources marked with different categories
Then I see a list of categories that appear in the channel
diff --git a/integration_testing/features/wip-studio-previewers-cards-channel-details/view-new-metadata-on-cards-and-list-items-in-the-studio-topic-tree-view.feature b/integration_testing/features/manage-resources/studio-previewers-cards-channel-details/view-new-metadata-on-cards-and-list-items-in-the-studio-topic-tree-view.feature
similarity index 94%
rename from integration_testing/features/wip-studio-previewers-cards-channel-details/view-new-metadata-on-cards-and-list-items-in-the-studio-topic-tree-view.feature
rename to integration_testing/features/manage-resources/studio-previewers-cards-channel-details/view-new-metadata-on-cards-and-list-items-in-the-studio-topic-tree-view.feature
index db25525014..8beefcf4d7 100755
--- a/integration_testing/features/wip-studio-previewers-cards-channel-details/view-new-metadata-on-cards-and-list-items-in-the-studio-topic-tree-view.feature
+++ b/integration_testing/features/manage-resources/studio-previewers-cards-channel-details/view-new-metadata-on-cards-and-list-items-in-the-studio-topic-tree-view.feature
@@ -1,6 +1,6 @@
Feature: View new metadata on cards and list items in the Studio topic tree view
- Background:
+ Background:
Given I am signed into Studio
And I am in an editable channel with all resource types
@@ -27,12 +27,12 @@ Feature: View new metadata on cards and list items in the Studio topic tree view
And I do not see any other metadata
Scenario: See metadata for .pdf, .epub, .mp3, .mp4, .mov, .zip while importing from a channel
- Given *View* is set to *Default view*
+ Given *View* is set to *Default view*
And has , , and options set
Then I see , , and on the in the topic tree
And I see the thumbnail, description, and title
- Scenario: See metadata for exercises while importing from a channle
+ Scenario: See metadata for exercises while importing from a channel
Given *View* is set to *Default view*
And has , , and options set
Then I see , , and on the in the topic tree
diff --git a/integration_testing/features/manage-resources/upload-files.feature b/integration_testing/features/manage-resources/upload-files.feature
index 1f65efbbe7..852418b6dd 100755
--- a/integration_testing/features/manage-resources/upload-files.feature
+++ b/integration_testing/features/manage-resources/upload-files.feature
@@ -1,7 +1,7 @@
Feature: Upload files
Users should be able to upload individual learning resources into their channel
- Background:
+ Background:
Given I am signed in to Studio
And I am on the channel editor page
diff --git a/integration_testing/features/manage-resources/view-resource-details.feature b/integration_testing/features/manage-resources/view-resource-details.feature
index dcae2944d4..9719526024 100755
--- a/integration_testing/features/manage-resources/view-resource-details.feature
+++ b/integration_testing/features/manage-resources/view-resource-details.feature
@@ -7,4 +7,4 @@ Feature: Click the information button
Scenario: Click the information button
When I click the information button
Then a sidebar opens from the right side of the screen
- And here I can preview the content in question and view the metadata associated
\ No newline at end of file
+ And here I can preview the content in question and view the metadata associated
diff --git a/integration_testing/features/wip-studio-edit-modal/new-metadata-defaults-while-uploading-or-creating-new-files.feature b/integration_testing/features/wip-studio-edit-modal/new-metadata-defaults-while-uploading-or-creating-new-files.feature
deleted file mode 100755
index 3dad8a676c..0000000000
--- a/integration_testing/features/wip-studio-edit-modal/new-metadata-defaults-while-uploading-or-creating-new-files.feature
+++ /dev/null
@@ -1,72 +0,0 @@
-Feature: New metadata defaults while uploading or creating new files
- User uploads files and sees which new metadata fields are and aren't set by default
-
- Background:
- Given I am signed into Studio
- And I am in an editable channel
- When I click the *Add* button
- And I select *Upload files*
- Then I see the *Upload files* page
-
- Scenario: Upload .PDF or .EPUB
- When I upload a .PDF or an .EPUB file
- Then I see the edit modal for <.PDF or .EPUB>
- And I see upload of <.PDF or .EPUB> is in progress
- And I see *Level* is empty
- And I see *Learning activity* is set to *Read*
- And I see *What you will need* is empty
- And I see *Allow marking as complete* is empty
- And I see *Completion* is set to *All content viewed*
- And I see *Accessibility* checkboxes are unchecked
- And I see *Category* is empty
-
- Scenario: Upload .MP3
- When I upload <.MP3>
- Then I see the edit modal for <.MP3>
- And I see upload of <.MP3> is in progress
- And I see *Level* is empty
- And I see *Learning activity* is set to *Listen*
- And I see *What you will need* is empty
- And I see *Allow marking as complete* is empty
- And I see *Completion* is set to *Exact time to complete*
- And I see *Exact time to complete* is set to the exact duration of <.MP3>
- And I see *Accessibility* checkboxes are unchecked
- And I see *Category* is empty
-
- Scenario: Upload .MP4 or .MOV
- When I upload <.MP4 or .MOV>
- Then I see the edit modal for <.MP4 or .MOV>
- And I see upload of <.MP4 or .MOV> is in progress
- And I see *Level* is empty
- And I see *Learning activity* is set to *Watch*
- And I see *What you will need* is empty
- And I see *Allow marking as complete* is empty
- And I see *Completion* is set to *Exact time to complete*
- And I see *Exact time to complete* is set to the exact duration of <.MP4 or .MOV>
- And I see *Accessibility* checkboxes are unchecked
- And I see *Category* is empty
-
- Scenario: Upload .ZIP
- When I upload <.ZIP>
- Then I see the edit modal for <.ZIP>
- And I see upload of <.ZIP> is in progress
- And I see *Level* is empty
- And I see *Learning activity* is empty
- And I see *What you will need* is empty
- And I see *Allow marking as complete* is empty
- And I see *Completion* is empty
- And I see *Accessibility* checkboxes are unchecked
- And I see *Category* is empty
-
- Scenario: Create new Practice resource
- Given I am in an editable channel
- When I click *Add*
- And I click *New exercise*
- Then I see the edit modal for the exercise
- And I see *Level* is empty
- And I see *Learning activity* is set to *Practice*
- And I see *What you will need* is empty
- And I see *Allow marking as complete* is empty
- And I see *Completion* is empty
- And I see *Accessibility* checkboxes are unchecked
- And I see *Category* is empty
diff --git a/integration_testing/features/wip-studio-edit-modal/wip-edit-accessibility-field.feature b/integration_testing/features/wip-studio-edit-modal/wip-edit-accessibility-field.feature
deleted file mode 100755
index 29d082daab..0000000000
--- a/integration_testing/features/wip-studio-edit-modal/wip-edit-accessibility-field.feature
+++ /dev/null
@@ -1,86 +0,0 @@
-Feature: Edit *Accessibility* field
- Across all file types
-
-# Comment here
-
- Background:
- Given I am signed into Studio
- And I am in an editable channel
- When I right click
- When I click *Edit details*
- Then I see the edit modal for
- And I see the *Accessibility* section underneath the *Audience* section
-
- Scenario: View options for .MP4
- Given I am viewing an .MP4 in the edit modal
- Then I see a checkbox that says *Has captions or subtitles*
- And I see a checkbox that says *Has audio descriptions*
- And I see a checkbox that says *Has sign language captions*
-
- Scenario: View tooltips for .MP4
- Given I am viewing an .MP4 in the edit modal
- When I hover my mouse over the info icon for *Has sign language captions*
- Then I see *text*
- When I hover my mouse over the info icon for *Has audio descriptions*
- Then I see *text*
-
- Scenario: View options for .PDF or .EPUB
- Given I am viewing a .PDF or .EPUB in the edit modal
- Then I see a checkbox that says *Has alternative text description for images*
- And I see a checkbox that says *Has high contrast display for low vision*
- And I see a checkbox that says *Tagged PDF*
-
- Scenario: View tooltips for .PDF or .EPUB
- Given I am viewing a .PDF or .EPUB in the edit modal
- When I hover my mouse over the info icon for *Has alternative text description for images*
- Then I see *text*
- When I hover my mouse over the info icon for *Has high contrast display for low vision*
- Then I see *text*
- When I hover my mouse over the info icon for *Tagged PDF*
- Then I see *text*
-
- Scenario: View options for .MP3
- Given I am viewing a .MP3 in the edit modal
- Then I see a *Captions and subtitles* section underneath the *Source* section
-
- Scenario: View tooltips for .MP3
- Given I am viewing an .MP3 in the edit modal
- When I hover my mouse over the info icon for *Captions and subtitles*
- Then I see *Supported formats: vtt*
-
- Scenario: View options for .ZIP
- Given I am viewing a .ZIP in the edit modal
- Then I see a checkbox that says *Has alternative text description for images*
- And I see a checkbox that says *Has high contrast display for low vision*
-
- Scenario: View tooltips for .ZIP
- Given I am viewing a .ZIP in the edit modal
- When I hover my mouse over the info icon for *Has alternative text description for images*
- Then I see *text*
- When I hover my mouse over the info icon for *Has high contrast display for low vision*
- Then I see *text*
-
- Scenario: View options for Exercises
- Given I am viewing an Exercise in the edit modal
- Then I see a checkbox that says *Has alternative text description for images*
-
- Scenario: View tooltips for Practice resources
- Given I am viewing an Exercise in the edit modal
- When I over my mouse over the info icon for *Has alternative text description for images*
- Then I see *text*
-
- Scenario: Select and deselect accessibility options
- Given that has checkboxes in the *Accessibility* section
- When I click under *Accessibility*
- Then I see the checkbox is selected
- When I click while it is selected
- Then I see the checkbox is empty
-
- Scenario: See that accessibility field is optional
- Given the *Accessibility* field of is empty
- When I click *FINISH*
- Then I see in the topic tree
- And I do not see an error icon
- When I left-click
- Then I see the previewer for
- And I see the *Accessibility* field is empty
diff --git a/integration_testing/features/wip-studio-edit-modal/wip-toggle-make-this-a-quiz-in-assessment-options.feature b/integration_testing/features/wip-studio-edit-modal/wip-toggle-make-this-a-quiz-in-assessment-options.feature
deleted file mode 100755
index afa4066e10..0000000000
--- a/integration_testing/features/wip-studio-edit-modal/wip-toggle-make-this-a-quiz-in-assessment-options.feature
+++ /dev/null
@@ -1,15 +0,0 @@
-Feature: Toggle *Make this a quiz* in assessment options of a Practice resource
-
-# Comment here
-
- Background:
- Given I
-
- Scenario: View *Make this a quiz* option
- When I
-
- Scenario: Toggle *Make this a quiz*
- When I
-
- Scenario: Toggle *Make this a quiz* when a completion option is set
- When I
diff --git a/jest_config/globalMocks/broadcastChannelMock.js b/jest_config/globalMocks/broadcastChannelMock.js
deleted file mode 100644
index 6fd584b845..0000000000
--- a/jest_config/globalMocks/broadcastChannelMock.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export class BroadcastChannel {
- addEventListener() {}
- removeEventListener() {}
- postMessage() {}
-};
diff --git a/jest_config/jest.conf.js b/jest_config/jest.conf.js
index eb4849b797..fd99eaf0e6 100644
--- a/jest_config/jest.conf.js
+++ b/jest_config/jest.conf.js
@@ -14,7 +14,6 @@ module.exports = {
__dirname,
'./globalMocks/fileMock.js'
),
- 'broadcast-channel$': path.resolve(__dirname, './globalMocks/broadcastChannelMock.js'),
'\\.worker.min.js': path.resolve(__dirname, './globalMocks/fileMock.js'),
'shared/client': path.resolve(__dirname, './globalMocks/client.js'),
'shared/urls': path.resolve(__dirname, './globalMocks/urls.js'),
@@ -25,9 +24,9 @@ module.exports = {
'^.+\\.js$': '/node_modules/babel-jest',
'.*\\.(vue)$': '/node_modules/vue-jest',
},
- transformIgnorePatterns: ['/node_modules/(?!vuetify|epubjs|kolibri-design-system|kolibri-constants)'],
+ transformIgnorePatterns: ['/node_modules/(?!vuetify|epubjs|kolibri-design-system|kolibri-constants|axios)'],
snapshotSerializers: ['/node_modules/jest-serializer-vue'],
- setupFilesAfterEnv: [path.resolve(__dirname, './setup')],
+ setupFilesAfterEnv: ['/jest_config/setup.js'],
coverageDirectory: '/coverage',
collectCoverageFrom: ['!**/node_modules/**'],
verbose: false,
diff --git a/jest_config/setup.js b/jest_config/setup.js
index c96391f83f..d5fb127156 100644
--- a/jest_config/setup.js
+++ b/jest_config/setup.js
@@ -9,7 +9,16 @@ import KThemePlugin from 'kolibri-design-system/lib/KThemePlugin';
import 'shared/i18n/setup';
// Polyfill indexeddb
import 'fake-indexeddb/auto';
+// Ponyfill webstreams
+import {ReadableStream, WritableStream, TransformStream, CountQueuingStrategy} from 'web-streams-polyfill/ponyfill/es2018';
import jquery from 'jquery';
+
+window.jQuery = window.$ = jquery;
+window.ReadableStream = global.ReadableStream = ReadableStream;
+window.WritableStream = global.WritableStream = WritableStream;
+window.TransformStream = global.TransformStream = TransformStream;
+window.CountQueuingStrategy = global.CountQueuingStrategy = CountQueuingStrategy;
+
import AnalyticsPlugin from 'shared/analytics/plugin';
import { setupSchema } from 'shared/data';
import * as resources from 'shared/data/resources';
@@ -34,8 +43,6 @@ global.afterEach(() => {
});
});
-window.jQuery = window.$ = jquery;
-
window.storageBaseUrl = '/content/storage/';
Vue.use(VueRouter);
diff --git a/k8s/images/app/Dockerfile b/k8s/images/app/Dockerfile
index de612c57ac..60f2471985 100644
--- a/k8s/images/app/Dockerfile
+++ b/k8s/images/app/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.9-slim-buster
+FROM python:3.10-slim-bookworm
# Set the timezone
RUN ln -fs /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
@@ -6,10 +6,19 @@ RUN ln -fs /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
ENV DEBIAN_FRONTEND noninteractive
# Default Python file.open file encoding to UTF-8 instead of ASCII, workaround for le-utils setup.py issue
ENV LANG C.UTF-8
-RUN apt-get update && apt-get -y install python3-pip python3-dev gcc libpq-dev make git curl libjpeg-dev ffmpeg
-
-# install node
-RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
+RUN apt-get update && apt-get -y install python3-pip python3-dev gcc libpq-dev libssl-dev libffi-dev make git curl libjpeg-dev ffmpeg
+
+# Pin, Download and install node 16.x
+RUN apt-get update \
+ && apt-get install -y ca-certificates curl gnupg \
+ && mkdir -p /etc/apt/keyrings \
+ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+ && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
+ && echo "Package: nodejs" >> /etc/apt/preferences.d/preferences \
+ && echo "Pin: origin deb.nodesource.com" >> /etc/apt/preferences.d/preferences \
+ && echo "Pin-Priority: 1001" >> /etc/apt/preferences.d/preferences\
+ && apt-get update \
+ && apt-get install -y nodejs
RUN npm install --location=global yarn && npm cache clean --force
diff --git a/k8s/images/nginx/Dockerfile b/k8s/images/nginx/Dockerfile
index 378d964e19..d0f41afaa5 100644
--- a/k8s/images/nginx/Dockerfile
+++ b/k8s/images/nginx/Dockerfile
@@ -1,17 +1,18 @@
FROM byrnedo/alpine-curl
# download all extra deps we need for the production container
-
# templating executable
-RUN curl -L "https://github.com/gliderlabs/sigil/releases/download/v0.4.0/sigil_0.4.0_$(uname -sm|tr \ _).tgz" \ | tar -zxC /usr/bin
+COPY /k8s/images/nginx/download_sigil.sh /tmp/download_sigil.sh
+RUN chmod +x /tmp/download_sigil.sh
+RUN /tmp/download_sigil.sh
-FROM nginx:1.11
+FROM nginx:1.25
RUN rm /etc/nginx/conf.d/* # if there's stuff here, nginx won't read sites-enabled
ADD deploy/nginx.conf.jinja2 /etc/nginx/nginx.conf.jinja2
ADD k8s/images/nginx/entrypoint.sh /usr/bin
# install the templating binary
-COPY --from=0 /usr/bin/sigil /usr/bin
+COPY --from=0 /tmp/sigil /usr/bin/
-CMD entrypoint.sh
\ No newline at end of file
+CMD entrypoint.sh
diff --git a/k8s/images/nginx/download_sigil.sh b/k8s/images/nginx/download_sigil.sh
new file mode 100755
index 0000000000..41c5b24a61
--- /dev/null
+++ b/k8s/images/nginx/download_sigil.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+set -eou pipefail
+
+export SIGIL_VERSION=0.10.1
+export OS=`sh -c "uname -s | tr '[:upper:]' '[:lower:]'"`
+export ARCH=`sh -c "uname -m | tr '[:upper:]' '[:lower:]' | sed 's/aarch64/arm64/' | sed 's/x86_64/amd64/'"`
+
+
+curl -L "https://github.com/gliderlabs/sigil/releases/download/v${SIGIL_VERSION}/gliderlabs-sigil_${SIGIL_VERSION}_${OS}_${ARCH}.tgz" | tar -zxC /tmp
+mv /tmp/gliderlabs-sigil-${ARCH} /tmp/sigil
diff --git a/package.json b/package.json
index 6457bfe0ed..d5d83182ab 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,9 @@
"lint-frontend:format": "yarn run lint-frontend --write",
"lint-frontend:watch": "yarn run lint-frontend --monitor",
"lint-frontend:watch:format": "yarn run lint-frontend --monitor --write",
- "makemessages": "node ./node_modules/kolibri-tools/lib/i18n/ExtractMessages.js --project=studio",
+ "makemessages": "kolibri-tools i18n-extract-messages --namespace contentcuration --searchPath contentcuration/contentcuration/frontend",
"combineprofiles": "node ./node_modules/kolibri-tools/lib/combineStringProfiles.js ./contentcuration/locale/en/LC_MESSAGES/profiles/",
- "transfercontext": "APP_NAME=contentcuration node ./node_modules/kolibri-tools/lib/i18n/SyncContext.js run && yarn lint-all:fix",
+ "transfercontext": "kolibri-tools i18n-transfer-context --namespace studio --searchPath contentcuration/contentcuration/frontend; yarn lint-all:fix",
"build": "webpack --env prod --config webpack.config.js",
"postgres": "pg_ctl -D /usr/local/var/postgresql@9.6 start || true",
"redis": "redis-server /usr/local/etc/redis.conf || true",
@@ -31,7 +31,7 @@
"devserver:hot": "npm-run-all --parallel build:dev:hot runserver",
"devserver-hot": "yarn run devserver:hot",
"devshell": "cd contentcuration && python manage.py shell --settings=contentcuration.dev_settings",
- "celery": "(cd contentcuration && DJANGO_SETTINGS_MODULE=contentcuration.dev_settings celery -A contentcuration worker --without-mingle --without-gossip -l info) || true",
+ "celery": "(cd contentcuration && DJANGO_SETTINGS_MODULE=contentcuration.dev_settings celery -A contentcuration worker --without-mingle --without-gossip -c 1 -l info) || true",
"storybook": "start-storybook",
"storybook:debug": "start-storybook --debug-webpack",
"storybook:build": "build-storybook",
@@ -56,29 +56,29 @@
"dependencies": {
"@sentry/vue": "^7.11.1",
"@toast-ui/editor": "^2.3.1",
- "ajv": "^8.9.0",
- "axios": "^0.27.2",
- "broadcast-channel": "^4.17.0",
+ "ajv": "^8.12.0",
+ "axios": "^1.6.2",
"codemirror": "5.58.2",
- "core-js": "^3.25.1",
- "dexie": "^3.2.2",
+ "core-js": "^3.35.0",
+ "dexie": "^3.2.6",
"dexie-observable": "3.0.0-beta.11",
"epubjs": "^0.3.89",
"file-saver": "^2.0.2",
"html2canvas": "^1.0.0-rc.5",
- "i18n-iso-countries": "^7.5.0",
+ "i18n-iso-countries": "^7.7.0",
"intl": "1.2.5",
"jquery": "^2.2.4",
- "jspdf": "https://github.com/MrRio/jsPDF.git#b7a1d8239c596292ce86dafa77f05987bcfa2e6e",
- "kolibri-constants": "^0.1.41",
- "kolibri-design-system": "https://github.com/learningequality/kolibri-design-system#e9a2ff34716bb6412fe99f835ded5b17345bab94",
+ "jspdf": "https://github.com/parallax/jsPDF.git#b7a1d8239c596292ce86dafa77f05987bcfa2e6e",
+ "jszip": "^3.10.1",
+ "kolibri-constants": "^0.2.0",
+ "kolibri-design-system": "3.0.1",
"lodash": "^4.17.21",
"material-icons": "0.3.1",
"mutex-js": "^1.1.5",
"node-vibrant": "^3.1.6",
- "papaparse": "^5.2.0",
+ "papaparse": "^5.4.1",
"pdfjs-dist": "^2.16.105",
- "qs": "^6.11.0",
+ "qs": "^6.11.2",
"regenerator-runtime": "^0.13.5",
"showdown": "^2.1.0",
"spark-md5": "^3.0.0",
@@ -89,16 +89,16 @@
"vue-croppa": "^1.3.8",
"vue-custom-element": "https://github.com/learningequality/vue-custom-element.git#master",
"vue-intl": "^3.0.0",
- "vue-router": "3.5.4",
+ "vue-router": "3.6.5",
"vuetify": "^1.5.24",
"vuex": "^3.0.1",
- "workbox-precaching": "^6.5.4",
- "workbox-window": "^6.5.4"
+ "workbox-precaching": "^7.0.0",
+ "workbox-window": "^7.0.0"
},
"devDependencies": {
"@vue/test-utils": "1.0.0-beta.29",
- "aphrodite": "https://github.com/learningequality/aphrodite/",
- "autoprefixer": "^9.6.1",
+ "aphrodite": "https://github.com/learningequality/aphrodite.git",
+ "autoprefixer": "^10.4.18",
"babel-jest": "^26.0.1",
"circular-dependency-plugin": "^5.2.0",
"eslint-import-resolver-webpack": "0.13.2",
@@ -109,15 +109,15 @@
"jest": "^26.0.1",
"jest-each": "^29.0.3",
"jest-environment-jsdom-sixteen": "^1.0.3",
- "jest-serializer-vue": "^2.0.2",
- "kolibri-tools": "^0.16.0-dev.1",
+ "jest-serializer-vue": "^3.1.0",
+ "kolibri-tools": "0.16.0-dev.3",
"less": "^3.0.1",
- "less-loader": "^11.0.0",
- "mini-css-extract-plugin": "1.6.2",
+ "less-loader": "^11.1.3",
"npm-run-all": "^4.1.3",
- "stylus": "^0.59.0",
- "stylus-loader": "^3.0.2",
- "workbox-webpack-plugin": "^6.5.4"
+ "stylus": "^0.62.0",
+ "stylus-loader": "^7.1.3",
+ "web-streams-polyfill": "^3.2.1",
+ "workbox-webpack-plugin": "^7.0.0"
},
"false": {},
"peerDependencies": {},
diff --git a/requirements-dev.in b/requirements-dev.in
index 5d977035f0..59157d10d7 100644
--- a/requirements-dev.in
+++ b/requirements-dev.in
@@ -14,7 +14,6 @@ pytest-pythonpath
pytest-timeout
pytest-watch
pre-commit==1.15.1
-codecov
coverage
pytest-cov
nodeenv
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 9bf9443073..f616b049fe 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,6 +1,6 @@
#
-# This file is autogenerated by pip-compile with python 3.9
-# To update, run:
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
#
# pip-compile requirements-dev.in
#
@@ -10,7 +10,7 @@ asgiref==3.3.4
# django
aspy-yaml==1.3.0
# via pre-commit
-attrs==19.3.0
+attrs==23.1.0
# via
# -c requirements.txt
# pytest
@@ -35,8 +35,6 @@ click==8.1.3
# -c requirements.txt
# flask
# pip-tools
-codecov==2.1.12
- # via -r requirements-dev.in
colorama==0.4.4
# via pytest-watch
configargparse==1.5.3
@@ -50,13 +48,12 @@ coreschema==0.0.4
coverage[toml]==6.2
# via
# -r requirements-dev.in
- # codecov
# pytest-cov
customizable-django-profiler @ git+https://github.com/someshchaturvedi/customizable-django-profiler.git
# via -r requirements-dev.in
distlib==0.3.1
# via virtualenv
-django==3.2.14
+django==3.2.24
# via
# -c requirements.txt
# django-debug-toolbar
@@ -93,15 +90,15 @@ flask-basicauth==0.2.0
# via locust
flask-cors==3.0.10
# via locust
-fonttools==4.27.1
+fonttools==4.43.0
# via -r requirements-dev.in
-gevent==21.12.0
+gevent==23.9.1
# via
# geventhttpclient
# locust
-geventhttpclient==1.5.3
+geventhttpclient==2.0.9
# via locust
-greenlet==1.1.2
+greenlet==2.0.2
# via gevent
identify==2.4.4
# via pre-commit
@@ -110,9 +107,7 @@ idna==2.10
# -c requirements.txt
# requests
importlib-metadata==1.7.0
- # via
- # -c requirements.txt
- # pre-commit
+ # via pre-commit
inflection==0.5.1
# via drf-yasg
iniconfig==1.1.1
@@ -125,11 +120,12 @@ jinja2==3.0.3
# via
# coreschema
# flask
- # locust
-locust==2.8.6
+locust==2.15.1
# via -r requirements-dev.in
-markupsafe==2.0.1
- # via jinja2
+markupsafe==2.1.2
+ # via
+ # jinja2
+ # werkzeug
mccabe==0.6.1
# via flake8
minio==7.1.1
@@ -205,7 +201,7 @@ pytest-timeout==1.4.2
# via -r requirements-dev.in
pytest-watch==4.2.0
# via -r requirements-dev.in
-python-dateutil==2.8.1
+python-dateutil==2.8.2
# via
# -c requirements.txt
# faker
@@ -222,7 +218,6 @@ pyzmq==23.1.0
requests==2.25.1
# via
# -c requirements.txt
- # codecov
# coreapi
# locust
roundrobin==0.0.2
@@ -269,7 +264,7 @@ uritemplate==3.0.1
# via
# coreapi
# drf-yasg
-urllib3==1.26.5
+urllib3==1.26.18
# via
# -c requirements.txt
# minio
@@ -278,18 +273,16 @@ virtualenv==20.14.1
# via pre-commit
watchdog==2.1.8
# via pytest-watch
-werkzeug==2.0.1
+werkzeug==2.2.3
# via
# flask
# locust
-wheel==0.37.1
+wheel==0.38.1
# via pip-tools
whitenoise==5.2.0
# via -r requirements-dev.in
zipp==3.4.1
- # via
- # -c requirements.txt
- # importlib-metadata
+ # via importlib-metadata
zope-event==4.5.0
# via gevent
zope-interface==5.4.0
diff --git a/requirements-docs.txt b/requirements-docs.txt
index 16ee145222..5ebb98b91a 100644
--- a/requirements-docs.txt
+++ b/requirements-docs.txt
@@ -1,5 +1,5 @@
#
-# This file is autogenerated by pip-compile with python 3.9
+# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile requirements-docs.in
@@ -44,7 +44,7 @@ pathtools==0.1.2
# watchdog
port-for==0.3.1
# via sphinx-autobuild
-pygments==2.7.4
+pygments==2.15.0
# via sphinx
pytz==2022.1
# via
@@ -80,11 +80,11 @@ sphinxcontrib-websupport==1.2.4
# via
# -r requirements-docs.in
# sphinx
-tornado==6.0.4
+tornado==6.3.3
# via
# livereload
# sphinx-autobuild
-urllib3==1.26.5
+urllib3==1.26.18
# via
# -c requirements.txt
# requests
diff --git a/requirements.in b/requirements.in
index 9ba054c973..1d07eec20e 100644
--- a/requirements.in
+++ b/requirements.in
@@ -1,45 +1,39 @@
-attrs==19.3.0
django-cte==1.2.1
-django-mptt==0.13.4
+django-mptt==0.14.0
django-filter==22.1
djangorestframework==3.12.4
psycopg2-binary==2.9.5
django-js-reverse==0.9.1
-django-registration==3.3
-le-utils==0.1.42
+django-registration==3.4
+le-utils==0.2.5
gunicorn==20.1.0
django-postmark==0.1.6
jsonfield==3.1.0
newrelic>=2.86.3.70
celery==5.2.7
redis
-pathlib
-progressbar2==3.55.0
python-postmark==0.5.8
-Django==3.2.14
+Django==3.2.24
django-webpack-loader==0.7.0
google-cloud-error-reporting
google-cloud-storage
-django-s3-storage==0.13.9
+django-s3-storage==0.14.0
requests>=2.20.0
google-cloud-core
django-db-readonly==0.7.0
-oauth2client
django-mathfilters
-google-cloud-kms==1.1.0
+google-cloud-kms==2.10.0
+google-crc32c==1.1.2
backoff
-backports-abc==0.5
-django-model-utils==4.2.0
+django-model-utils==4.3.1
django-redis
django-prometheus
future
sentry-sdk
-django-bulk-update
html5lib==1.1
-pillow==9.2.0
+pillow==10.2.0
python-dateutil>=2.8.1
jsonschema>=3.2.0
-importlib-metadata==1.7.0
channels==3.0.4
channels-redis==3.3.1
django-celery-results
diff --git a/requirements.txt b/requirements.txt
index 88bfa0a52c..639e31fddc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
#
-# This file is autogenerated by pip-compile with python 3.9
-# To update, run:
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
#
# pip-compile requirements.in
#
@@ -18,9 +18,8 @@ async-timeout==4.0.2
# via
# aioredis
# redis
-attrs==19.3.0
+attrs==23.1.0
# via
- # -r requirements.in
# automat
# jsonschema
# service-identity
@@ -31,8 +30,6 @@ automat==20.2.0
# via twisted
backoff==2.2.1
# via -r requirements.in
-backports-abc==0.5
- # via -r requirements.in
billiard==3.6.4.0
# via celery
boto3==1.17.75
@@ -86,13 +83,10 @@ cryptography==37.0.2
# service-identity
daphne==3.0.2
# via channels
-deprecated==1.2.13
- # via redis
-django==3.2.14
+django==3.2.24
# via
# -r requirements.in
# channels
- # django-bulk-update
# django-db-readonly
# django-filter
# django-js-reverse
@@ -102,8 +96,6 @@ django==3.2.14
# django-s3-storage
# djangorestframework
# jsonfield
-django-bulk-update==2.2.0
- # via -r requirements.in
django-celery-results==2.4.0
# via -r requirements.in
django-cte==1.2.1
@@ -118,9 +110,9 @@ django-js-reverse==0.9.1
# via -r requirements.in
django-mathfilters==1.0.0
# via -r requirements.in
-django-model-utils==4.2.0
+django-model-utils==4.3.1
# via -r requirements.in
-django-mptt==0.13.4
+django-mptt==0.14.0
# via -r requirements.in
django-postmark==0.1.6
# via -r requirements.in
@@ -128,15 +120,15 @@ django-prometheus==2.2.0
# via -r requirements.in
django-redis==5.2.0
# via -r requirements.in
-django-registration==3.3
+django-registration==3.4
# via -r requirements.in
-django-s3-storage==0.13.9
+django-s3-storage==0.14.0
# via -r requirements.in
django-webpack-loader==0.7.0
# via -r requirements.in
djangorestframework==3.12.4
# via -r requirements.in
-future==0.18.2
+future==0.18.3
# via -r requirements.in
google-api-core[grpc]==1.27.0
# via
@@ -156,23 +148,25 @@ google-cloud-core==1.7.3
# google-cloud-storage
google-cloud-error-reporting==1.4.0
# via -r requirements.in
-google-cloud-kms==1.1.0
+google-cloud-kms==2.10.0
# via -r requirements.in
google-cloud-logging==2.3.1
# via google-cloud-error-reporting
google-cloud-storage==1.41.1
# via -r requirements.in
google-crc32c==1.1.2
- # via google-resumable-media
+ # via
+ # -r requirements.in
+ # google-resumable-media
google-resumable-media==1.3.0
# via google-cloud-storage
-googleapis-common-protos[grpc]==1.53.0
+googleapis-common-protos[grpc]==1.57.0
# via
# google-api-core
# grpc-google-iam-v1
-grpc-google-iam-v1==0.11.4
+grpc-google-iam-v1==0.12.4
# via google-cloud-kms
-grpcio==1.37.1
+grpcio==1.53.2
# via
# google-api-core
# googleapis-common-protos
@@ -184,9 +178,7 @@ hiredis==2.0.0
html5lib==1.1
# via -r requirements.in
httplib2==0.19.1
- # via
- # django-postmark
- # oauth2client
+ # via django-postmark
hyperlink==21.0.0
# via
# autobahn
@@ -196,8 +188,6 @@ idna==2.10
# hyperlink
# requests
# twisted
-importlib-metadata==1.7.0
- # via -r requirements.in
incremental==21.3.0
# via twisted
jmespath==0.10.0
@@ -206,28 +196,22 @@ jmespath==0.10.0
# botocore
jsonfield==3.1.0
# via -r requirements.in
-jsonschema==4.16.0
+jsonschema==4.17.3
# via -r requirements.in
kombu==5.2.4
# via celery
-le-utils==0.1.42
+le-utils==0.2.5
# via -r requirements.in
msgpack==1.0.4
# via channels-redis
newrelic==6.2.0.156
# via -r requirements.in
-oauth2client==4.1.3
- # via -r requirements.in
packaging==20.9
# via
# google-api-core
# google-cloud-error-reporting
- # redis
-pathlib==1.0.1
- # via -r requirements.in
-pillow==9.2.0
- # via -r requirements.in
-progressbar2==3.55.0
+ # google-cloud-kms
+pillow==10.2.0
# via -r requirements.in
prometheus-client==0.10.1
# via django-prometheus
@@ -236,8 +220,9 @@ prompt-toolkit==3.0.23
proto-plus==1.18.1
# via
# google-cloud-error-reporting
+ # google-cloud-kms
# google-cloud-logging
-protobuf==3.18.3
+protobuf==3.20.3
# via
# google-api-core
# googleapis-common-protos
@@ -246,14 +231,12 @@ psycopg2-binary==2.9.5
# via -r requirements.in
pyasn1==0.4.8
# via
- # oauth2client
# pyasn1-modules
# rsa
# service-identity
pyasn1-modules==0.2.8
# via
# google-auth
- # oauth2client
# service-identity
pycparser==2.20
# via cffi
@@ -265,21 +248,19 @@ pyparsing==2.4.7
# packaging
pyrsistent==0.17.3
# via jsonschema
-python-dateutil==2.8.1
+python-dateutil==2.8.2
# via
# -r requirements.in
# botocore
python-postmark==0.5.8
# via -r requirements.in
-python-utils==2.5.6
- # via progressbar2
pytz==2022.1
# via
# celery
# django
# django-postmark
# google-api-core
-redis==4.3.4
+redis==4.5.4
# via
# -r requirements.in
# django-redis
@@ -289,12 +270,10 @@ requests==2.25.1
# google-api-core
# google-cloud-storage
rsa==4.7.2
- # via
- # google-auth
- # oauth2client
+ # via google-auth
s3transfer==0.4.2
# via boto3
-sentry-sdk==1.9.0
+sentry-sdk==1.16.0
# via -r requirements.in
service-identity==21.1.0
# via twisted
@@ -306,12 +285,8 @@ six==1.16.0
# google-auth
# google-cloud-core
# google-resumable-media
- # grpcio
# html5lib
- # oauth2client
- # progressbar2
# python-dateutil
- # python-utils
# service-identity
sqlparse==0.4.1
# via django
@@ -321,7 +296,7 @@ txaio==22.2.1
# via autobahn
typing-extensions==4.1.1
# via twisted
-urllib3==1.26.5
+urllib3==1.26.18
# via
# botocore
# requests
@@ -335,10 +310,6 @@ wcwidth==0.2.5
# via prompt-toolkit
webencodings==0.5.1
# via html5lib
-wrapt==1.14.1
- # via deprecated
-zipp==3.4.1
- # via importlib-metadata
zope-interface==5.4.0
# via twisted
diff --git a/runtime.txt b/runtime.txt
index ac3950ecf4..aab271d6a9 100644
--- a/runtime.txt
+++ b/runtime.txt
@@ -3,4 +3,4 @@
# inside Ubuntu Bionic, which is used to build images for Studio.
# We encode it here so that it can be picked up by Github's dependabot
# to manage automated package upgrades.
-python-3.9.13
+python-3.10.13
diff --git a/setup.cfg b/setup.cfg
index 9f71123935..a68141efa9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,3 +3,8 @@ max-line-length = 160
max-complexity = 10
exclude = contentcuration/contentcuration/migrations/*, contentcuration/kolibri_content/migrations/*
ignore = W503
+[kolibri:i18n]
+project = kolibri-studio
+locale_data_folder = contentcuration/locale
+webpack_config = webpack.config.js
+lang_info = contentcuration/locale/language_info.json
diff --git a/webpack.config.js b/webpack.config.js
index be0d1cc069..481f7c6b06 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -18,6 +18,7 @@ const webpack = require('webpack');
const djangoProjectDir = path.resolve('contentcuration');
const staticFilesDir = path.resolve(djangoProjectDir, 'contentcuration', 'static');
const srcDir = path.resolve(djangoProjectDir, 'contentcuration', 'frontend');
+const dummyModule = path.resolve(srcDir, 'shared', 'styles', 'modulePlaceholder.js')
const bundleOutputDir = path.resolve(staticFilesDir, 'studio');
@@ -26,7 +27,7 @@ module.exports = (env = {}) => {
const hot = env.hot;
const base = baseConfig({ mode: dev ? 'development' : 'production', hot, cache: dev });
- if (String(base.module.rules[2].test) !== String(/\.css$/)) {
+ if (String(base.module.rules[1].test) !== String(/\.css$/)) {
throw Error('Check base webpack configuration for update of location of css loader');
}
@@ -34,7 +35,32 @@ module.exports = (env = {}) => {
const rootNodeModules = path.join(rootDir, 'node_modules');
- const baseCssLoaders = base.module.rules[2].use;
+ const baseCssLoaders = base.module.rules[1].use;
+
+ const workboxPlugin = new InjectManifest({
+ swSrc: path.resolve(srcDir, 'serviceWorker/index.js'),
+ swDest: 'serviceWorker.js',
+ exclude: dev ? [/./] : [/\.map$/, /^manifest.*\.js$/]
+ });
+
+
+ if (dev) {
+ // Suppress the "InjectManifest has been called multiple times" warning by reaching into
+ // the private properties of the plugin and making sure it never ends up in the state
+ // where it makes that warning.
+ // https://github.com/GoogleChrome/workbox/blob/v6/packages/workbox-webpack-plugin/src/inject-manifest.ts#L260-L282
+ // Solution taken from here:
+ // https://github.com/GoogleChrome/workbox/issues/1790#issuecomment-1241356293
+ Object.defineProperty(workboxPlugin, "alreadyCalled", {
+ get() {
+ return false
+ },
+ set() {
+ // do nothing; the internals try to set it to true, which then results in a warning
+ // on the next run of webpack.
+ },
+ })
+ }
return merge(base, {
context: srcDir,
@@ -52,7 +78,7 @@ module.exports = (env = {}) => {
},
output: {
filename: dev ? '[name].js' : '[name]-[fullhash].js',
- chunkFilename: dev ? '[name]-[id].js' : '[name]-[id]-[fullhash].js',
+ chunkFilename: '[name]-[id]-[fullhash].js',
path: bundleOutputDir,
publicPath: dev ? 'http://127.0.0.1:4000/dist/' : '/static/studio/',
pathinfo: !dev,
@@ -91,9 +117,6 @@ module.exports = (env = {}) => {
modules: [rootNodeModules],
},
plugins: [
- new webpack.IgnorePlugin({
- resourceRegExp: /vuetify\/src\/stylus\//,
- }),
new BundleTracker({
filename: path.resolve(djangoProjectDir, 'build', 'webpack-stats.json'),
}),
@@ -117,17 +140,16 @@ module.exports = (env = {}) => {
// set the current working directory for displaying module paths
cwd: process.cwd(),
}),
- // This must be added in dev mode if you want to do dev work
- // on the service worker.
+ workboxPlugin,
].concat(
- dev
+ hot
? []
: [
- new InjectManifest({
- swSrc: path.resolve(srcDir, 'serviceWorker/index.js'),
- swDest: 'serviceWorker.js',
- }),
- ]
+ new webpack.NormalModuleReplacementPlugin(
+ /vuetify\/src\/stylus\//,
+ dummyModule
+ )
+ ]
),
stats: 'normal',
});
diff --git a/yarn.lock b/yarn.lock
index f42a49c523..6deb893ced 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,10 +2,10 @@
# yarn lockfile v1
-"@adobe/css-tools@^4.0.1":
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd"
- integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==
+"@adobe/css-tools@^4.0.1", "@adobe/css-tools@~4.3.1":
+ version "4.3.3"
+ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff"
+ integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==
"@ampproject/remapping@^2.1.0":
version "2.2.0"
@@ -15,6 +15,14 @@
"@jridgewell/gen-mapping" "^0.1.0"
"@jridgewell/trace-mapping" "^0.3.9"
+"@ampproject/remapping@^2.2.0":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
+ integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
"@apideck/better-ajv-errors@^0.3.1":
version "0.3.4"
resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.4.tgz#f89924dd4efd04a51835db7eb549a7177e0ca727"
@@ -31,12 +39,32 @@
dependencies:
"@babel/highlight" "^7.16.7"
+"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39"
+ integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
+ dependencies:
+ "@babel/highlight" "^7.18.6"
+
+"@babel/code-frame@^7.22.13":
+ version "7.22.13"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
+ integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
+ dependencies:
+ "@babel/highlight" "^7.22.13"
+ chalk "^2.4.2"
+
"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.10":
version "7.17.10"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab"
integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==
-"@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.9.6":
+"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5":
+ version "7.21.7"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc"
+ integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==
+
+"@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.3", "@babel/core@^7.7.5":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876"
integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==
@@ -57,7 +85,28 @@
json5 "^2.2.1"
semver "^6.3.0"
-"@babel/generator@^7.18.2", "@babel/generator@^7.4.0":
+"@babel/core@^7.11.6", "@babel/core@^7.20.12":
+ version "7.21.8"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4"
+ integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.21.4"
+ "@babel/generator" "^7.21.5"
+ "@babel/helper-compilation-targets" "^7.21.5"
+ "@babel/helper-module-transforms" "^7.21.5"
+ "@babel/helpers" "^7.21.5"
+ "@babel/parser" "^7.21.8"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.5"
+ "@babel/types" "^7.21.5"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.2"
+ semver "^6.3.0"
+
+"@babel/generator@^7.18.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d"
integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==
@@ -66,6 +115,26 @@
"@jridgewell/gen-mapping" "^0.3.0"
jsesc "^2.5.1"
+"@babel/generator@^7.21.5", "@babel/generator@^7.7.2":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f"
+ integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==
+ dependencies:
+ "@babel/types" "^7.21.5"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
+"@babel/generator@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
+ integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
+ dependencies:
+ "@babel/types" "^7.23.0"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
"@babel/helper-annotate-as-pure@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862"
@@ -73,6 +142,13 @@
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-annotate-as-pure@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
+ integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b"
@@ -81,6 +157,13 @@
"@babel/helper-explode-assignable-expression" "^7.16.7"
"@babel/types" "^7.16.7"
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz#817f73b6c59726ab39f6ba18c234268a519e5abb"
+ integrity sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g==
+ dependencies:
+ "@babel/types" "^7.21.5"
+
"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.10", "@babel/helper-compilation-targets@^7.18.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b"
@@ -91,6 +174,17 @@
browserslist "^4.20.2"
semver "^6.3.0"
+"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366"
+ integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==
+ dependencies:
+ "@babel/compat-data" "^7.21.5"
+ "@babel/helper-validator-option" "^7.21.0"
+ browserslist "^4.21.3"
+ lru-cache "^5.1.1"
+ semver "^6.3.0"
+
"@babel/helper-create-class-features-plugin@^7.17.12", "@babel/helper-create-class-features-plugin@^7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz#fac430912606331cb075ea8d82f9a4c145a4da19"
@@ -104,6 +198,21 @@
"@babel/helper-replace-supers" "^7.16.7"
"@babel/helper-split-export-declaration" "^7.16.7"
+"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0":
+ version "7.21.8"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz#205b26330258625ef8869672ebca1e0dee5a0f02"
+ integrity sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-member-expression-to-functions" "^7.21.5"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/helper-replace-supers" "^7.21.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ semver "^6.3.0"
+
"@babel/helper-create-regexp-features-plugin@^7.16.7", "@babel/helper-create-regexp-features-plugin@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz#bb37ca467f9694bbe55b884ae7a5cc1e0084e4fd"
@@ -112,6 +221,15 @@
"@babel/helper-annotate-as-pure" "^7.16.7"
regexpu-core "^5.0.1"
+"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5":
+ version "7.21.8"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz#a7886f61c2e29e21fd4aaeaf1e473deba6b571dc"
+ integrity sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ regexpu-core "^5.3.1"
+ semver "^6.3.0"
+
"@babel/helper-define-polyfill-provider@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665"
@@ -126,11 +244,33 @@
resolve "^1.14.2"
semver "^6.1.2"
+"@babel/helper-define-polyfill-provider@^0.3.3":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a"
+ integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.17.7"
+ "@babel/helper-plugin-utils" "^7.16.7"
+ debug "^4.1.1"
+ lodash.debounce "^4.0.8"
+ resolve "^1.14.2"
+ semver "^6.1.2"
+
"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd"
integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==
+"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba"
+ integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==
+
+"@babel/helper-environment-visitor@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
+ integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
+
"@babel/helper-explode-assignable-expression@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a"
@@ -146,6 +286,22 @@
"@babel/template" "^7.16.7"
"@babel/types" "^7.17.0"
+"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4"
+ integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==
+ dependencies:
+ "@babel/template" "^7.20.7"
+ "@babel/types" "^7.21.0"
+
+"@babel/helper-function-name@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
+ integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
+ dependencies:
+ "@babel/template" "^7.22.15"
+ "@babel/types" "^7.23.0"
+
"@babel/helper-hoist-variables@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246"
@@ -153,6 +309,20 @@
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-hoist-variables@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
+ integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-hoist-variables@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+ integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
"@babel/helper-member-expression-to-functions@^7.17.7":
version "7.17.7"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4"
@@ -160,6 +330,13 @@
dependencies:
"@babel/types" "^7.17.0"
+"@babel/helper-member-expression-to-functions@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz#3b1a009af932e586af77c1030fba9ee0bde396c0"
+ integrity sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==
+ dependencies:
+ "@babel/types" "^7.21.5"
+
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437"
@@ -167,6 +344,13 @@
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af"
+ integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==
+ dependencies:
+ "@babel/types" "^7.21.4"
+
"@babel/helper-module-transforms@^7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd"
@@ -181,6 +365,20 @@
"@babel/traverse" "^7.18.0"
"@babel/types" "^7.18.0"
+"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420"
+ integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/helper-module-imports" "^7.21.4"
+ "@babel/helper-simple-access" "^7.21.5"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.5"
+ "@babel/types" "^7.21.5"
+
"@babel/helper-optimise-call-expression@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2"
@@ -188,11 +386,23 @@
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-optimise-call-expression@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe"
+ integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96"
integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==
+"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56"
+ integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==
+
"@babel/helper-remap-async-to-generator@^7.16.8":
version "7.16.8"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3"
@@ -202,6 +412,16 @@
"@babel/helper-wrap-function" "^7.16.8"
"@babel/types" "^7.16.8"
+"@babel/helper-remap-async-to-generator@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519"
+ integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-wrap-function" "^7.18.9"
+ "@babel/types" "^7.18.9"
+
"@babel/helper-replace-supers@^7.16.7", "@babel/helper-replace-supers@^7.18.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz#41fdfcc9abaf900e18ba6e5931816d9062a7b2e0"
@@ -213,6 +433,18 @@
"@babel/traverse" "^7.18.2"
"@babel/types" "^7.18.2"
+"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz#a6ad005ba1c7d9bc2973dfde05a1bba7065dde3c"
+ integrity sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/helper-member-expression-to-functions" "^7.21.5"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.5"
+ "@babel/types" "^7.21.5"
+
"@babel/helper-simple-access@^7.17.7", "@babel/helper-simple-access@^7.18.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9"
@@ -220,6 +452,13 @@
dependencies:
"@babel/types" "^7.18.2"
+"@babel/helper-simple-access@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee"
+ integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==
+ dependencies:
+ "@babel/types" "^7.21.5"
+
"@babel/helper-skip-transparent-expression-wrappers@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09"
@@ -227,6 +466,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-skip-transparent-expression-wrappers@^7.20.0":
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684"
+ integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==
+ dependencies:
+ "@babel/types" "^7.20.0"
+
"@babel/helper-split-export-declaration@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b"
@@ -234,16 +480,55 @@
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-split-export-declaration@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075"
+ integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-split-export-declaration@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+ integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-string-parser@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
+ integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
+
+"@babel/helper-string-parser@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
+ integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
+
"@babel/helper-validator-identifier@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
+"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
+ version "7.19.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
+ integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
+
+"@babel/helper-validator-identifier@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+ integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
+
"@babel/helper-validator-option@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23"
integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==
+"@babel/helper-validator-option@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
+ integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==
+
"@babel/helper-wrap-function@^7.16.8":
version "7.16.8"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz#58afda087c4cd235de92f7ceedebca2c41274200"
@@ -254,6 +539,16 @@
"@babel/traverse" "^7.16.8"
"@babel/types" "^7.16.8"
+"@babel/helper-wrap-function@^7.18.9":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3"
+ integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==
+ dependencies:
+ "@babel/helper-function-name" "^7.19.0"
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.20.5"
+ "@babel/types" "^7.20.5"
+
"@babel/helpers@^7.18.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384"
@@ -263,6 +558,15 @@
"@babel/traverse" "^7.18.2"
"@babel/types" "^7.18.2"
+"@babel/helpers@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08"
+ integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==
+ dependencies:
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.5"
+ "@babel/types" "^7.21.5"
+
"@babel/highlight@^7.16.7":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351"
@@ -272,11 +576,39 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.0", "@babel/parser@^7.4.3":
+"@babel/highlight@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
+ integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.18.6"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
+"@babel/highlight@^7.22.13":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
+ integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.20"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.0":
version "7.18.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef"
integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==
+"@babel/parser@^7.20.7", "@babel/parser@^7.21.8":
+ version "7.21.8"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
+ integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
+
+"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
+ integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
+
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz#1dca338caaefca368639c9ffb095afbd4d420b1e"
@@ -284,6 +616,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
+ integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz#0d498ec8f0374b1e2eb54b9cb2c4c78714c77753"
@@ -293,6 +632,15 @@
"@babel/helper-skip-transparent-expression-wrappers" "^7.16.0"
"@babel/plugin-proposal-optional-chaining" "^7.17.12"
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1"
+ integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/plugin-proposal-optional-chaining" "^7.20.7"
+
"@babel/plugin-proposal-async-generator-functions@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz#094a417e31ce7e692d84bab06c8e2a607cbeef03"
@@ -302,6 +650,16 @@
"@babel/helper-remap-async-to-generator" "^7.16.8"
"@babel/plugin-syntax-async-generators" "^7.8.4"
+"@babel/plugin-proposal-async-generator-functions@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326"
+ integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-remap-async-to-generator" "^7.18.9"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+
"@babel/plugin-proposal-class-properties@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz#84f65c0cc247d46f40a6da99aadd6438315d80a4"
@@ -310,6 +668,14 @@
"@babel/helper-create-class-features-plugin" "^7.17.12"
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-proposal-class-properties@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3"
+ integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-proposal-class-static-block@^7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz#7d02253156e3c3793bdb9f2faac3a1c05f0ba710"
@@ -319,6 +685,15 @@
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-syntax-class-static-block" "^7.14.5"
+"@babel/plugin-proposal-class-static-block@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz#77bdd66fb7b605f3a61302d224bdfacf5547977d"
+ integrity sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.21.0"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+
"@babel/plugin-proposal-dynamic-import@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz#c19c897eaa46b27634a00fee9fb7d829158704b2"
@@ -327,6 +702,14 @@
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-syntax-dynamic-import" "^7.8.3"
+"@babel/plugin-proposal-dynamic-import@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94"
+ integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+
"@babel/plugin-proposal-export-namespace-from@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz#b22864ccd662db9606edb2287ea5fd1709f05378"
@@ -335,6 +718,14 @@
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+"@babel/plugin-proposal-export-namespace-from@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203"
+ integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
"@babel/plugin-proposal-json-strings@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz#f4642951792437233216d8c1af370bb0fbff4664"
@@ -343,6 +734,14 @@
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-syntax-json-strings" "^7.8.3"
+"@babel/plugin-proposal-json-strings@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b"
+ integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+
"@babel/plugin-proposal-logical-assignment-operators@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz#c64a1bcb2b0a6d0ed2ff674fd120f90ee4b88a23"
@@ -351,6 +750,14 @@
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+"@babel/plugin-proposal-logical-assignment-operators@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83"
+ integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
"@babel/plugin-proposal-nullish-coalescing-operator@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz#1e93079bbc2cbc756f6db6a1925157c4a92b94be"
@@ -359,6 +766,14 @@
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1"
+ integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+
"@babel/plugin-proposal-numeric-separator@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz#d6b69f4af63fb38b6ca2558442a7fb191236eba9"
@@ -367,6 +782,14 @@
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-syntax-numeric-separator" "^7.10.4"
+"@babel/plugin-proposal-numeric-separator@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75"
+ integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
"@babel/plugin-proposal-object-rest-spread@^7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz#79f2390c892ba2a68ec112eb0d895cfbd11155e8"
@@ -378,6 +801,17 @@
"@babel/plugin-syntax-object-rest-spread" "^7.8.3"
"@babel/plugin-transform-parameters" "^7.17.12"
+"@babel/plugin-proposal-object-rest-spread@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a"
+ integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==
+ dependencies:
+ "@babel/compat-data" "^7.20.5"
+ "@babel/helper-compilation-targets" "^7.20.7"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-transform-parameters" "^7.20.7"
+
"@babel/plugin-proposal-optional-catch-binding@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz#c623a430674ffc4ab732fd0a0ae7722b67cb74cf"
@@ -386,6 +820,14 @@
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+"@babel/plugin-proposal-optional-catch-binding@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb"
+ integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+
"@babel/plugin-proposal-optional-chaining@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz#f96949e9bacace3a9066323a5cf90cfb9de67174"
@@ -395,6 +837,15 @@
"@babel/helper-skip-transparent-expression-wrappers" "^7.16.0"
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
+"@babel/plugin-proposal-optional-chaining@^7.20.7", "@babel/plugin-proposal-optional-chaining@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea"
+ integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
"@babel/plugin-proposal-private-methods@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz#c2ca3a80beb7539289938da005ad525a038a819c"
@@ -403,6 +854,14 @@
"@babel/helper-create-class-features-plugin" "^7.17.12"
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-proposal-private-methods@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea"
+ integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-proposal-private-property-in-object@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz#b02efb7f106d544667d91ae97405a9fd8c93952d"
@@ -413,6 +872,16 @@
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+"@babel/plugin-proposal-private-property-in-object@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz#19496bd9883dd83c23c7d7fc45dcd9ad02dfa1dc"
+ integrity sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-create-class-features-plugin" "^7.21.0"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+
"@babel/plugin-proposal-unicode-property-regex@^7.17.12", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz#3dbd7a67bd7f94c8238b394da112d86aaf32ad4d"
@@ -421,6 +890,14 @@
"@babel/helper-create-regexp-features-plugin" "^7.17.12"
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-proposal-unicode-property-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e"
+ integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-syntax-async-generators@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
@@ -463,14 +940,21 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
-"@babel/plugin-syntax-import-assertions@^7.12.1", "@babel/plugin-syntax-import-assertions@^7.17.12":
+"@babel/plugin-syntax-import-assertions@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz#58096a92b11b2e4e54b24c6a0cc0e5e607abcedd"
integrity sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
-"@babel/plugin-syntax-import-meta@^7.8.3":
+"@babel/plugin-syntax-import-assertions@^7.20.0":
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4"
+ integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.19.0"
+
+"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
@@ -484,6 +968,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
+"@babel/plugin-syntax-jsx@^7.7.2":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2"
+ integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
@@ -505,7 +996,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.10.4"
-"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3":
+"@babel/plugin-syntax-object-rest-spread@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
@@ -540,6 +1031,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
+"@babel/plugin-syntax-typescript@^7.7.2":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz#2751948e9b7c6d771a8efa59340c15d4a2891ff8"
+ integrity sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+
"@babel/plugin-transform-arrow-functions@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz#dddd783b473b1b1537ef46423e3944ff24898c45"
@@ -547,6 +1045,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-arrow-functions@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929"
+ integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
"@babel/plugin-transform-async-to-generator@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz#dbe5511e6b01eee1496c944e35cdfe3f58050832"
@@ -556,6 +1061,15 @@
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/helper-remap-async-to-generator" "^7.16.8"
+"@babel/plugin-transform-async-to-generator@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354"
+ integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==
+ dependencies:
+ "@babel/helper-module-imports" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-remap-async-to-generator" "^7.18.9"
+
"@babel/plugin-transform-block-scoped-functions@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz#4d0d57d9632ef6062cdf354bb717102ee042a620"
@@ -563,6 +1077,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"
+"@babel/plugin-transform-block-scoped-functions@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8"
+ integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-block-scoping@^7.17.12":
version "7.18.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz#7988627b3e9186a13e4d7735dc9c34a056613fb9"
@@ -570,6 +1091,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-block-scoping@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02"
+ integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+
"@babel/plugin-transform-classes@^7.17.12":
version "7.18.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz#51310b812a090b846c784e47087fa6457baef814"
@@ -584,6 +1112,21 @@
"@babel/helper-split-export-declaration" "^7.16.7"
globals "^11.1.0"
+"@babel/plugin-transform-classes@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665"
+ integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-compilation-targets" "^7.20.7"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-replace-supers" "^7.20.7"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ globals "^11.1.0"
+
"@babel/plugin-transform-computed-properties@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz#bca616a83679698f3258e892ed422546e531387f"
@@ -591,6 +1134,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-computed-properties@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44"
+ integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/template" "^7.20.7"
+
"@babel/plugin-transform-destructuring@^7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz#dc4f92587e291b4daa78aa20cc2d7a63aa11e858"
@@ -598,6 +1149,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-destructuring@^7.21.3":
+ version "7.21.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401"
+ integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+
"@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz#6b2d67686fab15fb6a7fd4bd895d5982cfc81241"
@@ -606,6 +1164,14 @@
"@babel/helper-create-regexp-features-plugin" "^7.16.7"
"@babel/helper-plugin-utils" "^7.16.7"
+"@babel/plugin-transform-dotall-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8"
+ integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-duplicate-keys@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz#a09aa709a3310013f8e48e0e23bc7ace0f21477c"
@@ -613,6 +1179,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-duplicate-keys@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e"
+ integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
"@babel/plugin-transform-exponentiation-operator@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz#efa9862ef97e9e9e5f653f6ddc7b665e8536fe9b"
@@ -621,6 +1194,14 @@
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7"
"@babel/helper-plugin-utils" "^7.16.7"
+"@babel/plugin-transform-exponentiation-operator@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd"
+ integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==
+ dependencies:
+ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-for-of@^7.18.1":
version "7.18.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz#ed14b657e162b72afbbb2b4cdad277bf2bb32036"
@@ -628,6 +1209,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-for-of@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc"
+ integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
"@babel/plugin-transform-function-name@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz#5ab34375c64d61d083d7d2f05c38d90b97ec65cf"
@@ -637,6 +1225,15 @@
"@babel/helper-function-name" "^7.16.7"
"@babel/helper-plugin-utils" "^7.16.7"
+"@babel/plugin-transform-function-name@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0"
+ integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.18.9"
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.18.9"
+
"@babel/plugin-transform-literals@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz#97131fbc6bbb261487105b4b3edbf9ebf9c830ae"
@@ -644,6 +1241,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-literals@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc"
+ integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
"@babel/plugin-transform-member-expression-literals@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz#6e5dcf906ef8a098e630149d14c867dd28f92384"
@@ -651,6 +1255,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"
+"@babel/plugin-transform-member-expression-literals@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e"
+ integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-modules-amd@^7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz#7ef1002e67e36da3155edc8bf1ac9398064c02ed"
@@ -660,6 +1271,14 @@
"@babel/helper-plugin-utils" "^7.17.12"
babel-plugin-dynamic-import-node "^2.3.3"
+"@babel/plugin-transform-modules-amd@^7.20.11":
+ version "7.20.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a"
+ integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.20.11"
+ "@babel/helper-plugin-utils" "^7.20.2"
+
"@babel/plugin-transform-modules-commonjs@^7.18.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz#1aa8efa2e2a6e818b6a7f2235fceaf09bdb31e9e"
@@ -670,6 +1289,15 @@
"@babel/helper-simple-access" "^7.18.2"
babel-plugin-dynamic-import-node "^2.3.3"
+"@babel/plugin-transform-modules-commonjs@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc"
+ integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.21.5"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-simple-access" "^7.21.5"
+
"@babel/plugin-transform-modules-systemjs@^7.18.0":
version "7.18.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.4.tgz#3d6fd9868c735cce8f38d6ae3a407fb7e61e6d46"
@@ -681,6 +1309,16 @@
"@babel/helper-validator-identifier" "^7.16.7"
babel-plugin-dynamic-import-node "^2.3.3"
+"@babel/plugin-transform-modules-systemjs@^7.20.11":
+ version "7.20.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e"
+ integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==
+ dependencies:
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-module-transforms" "^7.20.11"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-validator-identifier" "^7.19.1"
+
"@babel/plugin-transform-modules-umd@^7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz#56aac64a2c2a1922341129a4597d1fd5c3ff020f"
@@ -689,6 +1327,14 @@
"@babel/helper-module-transforms" "^7.18.0"
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-modules-umd@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9"
+ integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-named-capturing-groups-regex@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz#9c4a5a5966e0434d515f2675c227fd8cc8606931"
@@ -697,6 +1343,14 @@
"@babel/helper-create-regexp-features-plugin" "^7.17.12"
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-named-capturing-groups-regex@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8"
+ integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.20.5"
+ "@babel/helper-plugin-utils" "^7.20.2"
+
"@babel/plugin-transform-new-target@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.17.12.tgz#10842cd605a620944e81ea6060e9e65c265742e3"
@@ -704,6 +1358,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-new-target@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8"
+ integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-object-super@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz#ac359cf8d32cf4354d27a46867999490b6c32a94"
@@ -712,6 +1373,14 @@
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/helper-replace-supers" "^7.16.7"
+"@babel/plugin-transform-object-super@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c"
+ integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-replace-supers" "^7.18.6"
+
"@babel/plugin-transform-parameters@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz#eb467cd9586ff5ff115a9880d6fdbd4a846b7766"
@@ -719,6 +1388,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.21.3":
+ version "7.21.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz#18fc4e797cf6d6d972cb8c411dbe8a809fa157db"
+ integrity sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+
"@babel/plugin-transform-property-literals@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz#2dadac85155436f22c696c4827730e0fe1057a55"
@@ -726,6 +1402,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"
+"@babel/plugin-transform-property-literals@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3"
+ integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-regenerator@^7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz#44274d655eb3f1af3f3a574ba819d3f48caf99d5"
@@ -734,6 +1417,14 @@
"@babel/helper-plugin-utils" "^7.17.12"
regenerator-transform "^0.15.0"
+"@babel/plugin-transform-regenerator@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e"
+ integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ regenerator-transform "^0.15.1"
+
"@babel/plugin-transform-reserved-words@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz#7dbd349f3cdffba751e817cf40ca1386732f652f"
@@ -741,16 +1432,23 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
-"@babel/plugin-transform-runtime@^7.9.6":
- version "7.18.2"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.2.tgz#04637de1e45ae8847ff14b9beead09c33d34374d"
- integrity sha512-mr1ufuRMfS52ttq+1G1PD8OJNqgcTFjq3hwn8SZ5n1x1pBhi0E36rYMdTK0TsKtApJ4lDEdfXJwtGobQMHSMPg==
+"@babel/plugin-transform-reserved-words@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a"
+ integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==
dependencies:
- "@babel/helper-module-imports" "^7.16.7"
- "@babel/helper-plugin-utils" "^7.17.12"
- babel-plugin-polyfill-corejs2 "^0.3.0"
- babel-plugin-polyfill-corejs3 "^0.5.0"
- babel-plugin-polyfill-regenerator "^0.3.0"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-runtime@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.4.tgz#2e1da21ca597a7d01fc96b699b21d8d2023191aa"
+ integrity sha512-1J4dhrw1h1PqnNNpzwxQ2UBymJUF8KuPjAAnlLwZcGhHAIqUigFW7cdK6GHoB64ubY4qXQNYknoUeks4Wz7CUA==
+ dependencies:
+ "@babel/helper-module-imports" "^7.21.4"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ babel-plugin-polyfill-corejs2 "^0.3.3"
+ babel-plugin-polyfill-corejs3 "^0.6.0"
+ babel-plugin-polyfill-regenerator "^0.4.1"
semver "^6.3.0"
"@babel/plugin-transform-shorthand-properties@^7.16.7":
@@ -760,6 +1458,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"
+"@babel/plugin-transform-shorthand-properties@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9"
+ integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-spread@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz#c112cad3064299f03ea32afed1d659223935d1f5"
@@ -768,6 +1473,14 @@
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/helper-skip-transparent-expression-wrappers" "^7.16.0"
+"@babel/plugin-transform-spread@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e"
+ integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+
"@babel/plugin-transform-sticky-regex@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz#c84741d4f4a38072b9a1e2e3fd56d359552e8660"
@@ -775,6 +1488,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"
+"@babel/plugin-transform-sticky-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc"
+ integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-template-literals@^7.18.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz#31ed6915721864847c48b656281d0098ea1add28"
@@ -782,6 +1502,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-template-literals@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e"
+ integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
"@babel/plugin-transform-typeof-symbol@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz#0f12f57ac35e98b35b4ed34829948d42bd0e6889"
@@ -789,6 +1516,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
+"@babel/plugin-transform-typeof-symbol@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0"
+ integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
"@babel/plugin-transform-unicode-escapes@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3"
@@ -796,6 +1530,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"
+"@babel/plugin-transform-unicode-escapes@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2"
+ integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
"@babel/plugin-transform-unicode-regex@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz#0f7aa4a501198976e25e82702574c34cfebe9ef2"
@@ -804,7 +1545,15 @@
"@babel/helper-create-regexp-features-plugin" "^7.16.7"
"@babel/helper-plugin-utils" "^7.16.7"
-"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.9.6":
+"@babel/plugin-transform-unicode-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca"
+ integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/preset-env@^7.11.0":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.2.tgz#f47d3000a098617926e674c945d95a28cb90977a"
integrity sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==
@@ -885,6 +1634,88 @@
core-js-compat "^3.22.1"
semver "^6.3.0"
+"@babel/preset-env@^7.21.4":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb"
+ integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg==
+ dependencies:
+ "@babel/compat-data" "^7.21.5"
+ "@babel/helper-compilation-targets" "^7.21.5"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-validator-option" "^7.21.0"
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.20.7"
+ "@babel/plugin-proposal-async-generator-functions" "^7.20.7"
+ "@babel/plugin-proposal-class-properties" "^7.18.6"
+ "@babel/plugin-proposal-class-static-block" "^7.21.0"
+ "@babel/plugin-proposal-dynamic-import" "^7.18.6"
+ "@babel/plugin-proposal-export-namespace-from" "^7.18.9"
+ "@babel/plugin-proposal-json-strings" "^7.18.6"
+ "@babel/plugin-proposal-logical-assignment-operators" "^7.20.7"
+ "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6"
+ "@babel/plugin-proposal-numeric-separator" "^7.18.6"
+ "@babel/plugin-proposal-object-rest-spread" "^7.20.7"
+ "@babel/plugin-proposal-optional-catch-binding" "^7.18.6"
+ "@babel/plugin-proposal-optional-chaining" "^7.21.0"
+ "@babel/plugin-proposal-private-methods" "^7.18.6"
+ "@babel/plugin-proposal-private-property-in-object" "^7.21.0"
+ "@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.20.0"
+ "@babel/plugin-syntax-import-meta" "^7.10.4"
+ "@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.21.5"
+ "@babel/plugin-transform-async-to-generator" "^7.20.7"
+ "@babel/plugin-transform-block-scoped-functions" "^7.18.6"
+ "@babel/plugin-transform-block-scoping" "^7.21.0"
+ "@babel/plugin-transform-classes" "^7.21.0"
+ "@babel/plugin-transform-computed-properties" "^7.21.5"
+ "@babel/plugin-transform-destructuring" "^7.21.3"
+ "@babel/plugin-transform-dotall-regex" "^7.18.6"
+ "@babel/plugin-transform-duplicate-keys" "^7.18.9"
+ "@babel/plugin-transform-exponentiation-operator" "^7.18.6"
+ "@babel/plugin-transform-for-of" "^7.21.5"
+ "@babel/plugin-transform-function-name" "^7.18.9"
+ "@babel/plugin-transform-literals" "^7.18.9"
+ "@babel/plugin-transform-member-expression-literals" "^7.18.6"
+ "@babel/plugin-transform-modules-amd" "^7.20.11"
+ "@babel/plugin-transform-modules-commonjs" "^7.21.5"
+ "@babel/plugin-transform-modules-systemjs" "^7.20.11"
+ "@babel/plugin-transform-modules-umd" "^7.18.6"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.20.5"
+ "@babel/plugin-transform-new-target" "^7.18.6"
+ "@babel/plugin-transform-object-super" "^7.18.6"
+ "@babel/plugin-transform-parameters" "^7.21.3"
+ "@babel/plugin-transform-property-literals" "^7.18.6"
+ "@babel/plugin-transform-regenerator" "^7.21.5"
+ "@babel/plugin-transform-reserved-words" "^7.18.6"
+ "@babel/plugin-transform-shorthand-properties" "^7.18.6"
+ "@babel/plugin-transform-spread" "^7.20.7"
+ "@babel/plugin-transform-sticky-regex" "^7.18.6"
+ "@babel/plugin-transform-template-literals" "^7.18.9"
+ "@babel/plugin-transform-typeof-symbol" "^7.18.9"
+ "@babel/plugin-transform-unicode-escapes" "^7.21.5"
+ "@babel/plugin-transform-unicode-regex" "^7.18.6"
+ "@babel/preset-modules" "^0.1.5"
+ "@babel/types" "^7.21.5"
+ babel-plugin-polyfill-corejs2 "^0.3.3"
+ babel-plugin-polyfill-corejs3 "^0.6.0"
+ babel-plugin-polyfill-regenerator "^0.4.1"
+ core-js-compat "^3.25.1"
+ semver "^6.3.0"
+
"@babel/preset-modules@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9"
@@ -896,14 +1727,19 @@
"@babel/types" "^7.4.4"
esutils "^2.0.2"
-"@babel/runtime@^7.11.2", "@babel/runtime@^7.14.0", "@babel/runtime@^7.16.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
- integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
+"@babel/regjsgen@^0.8.0":
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
+ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
+
+"@babel/runtime@^7.11.2", "@babel/runtime@^7.14.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+ version "7.23.4"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.4.tgz#36fa1d2b36db873d25ec631dcc4923fdc1cf2e2e"
+ integrity sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==
dependencies:
- regenerator-runtime "^0.13.4"
+ regenerator-runtime "^0.14.0"
-"@babel/template@^7.16.7", "@babel/template@^7.3.3", "@babel/template@^7.4.0":
+"@babel/template@^7.16.7", "@babel/template@^7.3.3":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==
@@ -912,23 +1748,41 @@
"@babel/parser" "^7.16.7"
"@babel/types" "^7.16.7"
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.8", "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.4.3":
- version "7.18.2"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.2.tgz#b77a52604b5cc836a9e1e08dca01cba67a12d2e8"
- integrity sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==
- dependencies:
- "@babel/code-frame" "^7.16.7"
- "@babel/generator" "^7.18.2"
- "@babel/helper-environment-visitor" "^7.18.2"
- "@babel/helper-function-name" "^7.17.9"
- "@babel/helper-hoist-variables" "^7.16.7"
- "@babel/helper-split-export-declaration" "^7.16.7"
- "@babel/parser" "^7.18.0"
- "@babel/types" "^7.18.2"
+"@babel/template@^7.18.10", "@babel/template@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
+ integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+
+"@babel/template@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
+ integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/parser" "^7.22.15"
+ "@babel/types" "^7.22.15"
+
+"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.8", "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.20.5", "@babel/traverse@^7.21.5", "@babel/traverse@^7.7.2":
+ version "7.23.2"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
+ integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/generator" "^7.23.0"
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-function-name" "^7.23.0"
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/parser" "^7.23.0"
+ "@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
-"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.0", "@babel/types@^7.4.4":
+"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.18.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354"
integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==
@@ -936,6 +1790,24 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
+ integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
+ dependencies:
+ "@babel/helper-string-parser" "^7.21.5"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
+ integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.20"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -949,16 +1821,33 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
+"@csstools/selector-specificity@^2.0.2":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016"
+ integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==
+
"@discoveryjs/json-ext@^0.5.0":
version "0.5.7"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
-"@gar/promisify@^1.0.1":
+"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
+"@isaacs/cliui@^8.0.2":
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
+ integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
+ dependencies:
+ string-width "^5.1.2"
+ string-width-cjs "npm:string-width@^4.2.0"
+ strip-ansi "^7.0.1"
+ strip-ansi-cjs "npm:strip-ansi@^6.0.1"
+ wrap-ansi "^8.1.0"
+ wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
+
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -975,15 +1864,6 @@
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
-"@jest/console@^24.7.1", "@jest/console@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0"
- integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==
- dependencies:
- "@jest/source-map" "^24.9.0"
- chalk "^2.0.1"
- slash "^2.0.0"
-
"@jest/console@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2"
@@ -996,39 +1876,17 @@
jest-util "^26.6.2"
slash "^3.0.0"
-"@jest/core@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4"
- integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==
- dependencies:
- "@jest/console" "^24.7.1"
- "@jest/reporters" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- ansi-escapes "^3.0.0"
- chalk "^2.0.1"
- exit "^0.1.2"
- graceful-fs "^4.1.15"
- jest-changed-files "^24.9.0"
- jest-config "^24.9.0"
- jest-haste-map "^24.9.0"
- jest-message-util "^24.9.0"
- jest-regex-util "^24.3.0"
- jest-resolve "^24.9.0"
- jest-resolve-dependencies "^24.9.0"
- jest-runner "^24.9.0"
- jest-runtime "^24.9.0"
- jest-snapshot "^24.9.0"
- jest-util "^24.9.0"
- jest-validate "^24.9.0"
- jest-watcher "^24.9.0"
- micromatch "^3.1.10"
- p-each-series "^1.0.0"
- realpath-native "^1.1.0"
- rimraf "^2.5.4"
- slash "^2.0.0"
- strip-ansi "^5.0.0"
+"@jest/console@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57"
+ integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==
+ dependencies:
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ jest-message-util "^29.5.0"
+ jest-util "^29.5.0"
+ slash "^3.0.0"
"@jest/core@^26.6.3":
version "26.6.3"
@@ -1064,15 +1922,39 @@
slash "^3.0.0"
strip-ansi "^6.0.0"
-"@jest/environment@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18"
- integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==
+"@jest/core@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03"
+ integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==
dependencies:
- "@jest/fake-timers" "^24.9.0"
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- jest-mock "^24.9.0"
+ "@jest/console" "^29.5.0"
+ "@jest/reporters" "^29.5.0"
+ "@jest/test-result" "^29.5.0"
+ "@jest/transform" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ ansi-escapes "^4.2.1"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ exit "^0.1.2"
+ graceful-fs "^4.2.9"
+ jest-changed-files "^29.5.0"
+ jest-config "^29.5.0"
+ jest-haste-map "^29.5.0"
+ jest-message-util "^29.5.0"
+ jest-regex-util "^29.4.3"
+ jest-resolve "^29.5.0"
+ jest-resolve-dependencies "^29.5.0"
+ jest-runner "^29.5.0"
+ jest-runtime "^29.5.0"
+ jest-snapshot "^29.5.0"
+ jest-util "^29.5.0"
+ jest-validate "^29.5.0"
+ jest-watcher "^29.5.0"
+ micromatch "^4.0.4"
+ pretty-format "^29.5.0"
+ slash "^3.0.0"
+ strip-ansi "^6.0.0"
"@jest/environment@^26.6.2":
version "26.6.2"
@@ -1084,14 +1966,30 @@
"@types/node" "*"
jest-mock "^26.6.2"
-"@jest/fake-timers@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93"
- integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==
+"@jest/environment@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65"
+ integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==
dependencies:
- "@jest/types" "^24.9.0"
- jest-message-util "^24.9.0"
- jest-mock "^24.9.0"
+ "@jest/fake-timers" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ jest-mock "^29.5.0"
+
+"@jest/expect-utils@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036"
+ integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==
+ dependencies:
+ jest-get-type "^29.4.3"
+
+"@jest/expect@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba"
+ integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==
+ dependencies:
+ expect "^29.5.0"
+ jest-snapshot "^29.5.0"
"@jest/fake-timers@^25.1.0":
version "25.5.0"
@@ -1116,6 +2014,18 @@
jest-mock "^26.6.2"
jest-util "^26.6.2"
+"@jest/fake-timers@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c"
+ integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==
+ dependencies:
+ "@jest/types" "^29.5.0"
+ "@sinonjs/fake-timers" "^10.0.2"
+ "@types/node" "*"
+ jest-message-util "^29.5.0"
+ jest-mock "^29.5.0"
+ jest-util "^29.5.0"
+
"@jest/globals@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a"
@@ -1125,32 +2035,15 @@
"@jest/types" "^26.6.2"
expect "^26.6.2"
-"@jest/reporters@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43"
- integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==
+"@jest/globals@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298"
+ integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==
dependencies:
- "@jest/environment" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- chalk "^2.0.1"
- exit "^0.1.2"
- glob "^7.1.2"
- istanbul-lib-coverage "^2.0.2"
- istanbul-lib-instrument "^3.0.1"
- istanbul-lib-report "^2.0.4"
- istanbul-lib-source-maps "^3.0.1"
- istanbul-reports "^2.2.6"
- jest-haste-map "^24.9.0"
- jest-resolve "^24.9.0"
- jest-runtime "^24.9.0"
- jest-util "^24.9.0"
- jest-worker "^24.6.0"
- node-notifier "^5.4.2"
- slash "^2.0.0"
- source-map "^0.6.0"
- string-length "^2.0.0"
+ "@jest/environment" "^29.5.0"
+ "@jest/expect" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ jest-mock "^29.5.0"
"@jest/reporters@^26.6.2":
version "26.6.2"
@@ -1184,6 +2077,36 @@
optionalDependencies:
node-notifier "^8.0.0"
+"@jest/reporters@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b"
+ integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==
+ dependencies:
+ "@bcoe/v8-coverage" "^0.2.3"
+ "@jest/console" "^29.5.0"
+ "@jest/test-result" "^29.5.0"
+ "@jest/transform" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@jridgewell/trace-mapping" "^0.3.15"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ collect-v8-coverage "^1.0.0"
+ exit "^0.1.2"
+ glob "^7.1.3"
+ graceful-fs "^4.2.9"
+ istanbul-lib-coverage "^3.0.0"
+ istanbul-lib-instrument "^5.1.0"
+ istanbul-lib-report "^3.0.0"
+ istanbul-lib-source-maps "^4.0.0"
+ istanbul-reports "^3.1.3"
+ jest-message-util "^29.5.0"
+ jest-util "^29.5.0"
+ jest-worker "^29.5.0"
+ slash "^3.0.0"
+ string-length "^4.0.1"
+ strip-ansi "^6.0.0"
+ v8-to-istanbul "^9.0.1"
+
"@jest/schemas@^29.0.0":
version "29.0.0"
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a"
@@ -1191,14 +2114,12 @@
dependencies:
"@sinclair/typebox" "^0.24.1"
-"@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714"
- integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==
+"@jest/schemas@^29.4.3":
+ version "29.4.3"
+ resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788"
+ integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==
dependencies:
- callsites "^3.0.0"
- graceful-fs "^4.1.15"
- source-map "^0.6.0"
+ "@sinclair/typebox" "^0.25.16"
"@jest/source-map@^26.6.2":
version "26.6.2"
@@ -1209,14 +2130,14 @@
graceful-fs "^4.2.4"
source-map "^0.6.0"
-"@jest/test-result@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca"
- integrity sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==
+"@jest/source-map@^29.4.3":
+ version "29.4.3"
+ resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20"
+ integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==
dependencies:
- "@jest/console" "^24.9.0"
- "@jest/types" "^24.9.0"
- "@types/istanbul-lib-coverage" "^2.0.0"
+ "@jridgewell/trace-mapping" "^0.3.15"
+ callsites "^3.0.0"
+ graceful-fs "^4.2.9"
"@jest/test-result@^26.6.2":
version "26.6.2"
@@ -1228,15 +2149,15 @@
"@types/istanbul-lib-coverage" "^2.0.0"
collect-v8-coverage "^1.0.0"
-"@jest/test-sequencer@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31"
- integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==
+"@jest/test-result@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408"
+ integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==
dependencies:
- "@jest/test-result" "^24.9.0"
- jest-haste-map "^24.9.0"
- jest-runner "^24.9.0"
- jest-runtime "^24.9.0"
+ "@jest/console" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ collect-v8-coverage "^1.0.0"
"@jest/test-sequencer@^26.6.3":
version "26.6.3"
@@ -1249,27 +2170,15 @@
jest-runner "^26.6.3"
jest-runtime "^26.6.3"
-"@jest/transform@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56"
- integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==
+"@jest/test-sequencer@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4"
+ integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==
dependencies:
- "@babel/core" "^7.1.0"
- "@jest/types" "^24.9.0"
- babel-plugin-istanbul "^5.1.0"
- chalk "^2.0.1"
- convert-source-map "^1.4.0"
- fast-json-stable-stringify "^2.0.0"
- graceful-fs "^4.1.15"
- jest-haste-map "^24.9.0"
- jest-regex-util "^24.9.0"
- jest-util "^24.9.0"
- micromatch "^3.1.10"
- pirates "^4.0.1"
- realpath-native "^1.1.0"
- slash "^2.0.0"
- source-map "^0.6.1"
- write-file-atomic "2.4.1"
+ "@jest/test-result" "^29.5.0"
+ graceful-fs "^4.2.9"
+ jest-haste-map "^29.5.0"
+ slash "^3.0.0"
"@jest/transform@^26.6.2":
version "26.6.2"
@@ -1292,35 +2201,26 @@
source-map "^0.6.1"
write-file-atomic "^3.0.0"
-"@jest/transform@^27.5.1":
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409"
- integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==
+"@jest/transform@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9"
+ integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==
dependencies:
- "@babel/core" "^7.1.0"
- "@jest/types" "^27.5.1"
+ "@babel/core" "^7.11.6"
+ "@jest/types" "^29.5.0"
+ "@jridgewell/trace-mapping" "^0.3.15"
babel-plugin-istanbul "^6.1.1"
chalk "^4.0.0"
- convert-source-map "^1.4.0"
- fast-json-stable-stringify "^2.0.0"
+ convert-source-map "^2.0.0"
+ fast-json-stable-stringify "^2.1.0"
graceful-fs "^4.2.9"
- jest-haste-map "^27.5.1"
- jest-regex-util "^27.5.1"
- jest-util "^27.5.1"
+ jest-haste-map "^29.5.0"
+ jest-regex-util "^29.4.3"
+ jest-util "^29.5.0"
micromatch "^4.0.4"
pirates "^4.0.4"
slash "^3.0.0"
- source-map "^0.6.1"
- write-file-atomic "^3.0.0"
-
-"@jest/types@^24.9.0":
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59"
- integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==
- dependencies:
- "@types/istanbul-lib-coverage" "^2.0.0"
- "@types/istanbul-reports" "^1.1.1"
- "@types/yargs" "^13.0.0"
+ write-file-atomic "^4.0.2"
"@jest/types@^25.5.0":
version "25.5.0"
@@ -1343,23 +2243,24 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
-"@jest/types@^27.5.1":
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80"
- integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==
+"@jest/types@^29.0.3":
+ version "29.0.3"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.0.3.tgz#0be78fdddb1a35aeb2041074e55b860561c8ef63"
+ integrity sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==
dependencies:
+ "@jest/schemas" "^29.0.0"
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
"@types/node" "*"
- "@types/yargs" "^16.0.0"
+ "@types/yargs" "^17.0.8"
chalk "^4.0.0"
-"@jest/types@^29.0.3":
- version "29.0.3"
- resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.0.3.tgz#0be78fdddb1a35aeb2041074e55b860561c8ef63"
- integrity sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==
+"@jest/types@^29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593"
+ integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==
dependencies:
- "@jest/schemas" "^29.0.0"
+ "@jest/schemas" "^29.4.3"
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
"@types/node" "*"
@@ -1482,7 +2383,16 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
-"@jridgewell/resolve-uri@^3.0.3":
+"@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
@@ -1505,18 +2415,18 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
-"@jridgewell/sourcemap-codec@^1.4.10":
+"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
-"@jridgewell/trace-mapping@^0.3.7":
- version "0.3.13"
- resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea"
- integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==
+"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17":
+ version "0.3.18"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
+ integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
dependencies:
- "@jridgewell/resolve-uri" "^3.0.3"
- "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/resolve-uri" "3.1.0"
+ "@jridgewell/sourcemap-codec" "1.4.14"
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
@@ -1560,6 +2470,14 @@
"@gar/promisify" "^1.0.1"
semver "^7.3.5"
+"@npmcli/fs@^2.1.0":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865"
+ integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==
+ dependencies:
+ "@gar/promisify" "^1.1.3"
+ semver "^7.3.5"
+
"@npmcli/move-file@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674"
@@ -1568,6 +2486,19 @@
mkdirp "^1.0.4"
rimraf "^3.0.2"
+"@npmcli/move-file@^2.0.0":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4"
+ integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==
+ dependencies:
+ mkdirp "^1.0.4"
+ rimraf "^3.0.2"
+
+"@pkgjs/parseargs@^0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
+ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@@ -1653,6 +2584,11 @@
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.28.tgz#15aa0b416f82c268b1573ab653e4413c965fe794"
integrity sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==
+"@sinclair/typebox@^0.25.16":
+ version "0.25.24"
+ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718"
+ integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==
+
"@sinonjs/commons@^1.7.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
@@ -1660,6 +2596,20 @@
dependencies:
type-detect "4.0.8"
+"@sinonjs/commons@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3"
+ integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==
+ dependencies:
+ type-detect "4.0.8"
+
+"@sinonjs/fake-timers@^10.0.2":
+ version "10.0.2"
+ resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c"
+ integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==
+ dependencies:
+ "@sinonjs/commons" "^2.0.0"
+
"@sinonjs/fake-timers@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
@@ -1677,16 +2627,16 @@
magic-string "^0.25.0"
string.prototype.matchall "^4.0.6"
-"@testing-library/jest-dom@^5.11.0":
- version "5.16.4"
- resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd"
- integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA==
+"@testing-library/jest-dom@^5.16.5":
+ version "5.16.5"
+ resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e"
+ integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==
dependencies:
+ "@adobe/css-tools" "^4.0.1"
"@babel/runtime" "^7.9.2"
"@types/testing-library__jest-dom" "^5.9.1"
aria-query "^5.0.0"
chalk "^3.0.0"
- css "^3.0.0"
css.escape "^1.5.1"
dom-accessibility-api "^0.5.6"
lodash "^4.17.15"
@@ -1705,12 +2655,17 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
+"@tootallnate/once@2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
+ integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
+
"@trysound/sax@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
-"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.0", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.7":
+"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.7":
version "7.1.19"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460"
integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==
@@ -1796,7 +2751,7 @@
"@types/estree" "*"
"@types/json-schema" "*"
-"@types/estree@*", "@types/estree@^0.0.51":
+"@types/estree@*":
version "0.0.51"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
@@ -1806,6 +2761,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
+"@types/estree@^1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
+ integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
+
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18":
version "4.17.28"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8"
@@ -1832,6 +2792,13 @@
dependencies:
"@types/node" "*"
+"@types/graceful-fs@^4.1.3":
+ version "4.1.6"
+ resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae"
+ integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==
+ dependencies:
+ "@types/node" "*"
+
"@types/http-proxy@^1.17.8":
version "1.17.9"
resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a"
@@ -1874,7 +2841,16 @@
jest-matcher-utils "^27.0.0"
pretty-format "^27.0.0"
-"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+"@types/jsdom@^20.0.0":
+ version "20.0.1"
+ resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808"
+ integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==
+ dependencies:
+ "@types/node" "*"
+ "@types/tough-cookie" "*"
+ parse5 "^7.0.0"
+
+"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
@@ -1896,6 +2872,11 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
+"@types/mime@*":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
+ integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==
+
"@types/mime@^1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
@@ -1936,6 +2917,11 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a"
integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==
+"@types/prettier@^2.1.5":
+ version "2.7.2"
+ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0"
+ integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==
+
"@types/qs@*":
version "6.9.7"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
@@ -1973,6 +2959,14 @@
"@types/mime" "^1"
"@types/node" "*"
+"@types/serve-static@^1.13.10":
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d"
+ integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==
+ dependencies:
+ "@types/mime" "*"
+ "@types/node" "*"
+
"@types/sockjs@^0.3.33":
version "0.3.33"
resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f"
@@ -2014,6 +3008,11 @@
dependencies:
"@types/jest" "*"
+"@types/tough-cookie@*":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
+ integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
+
"@types/trusted-types@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
@@ -2031,13 +3030,6 @@
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==
-"@types/yargs@^13.0.0":
- version "13.0.12"
- resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.12.tgz#d895a88c703b78af0465a9de88aa92c61430b092"
- integrity sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==
- dependencies:
- "@types/yargs-parser" "*"
-
"@types/yargs@^15.0.0":
version "15.0.14"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06"
@@ -2045,13 +3037,6 @@
dependencies:
"@types/yargs-parser" "*"
-"@types/yargs@^16.0.0":
- version "16.0.4"
- resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977"
- integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==
- dependencies:
- "@types/yargs-parser" "*"
-
"@types/yargs@^17.0.8":
version "17.0.11"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.11.tgz#5e10ca33e219807c0eee0f08b5efcba9b6a42c06"
@@ -2098,6 +3083,11 @@
optionalDependencies:
prettier "^1.18.2 || ^2.0.0"
+"@vue/composition-api@^1.7.2":
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/@vue/composition-api/-/composition-api-1.7.2.tgz#0b656f3ec39fefc2cf40aaa8c12426bcfeae1b44"
+ integrity sha512-M8jm9J/laYrYT02665HkZ5l2fWTK4dcVg3BsDHm/pfz+MjDYwX+9FUaZyGwEyXEDonQYRCo0H7aLgdklcIELjw==
+
"@vue/test-utils@1.0.0-beta.29":
version "1.0.0-beta.29"
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.29.tgz#c942cf25e891cf081b6a03332b4ae1ef430726f0"
@@ -2106,152 +3096,150 @@
dom-event-types "^1.0.0"
lodash "^4.17.4"
-"@vue/test-utils@^1.0.3":
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.3.0.tgz#d563decdcd9c68a7bca151d4179a2bfd6d5c3e15"
- integrity sha512-Xk2Xiyj2k5dFb8eYUKkcN9PzqZSppTlx7LaQWBbdA8tqh3jHr/KHX2/YLhNFc/xwDrgeLybqd+4ZCPJSGPIqeA==
+"@vue/test-utils@^1.3.4":
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.3.5.tgz#7beba75901c3a08a48b5657b63de02094dc40719"
+ integrity sha512-ezdlDNoxi5m/eP5Chg34AjnmNplrik4fyzB2DB9Yqa32OpywV8IvHqK9eCf+nIVsHFBejjV00agPFYRH2/D3Hg==
dependencies:
dom-event-types "^1.0.0"
lodash "^4.17.15"
pretty "^2.0.0"
-"@webassemblyjs/ast@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
- integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==
+"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c"
+ integrity sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==
dependencies:
- "@webassemblyjs/helper-numbers" "1.11.1"
- "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/helper-numbers" "1.11.5"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.5"
-"@webassemblyjs/floating-point-hex-parser@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f"
- integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==
+"@webassemblyjs/floating-point-hex-parser@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz#e85dfdb01cad16b812ff166b96806c050555f1b4"
+ integrity sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ==
-"@webassemblyjs/helper-api-error@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16"
- integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==
+"@webassemblyjs/helper-api-error@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz#1e82fa7958c681ddcf4eabef756ce09d49d442d1"
+ integrity sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA==
-"@webassemblyjs/helper-buffer@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5"
- integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==
+"@webassemblyjs/helper-buffer@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz#91381652ea95bb38bbfd270702351c0c89d69fba"
+ integrity sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg==
-"@webassemblyjs/helper-numbers@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae"
- integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==
+"@webassemblyjs/helper-numbers@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz#23380c910d56764957292839006fecbe05e135a9"
+ integrity sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA==
dependencies:
- "@webassemblyjs/floating-point-hex-parser" "1.11.1"
- "@webassemblyjs/helper-api-error" "1.11.1"
+ "@webassemblyjs/floating-point-hex-parser" "1.11.5"
+ "@webassemblyjs/helper-api-error" "1.11.5"
"@xtuc/long" "4.2.2"
-"@webassemblyjs/helper-wasm-bytecode@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1"
- integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==
+"@webassemblyjs/helper-wasm-bytecode@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz#e258a25251bc69a52ef817da3001863cc1c24b9f"
+ integrity sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA==
-"@webassemblyjs/helper-wasm-section@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a"
- integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==
+"@webassemblyjs/helper-wasm-section@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz#966e855a6fae04d5570ad4ec87fbcf29b42ba78e"
+ integrity sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA==
dependencies:
- "@webassemblyjs/ast" "1.11.1"
- "@webassemblyjs/helper-buffer" "1.11.1"
- "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
- "@webassemblyjs/wasm-gen" "1.11.1"
+ "@webassemblyjs/ast" "1.11.5"
+ "@webassemblyjs/helper-buffer" "1.11.5"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.5"
+ "@webassemblyjs/wasm-gen" "1.11.5"
-"@webassemblyjs/ieee754@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614"
- integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==
+"@webassemblyjs/ieee754@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz#b2db1b33ce9c91e34236194c2b5cba9b25ca9d60"
+ integrity sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==
dependencies:
"@xtuc/ieee754" "^1.2.0"
-"@webassemblyjs/leb128@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5"
- integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==
+"@webassemblyjs/leb128@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.5.tgz#482e44d26b6b949edf042a8525a66c649e38935a"
+ integrity sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==
dependencies:
"@xtuc/long" "4.2.2"
-"@webassemblyjs/utf8@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff"
- integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==
-
-"@webassemblyjs/wasm-edit@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6"
- integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==
- dependencies:
- "@webassemblyjs/ast" "1.11.1"
- "@webassemblyjs/helper-buffer" "1.11.1"
- "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
- "@webassemblyjs/helper-wasm-section" "1.11.1"
- "@webassemblyjs/wasm-gen" "1.11.1"
- "@webassemblyjs/wasm-opt" "1.11.1"
- "@webassemblyjs/wasm-parser" "1.11.1"
- "@webassemblyjs/wast-printer" "1.11.1"
-
-"@webassemblyjs/wasm-gen@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76"
- integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==
- dependencies:
- "@webassemblyjs/ast" "1.11.1"
- "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
- "@webassemblyjs/ieee754" "1.11.1"
- "@webassemblyjs/leb128" "1.11.1"
- "@webassemblyjs/utf8" "1.11.1"
-
-"@webassemblyjs/wasm-opt@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2"
- integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==
- dependencies:
- "@webassemblyjs/ast" "1.11.1"
- "@webassemblyjs/helper-buffer" "1.11.1"
- "@webassemblyjs/wasm-gen" "1.11.1"
- "@webassemblyjs/wasm-parser" "1.11.1"
-
-"@webassemblyjs/wasm-parser@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199"
- integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==
- dependencies:
- "@webassemblyjs/ast" "1.11.1"
- "@webassemblyjs/helper-api-error" "1.11.1"
- "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
- "@webassemblyjs/ieee754" "1.11.1"
- "@webassemblyjs/leb128" "1.11.1"
- "@webassemblyjs/utf8" "1.11.1"
-
-"@webassemblyjs/wast-printer@1.11.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0"
- integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==
- dependencies:
- "@webassemblyjs/ast" "1.11.1"
+"@webassemblyjs/utf8@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.5.tgz#83bef94856e399f3740e8df9f63bc47a987eae1a"
+ integrity sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ==
+
+"@webassemblyjs/wasm-edit@^1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz#93ee10a08037657e21c70de31c47fdad6b522b2d"
+ integrity sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.5"
+ "@webassemblyjs/helper-buffer" "1.11.5"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.5"
+ "@webassemblyjs/helper-wasm-section" "1.11.5"
+ "@webassemblyjs/wasm-gen" "1.11.5"
+ "@webassemblyjs/wasm-opt" "1.11.5"
+ "@webassemblyjs/wasm-parser" "1.11.5"
+ "@webassemblyjs/wast-printer" "1.11.5"
+
+"@webassemblyjs/wasm-gen@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz#ceb1c82b40bf0cf67a492c53381916756ef7f0b1"
+ integrity sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.5"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.5"
+ "@webassemblyjs/ieee754" "1.11.5"
+ "@webassemblyjs/leb128" "1.11.5"
+ "@webassemblyjs/utf8" "1.11.5"
+
+"@webassemblyjs/wasm-opt@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz#b52bac29681fa62487e16d3bb7f0633d5e62ca0a"
+ integrity sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.5"
+ "@webassemblyjs/helper-buffer" "1.11.5"
+ "@webassemblyjs/wasm-gen" "1.11.5"
+ "@webassemblyjs/wasm-parser" "1.11.5"
+
+"@webassemblyjs/wasm-parser@1.11.5", "@webassemblyjs/wasm-parser@^1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz#7ba0697ca74c860ea13e3ba226b29617046982e2"
+ integrity sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.5"
+ "@webassemblyjs/helper-api-error" "1.11.5"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.5"
+ "@webassemblyjs/ieee754" "1.11.5"
+ "@webassemblyjs/leb128" "1.11.5"
+ "@webassemblyjs/utf8" "1.11.5"
+
+"@webassemblyjs/wast-printer@1.11.5":
+ version "1.11.5"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz#7a5e9689043f3eca82d544d7be7a8e6373a6fa98"
+ integrity sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.5"
"@xtuc/long" "4.2.2"
-"@webpack-cli/configtest@^1.1.1":
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.1.tgz#9f53b1b7946a6efc2a749095a4f450e2932e8356"
- integrity sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==
+"@webpack-cli/configtest@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.0.1.tgz#a69720f6c9bad6aef54a8fa6ba9c3533e7ef4c7f"
+ integrity sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==
-"@webpack-cli/info@^1.4.1":
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.4.1.tgz#2360ea1710cbbb97ff156a3f0f24556e0fc1ebea"
- integrity sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==
- dependencies:
- envinfo "^7.7.3"
+"@webpack-cli/info@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.1.tgz#eed745799c910d20081e06e5177c2b2569f166c0"
+ integrity sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==
-"@webpack-cli/serve@^1.6.1":
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.1.tgz#0de2875ac31b46b6c5bb1ae0a7d7f0ba5678dffe"
- integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==
+"@webpack-cli/serve@^2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.2.tgz#10aa290e44a182c02e173a89452781b1acbc86d9"
+ integrity sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A==
"@xmldom/xmldom@^0.7.5":
version "0.7.5"
@@ -2273,7 +3261,7 @@ abab@^1.0.0:
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
integrity sha512-I+Wi+qiE2kUXyrRhNsWv6XsjUTBJjSoVSctKNBfLG5zG/Xe7Rjbxf13+vqYHNTwHaFU+FtSlVxOCTiMEVtPv0A==
-abab@^2.0.0, abab@^2.0.3, abab@^2.0.5:
+abab@^2.0.3, abab@^2.0.5, abab@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
@@ -2298,14 +3286,6 @@ acorn-globals@^1.0.4:
dependencies:
acorn "^2.1.0"
-acorn-globals@^4.1.0:
- version "4.3.4"
- resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7"
- integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==
- dependencies:
- acorn "^6.0.1"
- acorn-walk "^6.0.1"
-
acorn-globals@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45"
@@ -2314,47 +3294,50 @@ acorn-globals@^6.0.0:
acorn "^7.1.1"
acorn-walk "^7.1.1"
+acorn-globals@^7.0.0:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3"
+ integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==
+ dependencies:
+ acorn "^8.1.0"
+ acorn-walk "^8.0.2"
+
acorn-import-assertions@^1.7.6:
version "1.8.0"
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
-acorn-jsx@^5.2.0:
+acorn-jsx@^5.2.0, acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
- integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
-
-acorn-walk@^6.0.1:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
- integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn-walk@^7.1.1:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
+acorn-walk@^8.0.2:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
+ integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
+
acorn@^2.1.0, acorn@^2.4.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7"
integrity sha512-pXK8ez/pVjqFdAgBkF1YPVRacuLQ9EXBKaKWaeh58WNfMkCmZhOZzu+NtKSPD5PHmCCHheQ5cD29qM1K4QTxIg==
-acorn@^5.5.3:
- version "5.7.4"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
- integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
-
-acorn@^6.0.1:
- version "6.4.2"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
- integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
-
acorn@^7.1.1:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
-acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0:
+acorn@^8.1.0, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.1:
+ version "8.8.2"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
+ integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
+
+acorn@^8.2.4, acorn@^8.5.0:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@@ -2375,6 +3358,15 @@ agentkeepalive@^4.1.3:
depd "^1.1.2"
humanize-ms "^1.2.1"
+agentkeepalive@^4.2.1:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255"
+ integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==
+ dependencies:
+ debug "^4.1.0"
+ depd "^2.0.0"
+ humanize-ms "^1.2.1"
+
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -2402,7 +3394,7 @@ ajv-keywords@^5.0.0:
dependencies:
fast-deep-equal "^3.1.3"
-ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
+ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -2412,21 +3404,16 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
-ajv@^8.0.0, ajv@^8.0.1, ajv@^8.6.0, ajv@^8.8.0, ajv@^8.9.0:
- version "8.11.0"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
- integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
+ajv@^8.0.0, ajv@^8.0.1, ajv@^8.12.0, ajv@^8.6.0, ajv@^8.8.0:
+ version "8.12.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
+ integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
-ansi-escapes@^3.0.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
- integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
-
ansi-escapes@^4.2.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
@@ -2444,12 +3431,7 @@ ansi-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==
-ansi-regex@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
- integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
-
-ansi-regex@^4.0.0, ansi-regex@^4.1.0:
+ansi-regex@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed"
integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==
@@ -2459,6 +3441,11 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+ansi-regex@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
+ integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -2483,6 +3470,11 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
+ansi-styles@^6.1.0:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
+ integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+
any-base@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe"
@@ -2504,6 +3496,14 @@ anymatch@^3.0.3, anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
+"aphrodite@https://github.com/learningequality/aphrodite.git":
+ version "2.2.3"
+ resolved "https://github.com/learningequality/aphrodite.git#fdc8d7be8912a5cf17f74ff10f124013c52c3e32"
+ dependencies:
+ asap "^2.0.3"
+ inline-style-prefixer "^4.0.2"
+ string-hash "^1.1.3"
+
"aphrodite@https://github.com/learningequality/aphrodite/":
version "2.2.3"
resolved "https://github.com/learningequality/aphrodite/#fdc8d7be8912a5cf17f74ff10f124013c52c3e32"
@@ -2517,14 +3517,6 @@ anymatch@^3.0.3, anymatch@~3.1.2:
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
-are-we-there-yet@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
- integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
- dependencies:
- delegates "^1.0.0"
- readable-stream "^3.6.0"
-
are-we-there-yet@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz#ba20bd6b553e31d62fc8c31bd23d22b95734390d"
@@ -2540,6 +3532,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
aria-query@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c"
@@ -2560,6 +3557,14 @@ arr-union@^3.1.0:
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==
+array-buffer-byte-length@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead"
+ integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==
+ dependencies:
+ call-bind "^1.0.2"
+ is-array-buffer "^3.0.1"
+
array-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
@@ -2580,15 +3585,15 @@ array-flatten@^2.1.2:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
-array-includes@^3.1.4:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb"
- integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==
+array-includes@^3.1.6:
+ version "3.1.6"
+ resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f"
+ integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.4"
- es-abstract "^1.19.5"
- get-intrinsic "^1.1.1"
+ es-abstract "^1.20.4"
+ get-intrinsic "^1.1.3"
is-string "^1.0.7"
array-union@^2.1.0:
@@ -2601,26 +3606,25 @@ array-unique@^0.3.2:
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==
-array.prototype.flat@^1.2.5:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b"
- integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==
+array.prototype.flat@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2"
+ integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==
dependencies:
call-bind "^1.0.2"
- define-properties "^1.1.3"
- es-abstract "^1.19.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
es-shim-unscopables "^1.0.0"
-array.prototype.reduce@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f"
- integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==
+array.prototype.flatmap@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183"
+ integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==
dependencies:
call-bind "^1.0.2"
- define-properties "^1.1.3"
- es-abstract "^1.19.2"
- es-array-method-boxes-properly "^1.0.0"
- is-string "^1.0.7"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
+ es-shim-unscopables "^1.0.0"
arrify@^1.0.1:
version "1.0.1"
@@ -2644,6 +3648,16 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==
+assert@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32"
+ integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==
+ dependencies:
+ es6-object-assign "^1.1.0"
+ is-nan "^1.2.1"
+ object-is "^1.0.1"
+ util "^0.12.0"
+
assign-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
@@ -2654,6 +3668,13 @@ ast-traverse@^0.1.1:
resolved "https://registry.yarnpkg.com/ast-traverse/-/ast-traverse-0.1.1.tgz#69cf2b8386f19dcda1bb1e05d68fe359d8897de6"
integrity sha512-CPuE4BWIhJjsNMvFkrzjiBgOl56NJTuBPBkBqyRyfq/nZtx1Z1f5I+qx7G/Zt+FAOS+ABhghkEuWJrfW9Njjog==
+ast-types@0.15.2:
+ version "0.15.2"
+ resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.15.2.tgz#39ae4809393c4b16df751ee563411423e85fb49d"
+ integrity sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==
+ dependencies:
+ tslib "^2.0.1"
+
astral-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
@@ -2669,17 +3690,7 @@ async-foreach@^0.1.3:
resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
integrity sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==
-async-limiter@~1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
- integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
-
-async@1.4.2:
- version "1.4.2"
- resolved "https://registry.yarnpkg.com/async/-/async-1.4.2.tgz#6c9edcb11ced4f0dd2f2d40db0d49a109c088aab"
- integrity sha512-O4fvy4JjdS0Q8MYH4jOODxJdXGbZ61eqfXdmfFDloHSnWoggxkn/+xWbh2eQbmQ6pJNliaravcTK1iQMpW9k4Q==
-
-async@^3.2.3:
+async@3.2.3, async@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
@@ -2699,36 +3710,40 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
-autoprefixer@10.4.2:
- version "10.4.2"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b"
- integrity sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==
+autoprefixer@10.4.14:
+ version "10.4.14"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d"
+ integrity sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==
dependencies:
- browserslist "^4.19.1"
- caniuse-lite "^1.0.30001297"
- fraction.js "^4.1.2"
+ browserslist "^4.21.5"
+ caniuse-lite "^1.0.30001464"
+ fraction.js "^4.2.0"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
-autoprefixer@^9.6.1:
- version "9.8.8"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a"
- integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==
+autoprefixer@^10.4.18:
+ version "10.4.18"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.18.tgz#fcb171a3b017be7cb5d8b7a825f5aacbf2045163"
+ integrity sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==
dependencies:
- browserslist "^4.12.0"
- caniuse-lite "^1.0.30001109"
+ browserslist "^4.23.0"
+ caniuse-lite "^1.0.30001591"
+ fraction.js "^4.3.7"
normalize-range "^0.1.2"
- num2fraction "^1.2.2"
- picocolors "^0.2.1"
- postcss "^7.0.32"
- postcss-value-parser "^4.1.0"
+ picocolors "^1.0.0"
+ postcss-value-parser "^4.2.0"
autosize@^3.0.20, autosize@^3.0.21:
version "3.0.21"
resolved "https://registry.yarnpkg.com/autosize/-/autosize-3.0.21.tgz#f182f40d17757d978a139a4c9ca40c4c0e448603"
integrity sha512-xGFj5jTV4up6+fxRwtnAWiCIx/5N0tEnFn5rdhAkK1Lq2mliLMuGJgP5Bf4phck3sHGYrVKpYwugfJ61MSz9nA==
+available-typed-arrays@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
+ integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
+
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@@ -2739,13 +3754,14 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
-axios@^0.27.2:
- version "0.27.2"
- resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
- integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
+axios@^1.6.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2"
+ integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==
dependencies:
- follow-redirects "^1.14.9"
+ follow-redirects "^1.15.0"
form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
babel-code-frame@^6.26.0:
version "6.26.0"
@@ -2761,19 +3777,6 @@ babel-core@7.0.0-bridge.0:
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
-babel-jest@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54"
- integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==
- dependencies:
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- "@types/babel__core" "^7.1.0"
- babel-plugin-istanbul "^5.1.0"
- babel-preset-jest "^24.9.0"
- chalk "^2.4.2"
- slash "^2.0.0"
-
babel-jest@^26.0.1, babel-jest@^26.6.3:
version "26.6.3"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
@@ -2788,29 +3791,26 @@ babel-jest@^26.0.1, babel-jest@^26.6.3:
graceful-fs "^4.2.4"
slash "^3.0.0"
-babel-jest@^27.0.2:
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444"
- integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==
+babel-jest@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5"
+ integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==
dependencies:
- "@jest/transform" "^27.5.1"
- "@jest/types" "^27.5.1"
+ "@jest/transform" "^29.5.0"
"@types/babel__core" "^7.1.14"
babel-plugin-istanbul "^6.1.1"
- babel-preset-jest "^27.5.1"
+ babel-preset-jest "^29.5.0"
chalk "^4.0.0"
graceful-fs "^4.2.9"
slash "^3.0.0"
-babel-loader@^8.2.2:
- version "8.2.5"
- resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e"
- integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==
+babel-loader@^9.1.2:
+ version "9.1.2"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.1.2.tgz#a16a080de52d08854ee14570469905a5fc00d39c"
+ integrity sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==
dependencies:
- find-cache-dir "^3.3.1"
- loader-utils "^2.0.0"
- make-dir "^3.1.0"
- schema-utils "^2.6.5"
+ find-cache-dir "^3.3.2"
+ schema-utils "^4.0.0"
babel-messages@^6.23.0:
version "6.23.0"
@@ -2826,16 +3826,6 @@ babel-plugin-dynamic-import-node@^2.3.3:
dependencies:
object.assign "^4.1.0"
-babel-plugin-istanbul@^5.1.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854"
- integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==
- dependencies:
- "@babel/helper-plugin-utils" "^7.0.0"
- find-up "^3.0.0"
- istanbul-lib-instrument "^3.3.0"
- test-exclude "^5.2.3"
-
babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73"
@@ -2847,13 +3837,6 @@ babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1:
istanbul-lib-instrument "^5.0.4"
test-exclude "^6.0.0"
-babel-plugin-jest-hoist@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756"
- integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==
- dependencies:
- "@types/babel__traverse" "^7.0.6"
-
babel-plugin-jest-hoist@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d"
@@ -2864,14 +3847,14 @@ babel-plugin-jest-hoist@^26.6.2:
"@types/babel__core" "^7.0.0"
"@types/babel__traverse" "^7.0.6"
-babel-plugin-jest-hoist@^27.5.1:
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e"
- integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==
+babel-plugin-jest-hoist@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a"
+ integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==
dependencies:
"@babel/template" "^7.3.3"
"@babel/types" "^7.3.3"
- "@types/babel__core" "^7.0.0"
+ "@types/babel__core" "^7.1.14"
"@types/babel__traverse" "^7.0.6"
babel-plugin-polyfill-corejs2@^0.3.0:
@@ -2883,6 +3866,15 @@ babel-plugin-polyfill-corejs2@^0.3.0:
"@babel/helper-define-polyfill-provider" "^0.3.1"
semver "^6.1.1"
+babel-plugin-polyfill-corejs2@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122"
+ integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==
+ dependencies:
+ "@babel/compat-data" "^7.17.7"
+ "@babel/helper-define-polyfill-provider" "^0.3.3"
+ semver "^6.1.1"
+
babel-plugin-polyfill-corejs3@^0.5.0:
version "0.5.2"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72"
@@ -2891,6 +3883,14 @@ babel-plugin-polyfill-corejs3@^0.5.0:
"@babel/helper-define-polyfill-provider" "^0.3.1"
core-js-compat "^3.21.0"
+babel-plugin-polyfill-corejs3@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a"
+ integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.3.3"
+ core-js-compat "^3.25.1"
+
babel-plugin-polyfill-regenerator@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990"
@@ -2898,6 +3898,13 @@ babel-plugin-polyfill-regenerator@^0.3.0:
dependencies:
"@babel/helper-define-polyfill-provider" "^0.3.1"
+babel-plugin-polyfill-regenerator@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747"
+ integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.3.3"
+
babel-plugin-transform-es2015-modules-commonjs@^6.26.0:
version "6.26.2"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
@@ -2934,14 +3941,6 @@ babel-preset-current-node-syntax@^1.0.0:
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
"@babel/plugin-syntax-top-level-await" "^7.8.3"
-babel-preset-jest@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc"
- integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==
- dependencies:
- "@babel/plugin-syntax-object-rest-spread" "^7.0.0"
- babel-plugin-jest-hoist "^24.9.0"
-
babel-preset-jest@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee"
@@ -2950,12 +3949,12 @@ babel-preset-jest@^26.6.2:
babel-plugin-jest-hoist "^26.6.2"
babel-preset-current-node-syntax "^1.0.0"
-babel-preset-jest@^27.5.1:
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81"
- integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==
+babel-preset-jest@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2"
+ integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==
dependencies:
- babel-plugin-jest-hoist "^27.5.1"
+ babel-plugin-jest-hoist "^29.5.0"
babel-preset-current-node-syntax "^1.0.0"
babel-runtime@^6.22.0, babel-runtime@^6.26.0:
@@ -3102,6 +4101,24 @@ body-parser@1.20.0:
type-is "~1.6.18"
unpipe "1.0.0"
+body-parser@1.20.1:
+ version "1.20.1"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
+ integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
+ dependencies:
+ bytes "3.1.2"
+ content-type "~1.0.4"
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ on-finished "2.4.1"
+ qs "6.11.0"
+ raw-body "2.5.1"
+ type-is "~1.6.18"
+ unpipe "1.0.0"
+
bonjour-service@^1.0.11:
version "1.0.12"
resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.12.tgz#28fbd4683f5f2e36feedb833e24ba661cac960c3"
@@ -3122,7 +4139,7 @@ bowser@^1.7.3:
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a"
integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==
-brace-expansion@^1.0.0, brace-expansion@^1.1.7:
+brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
@@ -3160,44 +4177,25 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-broadcast-channel@^4.17.0:
- version "4.17.0"
- resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.17.0.tgz#599d44674b09a4e2e07af6da5d03b45ca8bffd11"
- integrity sha512-r2GSQMNgZv7eAsbdsu9xofSjc3J2diCQTPkSuyVhLBfx8fylLCVhi5KheuhuAQBJNd4pxqUyz9U6rvdnt7GZng==
- dependencies:
- "@babel/runtime" "^7.16.0"
- oblivious-set "1.1.1"
- p-queue "6.6.2"
- rimraf "3.0.2"
- unload "2.3.1"
-
browser-process-hrtime@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
-browser-resolve@^1.11.3:
- version "1.11.3"
- resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
- integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==
- dependencies:
- resolve "1.1.7"
-
-browserslist-config-kolibri@0.16.0-dev.1:
- version "0.16.0-dev.1"
- resolved "https://registry.yarnpkg.com/browserslist-config-kolibri/-/browserslist-config-kolibri-0.16.0-dev.1.tgz#66843be5f4d1fa51697e7db14912c49f7101e1e0"
- integrity sha512-7mm3OT7NTbRb8z7EsSGEr/topkfvoCrc7Nht3VZaWunhxS4dPCIBb6kwBjdrWyMVUZkSwTvSYSKoG/pOgVhBYw==
+browserslist-config-kolibri@0.16.0-dev.3:
+ version "0.16.0-dev.3"
+ resolved "https://registry.yarnpkg.com/browserslist-config-kolibri/-/browserslist-config-kolibri-0.16.0-dev.3.tgz#f73b0466575d715e012829f1110ad7489c24d32c"
+ integrity sha512-/sZ4PZtCs8pVne8DE9O6Xac8lcmvVoOG0e305egKP0zZkWiReq3yeF/Mnid3VJ2MceXJgIEPpjov6TtZjNG+WQ==
-browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.19.1, browserslist@^4.20.2, browserslist@^4.20.3:
- version "4.20.3"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf"
- integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==
+browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.20.3, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.21.5, browserslist@^4.23.0:
+ version "4.23.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab"
+ integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==
dependencies:
- caniuse-lite "^1.0.30001332"
- electron-to-chromium "^1.4.118"
- escalade "^3.1.1"
- node-releases "^2.0.3"
- picocolors "^1.0.0"
+ caniuse-lite "^1.0.30001587"
+ electron-to-chromium "^1.4.668"
+ node-releases "^2.0.14"
+ update-browserslist-db "^1.0.13"
bser@2.1.1:
version "2.1.1"
@@ -3268,6 +4266,30 @@ cacache@^15.2.0:
tar "^6.0.2"
unique-filename "^1.1.1"
+cacache@^16.1.0:
+ version "16.1.3"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e"
+ integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==
+ 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 "^2.0.0"
+
cache-base@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -3310,7 +4332,7 @@ camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-camelcase@^6.0.0:
+camelcase@^6.0.0, camelcase@^6.2.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
@@ -3325,10 +4347,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001332:
- version "1.0.30001346"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001346.tgz#e895551b46b9cc9cc9de852facd42f04839a8fbe"
- integrity sha512-q6ibZUO2t88QCIPayP/euuDREq+aMAxFE5S70PkrLh0iTDj/zEhgvJRKC2+CvXY6EWc6oQwUR48lL5vCW6jiXQ==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001591:
+ version "1.0.30001599"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce"
+ integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==
canvas-exif-orientation@^0.4.0:
version "0.4.0"
@@ -3376,7 +4398,7 @@ chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -3403,18 +4425,17 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
-check-node-version@^3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/check-node-version/-/check-node-version-3.3.0.tgz#a53d0be9c24e7fd22e029de032863d020362fe32"
- integrity sha512-OAtp7prQf+8YYKn2UB/fK1Ppb9OT+apW56atoKYUvucYLPq69VozOY0B295okBwCKymk2cictrS3qsdcZwyfzw==
+check-node-version@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/check-node-version/-/check-node-version-4.2.1.tgz#42f7e3c6e2427327b5c9080dae593d8997fe9a06"
+ integrity sha512-YYmFYHV/X7kSJhuN/QYHUu998n/TRuDe8UenM3+m5NrkiH670lb9ILqHIvBencvJc4SDh+XcbXMR4b+TtubJiw==
dependencies:
- chalk "^2.3.0"
+ chalk "^3.0.0"
map-values "^1.0.1"
minimist "^1.2.0"
object-filter "^1.0.2"
- object.assign "^4.0.4"
run-parallel "^1.1.4"
- semver "^5.0.3"
+ semver "^6.3.0"
chokidar@^3.5.3:
version "3.5.3"
@@ -3461,6 +4482,11 @@ cjs-module-lexer@^0.6.0:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f"
integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==
+cjs-module-lexer@^1.0.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
+ integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
+
class-utils@^0.3.5:
version "0.3.6"
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -3483,7 +4509,7 @@ cli-cursor@^3.1.0:
dependencies:
restore-cursor "^3.1.0"
-cli-table@^0.3.6:
+cli-table@^0.3.11:
version "0.3.11"
resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.11.tgz#ac69cdecbe81dccdba4889b9a18b7da312a9d3ee"
integrity sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==
@@ -3495,23 +4521,6 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
-cli@0.6.x:
- version "0.6.6"
- resolved "https://registry.yarnpkg.com/cli/-/cli-0.6.6.tgz#02ad44a380abf27adac5e6f0cdd7b043d74c53e3"
- integrity sha512-4H6IzYk78R+VBeJ3fH3VQejcQRkGPR+kMjA9n30srEN+YVMPJLHfoQDtLquIzcLnfrlUrVA8qSQRB9fdgWpUBw==
- dependencies:
- exit "0.1.2"
- glob "~ 3.2.1"
-
-cliui@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
- integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
- dependencies:
- string-width "^3.1.0"
- strip-ansi "^5.2.0"
- wrap-ansi "^5.1.0"
-
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
@@ -3530,6 +4539,15 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
+cliui@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
+ integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^7.0.0"
+
clone-deep@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
@@ -3539,13 +4557,6 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
-clone-regexp@^2.1.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f"
- integrity sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==
- dependencies:
- is-regexp "^2.0.0"
-
clone@2.x:
version "2.1.2"
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
@@ -3603,7 +4614,7 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-color-support@^1.1.2, color-support@^1.1.3:
+color-support@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
@@ -3630,17 +4641,17 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@2.6.0:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d"
- integrity sha512-PhbTMT+ilDXZKqH8xbvuUY2ZEQNef0Q7DKxgoEKb4ccytsdvVVJmYqR0sGbi96nxU6oGrwEIQnclpK2NBZuQlg==
+commander@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
+ integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
-commander@^2.19.0, commander@^2.20.0, commander@^2.9.0:
+commander@^2.19.0, commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
-commander@^7.0.0, commander@^7.2.0:
+commander@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
@@ -3650,6 +4661,11 @@ commander@^9.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c"
integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==
+commander@^9.1.0:
+ version "9.5.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30"
+ integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
+
common-tags@^1.8.0:
version "1.8.2"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6"
@@ -3707,19 +4723,12 @@ config-chain@^1.1.13:
ini "^1.3.4"
proto-list "~1.2.1"
-connect-history-api-fallback@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
- integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
-
-console-browserify@1.1.x:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
- integrity sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg==
- dependencies:
- date-now "^0.1.4"
+connect-history-api-fallback@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
+ integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
-console-control-strings@^1.0.0, console-control-strings@^1.1.0:
+console-control-strings@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
@@ -3750,6 +4759,11 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
dependencies:
safe-buffer "~5.1.1"
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -3780,10 +4794,17 @@ core-js-compat@^3.21.0, core-js-compat@^3.22.1:
browserslist "^4.20.3"
semver "7.0.0"
-core-js@3, core-js@^3.18.3, core-js@^3.25.1:
- version "3.25.1"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.1.tgz#5818e09de0db8956e16bf10e2a7141e931b7c69c"
- integrity sha512-sr0FY4lnO1hkQ4gLDr24K0DGnweGO1QwSj5BpfQjpSJPdqWalja4cTps29Y/PJVG/P7FYlPDkH3hO+Tr0CvDgQ==
+core-js-compat@^3.25.1:
+ version "3.30.1"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.1.tgz#961541e22db9c27fc48bfc13a3cafa8734171dfe"
+ integrity sha512-d690npR7MC6P0gq4npTl5n2VQeNAmUrJ90n+MHiKS7W2+xno4o3F5GDEuylSdi6EJ3VssibSGXOa1r3YXD3Mhw==
+ dependencies:
+ browserslist "^4.21.5"
+
+core-js@3, core-js@^3.18.3, core-js@^3.35.0:
+ version "3.35.0"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.35.0.tgz#58e651688484f83c34196ca13f099574ee53d6b4"
+ integrity sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==
core-js@^2.4.0:
version "2.6.12"
@@ -3800,7 +4821,7 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
-cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
+cosmiconfig@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d"
integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==
@@ -3811,6 +4832,16 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
path-type "^4.0.0"
yaml "^1.10.0"
+cosmiconfig@^8.1.3:
+ version "8.1.3"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.1.3.tgz#0e614a118fcc2d9e5afc2f87d53cd09931015689"
+ integrity sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==
+ dependencies:
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -3836,16 +4867,21 @@ crypto-random-string@^2.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
-css-declaration-sorter@^6.2.2:
- version "6.2.2"
- resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz#bfd2f6f50002d6a3ae779a87d3a0c5d5b10e0f02"
- integrity sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==
+css-declaration-sorter@^6.3.1:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz#630618adc21724484b3e9505bce812def44000ad"
+ integrity sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==
css-element-queries@^1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/css-element-queries/-/css-element-queries-1.2.3.tgz#e14940b1fcd4bf0da60ea4145d05742d7172e516"
integrity sha512-QK9uovYmKTsV2GXWQiMOByVNrLn2qz6m3P7vWpOR4IdD6I3iXoDw5qtgJEN3Xq7gIbdHVKvzHjdAtcl+4Arc4Q==
+css-functions-list@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.1.0.tgz#cf5b09f835ad91a00e5959bcfc627cd498e1321b"
+ integrity sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==
+
css-in-js-utils@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99"
@@ -3861,60 +4897,60 @@ css-line-break@^2.1.0:
dependencies:
utrie "^1.0.2"
-css-loader@6.5.1:
- version "6.5.1"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.5.1.tgz#0c43d4fbe0d97f699c91e9818cb585759091d1b1"
- integrity sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ==
+css-loader@6.7.3:
+ version "6.7.3"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.3.tgz#1e8799f3ccc5874fdd55461af51137fcc5befbcd"
+ integrity sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==
dependencies:
icss-utils "^5.1.0"
- postcss "^8.2.15"
+ postcss "^8.4.19"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
- postcss-value-parser "^4.1.0"
- semver "^7.3.5"
+ postcss-value-parser "^4.2.0"
+ semver "^7.3.8"
-css-minimizer-webpack-plugin@3.3.1:
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.3.1.tgz#5afc4507a4ec13dd223f043cda8953ee0bf6ecfa"
- integrity sha512-SHA7Hu/EiF0dOwdmV2+agvqYpG+ljlUa7Dvn1AVOmSH3N8KOERoaM9lGpstz9nGsoTjANGyUXdrxl/EwdMScRg==
+css-minimizer-webpack-plugin@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.0.tgz#a2c2d1bc5cc37450519e873e13d942bbe4478ef5"
+ integrity sha512-1wZ/PYvg+ZKwi5FX6YrvbB31jMAdurS+CmRQLwWCVSlfzJC85l/a6RVICqUHFa+jXyhilfnCyjafzJGbmz5tcA==
dependencies:
- cssnano "^5.0.6"
- jest-worker "^27.0.2"
- postcss "^8.3.5"
+ cssnano "^6.0.0"
+ jest-worker "^29.4.3"
+ postcss "^8.4.21"
schema-utils "^4.0.0"
- serialize-javascript "^6.0.0"
+ serialize-javascript "^6.0.1"
source-map "^0.6.1"
-css-select@^4.1.3:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
- integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==
+css-select@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
+ integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
dependencies:
boolbase "^1.0.0"
- css-what "^6.0.1"
- domhandler "^4.3.1"
- domutils "^2.8.0"
+ css-what "^6.1.0"
+ domhandler "^5.0.2"
+ domutils "^3.0.1"
nth-check "^2.0.1"
-css-tree@^1.1.2, css-tree@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
- integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
+css-tree@^2.2.1, css-tree@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20"
+ integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==
dependencies:
- mdn-data "2.0.14"
- source-map "^0.6.1"
+ mdn-data "2.0.30"
+ source-map-js "^1.0.1"
-css-tree@^2.0.1:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.1.0.tgz#170e27ccf94e7c5facb183765c25898be843d1d2"
- integrity sha512-PcysZRzToBbrpoUrZ9qfblRIRf8zbEAkU0AIpQFtgkFK0vSbzOmBCvdSAx2Zg7Xx5wiYJKUKk0NMP7kxevie/A==
+css-tree@~2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032"
+ integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==
dependencies:
- mdn-data "2.0.27"
+ mdn-data "2.0.28"
source-map-js "^1.0.1"
-css-what@^6.0.1:
+css-what@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
@@ -3934,84 +4970,67 @@ css@^2.1.0:
source-map-resolve "^0.5.2"
urix "^0.1.0"
-css@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d"
- integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==
- dependencies:
- inherits "^2.0.4"
- source-map "^0.6.1"
- source-map-resolve "^0.6.0"
-
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
-csslint@0.10.0:
- version "0.10.0"
- resolved "https://registry.yarnpkg.com/csslint/-/csslint-0.10.0.tgz#3a6a04e7565c8e9d19beb49767c7ec96e8365805"
- integrity sha512-mlD1oDw0juzD4dOthyAytPC4NsXqVZeIYAScIbgoYGY+Q7vcrhOQrH7js4JVZXcrOyKxi8ytC42ENMwO9CdnMQ==
- dependencies:
- parserlib "~0.2.2"
-
-cssnano-preset-default@^5.2.10:
- version "5.2.10"
- resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.10.tgz#6dfffe6cc3b13f3bb356a42c49a334a98700ef45"
- integrity sha512-H8TJRhTjBKVOPltp9vr9El9I+IfYsOMhmXdK0LwdvwJcxYX9oWkY7ctacWusgPWAgQq1vt/WO8v+uqpfLnM7QA==
- dependencies:
- css-declaration-sorter "^6.2.2"
- 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.5"
- 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.0"
- postcss-normalize-repeat-style "^5.1.0"
- 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.1"
- 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@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861"
- integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==
+cssnano-preset-default@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.0.1.tgz#2a93247140d214ddb9f46bc6a3562fa9177fe301"
+ integrity sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ==
+ dependencies:
+ css-declaration-sorter "^6.3.1"
+ cssnano-utils "^4.0.0"
+ postcss-calc "^9.0.0"
+ postcss-colormin "^6.0.0"
+ postcss-convert-values "^6.0.0"
+ postcss-discard-comments "^6.0.0"
+ postcss-discard-duplicates "^6.0.0"
+ postcss-discard-empty "^6.0.0"
+ postcss-discard-overridden "^6.0.0"
+ postcss-merge-longhand "^6.0.0"
+ postcss-merge-rules "^6.0.1"
+ postcss-minify-font-values "^6.0.0"
+ postcss-minify-gradients "^6.0.0"
+ postcss-minify-params "^6.0.0"
+ postcss-minify-selectors "^6.0.0"
+ postcss-normalize-charset "^6.0.0"
+ postcss-normalize-display-values "^6.0.0"
+ postcss-normalize-positions "^6.0.0"
+ postcss-normalize-repeat-style "^6.0.0"
+ postcss-normalize-string "^6.0.0"
+ postcss-normalize-timing-functions "^6.0.0"
+ postcss-normalize-unicode "^6.0.0"
+ postcss-normalize-url "^6.0.0"
+ postcss-normalize-whitespace "^6.0.0"
+ postcss-ordered-values "^6.0.0"
+ postcss-reduce-initial "^6.0.0"
+ postcss-reduce-transforms "^6.0.0"
+ postcss-svgo "^6.0.0"
+ postcss-unique-selectors "^6.0.0"
+
+cssnano-utils@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.0.tgz#d1da885ec04003ab19505ff0e62e029708d36b08"
+ integrity sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw==
-cssnano@^5.0.6:
- version "5.1.10"
- resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.10.tgz#fc6ddd9a4d7d238f320634326ed814cf0abf6e1c"
- integrity sha512-ACpnRgDg4m6CZD/+8SgnLcGCgy6DDGdkMbOawwdvVxNietTNLe/MtWcenp6qT0PRt5wzhGl6/cjMWCdhKXC9QA==
+cssnano@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.0.1.tgz#87c38c4cd47049c735ab756d7e77ac3ca855c008"
+ integrity sha512-fVO1JdJ0LSdIGJq68eIxOqFpIJrZqXUsBt8fkrBcztCQqAjQD51OhZp7tc0ImcbwXD4k7ny84QTV90nZhmqbkg==
dependencies:
- cssnano-preset-default "^5.2.10"
- lilconfig "^2.0.3"
- yaml "^1.10.2"
+ cssnano-preset-default "^6.0.1"
+ lilconfig "^2.1.0"
-csso@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529"
- integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==
+csso@^5.0.5:
+ version "5.0.5"
+ resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6"
+ integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==
dependencies:
- css-tree "^1.1.2"
+ css-tree "~2.2.0"
-cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0", "cssom@>= 0.3.2 < 0.4.0", cssom@~0.3.6:
+cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0", cssom@~0.3.6:
version "0.3.8"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
@@ -4021,6 +5040,11 @@ cssom@^0.4.4:
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==
+cssom@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
+ integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==
+
"cssstyle@>= 0.2.34 < 0.3.0":
version "0.2.37"
resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54"
@@ -4028,13 +5052,6 @@ cssom@^0.4.4:
dependencies:
cssom "0.3.x"
-cssstyle@^1.0.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1"
- integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==
- dependencies:
- cssom "0.3.x"
-
cssstyle@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
@@ -4042,10 +5059,10 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"
-csv-parse@^4.14.1:
- version "4.16.3"
- resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.16.3.tgz#7ca624d517212ebc520a36873c3478fa66efbaf7"
- integrity sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==
+csv-parse@^5.3.9:
+ version "5.3.9"
+ resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.3.9.tgz#7af0daa161b0213080bb2cdffacd9b699cf48963"
+ integrity sha512-Nuh09OE1+wG6x5Lu2T+woxlupPAnWJ6Wj9XVYK74gP646e5gDrUsrCws1zz5NbckpQ+jygnxb8xDLj3gfBxi3w==
csv-writer@^1.3.0:
version "1.6.0"
@@ -4067,15 +5084,6 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
-data-urls@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe"
- integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==
- dependencies:
- abab "^2.0.0"
- whatwg-mimetype "^2.2.0"
- whatwg-url "^7.0.0"
-
data-urls@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
@@ -4085,10 +5093,19 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
-date-now@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
- integrity sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==
+data-urls@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143"
+ integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==
+ dependencies:
+ abab "^2.0.6"
+ whatwg-mimetype "^3.0.0"
+ whatwg-url "^11.0.0"
+
+date-fns@^1.30.1:
+ version "1.30.1"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
+ integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
de-indent@^1.0.2:
version "1.0.2"
@@ -4103,14 +5120,14 @@ deasync@^0.1.15:
bindings "^1.5.0"
node-addon-api "^1.7.1"
-debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
-debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3:
+debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -4142,10 +5159,20 @@ decimal.js@^10.2.1:
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
+decimal.js@^10.4.2:
+ version "10.4.3"
+ resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
+ integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
+
decode-uri-component@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
- integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
+ integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
+
+dedent@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
+ integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==
deep-is@~0.1.3:
version "0.1.4"
@@ -4204,7 +5231,7 @@ define-property@^2.0.2:
is-descriptor "^1.0.2"
isobject "^3.0.1"
-del@^6.0.0:
+del@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a"
integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==
@@ -4228,7 +5255,7 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
-depd@2.0.0:
+depd@2.0.0, depd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
@@ -4243,17 +5270,12 @@ destroy@1.2.0:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
-detect-newline@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
- integrity sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==
-
detect-newline@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
-detect-node@2.1.0, detect-node@^2.0.4:
+detect-node@^2.0.4:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
@@ -4263,21 +5285,18 @@ dexie-observable@3.0.0-beta.11:
resolved "https://registry.yarnpkg.com/dexie-observable/-/dexie-observable-3.0.0-beta.11.tgz#2ab39e81f0fd2a8a040e014279d07b4c13c9100f"
integrity sha512-mKwSYy54Kj73Njp3Rs+GoldLxyDJK1LF7niBkZxSenGAlBCXB5RxafH2Mg6Ay0B1pLlDYvJCWH31s88/Uz0WSg==
-dexie@^3.2.2:
- version "3.2.2"
- resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.2.tgz#fa6f2a3c0d6ed0766f8d97a03720056f88fe0e01"
- integrity sha512-q5dC3HPmir2DERlX+toCBbHQXW5MsyrFqPFcovkH9N2S/UW/H3H5AWAB6iEOExeraAu+j+zRDG+zg/D7YhH0qg==
+dexie@^3.2.6:
+ version "3.2.6"
+ resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.6.tgz#c5980e4228d7e81744bb324c81459afa6604da41"
+ integrity sha512-R9VzQ27/cncQymoAeD1kfu66NUrdxcnMNXVfEoFLnQ+apVVbS4++veUcSGxft9V++zaeiLkMAREOMf8EwgR/Vw==
+ dependencies:
+ karma-safari-launcher "^1.0.0"
diacritics@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1"
integrity sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==
-diff-sequences@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
- integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
-
diff-sequences@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
@@ -4288,6 +5307,11 @@ diff-sequences@^27.5.1:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==
+diff-sequences@^29.4.3:
+ version "29.4.3"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2"
+ integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==
+
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@@ -4295,15 +5319,20 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
+dlv@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
+ integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
+
dns-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==
dns-packet@^5.2.2:
- version "5.3.1"
- resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.3.1.tgz#eb94413789daec0f0ebe2fcc230bdc9d7c91b43d"
- integrity sha512-spBwIj0TK0Ey3666GwIdWVfUpLyubpU53BTCu8iPn4r4oXd9O14Hjg3EHw3ts2oed77/SeckunUYCyRlSngqHw==
+ version "5.4.0"
+ resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b"
+ integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==
dependencies:
"@leichtgewicht/ip-codec" "^2.0.1"
@@ -4331,34 +5360,21 @@ dom-event-types@^1.0.0:
resolved "https://registry.yarnpkg.com/dom-event-types/-/dom-event-types-1.1.0.tgz#120c1f92ddea7758db1ccee0a100a33c39f4701b"
integrity sha512-jNCX+uNJ3v38BKvPbpki6j5ItVlnSqVV6vDWGS6rExzCMjsc39frLjm1n91o6YaKK6AZl0wLloItW6C6mr61BQ==
-dom-serializer@0:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
- integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
- dependencies:
- domelementtype "^2.0.1"
- entities "^2.0.0"
-
-dom-serializer@^1.0.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
- integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
+dom-serializer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+ integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
dependencies:
- domelementtype "^2.0.1"
- domhandler "^4.2.0"
- entities "^2.0.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.2"
+ entities "^4.2.0"
dom-walk@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
-domelementtype@1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
- integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
-
-domelementtype@^2.0.1, domelementtype@^2.2.0:
+domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
@@ -4377,41 +5393,38 @@ domexception@^2.0.1:
dependencies:
webidl-conversions "^5.0.0"
-domhandler@2.3:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738"
- integrity sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==
+domexception@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673"
+ integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==
dependencies:
- domelementtype "1"
+ webidl-conversions "^7.0.0"
-domhandler@^4.2.0, domhandler@^4.3.1:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
- integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
+domhandler@^5.0.2, domhandler@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+ integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
- domelementtype "^2.2.0"
+ domelementtype "^2.3.0"
dommatrix@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/dommatrix/-/dommatrix-1.0.3.tgz#e7c18e8d6f3abdd1fef3dd4aa74c4d2e620a0525"
integrity sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==
-domutils@1.5:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
- integrity sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==
+domutils@^3.0.1:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
+ integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==
dependencies:
- dom-serializer "0"
- domelementtype "1"
+ dom-serializer "^2.0.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
-domutils@^2.8.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
- integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
- dependencies:
- dom-serializer "^1.0.1"
- domelementtype "^2.2.0"
- domhandler "^4.2.0"
+eastasianwidth@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
+ integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
ecc-jsbn@~0.1.1:
version "0.1.2"
@@ -4443,10 +5456,15 @@ ejs@^3.1.6:
dependencies:
jake "^10.8.5"
-electron-to-chromium@^1.4.118:
- version "1.4.144"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.144.tgz#9a5d1f41452ecc65b686d529ae919248da44f406"
- integrity sha512-R3RV3rU1xWwFJlSClVWDvARaOk6VUO/FubHLodIASDB3Mc2dzuWvNdfOgH9bwHUTqT79u92qw60NWfwUdzAqdg==
+electron-to-chromium@^1.4.668:
+ version "1.4.711"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.711.tgz#f9fd04007878cc27ac327d5c6ce300f8b516f635"
+ integrity sha512-hRg81qzvUEibX2lDxnFlVCHACa+LtrCPIsWAxo161LDYIB3jauf57RGsMZV9mvGwE98yGH06icj3zBEoOkxd/w==
+
+emittery@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
+ integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==
emittery@^0.7.1:
version "0.7.2"
@@ -4463,6 +5481,11 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+emoji-regex@^9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
+ integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+
emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
@@ -4473,7 +5496,7 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
-encoding@^0.1.12:
+encoding@^0.1.12, encoding@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
@@ -4496,23 +5519,18 @@ enhanced-resolve@^0.9.1:
memory-fs "^0.2.0"
tapable "^0.1.8"
-enhanced-resolve@^5.9.3:
- version "5.9.3"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88"
- integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==
+enhanced-resolve@^5.13.0:
+ version "5.13.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz#26d1ecc448c02de997133217b5c1053f34a0a275"
+ integrity sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
-entities@1.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26"
- integrity sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==
-
-entities@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
- integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+entities@^4.2.0, entities@^4.4.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
env-paths@^2.2.0:
version "2.2.1"
@@ -4558,7 +5576,7 @@ error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
-es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1:
+es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.5:
version "1.20.1"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814"
integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==
@@ -4583,19 +5601,63 @@ es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19
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"
+ string.prototype.trimend "^1.0.5"
+ string.prototype.trimstart "^1.0.5"
+ unbox-primitive "^1.0.2"
+
+es-abstract@^1.20.4:
+ version "1.21.2"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff"
+ integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==
+ dependencies:
+ array-buffer-byte-length "^1.0.0"
+ available-typed-arrays "^1.0.5"
+ call-bind "^1.0.2"
+ es-set-tostringtag "^2.0.1"
+ es-to-primitive "^1.2.1"
+ function.prototype.name "^1.1.5"
+ get-intrinsic "^1.2.0"
+ get-symbol-description "^1.0.0"
+ globalthis "^1.0.3"
+ gopd "^1.0.1"
+ has "^1.0.3"
+ has-property-descriptors "^1.0.0"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+ internal-slot "^1.0.5"
+ is-array-buffer "^3.0.2"
+ is-callable "^1.2.7"
+ is-negative-zero "^2.0.2"
+ is-regex "^1.1.4"
+ is-shared-array-buffer "^1.0.2"
+ is-string "^1.0.7"
+ is-typed-array "^1.1.10"
+ is-weakref "^1.0.2"
+ object-inspect "^1.12.3"
+ object-keys "^1.1.1"
+ object.assign "^4.1.4"
+ regexp.prototype.flags "^1.4.3"
+ safe-regex-test "^1.0.0"
+ string.prototype.trim "^1.2.7"
+ string.prototype.trimend "^1.0.6"
+ string.prototype.trimstart "^1.0.6"
+ typed-array-length "^1.0.4"
unbox-primitive "^1.0.2"
+ which-typed-array "^1.1.9"
-es-array-method-boxes-properly@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e"
- integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==
+es-module-lexer@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.2.1.tgz#ba303831f63e6a394983fde2f97ad77b22324527"
+ integrity sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==
-es-module-lexer@^0.9.0:
- version "0.9.3"
- resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19"
- integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==
+es-set-tostringtag@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8"
+ integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==
+ dependencies:
+ get-intrinsic "^1.1.3"
+ has "^1.0.3"
+ has-tostringtag "^1.0.0"
es-shim-unscopables@^1.0.0:
version "1.0.0"
@@ -4613,13 +5675,14 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
-es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14:
- version "0.10.61"
- resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.61.tgz#311de37949ef86b6b0dcea894d1ffedb909d3269"
- integrity sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==
+es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@^0.10.62, es5-ext@~0.10.14:
+ version "0.10.63"
+ resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.63.tgz#9c222a63b6a332ac80b1e373b426af723b895bd6"
+ integrity sha512-hUCZd2Byj/mNKjfP9jXrdVZ62B8KuA/VoK7X8nUh5qT+AxDmcbvZz041oDVZdbIN1qW6XY9VDNwzkvKnZvK2TQ==
dependencies:
es6-iterator "^2.0.3"
es6-symbol "^3.1.3"
+ esniff "^2.0.1"
next-tick "^1.1.0"
es6-iterator@^2.0.3:
@@ -4631,6 +5694,11 @@ es6-iterator@^2.0.3:
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
+es6-object-assign@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
+ integrity sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==
+
es6-promise@^4.2.6:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
@@ -4664,7 +5732,7 @@ escape-string-regexp@^2.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
-escodegen@^1.6.1, escodegen@^1.9.1:
+escodegen@^1.6.1:
version "1.14.3"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==
@@ -4688,7 +5756,7 @@ escodegen@^2.0.0:
optionalDependencies:
source-map "~0.6.1"
-eslint-config-prettier@^6.9.0:
+eslint-config-prettier@^6.15.0:
version "6.15.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz#7f93f6cb7d45a92f1537a70ecc06366e1ac6fed9"
integrity sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==
@@ -4700,13 +5768,14 @@ eslint-config-vue@^2.0.2:
resolved "https://registry.yarnpkg.com/eslint-config-vue/-/eslint-config-vue-2.0.2.tgz#a3ab1004899e49327a94c63e24d47a396b2f4848"
integrity sha512-0k013WXCa22XTdi/ce4PQrBEsu2vt/GjViA7YPCmoAg5RFrlPUtbaCfo26Tes6mvp2M0HJzSSYZllWLtez/QdA==
-eslint-import-resolver-node@^0.3.6:
- version "0.3.6"
- resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd"
- integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==
+eslint-import-resolver-node@^0.3.7:
+ version "0.3.7"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7"
+ integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==
dependencies:
debug "^3.2.7"
- resolve "^1.20.0"
+ is-core-module "^2.11.0"
+ resolve "^1.22.1"
eslint-import-resolver-webpack@0.13.2:
version "0.13.2"
@@ -4725,31 +5794,32 @@ eslint-import-resolver-webpack@0.13.2:
resolve "^1.20.0"
semver "^5.7.1"
-eslint-module-utils@^2.7.3:
- version "2.7.3"
- resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee"
- integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==
+eslint-module-utils@^2.7.4:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49"
+ integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==
dependencies:
debug "^3.2.7"
- find-up "^2.1.0"
-eslint-plugin-import@^2.20.0:
- version "2.26.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b"
- integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==
+eslint-plugin-import@^2.26.0:
+ version "2.27.5"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65"
+ integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==
dependencies:
- array-includes "^3.1.4"
- array.prototype.flat "^1.2.5"
- debug "^2.6.9"
+ array-includes "^3.1.6"
+ array.prototype.flat "^1.3.1"
+ array.prototype.flatmap "^1.3.1"
+ debug "^3.2.7"
doctrine "^2.1.0"
- eslint-import-resolver-node "^0.3.6"
- eslint-module-utils "^2.7.3"
+ eslint-import-resolver-node "^0.3.7"
+ eslint-module-utils "^2.7.4"
has "^1.0.3"
- is-core-module "^2.8.1"
+ is-core-module "^2.11.0"
is-glob "^4.0.3"
minimatch "^3.1.2"
- object.values "^1.1.5"
- resolve "^1.22.0"
+ object.values "^1.1.6"
+ resolve "^1.22.1"
+ semver "^6.3.0"
tsconfig-paths "^3.14.1"
eslint-plugin-jest@^23.3.0:
@@ -4759,10 +5829,10 @@ eslint-plugin-jest@^23.3.0:
dependencies:
"@typescript-eslint/experimental-utils" "^2.5.0"
-eslint-plugin-kolibri@0.16.0-dev.1:
- version "0.16.0-dev.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-kolibri/-/eslint-plugin-kolibri-0.16.0-dev.1.tgz#07051d4e4873e78a7d4e10fd94f58b19b9abaf79"
- integrity sha512-EDjoIAhFaAFB5AUzdBSeYuIvguWzzJsx0YbEZlWNAyfDqAbY9C+zJ0cJ7tbVoXEE1w212q0CSGzTHxxf0OvpTQ==
+eslint-plugin-kolibri@0.16.0-dev.3:
+ version "0.16.0-dev.3"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-kolibri/-/eslint-plugin-kolibri-0.16.0-dev.3.tgz#2b0711b06987a574e3d708be62154bc230dab65c"
+ integrity sha512-y27OuOmgrWqrrcMd080QnPPji0zfw1mKYUJKg6wVuc8YTHmlo5lCJjcEVT1l4nxjlSAQi4L6w0+Eofls1VGRUQ==
dependencies:
requireindex "^1.1.0"
@@ -4803,6 +5873,11 @@ eslint-visitor-keys@^1.1.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
+eslint-visitor-keys@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc"
+ integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==
+
eslint@^6.8.0:
version "6.8.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
@@ -4846,7 +5921,26 @@ eslint@^6.8.0:
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
-espree@6.2.1, espree@^6.1.2, espree@^6.2.1:
+esniff@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308"
+ integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==
+ dependencies:
+ d "^1.0.1"
+ es5-ext "^0.10.62"
+ event-emitter "^0.3.5"
+ type "^2.7.2"
+
+espree@9.5.1:
+ version "9.5.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4"
+ integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==
+ dependencies:
+ acorn "^8.8.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^3.4.0"
+
+espree@^6.1.2, espree@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==
@@ -4855,7 +5949,7 @@ espree@6.2.1, espree@^6.1.2, espree@^6.2.1:
acorn-jsx "^5.2.0"
eslint-visitor-keys "^1.1.0"
-esprima@^4.0.0, esprima@^4.0.1:
+esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
@@ -4867,6 +5961,13 @@ esquery@^1.0.1, esquery@^1.4.0:
dependencies:
estraverse "^5.1.0"
+esquery@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
+ integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
+ dependencies:
+ estraverse "^5.1.0"
+
esrecurse@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
@@ -4907,7 +6008,7 @@ event-emitter@^0.3.5:
d "1"
es5-ext "~0.10.14"
-eventemitter3@^4.0.0, eventemitter3@^4.0.4:
+eventemitter3@^4.0.0:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
@@ -4965,19 +6066,12 @@ execa@^5.0.0:
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
-execall@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45"
- integrity sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==
- dependencies:
- clone-regexp "^2.1.0"
-
exif-parser@^0.1.12:
version "0.1.12"
resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922"
integrity sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==
-exit@0.1.2, exit@0.1.x, exit@^0.1.2:
+exit@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
@@ -4995,18 +6089,6 @@ expand-brackets@^2.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-expect@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca"
- integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==
- dependencies:
- "@jest/types" "^24.9.0"
- ansi-styles "^3.2.0"
- jest-get-type "^24.9.0"
- jest-matcher-utils "^24.9.0"
- jest-message-util "^24.9.0"
- jest-regex-util "^24.9.0"
-
expect@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417"
@@ -5019,7 +6101,18 @@ expect@^26.6.2:
jest-message-util "^26.6.2"
jest-regex-util "^26.0.0"
-express@^4.16.4, express@^4.17.3:
+expect@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7"
+ integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==
+ dependencies:
+ "@jest/expect-utils" "^29.5.0"
+ jest-get-type "^29.4.3"
+ jest-matcher-utils "^29.5.0"
+ jest-message-util "^29.5.0"
+ jest-util "^29.5.0"
+
+express@^4.17.3:
version "4.18.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf"
integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==
@@ -5056,6 +6149,43 @@ express@^4.16.4, express@^4.17.3:
utils-merge "1.0.1"
vary "~1.1.2"
+express@^4.18.2:
+ version "4.18.2"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
+ integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
+ dependencies:
+ accepts "~1.3.8"
+ array-flatten "1.1.1"
+ body-parser "1.20.1"
+ content-disposition "0.5.4"
+ content-type "~1.0.4"
+ cookie "0.5.0"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "2.0.0"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "1.2.0"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ path-to-regexp "0.1.7"
+ proxy-addr "~2.0.7"
+ qs "6.11.0"
+ range-parser "~1.2.1"
+ safe-buffer "5.2.1"
+ send "0.18.0"
+ serve-static "1.15.0"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ type-is "~1.6.18"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
ext@^1.1.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52"
@@ -5135,10 +6265,10 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
-fast-glob@^3.2.7, fast-glob@^3.2.9:
- version "3.2.11"
- resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
- integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
+fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9:
+ version "3.2.12"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
+ integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
@@ -5161,6 +6291,11 @@ fastest-levenshtein@^1.0.12:
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
+fastest-levenshtein@^1.0.16:
+ version "1.0.16"
+ resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
+ integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
+
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
@@ -5276,7 +6411,7 @@ find-babel-config@^1.1.0:
json5 "^0.5.1"
path-exists "^3.0.0"
-find-cache-dir@^3.3.1:
+find-cache-dir@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
@@ -5290,20 +6425,6 @@ find-root@^1.1.0:
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
-find-up@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
- integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==
- dependencies:
- locate-path "^2.0.0"
-
-find-up@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
- integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
- dependencies:
- locate-path "^3.0.0"
-
find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@@ -5312,14 +6433,6 @@ find-up@^4.0.0, find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
-find-up@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
- integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
- dependencies:
- locate-path "^6.0.0"
- path-exists "^4.0.0"
-
flat-cache@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
@@ -5352,10 +6465,10 @@ flush-promises@^1.0.2:
resolved "https://registry.yarnpkg.com/flush-promises/-/flush-promises-1.0.2.tgz#4948fd58f15281fed79cbafc86293d5bb09b2ced"
integrity sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==
-follow-redirects@^1.0.0, follow-redirects@^1.14.9:
- version "1.15.1"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
- integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
+follow-redirects@^1.0.0, follow-redirects@^1.15.0:
+ version "1.15.6"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
+ integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
for-each@^0.3.3:
version "0.3.3"
@@ -5369,6 +6482,14 @@ for-in@^1.0.2:
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==
+foreground-child@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d"
+ integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==
+ dependencies:
+ cross-spawn "^7.0.0"
+ signal-exit "^4.0.1"
+
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -5406,10 +6527,10 @@ forwarded@0.2.0:
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
-fraction.js@^4.1.2:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
- integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
+fraction.js@^4.2.0, fraction.js@^4.3.7:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
+ integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
fragment-cache@^0.2.1:
version "0.2.1"
@@ -5438,7 +6559,7 @@ fs-extra@^9.0.1:
jsonfile "^6.0.1"
universalify "^2.0.0"
-fs-minipass@^2.0.0:
+fs-minipass@^2.0.0, fs-minipass@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
@@ -5455,14 +6576,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
-fsevents@^1.2.7:
- version "1.2.13"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
- integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==
- dependencies:
- bindings "^1.5.0"
- nan "^2.12.1"
-
fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
@@ -5498,21 +6611,6 @@ fuzzysearch@^1.0.3:
resolved "https://registry.yarnpkg.com/fuzzysearch/-/fuzzysearch-1.0.3.tgz#dffc80f6d6b04223f2226aa79dd194231096d008"
integrity sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==
-gauge@^3.0.0:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
- integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
- dependencies:
- aproba "^1.0.3 || ^2.0.0"
- color-support "^1.1.2"
- console-control-strings "^1.0.0"
- has-unicode "^2.0.1"
- object-assign "^4.1.1"
- signal-exit "^3.0.0"
- string-width "^4.2.3"
- strip-ansi "^6.0.1"
- wide-align "^1.1.2"
-
gauge@^4.0.3:
version "4.0.4"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce"
@@ -5553,6 +6651,15 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
+get-intrinsic@^1.1.3, get-intrinsic@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
+ integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.3"
+
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
@@ -5573,11 +6680,6 @@ get-stdin@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
-get-stdin@^8.0.0:
- version "8.0.0"
- resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
- integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==
-
get-stream@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
@@ -5625,21 +6727,6 @@ gifwrap@^0.9.2:
image-q "^4.0.0"
omggif "^1.0.10"
-glob-base@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
- integrity sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==
- dependencies:
- glob-parent "^2.0.0"
- is-glob "^2.0.0"
-
-glob-parent@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
- integrity sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==
- dependencies:
- is-glob "^2.0.0"
-
glob-parent@^5.0.0, glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -5652,18 +6739,18 @@ glob-to-regexp@^0.4.1:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
-glob@5.0.15:
- version "5.0.15"
- resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
- integrity sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==
+glob@^10.2.2:
+ version "10.2.2"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.2.tgz#ce2468727de7e035e8ecf684669dc74d0526ab75"
+ integrity sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==
dependencies:
- inflight "^1.0.4"
- inherits "2"
- minimatch "2 || 3"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
+ foreground-child "^3.1.0"
+ jackspeak "^2.0.3"
+ minimatch "^9.0.0"
+ minipass "^5.0.0"
+ path-scurry "^1.7.0"
-glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -5675,13 +6762,16 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl
once "^1.3.0"
path-is-absolute "^1.0.0"
-"glob@~ 3.2.1":
- version "3.2.11"
- resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d"
- integrity sha512-hVb0zwEZwC1FXSKRPFTeOtN7AArJcJlI6ULGLtrstaswKNlrTJqAA+1lYlSUop4vjA423xlBzqfVS3iWGlqJ+g==
+glob@^8.0.1:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
+ integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
inherits "2"
- minimatch "0.3"
+ minimatch "^5.0.1"
+ once "^1.3.0"
glob@~7.1.1:
version "7.1.7"
@@ -5736,7 +6826,14 @@ globals@^9.18.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
-globby@^11.0.1, globby@^11.0.4:
+globalthis@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
+ integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
+ dependencies:
+ define-properties "^1.1.3"
+
+globby@^11.0.1, globby@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
@@ -5769,7 +6866,14 @@ gonzales-pe@^4.3.0:
dependencies:
minimist "^1.2.5"
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
+gopd@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+ dependencies:
+ get-intrinsic "^1.1.3"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@@ -5836,6 +6940,11 @@ has-property-descriptors@^1.0.0:
dependencies:
get-intrinsic "^1.1.1"
+has-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
+ integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
+
has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
@@ -5923,13 +7032,6 @@ hpack.js@^2.1.6:
readable-stream "^2.0.1"
wbuf "^1.1.0"
-html-encoding-sniffer@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
- integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==
- dependencies:
- whatwg-encoding "^1.0.1"
-
html-encoding-sniffer@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
@@ -5937,6 +7039,13 @@ html-encoding-sniffer@^2.0.1:
dependencies:
whatwg-encoding "^1.0.5"
+html-encoding-sniffer@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
+ integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==
+ dependencies:
+ whatwg-encoding "^2.0.0"
+
html-entities@^2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
@@ -5947,10 +7056,10 @@ html-escaper@^2.0.0:
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
-html-tags@^3.1.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961"
- integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==
+html-tags@^3.2.0:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
+ integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
html2canvas@^1.0.0-rc.1, html2canvas@^1.0.0-rc.5:
version "1.4.1"
@@ -5960,36 +7069,24 @@ html2canvas@^1.0.0-rc.1, html2canvas@^1.0.0-rc.5:
css-line-break "^2.1.0"
text-segmentation "^1.0.3"
-htmlhint@^0.9.13:
- version "0.9.13"
- resolved "https://registry.yarnpkg.com/htmlhint/-/htmlhint-0.9.13.tgz#08163cb1e6aa505048ebb0b41063a7ca07dc6c88"
- integrity sha512-v+MLf9ipmmeFnsOJVceg5chOwPtXszVc1weN0jBFO71Zv9zet6ae4aCpXx95+MwXyyl0FXR57CB9PkjFc0pL0w==
+htmlhint@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/htmlhint/-/htmlhint-1.1.4.tgz#cd1dac2d7e1c00da87a8fb24a80422d7734b866e"
+ integrity sha512-tSKPefhIaaWDk/vKxAOQbN+QwZmDeJCq3bZZGbJMoMQAfTjepudC+MkuT9MOBbuQI3dLLzDWbmU7fLV3JASC7Q==
dependencies:
- async "1.4.2"
- colors "1.0.3"
- commander "2.6.0"
- csslint "0.10.0"
- glob "5.0.15"
- jshint "2.8.0"
- parse-glob "3.0.4"
- strip-json-comments "1.0.4"
- xml "1.0.0"
-
-htmlparser2@3.8.x:
- version "3.8.3"
- resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068"
- integrity sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==
- dependencies:
- domelementtype "1"
- domhandler "2.3"
- domutils "1.5"
- entities "1.0"
- readable-stream "1.1"
+ async "3.2.3"
+ chalk "^4.1.2"
+ commander "^9.1.0"
+ glob "^7.2.0"
+ is-glob "^4.0.3"
+ node-fetch "^2.6.2"
+ strip-json-comments "3.1.0"
+ xml "1.0.1"
http-cache-semantics@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
- integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
+ integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
http-deceiver@^1.2.7:
version "1.2.7"
@@ -6031,6 +7128,15 @@ http-proxy-agent@^4.0.1:
agent-base "6"
debug "4"
+http-proxy-agent@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
+ integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
+ dependencies:
+ "@tootallnate/once" "2"
+ agent-base "6"
+ debug "4"
+
http-proxy-middleware@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
@@ -6060,7 +7166,7 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
-https-proxy-agent@^5.0.0:
+https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
@@ -6090,10 +7196,10 @@ hyphenate-style-name@^1.0.2:
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
-i18n-iso-countries@^7.5.0:
- version "7.5.0"
- resolved "https://registry.yarnpkg.com/i18n-iso-countries/-/i18n-iso-countries-7.5.0.tgz#74fedd72619526a195cfb2e768fe1d82eed2123f"
- integrity sha512-PtfKJNWLVhhU0KBX/8asmywjAcuyQk07mmmMwxFJcddTNBJJ1yvpY2qxVmyxbtVF+9+6eg9phgpv83XPUKU5CA==
+i18n-iso-countries@^7.7.0:
+ version "7.7.0"
+ resolved "https://registry.yarnpkg.com/i18n-iso-countries/-/i18n-iso-countries-7.7.0.tgz#33a0974d6ffbe864fb853325de3afb4717c54b52"
+ integrity sha512-07zMatrSsR1Z+cnxW//7s14Xf4v5g6U6ORHPaH8+Ox4uPqV+y46Uq78veYV8H1DKTr76EfdjSeaTxHpnaYq+bw==
dependencies:
diacritics "1.3.0"
@@ -6104,7 +7210,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.13, iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
-iconv-lite@^0.6.2:
+iconv-lite@0.6.3, iconv-lite@^0.6.2:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@@ -6173,14 +7279,6 @@ import-lazy@^4.0.0:
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153"
integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==
-import-local@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
- integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==
- dependencies:
- pkg-dir "^3.0.0"
- resolve-cwd "^2.0.0"
-
import-local@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
@@ -6212,7 +7310,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -6227,10 +7325,10 @@ ini@^1.3.4, ini@^1.3.5:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
-ini@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
- integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
+ini@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.0.tgz#3bca65a0ae224f07f8f8b3392d8c94a7f1bb007b"
+ integrity sha512-HLR38RSF2iulAzc3I/sma4CoYxQP844rPYCNfzGDOHqa/YqVlwuuZgBx6M50/X8dKgzk0cm1qRg3+47mK2N+cQ==
inline-style-prefixer@^4.0.2:
version "4.0.2"
@@ -6268,15 +7366,24 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
+internal-slot@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
+ integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==
+ dependencies:
+ get-intrinsic "^1.2.0"
+ has "^1.0.3"
+ side-channel "^1.0.4"
+
interpret@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
-interpret@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
- integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
+interpret@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4"
+ integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==
intl-format-cache@^2.0.5:
version "2.2.9"
@@ -6307,7 +7414,7 @@ intl@1.2.5:
resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde"
integrity sha512-rK0KcPHeBFBcqsErKSpvZnrOmWOj+EmDkyJ57e90YWaQNqbcivcqmKDlHEeNprDWOsKzPsh1BfSpPQdDvclHVw==
-invariant@2.2.4, invariant@^2.2.2, invariant@^2.2.4:
+invariant@2.2.4, invariant@^2.2.2:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
@@ -6315,9 +7422,9 @@ invariant@2.2.4, invariant@^2.2.2, invariant@^2.2.4:
loose-envify "^1.0.0"
ip@^1.1.5:
- version "1.1.8"
- resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48"
- integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396"
+ integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==
ipaddr.js@1.9.1:
version "1.9.1"
@@ -6343,6 +7450,23 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
+is-arguments@^1.0.4:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
+ integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
+ dependencies:
+ call-bind "^1.0.2"
+ has-tostringtag "^1.0.0"
+
+is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe"
+ integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==
+ dependencies:
+ call-bind "^1.0.2"
+ get-intrinsic "^1.2.0"
+ is-typed-array "^1.1.10"
+
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@@ -6380,6 +7504,11 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
+is-callable@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
+ integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
+
is-ci@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
@@ -6387,6 +7516,13 @@ is-ci@^2.0.0:
dependencies:
ci-info "^2.0.0"
+is-core-module@^2.11.0:
+ version "2.12.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4"
+ integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==
+ dependencies:
+ has "^1.0.3"
+
is-core-module@^2.5.0, is-core-module@^2.7.0, is-core-module@^2.8.1:
version "2.9.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
@@ -6438,11 +7574,6 @@ is-docker@^2.0.0, is-docker@^2.1.1:
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
-is-dotfile@^1.0.0:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
- integrity sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==
-
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -6455,11 +7586,6 @@ is-extendable@^1.0.1:
dependencies:
is-plain-object "^2.0.4"
-is-extglob@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
- integrity sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==
-
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -6485,12 +7611,12 @@ is-generator-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
-is-glob@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
- integrity sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==
+is-generator-function@^1.0.7:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
+ integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
dependencies:
- is-extglob "^1.0.0"
+ has-tostringtag "^1.0.0"
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
@@ -6509,6 +7635,14 @@ is-module@^1.0.0:
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==
+is-nan@^1.2.1:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
+ integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
+ dependencies:
+ call-bind "^1.0.0"
+ define-properties "^1.1.3"
+
is-negative-zero@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
@@ -6588,11 +7722,6 @@ is-regexp@^1.0.0:
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==
-is-regexp@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d"
- integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==
-
is-shared-array-buffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
@@ -6624,6 +7753,17 @@ is-symbol@^1.0.2, is-symbol@^1.0.3:
dependencies:
has-symbols "^1.0.2"
+is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9:
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f"
+ integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
+ dependencies:
+ available-typed-arrays "^1.0.5"
+ call-bind "^1.0.2"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ has-tostringtag "^1.0.0"
+
is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -6651,11 +7791,6 @@ is-windows@^1.0.2:
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-is-wsl@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
- integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==
-
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
@@ -6663,11 +7798,6 @@ is-wsl@^2.2.0:
dependencies:
is-docker "^2.0.0"
-isarray@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
- integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
-
isarray@1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -6695,29 +7825,11 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
-istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
- integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==
-
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
-istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630"
- integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==
- dependencies:
- "@babel/generator" "^7.4.0"
- "@babel/parser" "^7.4.3"
- "@babel/template" "^7.4.0"
- "@babel/traverse" "^7.4.3"
- "@babel/types" "^7.4.0"
- istanbul-lib-coverage "^2.0.5"
- semver "^6.0.0"
-
istanbul-lib-instrument@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d"
@@ -6739,14 +7851,16 @@ istanbul-lib-instrument@^5.0.4:
istanbul-lib-coverage "^3.2.0"
semver "^6.3.0"
-istanbul-lib-report@^2.0.4:
- version "2.0.8"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33"
- integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==
+istanbul-lib-instrument@^5.1.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d"
+ integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==
dependencies:
- istanbul-lib-coverage "^2.0.5"
- make-dir "^2.1.0"
- supports-color "^6.1.0"
+ "@babel/core" "^7.12.3"
+ "@babel/parser" "^7.14.7"
+ "@istanbuljs/schema" "^0.1.2"
+ istanbul-lib-coverage "^3.2.0"
+ semver "^6.3.0"
istanbul-lib-report@^3.0.0:
version "3.0.0"
@@ -6757,17 +7871,6 @@ istanbul-lib-report@^3.0.0:
make-dir "^3.0.0"
supports-color "^7.1.0"
-istanbul-lib-source-maps@^3.0.1:
- version "3.0.6"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8"
- integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==
- dependencies:
- debug "^4.1.1"
- istanbul-lib-coverage "^2.0.5"
- make-dir "^2.1.0"
- rimraf "^2.6.3"
- source-map "^0.6.1"
-
istanbul-lib-source-maps@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551"
@@ -6777,13 +7880,6 @@ istanbul-lib-source-maps@^4.0.0:
istanbul-lib-coverage "^3.0.0"
source-map "^0.6.1"
-istanbul-reports@^2.2.6:
- version "2.2.7"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931"
- integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==
- dependencies:
- html-escaper "^2.0.0"
-
istanbul-reports@^3.0.2:
version "3.1.4"
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c"
@@ -6792,6 +7888,23 @@ istanbul-reports@^3.0.2:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
+istanbul-reports@^3.1.3:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae"
+ integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==
+ dependencies:
+ html-escaper "^2.0.0"
+ istanbul-lib-report "^3.0.0"
+
+jackspeak@^2.0.3:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.0.tgz#497cbaedc902ec3f31d5d61be804d2364ff9ddad"
+ integrity sha512-r5XBrqIJfwRIjRt/Xr5fv9Wh09qyhHfKnYddDlpM+ibRR20qrYActpCAgU6U+d53EOEjzkvxPMVHSlgR7leXrQ==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+ optionalDependencies:
+ "@pkgjs/parseargs" "^0.11.0"
+
jake@^10.8.5:
version "10.8.5"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
@@ -6802,15 +7915,6 @@ jake@^10.8.5:
filelist "^1.0.1"
minimatch "^3.0.4"
-jest-changed-files@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039"
- integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==
- dependencies:
- "@jest/types" "^24.9.0"
- execa "^1.0.0"
- throat "^4.0.0"
-
jest-changed-files@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0"
@@ -6820,24 +7924,39 @@ jest-changed-files@^26.6.2:
execa "^4.0.0"
throat "^5.0.0"
-jest-cli@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af"
- integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==
+jest-changed-files@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e"
+ integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==
dependencies:
- "@jest/core" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- chalk "^2.0.1"
- exit "^0.1.2"
- import-local "^2.0.0"
- is-ci "^2.0.0"
- jest-config "^24.9.0"
- jest-util "^24.9.0"
- jest-validate "^24.9.0"
- prompts "^2.0.1"
- realpath-native "^1.1.0"
- yargs "^13.3.0"
+ execa "^5.0.0"
+ p-limit "^3.1.0"
+
+jest-circus@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317"
+ integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==
+ dependencies:
+ "@jest/environment" "^29.5.0"
+ "@jest/expect" "^29.5.0"
+ "@jest/test-result" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ co "^4.6.0"
+ dedent "^0.7.0"
+ is-generator-fn "^2.0.0"
+ jest-each "^29.5.0"
+ jest-matcher-utils "^29.5.0"
+ jest-message-util "^29.5.0"
+ jest-runtime "^29.5.0"
+ jest-snapshot "^29.5.0"
+ jest-util "^29.5.0"
+ p-limit "^3.1.0"
+ pretty-format "^29.5.0"
+ pure-rand "^6.0.0"
+ slash "^3.0.0"
+ stack-utils "^2.0.3"
jest-cli@^26.6.3:
version "26.6.3"
@@ -6858,28 +7977,23 @@ jest-cli@^26.6.3:
prompts "^2.0.1"
yargs "^15.4.1"
-jest-config@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5"
- integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==
+jest-cli@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67"
+ integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==
dependencies:
- "@babel/core" "^7.1.0"
- "@jest/test-sequencer" "^24.9.0"
- "@jest/types" "^24.9.0"
- babel-jest "^24.9.0"
- chalk "^2.0.1"
- glob "^7.1.1"
- jest-environment-jsdom "^24.9.0"
- jest-environment-node "^24.9.0"
- jest-get-type "^24.9.0"
- jest-jasmine2 "^24.9.0"
- jest-regex-util "^24.3.0"
- jest-resolve "^24.9.0"
- jest-util "^24.9.0"
- jest-validate "^24.9.0"
- micromatch "^3.1.10"
- pretty-format "^24.9.0"
- realpath-native "^1.1.0"
+ "@jest/core" "^29.5.0"
+ "@jest/test-result" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ chalk "^4.0.0"
+ exit "^0.1.2"
+ graceful-fs "^4.2.9"
+ import-local "^3.0.2"
+ jest-config "^29.5.0"
+ jest-util "^29.5.0"
+ jest-validate "^29.5.0"
+ prompts "^2.0.1"
+ yargs "^17.3.1"
jest-config@^26.6.3:
version "26.6.3"
@@ -6905,15 +8019,33 @@ jest-config@^26.6.3:
micromatch "^4.0.2"
pretty-format "^26.6.2"
-jest-diff@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
- integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
+jest-config@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da"
+ integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==
dependencies:
- chalk "^2.0.1"
- diff-sequences "^24.9.0"
- jest-get-type "^24.9.0"
- pretty-format "^24.9.0"
+ "@babel/core" "^7.11.6"
+ "@jest/test-sequencer" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ babel-jest "^29.5.0"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ deepmerge "^4.2.2"
+ glob "^7.1.3"
+ graceful-fs "^4.2.9"
+ jest-circus "^29.5.0"
+ jest-environment-node "^29.5.0"
+ jest-get-type "^29.4.3"
+ jest-regex-util "^29.4.3"
+ jest-resolve "^29.5.0"
+ jest-runner "^29.5.0"
+ jest-util "^29.5.0"
+ jest-validate "^29.5.0"
+ micromatch "^4.0.4"
+ parse-json "^5.2.0"
+ pretty-format "^29.5.0"
+ slash "^3.0.0"
+ strip-json-comments "^3.1.1"
jest-diff@^26.6.2:
version "26.6.2"
@@ -6935,12 +8067,15 @@ jest-diff@^27.5.1:
jest-get-type "^27.5.1"
pretty-format "^27.5.1"
-jest-docblock@^24.3.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2"
- integrity sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==
+jest-diff@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63"
+ integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==
dependencies:
- detect-newline "^2.1.0"
+ chalk "^4.0.0"
+ diff-sequences "^29.4.3"
+ jest-get-type "^29.4.3"
+ pretty-format "^29.5.0"
jest-docblock@^26.0.0:
version "26.0.0"
@@ -6949,16 +8084,12 @@ jest-docblock@^26.0.0:
dependencies:
detect-newline "^3.0.0"
-jest-each@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05"
- integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==
+jest-docblock@^29.4.3:
+ version "29.4.3"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8"
+ integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==
dependencies:
- "@jest/types" "^24.9.0"
- chalk "^2.0.1"
- jest-get-type "^24.9.0"
- jest-util "^24.9.0"
- pretty-format "^24.9.0"
+ detect-newline "^3.0.0"
jest-each@^26.6.2:
version "26.6.2"
@@ -6982,6 +8113,17 @@ jest-each@^29.0.3:
jest-util "^29.0.3"
pretty-format "^29.0.3"
+jest-each@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06"
+ integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==
+ dependencies:
+ "@jest/types" "^29.5.0"
+ chalk "^4.0.0"
+ jest-get-type "^29.4.3"
+ jest-util "^29.5.0"
+ pretty-format "^29.5.0"
+
jest-environment-jsdom-sixteen@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-1.0.3.tgz#e222228fac537ef15cca5ad470b19b47d9690165"
@@ -6992,18 +8134,6 @@ jest-environment-jsdom-sixteen@^1.0.3:
jest-util "^25.1.0"
jsdom "^16.2.1"
-jest-environment-jsdom@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b"
- integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==
- dependencies:
- "@jest/environment" "^24.9.0"
- "@jest/fake-timers" "^24.9.0"
- "@jest/types" "^24.9.0"
- jest-mock "^24.9.0"
- jest-util "^24.9.0"
- jsdom "^11.5.1"
-
jest-environment-jsdom@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e"
@@ -7017,16 +8147,19 @@ jest-environment-jsdom@^26.6.2:
jest-util "^26.6.2"
jsdom "^16.4.0"
-jest-environment-node@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3"
- integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==
+jest-environment-jsdom@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.5.0.tgz#cfe86ebaf1453f3297b5ff3470fbe94739c960cb"
+ integrity sha512-/KG8yEK4aN8ak56yFVdqFDzKNHgF4BAymCx2LbPNPsUshUlfAl0eX402Xm1pt+eoG9SLZEUVifqXtX8SK74KCw==
dependencies:
- "@jest/environment" "^24.9.0"
- "@jest/fake-timers" "^24.9.0"
- "@jest/types" "^24.9.0"
- jest-mock "^24.9.0"
- jest-util "^24.9.0"
+ "@jest/environment" "^29.5.0"
+ "@jest/fake-timers" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/jsdom" "^20.0.0"
+ "@types/node" "*"
+ jest-mock "^29.5.0"
+ jest-util "^29.5.0"
+ jsdom "^20.0.0"
jest-environment-node@^26.6.2:
version "26.6.2"
@@ -7040,10 +8173,17 @@ jest-environment-node@^26.6.2:
jest-mock "^26.6.2"
jest-util "^26.6.2"
-jest-get-type@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
- integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
+jest-environment-node@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967"
+ integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==
+ dependencies:
+ "@jest/environment" "^29.5.0"
+ "@jest/fake-timers" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ jest-mock "^29.5.0"
+ jest-util "^29.5.0"
jest-get-type@^26.3.0:
version "26.3.0"
@@ -7060,24 +8200,10 @@ jest-get-type@^29.0.0:
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.0.0.tgz#843f6c50a1b778f7325df1129a0fd7aa713aef80"
integrity sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==
-jest-haste-map@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
- integrity sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==
- dependencies:
- "@jest/types" "^24.9.0"
- anymatch "^2.0.0"
- fb-watchman "^2.0.0"
- graceful-fs "^4.1.15"
- invariant "^2.2.4"
- jest-serializer "^24.9.0"
- jest-util "^24.9.0"
- jest-worker "^24.9.0"
- micromatch "^3.1.10"
- sane "^4.0.3"
- walker "^1.0.7"
- optionalDependencies:
- fsevents "^1.2.7"
+jest-get-type@^29.4.3:
+ version "29.4.3"
+ resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5"
+ integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==
jest-haste-map@^26.6.2:
version "26.6.2"
@@ -7100,48 +8226,25 @@ jest-haste-map@^26.6.2:
optionalDependencies:
fsevents "^2.1.2"
-jest-haste-map@^27.5.1:
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f"
- integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==
+jest-haste-map@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de"
+ integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==
dependencies:
- "@jest/types" "^27.5.1"
- "@types/graceful-fs" "^4.1.2"
+ "@jest/types" "^29.5.0"
+ "@types/graceful-fs" "^4.1.3"
"@types/node" "*"
anymatch "^3.0.3"
fb-watchman "^2.0.0"
graceful-fs "^4.2.9"
- jest-regex-util "^27.5.1"
- jest-serializer "^27.5.1"
- jest-util "^27.5.1"
- jest-worker "^27.5.1"
+ jest-regex-util "^29.4.3"
+ jest-util "^29.5.0"
+ jest-worker "^29.5.0"
micromatch "^4.0.4"
- walker "^1.0.7"
+ walker "^1.0.8"
optionalDependencies:
fsevents "^2.3.2"
-jest-jasmine2@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0"
- integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==
- dependencies:
- "@babel/traverse" "^7.1.0"
- "@jest/environment" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- chalk "^2.0.1"
- co "^4.6.0"
- expect "^24.9.0"
- is-generator-fn "^2.0.0"
- jest-each "^24.9.0"
- jest-matcher-utils "^24.9.0"
- jest-message-util "^24.9.0"
- jest-runtime "^24.9.0"
- jest-snapshot "^24.9.0"
- jest-util "^24.9.0"
- pretty-format "^24.9.0"
- throat "^4.0.0"
-
jest-jasmine2@^26.6.3:
version "26.6.3"
resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd"
@@ -7166,14 +8269,6 @@ jest-jasmine2@^26.6.3:
pretty-format "^26.6.2"
throat "^5.0.0"
-jest-leak-detector@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a"
- integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==
- dependencies:
- jest-get-type "^24.9.0"
- pretty-format "^24.9.0"
-
jest-leak-detector@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af"
@@ -7182,15 +8277,13 @@ jest-leak-detector@^26.6.2:
jest-get-type "^26.3.0"
pretty-format "^26.6.2"
-jest-matcher-utils@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
- integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==
+jest-leak-detector@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c"
+ integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==
dependencies:
- chalk "^2.0.1"
- jest-diff "^24.9.0"
- jest-get-type "^24.9.0"
- pretty-format "^24.9.0"
+ jest-get-type "^29.4.3"
+ pretty-format "^29.5.0"
jest-matcher-utils@^26.6.2:
version "26.6.2"
@@ -7212,19 +8305,15 @@ jest-matcher-utils@^27.0.0:
jest-get-type "^27.5.1"
pretty-format "^27.5.1"
-jest-message-util@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3"
- integrity sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==
+jest-matcher-utils@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5"
+ integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==
dependencies:
- "@babel/code-frame" "^7.0.0"
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- "@types/stack-utils" "^1.0.1"
- chalk "^2.0.1"
- micromatch "^3.1.10"
- slash "^2.0.0"
- stack-utils "^1.0.1"
+ chalk "^4.0.0"
+ jest-diff "^29.5.0"
+ jest-get-type "^29.4.3"
+ pretty-format "^29.5.0"
jest-message-util@^25.5.0:
version "25.5.0"
@@ -7255,12 +8344,20 @@ jest-message-util@^26.6.2:
slash "^3.0.0"
stack-utils "^2.0.2"
-jest-mock@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6"
- integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==
+jest-message-util@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e"
+ integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==
dependencies:
- "@jest/types" "^24.9.0"
+ "@babel/code-frame" "^7.12.13"
+ "@jest/types" "^29.5.0"
+ "@types/stack-utils" "^2.0.0"
+ chalk "^4.0.0"
+ graceful-fs "^4.2.9"
+ micromatch "^4.0.4"
+ pretty-format "^29.5.0"
+ slash "^3.0.0"
+ stack-utils "^2.0.3"
jest-mock@^25.1.0, jest-mock@^25.5.0:
version "25.5.0"
@@ -7277,34 +8374,29 @@ jest-mock@^26.6.2:
"@jest/types" "^26.6.2"
"@types/node" "*"
-jest-pnp-resolver@^1.2.1, jest-pnp-resolver@^1.2.2:
+jest-mock@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed"
+ integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==
+ dependencies:
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ jest-util "^29.5.0"
+
+jest-pnp-resolver@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c"
integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==
-jest-regex-util@^24.3.0, jest-regex-util@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636"
- integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==
-
jest-regex-util@^26.0.0:
version "26.0.0"
resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28"
integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==
-jest-regex-util@^27.5.1:
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95"
- integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==
-
-jest-resolve-dependencies@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab"
- integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==
- dependencies:
- "@jest/types" "^24.9.0"
- jest-regex-util "^24.3.0"
- jest-snapshot "^24.9.0"
+jest-regex-util@^29.4.3:
+ version "29.4.3"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8"
+ integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==
jest-resolve-dependencies@^26.6.3:
version "26.6.3"
@@ -7315,16 +8407,13 @@ jest-resolve-dependencies@^26.6.3:
jest-regex-util "^26.0.0"
jest-snapshot "^26.6.2"
-jest-resolve@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.9.0.tgz#dff04c7687af34c4dd7e524892d9cf77e5d17321"
- integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==
+jest-resolve-dependencies@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4"
+ integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==
dependencies:
- "@jest/types" "^24.9.0"
- browser-resolve "^1.11.3"
- chalk "^2.0.1"
- jest-pnp-resolver "^1.2.1"
- realpath-native "^1.1.0"
+ jest-regex-util "^29.4.3"
+ jest-snapshot "^29.5.0"
jest-resolve@^26.6.2:
version "26.6.2"
@@ -7340,30 +8429,20 @@ jest-resolve@^26.6.2:
resolve "^1.18.1"
slash "^3.0.0"
-jest-runner@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42"
- integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==
+jest-resolve@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc"
+ integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==
dependencies:
- "@jest/console" "^24.7.1"
- "@jest/environment" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- chalk "^2.4.2"
- exit "^0.1.2"
- graceful-fs "^4.1.15"
- jest-config "^24.9.0"
- jest-docblock "^24.3.0"
- jest-haste-map "^24.9.0"
- jest-jasmine2 "^24.9.0"
- jest-leak-detector "^24.9.0"
- jest-message-util "^24.9.0"
- jest-resolve "^24.9.0"
- jest-runtime "^24.9.0"
- jest-util "^24.9.0"
- jest-worker "^24.6.0"
- source-map-support "^0.5.6"
- throat "^4.0.0"
+ chalk "^4.0.0"
+ graceful-fs "^4.2.9"
+ jest-haste-map "^29.5.0"
+ jest-pnp-resolver "^1.2.2"
+ jest-util "^29.5.0"
+ jest-validate "^29.5.0"
+ resolve "^1.20.0"
+ resolve.exports "^2.0.0"
+ slash "^3.0.0"
jest-runner@^26.6.3:
version "26.6.3"
@@ -7391,34 +8470,32 @@ jest-runner@^26.6.3:
source-map-support "^0.5.6"
throat "^5.0.0"
-jest-runtime@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac"
- integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==
- dependencies:
- "@jest/console" "^24.7.1"
- "@jest/environment" "^24.9.0"
- "@jest/source-map" "^24.3.0"
- "@jest/transform" "^24.9.0"
- "@jest/types" "^24.9.0"
- "@types/yargs" "^13.0.0"
- chalk "^2.0.1"
- exit "^0.1.2"
- glob "^7.1.3"
- graceful-fs "^4.1.15"
- jest-config "^24.9.0"
- jest-haste-map "^24.9.0"
- jest-message-util "^24.9.0"
- jest-mock "^24.9.0"
- jest-regex-util "^24.3.0"
- jest-resolve "^24.9.0"
- jest-snapshot "^24.9.0"
- jest-util "^24.9.0"
- jest-validate "^24.9.0"
- realpath-native "^1.1.0"
- slash "^2.0.0"
- strip-bom "^3.0.0"
- yargs "^13.3.0"
+jest-runner@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8"
+ integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==
+ dependencies:
+ "@jest/console" "^29.5.0"
+ "@jest/environment" "^29.5.0"
+ "@jest/test-result" "^29.5.0"
+ "@jest/transform" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ emittery "^0.13.1"
+ graceful-fs "^4.2.9"
+ jest-docblock "^29.4.3"
+ jest-environment-node "^29.5.0"
+ jest-haste-map "^29.5.0"
+ jest-leak-detector "^29.5.0"
+ jest-message-util "^29.5.0"
+ jest-resolve "^29.5.0"
+ jest-runtime "^29.5.0"
+ jest-util "^29.5.0"
+ jest-watcher "^29.5.0"
+ jest-worker "^29.5.0"
+ p-limit "^3.1.0"
+ source-map-support "0.5.13"
jest-runtime@^26.6.3:
version "26.6.3"
@@ -7453,18 +8530,41 @@ jest-runtime@^26.6.3:
strip-bom "^4.0.0"
yargs "^15.4.1"
-jest-serializer-vue@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/jest-serializer-vue/-/jest-serializer-vue-2.0.2.tgz#b238ef286357ec6b480421bd47145050987d59b3"
- integrity sha512-nK/YIFo6qe3i9Ge+hr3h4PpRehuPPGZFt8LDBdTHYldMb7ZWlkanZS8Ls7D8h6qmQP2lBQVDLP0DKn5bJ9QApQ==
+jest-runtime@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420"
+ integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==
+ dependencies:
+ "@jest/environment" "^29.5.0"
+ "@jest/fake-timers" "^29.5.0"
+ "@jest/globals" "^29.5.0"
+ "@jest/source-map" "^29.4.3"
+ "@jest/test-result" "^29.5.0"
+ "@jest/transform" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ cjs-module-lexer "^1.0.0"
+ collect-v8-coverage "^1.0.0"
+ glob "^7.1.3"
+ graceful-fs "^4.2.9"
+ jest-haste-map "^29.5.0"
+ jest-message-util "^29.5.0"
+ jest-mock "^29.5.0"
+ jest-regex-util "^29.4.3"
+ jest-resolve "^29.5.0"
+ jest-snapshot "^29.5.0"
+ jest-util "^29.5.0"
+ slash "^3.0.0"
+ strip-bom "^4.0.0"
+
+jest-serializer-vue@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/jest-serializer-vue/-/jest-serializer-vue-3.1.0.tgz#af65817aa416d019f837b6cc53f121a3222846f4"
+ integrity sha512-vXz9/3IgBbLhsaVANYLG4ROCQd+Wg3qbB6ICofzFL+fbhSFPlqb0/MMGXcueVsjaovdWlYiRaLQLpdi1PTcoRQ==
dependencies:
pretty "2.0.0"
-jest-serializer@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73"
- integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==
-
jest-serializer@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1"
@@ -7473,33 +8573,6 @@ jest-serializer@^26.6.2:
"@types/node" "*"
graceful-fs "^4.2.4"
-jest-serializer@^27.5.1:
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64"
- integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==
- dependencies:
- "@types/node" "*"
- graceful-fs "^4.2.9"
-
-jest-snapshot@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba"
- integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==
- dependencies:
- "@babel/types" "^7.0.0"
- "@jest/types" "^24.9.0"
- chalk "^2.0.1"
- expect "^24.9.0"
- jest-diff "^24.9.0"
- jest-get-type "^24.9.0"
- jest-matcher-utils "^24.9.0"
- jest-message-util "^24.9.0"
- jest-resolve "^24.9.0"
- mkdirp "^0.5.1"
- natural-compare "^1.4.0"
- pretty-format "^24.9.0"
- semver "^6.2.0"
-
jest-snapshot@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84"
@@ -7522,23 +8595,34 @@ jest-snapshot@^26.6.2:
pretty-format "^26.6.2"
semver "^7.3.2"
-jest-util@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162"
- integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==
+jest-snapshot@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce"
+ integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==
dependencies:
- "@jest/console" "^24.9.0"
- "@jest/fake-timers" "^24.9.0"
- "@jest/source-map" "^24.9.0"
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- callsites "^3.0.0"
- chalk "^2.0.1"
- graceful-fs "^4.1.15"
- is-ci "^2.0.0"
- mkdirp "^0.5.1"
- slash "^2.0.0"
- source-map "^0.6.0"
+ "@babel/core" "^7.11.6"
+ "@babel/generator" "^7.7.2"
+ "@babel/plugin-syntax-jsx" "^7.7.2"
+ "@babel/plugin-syntax-typescript" "^7.7.2"
+ "@babel/traverse" "^7.7.2"
+ "@babel/types" "^7.3.3"
+ "@jest/expect-utils" "^29.5.0"
+ "@jest/transform" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/babel__traverse" "^7.0.6"
+ "@types/prettier" "^2.1.5"
+ babel-preset-current-node-syntax "^1.0.0"
+ chalk "^4.0.0"
+ expect "^29.5.0"
+ graceful-fs "^4.2.9"
+ jest-diff "^29.5.0"
+ jest-get-type "^29.4.3"
+ jest-matcher-utils "^29.5.0"
+ jest-message-util "^29.5.0"
+ jest-util "^29.5.0"
+ natural-compare "^1.4.0"
+ pretty-format "^29.5.0"
+ semver "^7.3.5"
jest-util@^25.1.0, jest-util@^25.5.0:
version "25.5.0"
@@ -7563,18 +8647,6 @@ jest-util@^26.6.2:
is-ci "^2.0.0"
micromatch "^4.0.2"
-jest-util@^27.5.1:
- version "27.5.1"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9"
- integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==
- dependencies:
- "@jest/types" "^27.5.1"
- "@types/node" "*"
- chalk "^4.0.0"
- ci-info "^3.2.0"
- graceful-fs "^4.2.9"
- picomatch "^2.2.3"
-
jest-util@^29.0.3:
version "29.0.3"
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.0.3.tgz#06d1d77f9a1bea380f121897d78695902959fbc0"
@@ -7587,17 +8659,17 @@ jest-util@^29.0.3:
graceful-fs "^4.2.9"
picomatch "^2.2.3"
-jest-validate@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab"
- integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==
+jest-util@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f"
+ integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==
dependencies:
- "@jest/types" "^24.9.0"
- camelcase "^5.3.1"
- chalk "^2.0.1"
- jest-get-type "^24.9.0"
- leven "^3.1.0"
- pretty-format "^24.9.0"
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ graceful-fs "^4.2.9"
+ picomatch "^2.2.3"
jest-validate@^26.6.2:
version "26.6.2"
@@ -7611,18 +8683,17 @@ jest-validate@^26.6.2:
leven "^3.1.0"
pretty-format "^26.6.2"
-jest-watcher@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b"
- integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==
+jest-validate@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc"
+ integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==
dependencies:
- "@jest/test-result" "^24.9.0"
- "@jest/types" "^24.9.0"
- "@types/yargs" "^13.0.0"
- ansi-escapes "^3.0.0"
- chalk "^2.0.1"
- jest-util "^24.9.0"
- string-length "^2.0.0"
+ "@jest/types" "^29.5.0"
+ camelcase "^6.2.0"
+ chalk "^4.0.0"
+ jest-get-type "^29.4.3"
+ leven "^3.1.0"
+ pretty-format "^29.5.0"
jest-watcher@^26.6.2:
version "26.6.2"
@@ -7637,13 +8708,19 @@ jest-watcher@^26.6.2:
jest-util "^26.6.2"
string-length "^4.0.1"
-jest-worker@^24.6.0, jest-worker@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
- integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==
+jest-watcher@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363"
+ integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==
dependencies:
- merge-stream "^2.0.0"
- supports-color "^6.1.0"
+ "@jest/test-result" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ ansi-escapes "^4.2.1"
+ chalk "^4.0.0"
+ emittery "^0.13.1"
+ jest-util "^29.5.0"
+ string-length "^4.0.1"
jest-worker@^26.2.1, jest-worker@^26.6.2:
version "26.6.2"
@@ -7654,7 +8731,7 @@ jest-worker@^26.2.1, jest-worker@^26.6.2:
merge-stream "^2.0.0"
supports-color "^7.0.0"
-jest-worker@^27.0.2, jest-worker@^27.4.5, jest-worker@^27.5.1:
+jest-worker@^27.4.5:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0"
integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
@@ -7663,13 +8740,15 @@ jest-worker@^27.0.2, jest-worker@^27.4.5, jest-worker@^27.5.1:
merge-stream "^2.0.0"
supports-color "^8.0.0"
-jest@^24.5.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171"
- integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==
+jest-worker@^29.4.3, jest-worker@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d"
+ integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==
dependencies:
- import-local "^2.0.0"
- jest-cli "^24.9.0"
+ "@types/node" "*"
+ jest-util "^29.5.0"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
jest@^26.0.1:
version "26.6.3"
@@ -7680,6 +8759,21 @@ jest@^26.0.1:
import-local "^3.0.2"
jest-cli "^26.6.3"
+jest@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e"
+ integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==
+ dependencies:
+ "@jest/core" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ import-local "^3.0.2"
+ jest-cli "^29.5.0"
+
+jiti@^1.18.2:
+ version "1.18.2"
+ resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.18.2.tgz#80c3ef3d486ebf2450d9335122b32d121f2a83cd"
+ integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
+
jpeg-js@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d"
@@ -7690,7 +8784,7 @@ jquery@^2.2.4:
resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.4.tgz#2c89d6889b5eac522a7eea32c14521559c6cbf02"
integrity sha512-lBHj60ezci2u1v2FqnZIraShGgEXq35qCzMv4lITyHGppTnA13rwR0MgwyNJh9TnDs3aXUvd1xjAotfraMHX/Q==
-js-base64@^2.4.3:
+js-base64@^2.4.9:
version "2.6.4"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
@@ -7723,43 +8817,18 @@ js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
-jsdom@^11.5.1:
- version "11.12.0"
- resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8"
- integrity sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==
- dependencies:
- abab "^2.0.0"
- acorn "^5.5.3"
- acorn-globals "^4.1.0"
- array-equal "^1.0.0"
- cssom ">= 0.3.2 < 0.4.0"
- cssstyle "^1.0.0"
- data-urls "^1.0.0"
- domexception "^1.0.1"
- escodegen "^1.9.1"
- html-encoding-sniffer "^1.0.2"
- left-pad "^1.3.0"
- nwsapi "^2.0.7"
- parse5 "4.0.0"
- pn "^1.1.0"
- request "^2.87.0"
- request-promise-native "^1.0.5"
- sax "^1.2.4"
- symbol-tree "^3.2.2"
- tough-cookie "^2.3.4"
- w3c-hr-time "^1.0.1"
- webidl-conversions "^4.0.2"
- whatwg-encoding "^1.0.3"
- whatwg-mimetype "^2.1.0"
- whatwg-url "^6.4.1"
- ws "^5.2.0"
- xml-name-validator "^3.0.0"
-
jsdom@^16.2.1, jsdom@^16.4.0:
version "16.7.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
@@ -7793,6 +8862,38 @@ jsdom@^16.2.1, jsdom@^16.4.0:
ws "^7.4.6"
xml-name-validator "^3.0.0"
+jsdom@^20.0.0:
+ version "20.0.3"
+ resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db"
+ integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==
+ dependencies:
+ abab "^2.0.6"
+ acorn "^8.8.1"
+ acorn-globals "^7.0.0"
+ cssom "^0.5.0"
+ cssstyle "^2.3.0"
+ data-urls "^3.0.2"
+ decimal.js "^10.4.2"
+ domexception "^4.0.0"
+ escodegen "^2.0.0"
+ form-data "^4.0.0"
+ html-encoding-sniffer "^3.0.0"
+ http-proxy-agent "^5.0.0"
+ https-proxy-agent "^5.0.1"
+ is-potential-custom-element-name "^1.0.1"
+ nwsapi "^2.2.2"
+ parse5 "^7.1.1"
+ saxes "^6.0.0"
+ symbol-tree "^3.2.4"
+ tough-cookie "^4.1.2"
+ w3c-xmlserializer "^4.0.0"
+ webidl-conversions "^7.0.0"
+ whatwg-encoding "^2.0.0"
+ whatwg-mimetype "^3.0.0"
+ whatwg-url "^11.0.0"
+ ws "^8.11.0"
+ xml-name-validator "^4.0.0"
+
jsdom@^8.1.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-8.5.0.tgz#d4d8f5dbf2768635b62a62823b947cf7071ebc98"
@@ -7826,20 +8927,6 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
-jshint@2.8.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.8.0.tgz#1d09a3bd913c4cadfa81bf18d582bd85bffe0d44"
- integrity sha512-IJJlrjPQU0fBkuV2Sgm4OKyHH5cFx2QToQgpkcyJcrF8gjG2AVpVnRqPQ3JrmlTQPiVlp9KAKfGezHk3mr7VJw==
- dependencies:
- cli "0.6.x"
- console-browserify "1.1.x"
- exit "0.1.x"
- htmlparser2 "3.8.x"
- lodash "3.7.x"
- minimatch "2.0.x"
- shelljs "0.3.x"
- strip-json-comments "1.0.x"
-
json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@@ -7881,9 +8968,9 @@ json5@^0.5.1:
integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==
json5@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
- integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
+ integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
dependencies:
minimist "^1.2.0"
@@ -7892,6 +8979,11 @@ json5@^2.1.2, json5@^2.2.0, json5@^2.2.1:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+json5@^2.2.2:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
@@ -7906,9 +8998,9 @@ jsonpointer@^5.0.0:
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072"
integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==
-"jspdf@https://github.com/MrRio/jsPDF.git#b7a1d8239c596292ce86dafa77f05987bcfa2e6e":
+"jspdf@https://github.com/parallax/jsPDF.git#b7a1d8239c596292ce86dafa77f05987bcfa2e6e":
version "2.1.1"
- resolved "https://github.com/MrRio/jsPDF.git#b7a1d8239c596292ce86dafa77f05987bcfa2e6e"
+ resolved "https://github.com/parallax/jsPDF.git#b7a1d8239c596292ce86dafa77f05987bcfa2e6e"
dependencies:
atob "^2.1.2"
btoa "^1.2.1"
@@ -7929,6 +9021,16 @@ jsprim@^1.2.2:
json-schema "0.4.0"
verror "1.10.0"
+jszip@^3.10.1:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
+ integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
+ dependencies:
+ lie "~3.3.0"
+ pako "~1.0.2"
+ readable-stream "~2.3.6"
+ setimmediate "^1.0.5"
+
jszip@^3.7.1:
version "3.10.0"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.0.tgz#faf3db2b4b8515425e34effcdbb086750a346061"
@@ -7939,6 +9041,11 @@ jszip@^3.7.1:
readable-stream "~2.3.6"
setimmediate "^1.0.5"
+karma-safari-launcher@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz#96982a2cc47d066aae71c553babb28319115a2ce"
+ integrity sha512-qmypLWd6F2qrDJfAETvXDfxHvKDk+nyIjpH9xIeI3/hENr0U3nuqkxaftq73PfXZ4aOuOChA6SnLW4m4AxfRjQ==
+
keen-ui@^1.3.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/keen-ui/-/keen-ui-1.3.2.tgz#252cab973282d4cd477c45ac92015595d1b1fff1"
@@ -7979,28 +9086,31 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-klona@^2.0.4:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
- integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
+klona@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
+ integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
-known-css-properties@^0.24.0:
- version "0.24.0"
- resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.24.0.tgz#19aefd85003ae5698a5560d2b55135bf5432155c"
- integrity sha512-RTSoaUAfLvpR357vWzAz/50Q/BmHfmE6ETSWfutT0AJiw10e6CmcdYRQJlLRd95B53D0Y2aD1jSxD3V3ySF+PA==
+known-css-properties@^0.25.0:
+ version "0.25.0"
+ resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.25.0.tgz#6ebc4d4b412f602e5cfbeb4086bd544e34c0a776"
+ integrity sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==
-kolibri-constants@^0.1.41:
- version "0.1.42"
- resolved "https://registry.yarnpkg.com/kolibri-constants/-/kolibri-constants-0.1.42.tgz#2f62a8d8b8894e5cfbd47ee6564b31018818c93f"
- integrity sha512-2hUxYnzUEfhLFJO9egVSwYW8/PKob9wLeDYfB74mtIzgQ4zy6huRj3574WetK9gREi+W1Jcm7HGPsfZIFzFvrA==
+kolibri-constants@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/kolibri-constants/-/kolibri-constants-0.2.0.tgz#47c9d773894e23251ba5ac4db420822e45603142"
+ integrity sha512-WYDMGDzB9gNxRbpX1O2cGe1HrJvLvSZGwMuAv6dqrxJgPf7iO+Hi40/1CXjHM7nk5CRt+hn5bqnMzCBmj1omPA==
-"kolibri-design-system@https://github.com/learningequality/kolibri-design-system#e9a2ff34716bb6412fe99f835ded5b17345bab94":
- version "1.3.0"
- resolved "https://github.com/learningequality/kolibri-design-system#e9a2ff34716bb6412fe99f835ded5b17345bab94"
+kolibri-design-system@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/kolibri-design-system/-/kolibri-design-system-3.0.1.tgz#ba6368c78ffab1d6195b828b1aacc4f11b9a67c3"
+ integrity sha512-/O+KuYTGiv5z9VTlm8bIWZwanMdearGmIP162EqaVn2tIbo/hY42lsuAbGSTZPi5CITm+LJ9r99ogImDRH8z2w==
dependencies:
+ "@vue/composition-api" "^1.7.2"
aphrodite "https://github.com/learningequality/aphrodite/"
autosize "^3.0.21"
css-element-queries "^1.2.0"
+ date-fns "^1.30.1"
frame-throttle "^3.0.0"
fuzzysearch "^1.0.3"
keen-ui "^1.3.0"
@@ -8008,115 +9118,112 @@ kolibri-constants@^0.1.41:
popper.js "^1.14.6"
purecss "^0.6.2"
tippy.js "^4.2.1"
-
-kolibri-tools@^0.16.0-dev.1:
- version "0.16.0-dev.1"
- resolved "https://registry.yarnpkg.com/kolibri-tools/-/kolibri-tools-0.16.0-dev.1.tgz#a6d0b8c521a2e5362399344edcc6e5514efeae09"
- integrity sha512-ueECTjIRP8e/jp2woNXMjSmE+W0uPgcEmGgrvsOqbmRksrDZNWCcWTlSY28m0AIx5OqC90g68abkdo4+VB0BOQ==
- dependencies:
- "@babel/core" "^7.9.6"
- "@babel/plugin-syntax-import-assertions" "^7.12.1"
- "@babel/plugin-transform-runtime" "^7.9.6"
- "@babel/preset-env" "^7.9.6"
- "@testing-library/jest-dom" "^5.11.0"
- "@vue/test-utils" "^1.0.3"
+ vue-intl "^3.1.0"
+ xstate "^4.38.3"
+
+kolibri-tools@0.16.0-dev.3:
+ version "0.16.0-dev.3"
+ resolved "https://registry.yarnpkg.com/kolibri-tools/-/kolibri-tools-0.16.0-dev.3.tgz#75bbbd1f0bece844bb552904c61f706c5a8aa911"
+ integrity sha512-O24ENVk0bF9S2WYoNG/nFW0cr9ugW0HIBlKED+/igav9MjqzaW1NYa8hbBEzOXWpfUnaAkbqGj5kF/mXdg+KFA==
+ dependencies:
+ "@babel/core" "^7.20.12"
+ "@babel/plugin-syntax-import-assertions" "^7.20.0"
+ "@babel/plugin-transform-runtime" "^7.21.4"
+ "@babel/preset-env" "^7.21.4"
+ "@testing-library/jest-dom" "^5.16.5"
+ "@vue/test-utils" "^1.3.4"
ast-traverse "^0.1.1"
- autoprefixer "10.4.2"
+ autoprefixer "10.4.14"
babel-core "7.0.0-bridge.0"
- babel-jest "^27.0.2"
- babel-loader "^8.2.2"
- browserslist-config-kolibri "0.16.0-dev.1"
+ babel-jest "^29.5.0"
+ babel-loader "^9.1.2"
+ browserslist-config-kolibri "0.16.0-dev.3"
chalk "4.1.2"
- check-node-version "^3.3.0"
+ check-node-version "^4.2.1"
chokidar "^3.5.3"
- cli-table "^0.3.6"
- commander "^2.9.0"
+ cli-table "^0.3.11"
+ commander "^10.0.1"
core-js "3"
- css-loader "6.5.1"
- css-minimizer-webpack-plugin "3.3.1"
- csv-parse "^4.14.1"
+ css-loader "6.7.3"
+ css-minimizer-webpack-plugin "5.0.0"
+ csv-parse "^5.3.9"
csv-writer "^1.3.0"
- del "^6.0.0"
+ del "^6.1.1"
+ escodegen "^2.0.0"
eslint "^6.8.0"
- eslint-config-prettier "^6.9.0"
+ eslint-config-prettier "^6.15.0"
eslint-config-vue "^2.0.2"
- eslint-plugin-import "^2.20.0"
+ eslint-plugin-import "^2.26.0"
eslint-plugin-jest "^23.3.0"
- eslint-plugin-kolibri "0.16.0-dev.1"
+ eslint-plugin-kolibri "0.16.0-dev.3"
eslint-plugin-vue "^7.3.0"
- espree "6.2.1"
- esquery "^1.0.1"
- express "^4.16.4"
- glob "^7.1.2"
- htmlhint "^0.9.13"
- ini "^2.0.0"
- jest "^24.5.0"
- jest-serializer-vue "^2.0.2"
- launch-editor-middleware "^2.2.1"
+ espree "9.5.1"
+ esquery "^1.5.0"
+ express "^4.18.2"
+ glob "^10.2.2"
+ htmlhint "^1.1.4"
+ ini "^4.1.0"
+ jest "^29.5.0"
+ jest-environment-jsdom "^29.5.0"
+ jest-serializer-vue "^3.1.0"
+ launch-editor-middleware "^2.5.0"
lodash "^4.17.21"
- mini-css-extract-plugin "^1.4.1"
- mkdirp "^1.0.4"
- node-sass "7.0.1"
+ mini-css-extract-plugin "^2.7.5"
+ node-sass "8.0.0"
postcss-less "^6.0.0"
- postcss-loader "^5.2.0"
+ postcss-loader "^7.2.4"
postcss-sass "^0.5.0"
- postcss-scss "^4.0.3"
+ postcss-scss "^4.0.6"
prettier "^1.18.2"
process "^0.11.10"
- query-ast "^1.0.1"
+ query-ast "^1.0.5"
readline-sync "^1.4.9"
- rtlcss "3.5.0"
- sass-loader "12.4.0"
- scss-parser "^1.0.0"
- semver "^5.6.0"
+ recast "^0.22.0"
+ rtlcss "4.0.0"
+ sass-loader "13.2.2"
+ scss-parser "^1.0.6"
+ semver "^7.3.8"
strip-ansi "6.0.1"
- style-loader "^2.0.0"
- stylelint "14.2.0"
- stylelint-config-prettier "9.0.3"
- stylelint-config-recess-order "3.0.0"
- stylelint-config-recommended-scss "5.0.2"
- stylelint-config-sass-guidelines "9.0.1"
+ style-loader "^3.3.2"
+ stylelint "14.10.0"
+ stylelint-config-prettier "9.0.5"
+ stylelint-config-recess-order "4.0.0"
+ stylelint-config-recommended-scss "9.0.1"
+ stylelint-config-sass-guidelines "10.0.0"
stylelint-config-standard "24.0.0"
- stylelint-csstree-validator "2.0.0"
- stylelint-scss "4.1.0"
+ stylelint-csstree-validator "2.1.0"
+ stylelint-scss "4.6.0"
temp "^0.8.3"
- terser-webpack-plugin "^5.1.1"
+ terser-webpack-plugin "^5.3.7"
url-loader "^4.1.1"
vue-jest "^3.0.0"
- vue-loader "^15.9.8"
- vue-style-loader "^3.0.3"
+ vue-loader "^15.10.1"
+ vue-style-loader "^4.1.3"
vue-template-compiler "^2.6.11"
- webpack "^5.73.0"
- webpack-cli "^4.9.1"
- webpack-dev-server "^4.7.3"
- webpack-merge "^5.7.3"
+ webpack "^5.81.0"
+ webpack-cli "^5.0.2"
+ webpack-dev-server "^4.13.3"
+ webpack-merge "^5.8.0"
-launch-editor-middleware@^2.2.1:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/launch-editor-middleware/-/launch-editor-middleware-2.4.0.tgz#0c3f24608fa69e4ae00d5d1c89f34bf0ba25a8f4"
- integrity sha512-/M7AX/6xktZY60KE7j71XLrj9U6H5TBoP+mJzhYB3fcdAq8rcazit/K0qWiu1jvytUPXP4lJRd1VJFwvdMQ/uw==
+launch-editor-middleware@^2.5.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/launch-editor-middleware/-/launch-editor-middleware-2.6.0.tgz#2ba4fe4b695d7fe3d44dee86b6d46d57b8332dfd"
+ integrity sha512-K2yxgljj5TdCeRN1lBtO3/J26+AIDDDw+04y6VAiZbWcTdBwsYN6RrZBnW5DN/QiSIdKNjKdATLUUluWWFYTIA==
dependencies:
- launch-editor "^2.4.0"
+ launch-editor "^2.6.0"
-launch-editor@^2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.4.0.tgz#2849de434e4684da2298db48c4e5b8ca64291173"
- integrity sha512-mZ0BHeSn/ohL+Ib+b+JnxC59vcNz6v5IR9d0CuM8f0x8ni8oK3IIG6G0vMkpxc0gFsmvINkztGOHiWTaX4BmAg==
+launch-editor@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.0.tgz#4c0c1a6ac126c572bd9ff9a30da1d2cae66defd7"
+ integrity sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==
dependencies:
picocolors "^1.0.0"
- shell-quote "^1.6.1"
-
-left-pad@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
- integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==
+ shell-quote "^1.7.3"
-less-loader@^11.0.0:
- version "11.0.0"
- resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-11.0.0.tgz#a31b2bc5cdfb62f1c7de9b2d01cd944c22b1a024"
- integrity sha512-9+LOWWjuoectIEx3zrfN83NAGxSUB5pWEabbbidVQVgZhN+wN68pOvuyirVlH1IK4VT1f3TmlyvAnCXh8O5KEw==
- dependencies:
- klona "^2.0.4"
+less-loader@^11.1.3:
+ version "11.1.3"
+ resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-11.1.3.tgz#1bb62d6ca9bf00a177c02793b54baac40f9be694"
+ integrity sha512-A5b7O8dH9xpxvkosNrP0dFp2i/dISOJa9WwGF3WJflfqIERE2ybxh1BFDj5CovC2+jCE4M354mk90hN6ziXlVw==
less@^3.0.1:
version "3.13.1"
@@ -8161,10 +9268,10 @@ lie@~3.3.0:
dependencies:
immediate "~3.0.5"
-lilconfig@^2.0.3:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"
- integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==
+lilconfig@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
+ integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
lines-and-columns@^1.1.6:
version "1.2.4"
@@ -8201,9 +9308,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^1.0.2, loader-utils@^1.1.0:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.1.tgz#278ad7006660bccc4d2c0c1578e17c5c78d5c0e0"
- integrity sha512-1Qo97Y2oKaU+Ro2xnDMR26g1BwMT29jNbem1EvcujW2jqt+j5COXyscjM7bLQkM9HaxI7pkWeW7gnI072yMI9Q==
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3"
+ integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@@ -8225,22 +9332,6 @@ localforage@*, localforage@^1.10.0:
dependencies:
lie "3.1.1"
-locate-path@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
- integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==
- dependencies:
- p-locate "^2.0.0"
- path-exists "^3.0.0"
-
-locate-path@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
- integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
- dependencies:
- p-locate "^3.0.0"
- path-exists "^3.0.0"
-
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
@@ -8248,13 +9339,6 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
-locate-path@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
- integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
- dependencies:
- p-locate "^5.0.0"
-
lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
@@ -8300,12 +9384,7 @@ lodash.without@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
integrity sha512-M3MefBwfDhgKgINVuBJCO1YR3+gf6s9HNJsIiZ/Ru77Ws6uTb9eBuvrkpzO+9iLoAaRodGuq7tyrPCx+74QYGQ==
-lodash@3.7.x:
- version "3.7.0"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45"
- integrity sha512-73GDDlioRJOCHV8N9gnBEpjdWI34+e9AvMnS4qdqdMfl8/yH/dJP1tfuqUFccZ/deZQlHuJiRSuKXjKIfDwBOg==
-
-lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0, lodash@~4.17.10:
+lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0, lodash@~4.17.10:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -8324,11 +9403,6 @@ loose-envify@^1.0.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
-lru-cache@2:
- version "2.7.3"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
- integrity sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==
-
lru-cache@^4.1.2, lru-cache@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -8337,6 +9411,13 @@ lru-cache@^4.1.2, lru-cache@^4.1.5:
pseudomap "^1.0.2"
yallist "^2.1.2"
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
@@ -8344,6 +9425,16 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
+lru-cache@^7.7.1:
+ version "7.18.3"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89"
+ integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==
+
+lru-cache@^9.0.0:
+ version "9.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1"
+ integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==
+
magic-string@^0.25.0, magic-string@^0.25.7:
version "0.25.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
@@ -8359,13 +9450,35 @@ make-dir@^2.1.0:
pify "^4.0.1"
semver "^5.6.0"
-make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0:
+make-dir@^3.0.0, make-dir@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
dependencies:
semver "^6.0.0"
+make-fetch-happen@^10.0.4:
+ version "10.2.1"
+ resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164"
+ integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==
+ 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"
+
make-fetch-happen@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968"
@@ -8437,15 +9550,15 @@ mathml-tag-names@^2.1.3:
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
-mdn-data@2.0.14:
- version "2.0.14"
- resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
- integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
+mdn-data@2.0.28:
+ version "2.0.28"
+ resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba"
+ integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==
-mdn-data@2.0.27:
- version "2.0.27"
- resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.27.tgz#1710baa7b0db8176d3b3d565ccb7915fc69525ab"
- integrity sha512-kwqO0I0jtWr25KcfLm9pia8vLZ8qoAKhWZuZMbneJq3jjBD3gl5nZs8l8Tu3ZBlBAHVQtDur9rdDGyvtfVraHQ==
+mdn-data@2.0.30:
+ version "2.0.30"
+ resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
+ integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
media-typer@0.3.0:
version "0.3.0"
@@ -8514,7 +9627,7 @@ methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
-micromatch@^3.1.10, micromatch@^3.1.4:
+micromatch@^3.1.4:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
@@ -8533,7 +9646,7 @@ micromatch@^3.1.10, micromatch@^3.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.2"
-micromatch@^4.0.2, micromatch@^4.0.4:
+micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
@@ -8575,42 +9688,25 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
-mini-css-extract-plugin@1.6.2, mini-css-extract-plugin@^1.4.1:
- version "1.6.2"
- resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8"
- integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q==
+mini-css-extract-plugin@^2.7.5:
+ version "2.7.5"
+ resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.5.tgz#afbb344977659ec0f1f6e050c7aea456b121cfc5"
+ integrity sha512-9HaR++0mlgom81s95vvNjxkg52n2b5s//3ZTI1EtzFb98awsLSivs2LMsVqnQ3ay0PVhqWcGNyDaTE961FOcjQ==
dependencies:
- loader-utils "^2.0.0"
- schema-utils "^3.0.0"
- webpack-sources "^1.1.0"
+ schema-utils "^4.0.0"
minimalistic-assert@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
-minimatch@0.3:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd"
- integrity sha512-WFX1jI1AaxNTZVOHLBVazwTWKaQjoykSzCBNXB72vDTCzopQGtyP91tKdFK5cv1+qMwPyiTu1HqUriqplI8pcA==
- dependencies:
- lru-cache "2"
- sigmund "~1.0.0"
-
-"minimatch@2 || 3", minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
+minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
-minimatch@2.0.x:
- version "2.0.10"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7"
- integrity sha512-jQo6o1qSVLEWaw3l+bwYA2X0uLuK2KjNh2wjgO7Q/9UJnXr1Q3yQKR8BI0/Bt/rPg75e6SMW4hW/6cBHVTZUjA==
- dependencies:
- brace-expansion "^1.0.0"
-
minimatch@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7"
@@ -8618,6 +9714,13 @@ minimatch@^5.0.1:
dependencies:
brace-expansion "^2.0.1"
+minimatch@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.0.tgz#bfc8e88a1c40ffd40c172ddac3decb8451503b56"
+ integrity sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==
+ dependencies:
+ brace-expansion "^2.0.1"
+
minimatch@~3.0.2:
version "3.0.8"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1"
@@ -8640,9 +9743,9 @@ minimist@^1.1.1, minimist@^1.2.5, minimist@^1.2.6:
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minimist@^1.2.0:
- version "1.2.7"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
- integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
minipass-collect@^1.0.2:
version "1.0.2"
@@ -8662,6 +9765,17 @@ minipass-fetch@^1.3.2:
optionalDependencies:
encoding "^0.1.12"
+minipass-fetch@^2.0.3:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add"
+ integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==
+ dependencies:
+ minipass "^3.1.6"
+ minipass-sized "^1.0.3"
+ minizlib "^2.1.2"
+ optionalDependencies:
+ encoding "^0.1.13"
+
minipass-flush@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373"
@@ -8690,7 +9804,19 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3:
dependencies:
yallist "^4.0.0"
-minizlib@^2.0.0, minizlib@^2.1.1:
+minipass@^3.1.6:
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a"
+ integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==
+ dependencies:
+ yallist "^4.0.0"
+
+minipass@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
+ integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
+
+minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
@@ -8751,16 +9877,21 @@ mutex-js@^1.1.5:
resolved "https://registry.yarnpkg.com/mutex-js/-/mutex-js-1.1.5.tgz#501dfa1b6192ded109f0e100b77fa7f89523eeda"
integrity sha512-e0PCNr2q4Er5SNrRwE/1yDFe8ILOk0yS4xFuDbO0KOfr3/5VmUcWB6SaTFaxRPqC/MWiGkfBuTRgIGz5EJtlcA==
-nan@^2.12.1, nan@^2.13.2:
- version "2.16.0"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916"
- integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==
+nan@^2.17.0:
+ version "2.17.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
+ integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
nanoid@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
+nanoid@^3.3.6:
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
+ integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -8788,7 +9919,7 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
-negotiator@0.6.3, negotiator@^0.6.2:
+negotiator@0.6.3, negotiator@^0.6.2, negotiator@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
@@ -8821,6 +9952,13 @@ node-cache@^4.1.1:
clone "2.x"
lodash "^4.17.15"
+node-fetch@^2.6.2:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
+ integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==
+ dependencies:
+ whatwg-url "^5.0.0"
+
node-forge@^1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
@@ -8847,17 +9985,6 @@ node-int64@^0.4.0:
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
-node-notifier@^5.4.2:
- version "5.4.5"
- resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.5.tgz#0cbc1a2b0f658493b4025775a13ad938e96091ef"
- integrity sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==
- dependencies:
- growly "^1.3.0"
- is-wsl "^1.1.0"
- semver "^5.5.0"
- shellwords "^0.1.1"
- which "^1.3.0"
-
node-notifier@^8.0.0:
version "8.0.2"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5"
@@ -8870,15 +9997,15 @@ node-notifier@^8.0.0:
uuid "^8.3.0"
which "^2.0.2"
-node-releases@^2.0.3:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666"
- integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==
+node-releases@^2.0.14:
+ version "2.0.14"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
+ integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
-node-sass@7.0.1:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-7.0.1.tgz#ad4f6bc663de8acc0a9360db39165a1e2620aa72"
- integrity sha512-uMy+Xt29NlqKCFdFRZyXKOTqGt+QaKHexv9STj2WeLottnlqZEEWx6Bj0MXNthmFRRdM/YwyNo/8Tr46TOM0jQ==
+node-sass@8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-8.0.0.tgz#c80d52148db0ce88610bcf1e1d112027393c13e1"
+ integrity sha512-jPzqCF2/e6JXw6r3VxfIqYc8tKQdkj5Z/BDATYyG6FL6b/LuYBNFGFVhus0mthcWifHm/JzBpKAd+3eXsWeK/A==
dependencies:
async-foreach "^0.1.3"
chalk "^4.1.2"
@@ -8887,14 +10014,13 @@ node-sass@7.0.1:
get-stdin "^4.0.1"
glob "^7.0.3"
lodash "^4.17.15"
+ make-fetch-happen "^10.0.4"
meow "^9.0.0"
- nan "^2.13.2"
+ nan "^2.17.0"
node-gyp "^8.4.1"
- npmlog "^5.0.0"
- request "^2.88.0"
- sass-graph "4.0.0"
+ sass-graph "^4.0.1"
stdout-stream "^1.4.0"
- "true-case-path" "^1.0.2"
+ "true-case-path" "^2.2.1"
node-vibrant@^3.1.6:
version "3.1.6"
@@ -8953,16 +10079,6 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
-normalize-selector@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03"
- integrity sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw==
-
-normalize-url@^6.0.1:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
- integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
-
npm-run-all@^4.1.3:
version "4.1.5"
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
@@ -8992,16 +10108,6 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1:
dependencies:
path-key "^3.0.0"
-npmlog@^5.0.0:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
- integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
- dependencies:
- are-we-there-yet "^2.0.0"
- console-control-strings "^1.1.0"
- gauge "^3.0.0"
- set-blocking "^2.0.0"
-
npmlog@^6.0.0:
version "6.0.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830"
@@ -9019,21 +10125,21 @@ nth-check@^2.0.1:
dependencies:
boolbase "^1.0.0"
-num2fraction@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
- integrity sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==
-
"nwmatcher@>= 1.3.7 < 2.0.0":
version "1.4.4"
resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e"
integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==
-nwsapi@^2.0.7, nwsapi@^2.2.0:
+nwsapi@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
+nwsapi@^2.2.2:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.4.tgz#fd59d5e904e8e1f03c25a7d5a15cfa16c714a1e5"
+ integrity sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==
+
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
@@ -9063,6 +10169,19 @@ object-inspect@^1.12.0, object-inspect@^1.9.0:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
+object-inspect@^1.12.3:
+ version "1.12.3"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
+ integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
+
+object-is@^1.0.1:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
+ integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+
object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@@ -9075,7 +10194,7 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
-object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.2:
+object.assign@^4.1.0, object.assign@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
@@ -9085,15 +10204,15 @@ object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.2:
has-symbols "^1.0.1"
object-keys "^1.1.1"
-object.getownpropertydescriptors@^2.1.1:
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37"
- integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==
+object.assign@^4.1.4:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
+ integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
dependencies:
- array.prototype.reduce "^1.0.4"
call-bind "^1.0.2"
define-properties "^1.1.4"
- es-abstract "^1.20.1"
+ has-symbols "^1.0.3"
+ object-keys "^1.1.1"
object.pick@^1.3.0:
version "1.3.0"
@@ -9102,19 +10221,14 @@ object.pick@^1.3.0:
dependencies:
isobject "^3.0.1"
-object.values@^1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac"
- integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==
+object.values@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d"
+ integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==
dependencies:
call-bind "^1.0.2"
- define-properties "^1.1.3"
- es-abstract "^1.19.1"
-
-oblivious-set@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.1.1.tgz#d9d38e9491d51f27a5c3ec1681d2ba40aa81e98b"
- integrity sha512-Oh+8fK09mgGmAshFdH6hSVco6KZmd1tTwNFWj35OvzdmJTMZtAkbn05zar2iG3v6sDs1JLEtOiBGNb6BHwkb2w==
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
obuf@^1.0.0, obuf@^1.1.2:
version "1.1.2"
@@ -9183,13 +10297,6 @@ os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
-p-each-series@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"
- integrity sha512-J/e9xiZZQNrt+958FFzJ+auItsBGq+UrQ7nE89AUP7UOTtjHnkISANXLdayhVzh538UnLMCSlf13lFfRIAKQOA==
- dependencies:
- p-reduce "^1.0.0"
-
p-each-series@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a"
@@ -9200,41 +10307,20 @@ p-finally@^1.0.0:
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
-p-limit@^1.1.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
- integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
- dependencies:
- p-try "^1.0.0"
-
-p-limit@^2.0.0, p-limit@^2.2.0:
+p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
-p-limit@^3.0.2:
+p-limit@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
dependencies:
yocto-queue "^0.1.0"
-p-locate@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
- integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==
- dependencies:
- p-limit "^1.1.0"
-
-p-locate@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
- integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
- dependencies:
- p-limit "^2.0.0"
-
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
@@ -9242,13 +10328,6 @@ p-locate@^4.1.0:
dependencies:
p-limit "^2.2.0"
-p-locate@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
- integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
- dependencies:
- p-limit "^3.0.2"
-
p-map@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
@@ -9256,19 +10335,6 @@ p-map@^4.0.0:
dependencies:
aggregate-error "^3.0.0"
-p-queue@6.6.2:
- version "6.6.2"
- resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
- integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==
- dependencies:
- eventemitter3 "^4.0.4"
- p-timeout "^3.2.0"
-
-p-reduce@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
- integrity sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ==
-
p-retry@^4.5.0:
version "4.6.2"
resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16"
@@ -9277,18 +10343,6 @@ p-retry@^4.5.0:
"@types/retry" "0.12.0"
retry "^0.13.1"
-p-timeout@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
- integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
- dependencies:
- p-finally "^1.0.0"
-
-p-try@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
- integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==
-
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
@@ -9299,10 +10353,10 @@ pako@^1.0.5, pako@~1.0.2:
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
-papaparse@^5.2.0:
- version "5.3.2"
- resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.3.2.tgz#d1abed498a0ee299f103130a6109720404fbd467"
- integrity sha512-6dNZu0Ki+gyV0eBsFKJhYr+MdQYAzFUGlBMNj3GNrmHxmz1lfRa24CjFObPXtjcetlOv5Ad299MhIK0znp3afw==
+papaparse@^5.4.1:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.4.1.tgz#f45c0f871853578bd3a30f92d96fdcfb6ebea127"
+ integrity sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==
parent-module@^1.0.0:
version "1.0.1"
@@ -9329,16 +10383,6 @@ parse-bmfont-xml@^1.1.4:
xml-parse-from-string "^1.0.0"
xml2js "^0.4.5"
-parse-glob@3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
- integrity sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==
- dependencies:
- glob-base "^0.3.0"
- is-dotfile "^1.0.0"
- is-extglob "^1.0.0"
- is-glob "^2.0.0"
-
parse-headers@^2.0.0:
version "2.0.5"
resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9"
@@ -9352,7 +10396,7 @@ parse-json@^4.0.0:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
-parse-json@^5.0.0:
+parse-json@^5.0.0, parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
@@ -9362,11 +10406,6 @@ parse-json@^5.0.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
-parse5@4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
- integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==
-
parse5@6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
@@ -9377,10 +10416,12 @@ parse5@^1.5.1:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94"
integrity sha512-w2jx/0tJzvgKwZa58sj2vAYq/S/K1QJfIB3cWYea/Iu1scFPDQQ3IQiVZTHWtRBwAjv2Yd7S/xeZf3XqLDb3bA==
-parserlib@~0.2.2:
- version "0.2.5"
- resolved "https://registry.yarnpkg.com/parserlib/-/parserlib-0.2.5.tgz#85907dd8605aa06abb3dd295d50bb2b8fa4dd117"
- integrity sha512-SNu7MNq2Lp5aHXM1HZLyXEHpSAVpHU1y3pvPpxnq6jVK/5WIpKv9aA11PyMeiW9Y+EORem2J7XhiEIaOKizUHA==
+parse5@^7.0.0, parse5@^7.1.1:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
+ integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
+ dependencies:
+ entities "^4.4.0"
parseurl@~1.3.2, parseurl@~1.3.3:
version "1.3.3"
@@ -9422,6 +10463,14 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+path-scurry@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.7.0.tgz#99c741a2cfbce782294a39994d63748b5a24f6db"
+ integrity sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==
+ dependencies:
+ lru-cache "^9.0.0"
+ minipass "^5.0.0"
+
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@@ -9504,13 +10553,6 @@ pixelmatch@^4.0.2:
dependencies:
pngjs "^3.0.0"
-pkg-dir@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
- integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==
- dependencies:
- find-up "^3.0.0"
-
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
@@ -9518,11 +10560,6 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
-pn@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
- integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
-
pngjs@^3.0.0, pngjs@^3.3.3:
version "3.4.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
@@ -9538,118 +10575,119 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==
-postcss-calc@^8.2.3:
- version "8.2.4"
- resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5"
- integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==
+postcss-calc@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.0.tgz#cd9b2b155e57c823687eb67c9afcbe97c98ecaa4"
+ integrity sha512-B9BNW/SVh4SMJfoCQ6D9h1Wo7Yjqks7UdbiARJ16J5TIsQn5NEqwMF5joSgOYb26oJPUR5Uv3fCQ/4PvmZWeJQ==
dependencies:
- postcss-selector-parser "^6.0.9"
+ postcss-selector-parser "^6.0.11"
postcss-value-parser "^4.2.0"
-postcss-colormin@^5.3.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a"
- integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==
+postcss-colormin@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.0.0.tgz#d4250652e952e1c0aca70c66942da93d3cdeaafe"
+ integrity sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw==
dependencies:
- browserslist "^4.16.6"
+ browserslist "^4.21.4"
caniuse-api "^3.0.0"
colord "^2.9.1"
postcss-value-parser "^4.2.0"
-postcss-convert-values@^5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz#31586df4e184c2e8890e8b34a0b9355313f503ab"
- integrity sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==
+postcss-convert-values@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.0.0.tgz#ec94a954957e5c3f78f0e8f65dfcda95280b8996"
+ integrity sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw==
dependencies:
- browserslist "^4.20.3"
+ browserslist "^4.21.4"
postcss-value-parser "^4.2.0"
-postcss-discard-comments@^5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696"
- integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==
+postcss-discard-comments@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.0.tgz#9ca335e8b68919f301b24ba47dde226a42e535fe"
+ integrity sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw==
-postcss-discard-duplicates@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848"
- integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==
+postcss-discard-duplicates@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.0.tgz#c26177a6c33070922e67e9a92c0fd23d443d1355"
+ integrity sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA==
-postcss-discard-empty@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c"
- integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==
+postcss-discard-empty@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.0.tgz#06c1c4fce09e22d2a99e667c8550eb8a3a1b9aee"
+ integrity sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ==
-postcss-discard-overridden@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e"
- integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==
+postcss-discard-overridden@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz#49c5262db14e975e349692d9024442de7cd8e234"
+ integrity sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==
postcss-less@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-6.0.0.tgz#463b34c60f53b648c237f569aeb2e09149d85af4"
integrity sha512-FPX16mQLyEjLzEuuJtxA8X3ejDLNGGEG503d2YGZR5Ask1SpDN8KmZUMpzCvyalWRywAn1n1VOA5dcqfCLo5rg==
-postcss-loader@^5.2.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-5.3.0.tgz#1657f869e48d4fdb018a40771c235e499ee26244"
- integrity sha512-/+Z1RAmssdiSLgIZwnJHwBMnlABPgF7giYzTN2NOfr9D21IJZ4mQC1R2miwp80zno9M4zMD/umGI8cR+2EL5zw==
+postcss-loader@^7.2.4:
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.0.tgz#05991c1e490d8ff86ef18358d87db3b5b2dcb5f5"
+ integrity sha512-qLAFjvR2BFNz1H930P7mj1iuWJFjGey/nVhimfOAAQ1ZyPpcClAxP8+A55Sl8mBvM+K2a9Pjgdj10KpANWrNfw==
dependencies:
- cosmiconfig "^7.0.0"
- klona "^2.0.4"
- semver "^7.3.4"
+ cosmiconfig "^8.1.3"
+ jiti "^1.18.2"
+ klona "^2.0.6"
+ semver "^7.3.8"
postcss-media-query-parser@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==
-postcss-merge-longhand@^5.1.5:
- version "5.1.5"
- resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.5.tgz#b0e03bee3b964336f5f33c4fc8eacae608e91c05"
- integrity sha512-NOG1grw9wIO+60arKa2YYsrbgvP6tp+jqc7+ZD5/MalIw234ooH2C6KlR6FEn4yle7GqZoBxSK1mLBE9KPur6w==
+postcss-merge-longhand@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz#6f627b27db939bce316eaa97e22400267e798d69"
+ integrity sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg==
dependencies:
postcss-value-parser "^4.2.0"
- stylehacks "^5.1.0"
+ stylehacks "^6.0.0"
-postcss-merge-rules@^5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz#7049a14d4211045412116d79b751def4484473a5"
- integrity sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==
+postcss-merge-rules@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.0.1.tgz#39f165746404e646c0f5c510222ccde4824a86aa"
+ integrity sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw==
dependencies:
- browserslist "^4.16.6"
+ browserslist "^4.21.4"
caniuse-api "^3.0.0"
- cssnano-utils "^3.1.0"
+ cssnano-utils "^4.0.0"
postcss-selector-parser "^6.0.5"
-postcss-minify-font-values@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b"
- integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==
+postcss-minify-font-values@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.0.0.tgz#68d4a028f9fa5f61701974724b2cc9445d8e6070"
+ integrity sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA==
dependencies:
postcss-value-parser "^4.2.0"
-postcss-minify-gradients@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c"
- integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==
+postcss-minify-gradients@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.0.tgz#22b5c88cc63091dadbad34e31ff958404d51d679"
+ integrity sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA==
dependencies:
colord "^2.9.1"
- cssnano-utils "^3.1.0"
+ cssnano-utils "^4.0.0"
postcss-value-parser "^4.2.0"
-postcss-minify-params@^5.1.3:
- version "5.1.3"
- resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz#ac41a6465be2db735099bbd1798d85079a6dc1f9"
- integrity sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==
+postcss-minify-params@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.0.0.tgz#2b3a85a9e3b990d7a16866f430f5fd1d5961b539"
+ integrity sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ==
dependencies:
- browserslist "^4.16.6"
- cssnano-utils "^3.1.0"
+ browserslist "^4.21.4"
+ cssnano-utils "^4.0.0"
postcss-value-parser "^4.2.0"
-postcss-minify-selectors@^5.2.1:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6"
- integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==
+postcss-minify-selectors@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.0.tgz#5046c5e8680a586e5a0cad52cc9aa36d6be5bda2"
+ integrity sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g==
dependencies:
postcss-selector-parser "^6.0.5"
@@ -9681,89 +10719,88 @@ postcss-modules-values@^4.0.0:
dependencies:
icss-utils "^5.0.0"
-postcss-normalize-charset@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed"
- integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==
+postcss-normalize-charset@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.0.tgz#36cc12457259064969fb96f84df491652a4b0975"
+ integrity sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ==
-postcss-normalize-display-values@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8"
- integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==
+postcss-normalize-display-values@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.0.tgz#8d2961415078644d8c6bbbdaf9a2fdd60f546cd4"
+ integrity sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw==
dependencies:
postcss-value-parser "^4.2.0"
-postcss-normalize-positions@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz#902a7cb97cf0b9e8b1b654d4a43d451e48966458"
- integrity sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ==
+postcss-normalize-positions@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.0.tgz#25b96df99a69f8925f730eaee0be74416865e301"
+ integrity sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg==
dependencies:
postcss-value-parser "^4.2.0"
-postcss-normalize-repeat-style@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz#f6d6fd5a54f51a741cc84a37f7459e60ef7a6398"
- integrity sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw==
+postcss-normalize-repeat-style@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.0.tgz#ddf30ad8762feb5b1eb97f39f251acd7b8353299"
+ integrity sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A==
dependencies:
postcss-value-parser "^4.2.0"
-postcss-normalize-string@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228"
- integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==
+postcss-normalize-string@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.0.tgz#948282647a51e409d69dde7910f0ac2ff97cb5d8"
+ integrity sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w==
dependencies:
postcss-value-parser "^4.2.0"
-postcss-normalize-timing-functions@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb"
- integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==
+postcss-normalize-timing-functions@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.0.tgz#5f13e650b8c43351989fc5de694525cc2539841c"
+ integrity sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg==
dependencies:
postcss-value-parser "^4.2.0"
-postcss-normalize-unicode@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75"
- integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==
+postcss-normalize-unicode@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.0.tgz#741b3310f874616bdcf07764f5503695d3604730"
+ integrity sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg==
dependencies:
- browserslist "^4.16.6"
+ browserslist "^4.21.4"
postcss-value-parser "^4.2.0"
-postcss-normalize-url@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc"
- integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==
+postcss-normalize-url@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.0.tgz#d0a31e962a16401fb7deb7754b397a323fb650b4"
+ integrity sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw==
dependencies:
- normalize-url "^6.0.1"
postcss-value-parser "^4.2.0"
-postcss-normalize-whitespace@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa"
- integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==
+postcss-normalize-whitespace@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.0.tgz#accb961caa42e25ca4179b60855b79b1f7129d4d"
+ integrity sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw==
dependencies:
postcss-value-parser "^4.2.0"
-postcss-ordered-values@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.1.tgz#0b41b610ba02906a3341e92cab01ff8ebc598adb"
- integrity sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw==
+postcss-ordered-values@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.0.tgz#374704cdff25560d44061d17ba3c6308837a3218"
+ integrity sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg==
dependencies:
- cssnano-utils "^3.1.0"
+ cssnano-utils "^4.0.0"
postcss-value-parser "^4.2.0"
-postcss-reduce-initial@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6"
- integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==
+postcss-reduce-initial@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz#7d16e83e60e27e2fa42f56ec0b426f1da332eca7"
+ integrity sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA==
dependencies:
- browserslist "^4.16.6"
+ browserslist "^4.21.4"
caniuse-api "^3.0.0"
-postcss-reduce-transforms@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9"
- integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==
+postcss-reduce-transforms@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.0.tgz#28ff2601a6d9b96a2f039b3501526e1f4d584a46"
+ integrity sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w==
dependencies:
postcss-value-parser "^4.2.0"
@@ -9785,12 +10822,25 @@ postcss-sass@^0.5.0:
gonzales-pe "^4.3.0"
postcss "^8.2.14"
-postcss-scss@^4.0.2, postcss-scss@^4.0.3:
+postcss-scss@^4.0.2:
version "4.0.4"
resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.4.tgz#aa8f60e19ee18259bc193db9e4b96edfce3f3b1f"
integrity sha512-aBBbVyzA8b3hUL0MGrpydxxXKXFZc5Eqva0Q3V9qsBOLEMsjb6w49WfpsoWzpEgcqJGW4t7Rio8WXVU9Gd8vWg==
-postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.7, postcss-selector-parser@^6.0.9:
+postcss-scss@^4.0.6:
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.6.tgz#5d62a574b950a6ae12f2aa89b60d63d9e4432bfd"
+ integrity sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==
+
+postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11:
+ version "6.0.12"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz#2efae5ffab3c8bfb2b7fbf0c426e3bca616c4abb"
+ integrity sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
@@ -9798,23 +10848,23 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector
cssesc "^3.0.0"
util-deprecate "^1.0.2"
-postcss-sorting@^7.0.1:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/postcss-sorting/-/postcss-sorting-7.0.1.tgz#923b5268451cf2d93ebf8835e17a6537757049a5"
- integrity sha512-iLBFYz6VRYyLJEJsBJ8M3TCqNcckVzz4wFounSc5Oez35ogE/X+aoC5fFu103Ot7NyvjU3/xqIXn93Gp3kJk4g==
+postcss-sorting@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-sorting/-/postcss-sorting-8.0.2.tgz#6393385ece272baf74bee9820fb1b58098e4eeca"
+ integrity sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==
-postcss-svgo@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d"
- integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==
+postcss-svgo@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.0.tgz#7b18742d38d4505a0455bbe70d52b49f00eaf69d"
+ integrity sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw==
dependencies:
postcss-value-parser "^4.2.0"
- svgo "^2.7.0"
+ svgo "^3.0.2"
-postcss-unique-selectors@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6"
- integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==
+postcss-unique-selectors@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.0.tgz#c94e9b0f7bffb1203894e42294b5a1b3fb34fbe1"
+ integrity sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw==
dependencies:
postcss-selector-parser "^6.0.5"
@@ -9823,7 +10873,7 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
-postcss@^7.0.32, postcss@^7.0.36:
+postcss@^7.0.36:
version "7.0.39"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309"
integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==
@@ -9831,7 +10881,7 @@ postcss@^7.0.32, postcss@^7.0.36:
picocolors "^0.2.1"
source-map "^0.6.1"
-postcss@^8.2.14, postcss@^8.2.15, postcss@^8.3.11, postcss@^8.3.5:
+postcss@^8.2.14:
version "8.4.14"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
@@ -9840,6 +10890,15 @@ postcss@^8.2.14, postcss@^8.2.15, postcss@^8.3.11, postcss@^8.3.5:
picocolors "^1.0.0"
source-map-js "^1.0.2"
+postcss@^8.4.16, postcss@^8.4.19, postcss@^8.4.21, postcss@^8.4.6:
+ version "8.4.23"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
+ integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==
+ dependencies:
+ nanoid "^3.3.6"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -9860,16 +10919,6 @@ pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
-pretty-format@^24.9.0:
- version "24.9.0"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
- integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==
- dependencies:
- "@jest/types" "^24.9.0"
- ansi-regex "^4.0.0"
- ansi-styles "^3.2.0"
- react-is "^16.8.4"
-
pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
@@ -9898,6 +10947,15 @@ pretty-format@^29.0.3:
ansi-styles "^5.0.0"
react-is "^18.0.0"
+pretty-format@^29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a"
+ integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==
+ dependencies:
+ "@jest/schemas" "^29.4.3"
+ ansi-styles "^5.0.0"
+ react-is "^18.0.0"
+
pretty@2.0.0, pretty@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pretty/-/pretty-2.0.0.tgz#adbc7960b7bbfe289a557dc5f737619a220d06a5"
@@ -9956,6 +11014,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@@ -9989,6 +11052,11 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+pure-rand@^6.0.0:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306"
+ integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==
+
purecss@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/purecss/-/purecss-0.6.2.tgz#f0827d227e909543c5138f36d56c8c613f476b8c"
@@ -10001,30 +11069,43 @@ qs@6.10.3:
dependencies:
side-channel "^1.0.4"
-qs@^6.11.0:
+qs@6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
dependencies:
side-channel "^1.0.4"
+qs@^6.11.2:
+ version "6.11.2"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
+ integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
+ dependencies:
+ side-channel "^1.0.4"
+
qs@~6.5.2:
version "6.5.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
-query-ast@^1.0.1:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/query-ast/-/query-ast-1.0.4.tgz#efa832e1270cc3e0ab42291716f73a7de1928c2a"
- integrity sha512-KFJFSvODCBjIH5HbHvITj9EEZKYUU6VX0T5CuB1ayvjUoUaZkKMi6eeby5Tf8DMukyZHlJQOE1+f3vevKUe6eg==
+query-ast@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/query-ast/-/query-ast-1.0.5.tgz#60f60593e8ea085082aaf9f316631a5cc070074a"
+ integrity sha512-JK+1ma4YDuLjvKKcz9JZ70G+CM9qEOs/l1cZzstMMfwKUabTJ9sud5jvDGrUNuv03yKUgs82bLkHXJkDyhRmBw==
dependencies:
invariant "2.2.4"
+ lodash "^4.17.21"
querystring@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==
+querystringify@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
+ integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
+
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -10091,11 +11172,6 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
-react-is@^16.8.4:
- version "16.13.1"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
- integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-
react-is@^17.0.1:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
@@ -10106,14 +11182,6 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
-read-pkg-up@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978"
- integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==
- dependencies:
- find-up "^3.0.0"
- read-pkg "^3.0.0"
-
read-pkg-up@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507"
@@ -10142,16 +11210,6 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
-readable-stream@1.1:
- version "1.1.13"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"
- integrity sha1-9u73ZPUUyJ4rniMUanW6EGdW0j4=
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.1"
- isarray "0.0.1"
- string_decoder "~0.10.x"
-
readable-stream@^2.0.1, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
@@ -10195,19 +11253,23 @@ realistic-structured-clone@^3.0.0:
typeson "^6.1.0"
typeson-registry "^1.0.0-alpha.20"
-realpath-native@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
- integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==
+recast@^0.22.0:
+ version "0.22.0"
+ resolved "https://registry.yarnpkg.com/recast/-/recast-0.22.0.tgz#1dd3bf1b86e5eb810b044221a1a734234ed3e9c0"
+ integrity sha512-5AAx+mujtXijsEavc5lWXBPQqrM4+Dl5qNH96N2aNeuJFUzpiiToKPsxQD/zAIJHspz7zz0maX0PCtCTFVlixQ==
dependencies:
- util.promisify "^1.0.0"
+ assert "^2.0.0"
+ ast-types "0.15.2"
+ esprima "~4.0.0"
+ source-map "~0.6.1"
+ tslib "^2.0.1"
-rechoir@^0.7.0:
- version "0.7.1"
- resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686"
- integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==
+rechoir@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22"
+ integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==
dependencies:
- resolve "^1.9.0"
+ resolve "^1.20.0"
redent@^3.0.0:
version "3.0.0"
@@ -10224,6 +11286,13 @@ regenerate-unicode-properties@^10.0.1:
dependencies:
regenerate "^1.4.2"
+regenerate-unicode-properties@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
+ integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==
+ dependencies:
+ regenerate "^1.4.2"
+
regenerate@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
@@ -10234,11 +11303,16 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.5:
+regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.5:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
+regenerator-runtime@^0.14.0:
+ version "0.14.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
+ integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
+
regenerator-transform@^0.15.0:
version "0.15.0"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537"
@@ -10246,6 +11320,13 @@ regenerator-transform@^0.15.0:
dependencies:
"@babel/runtime" "^7.8.4"
+regenerator-transform@^0.15.1:
+ version "0.15.1"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"
+ integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==
+ dependencies:
+ "@babel/runtime" "^7.8.4"
+
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@@ -10280,6 +11361,18 @@ regexpu-core@^5.0.1:
unicode-match-property-ecmascript "^2.0.0"
unicode-match-property-value-ecmascript "^2.0.0"
+regexpu-core@^5.3.1:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b"
+ integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==
+ dependencies:
+ "@babel/regjsgen" "^0.8.0"
+ regenerate "^1.4.2"
+ regenerate-unicode-properties "^10.1.0"
+ regjsparser "^0.9.1"
+ unicode-match-property-ecmascript "^2.0.0"
+ unicode-match-property-value-ecmascript "^2.1.0"
+
regjsgen@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d"
@@ -10292,6 +11385,13 @@ regjsparser@^0.8.2:
dependencies:
jsesc "~0.5.0"
+regjsparser@^0.9.1:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709"
+ integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==
+ dependencies:
+ jsesc "~0.5.0"
+
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -10307,23 +11407,7 @@ repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
-request-promise-core@1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
- integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==
- dependencies:
- lodash "^4.17.19"
-
-request-promise-native@^1.0.5:
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28"
- integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==
- dependencies:
- request-promise-core "1.1.4"
- stealthy-require "^1.1.1"
- tough-cookie "^2.3.3"
-
-request@^2.55.0, request@^2.87.0, request@^2.88.0:
+request@^2.55.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -10374,13 +11458,6 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
-resolve-cwd@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
- integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=
- dependencies:
- resolve-from "^3.0.0"
-
resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@@ -10388,11 +11465,6 @@ resolve-cwd@^3.0.0:
dependencies:
resolve-from "^5.0.0"
-resolve-from@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
- integrity sha1-six699nWiBvItuZTM17rywoYh0g=
-
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -10408,12 +11480,12 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
-resolve@1.1.7:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
- integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
+resolve.exports@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
+ integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
-resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.9.0:
+resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
@@ -10422,6 +11494,15 @@ resolve@^1.10.0, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.2
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
+resolve@^1.22.1:
+ version "1.22.2"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
+ integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
+ dependencies:
+ is-core-module "^2.11.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@@ -10462,20 +11543,13 @@ rimraf@2.6.3, rimraf@~2.6.2:
dependencies:
glob "^7.1.3"
-rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
+rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
-rimraf@^2.5.4, rimraf@^2.6.3:
- version "2.7.1"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
- integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
- dependencies:
- glob "^7.1.3"
-
rollup-plugin-terser@^7.0.0:
version "7.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
@@ -10498,14 +11572,14 @@ rsvp@^4.8.4:
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
-rtlcss@3.5.0:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3"
- integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==
+rtlcss@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-4.0.0.tgz#ba73233b9c79fbf66eb867f2ae937713acbf2b40"
+ integrity sha512-j6oypPP+mgFwDXL1JkLCtm6U/DQntMUqlv5SOhpgHhdIE+PmBcjrtAHIpXfbIup47kD5Sgja9JDsDF1NNOsBwQ==
dependencies:
- find-up "^5.0.0"
+ escalade "^3.1.1"
picocolors "^1.0.0"
- postcss "^8.3.11"
+ postcss "^8.4.6"
strip-json-comments "^3.1.1"
run-async@^2.4.0:
@@ -10537,6 +11611,15 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0,
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+safe-regex-test@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
+ integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==
+ dependencies:
+ call-bind "^1.0.2"
+ get-intrinsic "^1.1.3"
+ is-regex "^1.1.4"
+
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@@ -10564,28 +11647,28 @@ sane@^4.0.3:
minimist "^1.1.1"
walker "~1.0.5"
-sass-graph@4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-4.0.0.tgz#fff8359efc77b31213056dfd251d05dadc74c613"
- integrity sha512-WSO/MfXqKH7/TS8RdkCX3lVkPFQzCgbqdGsmSKq6tlPU+GpGEsa/5aW18JqItnqh+lPtcjifqdZ/VmiILkKckQ==
+sass-graph@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-4.0.1.tgz#2ff8ca477224d694055bf4093f414cf6cfad1d2e"
+ integrity sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==
dependencies:
glob "^7.0.0"
lodash "^4.17.11"
- scss-tokenizer "^0.3.0"
+ scss-tokenizer "^0.4.3"
yargs "^17.2.1"
-sass-loader@12.4.0:
- version "12.4.0"
- resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.4.0.tgz#260b0d51a8a373bb8e88efc11f6ba5583fea0bcf"
- integrity sha512-7xN+8khDIzym1oL9XyS6zP6Ges+Bo2B2xbPrjdMHEYyV3AQYhd/wXeru++3ODHF0zMjYmVadblSKrPrjEkL8mg==
+sass-loader@13.2.2:
+ version "13.2.2"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.2.2.tgz#f97e803993b24012c10d7ba9676548bf7a6b18b9"
+ integrity sha512-nrIdVAAte3B9icfBiGWvmMhT/D+eCDwnk+yA7VE/76dp/WkHX+i44Q/pfo71NYbwj0Ap+PGsn0ekOuU1WFJ2AA==
dependencies:
- klona "^2.0.4"
+ klona "^2.0.6"
neo-async "^2.6.2"
-sax@>=0.6.0, sax@^1.1.4, sax@^1.2.4, sax@~1.2.4:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
- integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
+sax@>=0.6.0, sax@^1.1.4, sax@~1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
+ integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
saxes@^5.0.1:
version "5.0.1"
@@ -10594,16 +11677,14 @@ saxes@^5.0.1:
dependencies:
xmlchars "^2.2.0"
-schema-utils@^2.6.5:
- version "2.7.1"
- resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
- integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
+saxes@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5"
+ integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==
dependencies:
- "@types/json-schema" "^7.0.5"
- ajv "^6.12.4"
- ajv-keywords "^3.5.2"
+ xmlchars "^2.2.0"
-schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1:
+schema-utils@^3.0.0, schema-utils@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==
@@ -10612,6 +11693,15 @@ schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1:
ajv "^6.12.5"
ajv-keywords "^3.5.2"
+schema-utils@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.2.tgz#36c10abca6f7577aeae136c804b0c741edeadc99"
+ integrity sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==
+ dependencies:
+ "@types/json-schema" "^7.0.8"
+ ajv "^6.12.5"
+ ajv-keywords "^3.5.2"
+
schema-utils@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7"
@@ -10622,52 +11712,53 @@ schema-utils@^4.0.0:
ajv-formats "^2.1.1"
ajv-keywords "^5.0.0"
-scss-parser@^1.0.0:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/scss-parser/-/scss-parser-1.0.5.tgz#2297d688a4c300e94552f72c41fd7de092d19c4b"
- integrity sha512-RZOtvCmCnwkDo7kdcYBi807Y5EoTIxJ34AgEgJNDmOH1jl0/xG0FyYZFbH6Ga3Iwu7q8LSdxJ4C5UkzNXjQxKQ==
+scss-parser@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/scss-parser/-/scss-parser-1.0.6.tgz#cd1ba01ee32db19322c8df2badd26da8f166b1c1"
+ integrity sha512-SH3TaoaJFzfAtqs3eG1j5IuHJkeEW5rKUPIjIN+ZorLAyJLHItQGnsgwHk76v25GtLtpT9IqfAcqK4vFWdiw+w==
dependencies:
invariant "2.2.4"
+ lodash "4.17.21"
-scss-tokenizer@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.3.0.tgz#ef7edc3bc438b25cd6ffacf1aa5b9ad5813bf260"
- integrity sha512-14Zl9GcbBvOT9057ZKjpz5yPOyUWG2ojd9D5io28wHRYsOrs7U95Q+KNL87+32p8rc+LvDpbu/i9ZYjM9Q+FsQ==
+scss-tokenizer@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz#1058400ee7d814d71049c29923d2b25e61dc026c"
+ integrity sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==
dependencies:
- js-base64 "^2.4.3"
- source-map "^0.7.1"
+ js-base64 "^2.4.9"
+ source-map "^0.7.3"
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
-selfsigned@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.1.tgz#8b2df7fa56bf014d19b6007655fff209c0ef0a56"
- integrity sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==
+selfsigned@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61"
+ integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==
dependencies:
node-forge "^1"
-"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
- version "5.7.1"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
- integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
+ version "5.7.2"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
+ integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
semver@7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
-semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
- version "6.3.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
- integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
+ version "6.3.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
- version "7.3.7"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
- integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
+semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.8:
+ version "7.5.4"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
+ integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
@@ -10697,10 +11788,10 @@ serialize-javascript@^4.0.0:
dependencies:
randombytes "^2.1.0"
-serialize-javascript@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
- integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
+serialize-javascript@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c"
+ integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==
dependencies:
randombytes "^2.1.0"
@@ -10793,10 +11884,10 @@ shell-quote@^1.6.1:
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123"
integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==
-shelljs@0.3.x:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1"
- integrity sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=
+shell-quote@^1.7.3:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
+ integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
shellwords@^0.1.1:
version "0.1.1"
@@ -10819,7 +11910,7 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
-sigmund@^1.0.1, sigmund@~1.0.0:
+sigmund@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=
@@ -10829,16 +11920,16 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+signal-exit@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.1.tgz#96a61033896120ec9335d96851d902cc98f0ba2a"
+ integrity sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==
+
sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
-slash@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
- integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
-
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@@ -10915,6 +12006,15 @@ socks-proxy-agent@^6.0.0:
debug "^4.3.3"
socks "^2.6.2"
+socks-proxy-agent@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6"
+ integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==
+ dependencies:
+ agent-base "^6.0.2"
+ debug "^4.3.3"
+ socks "^2.6.2"
+
socks@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.2.tgz#ec042d7960073d40d94268ff3bb727dc685f111a"
@@ -10944,13 +12044,13 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
source-map-url "^0.4.0"
urix "^0.1.0"
-source-map-resolve@^0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2"
- integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==
+source-map-support@0.5.13:
+ version "0.5.13"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
+ integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==
dependencies:
- atob "^2.1.2"
- decode-uri-component "^0.2.0"
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
source-map-support@^0.5.6, source-map-support@~0.5.20:
version "0.5.21"
@@ -10975,10 +12075,10 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-source-map@^0.7.1, source-map@^0.7.3:
- version "0.7.3"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
- integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
+source-map@^0.7.3:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
+ integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
source-map@^0.8.0-beta.0:
version "0.8.0-beta.0"
@@ -11046,11 +12146,6 @@ spdy@^4.0.2:
select-hose "^2.0.0"
spdy-transport "^3.0.0"
-specificity@^0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
- integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
-
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -11085,10 +12180,12 @@ ssri@^8.0.0, ssri@^8.0.1:
dependencies:
minipass "^3.1.1"
-stable@^0.1.8:
- version "0.1.8"
- resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
- integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
+ssri@^9.0.0:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057"
+ integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==
+ dependencies:
+ minipass "^3.1.1"
stack-utils@^1.0.1:
version "1.0.5"
@@ -11104,6 +12201,13 @@ stack-utils@^2.0.2:
dependencies:
escape-string-regexp "^2.0.0"
+stack-utils@^2.0.3:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
+ integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==
+ dependencies:
+ escape-string-regexp "^2.0.0"
+
stackblur-canvas@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-2.2.0.tgz#cacc5924a0744b3e183eb2e6c1d8559c1a17c26e"
@@ -11112,7 +12216,7 @@ stackblur-canvas@2.2.0:
stackblur-canvas@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-1.4.1.tgz#849aa6f94b272ff26f6471fa4130ed1f7e47955b"
- integrity sha1-hJqm+UsnL/JvZHH6QTDtH35HlVs=
+ integrity sha512-TfbTympL5C1K+F/RizDkMBqH18EkUKU8V+4PphIXR+fWhZwwRi3bekP04gy2TOwOT3R6rJQJXAXFrbcZde7wow==
static-extend@^0.1.1:
version "0.1.2"
@@ -11139,11 +12243,6 @@ stdout-stream@^1.4.0:
dependencies:
readable-stream "^2.0.1"
-stealthy-require@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
- integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
-
store2@^2.14.2:
version "2.14.2"
resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068"
@@ -11170,14 +12269,6 @@ string-left-right@^4.1.0:
lodash.clonedeep "^4.5.0"
lodash.isplainobject "^4.0.6"
-string-length@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
- integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=
- dependencies:
- astral-regex "^1.0.0"
- strip-ansi "^4.0.0"
-
string-length@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
@@ -11207,7 +12298,7 @@ string-trim-spaces-only@^3.1.0:
dependencies:
"@babel/runtime" "^7.14.0"
-"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -11216,7 +12307,7 @@ string-trim-spaces-only@^3.1.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
-string-width@^3.0.0, string-width@^3.1.0:
+string-width@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
@@ -11225,6 +12316,15 @@ string-width@^3.0.0, string-width@^3.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
+string-width@^5.0.1, string-width@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
+ integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
+ dependencies:
+ eastasianwidth "^0.2.0"
+ emoji-regex "^9.2.2"
+ strip-ansi "^7.0.1"
+
string.prototype.matchall@^4.0.6:
version "4.0.7"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d"
@@ -11248,6 +12348,15 @@ string.prototype.padend@^3.0.0:
define-properties "^1.1.3"
es-abstract "^1.19.1"
+string.prototype.trim@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533"
+ integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
+
string.prototype.trimend@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0"
@@ -11257,6 +12366,15 @@ string.prototype.trimend@^1.0.5:
define-properties "^1.1.4"
es-abstract "^1.19.5"
+string.prototype.trimend@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533"
+ integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
+
string.prototype.trimstart@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef"
@@ -11266,6 +12384,15 @@ string.prototype.trimstart@^1.0.5:
define-properties "^1.1.4"
es-abstract "^1.19.5"
+string.prototype.trimstart@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4"
+ integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.20.4"
+
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -11273,11 +12400,6 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
-string_decoder@~0.10.x:
- version "0.10.31"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
- integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
-
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -11294,7 +12416,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -11308,20 +12430,20 @@ strip-ansi@^3.0.0:
dependencies:
ansi-regex "^2.0.0"
-strip-ansi@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
- integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
- dependencies:
- ansi-regex "^3.0.0"
-
-strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
+strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
ansi-regex "^4.1.0"
+strip-ansi@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
+ integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==
+ dependencies:
+ ansi-regex "^6.0.1"
+
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@@ -11354,10 +12476,10 @@ strip-indent@^3.0.0:
dependencies:
min-indent "^1.0.0"
-strip-json-comments@1.0.4, strip-json-comments@1.0.x:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
- integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=
+strip-json-comments@3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180"
+ integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==
strip-json-comments@^2.0.0:
version "2.0.1"
@@ -11369,61 +12491,62 @@ strip-json-comments@^3.0.1, strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
-style-loader@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c"
- integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==
- dependencies:
- loader-utils "^2.0.0"
- schema-utils "^3.0.0"
+style-loader@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.2.tgz#eaebca714d9e462c19aa1e3599057bc363924899"
+ integrity sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw==
style-search@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=
-stylehacks@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520"
- integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==
+stylehacks@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.0.0.tgz#9fdd7c217660dae0f62e14d51c89f6c01b3cb738"
+ integrity sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==
dependencies:
- browserslist "^4.16.6"
+ browserslist "^4.21.4"
postcss-selector-parser "^6.0.4"
-stylelint-config-prettier@9.0.3:
- version "9.0.3"
- resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-9.0.3.tgz#0dccebeff359dcc393c9229184408b08964d561c"
- integrity sha512-5n9gUDp/n5tTMCq1GLqSpA30w2sqWITSSEiAWQlpxkKGAUbjcemQ0nbkRvRUa0B1LgD3+hCvdL7B1eTxy1QHJg==
+stylelint-config-prettier@9.0.5:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-9.0.5.tgz#9f78bbf31c7307ca2df2dd60f42c7014ee9da56e"
+ integrity sha512-U44lELgLZhbAD/xy/vncZ2Pq8sh2TnpiPvo38Ifg9+zeioR+LAkHu0i6YORIOxFafZoVg0xqQwex6e6F25S5XA==
-stylelint-config-recess-order@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-recess-order/-/stylelint-config-recess-order-3.0.0.tgz#f6f378179becd3e0842101cf652a30733aac25b1"
- integrity sha512-uNXrlDz570Q7HJlrq8mNjgfO/xlKIh2hKVKEFMTG1/ih/6tDLcTbuvO1Zoo2dnQay990OAkWLDpTDOorB+hmBw==
+stylelint-config-recess-order@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recess-order/-/stylelint-config-recess-order-4.0.0.tgz#819b4b8f9bf83d23a0bc918a9bf834f42f87b12a"
+ integrity sha512-sOb+OofMryBR91CbzgV2FavpONqiIeAE7cfrgyUHqePblWBKsYzoUuWThI5EjPRA7KKeovm6ykr7twWYLeafPQ==
dependencies:
- stylelint-order "5.x"
+ stylelint-order "6.x"
-stylelint-config-recommended-scss@5.0.2:
- version "5.0.2"
- resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-5.0.2.tgz#193f483861c76a36ece24c52eb6baca4838f4a48"
- integrity sha512-b14BSZjcwW0hqbzm9b0S/ScN2+3CO3O4vcMNOw2KGf8lfVSwJ4p5TbNEXKwKl1+0FMtgRXZj6DqVUe/7nGnuBg==
+stylelint-config-recommended-scss@9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-9.0.1.tgz#7ea233ea637ac2d8f0b50d8aad236257e44e2cbb"
+ integrity sha512-qAmz/TdrqslwiMTuLM3QXeISUkfEDUXGMfRBCHm/xrkCJNnQefv+mzG2mWTsWkqcVk8HAyUkug10dwAcYp2fCQ==
dependencies:
postcss-scss "^4.0.2"
- stylelint-config-recommended "^6.0.0"
- stylelint-scss "^4.0.0"
+ stylelint-config-recommended "^10.0.1"
+ stylelint-scss "^4.4.0"
+
+stylelint-config-recommended@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-10.0.1.tgz#25a8828acf6cde87dac6db2950c8c4ed82a69ae1"
+ integrity sha512-TQ4xQ48tW4QSlODcti7pgSRqBZcUaBzuh0jPpfiMhwJKBPkqzTIAU+IrSWL/7BgXlOM90DjB7YaNgFpx8QWhuA==
stylelint-config-recommended@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz#fd2523a322836005ad9bf473d3e5534719c09f9d"
integrity sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==
-stylelint-config-sass-guidelines@9.0.1:
- version "9.0.1"
- resolved "https://registry.yarnpkg.com/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-9.0.1.tgz#3114ce780f2085ba9ea5da2b7d97a1e85e968fa7"
- integrity sha512-N06PsVsrgKijQ3YT5hqKA7x3NUkgELTRI1cbWMqcYiCGG6MjzvNk6Cb5YYA1PrvrksBV76BvY9P9bAswojVMqA==
+stylelint-config-sass-guidelines@10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-10.0.0.tgz#ace99689eb6769534c9b40d62e2a8562b1ddc9f2"
+ integrity sha512-+Rr2Dd4b72CWA4qoj1Kk+y449nP/WJsrD0nzQAWkmPPIuyVcy2GMIcfNr0Z8JJOLjRvtlkKxa49FCNXMePBikQ==
dependencies:
- postcss-scss "^4.0.2"
- stylelint-order "^5.0.0"
- stylelint-scss "^4.0.0"
+ postcss-scss "^4.0.6"
+ stylelint-scss "^4.4.0"
stylelint-config-standard@24.0.0:
version "24.0.0"
@@ -11432,106 +12555,103 @@ stylelint-config-standard@24.0.0:
dependencies:
stylelint-config-recommended "^6.0.0"
-stylelint-csstree-validator@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-csstree-validator/-/stylelint-csstree-validator-2.0.0.tgz#1bd0b609596e3d5d78c3f4f0e1fe35e184f7e774"
- integrity sha512-nAnG4CynM4P4POUCGVz/PnrByKcgdD2isKlmAccoxykkaH1IuEposiOJcUQlAp/kXD/T0ZRwlDbqMGgrHRbhog==
+stylelint-csstree-validator@2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/stylelint-csstree-validator/-/stylelint-csstree-validator-2.1.0.tgz#1170a0b769ce52149a9047b2d0a6b1a5d085a3dc"
+ integrity sha512-FKUMEz/iicwkOsY+ohstjlD9dj3qFdAVcw/oVwBDJwToUpwAh/GkvV5FXUd60DoAzn19s1TPsvUuGPbzDo0ZMw==
dependencies:
- css-tree "^2.0.1"
+ css-tree "^2.3.1"
-stylelint-order@5.x, stylelint-order@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-order/-/stylelint-order-5.0.0.tgz#abd20f6b85ac640774cbe40e70d3fe9c6fdf4400"
- integrity sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==
+stylelint-order@6.x:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/stylelint-order/-/stylelint-order-6.0.3.tgz#160b78650bd90463241b992581efee7159baefc2"
+ integrity sha512-1j1lOb4EU/6w49qZeT2SQVJXm0Ht+Qnq9GMfUa3pMwoyojIWfuA+JUDmoR97Bht1RLn4ei0xtLGy87M7d29B1w==
dependencies:
- postcss "^8.3.11"
- postcss-sorting "^7.0.1"
+ postcss "^8.4.21"
+ postcss-sorting "^8.0.2"
-stylelint-scss@4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.1.0.tgz#39b808696f8152081163d970449257ff80b5c041"
- integrity sha512-BNYTo7MMamhFOlcaAWp2dMpjg6hPyM/FFqfDIYzmYVLMmQJqc8lWRIiTqP4UX5bresj9Vo0dKC6odSh43VP2NA==
+stylelint-scss@4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.6.0.tgz#f7602d6d562bb256802e38e3fd5e49c46d2e31b6"
+ integrity sha512-M+E0BQim6G4XEkaceEhfVjP/41C9Klg5/tTPTCQVlgw/jm2tvB+OXJGaU0TDP5rnTCB62aX6w+rT+gqJW/uwjA==
dependencies:
- lodash "^4.17.21"
+ dlv "^1.1.3"
postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.1"
- postcss-selector-parser "^6.0.6"
- postcss-value-parser "^4.1.0"
+ postcss-selector-parser "^6.0.11"
+ postcss-value-parser "^4.2.0"
-stylelint-scss@^4.0.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.2.0.tgz#e25fd390ee38a7e89fcfaec2a8f9dce2ec6ddee8"
- integrity sha512-HHHMVKJJ5RM9pPIbgJ/XA67h9H0407G68Rm69H4fzFbFkyDMcTV1Byep3qdze5+fJ3c0U7mJrbj6S0Fg072uZA==
+stylelint-scss@^4.4.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.7.0.tgz#f986bf8c5a4b93eae2b67d3a3562eef822657908"
+ integrity sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==
dependencies:
- lodash "^4.17.21"
postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.1"
- postcss-selector-parser "^6.0.6"
- postcss-value-parser "^4.1.0"
+ postcss-selector-parser "^6.0.11"
+ postcss-value-parser "^4.2.0"
-stylelint@14.2.0:
- version "14.2.0"
- resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-14.2.0.tgz#da4f0f4580e66911c38c376ed82447b78e32b0fb"
- integrity sha512-i0DrmDXFNpDsWiwx6SPRs4/pyw4kvZgqpDGvsTslQMY7hpUl6r33aQvNSn6cnTg2wtZ9rreFElI7XAKpOWi1vQ==
+stylelint@14.10.0:
+ version "14.10.0"
+ resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-14.10.0.tgz#c588f5cd47cd214cf1acee5bc165961b6a3ad836"
+ integrity sha512-VAmyKrEK+wNFh9R8mNqoxEFzaa4gsHGhcT4xgkQDuOA5cjF6CaNS8loYV7gpi4tIZBPUyXesotPXzJAMN8VLOQ==
dependencies:
+ "@csstools/selector-specificity" "^2.0.2"
balanced-match "^2.0.0"
colord "^2.9.2"
cosmiconfig "^7.0.1"
- debug "^4.3.3"
- execall "^2.0.0"
- fast-glob "^3.2.7"
- fastest-levenshtein "^1.0.12"
+ css-functions-list "^3.1.0"
+ debug "^4.3.4"
+ fast-glob "^3.2.11"
+ fastest-levenshtein "^1.0.16"
file-entry-cache "^6.0.1"
- get-stdin "^8.0.0"
global-modules "^2.0.0"
- globby "^11.0.4"
+ globby "^11.1.0"
globjoin "^0.1.4"
- html-tags "^3.1.0"
+ 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.24.0"
+ known-css-properties "^0.25.0"
mathml-tag-names "^2.1.3"
meow "^9.0.0"
- micromatch "^4.0.4"
+ micromatch "^4.0.5"
normalize-path "^3.0.0"
- normalize-selector "^0.2.0"
picocolors "^1.0.0"
- postcss "^8.3.11"
+ 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.7"
- postcss-value-parser "^4.1.0"
+ postcss-selector-parser "^6.0.10"
+ postcss-value-parser "^4.2.0"
resolve-from "^5.0.0"
- specificity "^0.4.1"
string-width "^4.2.3"
strip-ansi "^6.0.1"
style-search "^0.1.0"
+ supports-hyperlinks "^2.2.0"
svg-tags "^1.0.0"
- table "^6.7.5"
+ table "^6.8.0"
v8-compile-cache "^2.3.0"
- write-file-atomic "^3.0.3"
+ write-file-atomic "^4.0.1"
-stylus-loader@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-3.0.2.tgz#27a706420b05a38e038e7cacb153578d450513c6"
- integrity sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==
+stylus-loader@^7.1.3:
+ version "7.1.3"
+ resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-7.1.3.tgz#1fdfa0d34e8c05a569bc0902e1ecdb857d764964"
+ integrity sha512-TY0SKwiY7D2kMd3UxaWKSf3xHF0FFN/FAfsSqfrhxRT/koXTwffq2cgEWDkLQz7VojMu7qEEHt5TlMjkPx9UDw==
dependencies:
- loader-utils "^1.0.2"
- lodash.clonedeep "^4.5.0"
- when "~3.6.x"
+ fast-glob "^3.2.12"
+ normalize-path "^3.0.0"
-stylus@^0.59.0:
- version "0.59.0"
- resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.59.0.tgz#a344d5932787142a141946536d6e24e6a6be7aa6"
- integrity sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==
+stylus@^0.62.0:
+ version "0.62.0"
+ resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.62.0.tgz#648a020e2bf90ed87587ab9c2f012757e977bb5d"
+ integrity sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==
dependencies:
- "@adobe/css-tools" "^4.0.1"
+ "@adobe/css-tools" "~4.3.1"
debug "^4.3.2"
glob "^7.1.6"
- sax "~1.2.4"
+ sax "~1.3.0"
source-map "^0.7.3"
supports-color@^2.0.0:
@@ -11546,13 +12666,6 @@ supports-color@^5.3.0:
dependencies:
has-flag "^3.0.0"
-supports-color@^6.1.0:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
- integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
- dependencies:
- has-flag "^3.0.0"
-
supports-color@^7.0.0, supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -11575,6 +12688,14 @@ supports-hyperlinks@^2.0.0:
has-flag "^4.0.0"
supports-color "^7.0.0"
+supports-hyperlinks@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624"
+ integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==
+ dependencies:
+ has-flag "^4.0.0"
+ supports-color "^7.0.0"
+
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
@@ -11585,20 +12706,19 @@ svg-tags@^1.0.0:
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
-svgo@^2.7.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24"
- integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==
+svgo@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.0.2.tgz#5e99eeea42c68ee0dc46aa16da093838c262fe0a"
+ integrity sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==
dependencies:
"@trysound/sax" "0.2.0"
commander "^7.2.0"
- css-select "^4.1.3"
- css-tree "^1.1.3"
- csso "^4.2.0"
+ css-select "^5.1.0"
+ css-tree "^2.2.1"
+ csso "^5.0.5"
picocolors "^1.0.0"
- stable "^0.1.8"
-"symbol-tree@>= 3.1.0 < 4.0.0", symbol-tree@^3.2.2, symbol-tree@^3.2.4:
+"symbol-tree@>= 3.1.0 < 4.0.0", symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
@@ -11613,10 +12733,10 @@ table@^5.2.3:
slice-ansi "^2.1.0"
string-width "^3.0.0"
-table@^6.7.5:
- version "6.8.0"
- resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca"
- integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==
+table@^6.8.0:
+ version "6.8.1"
+ resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf"
+ integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==
dependencies:
ajv "^8.0.1"
lodash.truncate "^4.4.2"
@@ -11646,6 +12766,18 @@ tar@^6.0.2, tar@^6.1.2:
mkdirp "^1.0.3"
yallist "^4.0.0"
+tar@^6.1.11:
+ version "6.1.14"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.14.tgz#e87926bec1cfe7c9e783a77a79f3e81c1cfa3b66"
+ integrity sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==
+ dependencies:
+ chownr "^2.0.0"
+ fs-minipass "^2.0.0"
+ minipass "^5.0.0"
+ minizlib "^2.1.1"
+ mkdirp "^1.0.3"
+ yallist "^4.0.0"
+
temp-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
@@ -11676,18 +12808,18 @@ terminal-link@^2.0.0:
ansi-escapes "^4.2.1"
supports-hyperlinks "^2.0.0"
-terser-webpack-plugin@^5.1.1, terser-webpack-plugin@^5.1.3:
- version "5.3.3"
- resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz#8033db876dd5875487213e87c627bca323e5ed90"
- integrity sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==
+terser-webpack-plugin@^5.3.7:
+ version "5.3.7"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz#ef760632d24991760f339fe9290deb936ad1ffc7"
+ integrity sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==
dependencies:
- "@jridgewell/trace-mapping" "^0.3.7"
+ "@jridgewell/trace-mapping" "^0.3.17"
jest-worker "^27.4.5"
schema-utils "^3.1.1"
- serialize-javascript "^6.0.0"
- terser "^5.7.2"
+ serialize-javascript "^6.0.1"
+ terser "^5.16.5"
-terser@^5.0.0, terser@^5.7.2:
+terser@^5.0.0:
version "5.14.2"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
@@ -11697,15 +12829,15 @@ terser@^5.0.0, terser@^5.7.2:
commander "^2.20.0"
source-map-support "~0.5.20"
-test-exclude@^5.2.3:
- version "5.2.3"
- resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0"
- integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==
+terser@^5.16.5:
+ version "5.17.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.1.tgz#948f10830454761e2eeedc6debe45c532c83fd69"
+ integrity sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==
dependencies:
- glob "^7.1.3"
- minimatch "^3.0.4"
- read-pkg-up "^4.0.0"
- require-main-filename "^2.0.0"
+ "@jridgewell/source-map" "^0.3.2"
+ acorn "^8.5.0"
+ commander "^2.20.0"
+ source-map-support "~0.5.20"
test-exclude@^6.0.0:
version "6.0.0"
@@ -11728,11 +12860,6 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
-throat@^4.0.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
- integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=
-
throat@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
@@ -11824,7 +12951,7 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
-tough-cookie@^2.2.0, tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0:
+tough-cookie@^2.2.0, tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@@ -11841,6 +12968,16 @@ tough-cookie@^4.0.0:
punycode "^2.1.1"
universalify "^0.1.2"
+tough-cookie@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"
+ integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==
+ dependencies:
+ psl "^1.1.33"
+ punycode "^2.1.1"
+ universalify "^0.2.0"
+ url-parse "^1.5.3"
+
tr46@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@@ -11855,22 +12992,27 @@ tr46@^2.1.0:
dependencies:
punycode "^2.1.1"
+tr46@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
+ integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
+ dependencies:
+ punycode "^2.1.1"
+
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
- integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
trim-newlines@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
-"true-case-path@^1.0.2":
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d"
- integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==
- dependencies:
- glob "^7.1.2"
+"true-case-path@^2.2.1":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf"
+ integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==
tsconfig-paths@^3.14.1:
version "3.14.1"
@@ -11897,6 +13039,11 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+tslib@^2.0.1:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
+ integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
+
tsutils@^3.17.1:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@@ -11971,6 +13118,20 @@ type@^2.5.0:
resolved "https://registry.yarnpkg.com/type/-/type-2.6.0.tgz#3ca6099af5981d36ca86b78442973694278a219f"
integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==
+type@^2.7.2:
+ version "2.7.2"
+ resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0"
+ integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==
+
+typed-array-length@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb"
+ integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==
+ dependencies:
+ call-bind "^1.0.2"
+ for-each "^0.3.3"
+ is-typed-array "^1.1.9"
+
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
@@ -12020,6 +13181,11 @@ unicode-match-property-value-ecmascript@^2.0.0:
resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714"
integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==
+unicode-match-property-value-ecmascript@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0"
+ integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==
+
unicode-property-aliases-ecmascript@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8"
@@ -12042,6 +13208,13 @@ unique-filename@^1.1.1:
dependencies:
unique-slug "^2.0.0"
+unique-filename@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2"
+ integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==
+ dependencies:
+ unique-slug "^3.0.0"
+
unique-slug@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
@@ -12049,6 +13222,13 @@ unique-slug@^2.0.0:
dependencies:
imurmurhash "^0.1.4"
+unique-slug@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9"
+ integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==
+ dependencies:
+ imurmurhash "^0.1.4"
+
unique-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
@@ -12061,19 +13241,16 @@ universalify@^0.1.2:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+universalify@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
+ integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
+
universalify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
-unload@2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/unload/-/unload-2.3.1.tgz#9d16862d372a5ce5cb630ad1309c2fd6e35dacfe"
- integrity sha512-MUZEiDqvAN9AIDRbbBnVYVvfcR6DrjCqeU2YQMmliFZl9uaBUjTkhuDQkBiyAy8ad5bx1TXVbqZ3gg7namsWjA==
- dependencies:
- "@babel/runtime" "^7.6.2"
- detect-node "2.1.0"
-
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -12092,6 +13269,14 @@ upath@^1.2.0:
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
+update-browserslist-db@^1.0.13:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
+ integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -12113,6 +13298,14 @@ url-loader@^4.1.1:
mime-types "^2.1.27"
schema-utils "^3.0.0"
+url-parse@^1.5.3:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
+ integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
+ dependencies:
+ querystringify "^2.1.1"
+ requires-port "^1.0.0"
+
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@@ -12138,16 +13331,16 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
-util.promisify@^1.0.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.1.1.tgz#77832f57ced2c9478174149cae9b96e9918cd54b"
- integrity sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==
+util@^0.12.0:
+ version "0.12.5"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc"
+ integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==
dependencies:
- call-bind "^1.0.0"
- define-properties "^1.1.3"
- for-each "^0.3.3"
- has-symbols "^1.0.1"
- object.getownpropertydescriptors "^2.1.1"
+ inherits "^2.0.3"
+ is-arguments "^1.0.4"
+ is-generator-function "^1.0.7"
+ is-typed-array "^1.1.3"
+ which-typed-array "^1.1.2"
utils-merge@1.0.1:
version "1.0.1"
@@ -12190,6 +13383,15 @@ v8-to-istanbul@^7.0.0:
convert-source-map "^1.6.0"
source-map "^0.7.3"
+v8-to-istanbul@^9.0.1:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265"
+ integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.12"
+ "@types/istanbul-lib-coverage" "^2.0.1"
+ convert-source-map "^1.6.0"
+
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
@@ -12242,7 +13444,7 @@ vue-hot-reload-api@^2.3.0:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
-vue-intl@^3.0.0:
+vue-intl@^3.0.0, vue-intl@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vue-intl/-/vue-intl-3.1.0.tgz#707f1f7406595c9b4afc6049254b333093be37be"
integrity sha512-0v3S5gspuYnt6j1G+KLfPUsNnjRdbMOcYrWYoSd1gYk6rX8VuOyh7NLztPrSIJt+NLs/qzLOZXxI1LORukEiqA==
@@ -12269,10 +13471,10 @@ vue-jest@^3.0.0:
tsconfig "^7.0.0"
vue-template-es2015-compiler "^1.6.0"
-vue-loader@^15.9.8:
- version "15.9.8"
- resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.8.tgz#4b0f602afaf66a996be1e534fb9609dc4ab10e61"
- integrity sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==
+vue-loader@^15.10.1:
+ version "15.10.1"
+ resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.10.1.tgz#c451c4cd05a911aae7b5dbbbc09fb913fb3cca18"
+ integrity sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==
dependencies:
"@vue/component-compiler-utils" "^3.1.0"
hash-sum "^1.0.2"
@@ -12280,20 +13482,12 @@ vue-loader@^15.9.8:
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
-vue-router@3.5.4:
- version "3.5.4"
- resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.5.4.tgz#c453c0b36bc75554de066fefc3f2a9c3212aca70"
- integrity sha512-x+/DLAJZv2mcQ7glH2oV9ze8uPwcI+H+GgTgTmb5I55bCgY3+vXWIsqbYUzbBSZnwFHEJku4eoaH/x98veyymQ==
-
-vue-style-loader@^3.0.3:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.1.2.tgz#6b66ad34998fc9520c2f1e4d5fa4091641c1597a"
- integrity sha512-ICtVdK/p+qXWpdSs2alWtsXt9YnDoYjQe0w5616j9+/EhjoxZkbun34uWgsMFnC1MhrMMwaWiImz3K2jK1Yp2Q==
- dependencies:
- hash-sum "^1.0.2"
- loader-utils "^1.0.2"
+vue-router@3.6.5:
+ version "3.6.5"
+ resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.6.5.tgz#95847d52b9a7e3f1361cb605c8e6441f202afad8"
+ integrity sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ==
-vue-style-loader@^4.1.0:
+vue-style-loader@^4.1.0, vue-style-loader@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==
@@ -12329,7 +13523,7 @@ vuex@^3.0.1:
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71"
integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
-w3c-hr-time@^1.0.1, w3c-hr-time@^1.0.2:
+w3c-hr-time@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==
@@ -12343,14 +13537,21 @@ w3c-xmlserializer@^2.0.0:
dependencies:
xml-name-validator "^3.0.0"
-walker@^1.0.7, walker@~1.0.5:
+w3c-xmlserializer@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073"
+ integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==
+ dependencies:
+ xml-name-validator "^4.0.0"
+
+walker@^1.0.7, walker@^1.0.8, walker@~1.0.5:
version "1.0.8"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==
dependencies:
makeerror "1.0.12"
-watchpack@^2.3.1:
+watchpack@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
@@ -12373,7 +13574,7 @@ web-streams-polyfill@^3.2.1:
webidl-conversions@^3.0.0, webidl-conversions@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
- integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
webidl-conversions@^4.0.2:
version "4.0.2"
@@ -12390,28 +13591,34 @@ webidl-conversions@^6.1.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
-webpack-cli@^4.9.1:
- version "4.9.2"
- resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.2.tgz#77c1adaea020c3f9e2db8aad8ea78d235c83659d"
- integrity sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==
+webidl-conversions@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
+ integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
+
+webpack-cli@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.0.2.tgz#2954c10ecb61c5d4dad6f68ee2d77f051741946c"
+ integrity sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ==
dependencies:
"@discoveryjs/json-ext" "^0.5.0"
- "@webpack-cli/configtest" "^1.1.1"
- "@webpack-cli/info" "^1.4.1"
- "@webpack-cli/serve" "^1.6.1"
+ "@webpack-cli/configtest" "^2.0.1"
+ "@webpack-cli/info" "^2.0.1"
+ "@webpack-cli/serve" "^2.0.2"
colorette "^2.0.14"
- commander "^7.0.0"
- execa "^5.0.0"
+ commander "^10.0.1"
+ cross-spawn "^7.0.3"
+ envinfo "^7.7.3"
fastest-levenshtein "^1.0.12"
import-local "^3.0.2"
- interpret "^2.2.0"
- rechoir "^0.7.0"
+ interpret "^3.1.1"
+ rechoir "^0.8.0"
webpack-merge "^5.7.3"
webpack-dev-middleware@^5.3.1:
- version "5.3.3"
- resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f"
- integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==
+ version "5.3.4"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517"
+ integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==
dependencies:
colorette "^2.0.10"
memfs "^3.4.3"
@@ -12419,15 +13626,16 @@ webpack-dev-middleware@^5.3.1:
range-parser "^1.2.1"
schema-utils "^4.0.0"
-webpack-dev-server@^4.7.3:
- version "4.9.1"
- resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.1.tgz#184607b0287c791aeaa45e58e8fe75fcb4d7e2a8"
- integrity sha512-CTMfu2UMdR/4OOZVHRpdy84pNopOuigVIsRbGX3LVDMWNP8EUgC5mUBMErbwBlHTEX99ejZJpVqrir6EXAEajA==
+webpack-dev-server@^4.13.3:
+ version "4.13.3"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.13.3.tgz#9feb740b8b56b886260bae1360286818a221bae8"
+ integrity sha512-KqqzrzMRSRy5ePz10VhjyL27K2dxqwXQLP5rAKwRJBPUahe7Z2bBWzHw37jeb8GCPKxZRO79ZdQUAPesMh/Nug==
dependencies:
"@types/bonjour" "^3.5.9"
"@types/connect-history-api-fallback" "^1.3.5"
"@types/express" "^4.17.13"
"@types/serve-index" "^1.9.1"
+ "@types/serve-static" "^1.13.10"
"@types/sockjs" "^0.3.33"
"@types/ws" "^8.5.1"
ansi-html-community "^0.0.8"
@@ -12435,25 +13643,26 @@ webpack-dev-server@^4.7.3:
chokidar "^3.5.3"
colorette "^2.0.10"
compression "^1.7.4"
- connect-history-api-fallback "^1.6.0"
+ connect-history-api-fallback "^2.0.0"
default-gateway "^6.0.3"
express "^4.17.3"
graceful-fs "^4.2.6"
html-entities "^2.3.2"
http-proxy-middleware "^2.0.3"
ipaddr.js "^2.0.1"
+ launch-editor "^2.6.0"
open "^8.0.9"
p-retry "^4.5.0"
rimraf "^3.0.2"
schema-utils "^4.0.0"
- selfsigned "^2.0.1"
+ selfsigned "^2.1.1"
serve-index "^1.9.1"
sockjs "^0.3.24"
spdy "^4.0.2"
webpack-dev-middleware "^5.3.1"
- ws "^8.4.2"
+ ws "^8.13.0"
-webpack-merge@^5.7.3:
+webpack-merge@^5.7.3, webpack-merge@^5.8.0:
version "5.8.0"
resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61"
integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==
@@ -12461,7 +13670,7 @@ webpack-merge@^5.7.3:
clone-deep "^4.0.1"
wildcard "^2.0.0"
-webpack-sources@^1.1.0, webpack-sources@^1.4.3:
+webpack-sources@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
@@ -12474,22 +13683,22 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
-webpack@^5.73.0:
- version "5.73.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38"
- integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==
+webpack@^5.81.0:
+ version "5.81.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.81.0.tgz#27a2e8466c8b4820d800a8d90f06ef98294f9956"
+ integrity sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==
dependencies:
"@types/eslint-scope" "^3.7.3"
- "@types/estree" "^0.0.51"
- "@webassemblyjs/ast" "1.11.1"
- "@webassemblyjs/wasm-edit" "1.11.1"
- "@webassemblyjs/wasm-parser" "1.11.1"
- acorn "^8.4.1"
+ "@types/estree" "^1.0.0"
+ "@webassemblyjs/ast" "^1.11.5"
+ "@webassemblyjs/wasm-edit" "^1.11.5"
+ "@webassemblyjs/wasm-parser" "^1.11.5"
+ acorn "^8.7.1"
acorn-import-assertions "^1.7.6"
browserslist "^4.14.5"
chrome-trace-event "^1.0.2"
- enhanced-resolve "^5.9.3"
- es-module-lexer "^0.9.0"
+ enhanced-resolve "^5.13.0"
+ es-module-lexer "^1.2.1"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
@@ -12498,10 +13707,10 @@ webpack@^5.73.0:
loader-runner "^4.2.0"
mime-types "^2.1.27"
neo-async "^2.6.2"
- schema-utils "^3.1.0"
+ schema-utils "^3.1.2"
tapable "^2.1.1"
- terser-webpack-plugin "^5.1.3"
- watchpack "^2.3.1"
+ terser-webpack-plugin "^5.3.7"
+ watchpack "^2.4.0"
webpack-sources "^3.2.3"
websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
@@ -12518,34 +13727,53 @@ websocket-extensions@>=0.1.1:
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
-whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
+whatwg-encoding@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==
dependencies:
iconv-lite "0.4.24"
-whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
+whatwg-encoding@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
+ integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==
+ dependencies:
+ iconv-lite "0.6.3"
+
+whatwg-mimetype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
+whatwg-mimetype@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
+ integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==
+
+whatwg-url@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
+ integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
+ dependencies:
+ tr46 "^3.0.0"
+ webidl-conversions "^7.0.0"
+
whatwg-url@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-2.0.1.tgz#5396b2043f020ee6f704d9c45ea8519e724de659"
- integrity sha1-U5ayBD8CDub3BNnEXqhRnnJN5lk=
+ integrity sha512-sX+FT4N6iR0ZiqGqyDEKklyfMGR99zvxZD+LQ8IGae5uVGswQ7DOeLPB5KgJY8FzkwSzwqOXLQeVQvtOTSQU9Q==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
-whatwg-url@^6.4.1:
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
- integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
- lodash.sortby "^4.7.0"
- tr46 "^1.0.1"
- webidl-conversions "^4.0.2"
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
whatwg-url@^7.0.0:
version "7.1.0"
@@ -12565,11 +13793,6 @@ whatwg-url@^8.0.0, whatwg-url@^8.4.0, whatwg-url@^8.5.0:
tr46 "^2.1.0"
webidl-conversions "^6.1.0"
-when@~3.6.x:
- version "3.6.4"
- resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e"
- integrity sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=
-
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
@@ -12586,7 +13809,19 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
-which@^1.2.9, which@^1.3.0, which@^1.3.1:
+which-typed-array@^1.1.2, which-typed-array@^1.1.9:
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
+ integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
+ dependencies:
+ available-typed-arrays "^1.0.5"
+ call-bind "^1.0.2"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ has-tostringtag "^1.0.0"
+ is-typed-array "^1.1.10"
+
+which@^1.2.9, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -12600,7 +13835,7 @@ which@^2.0.1, which@^2.0.2:
dependencies:
isexe "^2.0.0"
-wide-align@^1.1.2, wide-align@^1.1.5:
+wide-align@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
@@ -12613,29 +13848,29 @@ wildcard@^2.0.0:
integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==
word-wrap@~1.2.3:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
- integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f"
+ integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==
-workbox-background-sync@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9"
- integrity sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==
+workbox-background-sync@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz#2b84b96ca35fec976e3bd2794b70e4acec46b3a5"
+ integrity sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA==
dependencies:
idb "^7.0.1"
- workbox-core "6.5.4"
+ workbox-core "7.0.0"
-workbox-broadcast-update@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz#8441cff5417cd41f384ba7633ca960a7ffe40f66"
- integrity sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==
+workbox-broadcast-update@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-7.0.0.tgz#7f611ca1a94ba8ac0aa40fa171c9713e0f937d22"
+ integrity sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "7.0.0"
-workbox-build@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.5.4.tgz#7d06d31eb28a878817e1c991c05c5b93409f0389"
- integrity sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==
+workbox-build@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-7.0.0.tgz#02ab5ef2991b3369b8b9395703f08912212769b4"
+ integrity sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg==
dependencies:
"@apideck/better-ajv-errors" "^0.3.1"
"@babel/core" "^7.11.1"
@@ -12659,141 +13894,142 @@ workbox-build@6.5.4:
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"
-
-workbox-cacheable-response@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz#a5c6ec0c6e2b6f037379198d4ef07d098f7cf137"
- integrity sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==
- dependencies:
- workbox-core "6.5.4"
-
-workbox-core@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.5.4.tgz#df48bf44cd58bb1d1726c49b883fb1dffa24c9ba"
- integrity sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==
-
-workbox-expiration@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.5.4.tgz#501056f81e87e1d296c76570bb483ce5e29b4539"
- integrity sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==
+ workbox-background-sync "7.0.0"
+ workbox-broadcast-update "7.0.0"
+ workbox-cacheable-response "7.0.0"
+ workbox-core "7.0.0"
+ workbox-expiration "7.0.0"
+ workbox-google-analytics "7.0.0"
+ workbox-navigation-preload "7.0.0"
+ workbox-precaching "7.0.0"
+ workbox-range-requests "7.0.0"
+ workbox-recipes "7.0.0"
+ workbox-routing "7.0.0"
+ workbox-strategies "7.0.0"
+ workbox-streams "7.0.0"
+ workbox-sw "7.0.0"
+ workbox-window "7.0.0"
+
+workbox-cacheable-response@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz#ee27c036728189eed69d25a135013053277482d2"
+ integrity sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g==
+ dependencies:
+ workbox-core "7.0.0"
+
+workbox-core@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.0.0.tgz#dec114ec923cc2adc967dd9be1b8a0bed50a3545"
+ integrity sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==
+
+workbox-expiration@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-7.0.0.tgz#3d90bcf2a7577241de950f89784f6546b66c2baa"
+ integrity sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ==
dependencies:
idb "^7.0.1"
- workbox-core "6.5.4"
+ workbox-core "7.0.0"
-workbox-google-analytics@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz#c74327f80dfa4c1954cbba93cd7ea640fe7ece7d"
- integrity sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==
+workbox-google-analytics@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-7.0.0.tgz#603b2c4244af1e85de0fb26287d4e17d3293452a"
+ integrity sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==
dependencies:
- workbox-background-sync "6.5.4"
- workbox-core "6.5.4"
- workbox-routing "6.5.4"
- workbox-strategies "6.5.4"
+ workbox-background-sync "7.0.0"
+ workbox-core "7.0.0"
+ workbox-routing "7.0.0"
+ workbox-strategies "7.0.0"
-workbox-navigation-preload@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz#ede56dd5f6fc9e860a7e45b2c1a8f87c1c793212"
- integrity sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==
+workbox-navigation-preload@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-7.0.0.tgz#4913878dbbd97057181d57baa18d2bbdde085c6c"
+ integrity sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "7.0.0"
-workbox-precaching@6.5.4, workbox-precaching@^6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.5.4.tgz#740e3561df92c6726ab5f7471e6aac89582cab72"
- integrity sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==
+workbox-precaching@7.0.0, workbox-precaching@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-7.0.0.tgz#3979ba8033aadf3144b70e9fe631d870d5fbaa03"
+ integrity sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA==
dependencies:
- workbox-core "6.5.4"
- workbox-routing "6.5.4"
- workbox-strategies "6.5.4"
+ workbox-core "7.0.0"
+ workbox-routing "7.0.0"
+ workbox-strategies "7.0.0"
-workbox-range-requests@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz#86b3d482e090433dab38d36ae031b2bb0bd74399"
- integrity sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==
+workbox-range-requests@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-7.0.0.tgz#97511901e043df27c1aa422adcc999a7751f52ed"
+ integrity sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "7.0.0"
-workbox-recipes@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.5.4.tgz#cca809ee63b98b158b2702dcfb741b5cc3e24acb"
- integrity sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==
+workbox-recipes@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-7.0.0.tgz#1a6a01c8c2dfe5a41eef0fed3fe517e8a45c6514"
+ integrity sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww==
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"
+ workbox-cacheable-response "7.0.0"
+ workbox-core "7.0.0"
+ workbox-expiration "7.0.0"
+ workbox-precaching "7.0.0"
+ workbox-routing "7.0.0"
+ workbox-strategies "7.0.0"
-workbox-routing@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.5.4.tgz#6a7fbbd23f4ac801038d9a0298bc907ee26fe3da"
- integrity sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==
+workbox-routing@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-7.0.0.tgz#6668438a06554f60645aedc77244a4fe3a91e302"
+ integrity sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "7.0.0"
-workbox-strategies@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.5.4.tgz#4edda035b3c010fc7f6152918370699334cd204d"
- integrity sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==
+workbox-strategies@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-7.0.0.tgz#dcba32b3f3074476019049cc490fe1a60ea73382"
+ integrity sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "7.0.0"
-workbox-streams@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.5.4.tgz#1cb3c168a6101df7b5269d0353c19e36668d7d69"
- integrity sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==
+workbox-streams@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-7.0.0.tgz#36722aecd04785f88b6f709e541c094fc658c0f9"
+ integrity sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ==
dependencies:
- workbox-core "6.5.4"
- workbox-routing "6.5.4"
+ workbox-core "7.0.0"
+ workbox-routing "7.0.0"
-workbox-sw@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.5.4.tgz#d93e9c67924dd153a61367a4656ff4d2ae2ed736"
- integrity sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==
+workbox-sw@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-7.0.0.tgz#7350126411e3de1409f7ec243df8d06bb5b08b86"
+ integrity sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==
-workbox-webpack-plugin@^6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz#baf2d3f4b8f435f3469887cf4fba2b7fac3d0fd7"
- integrity sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==
+workbox-webpack-plugin@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-7.0.0.tgz#6c61661a2cacde1239192a5877a041a2943d1a55"
+ integrity sha512-R1ZzCHPfzeJjLK2/TpKUhxSQ3fFDCxlWxgRhhSjMQLz3G2MlBnyw/XeYb34e7SGgSv0qG22zEhMIzjMNqNeKbw==
dependencies:
fast-json-stable-stringify "^2.1.0"
pretty-bytes "^5.4.1"
upath "^1.2.0"
webpack-sources "^1.4.3"
- workbox-build "6.5.4"
+ workbox-build "7.0.0"
-workbox-window@6.5.4, workbox-window@^6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.5.4.tgz#d991bc0a94dff3c2dbb6b84558cff155ca878e91"
- integrity sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==
+workbox-window@7.0.0, workbox-window@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-7.0.0.tgz#a683ab33c896e4f16786794eac7978fc98a25d08"
+ integrity sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==
dependencies:
"@types/trusted-types" "^2.0.2"
- workbox-core "6.5.4"
+ workbox-core "7.0.0"
-wrap-ansi@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
- integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+ name wrap-ansi-cjs
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
- ansi-styles "^3.2.0"
- string-width "^3.0.0"
- strip-ansi "^5.0.0"
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
wrap-ansi@^6.2.0:
version "6.2.0"
@@ -12804,30 +14040,21 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+wrap-ansi@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
+ integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
+ ansi-styles "^6.1.0"
+ string-width "^5.0.1"
+ strip-ansi "^7.0.1"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
- integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-
-write-file-atomic@2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529"
- integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==
- dependencies:
- graceful-fs "^4.1.11"
- imurmurhash "^0.1.4"
- signal-exit "^3.0.2"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
-write-file-atomic@^3.0.0, write-file-atomic@^3.0.3:
+write-file-atomic@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
@@ -12837,6 +14064,14 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3:
signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5"
+write-file-atomic@^4.0.1, write-file-atomic@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd"
+ integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==
+ dependencies:
+ imurmurhash "^0.1.4"
+ signal-exit "^3.0.7"
+
write@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
@@ -12844,22 +14079,15 @@ write@1.0.3:
dependencies:
mkdirp "^0.5.1"
-ws@^5.2.0:
- version "5.2.3"
- resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.3.tgz#05541053414921bc29c63bee14b8b0dd50b07b3d"
- integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==
- dependencies:
- async-limiter "~1.0.0"
-
ws@^7.4.6:
version "7.5.8"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a"
integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==
-ws@^8.4.2:
- version "8.7.0"
- resolved "https://registry.yarnpkg.com/ws/-/ws-8.7.0.tgz#eaf9d874b433aa00c0e0d8752532444875db3957"
- integrity sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg==
+ws@^8.11.0, ws@^8.13.0:
+ version "8.13.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
+ integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
xhr@^2.0.1:
version "2.6.0"
@@ -12874,13 +14102,18 @@ xhr@^2.0.1:
"xml-name-validator@>= 2.0.1 < 3.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
- integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=
+ integrity sha512-jRKe/iQYMyVJpzPH+3HL97Lgu5HrCfii+qSo+TfjKHtOnvbnvdVfMYrn9Q34YV81M2e5sviJlI6Ko9y+nByzvA==
xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
+xml-name-validator@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
+ integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
+
xml-parse-from-string@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28"
@@ -12894,10 +14127,10 @@ xml2js@^0.4.5:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
-xml@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.0.tgz#de3ee912477be2f250b60f612f34a8c4da616efe"
- integrity sha1-3j7pEkd74vJQtg9hLzSoxNphbv4=
+xml@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
+ integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==
xmlbuilder@~11.0.0:
version "11.0.1"
@@ -12914,6 +14147,11 @@ xmldom@^0.1.22:
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
+xstate@^4.38.3:
+ version "4.38.3"
+ resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.3.tgz#4e15e7ad3aa0ca1eea2010548a5379966d8f1075"
+ integrity sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw==
+
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
@@ -12934,24 +14172,21 @@ yallist@^2.1.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
-yaml@^1.10.0, yaml@^1.10.2:
+yaml@^1.10.0:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
-yargs-parser@^13.1.2:
- version "13.1.2"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
- integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
- dependencies:
- camelcase "^5.0.0"
- decamelize "^1.2.0"
-
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
@@ -12970,21 +14205,10 @@ yargs-parser@^21.0.0:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35"
integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==
-yargs@^13.3.0:
- version "13.3.2"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
- integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
- dependencies:
- cliui "^5.0.0"
- find-up "^3.0.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 "^3.0.0"
- which-module "^2.0.0"
- y18n "^4.0.0"
- yargs-parser "^13.1.2"
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^15.4.1:
version "15.4.1"
@@ -13016,6 +14240,19 @@ yargs@^17.2.1:
y18n "^5.0.5"
yargs-parser "^21.0.0"
+yargs@^17.3.1:
+ version "17.7.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+ dependencies:
+ cliui "^8.0.1"
+ 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.1.1"
+
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"