diff --git a/.eslintrc.json b/.eslintrc.json index 009e9db2fe..22dbc24aab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,6 +26,7 @@ "rules": { "func-names": "off", "indent": ["error", 4], + "react/jsx-indent": ["error", 4], "new-cap": "off", "no-else-return": "off", "no-shadow": "error", @@ -40,23 +41,16 @@ "camelcase": "off", "comma-dangle": "off", "consistent-return": "off", - "curly": "off", "eqeqeq": "off", "function-call-argument-newline": "off", "function-paren-newline": "off", - "implicit-arrow-linebreak": "off", "import/extensions": "off", "import/no-amd": "off", "import/no-dynamic-require": "off", "import/no-unresolved": "off", - "linebreak-style": "off", - "lines-around-directive": "off", "max-len": "off", - "newline-per-chained-call": "off", "no-console": "off", "no-lonely-if": "off", - "no-multi-spaces": "off", - "no-multiple-empty-lines": "off", "no-param-reassign": "off", "no-proto": "off", "no-prototype-builtins": "off", @@ -70,20 +64,14 @@ "no-use-before-define": "off", "no-useless-escape": "off", "no-var": "off", - "object-curly-newline": "off", "object-shorthand": "off", - "operator-linebreak": "off", "prefer-arrow-callback": "off", "prefer-destructuring": "off", "prefer-rest-params": "off", "prefer-template": "off", - "quotes": "off", "radix": "off", - "react/jsx-indent": "off", - "react/jsx-indent-props": "off", - "react/jsx-wrap-multilines": "off", + "react/jsx-indent-props": ["error", 4], "react/prop-types": "off", - "semi": "off", "vars-on-top": "off" } } diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 02731cb3b4..06acb35aae 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -46,3 +46,7 @@ openedx/features/discounts/ # Ping tCRIL On-call if someone uses the QuickStart # https://docs.openedx.org/en/latest/developers/quickstarts/first_openedx_pr.html lms/templates/dashboard.html @openedx/tcril-oncall + +# Ensure minimal.yml stays minimal, this could be a team in the future +# but it's just me for now, others can sign up if they care as well. +lms/envs/minimal.yml @feanil diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..d0fde72ac1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + # Adding new check for github-actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + reviewers: + - "openedx/arbi-bom" diff --git a/.github/workflows/check-for-tutorial-prs.yml b/.github/workflows/check-for-tutorial-prs.yml index 6920542ac1..5d64a50573 100644 --- a/.github/workflows/check-for-tutorial-prs.yml +++ b/.github/workflows/check-for-tutorial-prs.yml @@ -26,7 +26,7 @@ jobs: uses: actions/checkout@v3 - name: Comment PR - uses: thollander/actions-comment-pull-request@v1 + uses: thollander/actions-comment-pull-request@v2 with: message: | Thank you for your pull request! Congratulations on completing the Open edX tutorial! A team member will be by to take a look shortly. diff --git a/.github/workflows/ci-static-analysis.yml b/.github/workflows/ci-static-analysis.yml index 94b6b30600..dd21eb3cac 100644 --- a/.github/workflows/ci-static-analysis.yml +++ b/.github/workflows/ci-static-analysis.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -30,7 +30,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/development.txt') }} diff --git a/.github/workflows/docs-build-check.yml b/.github/workflows/docs-build-check.yml index 10dd000ce0..d298b85101 100644 --- a/.github/workflows/docs-build-check.yml +++ b/.github/workflows/docs-build-check.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -34,7 +34,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/development.txt') }} diff --git a/.github/workflows/js-tests.yml b/.github/workflows/js-tests.yml index 743b5286d8..10066a2131 100644 --- a/.github/workflows/js-tests.yml +++ b/.github/workflows/js-tests.yml @@ -44,7 +44,7 @@ jobs: run: sudo apt-get update && sudo apt-get install libxmlsec1-dev ubuntu-restricted-extras xvfb - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -55,7 +55,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/base.txt') }} diff --git a/.github/workflows/lint-imports.yml b/.github/workflows/lint-imports.yml index 63caae452f..3bc7d2bf9d 100644 --- a/.github/workflows/lint-imports.yml +++ b/.github/workflows/lint-imports.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.8' @@ -33,7 +33,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/development.txt') }} diff --git a/.github/workflows/migrations-check-mysql8.yml b/.github/workflows/migrations-check-mysql8.yml index 627bceb9ba..dd681f292f 100644 --- a/.github/workflows/migrations-check-mysql8.yml +++ b/.github/workflows/migrations-check-mysql8.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -37,7 +37,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/development.txt') }} diff --git a/.github/workflows/migrations-check.yml b/.github/workflows/migrations-check.yml index fc4a48876a..8bff55c0f6 100644 --- a/.github/workflows/migrations-check.yml +++ b/.github/workflows/migrations-check.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -37,7 +37,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/development.txt') }} diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index 0f1dbd750d..3a9dadfeea 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -37,7 +37,7 @@ jobs: run: sudo apt-get update && sudo apt-get install libxmlsec1-dev - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.8 @@ -48,7 +48,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/development.txt') }} diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index f6e99ffc92..7e441e2dbe 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -30,7 +30,7 @@ jobs: run: sudo apt-get update && sudo apt-get install libxmlsec1-dev - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -49,7 +49,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/testing.txt') }} diff --git a/.github/workflows/static-assets-check.yml b/.github/workflows/static-assets-check.yml index 6bc878102e..c4da37f2d1 100644 --- a/.github/workflows/static-assets-check.yml +++ b/.github/workflows/static-assets-check.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -46,7 +46,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/development.txt') }} diff --git a/.github/workflows/unit-test-shards.json b/.github/workflows/unit-test-shards.json index c9df5c1fb6..c71bf4557a 100644 --- a/.github/workflows/unit-test-shards.json +++ b/.github/workflows/unit-test-shards.json @@ -126,6 +126,7 @@ "openedx/core/djangoapps/lang_pref/", "openedx/core/djangoapps/models/", "openedx/core/djangoapps/monkey_patch/", + "openedx/core/djangoapps/notifications/", "openedx/core/djangoapps/oauth_dispatch/", "openedx/core/djangoapps/olx_rest_api/", "openedx/core/djangoapps/password_policy/", diff --git a/.github/workflows/unit-tests-gh-hosted.yml b/.github/workflows/unit-tests-gh-hosted.yml index fc5f9bee63..5062df625c 100644 --- a/.github/workflows/unit-tests-gh-hosted.yml +++ b/.github/workflows/unit-tests-gh-hosted.yml @@ -17,23 +17,24 @@ jobs: python-version: [ '3.8' ] django-version: - "pinned" - shard_name: [ - "lms-1", - "lms-2", - "lms-3", - "lms-4", - "lms-5", - "lms-6", - "openedx-1", - "openedx-2", - "openedx-3", - "openedx-4", - "cms-1", - "cms-2", - "common-1", - "common-2", - "common-3", - ] + # When updating the shards, remember to make the same changes in + # .github/workflows/unit-tests.yml + shard_name: + - "lms-1" + - "lms-2" + - "lms-3" + - "lms-4" + - "lms-5" + - "lms-6" + - "openedx-1" + - "openedx-2" + - "openedx-3" + - "openedx-4" + - "cms-1" + - "cms-2" + - "common-1" + - "common-2" + - "xmodule-1" name: gh-hosted-python-${{ matrix.python-version }},django-${{ matrix.django-version }},${{ matrix.shard_name }} steps: - uses: actions/checkout@v2 @@ -47,7 +48,7 @@ jobs: mongodb-version: 4.4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -58,7 +59,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/testing.txt') }} @@ -92,7 +93,7 @@ jobs: run: sudo apt-get update && sudo apt-get install libxmlsec1-dev - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -103,7 +104,7 @@ jobs: - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/edx/testing.txt') }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 69a03bbeaf..c66c30d7be 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -18,6 +18,8 @@ jobs: django-version: - "pinned" #- "4.0" + # When updating the shards, remember to make the same changes in + # .github/workflows/unit-tests-gh-hosted.yml shard_name: - "lms-1" - "lms-2" @@ -80,7 +82,7 @@ jobs: # https://github.com/orgs/community/discussions/33579 success: name: Tests successful - if: always() + if: (github.repository == 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == false)) needs: - run-tests runs-on: ubuntu-latest diff --git a/.github/workflows/upgrade-one-python-dependency.yml b/.github/workflows/upgrade-one-python-dependency.yml new file mode 100644 index 0000000000..1802fa75ac --- /dev/null +++ b/.github/workflows/upgrade-one-python-dependency.yml @@ -0,0 +1,97 @@ +name: Upgrade one Python dependency + +on: + workflow_dispatch: + inputs: + branch: + description: 'Target branch to create requirements PR against' + required: true + default: 'master' + type: string + package: + description: 'Name of package to upgrade' + required: true + type: string + change_desc: + description: | + Description of change, for commit message and PR. (What does the new version add or fix?) + default: '' + type: string + +defaults: + run: + shell: bash # making this explicit opts into -e -o pipefail + +jobs: + upgrade-one-python-dependency-workflow: + runs-on: ubuntu-20.04 + + steps: + - name: Check out target branch + uses: actions/checkout@v3 + with: + ref: "${{ inputs.branch }}" + + - name: Set up Python environment + uses: actions/setup-python@v4 + with: + python-version: "3.8" + + - name: Run make upgrade-package + env: + PACKAGE: "${{ inputs.package }}" + run: | + make upgrade-package package="$PACKAGE" + + - name: PR preflight + env: + CHANGE_DESC: "${{ inputs.change_desc }}" + run: | + if git diff --exit-code; then + # Fail early (and avoid quiet failure of create-pull-request action) + echo "Error: No changes, so not creating PR." | tee -a "$GITHUB_STEP_SUMMARY" + exit 1 + else + # There are changes to commit, so prep some info for the PR. + + # This is honestly a lot to go through just to say "add two newlines + # on the end if the variable isn't empty" but I guess this is what we + # have to do to get a conditional delimiter. + if [[ -z "$CHANGE_DESC" ]]; then + echo "body_prefix=" >> "$GITHUB_ENV" + else + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "body_prefix<<$EOF" >> "$GITHUB_ENV" + echo "$CHANGE_DESC" >> "$GITHUB_ENV" + echo "" >> "$GITHUB_ENV" + echo "" >> "$GITHUB_ENV" + echo "$EOF" >> "$GITHUB_ENV" + fi + fi + + - name: Make a PR + id: make-pr + uses: peter-evans/create-pull-request@v5 + with: + branch: "${{ github.triggering_actor }}/upgrade-${{ inputs.package }}" + branch-suffix: short-commit-hash + add-paths: requirements + commit-message: | + feat: Upgrade Python dependency ${{ inputs.package }} + + ${{ env.body_prefix }}Commit generated by workflow `${{ github.workflow_ref }}` + title: "feat: Upgrade Python dependency ${{ inputs.package }}" + body: | + ${{ env.body_prefix }}PR generated by workflow `${{ github.workflow_ref }}` on behalf of @${{ github.triggering_actor }}. + assignees: "${{ github.triggering_actor }}" + + - name: Job summary + env: + PR_URL: "${{ steps.make-pr.outputs.pull-request-url }}" + run: | + if [[ -z "$PR_URL" ]]; then + echo "PR not created; see log for more information" | tee -a "$GITHUB_STEP_SUMMARY" + exit 1 + else + echo "PR created or updated: $PR_URL" | tee -a "$GITHUB_STEP_SUMMARY" + fi diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 06d24f0b4b..0000000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,227 +0,0 @@ -######################## -Contributing to the Open edX Platform -######################## - -Contributions to the Open edX Platform are very welcome, and strongly encouraged! We've -put together `some documentation that describes our contribution process`_, -but here's a step-by-step guide that should help you get started. - -.. _some documentation that describes our contribution process: https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/index.html - -Step 0: Join the Conversation -============================= - -Got an idea for how to improve the codebase? Fantastic, we'd love to hear about -it! Before you dive in and spend a lot of time and effort making a pull request, -it's a good idea to discuss your idea with other interested developers and/or the -edX product team. You may get some valuable feedback that changes how you think -about your idea, or you may find other developers who have the same idea and want -to work together. - -Documentation -------------- - -The Open edX documentation is at `https://docs.edx.org -`_. A number of topics are covered there, from -operations to development. - -JIRA ----- - -If you've got an idea for a new feature or new functionality for an existing feature, -please start a discussion in the `development `__ -category of the discussion forum list to get feedback from the community about the idea -and your implementation choices. - -If you then plan to contribute your code upstream, please `start a discussion on JIRA`_ -(you may first need to `create a free JIRA account`_). -Start a discussion by visiting the JIRA website and clicking the "Create" button at the -top of the page. Choose the project "Open Source Pull Requests" and the issue type -"Feature Proposal". In the description give us as much detail as you can for the feature -or functionality you are thinking about implementing. Include a link to any relevant -forum discussions about your idea. We encourage you to do this before you begin -implementing your feature, in order to get valuable feedback from the edX product team -early on in your journey and increase the likelihood of a successful pull request. - -.. _start a discussion on JIRA: https://openedx.atlassian.net/secure/Dashboard.jspa -.. _create a free JIRA account: https://openedx.atlassian.net/admin/users/sign-up - -.. _forum: - -Discussion forum ----------------- - -To ask technical questions and chat with the community, do not hesitate to join the -`Open edX discussion forum `__. There are different -categories for different topics: - -- `Site operators `__ to get help running production sites of Open edX -- `Development `__, where Open edX developers - unite -- `Community `__ to discuss organizational - matters in the open source community -- `Announcements `__ where official Open edX - announcement are made -- `Educators `__, to discuss online learning in general - -Slack ------ - -To talk with others in the Open edX community, join us on `Slack`_. -`Sign up for a free account`_ and join the conversation! -The group tends to be most active Monday through Friday -between 13:00 and 21:00 UTC (9am to 5pm US Eastern time), -but interesting conversations can happen at any time. -There are many different channels available for different topics, including: - -* ``#events`` for upcoming events related to Open edX project -* ``#content`` for discussions about course content and creating the best courses - -And lots more! You can also make your own channels to discuss new topics. - -Note that Slack is no longer the recommended communication channel to ask about -technical issues. To do so, you should instead join the `official forum <#forum>`__. - -.. _Slack: https://slack.com/ -.. _Sign up for a free account: https://openedx.org/slack - -Byte-sized Tasks & Bugs ------------------------ - -If you are contributing for the first time and want a gentle introduction, -or if you aren't sure what to work on, have a look at the list of -`byte-sized bugs and tasks`_ in the tracker. These tasks are selected for their -small size, and usually don't require a broad knowledge of the edX platform. -It makes them good candidates for a first task, allowing you to focus on getting -familiar with the development environment and the contribution process. - -.. _byte-sized bugs and tasks: https://github.com/search?q=user%3Aopenedx+label%3A%22help+wanted%22&type=Issues&ref=advsearch&l=&l= - -Once you have identified a bug or task, `create an account on the tracker`_ and -then comment on the ticket to indicate that you are working on it. Don't hesitate -to ask clarifying questions on the ticket as needed, too, if anything is unclear. - -.. _create an account on the tracker: https://openedx.atlassian.net/admin/users/sign-up - -Step 1: Sign a Contribution Agreement -===================================== - -Before edX can accept any code contributions from you, you'll need to sign the -`Individual Contributor Agreement`_. This confirms that you have the authority -to contribute the code in the pull request and ensures that edX can re-license -it. - -.. _Individual Contributor Agreement: https://openedx.org/cla - -If you will be contributing code on behalf of your employer or another -institution you are affiliated with, please reach out by email to oscm@tcril.org -to request the Entity Contributor Agreement. - -Once we have received and processed your agreement, we will reach out to you by -email to confirm. After that we can begin reviewing and merging your work. - -Step 2: Fork, Commit, and Pull Request -====================================== - -GitHub has some great documentation on `how to fork a git repository`_. Once -you've done that, make your changes and `send us a pull request`_! Be sure to -include a detailed description for your pull request, so that a community -manager can understand *what* change you're making, *why* you're making it, *how* -it should work now, and how you can *test* that it's working correctly. - -.. _how to fork a git repository: https://help.github.com/articles/fork-a-repo -.. _send us a pull request: https://help.github.com/articles/creating-a-pull-request - -Step 3: Meet PR Requirements -============================ - -Our `contributor documentation`_ includes a long list of requirements that pull -requests must meet in order to be reviewed by a core committer. These requirements -include things like documentation and passing tests: see the -`contributor documentation`_ page for the full list. - -.. _contributor documentation: https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/contributor.html - - -Areas of particular concern with their own detailed guidelines are: - -* `Accessibility`_: making sure our applications can - be used by people with disabilities, in keeping with the edX - `website accessibility policy`_. -* `Internationalization`_: enabling translation for use - around the world. - - -.. _Accessibility: https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/conventions/accessibility.html -.. _website accessibility policy: https://www.edx.org/accessibility -.. _Internationalization: https://edx.readthedocs.io/projects/edx-developer-guide/en/latest/internationalization/index.html - -Step 4: Approval by Community Manager and Product Owner -======================================================= - -A community manager will read the description of your pull request. If the -description is understandable, the community manager will send the pull request -to a product owner. The product owner will evaluate if the pull request is a -good idea for the Open edX platform, and if not, your pull request will be rejected. This -is another good reason why you should discuss your ideas with other members -of the community before working on a pull request! - -Step 5: Code Review by Core Committer(s) -======================================== - -If your pull request meets the requirements listed in the -`contributor documentation`_, and it hasn't been rejected by a product owner, -then it will be scheduled for code review by one or more core committers. This -process sometimes takes awhile: most of the core committers on the project -are employees of edX, and they have to balance their time between code review -and new development. - -Once the code review process has started, please be responsive to comments on -the pull request, so we can keep the review process moving forward. -If you are unable to respond for a few days, that's fine, but -please add a comment informing us of that -- otherwise, it looks like you're -abandoning your work! - -Step 6: Merge! -============== - -Once the core committers are satisfied that your pull request is ready to go, -one of them will merge it for you. Your code will end up on the edX production -servers in the next release, which usually which happens every week. Congrats! - - -############################ -Expectations We Have of You -############################ - -By opening up a pull request, we expect the following things: - -1. You've read and understand the instructions in this contributing file and - the contribution process documentation. - -2. You are ready to engage with the edX community. Engaging means you will be - prompt in following up with review comments and critiques. Do not open up a - pull request right before a vacation or heavy workload that will render you - unable to participate in the review process. - -3. If you have questions, you will ask them by either commenting on the pull - request or asking us in the discussion forum or on Slack. - -4. If you do not respond to comments on your pull request within 7 days, we - will close it. You are welcome to re-open it when you are ready to engage. - -############################ -Expectations You Have of Us -############################ - -1. Within a week of opening up a pull request, one of our community managers - will triage it, starting the documented contribution process. (Please - give us a little extra time if you open the PR on a weekend or - around a US holiday! We may take a little longer getting to it.) - -2. We promise to engage in an active dialogue with you from the time we begin - reviewing until either the PR is merged (by a core committer), or we - decide that, for whatever reason, it should be closed. - -3. Once we have determined through visual review that your code is not - malicious, we will run a Jenkins build on your branch. diff --git a/Makefile b/Makefile index fec1c2caf2..8a0a6840f4 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,10 @@ # Do things in edx-platform -.PHONY: clean extract_translations help pull pull_translations push_translations requirements shell upgrade -.PHONY: api-docs docs guides swagger +.PHONY: api-docs-sphinx api-docs base-requirements check-types clean \ + compile-requirements detect_changed_source_translations dev-requirements \ + docker_auth docker_build docker_push docker_tag docs extract_translations \ + guides help lint-imports local-requirements pre-requirements pull \ + pull_translations push_translations requirements shell swagger \ + technical-docs test-requirements ubuntu-requirements upgrade-package upgrade # Careful with mktemp syntax: it has to work on Mac and Ubuntu, which have differences. PRIVATE_FILES := $(shell mktemp -u /tmp/private_files.XXXXXX) @@ -113,7 +117,7 @@ COMMON_CONSTRAINTS_TXT=requirements/common_constraints.txt .PHONY: $(COMMON_CONSTRAINTS_TXT) $(COMMON_CONSTRAINTS_TXT): wget -O "$(@)" https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt || touch "$(@)" - echo "$(COMMON_CONSTRAINTS_TEMP_COMMENT)" | cat - $(@) > temp && mv temp $(@) + printf "$(COMMON_CONSTRAINTS_TEMP_COMMENT)" | cat - $(@) > temp && mv temp $(@) compile-requirements: export CUSTOM_COMPILE_COMMAND=make upgrade compile-requirements: pre-requirements $(COMMON_CONSTRAINTS_TXT) ## Re-compile *.in requirements to *.txt @@ -138,6 +142,10 @@ compile-requirements: pre-requirements $(COMMON_CONSTRAINTS_TXT) ## Re-compile * upgrade: ## update the pip requirements files to use the latest releases satisfying our constraints $(MAKE) compile-requirements COMPILE_OPTS="--upgrade" +upgrade-package: ## update just one package to the latest usable release + @test -n "$(package)" || { echo "\nUsage: make upgrade_package package=...\n"; exit 1; } + $(MAKE) compile-requirements COMPILE_OPTS="--upgrade-package $(package)" + check-types: ## run static type-checking tests mypy diff --git a/README.rst b/README.rst index da00daeccf..7e957e4f2f 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,3 @@ -################# Open edX Platform ################# | |License: AGPL v3| |Status| |Python CI| @@ -12,7 +11,7 @@ Open edX Platform .. |Status| image:: https://img.shields.io/badge/status-maintained-31c653 Purpose -------- +******* The `Open edX Platform `_ is a service-oriented platform for authoring and delivering online learning at any scale. The platform is written in Python and JavaScript and makes extensive use of the Django @@ -27,7 +26,7 @@ platform. Functionally, the edx-platform repository provides two services: * LMS (Learning Management Service), which delivers learning content. Installation ------------- +************ Installing and running an Open edX instance is not simple. We strongly recommend that you use a service provider to run the software for you. They @@ -43,8 +42,28 @@ so, `Open edX Installation Options`_ explains your options. .. _Open edX Developer Stack: https://github.com/openedx/devstack .. _Open edX Installation Options: https://openedx.atlassian.net/wiki/spaces/OpenOPS/pages/60227779/Open+edX+Installation+Options +Dependencies +============ + +In order to build and run this code you'll need the following available on your +system: + +Interperters/Tools: + +* Python 3.8 + +* Node 16 + +Services: + +* MySQL 5.7 + +* Mongo 4.x + +* Memcached + License -------- +******* The code in this repository is licensed under version 3 of the AGPL unless otherwise noted. Please see the `LICENSE`_ file for details. @@ -53,7 +72,7 @@ unless otherwise noted. Please see the `LICENSE`_ file for details. More about Open edX -------------------- +******************* See the `Open edX site`_ to learn more about the Open edX world. You can find information about hosting, extending, and contributing to Open edX software. In @@ -63,13 +82,13 @@ and other rich community resources. .. _Open edX site: https://openedx.org Documentation -------------- +************* -Documentation can be found at https://docs.edx.org. +Documentation can be found at https://docs.openedx.org. Getting Help ------------- +************ If you're having trouble, we have discussion forums at https://discuss.openedx.org where you can connect with others in the community. @@ -85,18 +104,18 @@ For more information about these options, see the `Getting Help`_ page. Issue Tracker -------------- +************* -We use JIRA for our issue tracker, not GitHub issues. You can search -`previously reported issues`_. If you need to report a problem, -please make a free account on our JIRA and `create a new issue`_. +We use Github Issues for our issue tracker. You can search +`previously reported issues`_. If you need to report a bug, or want to discuss +a new feature before you implement it, please `create a new issue`_. -.. _previously reported issues: https://openedx.atlassian.net/projects/CRI/issues -.. _create a new issue: https://openedx.atlassian.net/secure/CreateIssue.jspa?issuetype=1&pid=11900 +.. _previously reported issues: https://github.com/openedx/edx-platform/issues +.. _create a new issue: https://github.com/openedx/edx-platform/issues/new/choose How to Contribute ------------------ +***************** Contributions are welcome! The first step is to submit a signed `individual contributor agreement`_. See our `CONTRIBUTING`_ file for more @@ -105,7 +124,7 @@ quality, which will make your contribution more likely to be accepted. Reporting Security Issues -------------------------- +************************* Please do not report security issues in public. Please email security@edx.org. diff --git a/cms/djangoapps/contentstore/exams.py b/cms/djangoapps/contentstore/exams.py index 314f1df1f2..1e9f1b75d3 100644 --- a/cms/djangoapps/contentstore/exams.py +++ b/cms/djangoapps/contentstore/exams.py @@ -70,12 +70,20 @@ def register_exams(course_key): timed_exam.is_practice_exam, timed_exam.is_onboarding_exam ) + exams_list.append({ 'course_id': str(course_key), 'content_id': str(timed_exam.location), 'exam_name': timed_exam.display_name, 'time_limit_mins': timed_exam.default_time_limit_minutes, - 'due_date': timed_exam.due.isoformat() if timed_exam.due and not course.self_paced else None, + # If the subsection has no due date, then infer a due date from the course end date. This behavior is a + # departure from the legacy register_exams function used by the edx-proctoring plugin because + # edx-proctoring makes a direct call to edx-when API when computing an exam's due date. + # By sending the course end date when registering exams, we can avoid calling to the platform from the + # exam service. Also note that we no longer consider the pacing type of the course - this applies to both + # self-paced and indstructor-paced courses. Therefore, this effectively opts out exams powered by edx-exams + # from personalized learner schedules/relative dates. + 'due_date': timed_exam.due.isoformat() if timed_exam.due else course.end.isoformat(), 'exam_type': exam_type, 'is_active': True, 'hide_after_due': timed_exam.hide_after_due, diff --git a/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py b/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py index 0381d638d8..529d2f881e 100644 --- a/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py +++ b/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py @@ -25,7 +25,7 @@ def print_course(course): print('num type name') for index, item in enumerate(course.tabs): print(index + 1, '"' + item.get('type') + '"', '"' + item.get('name', '') + '"') - # If a course is bad we will get an error descriptor here, dump it and die instead of + # If a course is bad we will get an error here, dump it and die instead of # just sending up the error that .id doesn't exist. except AttributeError: print(course) diff --git a/cms/djangoapps/contentstore/rest_api/v0/tests/test_advanced_settings.py b/cms/djangoapps/contentstore/rest_api/v0/tests/test_advanced_settings.py index 70bb851927..765246258b 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/tests/test_advanced_settings.py +++ b/cms/djangoapps/contentstore/rest_api/v0/tests/test_advanced_settings.py @@ -81,3 +81,13 @@ class CourseAdvanceSettingViewTest(CourseTestCase, MilestonesTestCaseMixin): assert field in content.keys() for field in absent_fields: assert field not in content.keys() + + @ddt.data( + ("ENABLE_EDXNOTES", "edxnotes"), + ("ENABLE_OTHER_COURSE_SETTINGS", "other_course_settings"), + ) + @ddt.unpack + def test_disabled_fetch_all_query_param(self, setting, excluded_field): + with override_settings(FEATURES={setting: False}): + resp = self.client.get(self.url, {"fetch_all": 0}) + assert excluded_field not in resp.data diff --git a/cms/djangoapps/contentstore/rest_api/v0/views/advanced_settings.py b/cms/djangoapps/contentstore/rest_api/v0/views/advanced_settings.py index 9991f13889..7516bba372 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/views/advanced_settings.py +++ b/cms/djangoapps/contentstore/rest_api/v0/views/advanced_settings.py @@ -10,6 +10,7 @@ from rest_framework.views import APIView from xmodule.modulestore.django import modulestore from cms.djangoapps.models.settings.course_metadata import CourseMetadata +from cms.djangoapps.contentstore.api.views.utils import get_bool_param from common.djangoapps.student.auth import has_studio_read_access, has_studio_write_access from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, verify_course_exists, view_auth_classes from ..serializers import CourseAdvancedSettingsSerializer @@ -39,9 +40,14 @@ class AdvancedCourseSettingsView(DeveloperErrorViewMixin, APIView): apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"), apidocs.string_parameter( "filter_fields", - apidocs.ParameterLocation.PATH, + apidocs.ParameterLocation.QUERY, description="Comma separated list of fields to filter", ), + apidocs.string_parameter( + "fetch_all", + apidocs.ParameterLocation.QUERY, + description="Specifies whether to fetch all settings or only enabled ones", + ), ], responses={ 200: CourseAdvancedSettingsSerializer, @@ -112,7 +118,13 @@ class AdvancedCourseSettingsView(DeveloperErrorViewMixin, APIView): if not has_studio_read_access(request.user, course_key): self.permission_denied(request) course_block = modulestore().get_course(course_key) - return Response(CourseMetadata.fetch_all( + fetch_all = get_bool_param(request, 'fetch_all', True) + if fetch_all: + return Response(CourseMetadata.fetch_all( + course_block, + filter_fields=filter_query_data.cleaned_data['filter_fields'], + )) + return Response(CourseMetadata.fetch( course_block, filter_fields=filter_query_data.cleaned_data['filter_fields'], )) diff --git a/cms/djangoapps/contentstore/rest_api/v1/mixins.py b/cms/djangoapps/contentstore/rest_api/v1/mixins.py new file mode 100644 index 0000000000..849a483490 --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/mixins.py @@ -0,0 +1,42 @@ +""" +Common mixins for module. +""" +import json +from unittest.mock import patch + +from rest_framework import status + + +class PermissionAccessMixin: + """ + Mixin for testing permission access for views. + """ + + def get_and_check_developer_response(self, response): + """ + Make basic asserting about the presence of an error response, and return the developer response. + """ + content = json.loads(response.content.decode("utf-8")) + assert "developer_message" in content + return content["developer_message"] + + def test_permissions_unauthenticated(self): + """ + Test that an error is returned in the absence of auth credentials. + """ + self.client.logout() + response = self.client.get(self.url) + error = self.get_and_check_developer_response(response) + self.assertEqual(error, "Authentication credentials were not provided.") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + @patch.dict('django.conf.settings.FEATURES', {'DISABLE_ADVANCED_SETTINGS': True}) + def test_permissions_unauthorized(self): + """ + Test that an error is returned if the user is unauthorised. + """ + client, _ = self.create_non_staff_authed_user_client() + response = client.get(self.url) + error = self.get_and_check_developer_response(response) + self.assertEqual(error, "You do not have permission to perform this action.") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py new file mode 100644 index 0000000000..174f79a8cd --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py @@ -0,0 +1,11 @@ +""" +Serializers for v1 contentstore API. +""" +from .settings import CourseSettingsSerializer +from .course_details import CourseDetailsSerializer +from .proctoring import ( + LimitedProctoredExamSettingsSerializer, + ProctoredExamConfigurationSerializer, + ProctoredExamSettingsSerializer, + ProctoringErrorsSerializer, +) diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/course_details.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/course_details.py new file mode 100644 index 0000000000..b799e6d203 --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/course_details.py @@ -0,0 +1,59 @@ +""" +API Serializers for course details +""" + +from rest_framework import serializers + +from openedx.core.lib.api.serializers import CourseKeyField + + +class InstructorInfoSerializer(serializers.Serializer): + """ Serializer for instructor info """ + name = serializers.CharField(allow_blank=True) + title = serializers.CharField(allow_blank=True) + organization = serializers.CharField(allow_blank=True) + image = serializers.CharField(allow_blank=True) + bio = serializers.CharField(allow_blank=True) + + +class InstructorsSerializer(serializers.Serializer): + """ Serializer for instructors """ + instructors = InstructorInfoSerializer(many=True, allow_empty=True) + + +class CourseDetailsSerializer(serializers.Serializer): + """ Serializer for course details """ + about_sidebar_html = serializers.CharField(allow_null=True, allow_blank=True) + banner_image_name = serializers.CharField(allow_blank=True) + banner_image_asset_path = serializers.CharField() + certificate_available_date = serializers.DateTimeField() + certificates_display_behavior = serializers.CharField(allow_null=True) + course_id = serializers.CharField() + course_image_asset_path = serializers.CharField(allow_blank=True) + course_image_name = serializers.CharField(allow_blank=True) + description = serializers.CharField(allow_blank=True) + duration = serializers.CharField(allow_blank=True) + effort = serializers.CharField(allow_null=True, allow_blank=True) + end_date = serializers.DateTimeField(allow_null=True) + enrollment_end = serializers.DateTimeField(allow_null=True) + enrollment_start = serializers.DateTimeField(allow_null=True) + entrance_exam_enabled = serializers.CharField(allow_blank=True) + entrance_exam_id = serializers.CharField(allow_blank=True) + entrance_exam_minimum_score_pct = serializers.CharField(allow_blank=True) + instructor_info = InstructorsSerializer() + intro_video = serializers.CharField(allow_null=True) + language = serializers.CharField(allow_null=True) + learning_info = serializers.ListField(child=serializers.CharField(allow_blank=True)) + license = serializers.CharField(allow_null=True) + org = serializers.CharField() + overview = serializers.CharField(allow_blank=True) + pre_requisite_courses = serializers.ListField(child=CourseKeyField()) + run = serializers.CharField() + self_paced = serializers.BooleanField() + short_description = serializers.CharField(allow_blank=True) + start_date = serializers.DateTimeField() + subtitle = serializers.CharField(allow_blank=True) + syllabus = serializers.CharField(allow_null=True) + title = serializers.CharField(allow_blank=True) + video_thumbnail_image_asset_path = serializers.CharField() + video_thumbnail_image_name = serializers.CharField(allow_blank=True) diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/proctoring.py similarity index 57% rename from cms/djangoapps/contentstore/rest_api/v1/serializers.py rename to cms/djangoapps/contentstore/rest_api/v1/serializers/proctoring.py index 2a5f92325f..df5b77f72f 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/serializers.py +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/proctoring.py @@ -1,5 +1,5 @@ """ -API Serializers for Contentstore +API Serializers for proctoring """ from rest_framework import serializers @@ -29,3 +29,31 @@ class ProctoredExamConfigurationSerializer(serializers.Serializer): proctored_exam_settings = ProctoredExamSettingsSerializer() available_proctoring_providers = serializers.ChoiceField(get_available_providers()) course_start_date = serializers.DateTimeField() + + +class ProctoringErrorModelSerializer(serializers.Serializer): + """ + Serializer for proctoring error model item. + """ + deprecated = serializers.BooleanField() + display_name = serializers.CharField() + help = serializers.CharField() + hide_on_enabled_publisher = serializers.BooleanField() + value = serializers.CharField() + + +class ProctoringErrorListSerializer(serializers.Serializer): + """ + Serializer for proctoring error list. + """ + key = serializers.CharField() + message = serializers.CharField() + model = ProctoringErrorModelSerializer() + + +class ProctoringErrorsSerializer(serializers.Serializer): + """ + Serializer for proctoring errors with url to proctored exam settings. + """ + mfe_proctored_exam_settings_url = serializers.CharField(required=False, allow_null=True, allow_blank=True) + proctoring_errors = ProctoringErrorListSerializer(many=True) diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/settings.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/settings.py new file mode 100644 index 0000000000..0b65389596 --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/settings.py @@ -0,0 +1,44 @@ +""" +API Serializers for course settings +""" + +from rest_framework import serializers + +from openedx.core.lib.api.serializers import CourseKeyField + + +class PossiblePreRequisiteCourseSerializer(serializers.Serializer): + """ Serializer for possible pre requisite course """ + course_key = CourseKeyField() + display_name = serializers.CharField() + lms_link = serializers.CharField() + number = serializers.CharField() + org = serializers.CharField() + rerun_link = serializers.CharField() + run = serializers.CharField() + url = serializers.CharField() + + +class CourseSettingsSerializer(serializers.Serializer): + """ Serializer for course settings """ + about_page_editable = serializers.BooleanField() + can_show_certificate_available_date_field = serializers.BooleanField() + course_display_name = serializers.CharField() + course_display_name_with_default = serializers.CharField() + credit_eligibility_enabled = serializers.BooleanField() + credit_requirements = serializers.DictField(required=False) + enable_extended_course_details = serializers.BooleanField() + enrollment_end_editable = serializers.BooleanField() + is_credit_course = serializers.BooleanField() + is_entrance_exams_enabled = serializers.BooleanField() + is_prerequisite_courses_enabled = serializers.BooleanField() + language_options = serializers.ListField(child=serializers.ListField(child=serializers.CharField())) + lms_link_for_about_page = serializers.URLField() + marketing_enabled = serializers.BooleanField() + mfe_proctored_exam_settings_url = serializers.CharField(required=False, allow_null=True, allow_blank=True) + possible_pre_requisite_courses = PossiblePreRequisiteCourseSerializer(required=False, many=True) + short_description_editable = serializers.BooleanField() + show_min_grade_warning = serializers.BooleanField() + sidebar_html_enabled = serializers.BooleanField() + upgrade_deadline = serializers.DateTimeField(allow_null=True) + use_v2_cert_display_settings = serializers.BooleanField() diff --git a/cms/djangoapps/contentstore/rest_api/v1/tests/test_course_details.py b/cms/djangoapps/contentstore/rest_api/v1/tests/test_course_details.py new file mode 100644 index 0000000000..8cc62ce28c --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/tests/test_course_details.py @@ -0,0 +1,108 @@ +""" +Unit tests for course details views. +""" +import json +from unittest.mock import patch + +import ddt +from django.urls import reverse +from rest_framework import status + +from cms.djangoapps.contentstore.tests.utils import CourseTestCase + +from ..mixins import PermissionAccessMixin + + +@ddt.ddt +class CourseDetailsViewTest(CourseTestCase, PermissionAccessMixin): + """ + Tests for CourseDetailsView. + """ + + def setUp(self): + super().setUp() + self.url = reverse( + 'cms.djangoapps.contentstore:v1:course_details', + kwargs={"course_id": self.course.id}, + ) + + def test_put_permissions_unauthenticated(self): + """ + Test that an error is returned in the absence of auth credentials. + """ + self.client.logout() + response = self.client.put(self.url) + error = self.get_and_check_developer_response(response) + self.assertEqual(error, "Authentication credentials were not provided.") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_put_permissions_unauthorized(self): + """ + Test that an error is returned if the user is unauthorised. + """ + client, _ = self.create_non_staff_authed_user_client() + response = client.put(self.url) + error = self.get_and_check_developer_response(response) + self.assertEqual(error, "You do not have permission to perform this action.") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + @patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True}) + def test_put_invalid_pre_requisite_course(self): + pre_requisite_course_keys = [str(self.course.id), 'invalid_key'] + request_data = {"pre_requisite_courses": pre_requisite_course_keys} + response = self.client.put(path=self.url, data=json.dumps(request_data), content_type="application/json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.json()['error'], 'Invalid prerequisite course key') + + def test_put_course_details(self): + request_data = { + "about_sidebar_html": "", + "banner_image_name": "images_course_image.jpg", + "banner_image_asset_path": "/asset-v1:edX+E2E-101+course+type@asset+block@images_course_image.jpg", + "certificate_available_date": "2029-01-02T00:00:00Z", + "certificates_display_behavior": "end", + "course_id": "E2E-101", + "course_image_asset_path": "/static/studio/images/pencils.jpg", + "course_image_name": "bar_course_image_name", + "description": "foo_description", + "duration": "", + "effort": None, + "end_date": "2023-08-01T01:30:00Z", + "enrollment_end": "2023-05-30T01:00:00Z", + "enrollment_start": "2023-05-29T01:00:00Z", + "entrance_exam_enabled": "", + "entrance_exam_id": "", + "entrance_exam_minimum_score_pct": "50", + "intro_video": None, + "language": "creative-commons: ver=4.0 BY NC ND", + "learning_info": [ + "foo", + "bar" + ], + "license": "creative-commons: ver=4.0 BY NC ND", + "org": "edX", + "overview": "
", + "pre_requisite_courses": [], + "run": "course", + "self_paced": None, + "short_description": "", + "start_date": "2023-06-01T01:30:00Z", + "subtitle": "", + "syllabus": None, + "title": "", + "video_thumbnail_image_asset_path": "/asset-v1:edX+E2E-101+course+type@asset+block@images_course_image.jpg", + "video_thumbnail_image_name": "images_course_image.jpg", + "instructor_info": { + "instructors": [ + { + "name": "foo bar", + "title": "title", + "organization": "org", + "image": "image", + "bio": "" + } + ] + }, + } + response = self.client.put(path=self.url, data=json.dumps(request_data), content_type="application/json") + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/cms/djangoapps/contentstore/rest_api/v1/tests/test_views.py b/cms/djangoapps/contentstore/rest_api/v1/tests/test_proctoring.py similarity index 94% rename from cms/djangoapps/contentstore/rest_api/v1/tests/test_views.py rename to cms/djangoapps/contentstore/rest_api/v1/tests/test_proctoring.py index 2a9e2d881a..1caeeacf99 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/tests/test_views.py +++ b/cms/djangoapps/contentstore/rest_api/v1/tests/test_proctoring.py @@ -1,7 +1,6 @@ """ Unit tests for Contentstore views. """ - import ddt from mock import patch from django.conf import settings @@ -12,6 +11,7 @@ from opaque_keys.edx.keys import CourseKey from rest_framework import status from rest_framework.test import APITestCase +from cms.djangoapps.contentstore.tests.utils import CourseTestCase from common.djangoapps.student.tests.factories import GlobalStaffFactory from common.djangoapps.student.tests.factories import InstructorFactory from common.djangoapps.student.tests.factories import UserFactory @@ -20,6 +20,8 @@ from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disa from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from ..mixins import PermissionAccessMixin + class ProctoringExamSettingsTestMixin(): """ setup for proctored exam settings tests """ @@ -437,3 +439,28 @@ class ProctoringExamSettingsPostTests(ProctoringExamSettingsTestMixin, ModuleSto updated = modulestore().get_item(self.course.location) assert updated.enable_proctored_exams is False assert updated.proctoring_provider == 'null' + + +@ddt.ddt +class CourseProctoringErrorsViewTest(CourseTestCase, PermissionAccessMixin): + """ + Tests for ProctoringErrorsView. + """ + + def setUp(self): + super().setUp() + self.url = reverse( + 'cms.djangoapps.contentstore:v1:proctoring_errors', + kwargs={"course_id": self.course.id}, + ) + self.non_staff_client, _ = self.create_non_staff_authed_user_client() + + @ddt.data(False, True) + def test_disable_advanced_settings_feature(self, disable_advanced_settings): + """ + If this feature is enabled, only Django Staff/Superuser should be able to see the proctoring errors. + For non-staff users the proctoring errors should be unavailable. + """ + with override_settings(FEATURES={'DISABLE_ADVANCED_SETTINGS': disable_advanced_settings}): + response = self.non_staff_client.get(self.url) + self.assertEqual(response.status_code, 403 if disable_advanced_settings else 200) diff --git a/cms/djangoapps/contentstore/rest_api/v1/tests/test_settings.py b/cms/djangoapps/contentstore/rest_api/v1/tests/test_settings.py new file mode 100644 index 0000000000..8a4267de57 --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/tests/test_settings.py @@ -0,0 +1,80 @@ +""" +Unit tests for course settings views. +""" +import ddt +from django.conf import settings +from django.urls import reverse +from mock import patch +from rest_framework import status + +from cms.djangoapps.contentstore.tests.utils import CourseTestCase +from cms.djangoapps.contentstore.utils import get_proctored_exam_settings_url +from common.djangoapps.util.course import get_link_for_about_page +from openedx.core.djangoapps.credit.tests.factories import CreditCourseFactory + +from ..mixins import PermissionAccessMixin + + +@ddt.ddt +class CourseSettingsViewTest(CourseTestCase, PermissionAccessMixin): + """ + Tests for CourseSettingsView. + """ + + def setUp(self): + super().setUp() + self.url = reverse( + 'cms.djangoapps.contentstore:v1:course_settings', + kwargs={"course_id": self.course.id}, + ) + + def test_course_settings_response(self): + """ Check successful response content """ + response = self.client.get(self.url) + expected_response = { + 'about_page_editable': True, + 'can_show_certificate_available_date_field': False, + 'course_display_name': self.course.display_name, + 'course_display_name_with_default': self.course.display_name_with_default, + 'credit_eligibility_enabled': True, + 'enrollment_end_editable': True, + 'enable_extended_course_details': False, + 'is_credit_course': False, + 'is_entrance_exams_enabled': True, + 'is_prerequisite_courses_enabled': False, + 'language_options': settings.ALL_LANGUAGES, + 'lms_link_for_about_page': get_link_for_about_page(self.course), + 'marketing_enabled': False, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(self.course.id), + 'short_description_editable': True, + 'sidebar_html_enabled': False, + 'show_min_grade_warning': False, + 'upgrade_deadline': None, + 'use_v2_cert_display_settings': False, + } + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(expected_response, response.data) + + @patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREDIT_ELIGIBILITY': True}) + def test_credit_eligibility_setting(self): + """ + Make sure if the feature flag is enabled we have updated the dict keys in response. + """ + _ = CreditCourseFactory(course_key=self.course.id, enabled=True) + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('credit_requirements', response.data) + self.assertTrue(response.data['is_credit_course']) + + @patch.dict('django.conf.settings.FEATURES', { + 'ENABLE_PREREQUISITE_COURSES': True, + 'MILESTONES_APP': True, + }) + def test_prerequisite_courses_enabled_setting(self): + """ + Make sure if the feature flags are enabled we have updated the dict keys in response. + """ + response = self.client.get(self.url) + self.assertIn('possible_pre_requisite_courses', response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/cms/djangoapps/contentstore/rest_api/v1/urls.py b/cms/djangoapps/contentstore/rest_api/v1/urls.py index 83e03bcb73..eb41a02d25 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/urls.py +++ b/cms/djangoapps/contentstore/rest_api/v1/urls.py @@ -4,14 +4,34 @@ from django.urls import re_path from openedx.core.constants import COURSE_ID_PATTERN -from . import views +from .views import ( + CourseDetailsView, + CourseSettingsView, + ProctoredExamSettingsView, + ProctoringErrorsView, +) app_name = 'v1' urlpatterns = [ re_path( fr'^proctored_exam_settings/{COURSE_ID_PATTERN}$', - views.ProctoredExamSettingsView.as_view(), + ProctoredExamSettingsView.as_view(), name="proctored_exam_settings" ), + re_path( + fr'^proctoring_errors/{COURSE_ID_PATTERN}$', + ProctoringErrorsView.as_view(), + name="proctoring_errors" + ), + re_path( + fr'^course_settings/{COURSE_ID_PATTERN}$', + CourseSettingsView.as_view(), + name="course_settings" + ), + re_path( + fr'^course_details/{COURSE_ID_PATTERN}$', + CourseDetailsView.as_view(), + name="course_details" + ), ] diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py b/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py new file mode 100644 index 0000000000..61db86cc40 --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py @@ -0,0 +1,6 @@ +""" +Views for v1 contentstore API. +""" +from .course_details import CourseDetailsView +from .settings import CourseSettingsView +from .proctoring import ProctoredExamSettingsView, ProctoringErrorsView diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/course_details.py b/cms/djangoapps/contentstore/rest_api/v1/views/course_details.py new file mode 100644 index 0000000000..d5ccf3c616 --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/views/course_details.py @@ -0,0 +1,155 @@ +""" API Views for course details """ + +import edx_api_doc_tools as apidocs +from django.core.exceptions import ValidationError +from common.djangoapps.util.json_request import JsonResponseBadRequest +from opaque_keys.edx.keys import CourseKey +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.views import APIView +from common.djangoapps.student.auth import has_studio_read_access +from openedx.core.djangoapps.models.course_details import CourseDetails +from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, verify_course_exists, view_auth_classes +from xmodule.modulestore.django import modulestore + +from ..serializers import CourseDetailsSerializer +from ....utils import update_course_details + + +@view_auth_classes(is_authenticated=True) +class CourseDetailsView(DeveloperErrorViewMixin, APIView): + """ + View for getting and setting the course details. + """ + @apidocs.schema( + parameters=[ + apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"), + ], + responses={ + 200: CourseDetailsSerializer, + 401: "The requester is not authenticated.", + 403: "The requester cannot access the specified course.", + 404: "The requested course does not exist.", + }, + ) + @verify_course_exists() + def get(self, request: Request, course_id: str): + """ + Get an object containing all the course details. + + **Example Request** + + GET /api/contentstore/v1/course_details/{course_id} + + **Response Values** + + If the request is successful, an HTTP 200 "OK" response is returned. + + The HTTP 200 response contains a single dict that contains keys that + are the course's details. + + **Example Response** + + ```json + { + "about_sidebar_html": "", + "banner_image_name": "images_course_image.jpg", + "banner_image_asset_path": "/asset-v1:edX+E2E-101+course+type@asset+block@images_course_image.jpg", + "certificate_available_date": "2029-01-02T00:00:00Z", + "certificates_display_behavior": "end", + "course_id": "E2E-101", + "course_image_asset_path": "/static/studio/images/pencils.jpg", + "course_image_name": "", + "description": "", + "duration": "", + "effort": null, + "end_date": "2023-08-01T01:30:00Z", + "enrollment_end": "2023-05-30T01:00:00Z", + "enrollment_start": "2023-05-29T01:00:00Z", + "entrance_exam_enabled": "", + "entrance_exam_id": "", + "entrance_exam_minimum_score_pct": "50", + "intro_video": null, + "language": "creative-commons: ver=4.0 BY NC ND", + "learning_info": [], + "license": "creative-commons: ver=4.0 BY NC ND", + "org": "edX", + "overview": "
", + "pre_requisite_courses": [], + "run": "course", + "self_paced": false, + "short_description": "", + "start_date": "2023-06-01T01:30:00Z", + "subtitle": "", + "syllabus": null, + "title": "", + "video_thumbnail_image_asset_path": "/asset-v1:edX+E2E-101+course+type@asset+block@images_course_image.jpg", + "video_thumbnail_image_name": "images_course_image.jpg", + "instructor_info": { + "instructors": [{ + "name": "foo bar", + "title": "title", + "organization": "org", + "image": "image", + "bio": "" + }] + } + } + ``` + """ + course_key = CourseKey.from_string(course_id) + if not has_studio_read_access(request.user, course_key): + self.permission_denied(request) + + course_details = CourseDetails.fetch(course_key) + serializer = CourseDetailsSerializer(course_details) + return Response(serializer.data) + + @apidocs.schema( + body=CourseDetailsSerializer, + parameters=[ + apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"), + ], + responses={ + 200: CourseDetailsSerializer, + 401: "The requester is not authenticated.", + 403: "The requester cannot access the specified course.", + 404: "The requested course does not exist.", + }, + ) + @verify_course_exists() + def put(self, request: Request, course_id: str): + """ + Update a course's details. + + **Example Request** + + PUT /api/contentstore/v1/course_details/{course_id} + + **PUT Parameters** + + The data sent for a put request should follow a similar format as + is returned by a ``GET`` request. Multiple details can be updated in + a single request, however only the ``value`` field can be updated + any other fields, if included, will be ignored. + + Example request data that updates the ``course_details`` the same as in GET method + + **Response Values** + + If the request is successful, an HTTP 200 "OK" response is returned, + along with all the course's details similar to a ``GET`` request. + """ + course_key = CourseKey.from_string(course_id) + if not has_studio_read_access(request.user, course_key): + self.permission_denied(request) + + course_block = modulestore().get_course(course_key) + + try: + updated_data = update_course_details(request, course_key, request.data, course_block) + except ValidationError as err: + return JsonResponseBadRequest({"error": err.message}) + + serializer = CourseDetailsSerializer(updated_data) + return Response(serializer.data) diff --git a/cms/djangoapps/contentstore/rest_api/v1/views.py b/cms/djangoapps/contentstore/rest_api/v1/views/proctoring.py similarity index 66% rename from cms/djangoapps/contentstore/rest_api/v1/views.py rename to cms/djangoapps/contentstore/rest_api/v1/views/proctoring.py index 9675665d2e..015ff31c29 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/views.py +++ b/cms/djangoapps/contentstore/rest_api/v1/views/proctoring.py @@ -1,23 +1,29 @@ -"Contentstore Views" +""" API Views for proctored exam settings and proctoring error """ import copy +from django.conf import settings +import edx_api_doc_tools as apidocs from opaque_keys.edx.keys import CourseKey from rest_framework import status from rest_framework.exceptions import NotFound +from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView from cms.djangoapps.contentstore.views.course import get_course_and_check_access +from cms.djangoapps.contentstore.utils import get_proctored_exam_settings_url from cms.djangoapps.models.settings.course_metadata import CourseMetadata +from common.djangoapps.student.auth import has_studio_advanced_settings_access from xmodule.course_block import get_available_providers # lint-amnesty, pylint: disable=wrong-import-order from openedx.core.djangoapps.course_apps.toggles import exams_ida_enabled -from openedx.core.lib.api.view_utils import view_auth_classes +from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, verify_course_exists, view_auth_classes from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order -from .serializers import ( +from ..serializers import ( LimitedProctoredExamSettingsSerializer, ProctoredExamConfigurationSerializer, - ProctoredExamSettingsSerializer + ProctoredExamSettingsSerializer, + ProctoringErrorsSerializer, ) @@ -182,3 +188,80 @@ class ProctoredExamSettingsView(APIView): ) return course_block + + +@view_auth_classes(is_authenticated=True) +class ProctoringErrorsView(DeveloperErrorViewMixin, APIView): + """ + View for getting the proctoring errors for a course with url to proctored exam settings. + """ + @apidocs.schema( + parameters=[ + apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"), + ], + responses={ + 200: ProctoringErrorsSerializer, + 401: "The requester is not authenticated.", + 403: "The requester cannot access the specified course.", + 404: "The requested course does not exist.", + }, + ) + @verify_course_exists() + def get(self, request: Request, course_id: str) -> Response: + """ + Get an object containing proctoring errors in a course. + + **Example Request** + + GET /api/contentstore/v1/proctoring_errors/{course_id} + + **Response Values** + + If the request is successful, an HTTP 200 "OK" response is returned. + + The HTTP 200 response contains a list of object proctoring errors. + Also response contains mfe proctored exam settings url. + For each item returned an object that contains the following fields: + + * **key**: This is proctoring settings key. + * **message**: This is a description for proctoring error. + * **model**: This is proctoring provider model object. + + **Example Response** + + ```json + { + "mfe_proctored_exam_settings_url": "http://course-authoring-mfe/course/course_key/proctored-exam-settings", + "proctoring_errors": [ + { + "key": "proctoring_provider", + "message": "The proctoring provider cannot be modified after a course has started.", + "model": { + "value": "null", + "display_name": "Proctoring Provider", + "help": "Enter the proctoring provider you want to use for this course run.", + "deprecated": false, + "hide_on_enabled_publisher": false + }} + ], + } + ``` + """ + course_key = CourseKey.from_string(course_id) + if not has_studio_advanced_settings_access(request.user): + self.permission_denied(request) + + course_block = modulestore().get_course(course_key) + advanced_dict = CourseMetadata.fetch(course_block) + if settings.FEATURES.get('DISABLE_MOBILE_COURSE_AVAILABLE', False): + advanced_dict.get('mobile_available')['deprecated'] = True + + proctoring_errors = CourseMetadata.validate_proctoring_settings(course_block, advanced_dict, request.user) + proctoring_context = { + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_key), + 'proctoring_errors': proctoring_errors, + } + + serializer = ProctoringErrorsSerializer(data=proctoring_context) + serializer.is_valid(raise_exception=True) + return Response(serializer.data) diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/settings.py b/cms/djangoapps/contentstore/rest_api/v1/views/settings.py new file mode 100644 index 0000000000..e42b7d85da --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/views/settings.py @@ -0,0 +1,115 @@ +""" API Views for course settings """ + +import edx_api_doc_tools as apidocs +from django.conf import settings +from opaque_keys.edx.keys import CourseKey +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.views import APIView + +from common.djangoapps.student.auth import has_studio_read_access +from lms.djangoapps.certificates.api import can_show_certificate_available_date_field +from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, verify_course_exists, view_auth_classes +from xmodule.modulestore.django import modulestore + +from ..serializers import CourseSettingsSerializer +from ....utils import get_course_settings + + +@view_auth_classes(is_authenticated=True) +class CourseSettingsView(DeveloperErrorViewMixin, APIView): + """ + View for getting the settings for a course. + """ + + @apidocs.schema( + parameters=[ + apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"), + ], + responses={ + 200: CourseSettingsSerializer, + 401: "The requester is not authenticated.", + 403: "The requester cannot access the specified course.", + 404: "The requested course does not exist.", + }, + ) + @verify_course_exists() + def get(self, request: Request, course_id: str): + """ + Get an object containing all the course settings. + + **Example Request** + + GET /api/contentstore/v1/course_settings/{course_id} + + **Response Values** + + If the request is successful, an HTTP 200 "OK" response is returned. + + The HTTP 200 response contains a single dict that contains keys that + are the course's settings. + + **Example Response** + + ```json + { + "about_page_editable": false, + "can_show_certificate_available_date_field": false, + "course_display_name": "E2E Test Course", + "course_display_name_with_default": "E2E Test Course", + "credit_eligibility_enabled": true, + "enable_extended_course_details": true, + "enrollment_end_editable": true, + "is_credit_course": false, + "is_entrance_exams_enabled": true, + "is_prerequisite_courses_enabled": true, + "language_options": [ + [ + "aa", + "Afar" + ], + [ + "uk", + "Ukrainian" + ], + ... + ], + "lms_link_for_about_page": "http://localhost:18000/courses/course-v1:edX+E2E-101+course/about", + "marketing_enabled": true, + "mfe_proctored_exam_settings_url": "", + "possible_pre_requisite_courses": [ + { + "course_key": "course-v1:edX+M12+2T2023", + "display_name": "Differential Equations", + "lms_link": "//localhost:18000/courses/course-v1:edX+M1...", + "number": "M12", + "org": "edX", + "rerun_link": "/course_rerun/course-v1:edX+M12+2T2023", + "run": "2T2023", + "url": "/course/course-v1:edX+M12+2T2023" + }, + ], + "short_description_editable": true, + "show_min_grade_warning": false, + "sidebar_html_enabled": true, + "upgrade_deadline": null, + "use_v2_cert_display_settings": false + } + ``` + """ + course_key = CourseKey.from_string(course_id) + if not has_studio_read_access(request.user, course_key): + self.permission_denied(request) + + with modulestore().bulk_operations(course_key): + course_block = modulestore().get_course(course_key) + settings_context = get_course_settings(request, course_key, course_block) + settings_context.update({ + 'can_show_certificate_available_date_field': can_show_certificate_available_date_field(course_block), + 'course_display_name': course_block.display_name, + 'course_display_name_with_default': course_block.display_name_with_default, + 'use_v2_cert_display_settings': settings.FEATURES.get("ENABLE_V2_CERT_DISPLAY_SETTINGS", False), + }) + + serializer = CourseSettingsSerializer(settings_context) + return Response(serializer.data) diff --git a/cms/djangoapps/contentstore/signals/handlers.py b/cms/djangoapps/contentstore/signals/handlers.py index badb7334ef..3c0742adb0 100644 --- a/cms/djangoapps/contentstore/signals/handlers.py +++ b/cms/djangoapps/contentstore/signals/handlers.py @@ -28,7 +28,7 @@ from cms.djangoapps.contentstore.courseware_index import ( LibrarySearchIndexer, ) from common.djangoapps.track.event_transaction_utils import get_event_transaction_id, get_event_transaction_type -from common.djangoapps.util.block_utils import yield_dynamic_descriptor_descendants +from common.djangoapps.util.block_utils import yield_dynamic_block_descendants from lms.djangoapps.grades.api import task_compute_all_grades_for_course from openedx.core.djangoapps.content.learning_sequences.api import key_supports_outlines from openedx.core.djangoapps.discussions.tasks import update_discussions_settings_from_course_task @@ -252,7 +252,7 @@ def handle_item_deleted(**kwargs): usage_key = usage_key.for_branch(None) course_key = usage_key.course_key deleted_block = modulestore().get_item(usage_key) - for block in yield_dynamic_descriptor_descendants(deleted_block, kwargs.get('user_id')): + for block in yield_dynamic_block_descendants(deleted_block, kwargs.get('user_id')): # Remove prerequisite milestone data gating_api.remove_prerequisite(block.location) # Remove any 'requires' course content milestone relationships diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index be120824a4..f7046cc01a 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -1713,17 +1713,17 @@ class MetadataSaveTestCase(ContentStoreTestCase): """ video_data = VideoBlock.parse_video_xml(video_sample_xml) video_data.pop('source') - self.video_descriptor = BlockFactory.create( + self.video_block = BlockFactory.create( parent_location=course.location, category='video', **video_data ) def test_metadata_not_persistence(self): """ - Test that descriptors which set metadata fields in their + Test that blocks which set metadata fields in their constructor are correctly deleted. """ - self.assertIn('html5_sources', own_metadata(self.video_descriptor)) + self.assertIn('html5_sources', own_metadata(self.video_block)) attrs_to_strip = { 'show_captions', 'youtube_id_1_0', @@ -1736,13 +1736,13 @@ class MetadataSaveTestCase(ContentStoreTestCase): 'track' } - location = self.video_descriptor.location + location = self.video_block.location for field_name in attrs_to_strip: - delattr(self.video_descriptor, field_name) + delattr(self.video_block, field_name) - self.assertNotIn('html5_sources', own_metadata(self.video_descriptor)) - self.store.update_item(self.video_descriptor, self.user.id) + self.assertNotIn('html5_sources', own_metadata(self.video_block)) + self.store.update_item(self.video_block, self.user.id) block = self.store.get_item(location) self.assertNotIn('html5_sources', own_metadata(block)) @@ -2047,12 +2047,12 @@ class ContentLicenseTest(ContentStoreTestCase): def test_video_license_export(self): content_store = contentstore() root_dir = path(mkdtemp_clean()) - video_descriptor = BlockFactory.create( + video_block = BlockFactory.create( parent_location=self.course.location, category='video', license="all-rights-reserved" ) export_course_to_xml(self.store, content_store, self.course.id, root_dir, 'test_license') - fname = f"{video_descriptor.scope_ids.usage_id.block_id}.xml" + fname = f"{video_block.scope_ids.usage_id.block_id}.xml" video_file_path = root_dir / "test_license" / "video" / fname with video_file_path.open() as f: video_xml = etree.parse(f) diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index a135faf067..605e0d2133 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -106,7 +106,12 @@ class CourseAdvanceSettingViewTest(CourseTestCase, MilestonesTestCaseMixin): super().setUp() self.fullcourse = CourseFactory.create() self.course_setting_url = get_url(self.course.id, 'advanced_settings_handler') - self.non_staff_client, _ = self.create_non_staff_authed_user_client() + + self.non_staff_client, self.nonstaff = self.create_non_staff_authed_user_client() + # "nonstaff" means "non Django staff" here. We assign this user to course staff + # role to check that even so they won't have advanced settings access when explicitly + # restricted. + CourseStaffRole(self.course.id).add_users(self.nonstaff) @override_settings(FEATURES={'DISABLE_MOBILE_COURSE_AVAILABLE': True}) def test_mobile_field_available(self): @@ -145,16 +150,50 @@ class CourseAdvanceSettingViewTest(CourseTestCase, MilestonesTestCaseMixin): self.assertEqual('discussion_blackouts' in response, fields_visible) self.assertEqual('discussion_topics' in response, fields_visible) - @override_settings(FEATURES={'DISABLE_ADVANCED_SETTINGS': True}) - def test_disable_advanced_settings_feature(self): + @ddt.data(False, True) + def test_disable_advanced_settings_feature(self, disable_advanced_settings): """ - If this feature is enabled, only staff should be able to access the advanced settings page. + If this feature is enabled, only Django Staff/Superuser should be able to access the "Advanced Settings" page. + For non-staff users the "Advanced Settings" tab link should not be visible. """ - response = self.non_staff_client.get_html(self.course_setting_url) - self.assertEqual(response.status_code, 403) + advanced_settings_link_html = f"Advanced Settings".encode('utf-8') - response = self.client.get_html(self.course_setting_url) - self.assertEqual(response.status_code, 200) + with override_settings(FEATURES={'DISABLE_ADVANCED_SETTINGS': disable_advanced_settings}): + for handler in ( + 'import_handler', + 'export_handler', + 'course_team_handler', + 'course_info_handler', + 'assets_handler', + 'tabs_handler', + 'settings_handler', + 'grading_handler', + 'textbooks_list_handler', + ): + # Test that non-staff users don't see the "Advanced Settings" tab link. + response = self.non_staff_client.get_html( + get_url(self.course.id, handler) + ) + self.assertEqual(response.status_code, 200) + if disable_advanced_settings: + self.assertNotIn(advanced_settings_link_html, response.content) + else: + self.assertIn(advanced_settings_link_html, response.content) + + # Test that staff users see the "Advanced Settings" tab link. + response = self.client.get_html( + get_url(self.course.id, handler) + ) + self.assertEqual(response.status_code, 200) + self.assertIn(advanced_settings_link_html, response.content) + + # Test that non-staff users can't access the "Advanced Settings" page. + response = self.non_staff_client.get_html(self.course_setting_url) + self.assertEqual(response.status_code, 403 if disable_advanced_settings else 200) + + # Test that staff users can access the "Advanced Settings" page. + response = self.client.get_html(self.course_setting_url) + self.assertEqual(response.status_code, 200) @ddt.ddt @@ -885,33 +924,33 @@ class CourseGradingTest(CourseTestCase): @mock.patch('cms.djangoapps.contentstore.signals.signals.GRADING_POLICY_CHANGED.send') def test_update_section_grader_type(self, send_signal, tracker, uuid): uuid.return_value = 'mockUUID' - # Get the descriptor and the section_grader_type and assert they are the default values - descriptor = modulestore().get_item(self.course.location) + # Get the block and the section_grader_type and assert they are the default values + block = modulestore().get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) self.assertEqual('notgraded', section_grader_type['graderType']) - self.assertEqual(None, descriptor.format) - self.assertEqual(False, descriptor.graded) + self.assertEqual(None, block.format) + self.assertEqual(False, block.graded) # Change the default grader type to Homework, which should also mark the section as graded CourseGradingModel.update_section_grader_type(self.course, 'Homework', self.user) - descriptor = modulestore().get_item(self.course.location) + block = modulestore().get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) grading_policy_1 = self._grading_policy_hash_for_course() self.assertEqual('Homework', section_grader_type['graderType']) - self.assertEqual('Homework', descriptor.format) - self.assertEqual(True, descriptor.graded) + self.assertEqual('Homework', block.format) + self.assertEqual(True, block.graded) # Change the grader type back to notgraded, which should also unmark the section as graded CourseGradingModel.update_section_grader_type(self.course, 'notgraded', self.user) - descriptor = modulestore().get_item(self.course.location) + block = modulestore().get_item(self.course.location) section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location) grading_policy_2 = self._grading_policy_hash_for_course() self.assertEqual('notgraded', section_grader_type['graderType']) - self.assertEqual(None, descriptor.format) - self.assertEqual(False, descriptor.graded) + self.assertEqual(None, block.format) + self.assertEqual(False, block.graded) # one for each call to update_section_grader_type() send_signal.assert_has_calls([ diff --git a/cms/djangoapps/contentstore/tests/test_exams.py b/cms/djangoapps/contentstore/tests/test_exams.py index 2a1a6e83a3..6b2f39687e 100644 --- a/cms/djangoapps/contentstore/tests/test_exams.py +++ b/cms/djangoapps/contentstore/tests/test_exams.py @@ -1,7 +1,7 @@ """ Test the exams service integration into Studio """ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from unittest.mock import patch, Mock import ddt @@ -147,8 +147,15 @@ class TestExamService(ModuleStoreTestCase): listen_for_course_publish(self, self.course.id) mock_patch_course_exams.assert_not_called() - def test_self_paced_no_due_dates(self, mock_patch_course_exams): - self.course.self_paced = True + @ddt.data(True, False) + def test_no_due_dates(self, is_self_paced, mock_patch_course_exams): + """ + Test that the coures end date is registered as the due date when the subsection does not have a due date for + both self-paced and instructor-paced exams. + """ + self.course.self_paced = is_self_paced + end_date = datetime(2035, 1, 1, 0, 0, tzinfo=timezone.utc) + self.course.end = end_date self.course = self.update_course(self.course, 1) BlockFactory.create( parent=self.chapter, @@ -159,18 +166,40 @@ class TestExamService(ModuleStoreTestCase): default_time_limit_minutes=60, is_proctored_enabled=False, is_practice_exam=False, - due=datetime.now(UTC) + timedelta(minutes=60), + due=None, hide_after_due=True, is_onboarding_exam=False, ) - listen_for_course_publish(self, self.course.id) - called_exams, called_course = mock_patch_course_exams.call_args[0] - assert called_exams[0]['due_date'] is None - # now switch to instructor paced - # the exam will be updated with a due date - self.course.self_paced = False - self.course = self.update_course(self.course, 1) listen_for_course_publish(self, self.course.id) called_exams, called_course = mock_patch_course_exams.call_args[0] - assert called_exams[0]['due_date'] is not None + assert called_exams[0]['due_date'] == end_date.isoformat() + + @ddt.data(True, False) + def test_subsection_due_date_prioritized(self, is_self_paced, mock_patch_course_exams): + """ + Test that the subsection due date is registered as the due date when both the subsection has a due date and the + course has an end date for both self-paced and instructor-paced exams. + """ + self.course.self_paced = is_self_paced + self.course.end = datetime(2035, 1, 1, 0, 0) + self.course = self.update_course(self.course, 1) + + sequential_due_date = datetime.now(UTC) + timedelta(minutes=60) + BlockFactory.create( + parent=self.chapter, + category='sequential', + display_name='Test Proctored Exam', + graded=True, + is_time_limited=True, + default_time_limit_minutes=60, + is_proctored_enabled=False, + is_practice_exam=False, + due=sequential_due_date, + hide_after_due=True, + is_onboarding_exam=False, + ) + + listen_for_course_publish(self, self.course.id) + called_exams, called_course = mock_patch_course_exams.call_args[0] + assert called_exams[0]['due_date'] == sequential_due_date.isoformat() diff --git a/cms/djangoapps/contentstore/tests/test_i18n.py b/cms/djangoapps/contentstore/tests/test_i18n.py index e38094a2d3..db5746f893 100644 --- a/cms/djangoapps/contentstore/tests/test_i18n.py +++ b/cms/djangoapps/contentstore/tests/test_i18n.py @@ -69,19 +69,19 @@ class TestXBlockI18nService(ModuleStoreTestCase): self.request = mock.Mock() self.course = CourseFactory.create() self.field_data = mock.Mock() - self.descriptor = BlockFactory(category="pure", parent=self.course) + self.block = BlockFactory(category="pure", parent=self.course) _prepare_runtime_for_preview( self.request, - self.descriptor, + self.block, self.field_data, ) self.addCleanup(translation.deactivate) - def get_block_i18n_service(self, descriptor): + def get_block_i18n_service(self, block): """ return the block i18n service. """ - i18n_service = self.descriptor.runtime.service(descriptor, 'i18n') + i18n_service = self.block.runtime.service(block, 'i18n') self.assertIsNotNone(i18n_service) self.assertIsInstance(i18n_service, XBlockI18nService) return i18n_service @@ -113,7 +113,7 @@ class TestXBlockI18nService(ModuleStoreTestCase): self.module.ugettext = self.old_ugettext self.module.gettext = self.old_ugettext - i18n_service = self.get_block_i18n_service(self.descriptor) + i18n_service = self.get_block_i18n_service(self.block) # Activate french, so that if the fr files haven't been loaded, they will be loaded now. with translation.override("fr"): @@ -150,13 +150,13 @@ class TestXBlockI18nService(ModuleStoreTestCase): translation.activate("es") with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir, languages=[get_language()])): - i18n_service = self.get_block_i18n_service(self.descriptor) + i18n_service = self.get_block_i18n_service(self.block) self.assertEqual(i18n_service.ugettext('Hello'), 'es-hello-world') translation.activate("ar") with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir, languages=[get_language()])): - i18n_service = self.get_block_i18n_service(self.descriptor) + i18n_service = self.get_block_i18n_service(self.block) self.assertEqual(get_gettext(i18n_service)('Hello'), 'Hello') self.assertNotEqual(get_gettext(i18n_service)('Hello'), 'fr-hello-world') self.assertNotEqual(get_gettext(i18n_service)('Hello'), 'es-hello-world') @@ -164,14 +164,14 @@ class TestXBlockI18nService(ModuleStoreTestCase): translation.activate("fr") with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir, languages=[get_language()])): - i18n_service = self.get_block_i18n_service(self.descriptor) + i18n_service = self.get_block_i18n_service(self.block) self.assertEqual(i18n_service.ugettext('Hello'), 'fr-hello-world') def test_i18n_service_callable(self): """ Test: i18n service should be callable in studio. """ - self.assertTrue(callable(self.descriptor.runtime._services.get('i18n'))) # pylint: disable=protected-access + self.assertTrue(callable(self.block.runtime._services.get('i18n'))) # pylint: disable=protected-access class InternationalizationTest(ModuleStoreTestCase): diff --git a/cms/djangoapps/contentstore/tests/test_libraries.py b/cms/djangoapps/contentstore/tests/test_libraries.py index 3e850e4794..eb9b109cdd 100644 --- a/cms/djangoapps/contentstore/tests/test_libraries.py +++ b/cms/djangoapps/contentstore/tests/test_libraries.py @@ -15,8 +15,8 @@ from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory from xmodule.x_module import STUDIO_VIEW from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient, parse_json -from cms.djangoapps.contentstore.utils import reverse_library_url, reverse_url, reverse_usage_url -from cms.djangoapps.contentstore.views.block import _duplicate_block +from cms.djangoapps.contentstore.utils import reverse_library_url, reverse_url, \ + reverse_usage_url, duplicate_block from cms.djangoapps.contentstore.views.preview import _load_preview_block from cms.djangoapps.contentstore.views.tests.test_library import LIBRARY_REST_URL from cms.djangoapps.course_creators.views import add_user_with_status_granted @@ -114,7 +114,7 @@ class LibraryTestCase(ModuleStoreTestCase): self.assertEqual(response.status_code, status_code_expected) return modulestore().get_item(lib_content_block.location) - def _bind_block(self, descriptor, user=None): + def _bind_block(self, block, user=None): """ Helper to use the CMS's module system so we can access student-specific fields. """ @@ -123,7 +123,7 @@ class LibraryTestCase(ModuleStoreTestCase): if user not in self.session_data: self.session_data[user] = {} request = Mock(user=user, session=self.session_data[user]) - _load_preview_block(request, descriptor) + _load_preview_block(request, block) def _update_block(self, usage_key, metadata): """ @@ -174,13 +174,13 @@ class TestLibraries(LibraryTestCase): lc_block = self._refresh_children(lc_block) # Now, we want to make sure that .children has the total # of potential - # children, and that get_child_descriptors() returns the actual children + # children, and that get_child_blocks() returns the actual children # chosen for a given student. - # In order to be able to call get_child_descriptors(), we must first + # In order to be able to call get_child_blocks(), we must first # call bind_for_student: self._bind_block(lc_block) self.assertEqual(len(lc_block.children), num_to_create) - self.assertEqual(len(lc_block.get_child_descriptors()), num_expected) + self.assertEqual(len(lc_block.get_child_blocks()), num_expected) def test_consistent_children(self): """ @@ -204,7 +204,7 @@ class TestLibraries(LibraryTestCase): """ Fetch the child shown to the current user. """ - children = block.get_child_descriptors() + children = block.get_child_blocks() self.assertEqual(len(children), 1) return children[0] @@ -947,7 +947,7 @@ class TestOverrides(LibraryTestCase): if duplicate: # Check that this also works when the RCB is duplicated. self.lc_block = modulestore().get_item( - _duplicate_block(self.course.location, self.lc_block.location, self.user) + duplicate_block(self.course.location, self.lc_block.location, self.user) ) self.problem_in_course = modulestore().get_item(self.lc_block.children[0]) else: @@ -1006,7 +1006,7 @@ class TestOverrides(LibraryTestCase): # Duplicate self.lc_block: duplicate = store.get_item( - _duplicate_block(self.course.location, self.lc_block.location, self.user) + duplicate_block(self.course.location, self.lc_block.location, self.user) ) # The duplicate should have identical children to the original: self.assertEqual(len(duplicate.children), 1) diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index 5b4f4338b2..4b6dc100db 100644 --- a/cms/djangoapps/contentstore/tests/test_utils.py +++ b/cms/djangoapps/contentstore/tests/test_utils.py @@ -620,7 +620,7 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase): self.assertEqual(partitions[0]["scheme"], "random") def _set_partitions(self, partitions): - """Set the user partitions of the course descriptor. """ + """Set the user partitions of the course block. """ self.course.user_partitions = partitions self.course = self.store.update_item(self.course, ModuleStoreEnum.UserID.test) diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py index b58ec17502..4c2b5b5adf 100644 --- a/cms/djangoapps/contentstore/tests/utils.py +++ b/cms/djangoapps/contentstore/tests/utils.py @@ -191,8 +191,8 @@ class CourseTestCase(ProceduralCourseTestMixin, ModuleStoreTestCase): """ Test getting the editing HTML for each vertical. """ # assert is here to make sure that the course being tested actually has verticals (units) to check. self.assertGreater(len(items), 0, "Course has no verticals (units) to check") - for descriptor in items: - resp = self.client.get_html(get_url('container_handler', descriptor.location)) + for block in items: + resp = self.client.get_html(get_url('container_handler', block.location)) self.assertEqual(resp.status_code, 200) def assertAssetsEqual(self, asset_son, course1_id, course2_id): diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index dc5dd26e94..1a339519e1 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -122,6 +122,24 @@ def use_new_video_editor(): return ENABLE_NEW_VIDEO_EDITOR_FLAG.is_enabled() +# .. toggle_name: new_core_editors.use_video_gallery_flow +# .. toggle_implementation: WaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use the video selection gallery on the flow of the new core video xblock editor +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-04-03 +# .. toggle_target_removal_date: 2023-6-01 +# .. toggle_warning: You need to activate the `new_core_editors.use_new_video_editor` flag to use this new flow. +ENABLE_VIDEO_GALLERY_FLOW_FLAG = WaffleFlag('new_core_editors.use_video_gallery_flow', __name__) + + +def use_video_gallery_flow(): + """ + Returns a boolean = true if the video gallery flow is enabled + """ + return ENABLE_VIDEO_GALLERY_FLOW_FLAG.is_enabled() + + # .. toggle_name: new_core_editors.use_new_problem_editor # .. toggle_implementation: WaffleFlag # .. toggle_default: False @@ -175,3 +193,262 @@ ENABLE_COPY_PASTE_FEATURE = WaffleFlag( __name__, CONTENTSTORE_LOG_PREFIX, ) + + +# .. toggle_name: new_studio_mfe.use_new_home_page +# .. toggle_implementation: WaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio home page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-9306 +# .. toggle_warning: +ENABLE_NEW_STUDIO_HOME_PAGE = WaffleFlag('new_studio_mfe.use_new_home_page', __name__) + + +def use_new_home_page(): + """ + Returns a boolean if new studio home page mfe is enabled + """ + return ENABLE_NEW_STUDIO_HOME_PAGE.is_enabled() + + +# .. toggle_name: new_studio_mfe.use_new_custom_pages +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio custom pages mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_CUSTOM_PAGES = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_custom_pages', __name__) + + +def use_new_custom_pages(): + """ + Returns a boolean if new studio custom pages mfe is enabled + """ + return ENABLE_NEW_STUDIO_CUSTOM_PAGES.is_enabled() + + +# .. toggle_name: new_studio_mfe.use_new_schedule_details_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio schedule and details mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_SCHEDULE_DETAILS_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_schedule_details_page', __name__) + + +def use_new_schedule_details_page(course_key): + """ + Returns a boolean if new studio schedule and details mfe is enabled + """ + return ENABLE_NEW_STUDIO_SCHEDULE_DETAILS_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_advanced_settings_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio advanced settings page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_ADVANCED_SETTINGS_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_advanced_settings_page', __name__) + + +def use_new_advanced_settings_page(course_key): + """ + Returns a boolean if new studio advanced settings pafe mfe is enabled + """ + return ENABLE_NEW_STUDIO_ADVANCED_SETTINGS_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_grading_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio grading page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_GRADING_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_grading_page', __name__) + + +def use_new_grading_page(course_key): + """ + Returns a boolean if new studio grading mfe is enabled + """ + return ENABLE_NEW_STUDIO_GRADING_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_updates_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio updates page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_UPDATES_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_updates_page', __name__) + + +def use_new_updates_page(course_key): + """ + Returns a boolean if new studio updates mfe is enabled + """ + return ENABLE_NEW_STUDIO_UPDATES_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_import_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio import page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_IMPORT_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_import_page', __name__) + + +def use_new_import_page(course_key): + """ + Returns a boolean if new studio import mfe is enabled + """ + return ENABLE_NEW_STUDIO_IMPORT_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_export_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio export page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_EXPORT_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_export_page', __name__) + + +def use_new_export_page(course_key): + """ + Returns a boolean if new studio export mfe is enabled + """ + return ENABLE_NEW_STUDIO_EXPORT_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_files_uploads_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio files and uploads page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_FILES_UPLOADS_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_files_uploads_page', __name__) + + +def use_new_files_uploads_page(course_key): + """ + Returns a boolean if new studio files and uploads mfe is enabled + """ + return ENABLE_NEW_STUDIO_FILES_UPLOADS_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_video_uploads_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new video uploads page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_VIDEO_UPLOADS_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_video_uploads_page', __name__) + + +def use_new_video_uploads_page(course_key): + """ + Returns a boolean if new studio video uploads mfe is enabled + """ + return ENABLE_NEW_STUDIO_VIDEO_UPLOADS_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_course_outline_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio course outline page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_COURSE_OUTLINE_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_course_outline_page', __name__) + + +def use_new_course_outline_page(course_key): + """ + Returns a boolean if new studio course outline mfe is enabled + """ + return ENABLE_NEW_STUDIO_COURSE_OUTLINE_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_unit_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio course outline page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_UNIT_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_unit_page', __name__) + + +def use_new_unit_page(course_key): + """ + Returns a boolean if new studio course outline mfe is enabled + """ + return ENABLE_NEW_STUDIO_UNIT_PAGE.is_enabled(course_key) + + +# .. toggle_name: new_studio_mfe.use_new_course_team_page +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of the new studio course team page mfe +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-5-15 +# .. toggle_target_removal_date: 2023-8-31 +# .. toggle_tickets: TNL-10619 +# .. toggle_warning: +ENABLE_NEW_STUDIO_COURSE_TEAM_PAGE = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.new_studio_mfe.use_new_course_team_page', __name__) + + +def use_new_course_team_page(course_key): + """ + Returns a boolean if new studio course team mfe is enabled + """ + return ENABLE_NEW_STUDIO_COURSE_TEAM_PAGE.is_enabled(course_key) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 8be19ebc7b..1d181f1921 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -5,33 +5,80 @@ Common utility functions useful throughout the contentstore from collections import defaultdict import logging from contextlib import contextmanager -from datetime import datetime +from datetime import datetime, timezone +from uuid import uuid4 from django.conf import settings +from django.core.exceptions import ValidationError from django.urls import reverse from django.utils import translation from django.utils.translation import gettext as _ +from lti_consumer.models import CourseAllowPIISharingInLTIFlag from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.locator import LibraryLocator +from openedx_events.content_authoring.data import DuplicatedXBlockData +from openedx_events.content_authoring.signals import XBLOCK_DUPLICATED +from milestones import api as milestones_api from pytz import UTC +from xblock.fields import Scope from cms.djangoapps.contentstore.toggles import exam_setting_view_enabled +from common.djangoapps.course_modes.models import CourseMode +from common.djangoapps.edxmako.services import MakoService from common.djangoapps.student import auth +from common.djangoapps.student.auth import has_studio_read_access, has_studio_write_access from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole +from common.djangoapps.student.roles import ( + CourseInstructorRole, + CourseStaffRole, + GlobalStaff, +) +from common.djangoapps.util.course import get_link_for_about_page +from common.djangoapps.util.milestones_helpers import ( + is_prerequisite_courses_enabled, + is_valid_course_key, + remove_prerequisite_course, + set_prerequisite_courses, + get_namespace_choices, + generate_milestone_namespace +) +from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService +from openedx.core import toggles as core_toggles from openedx.core.djangoapps.course_apps.toggles import proctoring_settings_modal_view_enabled +from openedx.core.djangoapps.credit.api import get_credit_requirements, is_credit_course from openedx.core.djangoapps.discussions.config.waffle import ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND +from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration from openedx.core.djangoapps.django_comment_common.models import assign_default_role from openedx.core.djangoapps.django_comment_common.utils import seed_permissions_roles from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration.models import SiteConfiguration +from openedx.core.djangoapps.models.course_details import CourseDetails +from openedx.core.lib.courses import course_image_url from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.content_type_gating.partitions import CONTENT_TYPE_GATING_SCHEME +from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML +from cms.djangoapps.contentstore.toggles import ( + use_new_advanced_settings_page, + use_new_course_outline_page, + use_new_export_page, + use_new_files_uploads_page, + use_new_grading_page, + use_new_course_team_page, + use_new_home_page, + use_new_import_page, + use_new_schedule_details_page, + use_new_unit_page, + use_new_updates_page, + use_new_video_uploads_page, +) from cms.djangoapps.contentstore.toggles import use_new_text_editor, use_new_video_editor +from xmodule.library_tools import LibraryToolsService from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order from xmodule.partitions.partitions_service import get_all_partitions_for_course # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.services import SettingsService, ConfigurationService, TeamsConfigurationService + log = logging.getLogger(__name__) @@ -212,6 +259,161 @@ def get_editor_page_base_url(course_locator) -> str: return editor_url +def get_studio_home_url(): + """ + Gets course authoring microfrontend URL for Studio Home view. + """ + studio_home_url = None + if use_new_home_page(): + mfe_base_url = settings.COURSE_AUTHORING_MICROFRONTEND_URL + if mfe_base_url: + studio_home_url = f'{mfe_base_url}/home' + return studio_home_url + + +def get_schedule_details_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for schedule and details pages view. + """ + schedule_details_url = None + if use_new_schedule_details_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/settings/details' + if mfe_base_url: + schedule_details_url = course_mfe_url + return schedule_details_url + + +def get_advanced_settings_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for advanced settings page view. + """ + advanced_settings_url = None + if use_new_advanced_settings_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/settings/advanced' + if mfe_base_url: + advanced_settings_url = course_mfe_url + return advanced_settings_url + + +def get_grading_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for grading page view. + """ + grading_url = None + if use_new_grading_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/settings/grading' + if mfe_base_url: + grading_url = course_mfe_url + return grading_url + + +def get_course_team_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for course team page view. + """ + course_team_url = None + if use_new_course_team_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/course_team' + if mfe_base_url: + course_team_url = course_mfe_url + return course_team_url + + +def get_updates_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for updates page view. + """ + updates_url = None + if use_new_updates_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/course_info' + if mfe_base_url: + updates_url = course_mfe_url + return updates_url + + +def get_import_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for import page view. + """ + import_url = None + if use_new_import_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/import' + if mfe_base_url: + import_url = course_mfe_url + return import_url + + +def get_export_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for export page view. + """ + export_url = None + if use_new_export_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/export' + if mfe_base_url: + export_url = course_mfe_url + return export_url + + +def get_files_uploads_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for files and uploads page view. + """ + files_uploads_url = None + if use_new_files_uploads_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/assets' + if mfe_base_url: + files_uploads_url = course_mfe_url + return files_uploads_url + + +def get_video_uploads_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for files and uploads page view. + """ + video_uploads_url = None + if use_new_video_uploads_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/videos/' + if mfe_base_url: + video_uploads_url = course_mfe_url + return video_uploads_url + + +def get_course_outline_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for course oultine page view. + """ + course_outline_url = None + if use_new_course_outline_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}' + if mfe_base_url: + course_outline_url = course_mfe_url + return course_outline_url + + +def get_unit_url(course_locator) -> str: + """ + Gets course authoring microfrontend URL for unit page view. + """ + unit_url = None + if use_new_unit_page(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/container/' + if mfe_base_url: + unit_url = course_mfe_url + return unit_url + + def course_import_olx_validation_is_enabled(): """ Check if course olx validation is enabled on course import. @@ -371,7 +573,7 @@ def get_split_group_display_name(xblock, course): Arguments: xblock (XBlock): The courseware component. - course (XBlock): The course descriptor. + course (XBlock): The course block. Returns: group name (String): Group name of the matching group xblock. @@ -398,14 +600,14 @@ def get_user_partition_info(xblock, schemes=None, course=None): schemes (iterable of str): If provided, filter partitions to include only schemes with the provided names. - course (XBlock): The course descriptor. If provided, uses this to look up the user partitions + course (XBlock): The course block. If provided, uses this to look up the user partitions instead of loading the course. This is useful if we're calling this function multiple times for the same course want to minimize queries to the modulestore. Returns: list Example Usage: - >>> get_user_partition_info(block, schemes=["cohort", "verification"]) + >>> get_user_partition_info(xblock, schemes=["cohort", "verification"]) [ { "id": 12345, @@ -508,7 +710,7 @@ def get_visibility_partition_info(xblock, course=None): Arguments: xblock (XBlock): The component being edited. - course (XBlock): The course descriptor. If provided, uses this to look up the user partitions + course (XBlock): The course block. If provided, uses this to look up the user partitions instead of loading the course. This is useful if we're calling this function multiple times for the same course want to minimize queries to the modulestore. @@ -568,8 +770,8 @@ def get_xblock_aside_instance(usage_key): :param usage_key: Usage key of aside xblock """ try: - descriptor = modulestore().get_item(usage_key.usage_key) - for aside in descriptor.runtime.get_asides(descriptor): + xblock = modulestore().get_item(usage_key.usage_key) + for aside in xblock.runtime.get_asides(xblock): if aside.scope_ids.block_type == usage_key.aside_type: return aside except ItemNotFoundError: @@ -753,3 +955,375 @@ def get_subsections_by_assignment_type(course_key): f'{section.display_name} - {subsection.display_name}' ) return subsections_by_assignment_type + + +def update_course_discussions_settings(course_key): + """ + Updates course provider_type when new course is created + """ + provider = DiscussionsConfiguration.get(context_key=course_key).provider_type + store = modulestore() + course = store.get_course(course_key) + course.discussions_settings['provider_type'] = provider + store.update_item(course, course.published_by) + + +def duplicate_block( + parent_usage_key, + duplicate_source_usage_key, + user, + dest_usage_key=None, + display_name=None, + shallow=False, + is_child=False +): + """ + Duplicate an existing xblock as a child of the supplied parent_usage_key. You can + optionally specify what usage key the new duplicate block will use via dest_usage_key. + + If shallow is True, does not copy children. Otherwise, this function calls itself + recursively, and will set the is_child flag to True when dealing with recursed child + blocks. + """ + store = modulestore() + with store.bulk_operations(duplicate_source_usage_key.course_key): + source_item = store.get_item(duplicate_source_usage_key) + if not dest_usage_key: + # Change the blockID to be unique. + dest_usage_key = source_item.location.replace(name=uuid4().hex) + + category = dest_usage_key.block_type + + duplicate_metadata, asides_to_create = gather_block_attributes( + source_item, display_name=display_name, is_child=is_child, + ) + + dest_block = store.create_item( + user.id, + dest_usage_key.course_key, + dest_usage_key.block_type, + block_id=dest_usage_key.block_id, + definition_data=source_item.get_explicitly_set_fields_by_scope(Scope.content), + metadata=duplicate_metadata, + runtime=source_item.runtime, + asides=asides_to_create + ) + + children_handled = False + + if hasattr(dest_block, 'studio_post_duplicate'): + # Allow an XBlock to do anything fancy it may need to when duplicated from another block. + # These blocks may handle their own children or parenting if needed. Let them return booleans to + # let us know if we need to handle these or not. + load_services_for_studio(dest_block.runtime, user) + children_handled = dest_block.studio_post_duplicate(store, source_item) + + # Children are not automatically copied over (and not all xblocks have a 'children' attribute). + # Because DAGs are not fully supported, we need to actually duplicate each child as well. + if source_item.has_children and not shallow and not children_handled: + dest_block.children = dest_block.children or [] + for child in source_item.children: + dupe = duplicate_block(dest_block.location, child, user=user, is_child=True) + if dupe not in dest_block.children: # _duplicate_block may add the child for us. + dest_block.children.append(dupe) + store.update_item(dest_block, user.id) + + # pylint: disable=protected-access + if 'detached' not in source_item.runtime.load_block_type(category)._class_tags: + parent = store.get_item(parent_usage_key) + # If source was already a child of the parent, add duplicate immediately afterward. + # Otherwise, add child to end. + if source_item.location in parent.children: + source_index = parent.children.index(source_item.location) + parent.children.insert(source_index + 1, dest_block.location) + else: + parent.children.append(dest_block.location) + store.update_item(parent, user.id) + + # .. event_implemented_name: XBLOCK_DUPLICATED + XBLOCK_DUPLICATED.send_event( + time=datetime.now(timezone.utc), + xblock_info=DuplicatedXBlockData( + usage_key=dest_block.location, + block_type=dest_block.location.block_type, + source_usage_key=duplicate_source_usage_key, + ) + ) + + return dest_block.location + + +def update_from_source(*, source_block, destination_block, user_id): + """ + Update a block to have all the settings and attributes of another source. + + Copies over all attributes and settings of a source block to a destination + block. Blocks must be the same type. This function does not modify or duplicate + children. + + This function is useful when a block, originally copied from a source block, drifts + and needs to be updated to match the original. + + The modulestore function copy_from_template will copy a block's children recursively, + replacing the target block's children. It does not, however, update any of the target + block's settings. copy_from_template, then, is useful for cases like the Library + Content Block, where the children are the same across all instances, but the settings + may differ. + + By contrast, for cases where we're copying a block that has drifted from its source, + we need to update the target block's settings, but we don't want to replace its children, + or, at least, not only replace its children. update_from_source is useful for these cases. + + This function is meant to be imported by pluggable django apps looking to manage duplicated + sections of a course. It is placed here for lack of a more appropriate location, since this + code has not yet been brought up to the standards in OEP-45. + """ + duplicate_metadata, asides = gather_block_attributes(source_block, display_name=source_block.display_name) + for key, value in duplicate_metadata.items(): + setattr(destination_block, key, value) + for key, value in source_block.get_explicitly_set_fields_by_scope(Scope.content).items(): + setattr(destination_block, key, value) + modulestore().update_item( + destination_block, + user_id, + metadata=duplicate_metadata, + asides=asides, + ) + + +def gather_block_attributes(source_item, display_name=None, is_child=False): + """ + Gather all the attributes of the source block that need to be copied over to a new or updated block. + """ + # Update the display name to indicate this is a duplicate (unless display name provided). + # Can't use own_metadata(), b/c it converts data for JSON serialization - + # not suitable for setting metadata of the new block + duplicate_metadata = {} + for field in source_item.fields.values(): + if field.scope == Scope.settings and field.is_set_on(source_item): + duplicate_metadata[field.name] = field.read_from(source_item) + + if is_child: + display_name = display_name or source_item.display_name or source_item.category + + if display_name is not None: + duplicate_metadata['display_name'] = display_name + else: + if source_item.display_name is None: + duplicate_metadata['display_name'] = _("Duplicate of {0}").format(source_item.category) + else: + duplicate_metadata['display_name'] = _("Duplicate of '{0}'").format(source_item.display_name) + + asides_to_create = [] + for aside in source_item.runtime.get_asides(source_item): + for field in aside.fields.values(): + if field.scope in (Scope.settings, Scope.content,) and field.is_set_on(aside): + asides_to_create.append(aside) + break + + for aside in asides_to_create: + for field in aside.fields.values(): + if field.scope not in (Scope.settings, Scope.content,): + field.delete_from(aside) + return duplicate_metadata, asides_to_create + + +def load_services_for_studio(runtime, user): + """ + Function to set some required services used for XBlock edits and studio_view. + (i.e. whenever we're not loading _prepare_runtime_for_preview.) This is required to make information + about the current user (especially permissions) available via services as needed. + """ + services = { + "user": DjangoXBlockUserService(user), + "studio_user_permissions": StudioPermissionsService(user), + "mako": MakoService(), + "settings": SettingsService(), + "lti-configuration": ConfigurationService(CourseAllowPIISharingInLTIFlag), + "teams_configuration": TeamsConfigurationService(), + "library_tools": LibraryToolsService(modulestore(), user.id) + } + + runtime._services.update(services) # lint-amnesty, pylint: disable=protected-access + + +def update_course_details(request, course_key, payload, course_block): + """ + Utils is used to update course details. + It is used for both DRF and django views. + """ + + from .views.entrance_exam import create_entrance_exam, delete_entrance_exam, update_entrance_exam + + # if pre-requisite course feature is enabled set pre-requisite course + if is_prerequisite_courses_enabled(): + prerequisite_course_keys = payload.get('pre_requisite_courses', []) + if prerequisite_course_keys: + if not all(is_valid_course_key(course_key) for course_key in prerequisite_course_keys): + raise ValidationError(_("Invalid prerequisite course key")) + set_prerequisite_courses(course_key, prerequisite_course_keys) + else: + # None is chosen, so remove the course prerequisites + course_milestones = milestones_api.get_course_milestones( + course_key=course_key, + relationship="requires", + ) + for milestone in course_milestones: + entrance_exam_namespace = generate_milestone_namespace( + get_namespace_choices().get('ENTRANCE_EXAM'), + course_key + ) + if milestone["namespace"] != entrance_exam_namespace: + remove_prerequisite_course(course_key, milestone) + + # If the entrance exams feature has been enabled, we'll need to check for some + # feature-specific settings and handle them accordingly + # We have to be careful that we're only executing the following logic if we actually + # need to create or delete an entrance exam from the specified course + if core_toggles.ENTRANCE_EXAMS.is_enabled(): + course_entrance_exam_present = course_block.entrance_exam_enabled + entrance_exam_enabled = payload.get('entrance_exam_enabled', '') == 'true' + ee_min_score_pct = payload.get('entrance_exam_minimum_score_pct', None) + # If the entrance exam box on the settings screen has been checked... + if entrance_exam_enabled: + # Load the default minimum score threshold from settings, then try to override it + entrance_exam_minimum_score_pct = float(settings.ENTRANCE_EXAM_MIN_SCORE_PCT) + if ee_min_score_pct: + entrance_exam_minimum_score_pct = float(ee_min_score_pct) + if entrance_exam_minimum_score_pct.is_integer(): + entrance_exam_minimum_score_pct = entrance_exam_minimum_score_pct / 100 + # If there's already an entrance exam defined, we'll update the existing one + if course_entrance_exam_present: + exam_data = { + 'entrance_exam_minimum_score_pct': entrance_exam_minimum_score_pct + } + update_entrance_exam(request, course_key, exam_data) + # If there's no entrance exam defined, we'll create a new one + else: + create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct) + + # If the entrance exam box on the settings screen has been unchecked, + # and the course has an entrance exam attached... + elif not entrance_exam_enabled and course_entrance_exam_present: + delete_entrance_exam(request, course_key) + + # Perform the normal update workflow for the CourseDetails model + return CourseDetails.update_from_json(course_key, payload, request.user) + + +def get_course_settings(request, course_key, course_block): + """ + Utils is used to get context of course settings. + It is used for both DRF and django views. + """ + + from .views.course import get_courses_accessible_to_user, _process_courses_list + + credit_eligibility_enabled = settings.FEATURES.get('ENABLE_CREDIT_ELIGIBILITY', False) + upload_asset_url = reverse_course_url('assets_handler', course_key) + + # see if the ORG of this course can be attributed to a defined configuration . In that case, the + # course about page should be editable in Studio + publisher_enabled = configuration_helpers.get_value_for_org( + course_block.location.org, + 'ENABLE_PUBLISHER', + settings.FEATURES.get('ENABLE_PUBLISHER', False) + ) + marketing_enabled = configuration_helpers.get_value_for_org( + course_block.location.org, + 'ENABLE_MKTG_SITE', + settings.FEATURES.get('ENABLE_MKTG_SITE', False) + ) + enable_extended_course_details = configuration_helpers.get_value_for_org( + course_block.location.org, + 'ENABLE_EXTENDED_COURSE_DETAILS', + settings.FEATURES.get('ENABLE_EXTENDED_COURSE_DETAILS', False) + ) + + about_page_editable = not publisher_enabled + enrollment_end_editable = GlobalStaff().has_user(request.user) or not publisher_enabled + short_description_editable = configuration_helpers.get_value_for_org( + course_block.location.org, + 'EDITABLE_SHORT_DESCRIPTION', + settings.FEATURES.get('EDITABLE_SHORT_DESCRIPTION', True) + ) + sidebar_html_enabled = ENABLE_COURSE_ABOUT_SIDEBAR_HTML.is_enabled() + + verified_mode = CourseMode.verified_mode_for_course(course_key, include_expired=True) + upgrade_deadline = (verified_mode and verified_mode.expiration_datetime and + verified_mode.expiration_datetime.isoformat()) + settings_context = { + 'context_course': course_block, + 'course_locator': course_key, + 'lms_link_for_about_page': get_link_for_about_page(course_block), + 'course_image_url': course_image_url(course_block, 'course_image'), + 'banner_image_url': course_image_url(course_block, 'banner_image'), + 'video_thumbnail_image_url': course_image_url(course_block, 'video_thumbnail_image'), + 'details_url': reverse_course_url('settings_handler', course_key), + 'about_page_editable': about_page_editable, + 'marketing_enabled': marketing_enabled, + 'short_description_editable': short_description_editable, + 'sidebar_html_enabled': sidebar_html_enabled, + 'upload_asset_url': upload_asset_url, + 'course_handler_url': reverse_course_url('course_handler', course_key), + 'language_options': settings.ALL_LANGUAGES, + 'credit_eligibility_enabled': credit_eligibility_enabled, + 'is_credit_course': False, + 'show_min_grade_warning': False, + 'enrollment_end_editable': enrollment_end_editable, + 'is_prerequisite_courses_enabled': is_prerequisite_courses_enabled(), + 'is_entrance_exams_enabled': core_toggles.ENTRANCE_EXAMS.is_enabled(), + 'enable_extended_course_details': enable_extended_course_details, + 'upgrade_deadline': upgrade_deadline, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id), + } + if is_prerequisite_courses_enabled(): + courses, in_process_course_actions = get_courses_accessible_to_user(request) + # exclude current course from the list of available courses + courses = [course for course in courses if course.id != course_key] + if courses: + courses, __ = _process_courses_list(courses, in_process_course_actions) + settings_context.update({'possible_pre_requisite_courses': courses}) + + if credit_eligibility_enabled: + if is_credit_course(course_key): + # get and all credit eligibility requirements + credit_requirements = get_credit_requirements(course_key) + # pair together requirements with same 'namespace' values + paired_requirements = {} + for requirement in credit_requirements: + namespace = requirement.pop("namespace") + paired_requirements.setdefault(namespace, []).append(requirement) + + # if 'minimum_grade_credit' of a course is not set or 0 then + # show warning message to course author. + show_min_grade_warning = False if course_block.minimum_grade_credit > 0 else True # lint-amnesty, pylint: disable=simplifiable-if-expression + settings_context.update( + { + 'is_credit_course': True, + 'credit_requirements': paired_requirements, + 'show_min_grade_warning': show_min_grade_warning, + } + ) + + return settings_context + + +class StudioPermissionsService: + """ + Service that can provide information about a user's permissions. + + Deprecated. To be replaced by a more general authorization service. + + Only used by LibraryContentBlock (and library_tools.py). + """ + def __init__(self, user): + self._user = user + + def can_read(self, course_key): + """ Does the user have read access to the given course/library? """ + return has_studio_read_access(self._user, course_key) + + def can_write(self, course_key): + """ Does the user have read access to the given course/library? """ + return has_studio_write_access(self._user, course_key) diff --git a/cms/djangoapps/contentstore/views/block.py b/cms/djangoapps/contentstore/views/block.py index 7c28f00501..a35230f3b8 100644 --- a/cms/djangoapps/contentstore/views/block.py +++ b/cms/djangoapps/contentstore/views/block.py @@ -4,19 +4,15 @@ import logging from collections import OrderedDict from datetime import datetime from functools import partial -from uuid import uuid4 from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.exceptions import PermissionDenied from django.http import Http404, HttpResponse, HttpResponseBadRequest -from django.utils.timezone import timezone from django.utils.translation import gettext as _ from django.views.decorators.http import require_http_methods from edx_django_utils.plugins import pluggable_override -from openedx_events.content_authoring.data import DuplicatedXBlockData -from openedx_events.content_authoring.signals import XBLOCK_DUPLICATED from edx_proctoring.api import ( does_backend_support_onboarding, get_exam_by_content_id, @@ -24,7 +20,6 @@ from edx_proctoring.api import ( ) from edx_proctoring.exceptions import ProctoredExamNotFoundException from help_tokens.core import HelpUrlExpert -from lti_consumer.models import CourseAllowPIISharingInLTIFlag from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import LibraryUsageLocator from pytz import UTC @@ -35,26 +30,23 @@ from xblock.fields import Scope from cms.djangoapps.contentstore.config.waffle import SHOW_REVIEW_RULES_FLAG from cms.djangoapps.models.settings.course_grading import CourseGradingModel from cms.lib.xblock.authoring_mixin import VISIBILITY_VIEW -from common.djangoapps.edxmako.services import MakoService from common.djangoapps.edxmako.shortcuts import render_to_string from common.djangoapps.static_replace import replace_static_urls from common.djangoapps.student.auth import has_studio_read_access, has_studio_write_access from common.djangoapps.util.date_utils import get_default_time_display from common.djangoapps.util.json_request import JsonResponse, expect_json -from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService from openedx.core.djangoapps.bookmarks import api as bookmarks_api from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration +from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE from openedx.core.lib.gating import api as gating_api from openedx.core.lib.xblock_utils import hash_resource, request_token, wrap_xblock, wrap_xblock_aside from openedx.core.toggles import ENTRANCE_EXAMS from xmodule.course_block import DEFAULT_START_DATE # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.library_tools import LibraryToolsService # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore import EdxJSONEncoder, ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.inheritance import own_metadata # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.services import ConfigurationService, SettingsService, TeamsConfigurationService # lint-amnesty, pylint: disable=wrong-import-order from xmodule.tabs import CourseTabList # lint-amnesty, pylint: disable=wrong-import-order from xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW, STUDIO_VIEW # lint-amnesty, pylint: disable=wrong-import-order @@ -67,11 +59,12 @@ from ..utils import ( get_visibility_partition_info, has_children_visible_to_specific_partition_groups, is_currently_visible_to_students, - is_self_paced + is_self_paced, duplicate_block, load_services_for_studio ) from .helpers import ( create_xblock, get_parent_xblock, + import_staged_content_from_user_clipboard, is_unit, usage_key_with_run, xblock_primary_child_category, @@ -169,6 +162,8 @@ def xblock_handler(request, usage_key_string=None): :display_name: name for new xblock, optional :boilerplate: template name for populating fields, optional and only used if duplicate_source_locator is not present + :staged_content: use "clipboard" to paste from the OLX user's clipboard. (Incompatible with all other + fields except parent_locator) The locator (unicode representation of a UsageKey) for the created xblock (minus children) is returned. """ if usage_key_string: @@ -241,11 +236,11 @@ def xblock_handler(request, usage_key_string=None): status=400 ) - dest_usage_key = _duplicate_block( + dest_usage_key = duplicate_block( parent_usage_key, duplicate_source_usage_key, request.user, - request.json.get('display_name'), + display_name=request.json.get('display_name'), ) return JsonResponse({ 'locator': str(dest_usage_key), @@ -273,45 +268,6 @@ def xblock_handler(request, usage_key_string=None): ) -class StudioPermissionsService: - """ - Service that can provide information about a user's permissions. - - Deprecated. To be replaced by a more general authorization service. - - Only used by LibraryContentBlock (and library_tools.py). - """ - def __init__(self, user): - self._user = user - - def can_read(self, course_key): - """ Does the user have read access to the given course/library? """ - return has_studio_read_access(self._user, course_key) - - def can_write(self, course_key): - """ Does the user have read access to the given course/library? """ - return has_studio_write_access(self._user, course_key) - - -def load_services_for_studio(runtime, user): - """ - Function to set some required services used for XBlock edits and studio_view. - (i.e. whenever we're not loading _prepare_runtime_for_preview.) This is required to make information - about the current user (especially permissions) available via services as needed. - """ - services = { - "user": DjangoXBlockUserService(user), - "studio_user_permissions": StudioPermissionsService(user), - "mako": MakoService(), - "settings": SettingsService(), - "lti-configuration": ConfigurationService(CourseAllowPIISharingInLTIFlag), - "teams_configuration": TeamsConfigurationService(), - "library_tools": LibraryToolsService(modulestore(), user.id) - } - - runtime._services.update(services) # lint-amnesty, pylint: disable=protected-access - - @require_http_methods("GET") @login_required @expect_json @@ -471,7 +427,7 @@ def xblock_outline_handler(request, usage_key_string): include_children_predicate=lambda xblock: not xblock.category == 'vertical' )) else: - return Http404 + raise Http404 @require_http_methods("GET") @@ -496,7 +452,7 @@ def xblock_container_handler(request, usage_key_string): ) return JsonResponse(response) else: - return Http404 + raise Http404 def _update_with_callback(xblock, user, old_metadata=None, old_content=None): @@ -701,6 +657,19 @@ def _create_block(request): if not has_studio_write_access(request.user, usage_key.course_key): raise PermissionDenied() + if request.json.get('staged_content') == "clipboard": + # Paste from the user's clipboard (content_staging app clipboard, not browser clipboard) into 'usage_key': + try: + created_xblock = import_staged_content_from_user_clipboard(parent_key=usage_key, request=request) + except Exception: # pylint: disable=broad-except + log.exception("Could not paste component into location {}".format(usage_key)) + return JsonResponse({"error": _('There was a problem pasting your component.')}, status=400) + if created_xblock is None: + return JsonResponse({"error": _('Your clipboard is empty or invalid.')}, status=400) + return JsonResponse( + {'locator': str(created_xblock.location), 'courseKey': str(created_xblock.location.course_key)} + ) + category = request.json['category'] if isinstance(usage_key, LibraryUsageLocator): # Only these categories are supported at this time. @@ -863,103 +832,6 @@ def _move_item(source_usage_key, target_parent_usage_key, user, target_index=Non return JsonResponse(context) -def _duplicate_block(parent_usage_key, duplicate_source_usage_key, user, display_name=None, is_child=False): - """ - Duplicate an existing xblock as a child of the supplied parent_usage_key. - """ - store = modulestore() - with store.bulk_operations(duplicate_source_usage_key.course_key): - source_item = store.get_item(duplicate_source_usage_key) - # Change the blockID to be unique. - dest_usage_key = source_item.location.replace(name=uuid4().hex) - category = dest_usage_key.block_type - - # Update the display name to indicate this is a duplicate (unless display name provided). - # Can't use own_metadata(), b/c it converts data for JSON serialization - - # not suitable for setting metadata of the new block - duplicate_metadata = {} - for field in source_item.fields.values(): - if field.scope == Scope.settings and field.is_set_on(source_item): - duplicate_metadata[field.name] = field.read_from(source_item) - - if is_child: - display_name = display_name or source_item.display_name or source_item.category - - if display_name is not None: - duplicate_metadata['display_name'] = display_name - else: - if source_item.display_name is None: - duplicate_metadata['display_name'] = _("Duplicate of {0}").format(source_item.category) - else: - duplicate_metadata['display_name'] = _("Duplicate of '{0}'").format(source_item.display_name) - - asides_to_create = [] - for aside in source_item.runtime.get_asides(source_item): - for field in aside.fields.values(): - if field.scope in (Scope.settings, Scope.content,) and field.is_set_on(aside): - asides_to_create.append(aside) - break - - for aside in asides_to_create: - for field in aside.fields.values(): - if field.scope not in (Scope.settings, Scope.content,): - field.delete_from(aside) - - dest_block = store.create_item( - user.id, - dest_usage_key.course_key, - dest_usage_key.block_type, - block_id=dest_usage_key.block_id, - definition_data=source_item.get_explicitly_set_fields_by_scope(Scope.content), - metadata=duplicate_metadata, - runtime=source_item.runtime, - asides=asides_to_create - ) - - children_handled = False - - if hasattr(dest_block, 'studio_post_duplicate'): - # Allow an XBlock to do anything fancy it may need to when duplicated from another block. - # These blocks may handle their own children or parenting if needed. Let them return booleans to - # let us know if we need to handle these or not. - load_services_for_studio(dest_block.runtime, user) - children_handled = dest_block.studio_post_duplicate(store, source_item) - - # Children are not automatically copied over (and not all xblocks have a 'children' attribute). - # Because DAGs are not fully supported, we need to actually duplicate each child as well. - if source_item.has_children and not children_handled: - dest_block.children = dest_block.children or [] - for child in source_item.children: - dupe = _duplicate_block(dest_block.location, child, user=user, is_child=True) - if dupe not in dest_block.children: # _duplicate_block may add the child for us. - dest_block.children.append(dupe) - store.update_item(dest_block, user.id) - - # pylint: disable=protected-access - if 'detached' not in source_item.runtime.load_block_type(category)._class_tags: - parent = store.get_item(parent_usage_key) - # If source was already a child of the parent, add duplicate immediately afterward. - # Otherwise, add child to end. - if source_item.location in parent.children: - source_index = parent.children.index(source_item.location) - parent.children.insert(source_index + 1, dest_block.location) - else: - parent.children.append(dest_block.location) - store.update_item(parent, user.id) - - # .. event_implemented_name: XBLOCK_DUPLICATED - XBLOCK_DUPLICATED.send_event( - time=datetime.now(timezone.utc), - xblock_info=DuplicatedXBlockData( - usage_key=dest_block.location, - block_type=dest_block.location.block_type, - source_usage_key=duplicate_source_usage_key, - ) - ) - - return dest_block.location - - @login_required @expect_json def delete_item(request, usage_key): @@ -1224,6 +1096,13 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F 'has_children': xblock.has_children } + if course is not None and PUBLIC_VIDEO_SHARE.is_enabled(xblock.location.course_key): + xblock_info.update({ + 'video_sharing_enabled': True, + 'video_sharing_options': course.video_sharing_options, + 'video_sharing_doc_url': HelpUrlExpert.the_one().url_for_token('social_sharing') + }) + if xblock.category == 'course': discussions_config = DiscussionsConfiguration.get(course.id) show_unit_level_discussions_toggle = ( diff --git a/cms/djangoapps/contentstore/views/certificates.py b/cms/djangoapps/contentstore/views/certificates.py index 6535e0a857..3c43ab1065 100644 --- a/cms/djangoapps/contentstore/views/certificates.py +++ b/cms/djangoapps/contentstore/views/certificates.py @@ -128,7 +128,7 @@ class CertificateValidationError(CertificateException): class CertificateManager: """ The CertificateManager is responsible for storage, retrieval, and manipulation of Certificates - Certificates are not stored in the Django ORM, they are a field/setting on the course descriptor + Certificates are not stored in the Django ORM, they are a field/setting on the course block """ @staticmethod def parse(json_string): diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 7f774ab161..69a5a5ffd9 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -27,12 +27,18 @@ from common.djangoapps.xblock_django.models import XBlockStudioConfigurationFlag from cms.djangoapps.contentstore.toggles import use_new_problem_editor from openedx.core.lib.xblock_utils import get_aside_from_xblock, is_xblock_aside from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration +try: + # Technically this is a django app plugin, so we should not error if it's not installed: + import openedx.core.djangoapps.content_staging.api as content_staging_api +except ImportError: + content_staging_api = None from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order -from ..utils import get_lms_link_for_item, get_sibling_urls, reverse_course_url +from ..utils import get_lms_link_for_item, get_sibling_urls, reverse_course_url, \ + load_services_for_studio from .helpers import get_parent_xblock, is_unit, xblock_type_display_name -from .block import add_container_page_publishing_info, create_xblock_info, load_services_for_studio +from .block import add_container_page_publishing_info, create_xblock_info __all__ = [ 'container_handler', @@ -185,6 +191,12 @@ def container_handler(request, usage_key_string): break index += 1 + # Get the status of the user's clipboard so they can paste components if they have something to paste + if content_staging_api: + user_clipboard = content_staging_api.get_user_clipboard_json(request.user.id, request) + else: + user_clipboard = {"content": None} + return render_to_response('container.html', { 'language_code': request.LANGUAGE_CODE, 'context_course': course, # Needed only for display of menus at top of page. @@ -205,7 +217,9 @@ def container_handler(request, usage_key_string): 'xblock_info': xblock_info, 'draft_preview_link': preview_lms_link, 'published_preview_link': lms_link, - 'templates': CONTAINER_TEMPLATES + 'templates': CONTAINER_TEMPLATES, + # Status of the user's clipboard, exactly as would be returned from the "GET clipboard" REST API. + 'user_clipboard': user_clipboard, }) else: return HttpResponseBadRequest("Only supports HTML requests") @@ -292,8 +306,8 @@ def get_component_templates(courselike, library=False): # lint-amnesty, pylint: # by the components in the order listed in COMPONENT_TYPES. component_types = COMPONENT_TYPES[:] - # Libraries do not support discussions and openassessment and other libraries - component_not_supported_by_library = ['discussion', 'library', 'openassessment'] + # Libraries do not support discussions, drag-and-drop, and openassessment and other libraries + component_not_supported_by_library = ['discussion', 'library', 'openassessment', 'drag-and-drop-v2'] if library: component_types = [component for component in component_types if component not in set(component_not_supported_by_library)] @@ -317,11 +331,14 @@ def get_component_templates(courselike, library=False): # lint-amnesty, pylint: # TODO: Once mixins are defined per-application, rather than per-runtime, # this should use a cms mixed-in class. (cpennington) template_id = None - display_name = xblock_type_display_name(category, _('Blank')) # this is the Blank Advanced problem + display_name = xblock_type_display_name(category, _('Blank')) # The ORA "blank" assessment should be Peer Assessment Only if category == 'openassessment': display_name = _("Peer Assessment Only") template_id = "peer-assessment" + elif category == 'problem': + # Override generic "Problem" name to describe this blank template: + display_name = _("Blank Advanced Problem") templates_for_category.append( create_template_dict(display_name, category, support_level_without_template, template_id, 'advanced') ) @@ -552,18 +569,18 @@ def component_handler(request, usage_key_string, handler, suffix=''): try: if is_xblock_aside(usage_key): - # Get the descriptor for the block being wrapped by the aside (not the aside itself) - descriptor = modulestore().get_item(usage_key.usage_key) - handler_descriptor = get_aside_from_xblock(descriptor, usage_key.aside_type) - asides = [handler_descriptor] + # Get the block being wrapped by the aside (not the aside itself) + block = modulestore().get_item(usage_key.usage_key) + handler_block = get_aside_from_xblock(block, usage_key.aside_type) + asides = [handler_block] else: - descriptor = modulestore().get_item(usage_key) - handler_descriptor = descriptor + block = modulestore().get_item(usage_key) + handler_block = block asides = [] - load_services_for_studio(handler_descriptor.runtime, request.user) - resp = handler_descriptor.handle(handler, req, suffix) + load_services_for_studio(handler_block.runtime, request.user) + resp = handler_block.handle(handler, req, suffix) except NoSuchHandlerError: - log.info("XBlock %s attempted to access missing handler %r", handler_descriptor, handler, exc_info=True) + log.info("XBlock %s attempted to access missing handler %r", handler_block, handler, exc_info=True) raise Http404 # lint-amnesty, pylint: disable=raise-missing-from # unintentional update to handle any side effects of handle call @@ -572,7 +589,7 @@ def component_handler(request, usage_key_string, handler, suffix=''): # TNL 101-62 studio write permission is also checked for editing content. if has_course_author_access(request.user, usage_key.course_key): - modulestore().update_item(descriptor, request.user.id, asides=asides) + modulestore().update_item(block, request.user.id, asides=asides) else: #fail quietly if user is not course author. log.warning( diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 26e54e9598..652cc20d2a 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -17,7 +17,7 @@ from ccx_keys.locator import CCXLocator from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.decorators import login_required -from django.core.exceptions import PermissionDenied +from django.core.exceptions import PermissionDenied, ValidationError as DjangoValidationError from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import redirect from django.urls import reverse @@ -26,7 +26,6 @@ from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.http import require_GET, require_http_methods from edx_django_utils.monitoring import function_trace from edx_toggles.toggles import WaffleSwitch -from milestones import api as milestones_api from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import BlockUsageLocator @@ -41,40 +40,32 @@ from cms.djangoapps.models.settings.course_metadata import CourseMetadata from cms.djangoapps.models.settings.encoder import CourseSettingsEncoder from common.djangoapps.course_action_state.managers import CourseActionStateItemNotFoundError from common.djangoapps.course_action_state.models import CourseRerunState, CourseRerunUIStateManager -from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.edxmako.shortcuts import render_to_response -from common.djangoapps.student.auth import has_course_author_access, has_studio_read_access, has_studio_write_access +from common.djangoapps.student.auth import ( + has_course_author_access, + has_studio_read_access, + has_studio_write_access, + has_studio_advanced_settings_access +) from common.djangoapps.student.roles import ( CourseInstructorRole, CourseStaffRole, GlobalStaff, UserBasedRole ) -from common.djangoapps.util.course import get_link_for_about_page from common.djangoapps.util.date_utils import get_default_time_display from common.djangoapps.util.json_request import JsonResponse, JsonResponseBadRequest, expect_json -from common.djangoapps.util.milestones_helpers import ( - is_prerequisite_courses_enabled, - is_valid_course_key, - remove_prerequisite_course, - set_prerequisite_courses, - get_namespace_choices, - generate_milestone_namespace -) from common.djangoapps.util.string_utils import _has_non_ascii_characters from common.djangoapps.xblock_django.api import deprecated_xblocks -from openedx.core import toggles as core_toggles from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from openedx.core.djangoapps.credit.api import get_credit_requirements, is_credit_course +from openedx.core.djangoapps.credit.api import is_credit_course from openedx.core.djangoapps.credit.tasks import update_credit_course_requirements from openedx.core.djangoapps.models.course_details import CourseDetails from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangolib.js_utils import dump_js_escaped_json from openedx.core.lib.course_tabs import CourseTabPluginManager -from openedx.core.lib.courses import course_image_url from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.content_type_gating.partitions import CONTENT_TYPE_GATING_SCHEME -from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML from organizations.models import Organization from xmodule.contentstore.content import StaticContent # lint-amnesty, pylint: disable=wrong-import-order from xmodule.course_block import CourseBlock, DEFAULT_START_DATE, CourseFields # lint-amnesty, pylint: disable=wrong-import-order @@ -98,6 +89,7 @@ from ..tasks import rerun_course as rerun_course_task from ..toggles import split_library_view_on_dashboard from ..utils import ( add_instructor, + get_course_settings, get_lms_link_for_item, get_proctored_exam_settings_url, get_subsections_by_assignment_type, @@ -106,11 +98,12 @@ from ..utils import ( reverse_course_url, reverse_library_url, reverse_url, - reverse_usage_url + reverse_usage_url, + update_course_discussions_settings, + update_course_details, ) from .component import ADVANCED_COMPONENT_TYPES from .helpers import is_content_creator -from .entrance_exam import create_entrance_exam, delete_entrance_exam, update_entrance_exam from .block import create_xblock_info from .library import ( LIBRARIES_ENABLED, @@ -148,17 +141,6 @@ class AccessListFallback(Exception): pass # lint-amnesty, pylint: disable=unnecessary-pass -def has_advanced_settings_access(user): - """ - If DISABLE_ADVANCED_SETTINGS feature is enabled, only global staff can access "Advanced Settings". - """ - return ( - not settings.FEATURES.get('DISABLE_ADVANCED_SETTINGS', False) - or user.is_staff - or user.is_superuser - ) - - def get_course_and_check_access(course_key, user, depth=0): """ Function used to calculate and return the locator and course block @@ -764,7 +746,6 @@ def course_index(request, course_key): 'frontend_app_publisher_url': frontend_app_publisher_url, 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id), 'advance_settings_url': reverse_course_url('advanced_settings_handler', course_block.id), - 'advance_settings_access': has_advanced_settings_access(request.user), 'proctoring_errors': proctoring_errors, }) @@ -992,6 +973,7 @@ def create_new_course(user, org, number, run, fields): store_for_new_course = modulestore().default_modulestore.get_modulestore_type() new_course = create_new_course_in_store(store_for_new_course, user, org, number, run, fields) add_organization_course(org_data, new_course.id) + update_course_discussions_settings(new_course.id) return new_course @@ -1163,96 +1145,11 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab json: update the Course and About xblocks through the CourseDetails model """ course_key = CourseKey.from_string(course_key_string) - credit_eligibility_enabled = settings.FEATURES.get('ENABLE_CREDIT_ELIGIBILITY', False) + with modulestore().bulk_operations(course_key): course_block = get_course_and_check_access(course_key, request.user) if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': - upload_asset_url = reverse_course_url('assets_handler', course_key) - - # see if the ORG of this course can be attributed to a defined configuration . In that case, the - # course about page should be editable in Studio - publisher_enabled = configuration_helpers.get_value_for_org( - course_block.location.org, - 'ENABLE_PUBLISHER', - settings.FEATURES.get('ENABLE_PUBLISHER', False) - ) - marketing_enabled = configuration_helpers.get_value_for_org( - course_block.location.org, - 'ENABLE_MKTG_SITE', - settings.FEATURES.get('ENABLE_MKTG_SITE', False) - ) - enable_extended_course_details = configuration_helpers.get_value_for_org( - course_block.location.org, - 'ENABLE_EXTENDED_COURSE_DETAILS', - settings.FEATURES.get('ENABLE_EXTENDED_COURSE_DETAILS', False) - ) - - about_page_editable = not publisher_enabled - enrollment_end_editable = GlobalStaff().has_user(request.user) or not publisher_enabled - short_description_editable = configuration_helpers.get_value_for_org( - course_block.location.org, - 'EDITABLE_SHORT_DESCRIPTION', - settings.FEATURES.get('EDITABLE_SHORT_DESCRIPTION', True) - ) - sidebar_html_enabled = ENABLE_COURSE_ABOUT_SIDEBAR_HTML.is_enabled() - - verified_mode = CourseMode.verified_mode_for_course(course_key, include_expired=True) - upgrade_deadline = (verified_mode and verified_mode.expiration_datetime and - verified_mode.expiration_datetime.isoformat()) - settings_context = { - 'context_course': course_block, - 'course_locator': course_key, - 'lms_link_for_about_page': get_link_for_about_page(course_block), - 'course_image_url': course_image_url(course_block, 'course_image'), - 'banner_image_url': course_image_url(course_block, 'banner_image'), - 'video_thumbnail_image_url': course_image_url(course_block, 'video_thumbnail_image'), - 'details_url': reverse_course_url('settings_handler', course_key), - 'about_page_editable': about_page_editable, - 'marketing_enabled': marketing_enabled, - 'short_description_editable': short_description_editable, - 'sidebar_html_enabled': sidebar_html_enabled, - 'upload_asset_url': upload_asset_url, - 'course_handler_url': reverse_course_url('course_handler', course_key), - 'language_options': settings.ALL_LANGUAGES, - 'credit_eligibility_enabled': credit_eligibility_enabled, - 'is_credit_course': False, - 'show_min_grade_warning': False, - 'enrollment_end_editable': enrollment_end_editable, - 'is_prerequisite_courses_enabled': is_prerequisite_courses_enabled(), - 'is_entrance_exams_enabled': core_toggles.ENTRANCE_EXAMS.is_enabled(), - 'enable_extended_course_details': enable_extended_course_details, - 'upgrade_deadline': upgrade_deadline, - 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id), - } - if is_prerequisite_courses_enabled(): - courses, in_process_course_actions = get_courses_accessible_to_user(request) - # exclude current course from the list of available courses - courses = [course for course in courses if course.id != course_key] - if courses: - courses, __ = _process_courses_list(courses, in_process_course_actions) - settings_context.update({'possible_pre_requisite_courses': courses}) - - if credit_eligibility_enabled: - if is_credit_course(course_key): - # get and all credit eligibility requirements - credit_requirements = get_credit_requirements(course_key) - # pair together requirements with same 'namespace' values - paired_requirements = {} - for requirement in credit_requirements: - namespace = requirement.pop("namespace") - paired_requirements.setdefault(namespace, []).append(requirement) - - # if 'minimum_grade_credit' of a course is not set or 0 then - # show warning message to course author. - show_min_grade_warning = False if course_block.minimum_grade_credit > 0 else True # lint-amnesty, pylint: disable=simplifiable-if-expression - settings_context.update( - { - 'is_credit_course': True, - 'credit_requirements': paired_requirements, - 'show_min_grade_warning': show_min_grade_warning, - } - ) - + settings_context = get_course_settings(request, course_key, course_block) return render_to_response('settings.html', settings_context) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): # pylint: disable=too-many-nested-blocks if request.method == 'GET': @@ -1264,63 +1161,12 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab ) # For every other possible method type submitted by the caller... else: - # if pre-requisite course feature is enabled set pre-requisite course - if is_prerequisite_courses_enabled(): - prerequisite_course_keys = request.json.get('pre_requisite_courses', []) - if prerequisite_course_keys: - if not all(is_valid_course_key(course_key) for course_key in prerequisite_course_keys): - return JsonResponseBadRequest({"error": _("Invalid prerequisite course key")}) - set_prerequisite_courses(course_key, prerequisite_course_keys) - else: - # None is chosen, so remove the course prerequisites - course_milestones = milestones_api.get_course_milestones( - course_key=course_key, - relationship="requires", - ) - for milestone in course_milestones: - entrance_exam_namespace = generate_milestone_namespace( - get_namespace_choices().get('ENTRANCE_EXAM'), - course_key - ) - if milestone["namespace"] != entrance_exam_namespace: - remove_prerequisite_course(course_key, milestone) + try: + update_data = update_course_details(request, course_key, request.json, course_block) + except DjangoValidationError as err: + return JsonResponseBadRequest({"error": err.message}) - # If the entrance exams feature has been enabled, we'll need to check for some - # feature-specific settings and handle them accordingly - # We have to be careful that we're only executing the following logic if we actually - # need to create or delete an entrance exam from the specified course - if core_toggles.ENTRANCE_EXAMS.is_enabled(): - course_entrance_exam_present = course_block.entrance_exam_enabled - entrance_exam_enabled = request.json.get('entrance_exam_enabled', '') == 'true' - ee_min_score_pct = request.json.get('entrance_exam_minimum_score_pct', None) - # If the entrance exam box on the settings screen has been checked... - if entrance_exam_enabled: - # Load the default minimum score threshold from settings, then try to override it - entrance_exam_minimum_score_pct = float(settings.ENTRANCE_EXAM_MIN_SCORE_PCT) - if ee_min_score_pct: - entrance_exam_minimum_score_pct = float(ee_min_score_pct) - if entrance_exam_minimum_score_pct.is_integer(): - entrance_exam_minimum_score_pct = entrance_exam_minimum_score_pct / 100 - # If there's already an entrance exam defined, we'll update the existing one - if course_entrance_exam_present: - exam_data = { - 'entrance_exam_minimum_score_pct': entrance_exam_minimum_score_pct - } - update_entrance_exam(request, course_key, exam_data) - # If there's no entrance exam defined, we'll create a new one - else: - create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct) - - # If the entrance exam box on the settings screen has been unchecked, - # and the course has an entrance exam attached... - elif not entrance_exam_enabled and course_entrance_exam_present: - delete_entrance_exam(request, course_key) - - # Perform the normal update workflow for the CourseDetails model - return JsonResponse( - CourseDetails.update_from_json(course_key, request.json, request.user), - encoder=CourseSettingsEncoder - ) + return JsonResponse(update_data, encoder=CourseSettingsEncoder) @login_required @@ -1435,7 +1281,7 @@ def advanced_settings_handler(request, course_key_string): json: update the Course's settings. The payload is a json rep of the metadata dicts. """ - if not has_advanced_settings_access(request.user): + if not has_studio_advanced_settings_access(request.user): raise PermissionDenied() course_key = CourseKey.from_string(course_key_string) diff --git a/cms/djangoapps/contentstore/views/entrance_exam.py b/cms/djangoapps/contentstore/views/entrance_exam.py index 63917d17f6..ced3ed531b 100644 --- a/cms/djangoapps/contentstore/views/entrance_exam.py +++ b/cms/djangoapps/contentstore/views/entrance_exam.py @@ -176,9 +176,9 @@ def _get_entrance_exam(request, course_key): except InvalidKeyError: return HttpResponse(status=404) try: - exam_descriptor = modulestore().get_item(exam_key) + exam_block = modulestore().get_item(exam_key) return HttpResponse( # lint-amnesty, pylint: disable=http-response-with-content-type-json - dump_js_escaped_json({'locator': str(exam_descriptor.location)}), + dump_js_escaped_json({'locator': str(exam_block.location)}), status=200, content_type='application/json') except ItemNotFoundError: return HttpResponse(status=404) diff --git a/cms/djangoapps/contentstore/views/helpers.py b/cms/djangoapps/contentstore/views/helpers.py index 398f0f6c63..0c2ba76fac 100644 --- a/cms/djangoapps/contentstore/views/helpers.py +++ b/cms/djangoapps/contentstore/views/helpers.py @@ -3,20 +3,31 @@ Helper methods for Studio views. """ import urllib +from lxml import etree from uuid import uuid4 from django.http import HttpResponse from django.utils.translation import gettext as _ from opaque_keys.edx.keys import UsageKey +from opaque_keys.edx.locator import DefinitionLocator, LocalId from xblock.core import XBlock +from xblock.fields import ScopeIds +from xblock.runtime import IdGenerator from xmodule.modulestore.django import modulestore from xmodule.tabs import StaticTab +from cms.djangoapps.contentstore.views.preview import _load_preview_block from cms.djangoapps.models.settings.course_grading import CourseGradingModel from common.djangoapps.student import auth from common.djangoapps.student.roles import CourseCreatorRole, OrgContentCreatorRole from openedx.core.toggles import ENTRANCE_EXAMS +try: + # Technically this is a django app plugin, so we should not error if it's not installed: + import openedx.core.djangoapps.content_staging.api as content_staging_api +except ImportError: + content_staging_api = None + from ..utils import reverse_course_url, reverse_library_url, reverse_usage_url __all__ = ['event'] @@ -90,12 +101,27 @@ def xblock_has_own_studio_page(xblock, parent_xblock=None): return xblock.has_children -def xblock_studio_url(xblock, parent_xblock=None): +def xblock_studio_url(xblock, parent_xblock=None, find_parent=False): """ Returns the Studio editing URL for the specified xblock. + + You can pass the parent xblock as an optimization, to avoid needing to load + it twice, as sometimes the parent has to be checked. + + If you pass in a leaf block that doesn't have its own Studio page, this will + normally return None, but if you use find_parent=True, this will find the + nearest ancestor (usually the parent unit) that does have a Studio page and + return that URL. """ if not xblock_has_own_studio_page(xblock, parent_xblock): - return None + if find_parent: + while xblock and not xblock_has_own_studio_page(xblock, parent_xblock): + xblock = parent_xblock or get_parent_xblock(xblock) + parent_xblock = None + if not xblock: + return None + else: + return None category = xblock.category if category == 'course': return reverse_course_url('course_handler', xblock.location.course_key) @@ -116,7 +142,7 @@ def xblock_type_display_name(xblock, default_display_name=None): Returns the display name for the specified type of xblock. Note that an instance can be passed in for context dependent names, e.g. a vertical beneath a sequential is a Unit. - :param xblock: An xblock instance or the type of xblock. + :param xblock: An xblock instance or the type of xblock (as a string). :param default_display_name: The default value to return if no display name can be found. :return: """ @@ -133,6 +159,13 @@ def xblock_type_display_name(xblock, default_display_name=None): return _('Subsection') elif category == 'vertical': return _('Unit') + elif category == 'problem': + # The problem XBlock's display_name.default is not helpful ("Blank Advanced Problem") but changing it could have + # too many ripple effects in other places, so we have a special case for capa problems here. + # Note: With a ProblemBlock instance, we could actually check block.problem_types to give a more specific + # description like "Multiple Choice Problem", but that won't work if our 'block' argument is just the block_type + # string ("problem"). + return _('Problem') component_class = XBlock.load_class(category) if hasattr(component_class, 'display_name') and component_class.display_name.default: return _(component_class.display_name.default) # lint-amnesty, pylint: disable=translation-of-non-string @@ -271,6 +304,76 @@ def create_xblock(parent_locator, user, category, display_name, boilerplate=None return created_block +class ImportIdGenerator(IdGenerator): + """ + Modulestore's IdGenerator doesn't work for importing single blocks as OLX, + so we implement our own + """ + def __init__(self, context_key): + super().__init__() + self.context_key = context_key + + def create_aside(self, definition_id, usage_id, aside_type): + """ Generate a new aside key """ + raise NotImplementedError() + + def create_usage(self, def_id) -> UsageKey: + """ Generate a new UsageKey for an XBlock """ + # Note: Split modulestore will detect this temporary ID and create a new block ID when the XBlock is saved. + return self.context_key.make_usage_key(def_id.block_type, LocalId()) + + def create_definition(self, block_type, slug=None) -> DefinitionLocator: + """ Generate a new definition_id for an XBlock """ + # Note: Split modulestore will detect this temporary ID and create a new definition ID when the XBlock is saved. + return DefinitionLocator(block_type, LocalId(block_type)) + + +def import_staged_content_from_user_clipboard(parent_key: UsageKey, request): + """ + Import a block (and any children it has) from "staged" OLX. + Does not deal with permissions or REST stuff - do that before calling this. + + Returns the newly created block on success or None if the clipboard is + empty. + """ + if not content_staging_api: + raise RuntimeError("The required content_staging app is not installed") + user_clipboard = content_staging_api.get_user_clipboard(request.user.id) + if not user_clipboard: + # Clipboard is empty or expired/error/loading + return None + block_type = user_clipboard.content.block_type + olx_str = content_staging_api.get_staged_content_olx(user_clipboard.content.id) + node = etree.fromstring(olx_str) + store = modulestore() + with store.bulk_operations(parent_key.course_key): + parent_descriptor = store.get_item(parent_key) + # Some blocks like drag-and-drop only work here with the full XBlock runtime loaded: + parent_xblock = _load_preview_block(request, parent_descriptor) + runtime = parent_xblock.runtime + # Generate the new ID: + id_generator = ImportIdGenerator(parent_key.context_key) + def_id = id_generator.create_definition(block_type, user_clipboard.source_usage_key.block_id) + usage_id = id_generator.create_usage(def_id) + keys = ScopeIds(None, block_type, def_id, usage_id) + # parse_xml is a really messy API. We pass both 'keys' and 'id_generator' and, depending on the XBlock, either + # one may be used to determine the new XBlock's usage key, and the other will be ignored. e.g. video ignores + # 'keys' and uses 'id_generator', but the default XBlock parse_xml ignores 'id_generator' and uses 'keys'. + # For children of this block, obviously only id_generator is used. + xblock_class = runtime.load_block_type(block_type) + temp_xblock = xblock_class.parse_xml(node, runtime, keys, id_generator) + if xblock_class.has_children and temp_xblock.children: + raise NotImplementedError("We don't yet support pasting XBlocks with children") + temp_xblock.parent = parent_key + # Store a reference to where this block was copied from, in the 'copied_from_block' field (AuthoringMixin) + temp_xblock.copied_from_block = str(user_clipboard.source_usage_key) + # Save the XBlock into modulestore. We need to save the block and its parent for this to work: + new_xblock = store.update_item(temp_xblock, request.user.id, allow_not_found=True) + parent_xblock.children.append(new_xblock.location) + store.update_item(parent_xblock, request.user.id) + return new_xblock + + def is_item_in_course_tree(item): """ Check that the item is in the course tree. diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index 12b00d16e6..bf5f463a1e 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -11,6 +11,7 @@ from django.urls import reverse from django.utils.translation import gettext as _ from django.views.decorators.clickjacking import xframe_options_exempt from opaque_keys.edx.keys import UsageKey +from rest_framework.request import Request from web_fragments.fragment import Fragment from xblock.django.request import django_to_webob_request, webob_to_django_response from xblock.exceptions import NoSuchHandlerError @@ -24,7 +25,7 @@ from xmodule.services import SettingsService, TeamsConfigurationService from xmodule.studio_editable import has_author_view from xmodule.util.sandboxing import SandboxService from xmodule.util.xmodule_django import add_webpack_to_fragment -from xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW +from xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW, XModuleMixin from cms.djangoapps.xblock_config.models import StudioConfig from cms.djangoapps.contentstore.toggles import individualize_anonymous_user_id, ENABLE_COPY_PASTE_FEATURE from cms.lib.xblock.field_data import CmsFieldData @@ -65,8 +66,8 @@ def preview_handler(request, usage_key_string, handler, suffix=''): """ usage_key = UsageKey.from_string(usage_key_string) - descriptor = modulestore().get_item(usage_key) - instance = _load_preview_block(request, descriptor) + block = modulestore().get_item(usage_key) + instance = _load_preview_block(request, block) # Let the module handle the AJAX req = django_to_webob_request(request) @@ -154,6 +155,7 @@ def _prepare_runtime_for_preview(request, block, field_data): required for rendering block previews. request: The active django request + block: An XBlock field_data: Wrapped field data for previews """ @@ -192,16 +194,11 @@ def _prepare_runtime_for_preview(request, block, field_data): # stick the license wrapper in front wrappers.insert(0, partial(wrap_with_license, mako_service=mako_service)) - preview_anonymous_user_id = 'student' + anonymous_user_id = deprecated_anonymous_user_id = 'student' if individualize_anonymous_user_id(course_id): - # There are blocks (capa, html, and video) where we do not want to scope - # the anonymous_user_id to specific courses. These are captured in the - # block attribute 'requires_per_student_anonymous_id'. Please note, - # the course_id field in AnynomousUserID model is blank if value is None. - if getattr(block, 'requires_per_student_anonymous_id', False): - preview_anonymous_user_id = anonymous_id_for_user(request.user, None) - else: - preview_anonymous_user_id = anonymous_id_for_user(request.user, course_id) + anonymous_user_id = anonymous_id_for_user(request.user, course_id) + # See the docstring of `DjangoXBlockUserService`. + deprecated_anonymous_user_id = anonymous_id_for_user(request.user, None) services = { "field-data": field_data, @@ -211,7 +208,8 @@ def _prepare_runtime_for_preview(request, block, field_data): "user": DjangoXBlockUserService( request.user, user_role=get_user_role(request.user, course_id), - anonymous_user_id=preview_anonymous_user_id, + anonymous_user_id=anonymous_user_id, + deprecated_anonymous_user_id=deprecated_anonymous_user_id, ), "partitions": StudioPartitionService(course_id=course_id), "teams_configuration": TeamsConfigurationService(), @@ -256,29 +254,29 @@ class StudioPartitionService(PartitionService): return None -def _load_preview_block(request, descriptor): +def _load_preview_block(request: Request, block: XModuleMixin): """ - Return a preview XBlock instantiated from the supplied descriptor. Will use mutable fields + Return a preview XBlock instantiated from the supplied block. Will use mutable fields if XBlock supports an author_view. Otherwise, will use immutable fields and student_view. request: The active django request - descriptor: An XModuleDescriptor + block: An XModuleMixin """ student_data = KvsFieldData(SessionKeyValueStore(request)) - if has_author_view(descriptor): + if has_author_view(block): wrapper = partial(CmsFieldData, student_data=student_data) else: wrapper = partial(LmsFieldData, student_data=student_data) # wrap the _field_data upfront to pass to _prepare_runtime_for_preview - wrapped_field_data = wrapper(descriptor._field_data) # pylint: disable=protected-access - _prepare_runtime_for_preview(request, descriptor, wrapped_field_data) + wrapped_field_data = wrapper(block._field_data) # pylint: disable=protected-access + _prepare_runtime_for_preview(request, block, wrapped_field_data) - descriptor.bind_for_student( + block.bind_for_student( request.user.id, [wrapper] ) - return descriptor + return block def _is_xblock_reorderable(xblock, context): @@ -307,8 +305,10 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): selected_groups_label = _('Access restricted to: {list_of_groups}').format(list_of_groups=selected_groups_label) # lint-amnesty, pylint: disable=line-too-long course = modulestore().get_course(xblock.location.course_key) can_edit = context.get('can_edit', True) + # Is this a course or a library? + is_course = xblock.scope_ids.usage_id.context_key.is_course # Copy-paste is a new feature; while we are beta-testing it, only beta users with the Waffle flag enabled see it - enable_copy_paste = can_edit and ENABLE_COPY_PASTE_FEATURE.is_enabled() + enable_copy_paste = can_edit and is_course and ENABLE_COPY_PASTE_FEATURE.is_enabled() template_context = { 'xblock_context': context, 'xblock': xblock, @@ -318,10 +318,10 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): 'is_reorderable': is_reorderable, 'can_edit': can_edit, 'enable_copy_paste': enable_copy_paste, - 'can_edit_visibility': context.get('can_edit_visibility', xblock.scope_ids.usage_id.context_key.is_course), + 'can_edit_visibility': context.get('can_edit_visibility', is_course), 'selected_groups_label': selected_groups_label, 'can_add': context.get('can_add', True), - 'can_move': context.get('can_move', xblock.scope_ids.usage_id.context_key.is_course), + 'can_move': context.get('can_move', is_course), 'language': getattr(course, 'language', None) } @@ -332,12 +332,12 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): return frag -def get_preview_fragment(request, descriptor, context): +def get_preview_fragment(request, block, context): """ Returns the HTML returned by the XModule's student_view or author_view (if available), - specified by the descriptor and idx. + specified by the block and idx. """ - block = _load_preview_block(request, descriptor) + block = _load_preview_block(request, block) preview_view = AUTHOR_VIEW if has_author_view(block) else STUDENT_VIEW diff --git a/cms/djangoapps/contentstore/views/tests/test_block.py b/cms/djangoapps/contentstore/views/tests/test_block.py index 863da2e02f..bb827d361c 100644 --- a/cms/djangoapps/contentstore/views/tests/test_block.py +++ b/cms/djangoapps/contentstore/views/tests/test_block.py @@ -12,6 +12,7 @@ from django.http import Http404 from django.test import TestCase from django.test.client import RequestFactory from django.urls import reverse +from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE from openedx_events.content_authoring.data import DuplicatedXBlockData from openedx_events.content_authoring.signals import XBLOCK_DUPLICATED from openedx_events.tests.utils import OpenEdxEventsTestMixin @@ -47,7 +48,7 @@ from xmodule.partitions.tests.test_partitions import MockPartitionService from xmodule.x_module import STUDENT_VIEW, STUDIO_VIEW from cms.djangoapps.contentstore.tests.utils import CourseTestCase -from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_usage_url +from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_usage_url, duplicate_block, update_from_source from cms.djangoapps.contentstore.views import block as item_module from common.djangoapps.student.tests.factories import StaffFactory, UserFactory from common.djangoapps.xblock_django.models import ( @@ -786,6 +787,30 @@ class TestDuplicateItem(ItemTest, DuplicateHelper, OpenEdxEventsTestMixin): # Now send a custom display name for the duplicate. verify_name(self.seq_usage_key, self.chapter_usage_key, "customized name", display_name="customized name") + def test_shallow_duplicate(self): + """ + Test that duplicate_block(..., shallow=True) can duplicate a block but ignores its children. + """ + source_course = CourseFactory() + user = UserFactory.create() + source_chapter = BlockFactory(parent=source_course, category='chapter', display_name='Source Chapter') + BlockFactory(parent=source_chapter, category='html', display_name='Child') + # Refresh. + source_chapter = self.store.get_item(source_chapter.location) + self.assertEqual(len(source_chapter.get_children()), 1) + destination_course = CourseFactory() + destination_location = duplicate_block( + parent_usage_key=destination_course.location, + duplicate_source_usage_key=source_chapter.location, + user=user, + display_name=source_chapter.display_name, + shallow=True, + ) + # Refresh here, too, just to be sure. + destination_chapter = self.store.get_item(destination_location) + self.assertEqual(len(destination_chapter.get_children()), 0) + self.assertEqual(destination_chapter.display_name, 'Source Chapter') + @ddt.ddt class TestMoveItem(ItemTest): @@ -2159,10 +2184,10 @@ class TestComponentHandler(TestCase): self.modulestore = patcher.start() self.addCleanup(patcher.stop) - # component_handler calls modulestore.get_item to get the descriptor of the requested xBlock. + # component_handler calls modulestore.get_item to get the requested xBlock. # Here, we mock the return value of modulestore.get_item so it can be used to mock the handler - # of the xBlock descriptor. - self.descriptor = self.modulestore.return_value.get_item.return_value + # of the xBlock. + self.block = self.modulestore.return_value.get_item.return_value self.usage_key = BlockUsageLocator( CourseLocator('dummy_org', 'dummy_course', 'dummy_run'), 'dummy_category', 'dummy_name' @@ -2173,7 +2198,7 @@ class TestComponentHandler(TestCase): self.request.user = self.user def test_invalid_handler(self): - self.descriptor.handle.side_effect = NoSuchHandlerError + self.block.handle.side_effect = NoSuchHandlerError with self.assertRaises(Http404): component_handler(self.request, self.usage_key_string, 'invalid_handler') @@ -2185,7 +2210,7 @@ class TestComponentHandler(TestCase): self.assertEqual(request.method, method) return Response() - self.descriptor.handle = check_handler + self.block.handle = check_handler # Have to use the right method to create the request to get the HTTP method that we want req_factory_method = getattr(self.request_factory, method.lower()) @@ -2198,7 +2223,7 @@ class TestComponentHandler(TestCase): def create_response(handler, request, suffix): # lint-amnesty, pylint: disable=unused-argument return Response(status_code=status_code) - self.descriptor.handle = create_response + self.block.handle = create_response self.assertEqual(component_handler(self.request, self.usage_key_string, 'dummy_handler').status_code, status_code) @@ -2219,7 +2244,7 @@ class TestComponentHandler(TestCase): self.request.user = UserFactory() mock_handler = 'dummy_handler' - self.descriptor.handle = create_response + self.block.handle = create_response with patch( 'cms.djangoapps.contentstore.views.component.is_xblock_aside', @@ -2253,7 +2278,7 @@ class TestComponentHandler(TestCase): else self.usage_key_string ) - self.descriptor.handle = create_response + self.block.handle = create_response with patch( 'cms.djangoapps.contentstore.views.component.is_xblock_aside', @@ -2770,6 +2795,28 @@ class TestXBlockInfo(ItemTest): course_xblock_info = create_xblock_info(self.course) self.assertTrue(course_xblock_info['highlights_enabled_for_messaging']) + def test_xblock_public_video_sharing_enabled(self): + """ + Public video sharing is included in the xblock info when enable. + """ + self.course.video_sharing_options = 'all-on' + with patch.object(PUBLIC_VIDEO_SHARE, 'is_enabled', return_value=True): + self.store.update_item(self.course, None) + course_xblock_info = create_xblock_info(self.course) + self.assertTrue(course_xblock_info['video_sharing_enabled']) + self.assertEqual(course_xblock_info['video_sharing_options'], 'all-on') + + def test_xblock_public_video_sharing_disabled(self): + """ + Public video sharing not is included in the xblock info when disabled. + """ + self.course.video_sharing_options = 'arbitrary' + with patch.object(PUBLIC_VIDEO_SHARE, 'is_enabled', return_value=False): + self.store.update_item(self.course, None) + course_xblock_info = create_xblock_info(self.course) + self.assertNotIn('video_sharing_enabled', course_xblock_info) + self.assertNotIn('video_sharing_options', course_xblock_info) + def validate_course_xblock_info(self, xblock_info, has_child_info=True, course_outline=False): """ Validate that the xblock info is correct for the test course. @@ -3472,3 +3519,111 @@ class TestXBlockPublishingInfo(ItemTest): # Check that in self paced course content has live state now xblock_info = self._get_xblock_info(chapter.location) self._verify_visibility_state(xblock_info, VisibilityState.live) + + +@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', + lambda self, block: ['test_aside']) +class TestUpdateFromSource(ModuleStoreTestCase): + """ + Test update_from_source. + """ + + def setUp(self): + """ + Set up the runtime for tests. + """ + super().setUp() + key_store = DictKeyValueStore() + field_data = KvsFieldData(key_store) + self.runtime = TestRuntime(services={'field-data': field_data}) + + def create_source_block(self, course): + """ + Create a chapter with all the fixings. + """ + source_block = BlockFactory( + parent=course, + category='course_info', + display_name='Source Block', + metadata={'due': datetime(2010, 11, 22, 4, 0, tzinfo=UTC)}, + ) + + def_id = self.runtime.id_generator.create_definition('html') + usage_id = self.runtime.id_generator.create_usage(def_id) + + aside = AsideTest(scope_ids=ScopeIds('user', 'html', def_id, usage_id), runtime=self.runtime) + aside.field11 = 'html_new_value1' + + # The data attribute is handled in a special manner and should be updated. + source_block.data = '
test
' + # This field is set on the content scope (definition_data), which should be updated. + source_block.items = ['test', 'beep'] + + self.store.update_item(source_block, self.user.id, asides=[aside]) + + # quick sanity checks + source_block = self.store.get_item(source_block.location) + self.assertEqual(source_block.due, datetime(2010, 11, 22, 4, 0, tzinfo=UTC)) + self.assertEqual(source_block.display_name, 'Source Block') + self.assertEqual(source_block.runtime.get_asides(source_block)[0].field11, 'html_new_value1') + self.assertEqual(source_block.data, '
test
') + self.assertEqual(source_block.items, ['test', 'beep']) + + return source_block + + def check_updated(self, source_block, destination_key): + """ + Check that the destination block has been updated to match our source block. + """ + revised = self.store.get_item(destination_key) + self.assertEqual(source_block.display_name, revised.display_name) + self.assertEqual(source_block.due, revised.due) + self.assertEqual(revised.data, source_block.data) + self.assertEqual(revised.items, source_block.items) + + self.assertEqual( + revised.runtime.get_asides(revised)[0].field11, + source_block.runtime.get_asides(source_block)[0].field11, + ) + + @XBlockAside.register_temp_plugin(AsideTest, 'test_aside') + def test_update_from_source(self): + """ + Test that update_from_source updates the destination block. + """ + course = CourseFactory() + user = UserFactory.create() + + source_block = self.create_source_block(course) + + destination_block = BlockFactory(parent=course, category='course_info', display_name='Destination Problem') + update_from_source(source_block=source_block, destination_block=destination_block, user_id=user.id) + self.check_updated(source_block, destination_block.location) + + @XBlockAside.register_temp_plugin(AsideTest, 'test_aside') + def test_update_clobbers(self): + """ + Verify that our update replaces all settings on the block. + """ + course = CourseFactory() + user = UserFactory.create() + + source_block = self.create_source_block(course) + + destination_block = BlockFactory( + parent=course, + category='course_info', + display_name='Destination Chapter', + metadata={'due': datetime(2025, 10, 21, 6, 5, tzinfo=UTC)}, + ) + + def_id = self.runtime.id_generator.create_definition('html') + usage_id = self.runtime.id_generator.create_usage(def_id) + aside = AsideTest(scope_ids=ScopeIds('user', 'html', def_id, usage_id), runtime=self.runtime) + aside.field11 = 'Other stuff' + destination_block.data = '
other stuff
' + destination_block.items = ['other stuff', 'boop'] + self.store.update_item(destination_block, user.id, asides=[aside]) + + update_from_source(source_block=source_block, destination_block=destination_block, user_id=user.id) + self.check_updated(source_block, destination_block.location) diff --git a/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py b/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py new file mode 100644 index 0000000000..d769239c85 --- /dev/null +++ b/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py @@ -0,0 +1,62 @@ +""" +Test the import_staged_content_from_user_clipboard() method, which is used to +allow users to paste XBlocks that were copied using the staged_content/clipboard +APIs. +""" +from opaque_keys.edx.keys import UsageKey +from rest_framework.test import APIClient +from xmodule.modulestore.django import modulestore +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import ToyCourseFactory + +CLIPBOARD_ENDPOINT = "/api/content-staging/v1/clipboard/" +XBLOCK_ENDPOINT = "/xblock/" + + +class ClipboardPasteTestCase(ModuleStoreTestCase): + """ + Test Clipboard Paste functionality + """ + + def _setup_course(self): + """ Set up the "Toy Course" and an APIClient for testing clipboard functionality. """ + # Setup: + course_key = ToyCourseFactory.create().id # See xmodule/modulestore/tests/sample_courses.py + client = APIClient() + client.login(username=self.user.username, password=self.user_password) + return (course_key, client) + + def test_copy_and_paste_video(self): + """ + Test copying a video from the course, and pasting it into the same unit + """ + course_key, client = self._setup_course() + + # Check how many blocks are in the vertical currently + parent_key = course_key.make_usage_key("vertical", "vertical_test") # This is the vertical that holds the video + orig_vertical = modulestore().get_item(parent_key) + assert len(orig_vertical.children) == 4 + + # Copy the video + video_key = course_key.make_usage_key("video", "sample_video") + copy_response = client.post(CLIPBOARD_ENDPOINT, {"usage_key": str(video_key)}, format="json") + assert copy_response.status_code == 200 + + # Paste the video + paste_response = client.post(XBLOCK_ENDPOINT, { + "parent_locator": str(parent_key), + "staged_content": "clipboard", + }, format="json") + assert paste_response.status_code == 200 + new_block_key = UsageKey.from_string(paste_response.json()["locator"]) + + # Now there should be an extra block in the vertical: + updated_vertical = modulestore().get_item(parent_key) + assert len(updated_vertical.children) == 5 + assert updated_vertical.children[-1] == new_block_key + # And it should match the original: + orig_video = modulestore().get_item(video_key) + new_video = modulestore().get_item(new_block_key) + assert new_video.youtube_id_1_0 == orig_video.youtube_id_1_0 + # The new block should store a reference to where it was copied from + assert new_video.copied_from_block == str(video_key) diff --git a/cms/djangoapps/contentstore/views/tests/test_helpers.py b/cms/djangoapps/contentstore/views/tests/test_helpers.py index 5d4a831417..d83c09e583 100644 --- a/cms/djangoapps/contentstore/views/tests/test_helpers.py +++ b/cms/djangoapps/contentstore/views/tests/test_helpers.py @@ -52,6 +52,8 @@ class HelpersTestCase(CourseTestCase): video = BlockFactory.create(parent_location=child_vertical.location, category="video", display_name="My Video") self.assertIsNone(xblock_studio_url(video)) + # Verify video URL with find_parent=True + self.assertEqual(xblock_studio_url(video, find_parent=True), f'/container/{child_vertical.location}') # Verify library URL library = LibraryFactory.create() diff --git a/cms/djangoapps/contentstore/views/tests/test_preview.py b/cms/djangoapps/contentstore/views/tests/test_preview.py index c8d1266853..1810842d4f 100644 --- a/cms/djangoapps/contentstore/views/tests/test_preview.py +++ b/cms/djangoapps/contentstore/views/tests/test_preview.py @@ -1,12 +1,11 @@ """ Tests for contentstore.views.preview.py """ - - import re from unittest import mock import ddt +from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID, ATTR_KEY_DEPRECATED_ANONYMOUS_USER_ID from django.test.client import Client, RequestFactory from django.test.utils import override_settings from edx_toggles.toggles.testutils import override_waffle_flag @@ -213,13 +212,13 @@ class StudioXBlockServiceBindingTest(ModuleStoreTestCase): """ Tests that the 'user' and 'i18n' services are provided by the Studio runtime. """ - descriptor = BlockFactory(category="pure", parent=self.course) + block = BlockFactory(category="pure", parent=self.course) _prepare_runtime_for_preview( self.request, - descriptor, + block, self.field_data, ) - service = descriptor.runtime.service(descriptor, expected_service) + service = block.runtime.service(block, expected_service) self.assertIsNotNone(service) @@ -242,32 +241,31 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase): self.request = RequestFactory().get('/dummy-url') self.request.user = self.user self.request.session = {} - self.descriptor = BlockFactory(category="video", parent=course) self.field_data = mock.Mock() self.contentstore = contentstore() - self.descriptor = BlockFactory(category="problem", parent=course) + self.block = BlockFactory(category="problem", parent=course) _prepare_runtime_for_preview( self.request, - block=self.descriptor, + block=self.block, field_data=mock.Mock(), ) self.course = self.store.get_item(course.location) def test_get_user_role(self): - assert self.descriptor.runtime.get_user_role() == 'staff' + assert self.block.runtime.get_user_role() == 'staff' @XBlock.register_temp_plugin(PureXBlock, identifier='pure') def test_render_template(self): - descriptor = BlockFactory(category="pure", parent=self.course) - html = get_preview_fragment(self.request, descriptor, {'element_id': 142}).content + block = BlockFactory(category="pure", parent=self.course) + html = get_preview_fragment(self.request, block, {'element_id': 142}).content assert '
Testing the MakoService
' in html @override_settings(COURSES_WITH_UNSAFE_CODE=[r'course-v1:edX\+LmsModuleShimTest\+2021_Fall']) def test_can_execute_unsafe_code(self): - assert self.descriptor.runtime.can_execute_unsafe_code() + assert self.block.runtime.can_execute_unsafe_code() def test_cannot_execute_unsafe_code(self): - assert not self.descriptor.runtime.can_execute_unsafe_code() + assert not self.block.runtime.can_execute_unsafe_code() @override_settings(PYTHON_LIB_FILENAME=PYTHON_LIB_FILENAME) def test_get_python_lib_zip(self): @@ -277,7 +275,7 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase): source_file=self.PYTHON_LIB_SOURCE_FILE, target_filename=self.PYTHON_LIB_FILENAME, ) - assert self.descriptor.runtime.get_python_lib_zip() == zipfile + assert self.block.runtime.get_python_lib_zip() == zipfile def test_no_get_python_lib_zip(self): zipfile = upload_file_to_course( @@ -286,40 +284,47 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase): source_file=self.PYTHON_LIB_SOURCE_FILE, target_filename=self.PYTHON_LIB_FILENAME, ) - assert self.descriptor.runtime.get_python_lib_zip() is None + assert self.block.runtime.get_python_lib_zip() is None def test_cache(self): - assert hasattr(self.descriptor.runtime.cache, 'get') - assert hasattr(self.descriptor.runtime.cache, 'set') + assert hasattr(self.block.runtime.cache, 'get') + assert hasattr(self.block.runtime.cache, 'set') def test_replace_urls(self): html = '' - assert self.descriptor.runtime.replace_urls(html) == \ + assert self.block.runtime.replace_urls(html) == \ static_replace.replace_static_urls(html, course_id=self.course.id) def test_anonymous_user_id_preview(self): - assert self.descriptor.runtime.anonymous_student_id == 'student' + assert self.block.runtime.anonymous_student_id == 'student' @override_waffle_flag(INDIVIDUALIZE_ANONYMOUS_USER_ID, active=True) def test_anonymous_user_id_individual_per_student(self): """Test anonymous_user_id on a block which uses per-student anonymous IDs""" # Create the runtime with the flag turned on. - descriptor = BlockFactory(category="problem", parent=self.course) + block = BlockFactory(category="problem", parent=self.course) _prepare_runtime_for_preview( self.request, - block=descriptor, + block=block, field_data=mock.Mock(), ) - assert descriptor.runtime.anonymous_student_id == '26262401c528d7c4a6bbeabe0455ec46' + deprecated_anonymous_user_id = ( + block.runtime.service(block, 'user').get_current_user().opt_attrs.get(ATTR_KEY_DEPRECATED_ANONYMOUS_USER_ID) + ) + assert deprecated_anonymous_user_id == '26262401c528d7c4a6bbeabe0455ec46' @override_waffle_flag(INDIVIDUALIZE_ANONYMOUS_USER_ID, active=True) def test_anonymous_user_id_individual_per_course(self): """Test anonymous_user_id on a block which uses per-course anonymous IDs""" # Create the runtime with the flag turned on. - descriptor = BlockFactory(category="lti", parent=self.course) + block = BlockFactory(category="lti", parent=self.course) _prepare_runtime_for_preview( self.request, - block=descriptor, + block=block, field_data=mock.Mock(), ) - assert descriptor.runtime.anonymous_student_id == 'ad503f629b55c531fed2e45aa17a3368' + + anonymous_user_id = ( + block.runtime.service(block, 'user').get_current_user().opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID) + ) + assert anonymous_user_id == 'ad503f629b55c531fed2e45aa17a3368' diff --git a/cms/djangoapps/contentstore/views/tests/test_transcripts.py b/cms/djangoapps/contentstore/views/tests/test_transcripts.py index 56391560e7..619cf6b775 100644 --- a/cms/djangoapps/contentstore/views/tests/test_transcripts.py +++ b/cms/djangoapps/contentstore/views/tests/test_transcripts.py @@ -256,6 +256,7 @@ class TestUploadTranscripts(BaseTranscripts): expected_edx_video_id = edx_video_id if edx_video_id else json_response['edx_video_id'] video = modulestore().get_item(self.video_usage_key) self.assertEqual(video.edx_video_id, expected_edx_video_id) + self.assertDictEqual(video.transcripts, {'en': f'{expected_edx_video_id}-en.srt'}) # Verify transcript content actual_transcript = get_video_transcript_content(video.edx_video_id, language_code='en') @@ -319,6 +320,8 @@ class TestUploadTranscripts(BaseTranscripts): expected_status_code=400, expected_message='There is a problem with this transcript file. Try to upload a different file.' ) + video = modulestore().get_item(self.video_usage_key) + self.assertDictEqual(video.transcripts, {}) def test_transcript_upload_unknown_category(self): """ @@ -364,7 +367,7 @@ class TestUploadTranscripts(BaseTranscripts): def test_transcript_upload_with_non_existant_edx_video_id(self): """ Test that transcript upload works as expected if `edx_video_id` set on - video descriptor is different from `edx_video_id` received in POST request. + video block is different from `edx_video_id` received in POST request. """ non_existant_edx_video_id = '1111-2222-3333-4444' diff --git a/cms/djangoapps/contentstore/views/tests/test_videos.py b/cms/djangoapps/contentstore/views/tests/test_videos.py index 7d02d667a6..c321129e95 100644 --- a/cms/djangoapps/contentstore/views/tests/test_videos.py +++ b/cms/djangoapps/contentstore/views/tests/test_videos.py @@ -16,6 +16,7 @@ import ddt import pytz from django.conf import settings from django.test.utils import override_settings +from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from edxval.api import ( create_or_update_transcript_preferences, @@ -364,6 +365,7 @@ class VideosHandlerTestCase( 'course_video_image_url', 'transcripts', 'transcription_status', + 'transcript_urls', 'error_description' } ) @@ -380,7 +382,7 @@ class VideosHandlerTestCase( [ 'edx_video_id', 'client_video_id', 'created', 'duration', 'status', 'course_video_image_url', 'transcripts', 'transcription_status', - 'error_description' + 'transcript_urls', 'error_description' ], [ { @@ -397,7 +399,7 @@ class VideosHandlerTestCase( [ 'edx_video_id', 'client_video_id', 'created', 'duration', 'status', 'course_video_image_url', 'transcripts', 'transcription_status', - 'error_description' + 'transcript_urls', 'error_description' ], [ { @@ -1574,7 +1576,6 @@ class VideoUrlsCsvTestCase( @ddt.ddt class GetVideoFeaturesTestCase( - VideoStudioAccessTestsMixin, CourseTestCase ): """Test cases for the get_video_features endpoint """ @@ -1582,10 +1583,9 @@ class GetVideoFeaturesTestCase( super().setUp() self.url = self.get_url_for_course_key() - def get_url_for_course_key(self, course_id=None): + def get_url_for_course_key(self): """ Helper to generate a url for a course key """ - course_id = course_id or str(self.course.id) - return reverse_course_url("video_features", course_id) + return reverse("video_features") def test_basic(self): """ Test for expected return keys """ diff --git a/cms/djangoapps/contentstore/views/transcripts_ajax.py b/cms/djangoapps/contentstore/views/transcripts_ajax.py index 9c20420996..fd272e2350 100644 --- a/cms/djangoapps/contentstore/views/transcripts_ajax.py +++ b/cms/djangoapps/contentstore/views/transcripts_ajax.py @@ -71,7 +71,7 @@ def link_video_to_component(video_component, user): Links a VAL video to the video component. Arguments: - video_component: video descriptor item. + video_component: video block. user: A requesting user. Returns: @@ -134,7 +134,7 @@ def validate_video_block(request, locator): locator: video locator. Returns: - A tuple containing error(or None) and video descriptor(i.e. if validation succeeds). + A tuple containing error(or None) and video block(i.e. if validation succeeds). Raises: PermissionDenied: if requesting user does not have access to author the video component. @@ -231,6 +231,8 @@ def upload_transcripts(request): file_data=ContentFile(sjson_subs), ) + video.transcripts['en'] = f"{edx_video_id}-en.srt" + video.save_with_metadata(request.user) if transcript_created is None: response = JsonResponse({'status': 'Invalid Video ID'}, status=400) diff --git a/cms/djangoapps/contentstore/views/videos.py b/cms/djangoapps/contentstore/views/videos.py index 3daa65b911..bc880de4cf 100644 --- a/cms/djangoapps/contentstore/views/videos.py +++ b/cms/djangoapps/contentstore/views/videos.py @@ -29,6 +29,7 @@ from edxval.api import ( create_video, get_3rd_party_transcription_plans, get_available_transcript_languages, + get_video_transcript_url, get_transcript_credentials_state_for_org, get_transcript_preferences, get_videos_for_course, @@ -45,7 +46,6 @@ from rest_framework.response import Response from common.djangoapps.edxmako.shortcuts import render_to_response from common.djangoapps.util.json_request import JsonResponse, expect_json -from common.djangoapps.util.views import ensure_valid_course_key from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE from openedx.core.djangoapps.video_pipeline.config.waffle import ( @@ -278,19 +278,14 @@ def video_images_upload_enabled(request): return JsonResponse({'allowThumbnailUpload': True}) -@ensure_valid_course_key @login_required @require_GET -def get_video_features(request, course_key_string): +def get_video_features(request): """ Return a dict with info about which video features are enabled """ - course_key = CourseKey.from_string(course_key_string) - course = get_course_and_check_access(course_key, request.user) - if not course: - return HttpResponseNotFound() features = { 'allowThumbnailUpload': VIDEO_IMAGE_UPLOAD_ENABLED.is_enabled(), - 'videoSharingEnabled': PUBLIC_VIDEO_SHARE.is_enabled(course_key), + 'videoSharingEnabled': PUBLIC_VIDEO_SHARE.is_enabled(), } return JsonResponse(features) @@ -580,6 +575,12 @@ def _get_videos(course, pagination_conf=None): video['transcription_status'] = ( StatusDisplayStrings.get(video['status']) if is_video_encodes_ready else '' ) + video['transcript_urls'] = {} + for language_code in video['transcripts']: + video['transcript_urls'][language_code] = get_video_transcript_url( + video_id=video['edx_video_id'], + language_code=language_code, + ) # Convert the video status. video['status'] = convert_video_status(video, is_video_encodes_ready) @@ -601,7 +602,7 @@ def _get_index_videos(course, pagination_conf=None): attrs = [ 'edx_video_id', 'client_video_id', 'created', 'duration', 'status', 'courses', 'transcripts', 'transcription_status', - 'error_description' + 'transcript_urls', 'error_description' ] def _get_values(video): diff --git a/cms/djangoapps/models/settings/course_grading.py b/cms/djangoapps/models/settings/course_grading.py index 6c72f3b9a6..e6a0e3690c 100644 --- a/cms/djangoapps/models/settings/course_grading.py +++ b/cms/djangoapps/models/settings/course_grading.py @@ -25,21 +25,21 @@ class CourseGradingModel: """ # Within this class, allow access to protected members of client classes. # This comes up when accessing kvs data and caches during kvs saves and modulestore writes. - def __init__(self, course_descriptor): + def __init__(self, course): self.graders = [ - CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course_descriptor.raw_grader) + CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course.raw_grader) ] # weights transformed to ints [0..100] - self.grade_cutoffs = course_descriptor.grade_cutoffs - self.grace_period = CourseGradingModel.convert_set_grace_period(course_descriptor) - self.minimum_grade_credit = course_descriptor.minimum_grade_credit + self.grade_cutoffs = course.grade_cutoffs + self.grace_period = CourseGradingModel.convert_set_grace_period(course) + self.minimum_grade_credit = course.minimum_grade_credit @classmethod def fetch(cls, course_key): """ Fetch the course grading policy for the given course from persistence and return a CourseGradingModel. """ - descriptor = modulestore().get_course(course_key) - model = cls(descriptor) + course = modulestore().get_course(course_key) + model = cls(course) return model @staticmethod @@ -48,10 +48,10 @@ class CourseGradingModel: Fetch the course's nth grader Returns an empty dict if there's no such grader. """ - descriptor = modulestore().get_course(course_key) + course = modulestore().get_course(course_key) index = int(index) - if len(descriptor.raw_grader) > index: - return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index]) + if len(course.raw_grader) > index: + return CourseGradingModel.jsonize_grader(index, course.raw_grader[index]) # return empty model else: @@ -69,27 +69,27 @@ class CourseGradingModel: Decode the json into CourseGradingModel and save any changes. Returns the modified model. Probably not the usual path for updates as it's too coarse grained. """ - descriptor = modulestore().get_course(course_key) - previous_grading_policy_hash = str(hash_grading_policy(descriptor.grading_policy)) + course = modulestore().get_course(course_key) + previous_grading_policy_hash = str(hash_grading_policy(course.grading_policy)) graders_parsed = [CourseGradingModel.parse_grader(jsonele) for jsonele in jsondict['graders']] fire_signal = CourseGradingModel.must_fire_grading_event_and_signal( course_key, graders_parsed, - descriptor, + course, jsondict ) - descriptor.raw_grader = graders_parsed - descriptor.grade_cutoffs = jsondict['grade_cutoffs'] + course.raw_grader = graders_parsed + course.grade_cutoffs = jsondict['grade_cutoffs'] - modulestore().update_item(descriptor, user.id) + modulestore().update_item(course, user.id) CourseGradingModel.update_grace_period_from_json(course_key, jsondict['grace_period'], user) CourseGradingModel.update_minimum_grade_credit_from_json(course_key, jsondict['minimum_grade_credit'], user) - descriptor = modulestore().get_course(course_key) - new_grading_policy_hash = str(hash_grading_policy(descriptor.grading_policy)) + course = modulestore().get_course(course_key) + new_grading_policy_hash = str(hash_grading_policy(course.grading_policy)) log.info( "Updated course grading policy for course %s from %s to %s. fire_signal = %s", str(course_key), @@ -153,28 +153,28 @@ class CourseGradingModel: Create or update the grader of the given type (string key) for the given course. Returns the modified grader which is a full model on the client but not on the server (just a dict) """ - descriptor = modulestore().get_course(course_key) - previous_grading_policy_hash = str(hash_grading_policy(descriptor.grading_policy)) + course = modulestore().get_course(course_key) + previous_grading_policy_hash = str(hash_grading_policy(course.grading_policy)) # parse removes the id; so, grab it before parse - index = int(grader.get('id', len(descriptor.raw_grader))) + index = int(grader.get('id', len(course.raw_grader))) grader = CourseGradingModel.parse_grader(grader) fire_signal = True - if index < len(descriptor.raw_grader): + if index < len(course.raw_grader): fire_signal = CourseGradingModel.must_fire_grading_event_and_signal_single_grader( course_key, grader, - descriptor.raw_grader[index] + course.raw_grader[index] ) - descriptor.raw_grader[index] = grader + course.raw_grader[index] = grader else: - descriptor.raw_grader.append(grader) + course.raw_grader.append(grader) - modulestore().update_item(descriptor, user.id) + modulestore().update_item(course, user.id) - descriptor = modulestore().get_course(course_key) - new_grading_policy_hash = str(hash_grading_policy(descriptor.grading_policy)) + course = modulestore().get_course(course_key) + new_grading_policy_hash = str(hash_grading_policy(course.grading_policy)) log.info( "Updated grader for course %s. Grading policy has changed from %s to %s. fire_signal = %s", str(course_key), @@ -185,7 +185,7 @@ class CourseGradingModel: if fire_signal: _grading_event_and_signal(course_key, user.id) - return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index]) + return CourseGradingModel.jsonize_grader(index, course.raw_grader[index]) @staticmethod def update_cutoffs_from_json(course_key, cutoffs, user): @@ -193,10 +193,10 @@ class CourseGradingModel: Create or update the grade cutoffs for the given course. Returns sent in cutoffs (ie., no extra db fetch). """ - descriptor = modulestore().get_course(course_key) - descriptor.grade_cutoffs = cutoffs + course = modulestore().get_course(course_key) + course.grade_cutoffs = cutoffs - modulestore().update_item(descriptor, user.id) + modulestore().update_item(course, user.id) _grading_event_and_signal(course_key, user.id) return cutoffs @@ -207,7 +207,7 @@ class CourseGradingModel: grace_period entry in an enclosing dict. It is also safe to call this method with a value of None for graceperiodjson. """ - descriptor = modulestore().get_course(course_key) + course = modulestore().get_course(course_key) # Before a graceperiod has ever been created, it will be None (once it has been # created, it cannot be set back to None). @@ -216,9 +216,9 @@ class CourseGradingModel: graceperiodjson = graceperiodjson['grace_period'] grace_timedelta = timedelta(**graceperiodjson) - descriptor.graceperiod = grace_timedelta + course.graceperiod = grace_timedelta - modulestore().update_item(descriptor, user.id) + modulestore().update_item(course, user.id) @staticmethod def update_minimum_grade_credit_from_json(course_key, minimum_grade_credit, user): @@ -230,29 +230,29 @@ class CourseGradingModel: user(User): The user object """ - descriptor = modulestore().get_course(course_key) + course = modulestore().get_course(course_key) # 'minimum_grade_credit' cannot be set to None if minimum_grade_credit is not None: minimum_grade_credit = minimum_grade_credit # lint-amnesty, pylint: disable=self-assigning-variable - descriptor.minimum_grade_credit = minimum_grade_credit - modulestore().update_item(descriptor, user.id) + course.minimum_grade_credit = minimum_grade_credit + modulestore().update_item(course, user.id) @staticmethod def delete_grader(course_key, index, user): """ Delete the grader of the given type from the given course. """ - descriptor = modulestore().get_course(course_key) + course = modulestore().get_course(course_key) index = int(index) - if index < len(descriptor.raw_grader): - del descriptor.raw_grader[index] + if index < len(course.raw_grader): + del course.raw_grader[index] # force propagation to definition - descriptor.raw_grader = descriptor.raw_grader + course.raw_grader = course.raw_grader - modulestore().update_item(descriptor, user.id) + modulestore().update_item(course, user.id) _grading_event_and_signal(course_key, user.id) @staticmethod @@ -260,37 +260,37 @@ class CourseGradingModel: """ Delete the course's grace period. """ - descriptor = modulestore().get_course(course_key) + course = modulestore().get_course(course_key) - del descriptor.graceperiod + del course.graceperiod - modulestore().update_item(descriptor, user.id) + modulestore().update_item(course, user.id) @staticmethod def get_section_grader_type(location): - descriptor = modulestore().get_item(location) + block = modulestore().get_item(location) return { - "graderType": descriptor.format if descriptor.format is not None else 'notgraded', + "graderType": block.format if block.format is not None else 'notgraded', "location": str(location), } @staticmethod - def update_section_grader_type(descriptor, grader_type, user): # lint-amnesty, pylint: disable=missing-function-docstring + def update_section_grader_type(block, grader_type, user): # lint-amnesty, pylint: disable=missing-function-docstring if grader_type is not None and grader_type != 'notgraded': - descriptor.format = grader_type - descriptor.graded = True + block.format = grader_type + block.graded = True else: - del descriptor.format - del descriptor.graded + del block.format + del block.graded - modulestore().update_item(descriptor, user.id) - _grading_event_and_signal(descriptor.location.course_key, user.id) + modulestore().update_item(block, user.id) + _grading_event_and_signal(block.location.course_key, user.id) return {'graderType': grader_type} @staticmethod - def convert_set_grace_period(descriptor): # lint-amnesty, pylint: disable=missing-function-docstring + def convert_set_grace_period(course): # lint-amnesty, pylint: disable=missing-function-docstring # 5 hours 59 minutes 59 seconds => converted to iso format - rawgrace = descriptor.graceperiod + rawgrace = course.graceperiod if rawgrace: hours_from_days = rawgrace.days * 24 seconds = rawgrace.seconds diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index c072a4ae55..f45130d9fa 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -155,14 +155,14 @@ class CourseMetadata: return exclude_list @classmethod - def fetch(cls, descriptor, filter_fields=None): + def fetch(cls, block, filter_fields=None): """ Fetch the key:value editable course details for the given course from persistence and return a CourseMetadata model. """ result = {} - metadata = cls.fetch_all(descriptor, filter_fields=filter_fields) - exclude_list_of_fields = cls.get_exclude_list_of_fields(descriptor.id) + metadata = cls.fetch_all(block, filter_fields=filter_fields) + exclude_list_of_fields = cls.get_exclude_list_of_fields(block.id) for key, value in metadata.items(): if key in exclude_list_of_fields: @@ -171,12 +171,12 @@ class CourseMetadata: return result @classmethod - def fetch_all(cls, descriptor, filter_fields=None): + def fetch_all(cls, block, filter_fields=None): """ Fetches all key:value pairs from persistence and returns a CourseMetadata model. """ result = {} - for field in descriptor.fields.values(): + for field in block.fields.values(): if field.scope != Scope.settings: continue @@ -189,7 +189,7 @@ class CourseMetadata: field_help = field_help.format(**help_args) result[field.name] = { - 'value': field.read_json(descriptor), + 'value': field.read_json(block), 'display_name': _(field.display_name), # lint-amnesty, pylint: disable=translation-of-non-string 'help': field_help, 'deprecated': field.runtime_options.get('deprecated', False), @@ -198,13 +198,13 @@ class CourseMetadata: return result @classmethod - def update_from_json(cls, descriptor, jsondict, user, filter_tabs=True): + def update_from_json(cls, block, jsondict, user, filter_tabs=True): """ Decode the json into CourseMetadata and save any changed attrs to the db. Ensures none of the fields are in the exclude list. """ - exclude_list_of_fields = cls.get_exclude_list_of_fields(descriptor.id) + exclude_list_of_fields = cls.get_exclude_list_of_fields(block.id) # Don't filter on the tab attribute if filter_tabs is False. if not filter_tabs: exclude_list_of_fields.remove("tabs") @@ -218,16 +218,16 @@ class CourseMetadata: continue try: val = model['value'] - if hasattr(descriptor, key) and getattr(descriptor, key) != val: - key_values[key] = descriptor.fields[key].from_json(val) + if hasattr(block, key) and getattr(block, key) != val: + key_values[key] = block.fields[key].from_json(val) except (TypeError, ValueError) as err: raise ValueError(_("Incorrect format for field '{name}'. {detailed_message}").format( # lint-amnesty, pylint: disable=raise-missing-from name=model['display_name'], detailed_message=str(err))) - return cls.update_from_dict(key_values, descriptor, user) + return cls.update_from_dict(key_values, block, user) @classmethod - def validate_and_update_from_json(cls, descriptor, jsondict, user, filter_tabs=True): + def validate_and_update_from_json(cls, block, jsondict, user, filter_tabs=True): """ Validate the values in the json dict (validated by xblock fields from_json method) @@ -240,7 +240,7 @@ class CourseMetadata: errors: list of error objects result: the updated course metadata or None if error """ - exclude_list_of_fields = cls.get_exclude_list_of_fields(descriptor.id) + exclude_list_of_fields = cls.get_exclude_list_of_fields(block.id) if not filter_tabs: exclude_list_of_fields.remove("tabs") @@ -254,8 +254,8 @@ class CourseMetadata: for key, model in filtered_dict.items(): try: val = model['value'] - if hasattr(descriptor, key) and getattr(descriptor, key) != val: - key_values[key] = descriptor.fields[key].from_json(val) + if hasattr(block, key) and getattr(block, key) != val: + key_values[key] = block.fields[key].from_json(val) except (TypeError, ValueError, ValidationError) as err: did_validate = False errors.append({'key': key, 'message': str(err), 'model': model}) @@ -264,7 +264,7 @@ class CourseMetadata: # Because we cannot pass course context to the exception, we need to check if the LTI provider # should actually be available to the course err_message = str(err) - if not exams_ida_enabled(descriptor.id): + if not exams_ida_enabled(block.id): available_providers = get_available_providers() available_providers.remove('lti_external') err_message = str(InvalidProctoringProvider(val, available_providers)) @@ -277,29 +277,29 @@ class CourseMetadata: errors = errors + team_setting_errors did_validate = False - proctoring_errors = cls.validate_proctoring_settings(descriptor, filtered_dict, user) + proctoring_errors = cls.validate_proctoring_settings(block, filtered_dict, user) if proctoring_errors: errors = errors + proctoring_errors did_validate = False # If did validate, go ahead and update the metadata if did_validate: - updated_data = cls.update_from_dict(key_values, descriptor, user, save=False) + updated_data = cls.update_from_dict(key_values, block, user, save=False) return did_validate, errors, updated_data @classmethod - def update_from_dict(cls, key_values, descriptor, user, save=True): + def update_from_dict(cls, key_values, block, user, save=True): """ - Update metadata descriptor from key_values. Saves to modulestore if save is true. + Update metadata from key_values. Saves to modulestore if save is true. """ for key, value in key_values.items(): - setattr(descriptor, key, value) + setattr(block, key, value) if save and key_values: - modulestore().update_item(descriptor, user.id) + modulestore().update_item(block, user.id) - return cls.fetch(descriptor) + return cls.fetch(block) @classmethod def validate_team_settings(cls, settings_dict): @@ -397,7 +397,7 @@ class CourseMetadata: return None @classmethod - def validate_proctoring_settings(cls, descriptor, settings_dict, user): + def validate_proctoring_settings(cls, block, settings_dict, user): """ Verify proctoring settings @@ -412,9 +412,9 @@ class CourseMetadata: if ( not user.is_staff and cls._has_requested_proctoring_provider_changed( - descriptor.proctoring_provider, proctoring_provider_model.get('value') + block.proctoring_provider, proctoring_provider_model.get('value') ) and - datetime.now(pytz.UTC) > descriptor.start + datetime.now(pytz.UTC) > block.start ): message = ( 'The proctoring provider cannot be modified after a course has started.' @@ -426,7 +426,7 @@ class CourseMetadata: # should only be allowed if the exams IDA is enabled for a course available_providers = get_available_providers() updated_provider = settings_dict.get('proctoring_provider', {}).get('value') - if updated_provider == 'lti_external' and not exams_ida_enabled(descriptor.id): + if updated_provider == 'lti_external' and not exams_ida_enabled(block.id): available_providers.remove('lti_external') error = InvalidProctoringProvider('lti_external', available_providers) errors.append({'key': 'proctoring_provider', 'message': str(error), 'model': proctoring_provider_model}) @@ -435,7 +435,7 @@ class CourseMetadata: if enable_proctoring_model: enable_proctoring = enable_proctoring_model.get('value') else: - enable_proctoring = descriptor.enable_proctored_exams + enable_proctoring = block.enable_proctored_exams if enable_proctoring: # Require a valid escalation email if Proctortrack is chosen as the proctoring provider @@ -443,12 +443,12 @@ class CourseMetadata: if escalation_email_model: escalation_email = escalation_email_model.get('value') else: - escalation_email = descriptor.proctoring_escalation_email + escalation_email = block.proctoring_escalation_email if proctoring_provider_model: proctoring_provider = proctoring_provider_model.get('value') else: - proctoring_provider = descriptor.proctoring_provider + proctoring_provider = block.proctoring_provider missing_escalation_email_msg = 'Provider \'{provider}\' requires an exam escalation contact.' if proctoring_provider_model and proctoring_provider == 'proctortrack': @@ -477,7 +477,7 @@ class CourseMetadata: if zendesk_ticket_model: create_zendesk_tickets = zendesk_ticket_model.get('value') else: - create_zendesk_tickets = descriptor.create_zendesk_tickets + create_zendesk_tickets = block.create_zendesk_tickets if ( (proctoring_provider == 'proctortrack' and create_zendesk_tickets) @@ -489,7 +489,7 @@ class CourseMetadata: 'should be updated for this course.'.format( ticket_value=create_zendesk_tickets, provider=proctoring_provider, - course_id=descriptor.id + course_id=block.id ) ) diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js index f27ead886d..6c44da31f1 100644 --- a/cms/djangoapps/pipeline_js/js/xmodule.js +++ b/cms/djangoapps/pipeline_js/js/xmodule.js @@ -18,8 +18,8 @@ define( window._ = _; $script( - 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js' + - '?config=TeX-MML-AM_SVG&delayStartupUntil=configured', + 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js' + + '?config=TeX-MML-AM_SVG&delayStartupUntil=configured', 'mathjax', function() { window.MathJax.Hub.Config({ @@ -55,7 +55,7 @@ define( * The module should be used until we'll use RequireJS for XModules. * @param {Array} modules A list of urls. * @return {jQuery Promise} - **/ + * */ function requireQueue(modules) { var deferred = $.Deferred(); function loadScript(queue) { diff --git a/cms/djangoapps/xblock_config/apps.py b/cms/djangoapps/xblock_config/apps.py index 92271608f6..ae04e3f547 100644 --- a/cms/djangoapps/xblock_config/apps.py +++ b/cms/djangoapps/xblock_config/apps.py @@ -19,9 +19,9 @@ class XBlockConfig(AppConfig): def ready(self): from openedx.core.lib.xblock_utils import xblock_local_resource_url - # In order to allow descriptors to use a handler url, we need to + # In order to allow blocks to use a handler url, we need to # monkey-patch the x_module library. # TODO: Remove this code when Runtimes are no longer created by modulestores # https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes - xmodule.x_module.descriptor_global_handler_url = cms.lib.xblock.runtime.handler_url - xmodule.x_module.descriptor_global_local_resource_url = xblock_local_resource_url + xmodule.x_module.block_global_handler_url = cms.lib.xblock.runtime.handler_url + xmodule.x_module.block_global_local_resource_url = xblock_local_resource_url diff --git a/cms/envs/common.py b/cms/envs/common.py index 4b72820594..77e8fe161d 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1439,7 +1439,8 @@ REQUIRE_DEBUG = False WEBPACK_LOADER = { 'DEFAULT': { 'BUNDLE_DIR_NAME': 'bundles/', - 'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-stats.json') + 'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-stats.json'), + 'LOADER_CLASS': 'xmodule.util.xmodule_django.XModuleWebpackLoader', }, 'WORKERS': { 'BUNDLE_DIR_NAME': 'bundles/', @@ -2240,6 +2241,12 @@ PARTNER_SUPPORT_EMAIL = '' # Affiliate cookie tracking AFFILIATE_COOKIE_NAME = 'dev_affiliate_id' +# API access management +API_ACCESS_MANAGER_EMAIL = 'api-access@example.com' +API_ACCESS_FROM_EMAIL = 'api-requests@example.com' +API_DOCUMENTATION_URL = 'https://course-catalog-api-guide.readthedocs.io/en/latest/' +AUTH_DOCUMENTATION_URL = 'https://course-catalog-api-guide.readthedocs.io/en/latest/authentication/index.html' + ############## Settings for Studio Context Sensitive Help ############## HELP_TOKENS_INI_FILE = REPO_ROOT / "cms" / "envs" / "help_tokens.ini" diff --git a/cms/envs/devstack.py b/cms/envs/devstack.py index 78aeea67b2..78313ea720 100644 --- a/cms/envs/devstack.py +++ b/cms/envs/devstack.py @@ -296,10 +296,10 @@ CREDENTIALS_PUBLIC_SERVICE_URL = 'http://localhost:18150' # in the LMS and CMS. # .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/31813' FEATURES['ENABLE_SEND_XBLOCK_EVENTS_OVER_BUS'] = True -EVENT_BUS_PRODUCER = 'edx_event_bus_kafka.create_producer' -EVENT_BUS_KAFKA_SCHEMA_REGISTRY_URL = 'http://edx.devstack.schema-registry:8081' -EVENT_BUS_KAFKA_BOOTSTRAP_SERVERS = 'edx.devstack.kafka:29092' +EVENT_BUS_PRODUCER = 'edx_event_bus_redis.create_producer' +EVENT_BUS_REDIS_CONNECTION_URL = 'redis://:password@edx.devstack.redis:6379/' EVENT_BUS_TOPIC_PREFIX = 'dev' +EVENT_BUS_CONSUMER = 'edx_event_bus_redis.RedisEventConsumer' ################# New settings must go ABOVE this line ################# ######################################################################## diff --git a/cms/envs/help_tokens.ini b/cms/envs/help_tokens.ini index 9ac8a16c75..08cd73b30e 100644 --- a/cms/envs/help_tokens.ini +++ b/cms/envs/help_tokens.ini @@ -34,6 +34,7 @@ video = course_author:video/index.html certificates = course_author:set_up_course/studio_add_course_information/studio_creating_certificates.html content_highlights = course_author:developing_course/course_sections.html#set-section-highlights-for-weekly-course-highlight-messages image_accessibility = course_author:accessibility/best_practices_course_content_dev.html#use-best-practices-for-describing-images +social_sharing = course_author:developing_course/social_sharing.html # below are the language directory names for the different locales [locales] diff --git a/cms/lib/xblock/authoring_mixin.py b/cms/lib/xblock/authoring_mixin.py index 5b44e1a432..bb6b742f44 100644 --- a/cms/lib/xblock/authoring_mixin.py +++ b/cms/lib/xblock/authoring_mixin.py @@ -8,6 +8,7 @@ import logging from django.conf import settings from web_fragments.fragment import Fragment from xblock.core import XBlock, XBlockMixin +from xblock.fields import String, Scope log = logging.getLogger(__name__) @@ -43,3 +44,10 @@ class AuthoringMixin(XBlockMixin): fragment.add_javascript_url(self._get_studio_resource_url('/js/xblock/authoring.js')) fragment.initialize_js('VisibilityEditorInit') return fragment + + copied_from_block = String( + # Note: used by the content_staging app. This field is not needed in the LMS. + help="ID of the block that this one was copied from, if any. Used when copying and pasting blocks in Studio.", + scope=Scope.settings, + enforce_type=True, + ) diff --git a/cms/static/cms/js/build.js b/cms/static/cms/js/build.js index 1abe9f7b14..e7b7a7730b 100644 --- a/cms/static/cms/js/build.js +++ b/cms/static/cms/js/build.js @@ -7,8 +7,8 @@ }); }; - var jsOptimize = process.env.REQUIRE_BUILD_PROFILE_OPTIMIZE !== undefined ? - process.env.REQUIRE_BUILD_PROFILE_OPTIMIZE : 'uglify2'; + var jsOptimize = process.env.REQUIRE_BUILD_PROFILE_OPTIMIZE !== undefined + ? process.env.REQUIRE_BUILD_PROFILE_OPTIMIZE : 'uglify2'; return { /** diff --git a/cms/static/cms/js/main.js b/cms/static/cms/js/main.js index 875b57d013..c7a2507cf2 100644 --- a/cms/static/cms/js/main.js +++ b/cms/static/cms/js/main.js @@ -35,7 +35,7 @@ define([ }); $(document).ajaxError(function(event, jqXHR, ajaxSettings) { var msg, contentType, - message = gettext('This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.'); // eslint-disable-line max-len + message = gettext('This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.'); // eslint-disable-line max-len if (ajaxSettings.notifyOnError === false) { return; } @@ -54,7 +54,7 @@ define([ }); return msg.show(); }); - sendJSON = function(url, data, callback, type) { // eslint-disable-line no-param-reassign + sendJSON = function(url, data, callback, type) { // eslint-disable-line no-param-reassign if ($.isFunction(data)) { callback = data; data = undefined; @@ -66,13 +66,13 @@ define([ dataType: 'json', data: JSON.stringify(data), success: callback, - global: data ? data.global : true // Trigger global AJAX error handler or not + global: data ? data.global : true // Trigger global AJAX error handler or not }); }; - $.postJSON = function(url, data, callback) { // eslint-disable-line no-param-reassign + $.postJSON = function(url, data, callback) { // eslint-disable-line no-param-reassign return sendJSON(url, data, callback, 'POST'); }; - $.patchJSON = function(url, data, callback) { // eslint-disable-line no-param-reassign + $.patchJSON = function(url, data, callback) { // eslint-disable-line no-param-reassign return sendJSON(url, data, callback, 'PATCH'); }; return domReady(function() { diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js index e6fbc304db..db784c45cb 100644 --- a/cms/static/cms/js/require-config.js +++ b/cms/static/cms/js/require-config.js @@ -137,7 +137,7 @@ 'jquery_extend_patch': 'js/src/jquery_extend_patch', // externally hosted files - mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len + mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len 'youtube': [ // youtube URL does not end in '.js'. We add '?noext' to the path so // that require.js adds the '.js' to the query component of the URL, diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js index f5aa089b04..46a20ac517 100644 --- a/cms/static/cms/js/spec/main.js +++ b/cms/static/cms/js/spec/main.js @@ -39,9 +39,9 @@ 'jquery.cookie': 'xmodule_js/common_static/js/vendor/jquery.cookie', 'jquery.qtip': 'xmodule_js/common_static/js/vendor/jquery.qtip.min', 'jquery.fileupload': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload', - 'jquery.fileupload-process': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-process', // eslint-disable-line max-len - 'jquery.fileupload-validate': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate', // eslint-disable-line max-len - 'jquery.iframe-transport': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport', // eslint-disable-line max-len + 'jquery.fileupload-process': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-process', // eslint-disable-line max-len + 'jquery.fileupload-validate': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate', // eslint-disable-line max-len + 'jquery.iframe-transport': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport', // eslint-disable-line max-len 'jquery.inputnumber': 'xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill', 'jquery.immediateDescendents': 'xmodule_js/common_static/js/src/jquery.immediateDescendents', 'jquery.simulate': 'xmodule_js/common_static/js/vendor/jquery.simulate', @@ -69,7 +69,7 @@ 'domReady': 'xmodule_js/common_static/js/vendor/domReady', 'URI': 'xmodule_js/common_static/js/vendor/URI.min', 'mock-ajax': 'xmodule_js/common_static/js/vendor/mock-ajax', - mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len + mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len 'youtube': '//www.youtube.com/player_api?noext', 'js/src/ajax_prefix': 'xmodule_js/common_static/js/src/ajax_prefix', 'js/spec/test_utils': 'js/spec/test_utils' @@ -297,6 +297,6 @@ ]; requireSerial(specHelpers.concat(testFiles), function() { - return window.__karma__.start(); // eslint-disable-line no-underscore-dangle + return window.__karma__.start(); // eslint-disable-line no-underscore-dangle }); }).call(this, requirejs, requireSerial); diff --git a/cms/static/cms/js/spec/main_spec.js b/cms/static/cms/js/spec/main_spec.js index 451f465409..5b59db8331 100644 --- a/cms/static/cms/js/spec/main_spec.js +++ b/cms/static/cms/js/spec/main_spec.js @@ -2,6 +2,7 @@ (function(sandbox) { 'use strict'; + require(['jquery', 'backbone', 'cms/js/main', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'jquery.cookie'], function($, Backbone, main, AjaxHelpers) { describe('CMS', function() { diff --git a/cms/static/cms/js/spec/main_squire.js b/cms/static/cms/js/spec/main_squire.js index 34ff4ace8f..8feb056922 100644 --- a/cms/static/cms/js/spec/main_squire.js +++ b/cms/static/cms/js/spec/main_squire.js @@ -23,9 +23,9 @@ 'jquery.cookie': 'xmodule_js/common_static/js/vendor/jquery.cookie', 'jquery.qtip': 'xmodule_js/common_static/js/vendor/jquery.qtip.min', 'jquery.fileupload': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload', - 'jquery.fileupload-process': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-process', // eslint-disable-line max-len - 'jquery.fileupload-validate': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate', // eslint-disable-line max-len - 'jquery.iframe-transport': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport', // eslint-disable-line max-len + 'jquery.fileupload-process': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-process', // eslint-disable-line max-len + 'jquery.fileupload-validate': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate', // eslint-disable-line max-len + 'jquery.iframe-transport': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport', // eslint-disable-line max-len 'jquery.inputnumber': 'xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill', 'jquery.immediateDescendents': 'xmodule_js/common_static/js/src/jquery.immediateDescendents', 'jquery.ajaxQueue': 'xmodule_js/common_static/js/vendor/jquery.ajaxQueue', @@ -48,7 +48,7 @@ 'draggabilly': 'xmodule_js/common_static/js/vendor/draggabilly', 'domReady': 'xmodule_js/common_static/js/vendor/domReady', 'URI': 'xmodule_js/common_static/js/vendor/URI.min', - mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len + mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len 'youtube': '//www.youtube.com/player_api?noext', 'js/src/ajax_prefix': 'xmodule_js/common_static/js/src/ajax_prefix' }, @@ -210,6 +210,6 @@ ]; requireSerial(specHelpers.concat(testFiles), function() { - return window.__karma__.start(); // eslint-disable-line no-underscore-dangle + return window.__karma__.start(); // eslint-disable-line no-underscore-dangle }); }).call(this, requirejs, requireSerial); diff --git a/cms/static/cms/js/spec/main_webpack.js b/cms/static/cms/js/spec/main_webpack.js index 3ca488a8db..f7440c3876 100644 --- a/cms/static/cms/js/spec/main_webpack.js +++ b/cms/static/cms/js/spec/main_webpack.js @@ -1,5 +1,3 @@ -jasmine.getFixtures().fixturesPath = '/base/templates'; - import 'common/js/spec_helpers/jasmine-extensions'; import 'common/js/spec_helpers/jasmine-stealth'; import 'common/js/spec_helpers/jasmine-waituntil'; @@ -12,11 +10,6 @@ import _ from 'underscore'; import str from 'underscore.string'; import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils'; import StringUtils from 'edx-ui-toolkit/js/utils/string-utils'; -window._ = _; -window._.str = str; -window.edx = window.edx || {}; -window.edx.HtmlUtils = HtmlUtils; -window.edx.StringUtils = StringUtils; // These are the tests that will be run import './xblock/cms.runtime.v1_spec.js'; @@ -31,4 +24,12 @@ import '../../../js/spec/views/pages/course_outline_spec.js'; import '../../../js/spec/views/xblock_editor_spec.js'; import '../../../js/spec/views/xblock_string_field_editor_spec.js'; -window.__karma__.start(); // eslint-disable-line no-underscore-dangle +jasmine.getFixtures().fixturesPath = '/base/templates'; + +window._ = _; +window._.str = str; +window.edx = window.edx || {}; +window.edx.HtmlUtils = HtmlUtils; +window.edx.StringUtils = StringUtils; + +window.__karma__.start(); // eslint-disable-line no-underscore-dangle diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 8992ac7d12..c8ab1a4691 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -27,6 +27,7 @@ function( DropdownMenuView ) { 'use strict'; + var $body; function smoothScrollLink(e) { diff --git a/cms/static/js/certificates/collections/certificates.js b/cms/static/js/certificates/collections/certificates.js index 74adf67b04..92535ae38a 100644 --- a/cms/static/js/certificates/collections/certificates.js +++ b/cms/static/js/certificates/collections/certificates.js @@ -7,6 +7,7 @@ define([ ], function(Backbone, gettext, Certificate) { 'use strict'; + var CertificateCollection = Backbone.Collection.extend({ model: Certificate, diff --git a/cms/static/js/certificates/collections/signatories.js b/cms/static/js/certificates/collections/signatories.js index cd76e1c502..472b01013b 100644 --- a/cms/static/js/certificates/collections/signatories.js +++ b/cms/static/js/certificates/collections/signatories.js @@ -6,6 +6,7 @@ define([ ], function(Backbone, Signatory) { 'use strict'; + var SignatoryCollection = Backbone.Collection.extend({ model: Signatory }); diff --git a/cms/static/js/certificates/factories/certificates_page_factory.js b/cms/static/js/certificates/factories/certificates_page_factory.js index 5c13f73e8e..923b8fea96 100644 --- a/cms/static/js/certificates/factories/certificates_page_factory.js +++ b/cms/static/js/certificates/factories/certificates_page_factory.js @@ -20,6 +20,7 @@ define([ ], function($, CertificatesCollection, Certificate, CertificatesPage, CertificatePreview) { 'use strict'; + return function(certificatesJson, certificateUrl, courseOutlineUrl, courseModes, certificateWebViewUrl, isActive, certificateActivationHandlerUrl) { // Initialize the model collection, passing any necessary options to the constructor diff --git a/cms/static/js/certificates/models/certificate.js b/cms/static/js/certificates/models/certificate.js index 97a2735d71..8ca59d7b09 100644 --- a/cms/static/js/certificates/models/certificate.js +++ b/cms/static/js/certificates/models/certificate.js @@ -13,6 +13,7 @@ define([ function(_, Backbone, BackboneRelational, BackboneAssociations, gettext, CoffeeSrcMain, SignatoryModel, SignatoryCollection) { 'use strict'; + var Certificate = Backbone.RelationalModel.extend({ idAttribute: 'id', defaults: { diff --git a/cms/static/js/certificates/spec/custom_matchers.js b/cms/static/js/certificates/spec/custom_matchers.js index 8079de4883..32e19dec6a 100644 --- a/cms/static/js/certificates/spec/custom_matchers.js +++ b/cms/static/js/certificates/spec/custom_matchers.js @@ -1,8 +1,10 @@ // Custom matcher library for Jasmine test assertions // http://tobyho.com/2012/01/30/write-a-jasmine-matcher/ -define(['jquery'], function($) { // eslint-disable-line no-unused-vars +define(['jquery'], function($) { // eslint-disable-line no-unused-vars + 'use strict'; + return function() { jasmine.addMatchers({ toBeCorrectValuesInModel: function() { diff --git a/cms/static/js/certificates/spec/views/certificate_details_spec.js b/cms/static/js/certificates/spec/views/certificate_details_spec.js index d1c810f2aa..b803bd0cbe 100644 --- a/cms/static/js/certificates/spec/views/certificate_details_spec.js +++ b/cms/static/js/certificates/spec/views/certificate_details_spec.js @@ -118,7 +118,6 @@ function(_, Course, CertificatesCollection, CertificateModel, CertificateDetails delete window.CMS.User; }); - describe('The Certificate Details view', function() { it('should parse a JSON string collection into a Backbone model collection', function() { var course_title = 'Test certificate course title override 2'; @@ -140,7 +139,7 @@ function(_, Course, CertificatesCollection, CertificateModel, CertificateDetails }); it('should have empty certificate collection if there is an error parsing certifcate JSON', function() { - var CERTIFICATE_INVALID_JSON = '[{"course_title": Test certificate course title override, "signatories":"[]"}]'; // eslint-disable-line max-len + var CERTIFICATE_INVALID_JSON = '[{"course_title": Test certificate course title override, "signatories":"[]"}]'; // eslint-disable-line max-len var collection_length = this.collection.length; this.collection.parse(CERTIFICATE_INVALID_JSON); // collection length should remain the same since we have error parsing JSON diff --git a/cms/static/js/certificates/spec/views/certificate_preview_spec.js b/cms/static/js/certificates/spec/views/certificate_preview_spec.js index 741c7c5996..ed7f078951 100644 --- a/cms/static/js/certificates/spec/views/certificate_preview_spec.js +++ b/cms/static/js/certificates/spec/views/certificate_preview_spec.js @@ -64,16 +64,16 @@ function(_, $, Course, CertificatePreview, TemplateHelpers, ViewHelpers, AjaxHel it('course mode selection updating the link successfully', function() { selectDropDownByText(this.view.$(SELECTORS.course_modes), 'test1'); - expect(this.view.$(SELECTORS.preview_certificate).attr('href')). - toEqual('/users/1/courses/orgX/009/2016?preview=test1'); + expect(this.view.$(SELECTORS.preview_certificate).attr('href')) + .toEqual('/users/1/courses/orgX/009/2016?preview=test1'); selectDropDownByText(this.view.$(SELECTORS.course_modes), 'test2'); - expect(this.view.$(SELECTORS.preview_certificate).attr('href')). - toEqual('/users/1/courses/orgX/009/2016?preview=test2'); + expect(this.view.$(SELECTORS.preview_certificate).attr('href')) + .toEqual('/users/1/courses/orgX/009/2016?preview=test2'); selectDropDownByText(this.view.$(SELECTORS.course_modes), 'test3'); - expect(this.view.$(SELECTORS.preview_certificate).attr('href')). - toEqual('/users/1/courses/orgX/009/2016?preview=test3'); + expect(this.view.$(SELECTORS.preview_certificate).attr('href')) + .toEqual('/users/1/courses/orgX/009/2016?preview=test3'); }); it('toggle certificate activation event works fine', function() { diff --git a/cms/static/js/certificates/views/certificate_details.js b/cms/static/js/certificates/views/certificate_details.js index 4c62c5fa0c..822e40ae6b 100644 --- a/cms/static/js/certificates/views/certificate_details.js +++ b/cms/static/js/certificates/views/certificate_details.js @@ -15,6 +15,7 @@ define([ function($, _, str, gettext, BaseView, SignatoryModel, SignatoryDetailsView, ViewUtils, smoothScroll, certificateDetailsTemplate) { 'use strict'; + var CertificateDetailsView = BaseView.extend({ tagName: 'div', events: { diff --git a/cms/static/js/certificates/views/certificate_editor.js b/cms/static/js/certificates/views/certificate_editor.js index 6b0365ffbf..e81da1b2b4 100644 --- a/cms/static/js/certificates/views/certificate_editor.js +++ b/cms/static/js/certificates/views/certificate_editor.js @@ -84,7 +84,7 @@ function($, _, Backbone, gettext, addSignatory: function() { // Append a new signatory to the certificate model's signatories collection - var signatory = new SignatoryModel({certificate: this.getSaveableModel()}); // eslint-disable-line max-len, no-unused-vars + var signatory = new SignatoryModel({certificate: this.getSaveableModel()}); // eslint-disable-line max-len, no-unused-vars this.render(); }, diff --git a/cms/static/js/certificates/views/certificate_item.js b/cms/static/js/certificates/views/certificate_item.js index ed9cc9fd29..d75753874d 100644 --- a/cms/static/js/certificates/views/certificate_item.js +++ b/cms/static/js/certificates/views/certificate_item.js @@ -9,6 +9,7 @@ define([ ], function(gettext, ListItemView, CertificateDetailsView, CertificateEditorView) { 'use strict'; + var CertificateItemView = ListItemView.extend({ events: { 'click .delete': 'deleteItem' diff --git a/cms/static/js/certificates/views/certificate_preview.js b/cms/static/js/certificates/views/certificate_preview.js index 5300f784c9..741d4d9669 100644 --- a/cms/static/js/certificates/views/certificate_preview.js +++ b/cms/static/js/certificates/views/certificate_preview.js @@ -13,6 +13,7 @@ define([ ], function(_, gettext, BaseView, ViewUtils, NotificationView, certificateWebPreviewTemplate, HtmlUtils) { 'use strict'; + var CertificateWebPreview = BaseView.extend({ el: $('.preview-certificate'), events: { diff --git a/cms/static/js/certificates/views/certificates_list.js b/cms/static/js/certificates/views/certificates_list.js index d246991e21..5e958667ff 100644 --- a/cms/static/js/certificates/views/certificates_list.js +++ b/cms/static/js/certificates/views/certificates_list.js @@ -7,6 +7,7 @@ define([ ], function(gettext, ListView, CertificateItemView) { 'use strict'; + var CertificatesListView = ListView.extend({ tagName: 'div', className: 'certificates-list', diff --git a/cms/static/js/certificates/views/certificates_page.js b/cms/static/js/certificates/views/certificates_page.js index 4b72f1b370..b10fc7ee9d 100644 --- a/cms/static/js/certificates/views/certificates_page.js +++ b/cms/static/js/certificates/views/certificates_page.js @@ -9,6 +9,7 @@ define([ ], function($, _, gettext, BasePage, CertificatesListView) { 'use strict'; + var CertificatesPage = BasePage.extend({ initialize: function(options) { diff --git a/cms/static/js/certificates/views/signatory_details.js b/cms/static/js/certificates/views/signatory_details.js index e3186ea7fe..6caf73dd55 100644 --- a/cms/static/js/certificates/views/signatory_details.js +++ b/cms/static/js/certificates/views/signatory_details.js @@ -17,6 +17,7 @@ define([ function($, _, str, Backbone, gettext, TemplateUtils, ViewUtils, BaseView, SignatoryEditorView, signatoryDetailsTemplate, signatoryActionsTemplate, HtmlUtils) { 'use strict'; + var SignatoryDetailsView = BaseView.extend({ tagName: 'div', events: { diff --git a/cms/static/js/certificates/views/signatory_editor.js b/cms/static/js/certificates/views/signatory_editor.js index 1e64a058be..e0dd135733 100644 --- a/cms/static/js/certificates/views/signatory_editor.js +++ b/cms/static/js/certificates/views/signatory_editor.js @@ -18,6 +18,7 @@ function($, _, Backbone, gettext, TemplateUtils, ViewUtils, PromptView, NotificationView, FileUploadModel, FileUploadDialog, signatoryEditorTemplate, HtmlUtils) { 'use strict'; + var SignatoryEditorView = Backbone.View.extend({ tagName: 'div', events: { @@ -61,7 +62,7 @@ function($, _, Backbone, gettext, var count = 0; this.model.collection.each(function(modelSignatory) { if (!modelSignatory.isNew()) { - count ++; + count++; } }); return count; diff --git a/cms/static/js/collections/chapter.js b/cms/static/js/collections/chapter.js index 95b0c24a01..2af2a85bd4 100644 --- a/cms/static/js/collections/chapter.js +++ b/cms/static/js/collections/chapter.js @@ -3,7 +3,7 @@ define(['backbone', 'js/models/chapter'], function(Backbone, ChapterModel) { model: ChapterModel, comparator: 'order', nextOrder: function() { - if (!this.length) return 1; + if (!this.length) { return 1; } return this.last().get('order') + 1; }, isEmpty: function() { diff --git a/cms/static/js/collections/group.js b/cms/static/js/collections/group.js index 0de151ac04..223715e451 100644 --- a/cms/static/js/collections/group.js +++ b/cms/static/js/collections/group.js @@ -3,6 +3,7 @@ define([ ], function(_, str, Backbone, gettext, GroupModel) { 'use strict'; + var GroupCollection = Backbone.Collection.extend({ model: GroupModel, comparator: 'order', @@ -20,7 +21,7 @@ function(_, str, Backbone, gettext, GroupModel) { /** * Indicates if the collection is empty when all the models are empty * or the collection does not include any models. - **/ + * */ isEmpty: function() { return this.length === 0 || this.every(function(m) { return m.isEmpty(); @@ -40,7 +41,7 @@ function(_, str, Backbone, gettext, GroupModel) { do { name = str.sprintf(gettext('Group %s'), this.getGroupId(index)); - index ++; + index++; } while (_.contains(usedNames, name)); return name; diff --git a/cms/static/js/collections/group_configuration.js b/cms/static/js/collections/group_configuration.js index e322995d76..29dc8e5031 100644 --- a/cms/static/js/collections/group_configuration.js +++ b/cms/static/js/collections/group_configuration.js @@ -3,6 +3,7 @@ define([ ], function(Backbone, GroupConfigurationModel) { 'use strict'; + var GroupConfigurationCollection = Backbone.Collection.extend({ model: GroupConfigurationModel }); diff --git a/cms/static/js/factories/asset_index.js b/cms/static/js/factories/asset_index.js index e1f187d899..9e199683e9 100644 --- a/cms/static/js/factories/asset_index.js +++ b/cms/static/js/factories/asset_index.js @@ -2,6 +2,7 @@ define([ 'jquery', 'js/collections/asset', 'js/views/assets', 'jquery.fileupload' ], function($, AssetCollection, AssetsView) { 'use strict'; + return function(config) { var assets = new AssetCollection(), assetsView; diff --git a/cms/static/js/factories/container.js b/cms/static/js/factories/container.js index cfca298851..b98e0f99f2 100644 --- a/cms/static/js/factories/container.js +++ b/cms/static/js/factories/container.js @@ -21,6 +21,6 @@ export default function ContainerFactory(componentTemplates, XBlockInfoJson, act var view = new ContainerPage(_.extend(main_options, options)); view.render(); }); -}; +} -export {ContainerFactory} +export {ContainerFactory}; diff --git a/cms/static/js/factories/context_course.js b/cms/static/js/factories/context_course.js index 475e5a6282..24745295ec 100644 --- a/cms/static/js/factories/context_course.js +++ b/cms/static/js/factories/context_course.js @@ -1,3 +1,3 @@ import * as ContextCourse from 'js/models/course'; -export {ContextCourse} +export {ContextCourse}; diff --git a/cms/static/js/factories/course_create_rerun.js b/cms/static/js/factories/course_create_rerun.js index f790380b3c..c345f0c910 100644 --- a/cms/static/js/factories/course_create_rerun.js +++ b/cms/static/js/factories/course_create_rerun.js @@ -1,4 +1,5 @@ define(['jquery', 'jquery.form', 'js/views/course_rerun'], function($) { 'use strict'; + return function() {}; }); diff --git a/cms/static/js/factories/course_info.js b/cms/static/js/factories/course_info.js index e0f5fb7c9e..54fd381f7a 100644 --- a/cms/static/js/factories/course_info.js +++ b/cms/static/js/factories/course_info.js @@ -3,6 +3,7 @@ define([ 'js/models/course_info', 'js/views/course_info_edit' ], function($, CourseUpdateCollection, ModuleInfoModel, CourseInfoModel, CourseInfoEditView) { 'use strict'; + return function(updatesUrl, handoutsLocator, baseAssetUrl) { var course_updates = new CourseUpdateCollection(), course_handouts, editor; diff --git a/cms/static/js/factories/edit_tabs.js b/cms/static/js/factories/edit_tabs.js index 51688bf6a1..cd82fd2d91 100644 --- a/cms/static/js/factories/edit_tabs.js +++ b/cms/static/js/factories/edit_tabs.js @@ -21,6 +21,6 @@ export default function EditTabsFactory(courseLocation, explicitUrl) { mast: $('.wrapper-mast') }); }); -}; +} -export {EditTabsFactory} +export {EditTabsFactory}; diff --git a/cms/static/js/factories/export.js b/cms/static/js/factories/export.js index 2da33eab8b..b2e8132064 100644 --- a/cms/static/js/factories/export.js +++ b/cms/static/js/factories/export.js @@ -2,6 +2,7 @@ define([ 'domReady', 'js/views/export', 'jquery', 'gettext' ], function(domReady, Export, $, gettext) { 'use strict'; + return function(courselikeHomeUrl, library, statusUrl) { var $submitBtn = $('.action-export'), unloading = false, diff --git a/cms/static/js/factories/group_configurations.js b/cms/static/js/factories/group_configurations.js index 285e23f2d6..648ad78f0b 100644 --- a/cms/static/js/factories/group_configurations.js +++ b/cms/static/js/factories/group_configurations.js @@ -2,6 +2,7 @@ define([ 'js/collections/group_configuration', 'js/models/group_configuration', 'js/views/pages/group_configurations' ], function(GroupConfigurationCollection, GroupConfigurationModel, GroupConfigurationsPage) { 'use strict'; + return function(experimentsEnabled, experimentGroupConfigurationsJson, allGroupConfigurationJson, diff --git a/cms/static/js/factories/index.js b/cms/static/js/factories/index.js index 9fa8071a13..f2a645620c 100644 --- a/cms/static/js/factories/index.js +++ b/cms/static/js/factories/index.js @@ -1,5 +1,6 @@ define(['jquery.form', 'js/index'], function() { 'use strict'; + return function() { // showing/hiding creation rights UI $('.show-creationrights').click(function(e) { diff --git a/cms/static/js/factories/library.js b/cms/static/js/factories/library.js index 7f8d458502..c104b3b049 100644 --- a/cms/static/js/factories/library.js +++ b/cms/static/js/factories/library.js @@ -24,6 +24,6 @@ export default function LibraryFactory(componentTemplates, XBlockInfoJson, optio var view = new PagedContainerPage(_.extend(main_options, options)); view.render(); }); -}; +} -export {LibraryFactory} +export {LibraryFactory}; diff --git a/cms/static/js/factories/manage_users.js b/cms/static/js/factories/manage_users.js index ba364ee38e..df25e03ff8 100644 --- a/cms/static/js/factories/manage_users.js +++ b/cms/static/js/factories/manage_users.js @@ -4,6 +4,7 @@ define(['underscore', 'gettext', 'js/views/manage_users_and_roles'], function(_, gettext, ManageUsersAndRoles) { 'use strict'; + return function(containerName, users, tplUserURL, current_user_id, allow_actions) { function updateMessages(messages) { var local_messages = _.extend({}, messages); diff --git a/cms/static/js/factories/manage_users_lib.js b/cms/static/js/factories/manage_users_lib.js index a2ffc86362..434eb3baa7 100644 --- a/cms/static/js/factories/manage_users_lib.js +++ b/cms/static/js/factories/manage_users_lib.js @@ -4,6 +4,7 @@ define(['underscore', 'gettext', 'js/views/manage_users_and_roles'], function(_, gettext, ManageUsersAndRoles) { 'use strict'; + return function(containerName, users, tplUserURL, current_user_id, allow_actions) { function updateMessages(messages) { var local_messages = _.extend({}, messages); diff --git a/cms/static/js/factories/outline.js b/cms/static/js/factories/outline.js index e4a881ad67..57e2fa17ab 100644 --- a/cms/static/js/factories/outline.js +++ b/cms/static/js/factories/outline.js @@ -2,6 +2,7 @@ define([ 'js/views/pages/course_outline', 'js/models/xblock_outline_info' ], function(CourseOutlinePage, XBlockOutlineInfo) { 'use strict'; + return function(XBlockOutlineInfoJson, initialStateJson) { var courseXBlock = new XBlockOutlineInfo(XBlockOutlineInfoJson, {parse: true}), view = new CourseOutlinePage({ diff --git a/cms/static/js/factories/settings.js b/cms/static/js/factories/settings.js index 0d023a7021..34faec2eb1 100644 --- a/cms/static/js/factories/settings.js +++ b/cms/static/js/factories/settings.js @@ -2,6 +2,7 @@ define([ 'jquery', 'js/models/settings/course_details', 'js/views/settings/main' ], function($, CourseDetailsModel, MainView) { 'use strict'; + return function(detailsUrl, showMinGradeWarning, showCertificateAvailableDate, upgradeDeadline, useV2CertDisplaySettings) { var model; // highlighting labels when fields are focused in @@ -14,10 +15,10 @@ define([ }); // Toggle collapsibles when trigger is clicked - $(".collapsible .collapsible-trigger").click(function() { - const contentId = this.id.replace("-trigger", "-content") - $(`#${contentId}`).toggleClass("collapsed") - }) + $('.collapsible .collapsible-trigger').click(function() { + const contentId = this.id.replace('-trigger', '-content'); + $(`#${contentId}`).toggleClass('collapsed'); + }); model = new CourseDetailsModel(); model.urlRoot = detailsUrl; diff --git a/cms/static/js/factories/settings_advanced.js b/cms/static/js/factories/settings_advanced.js index 474e94e33f..4ddc2305d6 100644 --- a/cms/static/js/factories/settings_advanced.js +++ b/cms/static/js/factories/settings_advanced.js @@ -2,6 +2,7 @@ define([ 'jquery', 'gettext', 'js/models/settings/advanced', 'js/views/settings/advanced' ], function($, gettext, AdvancedSettingsModel, AdvancedSettingsView) { 'use strict'; + return function(advancedDict, advancedSettingsUrl, publisherEnabled) { var advancedModel, editor; diff --git a/cms/static/js/factories/settings_graders.js b/cms/static/js/factories/settings_graders.js index a78235e5cd..dc75029e0f 100644 --- a/cms/static/js/factories/settings_graders.js +++ b/cms/static/js/factories/settings_graders.js @@ -2,6 +2,7 @@ define([ 'jquery', 'js/views/settings/grading', 'js/models/settings/course_grading_policy' ], function($, GradingView, CourseGradingPolicyModel) { 'use strict'; + return function(courseDetails, gradingUrl, courseAssignmentLists) { var model, editor; diff --git a/cms/static/js/factories/textbooks.js b/cms/static/js/factories/textbooks.js index 01950f867f..4c2cec3130 100644 --- a/cms/static/js/factories/textbooks.js +++ b/cms/static/js/factories/textbooks.js @@ -19,6 +19,6 @@ export default function TextbooksFactory(textbooksJson) { return gettext('You have unsaved changes. Do you really want to leave this page?'); } }); -}; +} -export {TextbooksFactory} +export {TextbooksFactory}; diff --git a/cms/static/js/factories/videos_index.js b/cms/static/js/factories/videos_index.js index 3d1c871ea0..398c0dfab5 100644 --- a/cms/static/js/factories/videos_index.js +++ b/cms/static/js/factories/videos_index.js @@ -3,6 +3,7 @@ define([ 'js/views/previous_video_upload_list', 'js/views/active_video_upload' ], function($, Backbone, ActiveVideoUploadListView, PreviousVideoUploadListView, ActiveVideoUpload) { 'use strict'; + var VideosIndexFactory = function( $contentWrapper, videoImageUploadURL, @@ -43,8 +44,8 @@ define([ // Include videos that are not in the active video upload list, // or that are marked as Upload Complete var isActive = activeVideos.where({videoId: video.get('edx_video_id')}); - return isActive.length === 0 || - isActive[0].get('status') === ActiveVideoUpload.STATUS_COMPLETE; + return isActive.length === 0 + || isActive[0].get('status') === ActiveVideoUpload.STATUS_COMPLETE; }), updatedView = new PreviousVideoUploadListView({ videoImageUploadURL: videoImageUploadURL, diff --git a/cms/static/js/factories/xblock_validation.js b/cms/static/js/factories/xblock_validation.js index 56786c89d9..9936208626 100644 --- a/cms/static/js/factories/xblock_validation.js +++ b/cms/static/js/factories/xblock_validation.js @@ -1,4 +1,3 @@ - import * as XBlockValidationView from 'js/views/xblock_validation'; import * as XBlockValidationModel from 'js/models/xblock_validation'; @@ -17,6 +16,6 @@ export default function XBlockValidationFactory(validationMessages, hasEditingUr if (!model.get('empty')) { new XBlockValidationView({el: validationEle, model: model, root: isRoot}).render(); } -}; +} -export {XBlockValidationFactory} +export {XBlockValidationFactory}; diff --git a/cms/static/js/features/import/factories/import.js b/cms/static/js/features/import/factories/import.js index 28bee92cec..6b81540dbe 100644 --- a/cms/static/js/features/import/factories/import.js +++ b/cms/static/js/features/import/factories/import.js @@ -116,7 +116,7 @@ define([ Import.reset(); onComplete(); - alert(gettext('Your import has failed.') + '\n\n' + errMsg); // eslint-disable-line max-len, no-alert + alert(gettext('Your import has failed.') + '\n\n' + errMsg); // eslint-disable-line max-len, no-alert } } }); @@ -124,7 +124,7 @@ define([ } else { // Can't fix this lint error without major structural changes, which I'm not comfortable // doing given this file's test coverage - data.files = []; // eslint-disable-line no-param-reassign + data.files = []; // eslint-disable-line no-param-reassign } }, diff --git a/cms/static/js/features/import/views/import.js b/cms/static/js/features/import/views/import.js index ee1af307d6..e34965a092 100644 --- a/cms/static/js/features/import/views/import.js +++ b/cms/static/js/features/import/views/import.js @@ -6,7 +6,7 @@ define( function($, _, gettext, moment, HtmlUtils) { 'use strict'; - /** ******** Private properties ****************************************/ + /** ******** Private properties *************************************** */ var COOKIE_NAME = 'lastimportupload'; @@ -37,7 +37,7 @@ define( var CourseImport; - /** ******** Private functions *****************************************/ + /** ******** Private functions **************************************** */ /** * Destroys any event listener Import might have needed @@ -61,7 +61,7 @@ define( * */ var initEventListeners = function() { - $(window).on('beforeunload.import', function() { // eslint-disable-line consistent-return + $(window).on('beforeunload.import', function() { // eslint-disable-line consistent-return if (current.stage < STAGE.UNPACKING) { return gettext('Your import is in progress; navigating away will abort it.'); } @@ -218,7 +218,7 @@ define( deferred.resolve(); }; - /** ******** Public functions ******************************************/ + /** ******** Public functions ***************************************** */ CourseImport = { diff --git a/cms/static/js/features_jsx/.eslintrc.js b/cms/static/js/features_jsx/.eslintrc.js index 0a86988ebb..b3039be23a 100644 --- a/cms/static/js/features_jsx/.eslintrc.js +++ b/cms/static/js/features_jsx/.eslintrc.js @@ -6,9 +6,9 @@ module.exports = { }, rules: { indent: ['error', 4], + 'react/jsx-indent': ['error', 4], + 'react/jsx-indent-props': ['error', 4], 'import/extensions': 'off', 'import/no-unresolved': 'off', - 'react/jsx-indent': 'off', - 'react/jsx-indent-props': 'off', }, }; diff --git a/cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx b/cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx index dd00984dc0..767287d149 100644 --- a/cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx +++ b/cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx @@ -22,15 +22,15 @@ export function CourseOrLibraryListing(props) { {gettext('Course Number:')} {item.number} - { item.run && - - {gettext('Course Run:')} - {item.run} - - } - { item.can_edit === false && - {gettext('(Read-only)')} - } + { item.run + && ( + + {gettext('Course Run:')} + {item.run} + + )} + { item.can_edit === false + && {gettext('(Read-only)')}} ); @@ -38,43 +38,44 @@ export function CourseOrLibraryListing(props) { return ( ); diff --git a/cms/static/js/i18n/ar/djangojs.js b/cms/static/js/i18n/ar/djangojs.js index c174a57a80..0e633a4e24 100644 --- a/cms/static/js/i18n/ar/djangojs.js +++ b/cms/static/js/i18n/ar/djangojs.js @@ -722,9 +722,11 @@ "Edit this certificate?": "\u0647\u0644 \u062a\u0631\u064a\u062f \u062a\u0639\u062f\u064a\u0644 \u0627\u0644\u0634\u0647\u0627\u062f\u0629\u061f", "Edit your post below.": "\u062d\u0631\u0651\u0631 \u0645\u0646\u0634\u0648\u0631\u0643 \u0623\u062f\u0646\u0627\u0647.", "Editable": "\u0642\u0627\u0628\u0644 \u0644\u0644\u062a\u0639\u062f\u064a\u0644", + "Editing access for: {title}": "\u062a\u0639\u062f\u064a\u0644 \u0627\u0644\u062f\u062e\u0648\u0644 \u0644\u0640: {title}", "Editing comment": "\u062a\u062d\u0631\u064a\u0631 \u0627\u0644\u062a\u0639\u0644\u064a\u0642", "Editing post": "\u062a\u062d\u0631\u064a\u0631 \u0627\u0644\u0645\u0646\u0634\u0648\u0631 ", "Editing response": "\u062a\u062d\u0631\u064a\u0631 \u0627\u0644\u0631\u062f ", + "Editing: {title}": "\u062a\u0639\u062f\u064a\u0644: {title}", "Editor": "\u0645\u062d\u0631\u0651\u0650\u0631", "Education Completed": "\u0627\u0633\u062a\u064f\u0643\u0645\u0650\u0644 \u0627\u0644\u062a\u0639\u0644\u064a\u0645", "Email": "\u0627\u0644\u0628\u0631\u064a\u062f \u0627\u0644\u0625\u0644\u0643\u062a\u0631\u0648\u0646\u064a", @@ -835,7 +837,6 @@ "Explain if other.": "\u064a\u064f\u0631\u062c\u0649 \u062a\u0628\u064a\u0627\u0646 \u0627\u0644\u062a\u0641\u0627\u0635\u064a\u0644 \u0641\u064a \u062d\u0627\u0644 \u0627\u062e\u062a\u064a\u0627\u0631 \u2019\u0623\u0633\u0628\u0627\u0628 \u0623\u062e\u0631\u0649\u2018", "Explanation": "\u0627\u0644\u0634\u0631\u062d", "Explicitly Hiding from Students": "\u0625\u062e\u0641\u0627\u0621 \u0648\u0627\u0636\u062d \u0639\u0646 \u0627\u0644\u0637\u0644\u0651\u0627\u0628", - "Explore New Programs": "\u0627\u0633\u062a\u0643\u0634\u0627\u0641 \u0628\u0631\u0627\u0645\u062c \u062c\u062f\u064a\u062f\u0629", "Explore Programs": "\u0627\u0633\u062a\u0643\u0634\u0627\u0641 \u0628\u0631\u0627\u0645\u062c", "Explore your course!": "\u0627\u0633\u062a\u0643\u0634\u0641 \u0645\u0633\u0627\u0642\u0643!", "Failed Proctoring": "\u0641\u0634\u0644 \u0627\u0644\u0645\u0631\u0627\u0642\u0628\u0629", @@ -1587,6 +1588,14 @@ "Show": "\u0623\u0638\u0647\u0631", "Show All": "\u0639\u0631\u0636 \u0627\u0644\u0643\u0644", "Show Annotations": "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0625\u064a\u0636\u0627\u062d\u0627\u062a", + "Show Comment (%(num_comments)s)": [ + "\u0627\u0638\u0647\u0631 \u0627\u0644\u062a\u0639\u0644\u064a\u0642 (%(num_comments)s)", + "\u0627\u0638\u0647\u0631 \u0627\u0644\u062a\u0639\u0644\u064a\u0642 (%(num_comments)s)", + "\u0627\u0638\u0647\u0631 \u0627\u0644\u062a\u0639\u0644\u064a\u0642\u0627\u0646 (%(num_comments)s)", + "\u0627\u0638\u0647\u0631 \u0627\u0644\u062a\u0639\u0644\u064a\u0642\u0627\u062a (%(num_comments)s)", + "\u0627\u0638\u0647\u0631 \u0627\u0644\u062a\u0639\u0644\u064a\u0642\u0627\u062a (%(num_comments)s)", + "\u0627\u0638\u0647\u0631 \u0627\u0644\u062a\u0639\u0644\u064a\u0642\u0627\u062a (%(num_comments)s)" + ], "Show Deprecated Settings": "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a \u0627\u0644\u0645\u0647\u0645\u0644\u0629", "Show Discussion": "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0646\u0642\u0627\u0634", "Show Less": "\u0625\u0638\u0647\u0627\u0631 \u0623\u0642\u0644", diff --git a/cms/static/js/i18n/ca/djangojs.js b/cms/static/js/i18n/ca/djangojs.js index 4690eb7fb1..d11cb7ac27 100644 --- a/cms/static/js/i18n/ca/djangojs.js +++ b/cms/static/js/i18n/ca/djangojs.js @@ -394,7 +394,6 @@ "Exception Granted": "Excepci\u00f3 concedida", "Expand All": "Expandeix-ho tot", "Explain if other.": "Expliqueu-ho si hi ha altres.", - "Explore New Programs": "Exploreu nous programes", "Explore Programs": "Explore programes", "Explore your course!": "Exploreu el vostre curs!", "Failed Proctoring": "Supervisat susp\u00e8s", diff --git a/cms/static/js/i18n/ca@valencia/djangojs.js b/cms/static/js/i18n/ca@valencia/djangojs.js index 5a0091f32a..71522042ff 100644 --- a/cms/static/js/i18n/ca@valencia/djangojs.js +++ b/cms/static/js/i18n/ca@valencia/djangojs.js @@ -394,7 +394,6 @@ "Exception Granted": "Excepci\u00f3 concedida", "Expand All": "Expandeix-ho tot", "Explain if other.": "Expliqueu-ho si hi ha altres.", - "Explore New Programs": "Exploreu nous programes", "Explore Programs": "Explore programes", "Explore your course!": "Exploreu el vostre curs!", "Failed Proctoring": "Supervisat susp\u00e8s", diff --git a/cms/static/js/i18n/de-de/djangojs.js b/cms/static/js/i18n/de-de/djangojs.js index d58b8a438a..1d15acb60b 100644 --- a/cms/static/js/i18n/de-de/djangojs.js +++ b/cms/static/js/i18n/de-de/djangojs.js @@ -797,7 +797,6 @@ "Explain if other.": "Erkl\u00e4ren Sie, wenn anders.", "Explanation": "Erkl\u00e4rung", "Explicitly Hiding from Students": "Gezielt vor Teilnehmern verstecken", - "Explore New Programs": "Nach neuen Programmen suchen", "Explore Programs": "Nach Programmen suchen", "Explore courses": "Kurs\u00fcbersicht", "Explore your course!": "Erkunde ihr Kurs!", @@ -1513,7 +1512,6 @@ "Select a subject for your support request.": "W\u00e4hlen Sie ein Betreff f\u00fcr Ihre Supportanfrage.", "Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.": "W\u00e4hlen Sie eine Zeitzuteilung f\u00fcr die Pr\u00fcfung aus. Wenn es mehr als 24 Stunden sind, geben Sie die Zeitspanne ein. Sie k\u00f6nnen einzelnen Lernenden \u00fcber das Lehrer-Dashboard zus\u00e4tzliche Zeit geben, um die Pr\u00fcfung abzuschlie\u00dfen.", "Select all": "Alles ausw\u00e4hlen", - "Select employment status": "W\u00e4hlen Sie Ihren Besch\u00e4ftigungsstatus", "Select fidelity": "Treue ausw\u00e4hlen", "Select language": "W\u00e4hlen Sie eine Sprache", "Select one or more groups:": "W\u00e4hlen Sie eine oder mehrere Gruppen aus:", @@ -2142,10 +2140,6 @@ "What if I can't see the camera image, or if I can't see my photo do determine which side is visible?": "Was, wenn ich mein Kamerabild oder mein Foto nicht sehen kann?", "What if I have difficulty holding my ID in position relative to the camera?": "Was, wenn ich Schwierigkeiten habe, meinen Ausweis in der richtigen Position zur Kamera zu halten?", "What if I have difficulty holding my head in position relative to the camera?": "Was, wenn ich Schwierigkeiten habe, meinen Kopf in der richtigen Position zur Kamera zu halten?", - "What industry do you currently work in?": "In welcher Branche arbeiten Sie derzeit?", - "What industry do you want to work in?": "In welcher Branchen wollen Sie arbeiten?", - "What is the highest level of education that any of your parents or guardians have achieved?": "Welches ist der h\u00f6chste Bildungsabschluss, den ein Elternteil oder Erziehungsberechtigter von Ihnen erreicht hat?", - "What is the highest level of education that you have achieved so far?": "Was ist der h\u00f6chste Bildungsabschluss, den Sie erreicht haben?", "What was the total combined income, during the last 12 months, of all members of your family? ": "Wie hoch war das gemeinsame Gesamteinkommen aller Mitglieder Ihrer Familie in den letzten 12 Monaten? ", "What's Your Next Accomplishment?": "Was ist Ihr n\u00e4chster Erfolg?", "When learners submit an answer to an assessment, they immediately see whether the answer is correct or incorrect, and the score received.": "Nutzer sehen sofort nach einreichen ihrer Antworten, ob diese richtig oder falsch sind und welche Punkte sie erhalten.", diff --git a/cms/static/js/i18n/el/djangojs.js b/cms/static/js/i18n/el/djangojs.js index 5c4c4676a4..8b0abc63a1 100644 --- a/cms/static/js/i18n/el/djangojs.js +++ b/cms/static/js/i18n/el/djangojs.js @@ -51,6 +51,7 @@ "Additional responses could not be loaded. Refresh the page and try again.": "\u0391\u03c0\u03ad\u03c4\u03c5\u03c7\u03b5 \u03b7 \u03b5\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03c9\u03bd \u03b1\u03c0\u03bf\u03ba\u03c1\u03af\u03c3\u03b5\u03c9\u03bd. \u0391\u03bd\u03b1\u03bd\u03b5\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1 \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ae\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", "Admin": "\u0394\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c2", "Advanced": "\u03a0\u03c1\u03bf\u03c7\u03c9\u03c1\u03b7\u03bc\u03ad\u03bd\u03bf", + "All Groups": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03bf\u03bc\u03ac\u03b4\u03b5\u03c2", "All Posts": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u03b1\u03bd\u03b1\u03c1\u03c4\u03ae\u03c3\u03b5\u03b9\u03c2", "All Rights Reserved": "\u039c\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03c6\u03cd\u03bb\u03b1\u03be\u03b7 \u03ba\u03ac\u03b8\u03b5 \u03bd\u03cc\u03bc\u03b9\u03bc\u03bf\u03c5 \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03bf\u03c2", "All Time Zones": "\u038c\u03bb\u03b5\u03c2 \u03bf\u03b9 \u0396\u03ce\u03bd\u03b5\u03c2 \u038f\u03c1\u03b1\u03c2", @@ -74,13 +75,14 @@ "April": "\u0391\u03c0\u03c1\u03af\u03bb\u03b9\u03bf\u03c2", "Are you sure you want to delete this post?": "\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03c2 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03bd\u03ac\u03c1\u03c4\u03b7\u03c3\u03b7;", "Are you sure you want to delete this response?": "\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03c2 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b1\u03c0\u03cc\u03ba\u03c1\u03b9\u03c3\u03b7;", - "Are you sure you want to unenroll from {courseName} ({courseNumber})?": "\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9 \u03c0\u03c9\u03c2 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03b5\u03af\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf \u03bc\u03ac\u03b8\u03b7\u03bc\u03b1 {courseName} ({courseNumber});", + "Are you sure you want to unenroll from {courseName} ({courseNumber})?": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03b5\u03af\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf \u03bc\u03ac\u03b8\u03b7\u03bc\u03b1 {courseName} ({courseNumber});", "Assessment": "\u0391\u03be\u03b9\u03bf\u03bb\u03cc\u03b3\u03b7\u03c3\u03b7", "Assessments": "\u0391\u03be\u03b9\u03bf\u03bb\u03bf\u03b3\u03ae\u03c3\u03b5\u03b9\u03c2", "August": "\u0391\u03cd\u03b3\u03bf\u03c5\u03c3\u03c4\u03bf\u03c2", "Available %s": "\u0394\u03b9\u03b1\u03b8\u03ad\u03c3\u03b9\u03bc\u03bf %s", "Back to sign in": "\u0395\u03c0\u03b9\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae \u03c3\u03c4\u03b7 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "Basic Account Information": "\u0392\u03b1\u03c3\u03b9\u03ba\u03ad\u03c2 \u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", + "Before proceeding, please {htmlStart}{emailMsg}{htmlEnd}.": "\u03a0\u03c1\u03b9\u03bd \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 {htmlStart}{emailMsg}{htmlEnd}.", "Blockquote": "\u0388\u03bd\u03b8\u03b5\u03c3\u03b7", "Blockquote (Ctrl+Q)": "\u0388\u03bd\u03b8\u03b5\u03c3\u03b7 \u03ba\u03b5\u03b9\u03bc\u03ad\u03bd\u03bf\u03c5 (Ctrl+Q)", "Bold (Ctrl+B)": "\u0388\u03bd\u03c4\u03bf\u03bd\u03b1 (Ctrl+B)", @@ -114,6 +116,10 @@ "Completed": "\u039f\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03b8\u03b7\u03ba\u03b5", "Confirm Password": "\u0395\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03b1\u03c2", "Country or Region of Residence": "\u03a7\u03ce\u03c1\u03b1 \u0394\u03b9\u03b1\u03bc\u03bf\u03bd\u03ae\u03c2", + "Course": [ + "\u039c\u03ac\u03b8\u03b7\u03bc\u03b1", + "\u039c\u03b1\u03b8\u03ae\u03bc\u03b1\u03c4\u03b1" + ], "Course Handouts": "\u0395\u03ba\u03c4\u03c5\u03c0\u03ce\u03c3\u03b9\u03bc\u03b1 \u039c\u03b1\u03b8\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", "Course ID": "\u03a4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03bc\u03b1\u03b8\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", "Course Id": "\u03a4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03bc\u03b1\u03b8\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", @@ -143,6 +149,7 @@ "Email": "Email", "Email Address (Sign In)": "Email (\u0391\u03c5\u03c4\u03cc \u03c0\u03bf\u03c5 \u03b4\u03b7\u03bb\u03ce\u03c3\u03b1\u03c4\u03b5 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae \u03c3\u03b1\u03c2)", "Email address": "E-mail", + "Endorse": "\u03a0\u03c1\u03bf\u03c4\u03b5\u03af\u03bd\u03c9", "Engage with posts": "\u0394\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7 \u03b1\u03bd\u03b1\u03c1\u03c4\u03ae\u03c3\u03b5\u03c9\u03bd", "Enter Due Date and Time": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b1\u03c4\u03b5 \u03b7\u03bc\u03b5\u03c1\u03bf\u03bc\u03b7\u03bd\u03af\u03b1 \u03ba\u03b1\u03b9 \u03ce\u03c1\u03b1 \u03bb\u03ae\u03be\u03b7\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03c1\u03bf\u03b8\u03b5\u03c3\u03bc\u03af\u03b1\u03c2 \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae\u03c2", "Enter a student's username or email address.": "\u03a0\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c8\u03b5\u03c5\u03b4\u03ce\u03bd\u03c5\u03bc\u03bf \u03ae \u03c4\u03bf e-mail \u03ba\u03ac\u03c0\u03bf\u03b9\u03bf\u03c5 \u03c3\u03c0\u03bf\u03c5\u03b4\u03b1\u03c3\u03c4\u03ae..", @@ -259,6 +266,7 @@ "Replace all": "\u0391\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03cc\u03bb\u03c9\u03bd", "Replace with": "\u0391\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03bc\u03b5", "Report abuse, topics, and responses": "\u0391\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03b1\u03bd\u03ac\u03c1\u03c4\u03b7\u03c3\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03b1\u03b2\u03b9\u03ac\u03b6\u03b5\u03b9 \u03c4\u03bf\u03c5\u03c2 \u03cc\u03c1\u03bf\u03c5\u03c2 \u03c7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03c6\u03cc\u03c1\u03bf\u03c5\u03bc", + "Reported": "\u039f\u03b9 \u03b1\u03bd\u03b1\u03c6\u03b5\u03c1\u03b8\u03b5\u03af\u03c3\u03b5\u03c2", "Required": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9", "Required field.": "\u03a5\u03c0\u03bf\u03c7\u03c1\u03b5\u03c9\u03c4\u03b9\u03ba\u03cc \u03c0\u03b5\u03b4\u03af\u03bf.", "Reset My Password": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03c4\u03bf\u03c5 \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03bc\u03bf\u03c5", @@ -352,6 +360,7 @@ "URL": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL", "Undo (Ctrl+Z)": "\u0391\u03bd\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 (Ctrl+Z)", "Undo Changes": "\u0391\u03bd\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ce\u03bd", + "Unendorse": "\u0394\u03b5\u03bd \u03c0\u03c1\u03bf\u03c4\u03b5\u03af\u03bd\u03c9", "Unit": "\u039a\u03b5\u03c6\u03ac\u03bb\u03b1\u03b9\u03bf", "Upload File": "\u039c\u03b5\u03c4\u03b1\u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7 \u0391\u03c1\u03c7\u03b5\u03af\u03bf\u03c5", "Upload New File": "\u039c\u03b5\u03c4\u03b1\u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7 \u03bd\u03ad\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5", @@ -367,13 +376,14 @@ "Verified": "\u039c\u03b5 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", "Verified Certificate": "\u03a0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03cc \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03c5\u03bc\u03ad\u03bd\u03bf", "View": "\u03a0\u03c1\u03bf\u03b2\u03bf\u03bb\u03ae", + "View Archived Course": "\u0391\u03c5\u03c4\u03bf\u03bc\u03b5\u03bb\u03ad\u03c4\u03b7", "View Course": "\u0394\u0395\u0399\u03a4\u0395 \u03a4\u039f \u039c\u0391\u0398\u0397\u039c\u0391", "Vote for good posts and responses": "\u03a5\u03c0\u03b5\u03c1\u03c8\u03b7\u03c6\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03ba\u03b1\u03bb\u03ad\u03c2 \u03b1\u03bd\u03b1\u03c1\u03c4\u03ae\u03c3\u03b5\u03b9\u03c2 \u03ba\u03b1\u03b9 \u03b1\u03c0\u03bf\u03ba\u03c1\u03af\u03c3\u03b5\u03b9\u03c2", "Warning": "\u03a0\u03c1\u03bf\u03b5\u03b9\u03b4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", "We couldn't create your account.": "\u0394\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03ad\u03c3\u03b1\u03bc\u03b5 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03bf\u03c5\u03bc\u03b5 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c3\u03b1\u03c2", "We couldn't sign you in.": "\u0397 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae", "We've sent a confirmation message to {new_email_address}. Click the link in the message to update your email address.": "\u03a3\u03b1\u03c2 \u03ad\u03c7\u03bf\u03c5\u03bc\u03b5 \u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03b9 \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03c3\u03c4\u03bf \u03bd\u03ad\u03bf e-mail {new_email_address} \u03c0\u03bf\u03c5 \u03bf\u03c1\u03af\u03c3\u03b1\u03c4\u03b5. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03bf e-mail \u03c3\u03b1\u03c2.", - "We've sent a message to {email}. Click the link in the message to reset your password. Didn't receive the message? Contact {anchorStart}technical support{anchorEnd}.": "\u0388\u03c7\u03bf\u03c5\u03bc\u03b5 \u03b1\u03c0\u03bf\u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03b9 \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {email}. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03b1\u03c2. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03bb\u03ac\u03b2\u03b1\u03c4\u03b5 \u03c4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1, \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03b7\u03bd {anchorStart}\u03c4\u03b5\u03c7\u03bd\u03b9\u03ba\u03ae \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7{anchorEnd}.", + "We've sent a message to {email}. Click the link in the message to reset your password. Didn't receive the message? Contact {anchorStart}technical support{anchorEnd}.": "\u0388\u03c7\u03bf\u03c5\u03bc\u03b5 \u03b1\u03c0\u03bf\u03c3\u03c4\u03b5\u03af\u03bb\u03b5\u03b9 \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {email}. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf \u03c0\u03bf\u03c5 \u03b8\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c3\u03b1\u03c2. \u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03bb\u03ac\u03b2\u03b1\u03c4\u03b5 \u03c4\u03bf \u03bc\u03ae\u03bd\u03c5\u03bc\u03b1, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b9\u03ba\u03bf\u03b9\u03bd\u03c9\u03bd\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b5 \u03c4\u03b7\u03bd {anchorStart}\u03c4\u03b5\u03c7\u03bd\u03b9\u03ba\u03ae \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7{anchorEnd}.", "We\u2019re sorry to see you go!": "\u039b\u03c5\u03c0\u03bf\u03cd\u03bc\u03b1\u03c3\u03c4\u03b5 \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03c7\u03ce\u03c1\u03b7\u03c3\u03ae \u03c3\u03b1\u03c2!", "Year of Birth": "\u0388\u03c4\u03bf\u03c2 \u03b3\u03ad\u03bd\u03bd\u03b7\u03c3\u03b7\u03c2", "Yesterday": "\u03a7\u03b8\u03ad\u03c2", @@ -405,6 +415,8 @@ "close": "\u039a\u03bb\u03b5\u03af\u03c3\u03b9\u03bc\u03bf", "correct": "\u03c3\u03c9\u03c3\u03c4\u03cc", "course id": "\u03a4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03bc\u03b1\u03b8\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2", + "endorsed %(time_ago)s": "\u03a0\u03c1\u03bf\u03c4\u03ac\u03b8\u03b7\u03ba\u03b5 %(time_ago)s", + "endorsed %(time_ago)s by %(user)s": "\u03a0\u03c1\u03bf\u03c4\u03ac\u03b8\u03b7\u03ba\u03b5 %(time_ago)s \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 %(user)s", "follow this post": "\u0391\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b1\u03bd\u03ac\u03c1\u03c4\u03b7\u03c3\u03b7", "incorrect": "\u03bb\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf", "one letter Friday\u0004F": "\u03a0", diff --git a/cms/static/js/i18n/eo/djangojs.js b/cms/static/js/i18n/eo/djangojs.js index 09987efc50..a0e1c2a44a 100644 --- a/cms/static/js/i18n/eo/djangojs.js +++ b/cms/static/js/i18n/eo/djangojs.js @@ -43,6 +43,7 @@ " records are not in the correct format and have not been added to the exception list": " r\u00e9\u00e7\u00f6rds \u00e4r\u00e9 n\u00f6t \u00efn th\u00e9 \u00e7\u00f6rr\u00e9\u00e7t f\u00f6rm\u00e4t \u00e4nd h\u00e4v\u00e9 n\u00f6t \u00df\u00e9\u00e9n \u00e4dd\u00e9d t\u00f6 th\u00e9 \u00e9x\u00e7\u00e9pt\u00ef\u00f6n l\u00efst \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442#", " to learn": " t\u00f6 l\u00e9\u00e4rn \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#", "${listPrice}": "${listPrice} \u2c60'\u03c3\u044f\u0454\u043c \u03b9#", + "${price}/month {currency}": "${price}/m\u00f6nth {currency} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442#", "%(cohort_name)s (%(user_count)s)": "%(cohort_name)s (%(user_count)s) \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#", "%(comments_count)s %(span_sr_open)scomments %(span_close)s": "%(comments_count)s %(span_sr_open)s\u00e7\u00f6mm\u00e9nts %(span_close)s \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", "%(comments_count)s %(span_sr_open)scomments (%(unread_comments_count)s unread comments)%(span_close)s": "%(comments_count)s %(span_sr_open)s\u00e7\u00f6mm\u00e9nts (%(unread_comments_count)s \u00fcnr\u00e9\u00e4d \u00e7\u00f6mm\u00e9nts)%(span_close)s \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f#", @@ -133,6 +134,7 @@ "Access to this {blockType} is restricted to: {selectedGroupsLabel}": "\u00c0\u00e7\u00e7\u00e9ss t\u00f6 th\u00efs {blockType} \u00efs r\u00e9str\u00ef\u00e7t\u00e9d t\u00f6: {selectedGroupsLabel} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f#", "Accomplishments": "\u00c0\u00e7\u00e7\u00f6mpl\u00efshm\u00e9nts \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", "Accomplishments Pagination": "\u00c0\u00e7\u00e7\u00f6mpl\u00efshm\u00e9nts P\u00e4g\u00efn\u00e4t\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#", + "According to our records, you are not enrolled in any courses included in your {programName} program subscription. Enroll in a course from the {i_start}Program Details{i_end} page.": "\u00c0\u00e7\u00e7\u00f6rd\u00efng t\u00f6 \u00f6\u00fcr r\u00e9\u00e7\u00f6rds, \u00fd\u00f6\u00fc \u00e4r\u00e9 n\u00f6t \u00e9nr\u00f6ll\u00e9d \u00efn \u00e4n\u00fd \u00e7\u00f6\u00fcrs\u00e9s \u00efn\u00e7l\u00fcd\u00e9d \u00efn \u00fd\u00f6\u00fcr {programName} pr\u00f6gr\u00e4m s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n. \u00c9nr\u00f6ll \u00efn \u00e4 \u00e7\u00f6\u00fcrs\u00e9 fr\u00f6m th\u00e9 {i_start}Pr\u00f6gr\u00e4m D\u00e9t\u00e4\u00efls{i_end} p\u00e4g\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9 \u03c3\u0192\u0192\u03b9\u00a2\u03b9\u03b1 \u2202\u0454\u0455\u0454\u044f\u03c5#", "Account": "\u00c0\u00e7\u00e7\u00f6\u00fcnt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c #", "Account Information": "\u00c0\u00e7\u00e7\u00f6\u00fcnt \u00ccnf\u00f6rm\u00e4t\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", "Account Not Activated": "\u00c0\u00e7\u00e7\u00f6\u00fcnt N\u00f6t \u00c0\u00e7t\u00efv\u00e4t\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", @@ -147,6 +149,8 @@ "Activating a link in this group will skip to the corresponding point in the video.": "\u00c0\u00e7t\u00efv\u00e4t\u00efng \u00e4 l\u00efnk \u00efn th\u00efs gr\u00f6\u00fcp w\u00efll sk\u00efp t\u00f6 th\u00e9 \u00e7\u00f6rr\u00e9sp\u00f6nd\u00efng p\u00f6\u00efnt \u00efn th\u00e9 v\u00efd\u00e9\u00f6. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454#", "Active": "\u00c0\u00e7t\u00efv\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5#", "Active Uploads": "\u00c0\u00e7t\u00efv\u00e9 \u00dbpl\u00f6\u00e4ds \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442#", + "Active subscription": "\u00c0\u00e7t\u00efv\u00e9 s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", + "Active trial ends {trialEndDate} at {trialEndTime}": "\u00c0\u00e7t\u00efv\u00e9 tr\u00ef\u00e4l \u00e9nds {trialEndDate} \u00e4t {trialEndTime} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2#", "Add": "\u00c0dd \u2c60'\u03c3\u044f\u0454\u043c#", "Add Additional Signatory": "\u00c0dd \u00c0dd\u00eft\u00ef\u00f6n\u00e4l S\u00efgn\u00e4t\u00f6r\u00fd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7#", "Add Attachment": "\u00c0dd \u00c0tt\u00e4\u00e7hm\u00e9nt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442#", @@ -687,6 +691,7 @@ "Ends {end}": "\u00c9nds {end} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#", "Engage with posts": "\u00c9ng\u00e4g\u00e9 w\u00efth p\u00f6sts \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#", "Enroll Now": "\u00c9nr\u00f6ll N\u00f6w \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3#", + "Enroll in a {programName} course": "\u00c9nr\u00f6ll \u00efn \u00e4 {programName} \u00e7\u00f6\u00fcrs\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#", "Enrolled": "\u00c9nr\u00f6ll\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#", "Enrolling you in the selected course": "\u00c9nr\u00f6ll\u00efng \u00fd\u00f6\u00fc \u00efn th\u00e9 s\u00e9l\u00e9\u00e7t\u00e9d \u00e7\u00f6\u00fcrs\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5#", "Enrollment Date": "\u00c9nr\u00f6llm\u00e9nt D\u00e4t\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", @@ -781,9 +786,10 @@ "Explain if other.": "\u00c9xpl\u00e4\u00efn \u00eff \u00f6th\u00e9r. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#", "Explanation": "\u00c9xpl\u00e4n\u00e4t\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f #", "Explicitly Hiding from Students": "\u00c9xpl\u00ef\u00e7\u00eftl\u00fd H\u00efd\u00efng fr\u00f6m St\u00fcd\u00e9nts \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442#", - "Explore New Programs": "\u00c9xpl\u00f6r\u00e9 N\u00e9w Pr\u00f6gr\u00e4ms \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Explore Programs": "\u00c9xpl\u00f6r\u00e9 Pr\u00f6gr\u00e4ms \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c#", "Explore courses": "\u00c9xpl\u00f6r\u00e9 \u00e7\u00f6\u00fcrs\u00e9s \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", + "Explore new programs": "\u00c9xpl\u00f6r\u00e9 n\u00e9w pr\u00f6gr\u00e4ms \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", + "Explore subscription options": "\u00c9xpl\u00f6r\u00e9 s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n \u00f6pt\u00ef\u00f6ns \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2#", "Explore your course!": "\u00c9xpl\u00f6r\u00e9 \u00fd\u00f6\u00fcr \u00e7\u00f6\u00fcrs\u00e9! \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Failed to clone rubric": "F\u00e4\u00efl\u00e9d t\u00f6 \u00e7l\u00f6n\u00e9 r\u00fc\u00dfr\u00ef\u00e7 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#", "Failed to delete student state for user.": "F\u00e4\u00efl\u00e9d t\u00f6 d\u00e9l\u00e9t\u00e9 st\u00fcd\u00e9nt st\u00e4t\u00e9 f\u00f6r \u00fcs\u00e9r. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f#", @@ -930,6 +936,7 @@ "If the unit was previously published and released to learners, any changes you made to the unit when it was hidden will now be visible to learners.": "\u00ccf th\u00e9 \u00fcn\u00eft w\u00e4s pr\u00e9v\u00ef\u00f6\u00fcsl\u00fd p\u00fc\u00dfl\u00efsh\u00e9d \u00e4nd r\u00e9l\u00e9\u00e4s\u00e9d t\u00f6 l\u00e9\u00e4rn\u00e9rs, \u00e4n\u00fd \u00e7h\u00e4ng\u00e9s \u00fd\u00f6\u00fc m\u00e4d\u00e9 t\u00f6 th\u00e9 \u00fcn\u00eft wh\u00e9n \u00eft w\u00e4s h\u00efdd\u00e9n w\u00efll n\u00f6w \u00df\u00e9 v\u00efs\u00ef\u00dfl\u00e9 t\u00f6 l\u00e9\u00e4rn\u00e9rs. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9 \u03c3\u0192\u0192\u03b9\u00a2\u03b9\u03b1 \u2202\u0454\u0455\u0454\u044f\u03c5\u03b7\u0442 \u043c\u03c3\u0142\u0142\u03b9\u0442 \u03b1\u03b7\u03b9\u043c #", "If the unit was previously published and released to students, any changes you made to the unit when it was hidden will now be visible to students. Do you want to proceed?": "\u00ccf th\u00e9 \u00fcn\u00eft w\u00e4s pr\u00e9v\u00ef\u00f6\u00fcsl\u00fd p\u00fc\u00dfl\u00efsh\u00e9d \u00e4nd r\u00e9l\u00e9\u00e4s\u00e9d t\u00f6 st\u00fcd\u00e9nts, \u00e4n\u00fd \u00e7h\u00e4ng\u00e9s \u00fd\u00f6\u00fc m\u00e4d\u00e9 t\u00f6 th\u00e9 \u00fcn\u00eft wh\u00e9n \u00eft w\u00e4s h\u00efdd\u00e9n w\u00efll n\u00f6w \u00df\u00e9 v\u00efs\u00ef\u00dfl\u00e9 t\u00f6 st\u00fcd\u00e9nts. D\u00f6 \u00fd\u00f6\u00fc w\u00e4nt t\u00f6 pr\u00f6\u00e7\u00e9\u00e9d? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9#", "If you do not yet have an account, use the button below to register.": "\u00ccf \u00fd\u00f6\u00fc d\u00f6 n\u00f6t \u00fd\u00e9t h\u00e4v\u00e9 \u00e4n \u00e4\u00e7\u00e7\u00f6\u00fcnt, \u00fcs\u00e9 th\u00e9 \u00df\u00fctt\u00f6n \u00df\u00e9l\u00f6w t\u00f6 r\u00e9g\u00efst\u00e9r. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", + "If you had a subscription previously, your payment history is still available on the {a_start}Orders and subscriptions{a_end} page": "\u00ccf \u00fd\u00f6\u00fc h\u00e4d \u00e4 s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n pr\u00e9v\u00ef\u00f6\u00fcsl\u00fd, \u00fd\u00f6\u00fcr p\u00e4\u00fdm\u00e9nt h\u00efst\u00f6r\u00fd \u00efs st\u00efll \u00e4v\u00e4\u00efl\u00e4\u00dfl\u00e9 \u00f6n th\u00e9 {a_start}\u00d6rd\u00e9rs \u00e4nd s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6ns{a_end} p\u00e4g\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5#", "If you leave this page without saving or submitting your response, you will lose any work you have done on the response.": "\u00ccf \u00fd\u00f6\u00fc l\u00e9\u00e4v\u00e9 th\u00efs p\u00e4g\u00e9 w\u00efth\u00f6\u00fct s\u00e4v\u00efng \u00f6r s\u00fc\u00dfm\u00eftt\u00efng \u00fd\u00f6\u00fcr r\u00e9sp\u00f6ns\u00e9, \u00fd\u00f6\u00fc w\u00efll l\u00f6s\u00e9 \u00e4n\u00fd w\u00f6rk \u00fd\u00f6\u00fc h\u00e4v\u00e9 d\u00f6n\u00e9 \u00f6n th\u00e9 r\u00e9sp\u00f6ns\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5#", "If you leave this page without submitting your peer assessment, you will lose any work you have done.": "\u00ccf \u00fd\u00f6\u00fc l\u00e9\u00e4v\u00e9 th\u00efs p\u00e4g\u00e9 w\u00efth\u00f6\u00fct s\u00fc\u00dfm\u00eftt\u00efng \u00fd\u00f6\u00fcr p\u00e9\u00e9r \u00e4ss\u00e9ssm\u00e9nt, \u00fd\u00f6\u00fc w\u00efll l\u00f6s\u00e9 \u00e4n\u00fd w\u00f6rk \u00fd\u00f6\u00fc h\u00e4v\u00e9 d\u00f6n\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#", "If you leave this page without submitting your self assessment, you will lose any work you have done.": "\u00ccf \u00fd\u00f6\u00fc l\u00e9\u00e4v\u00e9 th\u00efs p\u00e4g\u00e9 w\u00efth\u00f6\u00fct s\u00fc\u00dfm\u00eftt\u00efng \u00fd\u00f6\u00fcr s\u00e9lf \u00e4ss\u00e9ssm\u00e9nt, \u00fd\u00f6\u00fc w\u00efll l\u00f6s\u00e9 \u00e4n\u00fd w\u00f6rk \u00fd\u00f6\u00fc h\u00e4v\u00e9 d\u00f6n\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#", @@ -959,6 +966,7 @@ "In Progress": "\u00ccn Pr\u00f6gr\u00e9ss \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f #", "In order to sign in, you need to activate your account.{line_break}{line_break}We just sent an activation link to {strong_start} {email} {strong_end}. If you do not receive an email, check your spam folders or {anchorStart}contact {platform_name} Support{anchorEnd}.": "\u00ccn \u00f6rd\u00e9r t\u00f6 s\u00efgn \u00efn, \u00fd\u00f6\u00fc n\u00e9\u00e9d t\u00f6 \u00e4\u00e7t\u00efv\u00e4t\u00e9 \u00fd\u00f6\u00fcr \u00e4\u00e7\u00e7\u00f6\u00fcnt.{line_break}{line_break}W\u00e9 j\u00fcst s\u00e9nt \u00e4n \u00e4\u00e7t\u00efv\u00e4t\u00ef\u00f6n l\u00efnk t\u00f6 {strong_start} {email} {strong_end}. \u00ccf \u00fd\u00f6\u00fc d\u00f6 n\u00f6t r\u00e9\u00e7\u00e9\u00efv\u00e9 \u00e4n \u00e9m\u00e4\u00efl, \u00e7h\u00e9\u00e7k \u00fd\u00f6\u00fcr sp\u00e4m f\u00f6ld\u00e9rs \u00f6r {anchorStart}\u00e7\u00f6nt\u00e4\u00e7t {platform_name} S\u00fcpp\u00f6rt{anchorEnd}. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202#", "In the {linkStart}Course Outline{linkEnd}, use this group to control access to a component.": "\u00ccn th\u00e9 {linkStart}\u00c7\u00f6\u00fcrs\u00e9 \u00d6\u00fctl\u00efn\u00e9{linkEnd}, \u00fcs\u00e9 th\u00efs gr\u00f6\u00fcp t\u00f6 \u00e7\u00f6ntr\u00f6l \u00e4\u00e7\u00e7\u00e9ss t\u00f6 \u00e4 \u00e7\u00f6mp\u00f6n\u00e9nt. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442#", + "Inactive subscription": "\u00ccn\u00e4\u00e7t\u00efv\u00e9 s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Incorrect url format.": "\u00ccn\u00e7\u00f6rr\u00e9\u00e7t \u00fcrl f\u00f6rm\u00e4t. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Increase indent": "\u00ccn\u00e7r\u00e9\u00e4s\u00e9 \u00efnd\u00e9nt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", "Individual Exceptions": "\u00ccnd\u00efv\u00efd\u00fc\u00e4l \u00c9x\u00e7\u00e9pt\u00ef\u00f6ns \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", @@ -1023,6 +1031,7 @@ "Last published {lastPublishedStart}{publishedOn}{lastPublishedEnd} by {publishedByStart}{publishedBy}{publishedByEnd}": "L\u00e4st p\u00fc\u00dfl\u00efsh\u00e9d {lastPublishedStart}{publishedOn}{lastPublishedEnd} \u00df\u00fd {publishedByStart}{publishedBy}{publishedByEnd} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5#", "Last updated": "L\u00e4st \u00fcpd\u00e4t\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455#", "Learn More": "L\u00e9\u00e4rn M\u00f6r\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3#", + "Learn more": "L\u00e9\u00e4rn m\u00f6r\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3#", "Learn more about {license_name}": "L\u00e9\u00e4rn m\u00f6r\u00e9 \u00e4\u00df\u00f6\u00fct {license_name} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Learners are added to this cohort automatically.": "L\u00e9\u00e4rn\u00e9rs \u00e4r\u00e9 \u00e4dd\u00e9d t\u00f6 th\u00efs \u00e7\u00f6h\u00f6rt \u00e4\u00fct\u00f6m\u00e4t\u00ef\u00e7\u00e4ll\u00fd. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "Learners are added to this cohort only when you provide their email addresses or usernames on this page.": "L\u00e9\u00e4rn\u00e9rs \u00e4r\u00e9 \u00e4dd\u00e9d t\u00f6 th\u00efs \u00e7\u00f6h\u00f6rt \u00f6nl\u00fd wh\u00e9n \u00fd\u00f6\u00fc pr\u00f6v\u00efd\u00e9 th\u00e9\u00efr \u00e9m\u00e4\u00efl \u00e4ddr\u00e9ss\u00e9s \u00f6r \u00fcs\u00e9rn\u00e4m\u00e9s \u00f6n th\u00efs p\u00e4g\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", @@ -1092,6 +1101,7 @@ "Making Visible to Students": "M\u00e4k\u00efng V\u00efs\u00ef\u00dfl\u00e9 t\u00f6 St\u00fcd\u00e9nts \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#", "Manage": "M\u00e4n\u00e4g\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5#", "Manage Learners": "M\u00e4n\u00e4g\u00e9 L\u00e9\u00e4rn\u00e9rs \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", + "Manage my subscription": "M\u00e4n\u00e4g\u00e9 m\u00fd s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#", "Manual": "M\u00e4n\u00fc\u00e4l \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5#", "March": "marto", "Mark Exam As Completed": "M\u00e4rk \u00c9x\u00e4m \u00c0s \u00c7\u00f6mpl\u00e9t\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#", @@ -1112,6 +1122,7 @@ "Minimum Completion:": "M\u00efn\u00efm\u00fcm \u00c7\u00f6mpl\u00e9t\u00ef\u00f6n: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", "Minimum Score:": "M\u00efn\u00efm\u00fcm S\u00e7\u00f6r\u00e9: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442#", "Module state successfully deleted.": "M\u00f6d\u00fcl\u00e9 st\u00e4t\u00e9 s\u00fc\u00e7\u00e7\u00e9ssf\u00fcll\u00fd d\u00e9l\u00e9t\u00e9d. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442#", + "Monthly program subscriptions {emDash} more flexible, more affordable": "M\u00f6nthl\u00fd pr\u00f6gr\u00e4m s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6ns {emDash} m\u00f6r\u00e9 fl\u00e9x\u00ef\u00dfl\u00e9, m\u00f6r\u00e9 \u00e4ff\u00f6rd\u00e4\u00dfl\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "More": "M\u00f6r\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9#", "More opportunities for you": "M\u00f6r\u00e9 \u00f6pp\u00f6rt\u00fcn\u00eft\u00ef\u00e9s f\u00f6r \u00fd\u00f6\u00fc \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#", "More sessions coming soon": "M\u00f6r\u00e9 s\u00e9ss\u00ef\u00f6ns \u00e7\u00f6m\u00efng s\u00f6\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#", @@ -1126,6 +1137,7 @@ "Muted": "M\u00fct\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455#", "My Orders": "M\u00fd \u00d6rd\u00e9rs \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#", "My Teams": "M\u00fd T\u00e9\u00e4ms \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#", + "My programs": "M\u00fd pr\u00f6gr\u00e4ms \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f #", "N/A": "N/\u00c0 \u2c60'\u03c3\u044f\u0454\u043c#", "Name": "N\u00e4m\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9#", "Name ": "N\u00e4m\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455#", @@ -1136,10 +1148,12 @@ "Navigate up": "N\u00e4v\u00efg\u00e4t\u00e9 \u00fcp \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f #", "Need help logging in?": "N\u00e9\u00e9d h\u00e9lp l\u00f6gg\u00efng \u00efn? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Need help signing in?": "N\u00e9\u00e9d h\u00e9lp s\u00efgn\u00efng \u00efn? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", + "Need help? Check out the {a_start}Learner Help Center{span_start}{icon}{span_end}{a_end} to troubleshoot issues or contact support": "N\u00e9\u00e9d h\u00e9lp? \u00c7h\u00e9\u00e7k \u00f6\u00fct th\u00e9 {a_start}L\u00e9\u00e4rn\u00e9r H\u00e9lp \u00c7\u00e9nt\u00e9r{span_start}{icon}{span_end}{a_end} t\u00f6 tr\u00f6\u00fc\u00dfl\u00e9sh\u00f6\u00f6t \u00efss\u00fc\u00e9s \u00f6r \u00e7\u00f6nt\u00e4\u00e7t s\u00fcpp\u00f6rt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#", "Need other help signing in?": "N\u00e9\u00e9d \u00f6th\u00e9r h\u00e9lp s\u00efgn\u00efng \u00efn? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454#", "Needs verified certificate ": "N\u00e9\u00e9ds v\u00e9r\u00eff\u00ef\u00e9d \u00e7\u00e9rt\u00eff\u00ef\u00e7\u00e4t\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454#", "Never published": "N\u00e9v\u00e9r p\u00fc\u00dfl\u00efsh\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", "Never show assessment results": "N\u00e9v\u00e9r sh\u00f6w \u00e4ss\u00e9ssm\u00e9nt r\u00e9s\u00fclts \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2#", + "New": "N\u00e9w \u2c60'\u03c3\u044f\u0454\u043c#", "New %(item_type)s": "N\u00e9w %(item_type)s \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c #", "New Address": "N\u00e9w \u00c0ddr\u00e9ss \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f #", "New Password": "N\u00e9w P\u00e4ssw\u00f6rd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455#", @@ -1200,6 +1214,7 @@ "Notes visible": "N\u00f6t\u00e9s v\u00efs\u00ef\u00dfl\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9#", "November": "novembro", "Now": "Nun", + "Now available for many popular programs, affordable monthly subscription pricing can help you manage your budget more effectively. Subscriptions start at {minSubscriptionPrice}/month USD per program, after a 7-day full access free trial. Cancel at any time.": "N\u00f6w \u00e4v\u00e4\u00efl\u00e4\u00dfl\u00e9 f\u00f6r m\u00e4n\u00fd p\u00f6p\u00fcl\u00e4r pr\u00f6gr\u00e4ms, \u00e4ff\u00f6rd\u00e4\u00dfl\u00e9 m\u00f6nthl\u00fd s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n pr\u00ef\u00e7\u00efng \u00e7\u00e4n h\u00e9lp \u00fd\u00f6\u00fc m\u00e4n\u00e4g\u00e9 \u00fd\u00f6\u00fcr \u00df\u00fcdg\u00e9t m\u00f6r\u00e9 \u00e9ff\u00e9\u00e7t\u00efv\u00e9l\u00fd. S\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6ns st\u00e4rt \u00e4t {minSubscriptionPrice}/m\u00f6nth \u00dbSD p\u00e9r pr\u00f6gr\u00e4m, \u00e4ft\u00e9r \u00e4 7-d\u00e4\u00fd f\u00fcll \u00e4\u00e7\u00e7\u00e9ss fr\u00e9\u00e9 tr\u00ef\u00e4l. \u00c7\u00e4n\u00e7\u00e9l \u00e4t \u00e4n\u00fd t\u00efm\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454#", "Number Sent": "N\u00fcm\u00df\u00e9r S\u00e9nt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f #", "Number of Droppable": "N\u00fcm\u00df\u00e9r \u00f6f Dr\u00f6pp\u00e4\u00dfl\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", "Numbered List (Ctrl+O)": "N\u00fcm\u00df\u00e9r\u00e9d L\u00efst (\u00c7trl+\u00d6) \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#", @@ -1263,8 +1278,11 @@ "Paste row after": "P\u00e4st\u00e9 r\u00f6w \u00e4ft\u00e9r \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", "Paste row before": "P\u00e4st\u00e9 r\u00f6w \u00df\u00e9f\u00f6r\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c#", "Paste your embed code below:": "P\u00e4st\u00e9 \u00fd\u00f6\u00fcr \u00e9m\u00df\u00e9d \u00e7\u00f6d\u00e9 \u00df\u00e9l\u00f6w: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2#", + "Pasting": "P\u00e4st\u00efng \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c #", "Path to Signature Image": "P\u00e4th t\u00f6 S\u00efgn\u00e4t\u00fcr\u00e9 \u00ccm\u00e4g\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3#", "Pause": "P\u00e4\u00fcs\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455#", + "Pay {subscriptionPrice} after {trialLength}-day free trial": "P\u00e4\u00fd {subscriptionPrice} \u00e4ft\u00e9r {trialLength}-d\u00e4\u00fd fr\u00e9\u00e9 tr\u00ef\u00e4l \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454#", + "Pay {subscriptionPrice} for all courses in this program": "P\u00e4\u00fd {subscriptionPrice} f\u00f6r \u00e4ll \u00e7\u00f6\u00fcrs\u00e9s \u00efn th\u00efs pr\u00f6gr\u00e4m \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f#", "Peer": "P\u00e9\u00e9r \u2c60'\u03c3\u044f\u0454\u043c \u03b9#", "Peer Responses Received": "P\u00e9\u00e9r R\u00e9sp\u00f6ns\u00e9s R\u00e9\u00e7\u00e9\u00efv\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3#", "Peers Assessed": "P\u00e9\u00e9rs \u00c0ss\u00e9ss\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442#", @@ -1449,6 +1467,8 @@ "Reset Your Password": "R\u00e9s\u00e9t \u00dd\u00f6\u00fcr P\u00e4ssw\u00f6rd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", "Reset attempts for all students on problem '<%- problem_id %>'?": "R\u00e9s\u00e9t \u00e4tt\u00e9mpts f\u00f6r \u00e4ll st\u00fcd\u00e9nts \u00f6n pr\u00f6\u00dfl\u00e9m '<%- problem_id %>'? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "Responses could not be loaded. Refresh the page and try again.": "R\u00e9sp\u00f6ns\u00e9s \u00e7\u00f6\u00fcld n\u00f6t \u00df\u00e9 l\u00f6\u00e4d\u00e9d. R\u00e9fr\u00e9sh th\u00e9 p\u00e4g\u00e9 \u00e4nd tr\u00fd \u00e4g\u00e4\u00efn. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", + "Restart my subscription": "R\u00e9st\u00e4rt m\u00fd s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3#", + "Restart your subscription for {subscriptionPrice}. Your payment history is still available on the {a_start}Orders and subscriptions{a_end} page": "R\u00e9st\u00e4rt \u00fd\u00f6\u00fcr s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n f\u00f6r {subscriptionPrice}. \u00dd\u00f6\u00fcr p\u00e4\u00fdm\u00e9nt h\u00efst\u00f6r\u00fd \u00efs st\u00efll \u00e4v\u00e4\u00efl\u00e4\u00dfl\u00e9 \u00f6n th\u00e9 {a_start}\u00d6rd\u00e9rs \u00e4nd s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6ns{a_end} p\u00e4g\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#", "Restore enrollment code": "R\u00e9st\u00f6r\u00e9 \u00e9nr\u00f6llm\u00e9nt \u00e7\u00f6d\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3#", "Restore last draft": "R\u00e9st\u00f6r\u00e9 l\u00e4st dr\u00e4ft \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442#", "Restrict access to:": "R\u00e9str\u00ef\u00e7t \u00e4\u00e7\u00e7\u00e9ss t\u00f6: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", @@ -1512,10 +1532,14 @@ "Select a subject for your support request.": "S\u00e9l\u00e9\u00e7t \u00e4 s\u00fc\u00dfj\u00e9\u00e7t f\u00f6r \u00fd\u00f6\u00fcr s\u00fcpp\u00f6rt r\u00e9q\u00fc\u00e9st. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", "Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.": "S\u00e9l\u00e9\u00e7t \u00e4 t\u00efm\u00e9 \u00e4ll\u00f6tm\u00e9nt f\u00f6r th\u00e9 \u00e9x\u00e4m. \u00ccf \u00eft \u00efs \u00f6v\u00e9r 24 h\u00f6\u00fcrs, t\u00fdp\u00e9 \u00efn th\u00e9 \u00e4m\u00f6\u00fcnt \u00f6f t\u00efm\u00e9. \u00dd\u00f6\u00fc \u00e7\u00e4n gr\u00e4nt \u00efnd\u00efv\u00efd\u00fc\u00e4l l\u00e9\u00e4rn\u00e9rs \u00e9xtr\u00e4 t\u00efm\u00e9 t\u00f6 \u00e7\u00f6mpl\u00e9t\u00e9 th\u00e9 \u00e9x\u00e4m thr\u00f6\u00fcgh th\u00e9 \u00ccnstr\u00fc\u00e7t\u00f6r D\u00e4sh\u00df\u00f6\u00e4rd. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f#", "Select all": "S\u00e9l\u00e9\u00e7t \u00e4ll \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3#", - "Select employment status": "S\u00e9l\u00e9\u00e7t \u00e9mpl\u00f6\u00fdm\u00e9nt st\u00e4t\u00fcs \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7#", + "Select current industry": "S\u00e9l\u00e9\u00e7t \u00e7\u00fcrr\u00e9nt \u00efnd\u00fcstr\u00fd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3#", "Select fidelity": "S\u00e9l\u00e9\u00e7t f\u00efd\u00e9l\u00eft\u00fd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", + "Select guardian education": "S\u00e9l\u00e9\u00e7t g\u00fc\u00e4rd\u00ef\u00e4n \u00e9d\u00fc\u00e7\u00e4t\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#", "Select language": "S\u00e9l\u00e9\u00e7t l\u00e4ng\u00fc\u00e4g\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", + "Select level of education": "S\u00e9l\u00e9\u00e7t l\u00e9v\u00e9l \u00f6f \u00e9d\u00fc\u00e7\u00e4t\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#", + "Select military status": "S\u00e9l\u00e9\u00e7t m\u00efl\u00eft\u00e4r\u00fd st\u00e4t\u00fcs \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#", "Select one or more groups:": "S\u00e9l\u00e9\u00e7t \u00f6n\u00e9 \u00f6r m\u00f6r\u00e9 gr\u00f6\u00fcps: \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#", + "Select prospective industry": "S\u00e9l\u00e9\u00e7t pr\u00f6sp\u00e9\u00e7t\u00efv\u00e9 \u00efnd\u00fcstr\u00fd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454#", "Select the course-wide discussion topics that you want to divide.": "S\u00e9l\u00e9\u00e7t th\u00e9 \u00e7\u00f6\u00fcrs\u00e9-w\u00efd\u00e9 d\u00efs\u00e7\u00fcss\u00ef\u00f6n t\u00f6p\u00ef\u00e7s th\u00e4t \u00fd\u00f6\u00fc w\u00e4nt t\u00f6 d\u00efv\u00efd\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "Select the time zone for displaying course dates. If you do not specify a time zone, course dates, including assignment deadlines, will be displayed in your browser's local time zone.": "S\u00e9l\u00e9\u00e7t th\u00e9 t\u00efm\u00e9 z\u00f6n\u00e9 f\u00f6r d\u00efspl\u00e4\u00fd\u00efng \u00e7\u00f6\u00fcrs\u00e9 d\u00e4t\u00e9s. \u00ccf \u00fd\u00f6\u00fc d\u00f6 n\u00f6t sp\u00e9\u00e7\u00eff\u00fd \u00e4 t\u00efm\u00e9 z\u00f6n\u00e9, \u00e7\u00f6\u00fcrs\u00e9 d\u00e4t\u00e9s, \u00efn\u00e7l\u00fcd\u00efng \u00e4ss\u00efgnm\u00e9nt d\u00e9\u00e4dl\u00efn\u00e9s, w\u00efll \u00df\u00e9 d\u00efspl\u00e4\u00fd\u00e9d \u00efn \u00fd\u00f6\u00fcr \u00dfr\u00f6ws\u00e9r's l\u00f6\u00e7\u00e4l t\u00efm\u00e9 z\u00f6n\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455#", "Select turnaround": "S\u00e9l\u00e9\u00e7t t\u00fcrn\u00e4r\u00f6\u00fcnd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#", @@ -1628,6 +1652,7 @@ "Start regenerating certificates for students in this course?": "St\u00e4rt r\u00e9g\u00e9n\u00e9r\u00e4t\u00efng \u00e7\u00e9rt\u00eff\u00ef\u00e7\u00e4t\u00e9s f\u00f6r st\u00fcd\u00e9nts \u00efn th\u00efs \u00e7\u00f6\u00fcrs\u00e9? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "Start search": "St\u00e4rt s\u00e9\u00e4r\u00e7h \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455#", "Start working toward your next learning goal.": "St\u00e4rt w\u00f6rk\u00efng t\u00f6w\u00e4rd \u00fd\u00f6\u00fcr n\u00e9xt l\u00e9\u00e4rn\u00efng g\u00f6\u00e4l. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", + "Start {trialLength}-day free trial": "St\u00e4rt {trialLength}-d\u00e4\u00fd fr\u00e9\u00e9 tr\u00ef\u00e4l \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7#", "Started": "St\u00e4rt\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c #", "Started entrance exam rescore task for student '{student_id}'. Click the 'Show Task Status' button to see the status of the task.": "St\u00e4rt\u00e9d \u00e9ntr\u00e4n\u00e7\u00e9 \u00e9x\u00e4m r\u00e9s\u00e7\u00f6r\u00e9 t\u00e4sk f\u00f6r st\u00fcd\u00e9nt '{student_id}'. \u00c7l\u00ef\u00e7k th\u00e9 'Sh\u00f6w T\u00e4sk St\u00e4t\u00fcs' \u00df\u00fctt\u00f6n t\u00f6 s\u00e9\u00e9 th\u00e9 st\u00e4t\u00fcs \u00f6f th\u00e9 t\u00e4sk. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5#", "Started rescore problem task for problem '<%- problem_id %>' and student '<%- student_id %>'. Click the 'Show Task Status' button to see the status of the task.": "St\u00e4rt\u00e9d r\u00e9s\u00e7\u00f6r\u00e9 pr\u00f6\u00dfl\u00e9m t\u00e4sk f\u00f6r pr\u00f6\u00dfl\u00e9m '<%- problem_id %>' \u00e4nd st\u00fcd\u00e9nt '<%- student_id %>'. \u00c7l\u00ef\u00e7k th\u00e9 'Sh\u00f6w T\u00e4sk St\u00e4t\u00fcs' \u00df\u00fctt\u00f6n t\u00f6 s\u00e9\u00e9 th\u00e9 st\u00e4t\u00fcs \u00f6f th\u00e9 t\u00e4sk. #", @@ -1656,7 +1681,12 @@ "Submit Application": "S\u00fc\u00dfm\u00eft \u00c0ppl\u00ef\u00e7\u00e4t\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442#", "Submit enrollment change": "S\u00fc\u00dfm\u00eft \u00e9nr\u00f6llm\u00e9nt \u00e7h\u00e4ng\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7#", "Submitted": "S\u00fc\u00dfm\u00eftt\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#", + "Subscribed": "S\u00fc\u00dfs\u00e7r\u00ef\u00df\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3#", "Subscript": "S\u00fc\u00dfs\u00e7r\u00efpt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#", + "Subscription trial expires in {remainingDays} day": [ + "S\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n tr\u00ef\u00e4l \u00e9xp\u00efr\u00e9s \u00efn {remainingDays} d\u00e4\u00fd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5#", + "S\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n tr\u00ef\u00e4l \u00e9xp\u00efr\u00e9s \u00efn {remainingDays} d\u00e4\u00fds \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f#" + ], "Subsection": "S\u00fc\u00dfs\u00e9\u00e7t\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3#", "Subsection Visibility": "S\u00fc\u00dfs\u00e9\u00e7t\u00ef\u00f6n V\u00efs\u00ef\u00df\u00efl\u00eft\u00fd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Subsection is hidden after course end date": "S\u00fc\u00dfs\u00e9\u00e7t\u00ef\u00f6n \u00efs h\u00efdd\u00e9n \u00e4ft\u00e9r \u00e7\u00f6\u00fcrs\u00e9 \u00e9nd d\u00e4t\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", @@ -1724,6 +1754,7 @@ "Thank you for submitting a request! We appreciate your patience while we work to review your request.": "Th\u00e4nk \u00fd\u00f6\u00fc f\u00f6r s\u00fc\u00dfm\u00eftt\u00efng \u00e4 r\u00e9q\u00fc\u00e9st! W\u00e9 \u00e4ppr\u00e9\u00e7\u00ef\u00e4t\u00e9 \u00fd\u00f6\u00fcr p\u00e4t\u00ef\u00e9n\u00e7\u00e9 wh\u00efl\u00e9 w\u00e9 w\u00f6rk t\u00f6 r\u00e9v\u00ef\u00e9w \u00fd\u00f6\u00fcr r\u00e9q\u00fc\u00e9st. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#", "Thank you for submitting your financial assistance application for {course_name}! You can expect a response in 2-4 business days.": "Th\u00e4nk \u00fd\u00f6\u00fc f\u00f6r s\u00fc\u00dfm\u00eftt\u00efng \u00fd\u00f6\u00fcr f\u00efn\u00e4n\u00e7\u00ef\u00e4l \u00e4ss\u00efst\u00e4n\u00e7\u00e9 \u00e4ppl\u00ef\u00e7\u00e4t\u00ef\u00f6n f\u00f6r {course_name}! \u00dd\u00f6\u00fc \u00e7\u00e4n \u00e9xp\u00e9\u00e7t \u00e4 r\u00e9sp\u00f6ns\u00e9 \u00efn 2-4 \u00df\u00fcs\u00efn\u00e9ss d\u00e4\u00fds. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c#", "Thank you for submitting your photos. We will review them shortly. You can now sign up for any %(platformName)s course that offers verified certificates. Verification is good for one year. After one year, you must submit photos for verification again.": "Th\u00e4nk \u00fd\u00f6\u00fc f\u00f6r s\u00fc\u00dfm\u00eftt\u00efng \u00fd\u00f6\u00fcr ph\u00f6t\u00f6s. W\u00e9 w\u00efll r\u00e9v\u00ef\u00e9w th\u00e9m sh\u00f6rtl\u00fd. \u00dd\u00f6\u00fc \u00e7\u00e4n n\u00f6w s\u00efgn \u00fcp f\u00f6r \u00e4n\u00fd %(platformName)s \u00e7\u00f6\u00fcrs\u00e9 th\u00e4t \u00f6ff\u00e9rs v\u00e9r\u00eff\u00ef\u00e9d \u00e7\u00e9rt\u00eff\u00ef\u00e7\u00e4t\u00e9s. V\u00e9r\u00eff\u00ef\u00e7\u00e4t\u00ef\u00f6n \u00efs g\u00f6\u00f6d f\u00f6r \u00f6n\u00e9 \u00fd\u00e9\u00e4r. \u00c0ft\u00e9r \u00f6n\u00e9 \u00fd\u00e9\u00e4r, \u00fd\u00f6\u00fc m\u00fcst s\u00fc\u00dfm\u00eft ph\u00f6t\u00f6s f\u00f6r v\u00e9r\u00eff\u00ef\u00e7\u00e4t\u00ef\u00f6n \u00e4g\u00e4\u00efn. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454#", + "Thank you! You\u2019re helping make edX better for everyone.": "Th\u00e4nk \u00fd\u00f6\u00fc! \u00dd\u00f6\u00fc\u2019r\u00e9 h\u00e9lp\u00efng m\u00e4k\u00e9 \u00e9dX \u00df\u00e9tt\u00e9r f\u00f6r \u00e9v\u00e9r\u00fd\u00f6n\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "Thanks for returning to verify your ID in: {courseName}": "Th\u00e4nks f\u00f6r r\u00e9t\u00fcrn\u00efng t\u00f6 v\u00e9r\u00eff\u00fd \u00fd\u00f6\u00fcr \u00ccD \u00efn: {courseName} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "The \"{name}\" problem is configured to require a minimum of {min_grades} peer grades, and asks to review {min_graded} peers.": "Th\u00e9 \"{name}\" pr\u00f6\u00dfl\u00e9m \u00efs \u00e7\u00f6nf\u00efg\u00fcr\u00e9d t\u00f6 r\u00e9q\u00fc\u00efr\u00e9 \u00e4 m\u00efn\u00efm\u00fcm \u00f6f {min_grades} p\u00e9\u00e9r gr\u00e4d\u00e9s, \u00e4nd \u00e4sks t\u00f6 r\u00e9v\u00ef\u00e9w {min_graded} p\u00e9\u00e9rs. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c#", "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Th\u00e9 \u00dbRL \u00fd\u00f6\u00fc \u00e9nt\u00e9r\u00e9d s\u00e9\u00e9ms t\u00f6 \u00df\u00e9 \u00e4n \u00e9m\u00e4\u00efl \u00e4ddr\u00e9ss. D\u00f6 \u00fd\u00f6\u00fc w\u00e4nt t\u00f6 \u00e4dd th\u00e9 r\u00e9q\u00fc\u00efr\u00e9d m\u00e4\u00eflt\u00f6: pr\u00e9f\u00efx? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", @@ -1763,7 +1794,6 @@ "The language that team members primarily use to communicate with each other.": "Th\u00e9 l\u00e4ng\u00fc\u00e4g\u00e9 th\u00e4t t\u00e9\u00e4m m\u00e9m\u00df\u00e9rs pr\u00efm\u00e4r\u00efl\u00fd \u00fcs\u00e9 t\u00f6 \u00e7\u00f6mm\u00fcn\u00ef\u00e7\u00e4t\u00e9 w\u00efth \u00e9\u00e4\u00e7h \u00f6th\u00e9r. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5#", "The language used throughout this site. This site is currently available in a limited number of languages. Changing the value of this field will cause the page to refresh.": "Th\u00e9 l\u00e4ng\u00fc\u00e4g\u00e9 \u00fcs\u00e9d thr\u00f6\u00fcgh\u00f6\u00fct th\u00efs s\u00eft\u00e9. Th\u00efs s\u00eft\u00e9 \u00efs \u00e7\u00fcrr\u00e9ntl\u00fd \u00e4v\u00e4\u00efl\u00e4\u00dfl\u00e9 \u00efn \u00e4 l\u00efm\u00eft\u00e9d n\u00fcm\u00df\u00e9r \u00f6f l\u00e4ng\u00fc\u00e4g\u00e9s. \u00c7h\u00e4ng\u00efng th\u00e9 v\u00e4l\u00fc\u00e9 \u00f6f th\u00efs f\u00ef\u00e9ld w\u00efll \u00e7\u00e4\u00fcs\u00e9 th\u00e9 p\u00e4g\u00e9 t\u00f6 r\u00e9fr\u00e9sh. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9#", "The maximum number files that can be saved is ": "Th\u00e9 m\u00e4x\u00efm\u00fcm n\u00fcm\u00df\u00e9r f\u00efl\u00e9s th\u00e4t \u00e7\u00e4n \u00df\u00e9 s\u00e4v\u00e9d \u00efs \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", - "The maximum number of weeks this subsection can be due in is 18 weeks from the learner enrollment date.": "Th\u00e9 m\u00e4x\u00efm\u00fcm n\u00fcm\u00df\u00e9r \u00f6f w\u00e9\u00e9ks th\u00efs s\u00fc\u00dfs\u00e9\u00e7t\u00ef\u00f6n \u00e7\u00e4n \u00df\u00e9 d\u00fc\u00e9 \u00efn \u00efs 18 w\u00e9\u00e9ks fr\u00f6m th\u00e9 l\u00e9\u00e4rn\u00e9r \u00e9nr\u00f6llm\u00e9nt d\u00e4t\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c#", "The minimum completion percentage must be a whole number between 0 and 100.": "Th\u00e9 m\u00efn\u00efm\u00fcm \u00e7\u00f6mpl\u00e9t\u00ef\u00f6n p\u00e9r\u00e7\u00e9nt\u00e4g\u00e9 m\u00fcst \u00df\u00e9 \u00e4 wh\u00f6l\u00e9 n\u00fcm\u00df\u00e9r \u00df\u00e9tw\u00e9\u00e9n 0 \u00e4nd 100. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5#", "The minimum grade for course credit is not set.": "Th\u00e9 m\u00efn\u00efm\u00fcm gr\u00e4d\u00e9 f\u00f6r \u00e7\u00f6\u00fcrs\u00e9 \u00e7r\u00e9d\u00eft \u00efs n\u00f6t s\u00e9t. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "The minimum number of weeks this subsection can be due in is 1 week from the learner enrollment date.": "Th\u00e9 m\u00efn\u00efm\u00fcm n\u00fcm\u00df\u00e9r \u00f6f w\u00e9\u00e9ks th\u00efs s\u00fc\u00dfs\u00e9\u00e7t\u00ef\u00f6n \u00e7\u00e4n \u00df\u00e9 d\u00fc\u00e9 \u00efn \u00efs 1 w\u00e9\u00e9k fr\u00f6m th\u00e9 l\u00e9\u00e4rn\u00e9r \u00e9nr\u00f6llm\u00e9nt d\u00e4t\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454#", @@ -1954,6 +1984,7 @@ "Transcript Turnaround": "Tr\u00e4ns\u00e7r\u00efpt T\u00fcrn\u00e4r\u00f6\u00fcnd \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Transcript will be displayed when you start playing the video.": "Tr\u00e4ns\u00e7r\u00efpt w\u00efll \u00df\u00e9 d\u00efspl\u00e4\u00fd\u00e9d wh\u00e9n \u00fd\u00f6\u00fc st\u00e4rt pl\u00e4\u00fd\u00efng th\u00e9 v\u00efd\u00e9\u00f6. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "Transcripts": "Tr\u00e4ns\u00e7r\u00efpts \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f #", + "Trial subscription": "Tr\u00ef\u00e4l s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442#", "Try the transaction again in a few minutes.": "Tr\u00fd th\u00e9 tr\u00e4ns\u00e4\u00e7t\u00ef\u00f6n \u00e4g\u00e4\u00efn \u00efn \u00e4 f\u00e9w m\u00efn\u00fct\u00e9s. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", "Try using a different browser, such as Google Chrome.": "Tr\u00fd \u00fcs\u00efng \u00e4 d\u00efff\u00e9r\u00e9nt \u00dfr\u00f6ws\u00e9r, s\u00fc\u00e7h \u00e4s G\u00f6\u00f6gl\u00e9 \u00c7hr\u00f6m\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "Turn off transcripts": "T\u00fcrn \u00f6ff tr\u00e4ns\u00e7r\u00efpts \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", @@ -1990,6 +2021,7 @@ "Unlink This Account": "\u00dbnl\u00efnk Th\u00efs \u00c0\u00e7\u00e7\u00f6\u00fcnt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", "Unlink your {accountName} account": "\u00dbnl\u00efnk \u00fd\u00f6\u00fcr {accountName} \u00e4\u00e7\u00e7\u00f6\u00fcnt \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3#", "Unlinking": "\u00dbnl\u00efnk\u00efng \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#", + "Unlock verified access to all courses for {subscriptionPrice}. Cancel anytime.": "\u00dbnl\u00f6\u00e7k v\u00e9r\u00eff\u00ef\u00e9d \u00e4\u00e7\u00e7\u00e9ss t\u00f6 \u00e4ll \u00e7\u00f6\u00fcrs\u00e9s f\u00f6r {subscriptionPrice}. \u00c7\u00e4n\u00e7\u00e9l \u00e4n\u00fdt\u00efm\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "Unmark as Answer": "\u00dbnm\u00e4rk \u00e4s \u00c0nsw\u00e9r \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c#", "Unmute": "\u00dbnm\u00fct\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5#", "Unnamed Option": "\u00dbnn\u00e4m\u00e9d \u00d6pt\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442#", @@ -2011,6 +2043,7 @@ "Upgrade Deadline": "\u00dbpgr\u00e4d\u00e9 D\u00e9\u00e4dl\u00efn\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c#", "Upgrade to Verified": "\u00dbpgr\u00e4d\u00e9 t\u00f6 V\u00e9r\u00eff\u00ef\u00e9d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", "Upgrade to a Verified Certificate for {courseName}": "\u00dbpgr\u00e4d\u00e9 t\u00f6 \u00e4 V\u00e9r\u00eff\u00ef\u00e9d \u00c7\u00e9rt\u00eff\u00ef\u00e7\u00e4t\u00e9 f\u00f6r {courseName} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", + "Upgrade with a subscription": "\u00dbpgr\u00e4d\u00e9 w\u00efth \u00e4 s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454#", "Upload": "\u00dbpl\u00f6\u00e4d \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5#", "Upload %(file_name)s": "\u00dbpl\u00f6\u00e4d %(file_name)s \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3#", "Upload File": "\u00dbpl\u00f6\u00e4d F\u00efl\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f #", @@ -2092,6 +2125,7 @@ "Very low": "V\u00e9r\u00fd l\u00f6w \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#", "Video Capture Error": "V\u00efd\u00e9\u00f6 \u00c7\u00e4pt\u00fcr\u00e9 \u00c9rr\u00f6r \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442,#", "Video ID": "V\u00efd\u00e9\u00f6 \u00ccD \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#", + "Video Sharing": "V\u00efd\u00e9\u00f6 Sh\u00e4r\u00efng \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9#", "Video Source Language": "V\u00efd\u00e9\u00f6 S\u00f6\u00fcr\u00e7\u00e9 L\u00e4ng\u00fc\u00e4g\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Video Status": "V\u00efd\u00e9\u00f6 St\u00e4t\u00fcs \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455#", "Video duration is {humanizeDuration}": "V\u00efd\u00e9\u00f6 d\u00fcr\u00e4t\u00ef\u00f6n \u00efs {humanizeDuration} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", @@ -2115,6 +2149,8 @@ "View and grade responses": "V\u00ef\u00e9w \u00e4nd gr\u00e4d\u00e9 r\u00e9sp\u00f6ns\u00e9s \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7#", "View child items": "V\u00ef\u00e9w \u00e7h\u00efld \u00eft\u00e9ms \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c#", "View discussion": "V\u00ef\u00e9w d\u00efs\u00e7\u00fcss\u00ef\u00f6n \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1#", + "View program": "V\u00ef\u00e9w pr\u00f6gr\u00e4m \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455#", + "View your receipts or modify your subscription on the {a_start}Orders and subscriptions{a_end} page": "V\u00ef\u00e9w \u00fd\u00f6\u00fcr r\u00e9\u00e7\u00e9\u00efpts \u00f6r m\u00f6d\u00eff\u00fd \u00fd\u00f6\u00fcr s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n \u00f6n th\u00e9 {a_start}\u00d6rd\u00e9rs \u00e4nd s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6ns{a_end} p\u00e4g\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455#", "View {span_start} {team_name} {span_end}": "V\u00ef\u00e9w {span_start} {team_name} {span_end} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c#", "Viewing %s course": [ "V\u00ef\u00e9w\u00efng %s \u00e7\u00f6\u00fcrs\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442#", @@ -2160,10 +2196,6 @@ "What if I can't see the camera image, or if I can't see my photo do determine which side is visible?": "Wh\u00e4t \u00eff \u00cc \u00e7\u00e4n't s\u00e9\u00e9 th\u00e9 \u00e7\u00e4m\u00e9r\u00e4 \u00efm\u00e4g\u00e9, \u00f6r \u00eff \u00cc \u00e7\u00e4n't s\u00e9\u00e9 m\u00fd ph\u00f6t\u00f6 d\u00f6 d\u00e9t\u00e9rm\u00efn\u00e9 wh\u00ef\u00e7h s\u00efd\u00e9 \u00efs v\u00efs\u00ef\u00dfl\u00e9? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442#", "What if I have difficulty holding my ID in position relative to the camera?": "Wh\u00e4t \u00eff \u00cc h\u00e4v\u00e9 d\u00efff\u00ef\u00e7\u00fclt\u00fd h\u00f6ld\u00efng m\u00fd \u00ccD \u00efn p\u00f6s\u00eft\u00ef\u00f6n r\u00e9l\u00e4t\u00efv\u00e9 t\u00f6 th\u00e9 \u00e7\u00e4m\u00e9r\u00e4? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5#", "What if I have difficulty holding my head in position relative to the camera?": "Wh\u00e4t \u00eff \u00cc h\u00e4v\u00e9 d\u00efff\u00ef\u00e7\u00fclt\u00fd h\u00f6ld\u00efng m\u00fd h\u00e9\u00e4d \u00efn p\u00f6s\u00eft\u00ef\u00f6n r\u00e9l\u00e4t\u00efv\u00e9 t\u00f6 th\u00e9 \u00e7\u00e4m\u00e9r\u00e4? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442#", - "What industry do you currently work in?": "Wh\u00e4t \u00efnd\u00fcstr\u00fd d\u00f6 \u00fd\u00f6\u00fc \u00e7\u00fcrr\u00e9ntl\u00fd w\u00f6rk \u00efn? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f#", - "What industry do you want to work in?": "Wh\u00e4t \u00efnd\u00fcstr\u00fd d\u00f6 \u00fd\u00f6\u00fc w\u00e4nt t\u00f6 w\u00f6rk \u00efn? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5#", - "What is the highest level of education that any of your parents or guardians have achieved?": "Wh\u00e4t \u00efs th\u00e9 h\u00efgh\u00e9st l\u00e9v\u00e9l \u00f6f \u00e9d\u00fc\u00e7\u00e4t\u00ef\u00f6n th\u00e4t \u00e4n\u00fd \u00f6f \u00fd\u00f6\u00fcr p\u00e4r\u00e9nts \u00f6r g\u00fc\u00e4rd\u00ef\u00e4ns h\u00e4v\u00e9 \u00e4\u00e7h\u00ef\u00e9v\u00e9d? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7#", - "What is the highest level of education that you have achieved so far?": "Wh\u00e4t \u00efs th\u00e9 h\u00efgh\u00e9st l\u00e9v\u00e9l \u00f6f \u00e9d\u00fc\u00e7\u00e4t\u00ef\u00f6n th\u00e4t \u00fd\u00f6\u00fc h\u00e4v\u00e9 \u00e4\u00e7h\u00ef\u00e9v\u00e9d s\u00f6 f\u00e4r? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", "What was the total combined income, during the last 12 months, of all members of your family? ": "Wh\u00e4t w\u00e4s th\u00e9 t\u00f6t\u00e4l \u00e7\u00f6m\u00df\u00efn\u00e9d \u00efn\u00e7\u00f6m\u00e9, d\u00fcr\u00efng th\u00e9 l\u00e4st 12 m\u00f6nths, \u00f6f \u00e4ll m\u00e9m\u00df\u00e9rs \u00f6f \u00fd\u00f6\u00fcr f\u00e4m\u00efl\u00fd? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#", "What's Your Next Accomplishment?": "Wh\u00e4t's \u00dd\u00f6\u00fcr N\u00e9xt \u00c0\u00e7\u00e7\u00f6mpl\u00efshm\u00e9nt? \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454#", "When learners submit an answer to an assessment, they immediately see whether the answer is correct or incorrect, and the score received.": "Wh\u00e9n l\u00e9\u00e4rn\u00e9rs s\u00fc\u00dfm\u00eft \u00e4n \u00e4nsw\u00e9r t\u00f6 \u00e4n \u00e4ss\u00e9ssm\u00e9nt, th\u00e9\u00fd \u00efmm\u00e9d\u00ef\u00e4t\u00e9l\u00fd s\u00e9\u00e9 wh\u00e9th\u00e9r th\u00e9 \u00e4nsw\u00e9r \u00efs \u00e7\u00f6rr\u00e9\u00e7t \u00f6r \u00efn\u00e7\u00f6rr\u00e9\u00e7t, \u00e4nd th\u00e9 s\u00e7\u00f6r\u00e9 r\u00e9\u00e7\u00e9\u00efv\u00e9d. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9 \u03c3\u0192\u0192\u03b9\u00a2\u03b9\u03b1 \u2202\u0454\u0455\u0454\u044f\u03c5\u03b7\u0442 \u043c\u03c3\u0142\u0142\u03b9\u0442 \u03b1\u03b7\u03b9\u043c \u03b9\u2202 \u0454\u0455\u0442 \u0142\u03b1\u0432\u03c3#", @@ -2218,6 +2250,7 @@ "You don't seem to have a webcam connected.": "\u00dd\u00f6\u00fc d\u00f6n't s\u00e9\u00e9m t\u00f6 h\u00e4v\u00e9 \u00e4 w\u00e9\u00df\u00e7\u00e4m \u00e7\u00f6nn\u00e9\u00e7t\u00e9d. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", "You have added a criterion. You will need to select an option for the criterion in the Learner Training step. To do this, click the Assessment Steps tab.": "\u00dd\u00f6\u00fc h\u00e4v\u00e9 \u00e4dd\u00e9d \u00e4 \u00e7r\u00eft\u00e9r\u00ef\u00f6n. \u00dd\u00f6\u00fc w\u00efll n\u00e9\u00e9d t\u00f6 s\u00e9l\u00e9\u00e7t \u00e4n \u00f6pt\u00ef\u00f6n f\u00f6r th\u00e9 \u00e7r\u00eft\u00e9r\u00ef\u00f6n \u00efn th\u00e9 L\u00e9\u00e4rn\u00e9r Tr\u00e4\u00efn\u00efng st\u00e9p. T\u00f6 d\u00f6 th\u00efs, \u00e7l\u00ef\u00e7k th\u00e9 \u00c0ss\u00e9ssm\u00e9nt St\u00e9ps t\u00e4\u00df. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9 \u03c3\u0192\u0192\u03b9\u00a2\u03b9\u03b1 \u2202\u0454\u0455\u0454\u044f\u03c5\u03b7\u0442 \u043c\u03c3\u0142\u0142\u03b9#", "You have already verified your ID!": "\u00dd\u00f6\u00fc h\u00e4v\u00e9 \u00e4lr\u00e9\u00e4d\u00fd v\u00e9r\u00eff\u00ef\u00e9d \u00fd\u00f6\u00fcr \u00ccD! \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442#", + "You have an active subscription to the {programName} program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.": "\u00dd\u00f6\u00fc h\u00e4v\u00e9 \u00e4n \u00e4\u00e7t\u00efv\u00e9 s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n t\u00f6 th\u00e9 {programName} pr\u00f6gr\u00e4m \u00df\u00fct \u00e4r\u00e9 n\u00f6t \u00e9nr\u00f6ll\u00e9d \u00efn \u00e4n\u00fd \u00e7\u00f6\u00fcrs\u00e9s. \u00c9nr\u00f6ll \u00efn \u00e4 r\u00e9m\u00e4\u00efn\u00efng \u00e7\u00f6\u00fcrs\u00e9 \u00e4nd \u00e9nj\u00f6\u00fd v\u00e9r\u00eff\u00ef\u00e9d \u00e4\u00e7\u00e7\u00e9ss. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9 \u03c3\u0192\u0192\u03b9\u00a2\u03b9\u03b1 \u2202\u0454\u0455\u0454\u044f\u03c5\u03b7\u0442 \u043c\u03c3\u0142\u0142\u03b9\u0442 \u03b1\u03b7\u03b9\u043c \u03b9\u2202 \u0454\u0455#", "You have been logged out of your account. Click Okay to log in again now. Click Cancel to stay on this page (you must log in again to save your work).": "\u00dd\u00f6\u00fc h\u00e4v\u00e9 \u00df\u00e9\u00e9n l\u00f6gg\u00e9d \u00f6\u00fct \u00f6f \u00fd\u00f6\u00fcr \u00e4\u00e7\u00e7\u00f6\u00fcnt. \u00c7l\u00ef\u00e7k \u00d6k\u00e4\u00fd t\u00f6 l\u00f6g \u00efn \u00e4g\u00e4\u00efn n\u00f6w. \u00c7l\u00ef\u00e7k \u00c7\u00e4n\u00e7\u00e9l t\u00f6 st\u00e4\u00fd \u00f6n th\u00efs p\u00e4g\u00e9 (\u00fd\u00f6\u00fc m\u00fcst l\u00f6g \u00efn \u00e4g\u00e4\u00efn t\u00f6 s\u00e4v\u00e9 \u00fd\u00f6\u00fcr w\u00f6rk). \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9 \u03c3\u0192\u0192\u03b9\u00a2\u03b9\u03b1 \u2202\u0454\u0455\u0454\u044f\u03c5\u03b7\u0442 \u043c\u03c3\u0142\u0142\u03b9\u0442 \u03b1\u03b7#", "You have deleted a criterion. The criterion has been removed from the example responses in the Learner Training step.": "\u00dd\u00f6\u00fc h\u00e4v\u00e9 d\u00e9l\u00e9t\u00e9d \u00e4 \u00e7r\u00eft\u00e9r\u00ef\u00f6n. Th\u00e9 \u00e7r\u00eft\u00e9r\u00ef\u00f6n h\u00e4s \u00df\u00e9\u00e9n r\u00e9m\u00f6v\u00e9d fr\u00f6m th\u00e9 \u00e9x\u00e4mpl\u00e9 r\u00e9sp\u00f6ns\u00e9s \u00efn th\u00e9 L\u00e9\u00e4rn\u00e9r Tr\u00e4\u00efn\u00efng st\u00e9p. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#", "You have deleted all the options for this criterion. The criterion has been removed from the sample responses in the Learner Training step.": "\u00dd\u00f6\u00fc h\u00e4v\u00e9 d\u00e9l\u00e9t\u00e9d \u00e4ll th\u00e9 \u00f6pt\u00ef\u00f6ns f\u00f6r th\u00efs \u00e7r\u00eft\u00e9r\u00ef\u00f6n. Th\u00e9 \u00e7r\u00eft\u00e9r\u00ef\u00f6n h\u00e4s \u00df\u00e9\u00e9n r\u00e9m\u00f6v\u00e9d fr\u00f6m th\u00e9 s\u00e4mpl\u00e9 r\u00e9sp\u00f6ns\u00e9s \u00efn th\u00e9 L\u00e9\u00e4rn\u00e9r Tr\u00e4\u00efn\u00efng st\u00e9p. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442\u03b1\u0442 \u03b7\u03c3\u03b7 \u03c1\u044f\u03c3\u03b9\u2202\u0454\u03b7\u0442, \u0455\u03c5\u03b7\u0442 \u03b9\u03b7 \u00a2\u03c5\u0142\u03c1\u03b1 q\u03c5\u03b9 \u03c3\u0192\u0192\u03b9\u00a2\u03b9\u03b1 \u2202\u0454\u0455\u0454\u044f\u03c5\u03b7\u0442 \u043c\u03c3\u0142\u0142\u03b9\u0442 \u03b1\u03b7\u03b9\u043c \u03b9\u2202 \u0454\u0455\u0442 \u0142\u03b1#", @@ -2297,6 +2330,7 @@ "Your message cannot be blank.": "\u00dd\u00f6\u00fcr m\u00e9ss\u00e4g\u00e9 \u00e7\u00e4nn\u00f6t \u00df\u00e9 \u00dfl\u00e4nk. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2#", "Your message must have a subject.": "\u00dd\u00f6\u00fcr m\u00e9ss\u00e4g\u00e9 m\u00fcst h\u00e4v\u00e9 \u00e4 s\u00fc\u00dfj\u00e9\u00e7t. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454#", "Your message must have at least one target.": "\u00dd\u00f6\u00fcr m\u00e9ss\u00e4g\u00e9 m\u00fcst h\u00e4v\u00e9 \u00e4t l\u00e9\u00e4st \u00f6n\u00e9 t\u00e4rg\u00e9t. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", + "Your next billing date is {currentPeriodEnd}": "\u00dd\u00f6\u00fcr n\u00e9xt \u00df\u00efll\u00efng d\u00e4t\u00e9 \u00efs {currentPeriodEnd} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2#", "Your onboarding exam has been rejected. Please retry onboarding.": "\u00dd\u00f6\u00fcr \u00f6n\u00df\u00f6\u00e4rd\u00efng \u00e9x\u00e4m h\u00e4s \u00df\u00e9\u00e9n r\u00e9j\u00e9\u00e7t\u00e9d. Pl\u00e9\u00e4s\u00e9 r\u00e9tr\u00fd \u00f6n\u00df\u00f6\u00e4rd\u00efng. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "Your onboarding profile has been approved in another course, so you are eligible to take proctored exams in this course. However, it is highly recommended that you complete this course's onboarding exam in order to ensure that your device still meets the requirements for proctoring.": "\u00dd\u00f6\u00fcr \u00f6n\u00df\u00f6\u00e4rd\u00efng pr\u00f6f\u00efl\u00e9 h\u00e4s \u00df\u00e9\u00e9n \u00e4ppr\u00f6v\u00e9d \u00efn \u00e4n\u00f6th\u00e9r \u00e7\u00f6\u00fcrs\u00e9, s\u00f6 \u00fd\u00f6\u00fc \u00e4r\u00e9 \u00e9l\u00efg\u00ef\u00dfl\u00e9 t\u00f6 t\u00e4k\u00e9 pr\u00f6\u00e7t\u00f6r\u00e9d \u00e9x\u00e4ms \u00efn th\u00efs \u00e7\u00f6\u00fcrs\u00e9. H\u00f6w\u00e9v\u00e9r, \u00eft \u00efs h\u00efghl\u00fd r\u00e9\u00e7\u00f6mm\u00e9nd\u00e9d th\u00e4t \u00fd\u00f6\u00fc \u00e7\u00f6mpl\u00e9t\u00e9 th\u00efs \u00e7\u00f6\u00fcrs\u00e9's \u00f6n\u00df\u00f6\u00e4rd\u00efng \u00e9x\u00e4m \u00efn \u00f6rd\u00e9r t\u00f6 \u00e9ns\u00fcr\u00e9 th\u00e4t \u00fd\u00f6\u00fcr d\u00e9v\u00ef\u00e7\u00e9 st\u00efll m\u00e9\u00e9ts th\u00e9 r\u00e9q\u00fc\u00efr\u00e9m\u00e9nts f\u00f6r pr\u00f6\u00e7t\u00f6r\u00efng. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1#", "Your onboarding profile has been approved in another course, so you are eligible to take proctored exams in this course. However, your onboarding status is expiring soon. Please complete onboarding again to ensure that you will be able to continue taking proctored exams.": "\u00dd\u00f6\u00fcr \u00f6n\u00df\u00f6\u00e4rd\u00efng pr\u00f6f\u00efl\u00e9 h\u00e4s \u00df\u00e9\u00e9n \u00e4ppr\u00f6v\u00e9d \u00efn \u00e4n\u00f6th\u00e9r \u00e7\u00f6\u00fcrs\u00e9, s\u00f6 \u00fd\u00f6\u00fc \u00e4r\u00e9 \u00e9l\u00efg\u00ef\u00dfl\u00e9 t\u00f6 t\u00e4k\u00e9 pr\u00f6\u00e7t\u00f6r\u00e9d \u00e9x\u00e4ms \u00efn th\u00efs \u00e7\u00f6\u00fcrs\u00e9. H\u00f6w\u00e9v\u00e9r, \u00fd\u00f6\u00fcr \u00f6n\u00df\u00f6\u00e4rd\u00efng st\u00e4t\u00fcs \u00efs \u00e9xp\u00efr\u00efng s\u00f6\u00f6n. Pl\u00e9\u00e4s\u00e9 \u00e7\u00f6mpl\u00e9t\u00e9 \u00f6n\u00df\u00f6\u00e4rd\u00efng \u00e4g\u00e4\u00efn t\u00f6 \u00e9ns\u00fcr\u00e9 th\u00e4t \u00fd\u00f6\u00fc w\u00efll \u00df\u00e9 \u00e4\u00dfl\u00e9 t\u00f6 \u00e7\u00f6nt\u00efn\u00fc\u00e9 t\u00e4k\u00efng pr\u00f6\u00e7t\u00f6r\u00e9d \u00e9x\u00e4ms. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442.#", @@ -2317,6 +2351,10 @@ "Your upload of '{file}' succeeded.": "\u00dd\u00f6\u00fcr \u00fcpl\u00f6\u00e4d \u00f6f '{file}' s\u00fc\u00e7\u00e7\u00e9\u00e9d\u00e9d. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442#", "Your verification status is good until {verificationGoodUntil}.": "\u00dd\u00f6\u00fcr v\u00e9r\u00eff\u00ef\u00e7\u00e4t\u00ef\u00f6n st\u00e4t\u00fcs \u00efs g\u00f6\u00f6d \u00fcnt\u00efl {verificationGoodUntil}. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f #", "Your video uploads are not complete.": "\u00dd\u00f6\u00fcr v\u00efd\u00e9\u00f6 \u00fcpl\u00f6\u00e4ds \u00e4r\u00e9 n\u00f6t \u00e7\u00f6mpl\u00e9t\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5#", + "Your {programName} trial will expire in {remainingDays} day at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.": [ + "\u00dd\u00f6\u00fcr {programName} tr\u00ef\u00e4l w\u00efll \u00e9xp\u00efr\u00e9 \u00efn {remainingDays} d\u00e4\u00fd \u00e4t {trialEndTime} \u00f6n {trialEndDate} \u00e4nd th\u00e9 \u00e7\u00e4rd \u00f6n f\u00efl\u00e9 w\u00efll \u00df\u00e9 \u00e7h\u00e4rg\u00e9d {subscriptionPrice}. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3#", + "\u00dd\u00f6\u00fcr {programName} tr\u00ef\u00e4l w\u00efll \u00e9xp\u00efr\u00e9 \u00efn {remainingDays} d\u00e4\u00fds \u00e4t {trialEndTime} \u00f6n {trialEndDate} \u00e4nd th\u00e9 \u00e7\u00e4rd \u00f6n f\u00efl\u00e9 w\u00efll \u00df\u00e9 \u00e7h\u00e4rg\u00e9d {subscriptionPrice}. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2#" + ], "Your {program} Certificate": "\u00dd\u00f6\u00fcr {program} \u00c7\u00e9rt\u00eff\u00ef\u00e7\u00e4t\u00e9 \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, #", "Yourself": "\u00dd\u00f6\u00fcrs\u00e9lf \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#", "Zoom In": "Z\u00f6\u00f6m \u00ccn \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c #", @@ -2509,6 +2547,7 @@ "{startTag}{requestToken}{endTag}{selector}": "{startTag}{requestToken}{endTag}{selector} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455#", "{start_strong}{total}{end_strong} words submitted in total.": "{start_strong}{total}{end_strong} w\u00f6rds s\u00fc\u00dfm\u00eftt\u00e9d \u00efn t\u00f6t\u00e4l. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442#", "{strongStart}Warning: Account deletion is permanent.{strongEnd} Please read the above carefully before proceeding. This is an irreversible action, and {strongStart}you will no longer be able to use the same email on {platformName}.{strongEnd}": "{strongStart}W\u00e4rn\u00efng: \u00c0\u00e7\u00e7\u00f6\u00fcnt d\u00e9l\u00e9t\u00ef\u00f6n \u00efs p\u00e9rm\u00e4n\u00e9nt.{strongEnd} Pl\u00e9\u00e4s\u00e9 r\u00e9\u00e4d th\u00e9 \u00e4\u00df\u00f6v\u00e9 \u00e7\u00e4r\u00e9f\u00fcll\u00fd \u00df\u00e9f\u00f6r\u00e9 pr\u00f6\u00e7\u00e9\u00e9d\u00efng. Th\u00efs \u00efs \u00e4n \u00efrr\u00e9v\u00e9rs\u00ef\u00dfl\u00e9 \u00e4\u00e7t\u00ef\u00f6n, \u00e4nd {strongStart}\u00fd\u00f6\u00fc w\u00efll n\u00f6 l\u00f6ng\u00e9r \u00df\u00e9 \u00e4\u00dfl\u00e9 t\u00f6 \u00fcs\u00e9 th\u00e9 s\u00e4m\u00e9 \u00e9m\u00e4\u00efl \u00f6n {platformName}.{strongEnd} \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1\u2202\u03b9\u03c1\u03b9\u0455\u03b9\u00a2\u03b9\u03b7g \u0454\u0142\u03b9\u0442, \u0455\u0454\u2202 \u2202\u03c3 \u0454\u03b9\u03c5\u0455\u043c\u03c3\u2202 \u0442\u0454\u043c\u03c1\u03c3\u044f \u03b9\u03b7\u00a2\u03b9\u2202\u03b9\u2202\u03c5\u03b7\u0442 \u03c5\u0442 \u0142\u03b1\u0432\u03c3\u044f\u0454 \u0454\u0442 \u2202\u03c3\u0142\u03c3\u044f\u0454 \u043c\u03b1g\u03b7\u03b1 \u03b1\u0142\u03b9q\u03c5\u03b1. \u03c5\u0442 \u0454\u03b7\u03b9\u043c \u03b1\u2202 \u043c\u03b9\u03b7\u03b9\u043c \u03bd\u0454\u03b7\u03b9\u03b1\u043c, q\u03c5\u03b9\u0455 \u03b7\u03c3\u0455\u0442\u044f\u03c5\u2202 \u0454\u03c7\u0454\u044f\u00a2\u03b9\u0442\u03b1\u0442\u03b9\u03c3\u03b7 \u03c5\u0142\u0142\u03b1\u043c\u00a2\u03c3 \u0142\u03b1\u0432\u03c3\u044f\u03b9\u0455 \u03b7\u03b9\u0455\u03b9 \u03c5\u0442 \u03b1\u0142\u03b9q\u03c5\u03b9\u03c1 \u0454\u03c7 \u0454\u03b1 \u00a2\u03c3\u043c\u043c\u03c3\u2202\u03c3 \u00a2\u03c3\u03b7\u0455\u0454q\u03c5\u03b1\u0442. \u2202\u03c5\u03b9\u0455 \u03b1\u03c5\u0442\u0454 \u03b9\u044f\u03c5\u044f\u0454 \u2202\u03c3\u0142\u03c3\u044f \u03b9\u03b7 \u044f\u0454\u03c1\u044f\u0454\u043d\u0454\u03b7\u2202\u0454\u044f\u03b9\u0442 \u03b9\u03b7 \u03bd\u03c3\u0142\u03c5\u03c1\u0442\u03b1\u0442\u0454 \u03bd\u0454\u0142\u03b9\u0442 \u0454\u0455\u0455\u0454 \u00a2\u03b9\u0142\u0142\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f\u0454 \u0454\u03c5 \u0192\u03c5g\u03b9\u03b1\u0442 \u03b7\u03c5\u0142\u0142\u03b1 \u03c1\u03b1\u044f\u03b9\u03b1\u0442\u03c5\u044f. \u0454\u03c7\u00a2\u0454\u03c1\u0442\u0454\u03c5\u044f \u0455\u03b9\u03b7\u0442 \u03c3\u00a2\u00a2\u03b1\u0454\u00a2\u03b1\u0442 \u00a2\u03c5\u03c1\u03b9\u2202\u03b1\u0442#", + "{subscriptionPrice} subscription after trial ends. Cancel anytime.": "{subscriptionPrice} s\u00fc\u00dfs\u00e7r\u00efpt\u00ef\u00f6n \u00e4ft\u00e9r tr\u00ef\u00e4l \u00e9nds. \u00c7\u00e4n\u00e7\u00e9l \u00e4n\u00fdt\u00efm\u00e9. \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142\u03c3\u044f \u0455\u03b9\u0442 \u03b1\u043c\u0454\u0442, \u00a2\u03c3\u03b7\u0455\u0454\u00a2\u0442\u0454\u0442\u03c5\u044f \u03b1#", "{team_count} Team": [ "{team_count} T\u00e9\u00e4m \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202#", "{team_count} T\u00e9\u00e4ms \u2c60'\u03c3\u044f\u0454\u043c \u03b9\u03c1\u0455\u03c5\u043c \u2202\u03c3\u0142#" diff --git a/cms/static/js/i18n/es-419/djangojs.js b/cms/static/js/i18n/es-419/djangojs.js index dfcda39301..7b90a66d84 100644 --- a/cms/static/js/i18n/es-419/djangojs.js +++ b/cms/static/js/i18n/es-419/djangojs.js @@ -900,7 +900,6 @@ "Explain if other.": "Si otro, explique.", "Explanation": "Explicaci\u00f3n", "Explicitly Hiding from Students": "Ocultar solo a los estudiantes", - "Explore New Programs": "Explorar programas nuevos", "Explore Programs": "Explorar programas", "Explore courses": "Explorar cursos", "Explore your course!": "Explora tus cursos!", @@ -1653,7 +1652,6 @@ "Select a subject for your support request.": "Elige un tema para tu petici\u00f3n de ayuda.", "Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.": "Seleccione un tiempo disponible para el examen. Si es mayor a 24 horas, escriba la cantidad de tiempo. Puede otorgar a estudiantes individuales un tiempo extra para completar el examen a trav\u00e9s del panel de control de instructor.", "Select all": "Selecionar todo", - "Select employment status": "Seleccionar su estado laboral", "Select fidelity": "Seleccionar fidelidad", "Select language": "Seleccionar lenguaje", "Select one or more groups:": "Selecciones uno o mas grupos:", @@ -1906,7 +1904,6 @@ "The language that team members primarily use to communicate with each other.": "El idioma que usan los miembros del equipo para comunicarse.", "The language used throughout this site. This site is currently available in a limited number of languages. Changing the value of this field will cause the page to refresh.": "El idioma utilizado en este sitio. Actualmente este sitio est\u00e1 disponible en un n\u00famero limitado de idiomas. Cambiar el valor de este campo causar\u00e1 que la p\u00e1gina se actualice.", "The maximum number files that can be saved is ": "El n\u00famero m\u00e1ximo de archivos que se pueden guardar es", - "The maximum number of weeks this subsection can be due in is 18 weeks from the learner enrollment date.": "El n\u00famero m\u00e1ximo de semanas para esta subsecci\u00f3n puede tener fecha de vencimiento en 18 semanas a partir de la fecha de inscripci\u00f3n del estudiante.", "The minimum completion percentage must be a whole number between 0 and 100.": "El porcentaje m\u00ednimo a completar debe ser un n\u00famero entero entre 0 y 100. ", "The minimum grade for course credit is not set.": "La calificaci\u00f3n m\u00ednima para obtener cr\u00e9ditos por el curso no est\u00e1 definida.", "The minimum number of weeks this subsection can be due in is 1 week from the learner enrollment date.": "El m\u00ednimo n\u00famero de semanas para esta subsecci\u00f3n puede tener vencimiento en 1 semana a partir de la fecha de inscripci\u00f3n del estudiante.", @@ -2301,10 +2298,6 @@ "What if I can't see the camera image, or if I can't see my photo do determine which side is visible?": "\u00bfQu\u00e9 sucede si no puedo ver la imagen de la c\u00e1mara, o si no puedo ver mi foto para determinar qu\u00e9 lado es visible?", "What if I have difficulty holding my ID in position relative to the camera?": "\u00bfQu\u00e9 sucede si tengo dificultades para mantener mi identificaci\u00f3n en posici\u00f3n con respecto a la c\u00e1mara?", "What if I have difficulty holding my head in position relative to the camera?": "\u00bfQu\u00e9 sucede si tengo dificultades para mantener la cabeza en posici\u00f3n con respecto a la c\u00e1mara?", - "What industry do you currently work in?": "\u00bfEn qu\u00e9 industria se encuentra trabajando actualmente?", - "What industry do you want to work in?": "\u00bfEn qu\u00e9 industria te gustar\u00eda trabajar?", - "What is the highest level of education that any of your parents or guardians have achieved?": "\u00bfCu\u00e1l es el nivel de educaci\u00f3n m\u00e1s alto que alguno de sus padres o tutores obtuvieron? ", - "What is the highest level of education that you have achieved so far?": "\u00bfCu\u00e1l es el nivel m\u00e1s alto de educaci\u00f3n que obtuvo hasta la fecha?", "What was the total combined income, during the last 12 months, of all members of your family? ": "\u00bfCu\u00e1l fue el ingreso total de todos los miembros de tu familia durante los \u00faltimos 12 meses? ", "What's Your Next Accomplishment?": "\u00bfQu\u00e9 ser\u00e1 tu pr\u00f3ximo logro?", "When learners submit an answer to an assessment, they immediately see whether the answer is correct or incorrect, and the score received.": "Cuando un estudiante ya haya enviado su respuesta en una evaluaci\u00f3n, en seguida ver\u00e1 si esa respuesta es correcta o incorrecta y la puntuaci\u00f3n correspondiente.", diff --git a/cms/static/js/i18n/eu-es/djangojs.js b/cms/static/js/i18n/eu-es/djangojs.js index 2390d3b74b..5c7d6944b8 100644 --- a/cms/static/js/i18n/eu-es/djangojs.js +++ b/cms/static/js/i18n/eu-es/djangojs.js @@ -516,7 +516,6 @@ "Expand Instructions": "Zabaldu argibideak", "Explain if other.": "Azaldu, beste bat bada.", "Explanation": "Azalpena", - "Explore New Programs": "Arakatu programa berriak", "Explore Programs": "Arakatu programak", "Explore your course!": "Arakatu zure ikastaroa!", "February": "Otsaila", diff --git a/cms/static/js/i18n/fa-ir/djangojs.js b/cms/static/js/i18n/fa-ir/djangojs.js index c4ef66085a..6794dbe1ec 100644 --- a/cms/static/js/i18n/fa-ir/djangojs.js +++ b/cms/static/js/i18n/fa-ir/djangojs.js @@ -899,7 +899,6 @@ "Explain if other.": "\u062f\u0631 \u063a\u06cc\u0631 \u0627\u06cc\u0646 \u0635\u0648\u0631\u062a \u062a\u0648\u0636\u06cc\u062d \u062f\u0647\u06cc\u062f.", "Explanation": "\u062a\u0648\u0636\u06cc\u062d", "Explicitly Hiding from Students": "\u0635\u0631\u06cc\u062d\u0627 \u0627\u0632 \u062f\u06cc\u062f \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u0647 \u067e\u0646\u0647\u0627\u0646 \u0634\u062f\u0647 \u0627\u0633\u062a", - "Explore New Programs": "\u06a9\u0627\u0648\u0634 \u0628\u0631\u0646\u0627\u0645\u0647\u200c\u0647\u0627\u06cc \u062c\u062f\u06cc\u062f", "Explore Programs": "\u06a9\u0627\u0648\u0634 \u0628\u0631\u0646\u0627\u0645\u0647\u200c\u0647\u0627", "Explore courses": "\u06a9\u0627\u0648\u0634 \u062f\u0648\u0631\u0647\u200c\u0647\u0627\u06cc \u0622\u0645\u0648\u0632\u0634\u06cc", "Explore your course!": "\u062f\u0648\u0631\u0647\u200c\u062a\u0627\u0646 \u0631\u0627 \u0628\u06cc\u0627\u0628\u06cc\u062f!", @@ -1651,7 +1650,6 @@ "Select a subject for your support request.": "\u0645\u0648\u0636\u0648\u0639\u06cc \u0628\u0631\u0627\u06cc \u062f\u0631\u062e\u0648\u0627\u0633\u062a \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc \u062e\u0648\u062f \u0628\u0631\u06af\u0632\u06cc\u0646\u06cc\u062f.", "Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.": "\u0632\u0645\u0627\u0646\u06cc \u0631\u0627 \u0628\u0647 \u0622\u0632\u0645\u0648\u0646 \u0627\u062e\u062a\u0635\u0627\u0635 \u062f\u0647\u06cc\u062f. \u0627\u06af\u0631 \u0645\u0627\u06cc\u0644\u06cc\u062f \u0628\u06cc\u0634 \u0627\u0632 24 \u0633\u0627\u0639\u062a \u062f\u0631 \u0646\u0638\u0631 \u0628\u06af\u06cc\u0631\u06cc\u062f\u060c \u0645\u06cc\u0632\u0627\u0646 \u0632\u0645\u0627\u0646 \u0631\u0627 \u062a\u0627\u06cc\u067e \u06a9\u0646\u06cc\u062f. \u0634\u0645\u0627 \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u06cc\u062f \u0628\u0631\u0627\u06cc \u062a\u06a9\u0645\u06cc\u0644 \u0627\u0645\u062a\u062d\u0627\u0646 \u0627\u0632 \u0637\u0631\u06cc\u0642 \u067e\u06cc\u0634\u062e\u0648\u0627\u0646 \u0645\u0631\u0628\u06cc\u060c \u0628\u0647 \u062a\u06a9 \u062a\u06a9 \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u06af\u0627\u0646 \u0648\u0642\u062a \u0627\u0636\u0627\u0641\u06cc \u0628\u062f\u0647\u06cc\u062f.", "Select all": "\u0627\u0646\u062a\u062e\u0627\u0628 \u0647\u0645\u0647", - "Select employment status": "\u0633\u0645\u062a \u0627\u062f\u0627\u0631\u06cc", "Select fidelity": "\u0627\u0646\u062a\u062e\u0627\u0628 \u0648\u0641\u0627\u062f\u0627\u0631\u06cc", "Select language": "\u0627\u0646\u062a\u062e\u0627\u0628 \u0632\u0628\u0627\u0646", "Select one or more groups:": "\u06cc\u06a9 \u06cc\u0627 \u062f\u0648 \u06af\u0631\u0648\u0647 \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f:", @@ -1904,7 +1902,6 @@ "The language that team members primarily use to communicate with each other.": "\u0632\u0628\u0627\u0646\u06cc \u06a9\u0647 \u0627\u0639\u0636\u0627\u06cc \u062a\u06cc\u0645 \u0628\u0631\u0627\u06cc \u06af\u0641\u062a\u06af\u0648 \u0628\u0627 \u0647\u0645 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0645\u06cc\u200c\u06a9\u0646\u0646\u062f.", "The language used throughout this site. This site is currently available in a limited number of languages. Changing the value of this field will cause the page to refresh.": "\u0632\u0628\u0627\u0646 \u0645\u0648\u0631\u062f \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u062f\u0631 \u0627\u06cc\u0646 \u0648\u0628\u06af\u0627\u0647. \u0627\u06cc\u0646 \u0648\u0628\u06af\u0627\u0647 \u0627\u06a9\u0646\u0648\u0646 \u062a\u0639\u062f\u0627\u062f \u0645\u062d\u062f\u0648\u062f\u06cc \u0627\u0632 \u0632\u0628\u0627\u0646\u200c\u0647\u0627 \u0631\u0627 \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc \u0645\u06cc\u200c\u06a9\u0646\u062f. \u062a\u063a\u06cc\u06cc\u0631 \u0645\u0642\u0627\u062f\u06cc\u0631 \u062f\u0631 \u0627\u06cc\u0646 \u0642\u0633\u0645\u062a \u0628\u0627\u0639\u062b \u0628\u0627\u0631\u06af\u0630\u0627\u0631\u06cc \u062f\u0648\u0628\u0627\u0631\u06c0 \u0635\u0641\u062d\u0647 \u062e\u0648\u0627\u0647\u062f \u0634\u062f.", "The maximum number files that can be saved is ": "\u062d\u062f\u0627\u06a9\u062b\u0631 \u062a\u0639\u062f\u0627\u062f \u067e\u0631\u0648\u0646\u062f\u0647\u200c\u0647\u0627\u06cc\u06cc \u06a9\u0647 \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0630\u062e\u06cc\u0631\u0647 \u0634\u0648\u062f", - "The maximum number of weeks this subsection can be due in is 18 weeks from the learner enrollment date.": "\u0627\u06cc\u0646 \u0628\u062e\u0634 \u0641\u0631\u0639\u06cc\u060c \u0628\u06cc\u0634\u06cc\u0646\u0647 18 \u0647\u0641\u062a\u0647 \u0627\u0632 \u062a\u0627\u0631\u06cc\u062e \u062b\u0628\u062a\u200c\u0646\u0627\u0645 \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u0647\u060c \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u062f\u0631 \u0646\u0638\u0631 \u06af\u0631\u0641\u062a\u0647 \u0634\u0648\u062f.", "The minimum completion percentage must be a whole number between 0 and 100.": "\u062d\u062f\u0627\u0642\u0644 \u062f\u0631\u0635\u062f \u062a\u06a9\u0645\u06cc\u0644 \u0628\u0627\u06cc\u062f \u06cc\u06a9 \u0639\u062f\u062f \u06a9\u0627\u0645\u0644 \u0628\u06cc\u0646 0 \u062a\u0627 100 \u0628\u0627\u0634\u062f.", "The minimum grade for course credit is not set.": "\u062d\u062f\u0627\u0642\u0644 \u0646\u0645\u0631\u0647 \u0642\u0628\u0648\u0644\u06cc \u0628\u0631\u0627\u06cc \u0627\u06cc\u0646 \u062f\u0631\u0633 \u062b\u0628\u062a \u0646\u0634\u062f\u0647 \u0627\u0633\u062a.", "The minimum number of weeks this subsection can be due in is 1 week from the learner enrollment date.": "\u0627\u06cc\u0646 \u0628\u062e\u0634 \u0641\u0631\u0639\u06cc\u060c \u06a9\u0645\u06cc\u0646\u0647 \u06cc\u06a9 \u0647\u0641\u062a\u0647 \u0627\u0632 \u062a\u0627\u0631\u06cc\u062e \u062b\u0628\u062a\u200c\u0646\u0627\u0645 \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u0647\u060c \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u062f\u0631 \u0646\u0638\u0631 \u06af\u0631\u0641\u062a\u0647 \u0634\u0648\u062f.", @@ -2298,10 +2295,6 @@ "What if I can't see the camera image, or if I can't see my photo do determine which side is visible?": "\u0627\u06af\u0631 \u0646\u062a\u0648\u0627\u0646\u0645 \u062a\u0635\u0648\u06cc\u0631 \u062f\u0648\u0631\u0628\u06cc\u0646 \u0631\u0627 \u0628\u0628\u06cc\u0646\u0645 \u06cc\u0627 \u0627\u06af\u0631 \u0646\u0645\u06cc \u062f\u0627\u0646\u0645 \u062f\u0631 \u062a\u0635\u0648\u06cc\u0631\u0645 \u06a9\u062f\u0627\u0645 \u0637\u0631\u0641 \u0648\u0636\u0648\u062d \u0628\u06cc\u0634\u062a\u0631\u06cc \u062f\u0627\u0631\u062f \u0686\u0647 \u06a9\u0646\u0645\u061f", "What if I have difficulty holding my ID in position relative to the camera?": "\u0627\u06af\u0631 \u062f\u0631 \u0646\u06af\u0647 \u062f\u0627\u0634\u062a\u0646 \u06a9\u0627\u0631\u062a \u0634\u0646\u0627\u0633\u0627\u06cc\u06cc \u062e\u0648\u062f \u0646\u0633\u0628\u062a \u0628\u0647 \u0645\u0648\u0642\u0639\u06cc\u062a \u062f\u0648\u0631\u0628\u06cc\u0646 \u0645\u0634\u06a9\u0644 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u0645\u060c \u0686\u0647 \u06a9\u0646\u0645\u061f", "What if I have difficulty holding my head in position relative to the camera?": "\u0627\u06af\u0631 \u062f\u0631 \u0646\u06af\u0647 \u062f\u0627\u0634\u062a\u0646 \u0633\u0631\u0645 \u062f\u0631 \u0645\u0648\u0642\u0639\u06cc\u062a \u0645\u0637\u0644\u0648\u0628 \u0646\u0633\u0628\u062a \u0628\u0647 \u062f\u0648\u0631\u0628\u06cc\u0646 \u0645\u0634\u06a9\u0644 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u0645\u060c \u0686\u0647 \u06a9\u0646\u0645\u061f", - "What industry do you currently work in?": "\u062d\u0631\u0641\u06c0 \u0634\u0645\u0627 \u0686\u06cc\u0633\u062a\u061f", - "What industry do you want to work in?": "\u0645\u0627\u06cc\u0644\u06cc\u062f \u0648\u0627\u0631\u062f \u06a9\u062f\u0627\u0645 \u062d\u0631\u0641\u0647 \u0634\u0648\u06cc\u062f\u061f", - "What is the highest level of education that any of your parents or guardians have achieved?": "\u0645\u062f\u0627\u0631\u06a9 \u062a\u062d\u0635\u06cc\u0644\u06cc \u0648\u0627\u0644\u062f\u06cc\u0646 \u06cc\u0627 \u0642\u06cc\u0645 \u0634\u0645\u0627 \u06a9\u062f\u0627\u0645\u0633\u062a\u061f", - "What is the highest level of education that you have achieved so far?": "\u0622\u062e\u0631\u06cc\u0646 \u0645\u062f\u0631\u06a9 \u062a\u062d\u0635\u06cc\u0644\u06cc \u0634\u0645\u0627 \u06a9\u062f\u0627\u0645\u0633\u062a\u061f", "What was the total combined income, during the last 12 months, of all members of your family? ": "\u062f\u0631 \u06a9\u0644\u060c \u062f\u0631\u0622\u0645\u062f 12 \u0645\u0627\u0647 \u06af\u0630\u0634\u062a\u0647 \u0647\u0645\u06c0 \u0627\u0639\u0636\u0627\u06cc \u062e\u0627\u0646\u0648\u0627\u062f\u0647 \u0634\u0645\u0627 \u0686\u0642\u062f\u0631 \u0628\u0648\u062f\u0647 \u0627\u0633\u062a\u061f", "What's Your Next Accomplishment?": "\u062f\u0633\u062a\u0627\u0648\u0631\u062f \u0628\u0639\u062f\u06cc \u0634\u0645\u0627 \u0686\u06cc\u0633\u062a\u061f", "When learners submit an answer to an assessment, they immediately see whether the answer is correct or incorrect, and the score received.": "\u0648\u0642\u062a\u06cc \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u06af\u0627\u0646 \u0628\u0647 \u0627\u0631\u0632\u06cc\u0627\u0628\u06cc \u067e\u0627\u0633\u062e \u0645\u06cc\u200c\u062f\u0647\u0646\u062f\u060c \u0628\u0644\u0627\u0641\u0627\u0635\u0644\u0647 \u0645\u06cc\u200c\u0628\u06cc\u0646\u0646\u062f \u06a9\u0647 \u062c\u0648\u0627\u0628 \u062f\u0631\u0633\u062a \u0627\u0633\u062a \u06cc\u0627 \u0646\u0627\u062f\u0631\u0633\u062a\u060c \u0648 \u0646\u0645\u0631\u06c0 \u062f\u0631\u06cc\u0627\u0641\u062a\u06cc \u0631\u0627 \u0645\u06cc\u200c\u06af\u06cc\u0631\u0646\u062f.", diff --git a/cms/static/js/i18n/fa/djangojs.js b/cms/static/js/i18n/fa/djangojs.js index c4ef66085a..6794dbe1ec 100644 --- a/cms/static/js/i18n/fa/djangojs.js +++ b/cms/static/js/i18n/fa/djangojs.js @@ -899,7 +899,6 @@ "Explain if other.": "\u062f\u0631 \u063a\u06cc\u0631 \u0627\u06cc\u0646 \u0635\u0648\u0631\u062a \u062a\u0648\u0636\u06cc\u062d \u062f\u0647\u06cc\u062f.", "Explanation": "\u062a\u0648\u0636\u06cc\u062d", "Explicitly Hiding from Students": "\u0635\u0631\u06cc\u062d\u0627 \u0627\u0632 \u062f\u06cc\u062f \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u0647 \u067e\u0646\u0647\u0627\u0646 \u0634\u062f\u0647 \u0627\u0633\u062a", - "Explore New Programs": "\u06a9\u0627\u0648\u0634 \u0628\u0631\u0646\u0627\u0645\u0647\u200c\u0647\u0627\u06cc \u062c\u062f\u06cc\u062f", "Explore Programs": "\u06a9\u0627\u0648\u0634 \u0628\u0631\u0646\u0627\u0645\u0647\u200c\u0647\u0627", "Explore courses": "\u06a9\u0627\u0648\u0634 \u062f\u0648\u0631\u0647\u200c\u0647\u0627\u06cc \u0622\u0645\u0648\u0632\u0634\u06cc", "Explore your course!": "\u062f\u0648\u0631\u0647\u200c\u062a\u0627\u0646 \u0631\u0627 \u0628\u06cc\u0627\u0628\u06cc\u062f!", @@ -1651,7 +1650,6 @@ "Select a subject for your support request.": "\u0645\u0648\u0636\u0648\u0639\u06cc \u0628\u0631\u0627\u06cc \u062f\u0631\u062e\u0648\u0627\u0633\u062a \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc \u062e\u0648\u062f \u0628\u0631\u06af\u0632\u06cc\u0646\u06cc\u062f.", "Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.": "\u0632\u0645\u0627\u0646\u06cc \u0631\u0627 \u0628\u0647 \u0622\u0632\u0645\u0648\u0646 \u0627\u062e\u062a\u0635\u0627\u0635 \u062f\u0647\u06cc\u062f. \u0627\u06af\u0631 \u0645\u0627\u06cc\u0644\u06cc\u062f \u0628\u06cc\u0634 \u0627\u0632 24 \u0633\u0627\u0639\u062a \u062f\u0631 \u0646\u0638\u0631 \u0628\u06af\u06cc\u0631\u06cc\u062f\u060c \u0645\u06cc\u0632\u0627\u0646 \u0632\u0645\u0627\u0646 \u0631\u0627 \u062a\u0627\u06cc\u067e \u06a9\u0646\u06cc\u062f. \u0634\u0645\u0627 \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u06cc\u062f \u0628\u0631\u0627\u06cc \u062a\u06a9\u0645\u06cc\u0644 \u0627\u0645\u062a\u062d\u0627\u0646 \u0627\u0632 \u0637\u0631\u06cc\u0642 \u067e\u06cc\u0634\u062e\u0648\u0627\u0646 \u0645\u0631\u0628\u06cc\u060c \u0628\u0647 \u062a\u06a9 \u062a\u06a9 \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u06af\u0627\u0646 \u0648\u0642\u062a \u0627\u0636\u0627\u0641\u06cc \u0628\u062f\u0647\u06cc\u062f.", "Select all": "\u0627\u0646\u062a\u062e\u0627\u0628 \u0647\u0645\u0647", - "Select employment status": "\u0633\u0645\u062a \u0627\u062f\u0627\u0631\u06cc", "Select fidelity": "\u0627\u0646\u062a\u062e\u0627\u0628 \u0648\u0641\u0627\u062f\u0627\u0631\u06cc", "Select language": "\u0627\u0646\u062a\u062e\u0627\u0628 \u0632\u0628\u0627\u0646", "Select one or more groups:": "\u06cc\u06a9 \u06cc\u0627 \u062f\u0648 \u06af\u0631\u0648\u0647 \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f:", @@ -1904,7 +1902,6 @@ "The language that team members primarily use to communicate with each other.": "\u0632\u0628\u0627\u0646\u06cc \u06a9\u0647 \u0627\u0639\u0636\u0627\u06cc \u062a\u06cc\u0645 \u0628\u0631\u0627\u06cc \u06af\u0641\u062a\u06af\u0648 \u0628\u0627 \u0647\u0645 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0645\u06cc\u200c\u06a9\u0646\u0646\u062f.", "The language used throughout this site. This site is currently available in a limited number of languages. Changing the value of this field will cause the page to refresh.": "\u0632\u0628\u0627\u0646 \u0645\u0648\u0631\u062f \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u062f\u0631 \u0627\u06cc\u0646 \u0648\u0628\u06af\u0627\u0647. \u0627\u06cc\u0646 \u0648\u0628\u06af\u0627\u0647 \u0627\u06a9\u0646\u0648\u0646 \u062a\u0639\u062f\u0627\u062f \u0645\u062d\u062f\u0648\u062f\u06cc \u0627\u0632 \u0632\u0628\u0627\u0646\u200c\u0647\u0627 \u0631\u0627 \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc \u0645\u06cc\u200c\u06a9\u0646\u062f. \u062a\u063a\u06cc\u06cc\u0631 \u0645\u0642\u0627\u062f\u06cc\u0631 \u062f\u0631 \u0627\u06cc\u0646 \u0642\u0633\u0645\u062a \u0628\u0627\u0639\u062b \u0628\u0627\u0631\u06af\u0630\u0627\u0631\u06cc \u062f\u0648\u0628\u0627\u0631\u06c0 \u0635\u0641\u062d\u0647 \u062e\u0648\u0627\u0647\u062f \u0634\u062f.", "The maximum number files that can be saved is ": "\u062d\u062f\u0627\u06a9\u062b\u0631 \u062a\u0639\u062f\u0627\u062f \u067e\u0631\u0648\u0646\u062f\u0647\u200c\u0647\u0627\u06cc\u06cc \u06a9\u0647 \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0630\u062e\u06cc\u0631\u0647 \u0634\u0648\u062f", - "The maximum number of weeks this subsection can be due in is 18 weeks from the learner enrollment date.": "\u0627\u06cc\u0646 \u0628\u062e\u0634 \u0641\u0631\u0639\u06cc\u060c \u0628\u06cc\u0634\u06cc\u0646\u0647 18 \u0647\u0641\u062a\u0647 \u0627\u0632 \u062a\u0627\u0631\u06cc\u062e \u062b\u0628\u062a\u200c\u0646\u0627\u0645 \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u0647\u060c \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u062f\u0631 \u0646\u0638\u0631 \u06af\u0631\u0641\u062a\u0647 \u0634\u0648\u062f.", "The minimum completion percentage must be a whole number between 0 and 100.": "\u062d\u062f\u0627\u0642\u0644 \u062f\u0631\u0635\u062f \u062a\u06a9\u0645\u06cc\u0644 \u0628\u0627\u06cc\u062f \u06cc\u06a9 \u0639\u062f\u062f \u06a9\u0627\u0645\u0644 \u0628\u06cc\u0646 0 \u062a\u0627 100 \u0628\u0627\u0634\u062f.", "The minimum grade for course credit is not set.": "\u062d\u062f\u0627\u0642\u0644 \u0646\u0645\u0631\u0647 \u0642\u0628\u0648\u0644\u06cc \u0628\u0631\u0627\u06cc \u0627\u06cc\u0646 \u062f\u0631\u0633 \u062b\u0628\u062a \u0646\u0634\u062f\u0647 \u0627\u0633\u062a.", "The minimum number of weeks this subsection can be due in is 1 week from the learner enrollment date.": "\u0627\u06cc\u0646 \u0628\u062e\u0634 \u0641\u0631\u0639\u06cc\u060c \u06a9\u0645\u06cc\u0646\u0647 \u06cc\u06a9 \u0647\u0641\u062a\u0647 \u0627\u0632 \u062a\u0627\u0631\u06cc\u062e \u062b\u0628\u062a\u200c\u0646\u0627\u0645 \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u0647\u060c \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u062f\u0631 \u0646\u0638\u0631 \u06af\u0631\u0641\u062a\u0647 \u0634\u0648\u062f.", @@ -2298,10 +2295,6 @@ "What if I can't see the camera image, or if I can't see my photo do determine which side is visible?": "\u0627\u06af\u0631 \u0646\u062a\u0648\u0627\u0646\u0645 \u062a\u0635\u0648\u06cc\u0631 \u062f\u0648\u0631\u0628\u06cc\u0646 \u0631\u0627 \u0628\u0628\u06cc\u0646\u0645 \u06cc\u0627 \u0627\u06af\u0631 \u0646\u0645\u06cc \u062f\u0627\u0646\u0645 \u062f\u0631 \u062a\u0635\u0648\u06cc\u0631\u0645 \u06a9\u062f\u0627\u0645 \u0637\u0631\u0641 \u0648\u0636\u0648\u062d \u0628\u06cc\u0634\u062a\u0631\u06cc \u062f\u0627\u0631\u062f \u0686\u0647 \u06a9\u0646\u0645\u061f", "What if I have difficulty holding my ID in position relative to the camera?": "\u0627\u06af\u0631 \u062f\u0631 \u0646\u06af\u0647 \u062f\u0627\u0634\u062a\u0646 \u06a9\u0627\u0631\u062a \u0634\u0646\u0627\u0633\u0627\u06cc\u06cc \u062e\u0648\u062f \u0646\u0633\u0628\u062a \u0628\u0647 \u0645\u0648\u0642\u0639\u06cc\u062a \u062f\u0648\u0631\u0628\u06cc\u0646 \u0645\u0634\u06a9\u0644 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u0645\u060c \u0686\u0647 \u06a9\u0646\u0645\u061f", "What if I have difficulty holding my head in position relative to the camera?": "\u0627\u06af\u0631 \u062f\u0631 \u0646\u06af\u0647 \u062f\u0627\u0634\u062a\u0646 \u0633\u0631\u0645 \u062f\u0631 \u0645\u0648\u0642\u0639\u06cc\u062a \u0645\u0637\u0644\u0648\u0628 \u0646\u0633\u0628\u062a \u0628\u0647 \u062f\u0648\u0631\u0628\u06cc\u0646 \u0645\u0634\u06a9\u0644 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u0645\u060c \u0686\u0647 \u06a9\u0646\u0645\u061f", - "What industry do you currently work in?": "\u062d\u0631\u0641\u06c0 \u0634\u0645\u0627 \u0686\u06cc\u0633\u062a\u061f", - "What industry do you want to work in?": "\u0645\u0627\u06cc\u0644\u06cc\u062f \u0648\u0627\u0631\u062f \u06a9\u062f\u0627\u0645 \u062d\u0631\u0641\u0647 \u0634\u0648\u06cc\u062f\u061f", - "What is the highest level of education that any of your parents or guardians have achieved?": "\u0645\u062f\u0627\u0631\u06a9 \u062a\u062d\u0635\u06cc\u0644\u06cc \u0648\u0627\u0644\u062f\u06cc\u0646 \u06cc\u0627 \u0642\u06cc\u0645 \u0634\u0645\u0627 \u06a9\u062f\u0627\u0645\u0633\u062a\u061f", - "What is the highest level of education that you have achieved so far?": "\u0622\u062e\u0631\u06cc\u0646 \u0645\u062f\u0631\u06a9 \u062a\u062d\u0635\u06cc\u0644\u06cc \u0634\u0645\u0627 \u06a9\u062f\u0627\u0645\u0633\u062a\u061f", "What was the total combined income, during the last 12 months, of all members of your family? ": "\u062f\u0631 \u06a9\u0644\u060c \u062f\u0631\u0622\u0645\u062f 12 \u0645\u0627\u0647 \u06af\u0630\u0634\u062a\u0647 \u0647\u0645\u06c0 \u0627\u0639\u0636\u0627\u06cc \u062e\u0627\u0646\u0648\u0627\u062f\u0647 \u0634\u0645\u0627 \u0686\u0642\u062f\u0631 \u0628\u0648\u062f\u0647 \u0627\u0633\u062a\u061f", "What's Your Next Accomplishment?": "\u062f\u0633\u062a\u0627\u0648\u0631\u062f \u0628\u0639\u062f\u06cc \u0634\u0645\u0627 \u0686\u06cc\u0633\u062a\u061f", "When learners submit an answer to an assessment, they immediately see whether the answer is correct or incorrect, and the score received.": "\u0648\u0642\u062a\u06cc \u06cc\u0627\u062f\u06af\u06cc\u0631\u0646\u062f\u06af\u0627\u0646 \u0628\u0647 \u0627\u0631\u0632\u06cc\u0627\u0628\u06cc \u067e\u0627\u0633\u062e \u0645\u06cc\u200c\u062f\u0647\u0646\u062f\u060c \u0628\u0644\u0627\u0641\u0627\u0635\u0644\u0647 \u0645\u06cc\u200c\u0628\u06cc\u0646\u0646\u062f \u06a9\u0647 \u062c\u0648\u0627\u0628 \u062f\u0631\u0633\u062a \u0627\u0633\u062a \u06cc\u0627 \u0646\u0627\u062f\u0631\u0633\u062a\u060c \u0648 \u0646\u0645\u0631\u06c0 \u062f\u0631\u06cc\u0627\u0641\u062a\u06cc \u0631\u0627 \u0645\u06cc\u200c\u06af\u06cc\u0631\u0646\u062f.", diff --git a/cms/static/js/i18n/fr/djangojs.js b/cms/static/js/i18n/fr/djangojs.js index 8170c7dc8a..def3831105 100644 --- a/cms/static/js/i18n/fr/djangojs.js +++ b/cms/static/js/i18n/fr/djangojs.js @@ -889,7 +889,6 @@ "Explain if other.": "Expliquer si autre.", "Explanation": "Explication", "Explicitly Hiding from Students": "Cacher explicitement aux \u00e9tudiants", - "Explore New Programs": "Explorez les nouveaux programmes", "Explore Programs": "Explorez les programmes", "Explore your course!": "Explorez votre cours", "Failed Proctoring": "Echec de l'Examen Surveill\u00e9", @@ -1882,7 +1881,6 @@ "The language that team members primarily use to communicate with each other.": "La langue principalement utilis\u00e9e par les membres de l'\u00e9quipe pour communiquer entre eux.", "The language used throughout this site. This site is currently available in a limited number of languages. Changing the value of this field will cause the page to refresh.": "La langue employ\u00e9e sur ce site. Ce site est disponible uniquement dans certaines langues. Si vous modifiez ce champ, cela provoquera automatiquement une actualisation de la page.", "The maximum number files that can be saved is ": "Le nombre maximum de fichiers pouvant \u00eatre enregistr\u00e9s est", - "The maximum number of weeks this subsection can be due in is 18 weeks from the learner enrollment date.": "Le nombre maximum de semaines dans lesquelles cette sous-section peut \u00eatre due est de 18 semaines \u00e0 compter de la date d'inscription de l'apprenant.", "The minimum completion percentage must be a whole number between 0 and 100.": "Le pourcentage minimum d'ach\u00e8vement doit \u00eatre un nombre entier entre 0 et 100.", "The minimum grade for course credit is not set.": "La note minimale d'obtention d'un cr\u00e9dit pour ce cours n'est pas fix\u00e9e.", "The minimum number of weeks this subsection can be due in is 1 week from the learner enrollment date.": "Le nombre minimum de semaines dans lesquelles cette sous-section peut \u00eatre due est de 1 semaine \u00e0 compter de la date d'inscription de l'apprenant.", diff --git a/cms/static/js/i18n/id/djangojs.js b/cms/static/js/i18n/id/djangojs.js index a2fb3fd5dd..3c9daa12d2 100644 --- a/cms/static/js/i18n/id/djangojs.js +++ b/cms/static/js/i18n/id/djangojs.js @@ -553,7 +553,6 @@ "Expand All": "Buka Semua", "Expand Instructions": "Buka Panduan", "Explanation": "Penjelasan ", - "Explore New Programs": "Telusuri Program Baru", "Explore Programs": "Telusuri Program", "Explore your course!": "Jelajahi kursus anda", "Failed Proctoring": "Gagal dalam proses proktoring", diff --git a/cms/static/js/i18n/it-it/djangojs.js b/cms/static/js/i18n/it-it/djangojs.js index d4d2264703..53caf7abe8 100644 --- a/cms/static/js/i18n/it-it/djangojs.js +++ b/cms/static/js/i18n/it-it/djangojs.js @@ -897,7 +897,6 @@ "Explain if other.": "Se altro, immettere una spiegazione. ", "Explanation": "Spiegazione", "Explicitly Hiding from Students": "Dati nascosti esplicitamente agli studenti", - "Explore New Programs": "Esplora nuovi programmi ", "Explore Programs": "Esplora programmi ", "Explore courses": "Esplora corsi", "Explore your course!": "Esplora il tuo corso! ", @@ -1648,7 +1647,6 @@ "Select a subject for your support request.": "Seleziona un argomento per la tua richiesta di supporto. ", "Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.": "Seleziona il tempo da assegnare per l'esame. Se sono pi\u00f9 di 24 ore, digita la quantit\u00e0 di tempo. Puoi concedere ai singoli studenti pi\u00f9 tempo per completare l'esame tramite la Dashboard dell'Istruttore. ", "Select all": "Seleziona tutto", - "Select employment status": "Seleziona occupazione", "Select fidelity": "Seleziona fedelt\u00e0", "Select language": "Seleziona lingua", "Select one or more groups:": "Seleziona uno o pi\u00f9 gruppi:", @@ -1901,7 +1899,6 @@ "The language that team members primarily use to communicate with each other.": "Lingua utilizzata principalmente dai membri del gruppo per comunicare tra loro.", "The language used throughout this site. This site is currently available in a limited number of languages. Changing the value of this field will cause the page to refresh.": "La lingua utilizzata in questo sito. Questo sito \u00e8 attualmente disponibile in un numero limitato di lingue. La modifica del valore di questo campo provocher\u00e0 l'aggiornamento della pagina. ", "The maximum number files that can be saved is ": "Il numero massimo di file che \u00e8 possibile salvare \u00e8", - "The maximum number of weeks this subsection can be due in is 18 weeks from the learner enrollment date.": "Il numero massimo di settimane in cui questa sottosezione pu\u00f2 essere dovuta \u00e8 di 18 settimane dalla data di iscrizione dello studente.", "The minimum completion percentage must be a whole number between 0 and 100.": "La percentuale di completamento minima deve essere un numero intero compreso tra 0 e 100. ", "The minimum grade for course credit is not set.": "Il livello minimo di accreditamento del corso non \u00e8 impostato.", "The minimum number of weeks this subsection can be due in is 1 week from the learner enrollment date.": "Il numero minimo di settimane in cui questa sottosezione pu\u00f2 essere dovuta \u00e8 di 1 settimana dalla data di iscrizione dello studente.", @@ -2294,10 +2291,6 @@ "What if I can't see the camera image, or if I can't see my photo do determine which side is visible?": "Cosa devo fare se non posso visualizzare l'immagine della fotocamera o se non posso visualizzare la mia foto per determinare il lato visibile? ", "What if I have difficulty holding my ID in position relative to the camera?": "Cosa devo fare se ho difficolt\u00e0 a mantenere il documento di identit\u00e0 in posizione rispetto alla telecamera? ", "What if I have difficulty holding my head in position relative to the camera?": "Cosa devo fare se ho difficolt\u00e0 a mantenere la testa in posizione rispetto alla telecamera? ", - "What industry do you currently work in?": "In quale settore lavori attualmente? ", - "What industry do you want to work in?": "In quale settore vuoi lavorare? ", - "What is the highest level of education that any of your parents or guardians have achieved?": "Qual \u00e8 il livello di istruzione pi\u00f9 elevato raggiunto da uno dei tuoi genitori o tutori? ", - "What is the highest level of education that you have achieved so far?": "Qual \u00e8 il livello di istruzione pi\u00f9 elevato che hai raggiunto finora? ", "What was the total combined income, during the last 12 months, of all members of your family? ": "Qual \u00e8 stato il reddito totale complessivo, negli ultimi 12 mesi, di tutti i membri della tua famiglia? ", "What's Your Next Accomplishment?": "Quale \u00e8 il tuo prossimo risultato? ", "When learners submit an answer to an assessment, they immediately see whether the answer is correct or incorrect, and the score received.": "Quando gli studenti inviano una risposta a una valutazione, vedono immediatamente se la risposta \u00e8 corretta o errata e il punteggio ottenuto. ", diff --git a/cms/static/js/i18n/ja-jp/djangojs.js b/cms/static/js/i18n/ja-jp/djangojs.js index 3074c6f510..901215b8cc 100644 --- a/cms/static/js/i18n/ja-jp/djangojs.js +++ b/cms/static/js/i18n/ja-jp/djangojs.js @@ -473,7 +473,6 @@ "Exception Granted": "\u627f\u8afe\u6e08\u306e\u4f8b\u5916", "Expand All": "\u3059\u3079\u3066\u3092\u8868\u793a", "Explain if other.": "\u305d\u306e\u4ed6\u306e\u5834\u5408\u3001\u8aac\u660e\u3057\u3066\u304f\u3060\u3055\u3044\u3002", - "Explore New Programs": "\u65b0\u898f\u30d7\u30ed\u30b0\u30e9\u30e0\u3092\u63a2\u3059", "Explore Programs": "\u30d7\u30ed\u30b0\u30e9\u30e0\u3092\u63a2\u3059", "Explore your course!": "\u8b1b\u5ea7\u3092\u63a2\u305d\u3046\uff01", "Failed to delete student state for user.": "\u30e6\u30fc\u30b6\u30fc\u306e\u53d7\u8b1b\u8005\u72b6\u6cc1\u306e\u524a\u9664\u306b\u5931\u6557\u3002", diff --git a/cms/static/js/i18n/pl/djangojs.js b/cms/static/js/i18n/pl/djangojs.js index f5ef7d0965..1891e1363a 100644 --- a/cms/static/js/i18n/pl/djangojs.js +++ b/cms/static/js/i18n/pl/djangojs.js @@ -537,7 +537,6 @@ "Exception Granted": "Wyj\u0105tek przyznany", "Expand All": "Rozwi\u0144 wszystko", "Explain if other.": "Inny? Jaki?", - "Explore New Programs": "Odkryj nowe programy", "Explore Programs": "Przegl\u0105daj programy", "Explore your course!": "Przejrzyj sw\u00f3j kurs!", "Failed to delete student state for user.": "Nie uda\u0142o si\u0119 usun\u0105\u0107 stanu studenta dla u\u017cytkownika.", diff --git a/cms/static/js/i18n/pt-pt/djangojs.js b/cms/static/js/i18n/pt-pt/djangojs.js index 0ea4131067..b25ffe6016 100644 --- a/cms/static/js/i18n/pt-pt/djangojs.js +++ b/cms/static/js/i18n/pt-pt/djangojs.js @@ -527,7 +527,10 @@ "Components": "Componentes", "Configure": "Configurar", "Confirm": "Confirmar", + "Confirm Delete Uploaded File": "Confirmar exclus\u00e3o do arquivo enviado", + "Confirm Grade Team Submission": "Confirme o envio da equipe de notas", "Confirm Password": "Confirme Palavra-passe", + "Confirm Submit Response": "Confirmar Submiss\u00e3o da Resposta", "Confirm Timed Transcript": "Confirmar Transcri\u00e7\u00e3o Temporizada", "Congratulations!": "Parab\u00e9ns!", "Congratulations! You are now verified on %(platformName)s!": "Parab\u00e9ns! J\u00e1 est\u00e1 validado/a em %(platformName)s!", @@ -664,6 +667,7 @@ "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.": "A a\u00e7\u00e3o de eliminar um livro did\u00e1tico n\u00e3o pode ser anulada. Assim que eliminado, qualquer refer\u00eancia dele na navega\u00e7\u00e3o pelo seu seu material did\u00e1tico tamb\u00e9m ser\u00e1 eliminada. ", "Deleting this %(item_display_name)s is permanent and cannot be undone.": "Eliminar %(item_display_name)s \u00e9 permanente e n\u00e3o pode ser anulado.", "Deleting this {xblock_type} is permanent and cannot be undone.": "A elimina\u00e7\u00e3o deste {xblock_type} \u00e9 permanente e n\u00e3o pode ser desfeita.", + "Demo the new Grading Experience": "Demonstra\u00e7\u00e3o da nova experi\u00eancia de classifica\u00e7\u00e3o", "Deprecated": "Descontinuado", "Describe ": "Descreva ", "Description": "Descri\u00e7\u00e3o", @@ -821,6 +825,7 @@ "Error starting a task to rescore problem '<%- problem_id %>'. Make sure that the problem identifier is complete and correct.": "Erro ao iniciar a tarefa de reavaliar o problema '<%- problem_id %>'. verifique se o identificador do problema est\u00e1 completo e correto.", "Error starting a task to reset attempts for all students on problem '<%- problem_id %>'. Make sure that the problem identifier is complete and correct.": "Erro na tarefa de reiniciar tentativas para todos os estudantes no problema '<%- problem_id %>'. Certifique-se que o identificador do problema est\u00e1 correto.", "Error when looking up username": "Erro ao procurar o nome de utilizador", + "Error while fetching student data.": "Erro ao ir buscar dados de estudantes.", "Error while generating certificates. Please try again.": "Erro ao gerar certificados. Por favor, tente novamente.", "Error while regenerating certificates. Please try again.": "Erro ao gerar certificados. Por favor, tente novamente.", "Error.": "Erro.", @@ -834,6 +839,7 @@ "Error: User '<%- username %>' has not yet activated their account. Users must create and activate their accounts before they can be assigned a role.": "Erro: O utilizador '<%- username %>'\u00a0ainda n\u00e3o activou a sua conta. Os utilizadores devem criar e ativar as suas contas para lhes ser atribu\u00edda uma fun\u00e7\u00e3o.", "Error: You cannot remove yourself from the Instructor group!": "Erro: N\u00e3o pode sair do grupo de Formadores!", "Errors": "Erros", + "Errors detected on the following tabs: ": "Erros detetados nos seguintes separadores: ", "Errors/Technical Issues": "Erros / Problemas T\u00e9cnicos", "Everyone who has staff privileges in this course": "Qualquer um que tenha privil\u00e9gios de colaborador neste curso", "Exam Types": "Tipos de exame", @@ -845,11 +851,11 @@ "Explain if other.": "Explique se h\u00e1 outro.", "Explanation": "Explica\u00e7\u00e3o", "Explicitly Hiding from Students": "Ocultar Explicitamente dos Estudantes", - "Explore New Programs": "Explorar Novos Programas", "Explore Programs": "Explorar Programas", "Explore courses": "Explorar cursos", "Explore your course!": "Explore o seu curso!", "Failed Proctoring": "Supervis\u00e3o Falhou", + "Failed to clone rubric": "Falha na clonagem da rubrica", "Failed to delete student state for user.": "Falha ao apagar o estado do estudante do utilizador.", "Failed to rescore problem for user.": "Falha na resolu\u00e7\u00e3o do problema do utilizador.", "Failed to rescore problem to improve score for user.": "Falha ao reavaliar o problema para melhorar a pontua\u00e7\u00e3o do utilizador.", @@ -917,6 +923,7 @@ "Government-Issued Photo ID": "Fotografia do Documento de Identifica\u00e7\u00e3o", "Grace period must be specified in HH:MM format.": "O prazo de toler\u00e2ncia deve ser especificado no formato HH:MM. ", "Grade": "Nota", + "Grade Status": "Estado da Classifica\u00e7\u00e3o", "Grade as:": "Classificado como:", "Graded as:": "Classificado como:", "Grading": "Classifica\u00e7\u00e3o", @@ -980,6 +987,7 @@ "Horizontal space": "Espa\u00e7o horizontal", "How to create useful text alternatives.": "Como criar alternativas \u00fateis de texto.", "How to use %(platform_name)s discussions": "Como utilizar os a plataforma de debates na %(platform_name)s", + "However, {overwritten_count} of these students have received a grade through the staff grade override tool already.": "No entanto, {overwritten_count} destes estudantes j\u00e1 receberam uma classifica\u00e7\u00e3o atrav\u00e9s da ferramenta de substitui\u00e7\u00e3o de classifica\u00e7\u00f5es da equipa.", "Hyperlink (Ctrl+L)": "Link (Ctrl+L)", "I am ready to start this timed exam,": "Estou pronto para come\u00e7ar este exame cronometrado,", "I understand and want to reset this onboarding exam.": "Compreendo e quero reiniciar este exame de admiss\u00e3o.", @@ -1329,6 +1337,8 @@ "Path to Signature Image": "Caminho da Imagem de Assinatura", "Pause": "Pausa", "Peer": "Par", + "Peer Responses Received": "Respostas de Colegas Recebidas", + "Peers Assessed": "Colegas avaliados", "Pending Session Review": "Revis\u00e3o de Sess\u00e3o Pendente", "Photo": "Fotografia", "Photo Captured successfully.": "Fotografia capturada com sucesso.", @@ -1415,6 +1425,7 @@ "Previous Uploads table has been updated.": "A tabela dos Carregamentos anteriores foi atualizada.", "Previously published": "Publicado anteriormente", "Print": "Imprimir", + "Problem cloning rubric": "R\u00fabrica de clonagem de problemas", "Processing Re-run Request": "A processar pedido de nova Edi\u00e7\u00e3o do Curso", "Proctored": "Supervisionado", "Proctored Exam": "Exame Supervisionado", @@ -1467,6 +1478,7 @@ "Redo": "Refazer", "Redo (Ctrl+Shift+Z)": "Refazer (Ctrl + Shift + Z)", "Redo (Ctrl+Y)": "Refazer (Ctrl + Y)", + "Refresh": "Atualizar", "Regenerate": "Reformular", "Regenerate the user's certificate": "Reformular o certificado do utilizador", "Register with Institution/Campus Credentials": "Registar-se com credenciais de institui\u00e7\u00e3o/campus", @@ -1544,6 +1556,7 @@ "Rows": "Linhas", "Save": "Guardar", "Save Changes": "Guardar Altera\u00e7\u00f5es", + "Save Unsuccessful": "Sem Sucesso ao Guardar", "Save changes": "Guardar altera\u00e7\u00f5es", "Saved cohort": "Grupo guardado", "Saving": "A guardar", @@ -1578,7 +1591,6 @@ "Select a subject for your support request.": "Selecione um assunto para o seu pedido de apoio.", "Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.": "Selecione o tempo de aloca\u00e7\u00e3o para o exame. Se for mais de 24 horas, digite a quantidade de tempo. Pode conceder tempo adicional aos estudantes para completarem o exame atrav\u00e9s do painel do formador.", "Select all": "Selecionar tudo", - "Select employment status": "Selecione a situa\u00e7\u00e3o profissional", "Select fidelity": "Selecionar fidelidade", "Select language": "Selecionar idioma", "Select one or more groups:": "Selecione um ou mais grupos:", @@ -1679,8 +1691,10 @@ "Split cell": "Dividir c\u00e9lula", "Square": "Quadrado", "Staff": "Equipa", + "Staff Grader": "Graduador de Pessoal", "Staff Only": "Apenas para Elementos da Equipa", "Staff and Learners": "Elementos da Equipa e Estudantes", + "Staff assessment": "Avalia\u00e7\u00e3o da equipa", "Start Date": "Data de In\u00edcio", "Start System Check": "Iniciar Verifica\u00e7\u00e3o do Sistema", "Start generating certificates for all students in this course?": "Iniciar a emiss\u00e3o de certificados para todos os estudantes neste curso?", @@ -1789,6 +1803,7 @@ "Thank you for submitting your financial assistance application for {course_name}! You can expect a response in 2-4 business days.": "Obrigado por submeter o seu pedido de assist\u00eancia financeira para o curso {course_name}! Entraremos em contacto consigo entre 2-4 dias \u00fateis.", "Thank you for submitting your photos. We will review them shortly. You can now sign up for any %(platformName)s course that offers verified certificates. Verification is good for one year. After one year, you must submit photos for verification again.": "Obrigado por enviar as suas fotografias. Iremos rev\u00ea-las em breve. Pode agora inscrever-se em qualquer curso %(platformName)s que ofere\u00e7a certificados verificados. A verifica\u00e7\u00e3o \u00e9 v\u00e1lida por um ano. Ap\u00f3s um ano, dever\u00e1 submeter novamente as suas fotografias para verifica\u00e7\u00e3o.", "Thanks for returning to verify your ID in: {courseName}": "Obrigado por regressar para validar o seu ID em: {courseName}", + "The \"{name}\" problem is configured to require a minimum of {min_grades} peer grades, and asks to review {min_graded} peers.": "O problema \"{name}\" est\u00e1 configurado para exigir um m\u00ednimo de {min_grades} avalia\u00e7\u00f5es de colegas, e pede para rever {min_graded} colegas.", "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "O URL introduzido parece ser um endere\u00e7o de email. Deseja adicionar o prefixo mailto:?", "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?": "O URL introduzido parece ser um link externo. Deseja adicionar o prefixo necess\u00e1rio http: //?", "The assignment type must have a name.": "O tipo de tarefa deve ter um nome.", @@ -1824,7 +1839,7 @@ "The grading process is still running. Refresh the page to see updates.": "O processo de classifica\u00e7\u00e3o ainda est\u00e1 em execu\u00e7\u00e3o. Atualize a p\u00e1gina.", "The language that team members primarily use to communicate with each other.": "O idioma principal que os elementos da equipa utilizam para comunicar uns com os outros.", "The language used throughout this site. This site is currently available in a limited number of languages. Changing the value of this field will cause the page to refresh.": "O idioma usado neste servi\u00e7o. Este servi\u00e7o est\u00e1 atualmente dispon\u00edvel num conjunto limitado de idiomas. Ao alterar o idioma, o sistema ir\u00e1 recarregar a p\u00e1gina atual..", - "The maximum number of weeks this subsection can be due in is 18 weeks from the learner enrollment date.": "O n\u00famero m\u00e1ximo de semanas em que esta subsec\u00e7\u00e3o pode ser terminada \u00e9 de 18 semanas a partir da data de inscri\u00e7\u00e3o do estudante.", + "The maximum number files that can be saved is ": "O n\u00famero m\u00e1ximo de ficheiros que podem ser guardados \u00e9 ", "The minimum completion percentage must be a whole number between 0 and 100.": "A percentagem m\u00ednima de respostas a completar deve ser um n\u00famero inteiro entre 0 e 100.", "The minimum grade for course credit is not set.": "N\u00e3o est\u00e1 definida a classifica\u00e7\u00e3o m\u00ednima de cr\u00e9dito do curso.", "The minimum number of weeks this subsection can be due in is 1 week from the learner enrollment date.": "O n\u00famero m\u00ednimo de semanas em que esta subse\u00e7\u00e3o pode ser terminada \u00e9 de 1 semana a partir da data de inscri\u00e7\u00e3o do aluno.", @@ -1853,6 +1868,7 @@ "The topic \"{topic}\" could not be found.": "O t\u00f3pico \"{topic}\"\u00a0n\u00e3o foi encontrado.", "The weight of all assignments of this type as a percentage of the total grade, for example, 40. Do not include the percent symbol.": "O peso de todas as tarefas deste tipo, como por exemplo a percentagem da classifica\u00e7\u00e3o total, 40. N\u00e3o inclua o s\u00edmbolo de percentagem, %.", "The {cohortGroupName} cohort has been created. You can manually add students to this cohort below.": "O grupo {cohortGroupName} foi criado. Pode adicionar manualmente estudantes a este grupo, em baixo.", + "There are currently {stuck_learners} learners in the waiting state, meaning they have not yet met all requirements for Peer Assessment. ": "Existem atualmente {stuck_learners} estudantes no estado de espera, o que significa que ainda n\u00e3o cumpriram todos os requisitos para a Avalia\u00e7\u00e3o por Colegas. ", "There are invalid keywords in your email. Check the following keywords and try again.": "Existem palavras-chave inv\u00e1lidas no seu email. Verifique as seguintes palavras-chave e tente novamente.", "There are no posts in this topic yet.": "Ainda n\u00e3o existem publica\u00e7\u00f5es neste t\u00f3pico.", "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages.": "Tem havido uma falha na exporta\u00e7\u00e3o para XML de pelo menos um componente. Recomenda-se que v\u00e1 para a p\u00e1gina de edi\u00e7\u00e3o e repare o erro antes de tentar outra exporta\u00e7\u00e3o. Verifique se todos os componentes da p\u00e1gina s\u00e3o v\u00e1lidos e n\u00e3o exibem quaisquer mensagens de erro.", @@ -1896,6 +1912,7 @@ "These users will be enrolled once they register:": "Estes utilizadores ser\u00e3o inscritos assim que se registem:", "This Group Configuration is not in use. Start by adding a content experiment to any Unit via the {linkStart}Course Outline{linkEnd}.": "Esta Configura\u00e7\u00e3o de Grupo n\u00e3o est\u00e1 a ser utilizada. Comece por adicionar um conte\u00fado de exemplo a qualquer unidade atrav\u00e9s do {linkStart}Estrutura Geral do Curso{linkEnd}.", "This Group Configuration is used in:": "Esta Configura\u00e7\u00e3o do Grupo \u00e9 utilizada em:", + "This ORA has already been released. Changes will only affect learners making new submissions. Existing submissions will not be modified by this change.": "Esta Quest\u00e3o de Resposta Aberta j\u00e1 foi divulgada. As altera\u00e7\u00f5es apenas ir\u00e3o afetar os alunos que fizerem novas submiss\u00f5es. As submiss\u00f5es existentes n\u00e3o ser\u00e3o modificadas por esta altera\u00e7\u00e3o.", "This action cannot be undone.": "Esta a\u00e7\u00e3o n\u00e3o pode ser desfeita.", "This action updates the {provider} information for your entire organization.": "Essa a\u00e7\u00e3o atualiza as informa\u00e7\u00f5es de {provider} para toda a organiza\u00e7\u00e3o.", "This assessment could not be submitted.": "N\u00e3o foi poss\u00edvel submeter esta avalia\u00e7\u00e3o.", @@ -1964,6 +1981,7 @@ "Time Allotted (HH:MM):": "Tempo Alocado (HH:MM):", "Time Sent": "Hora de envio", "Time Sent:": "Hora de envio:", + "Time Spent On Current Step": "Tempo Gasto Na Etapa Atual", "Time Zone": "Hora local", "Timed": "Cronometrado", "Timed Exam": "Exame Cronometrado", @@ -2166,6 +2184,7 @@ "View Report": "Ver Relat\u00f3rio", "View Teams in the {topic_name} Topic": "Ver equipas no {topic_name} T\u00f3pico", "View all errors": "Ver todos os erros", + "View and grade responses": "Ver e classificar as respostas", "View child items": "Ver sub itens", "View discussion": "Ver f\u00f3rum", "View my exam": "Ver o meu exame", @@ -2213,10 +2232,6 @@ "What if I can't see the camera image, or if I can't see my photo do determine which side is visible?": "E se n\u00e3o conseguir ver a imagem da c\u00e2mara, ou se eu n\u00e3o conseguir ver a minha fotografia para determinar qual lado \u00e9 vis\u00edvel?", "What if I have difficulty holding my ID in position relative to the camera?": "E se eu tiver dificuldade em manter o meu documento de identifica\u00e7\u00e3o em posi\u00e7\u00e3o em rela\u00e7\u00e3o \u00e0 c\u00e2mara?", "What if I have difficulty holding my head in position relative to the camera?": "E se eu tiver dificuldade em manter minha cabe\u00e7a em posi\u00e7\u00e3o em rela\u00e7\u00e3o \u00e0 c\u00e2mara?", - "What industry do you currently work in?": "Em que sector trabalha atualmente?", - "What industry do you want to work in?": "Em que sector pretende trabalhar?", - "What is the highest level of education that any of your parents or guardians have achieved?": "Qual o n\u00edvel mais alto de educa\u00e7\u00e3o que um dos seus pais ou tutores atingiu?", - "What is the highest level of education that you have achieved so far?": "Qual o n\u00edvel mais alto de educa\u00e7\u00e3o que atingiu at\u00e9 agora?", "What was the total combined income, during the last 12 months, of all members of your family? ": "Qual foi o rendimento total combinado de todos os seus membros da sua fam\u00edlia, durante os \u00faltimos 12 meses? ", "What's Your Next Accomplishment?": "Qual \u00e9 a Sua Pr\u00f3xima Realiza\u00e7\u00e3o?", "When learners submit an answer to an assessment, they immediately see whether the answer is correct or incorrect, and the score received.": "Quando os estudantes submetem uma resposta a uma avalia\u00e7\u00e3o, podem confirmar imediatamente se a resposta est\u00e1 correta ou incorreta e qual a pontua\u00e7\u00e3o recebida.", @@ -2268,6 +2283,7 @@ "You did not submit the required files: {requiredFiles}.": "N\u00e3o submeteu os ficheiros necess\u00e1rios: {requiredFiles}.", "You don't seem to have Flash installed. Get Flash to continue your verification.": "Parece que o Flash n\u00e3o est\u00e1 instalado. Por favor instale o Flash para continuar a sua verifica\u00e7\u00e3o.", "You don't seem to have a webcam connected.": "Parece que n\u00e3o tem nenhuma webcam conectada.", + "You have added a criterion. You will need to select an option for the criterion in the Learner Training step. To do this, click the Assessment Steps tab.": "Adicionou um crit\u00e9rio. Ter\u00e1 de selecionar uma op\u00e7\u00e3o para o crit\u00e9rio na etapa de Forma\u00e7\u00e3o do Estudante. Para o fazer, clique no separador Passos de Avalia\u00e7\u00e3o.", "You have already verified your ID!": "O ID j\u00e1 est\u00e1 validado!", "You have been logged out of your account. Click Okay to log in again now. Click Cancel to stay on this page (you must log in again to save your work).": "Foi desconectado da sua conta. Clique em Ok para iniciar sess\u00e3o novamente agora. Clique em Cancelar para permanecer nesta p\u00e1gina (tem de iniciar sess\u00e3o novamente para guardar o seu trabalho).", "You have deleted a criterion. The criterion has been removed from the example responses in the Learner Training step.": "Eliminou um crit\u00e9rio. O crit\u00e9rio foi removido dos exemplos de resposta da etapa de Forma\u00e7\u00e3o do Estudante.", @@ -2335,6 +2351,7 @@ "Your face is well-lit.": "O rosto est\u00e1 bem iluminado.", "Your file '{file}' has been uploaded. Allow a few minutes for processing.": "O seu ficheiro '{file}' foi carregado. Aguarde uns minutos para concluir o processamento.", "Your file could not be uploaded": "N\u00e3o foi poss\u00edvel enviar o seu ficheiro", + "Your file has been deleted or path has been changed: ": "O seu ficheiro foi eliminado ou o caminho foi alterado: ", "Your file has been deleted.": "O seu ficheiro foi eliminado.", "Your file {filename} is too large (max size: {maxSize}MB).": "O seu ficheiro {filename} excede o tamanho m\u00e1ximo (limite m\u00e1ximo: {maxSize}MB).", "Your import has failed.": "A sua importa\u00e7\u00e3o falhou.", @@ -2413,6 +2430,7 @@ "endorsed %(time_ago)s by %(user)s": "aprovado %(time_ago)s por %(user)s", "enter code here": "introduza o c\u00f3digo aqui", "enter link description here": "inserir descri\u00e7\u00e3o do link aqui", + "error count: ": "contagem de erros: ", "finish later": "terminar mais tarde", "follow this post": "siga esta publica\u00e7\u00e3o", "for": "para", diff --git a/cms/static/js/i18n/rtl/djangojs.js b/cms/static/js/i18n/rtl/djangojs.js index 8db3b29d63..00cd35a615 100644 --- a/cms/static/js/i18n/rtl/djangojs.js +++ b/cms/static/js/i18n/rtl/djangojs.js @@ -43,6 +43,7 @@ " records are not in the correct format and have not been added to the exception list": " \u0279\u01dd\u0254\u00f8\u0279ds \u0250\u0279\u01dd n\u00f8\u0287 \u1d09n \u0287\u0265\u01dd \u0254\u00f8\u0279\u0279\u01dd\u0254\u0287 \u025f\u00f8\u0279\u026f\u0250\u0287 \u0250nd \u0265\u0250\u028c\u01dd n\u00f8\u0287 b\u01dd\u01ddn \u0250dd\u01ddd \u0287\u00f8 \u0287\u0265\u01dd \u01ddx\u0254\u01ddd\u0287\u1d09\u00f8n l\u1d09s\u0287", " to learn": " \u0287\u00f8 l\u01dd\u0250\u0279n", "${listPrice}": "${listPrice}", + "${price}/month {currency}": "${price}/\u026f\u00f8n\u0287\u0265 {currency}", "%(cohort_name)s (%(user_count)s)": "%(cohort_name)s (%(user_count)s)", "%(comments_count)s %(span_sr_open)scomments %(span_close)s": "%(comments_count)s %(span_sr_open)s\u0254\u00f8\u026f\u026f\u01ddn\u0287s %(span_close)s", "%(comments_count)s %(span_sr_open)scomments (%(unread_comments_count)s unread comments)%(span_close)s": "%(comments_count)s %(span_sr_open)s\u0254\u00f8\u026f\u026f\u01ddn\u0287s (%(unread_comments_count)s nn\u0279\u01dd\u0250d \u0254\u00f8\u026f\u026f\u01ddn\u0287s)%(span_close)s", @@ -126,6 +127,7 @@ "Access to this {blockType} is restricted to: {selectedGroupsLabel}": "\u023a\u0254\u0254\u01ddss \u0287\u00f8 \u0287\u0265\u1d09s {blockType} \u1d09s \u0279\u01dds\u0287\u0279\u1d09\u0254\u0287\u01ddd \u0287\u00f8: {selectedGroupsLabel}", "Accomplishments": "\u023a\u0254\u0254\u00f8\u026fdl\u1d09s\u0265\u026f\u01ddn\u0287s", "Accomplishments Pagination": "\u023a\u0254\u0254\u00f8\u026fdl\u1d09s\u0265\u026f\u01ddn\u0287s \u2c63\u0250\u0183\u1d09n\u0250\u0287\u1d09\u00f8n", + "According to our records, you are not enrolled in any courses included in your {programName} program subscription. Enroll in a course from the {i_start}Program Details{i_end} page.": "\u023a\u0254\u0254\u00f8\u0279d\u1d09n\u0183 \u0287\u00f8 \u00f8n\u0279 \u0279\u01dd\u0254\u00f8\u0279ds, \u028e\u00f8n \u0250\u0279\u01dd n\u00f8\u0287 \u01ddn\u0279\u00f8ll\u01ddd \u1d09n \u0250n\u028e \u0254\u00f8n\u0279s\u01dds \u1d09n\u0254lnd\u01ddd \u1d09n \u028e\u00f8n\u0279 {programName} d\u0279\u00f8\u0183\u0279\u0250\u026f snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n. \u0246n\u0279\u00f8ll \u1d09n \u0250 \u0254\u00f8n\u0279s\u01dd \u025f\u0279\u00f8\u026f \u0287\u0265\u01dd {i_start}\u2c63\u0279\u00f8\u0183\u0279\u0250\u026f \u0110\u01dd\u0287\u0250\u1d09ls{i_end} d\u0250\u0183\u01dd.", "Account": "\u023a\u0254\u0254\u00f8nn\u0287", "Account Information": "\u023a\u0254\u0254\u00f8nn\u0287 \u0197n\u025f\u00f8\u0279\u026f\u0250\u0287\u1d09\u00f8n", "Account Not Activated": "\u023a\u0254\u0254\u00f8nn\u0287 N\u00f8\u0287 \u023a\u0254\u0287\u1d09\u028c\u0250\u0287\u01ddd", @@ -140,6 +142,8 @@ "Activating a link in this group will skip to the corresponding point in the video.": "\u023a\u0254\u0287\u1d09\u028c\u0250\u0287\u1d09n\u0183 \u0250 l\u1d09n\u029e \u1d09n \u0287\u0265\u1d09s \u0183\u0279\u00f8nd \u028d\u1d09ll s\u029e\u1d09d \u0287\u00f8 \u0287\u0265\u01dd \u0254\u00f8\u0279\u0279\u01ddsd\u00f8nd\u1d09n\u0183 d\u00f8\u1d09n\u0287 \u1d09n \u0287\u0265\u01dd \u028c\u1d09d\u01dd\u00f8.", "Active": "\u023a\u0254\u0287\u1d09\u028c\u01dd", "Active Uploads": "\u023a\u0254\u0287\u1d09\u028c\u01dd \u0244dl\u00f8\u0250ds", + "Active subscription": "\u023a\u0254\u0287\u1d09\u028c\u01dd snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n", + "Active trial ends {trialEndDate} at {trialEndTime}": "\u023a\u0254\u0287\u1d09\u028c\u01dd \u0287\u0279\u1d09\u0250l \u01ddnds {trialEndDate} \u0250\u0287 {trialEndTime}", "Add": "\u023add", "Add Additional Signatory": "\u023add \u023add\u1d09\u0287\u1d09\u00f8n\u0250l S\u1d09\u0183n\u0250\u0287\u00f8\u0279\u028e", "Add Attachment": "\u023add \u023a\u0287\u0287\u0250\u0254\u0265\u026f\u01ddn\u0287", @@ -641,6 +645,7 @@ "Ends {end}": "\u0246nds {end}", "Engage with posts": "\u0246n\u0183\u0250\u0183\u01dd \u028d\u1d09\u0287\u0265 d\u00f8s\u0287s", "Enroll Now": "\u0246n\u0279\u00f8ll N\u00f8\u028d", + "Enroll in a {programName} course": "\u0246n\u0279\u00f8ll \u1d09n \u0250 {programName} \u0254\u00f8n\u0279s\u01dd", "Enrolled": "\u0246n\u0279\u00f8ll\u01ddd", "Enrolling you in the selected course": "\u0246n\u0279\u00f8ll\u1d09n\u0183 \u028e\u00f8n \u1d09n \u0287\u0265\u01dd s\u01ddl\u01dd\u0254\u0287\u01ddd \u0254\u00f8n\u0279s\u01dd", "Enrollment Date": "\u0246n\u0279\u00f8ll\u026f\u01ddn\u0287 \u0110\u0250\u0287\u01dd", @@ -728,9 +733,10 @@ "Explain if other.": "\u0246xdl\u0250\u1d09n \u1d09\u025f \u00f8\u0287\u0265\u01dd\u0279.", "Explanation": "\u0246xdl\u0250n\u0250\u0287\u1d09\u00f8n", "Explicitly Hiding from Students": "\u0246xdl\u1d09\u0254\u1d09\u0287l\u028e \u0126\u1d09d\u1d09n\u0183 \u025f\u0279\u00f8\u026f S\u0287nd\u01ddn\u0287s", - "Explore New Programs": "\u0246xdl\u00f8\u0279\u01dd N\u01dd\u028d \u2c63\u0279\u00f8\u0183\u0279\u0250\u026fs", "Explore Programs": "\u0246xdl\u00f8\u0279\u01dd \u2c63\u0279\u00f8\u0183\u0279\u0250\u026fs", "Explore courses": "\u0246xdl\u00f8\u0279\u01dd \u0254\u00f8n\u0279s\u01dds", + "Explore new programs": "\u0246xdl\u00f8\u0279\u01dd n\u01dd\u028d d\u0279\u00f8\u0183\u0279\u0250\u026fs", + "Explore subscription options": "\u0246xdl\u00f8\u0279\u01dd snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n \u00f8d\u0287\u1d09\u00f8ns", "Explore your course!": "\u0246xdl\u00f8\u0279\u01dd \u028e\u00f8n\u0279 \u0254\u00f8n\u0279s\u01dd!", "Failed to delete student state for user.": "F\u0250\u1d09l\u01ddd \u0287\u00f8 d\u01ddl\u01dd\u0287\u01dd s\u0287nd\u01ddn\u0287 s\u0287\u0250\u0287\u01dd \u025f\u00f8\u0279 ns\u01dd\u0279.", "Failed to rescore problem for user.": "F\u0250\u1d09l\u01ddd \u0287\u00f8 \u0279\u01dds\u0254\u00f8\u0279\u01dd d\u0279\u00f8bl\u01dd\u026f \u025f\u00f8\u0279 ns\u01dd\u0279.", @@ -867,6 +873,7 @@ "If the unit was previously published and released to learners, any changes you made to the unit when it was hidden will now be visible to learners.": "\u0197\u025f \u0287\u0265\u01dd nn\u1d09\u0287 \u028d\u0250s d\u0279\u01dd\u028c\u1d09\u00f8nsl\u028e dnbl\u1d09s\u0265\u01ddd \u0250nd \u0279\u01ddl\u01dd\u0250s\u01ddd \u0287\u00f8 l\u01dd\u0250\u0279n\u01dd\u0279s, \u0250n\u028e \u0254\u0265\u0250n\u0183\u01dds \u028e\u00f8n \u026f\u0250d\u01dd \u0287\u00f8 \u0287\u0265\u01dd nn\u1d09\u0287 \u028d\u0265\u01ddn \u1d09\u0287 \u028d\u0250s \u0265\u1d09dd\u01ddn \u028d\u1d09ll n\u00f8\u028d b\u01dd \u028c\u1d09s\u1d09bl\u01dd \u0287\u00f8 l\u01dd\u0250\u0279n\u01dd\u0279s.", "If the unit was previously published and released to students, any changes you made to the unit when it was hidden will now be visible to students. Do you want to proceed?": "\u0197\u025f \u0287\u0265\u01dd nn\u1d09\u0287 \u028d\u0250s d\u0279\u01dd\u028c\u1d09\u00f8nsl\u028e dnbl\u1d09s\u0265\u01ddd \u0250nd \u0279\u01ddl\u01dd\u0250s\u01ddd \u0287\u00f8 s\u0287nd\u01ddn\u0287s, \u0250n\u028e \u0254\u0265\u0250n\u0183\u01dds \u028e\u00f8n \u026f\u0250d\u01dd \u0287\u00f8 \u0287\u0265\u01dd nn\u1d09\u0287 \u028d\u0265\u01ddn \u1d09\u0287 \u028d\u0250s \u0265\u1d09dd\u01ddn \u028d\u1d09ll n\u00f8\u028d b\u01dd \u028c\u1d09s\u1d09bl\u01dd \u0287\u00f8 s\u0287nd\u01ddn\u0287s. \u0110\u00f8 \u028e\u00f8n \u028d\u0250n\u0287 \u0287\u00f8 d\u0279\u00f8\u0254\u01dd\u01ddd?", "If you do not yet have an account, use the button below to register.": "\u0197\u025f \u028e\u00f8n d\u00f8 n\u00f8\u0287 \u028e\u01dd\u0287 \u0265\u0250\u028c\u01dd \u0250n \u0250\u0254\u0254\u00f8nn\u0287, ns\u01dd \u0287\u0265\u01dd bn\u0287\u0287\u00f8n b\u01ddl\u00f8\u028d \u0287\u00f8 \u0279\u01dd\u0183\u1d09s\u0287\u01dd\u0279.", + "If you had a subscription previously, your payment history is still available on the {a_start}Orders and subscriptions{a_end} page": "\u0197\u025f \u028e\u00f8n \u0265\u0250d \u0250 snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n d\u0279\u01dd\u028c\u1d09\u00f8nsl\u028e, \u028e\u00f8n\u0279 d\u0250\u028e\u026f\u01ddn\u0287 \u0265\u1d09s\u0287\u00f8\u0279\u028e \u1d09s s\u0287\u1d09ll \u0250\u028c\u0250\u1d09l\u0250bl\u01dd \u00f8n \u0287\u0265\u01dd {a_start}\u00d8\u0279d\u01dd\u0279s \u0250nd snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8ns{a_end} d\u0250\u0183\u01dd", "If you leave, you can no longer post in this team's discussions.Your place will be available to another learner.": "\u0197\u025f \u028e\u00f8n l\u01dd\u0250\u028c\u01dd, \u028e\u00f8n \u0254\u0250n n\u00f8 l\u00f8n\u0183\u01dd\u0279 d\u00f8s\u0287 \u1d09n \u0287\u0265\u1d09s \u0287\u01dd\u0250\u026f's d\u1d09s\u0254nss\u1d09\u00f8ns.\u024e\u00f8n\u0279 dl\u0250\u0254\u01dd \u028d\u1d09ll b\u01dd \u0250\u028c\u0250\u1d09l\u0250bl\u01dd \u0287\u00f8 \u0250n\u00f8\u0287\u0265\u01dd\u0279 l\u01dd\u0250\u0279n\u01dd\u0279.", "If you make significant changes, make sure you notify members of the team before making these changes.": "\u0197\u025f \u028e\u00f8n \u026f\u0250\u029e\u01dd s\u1d09\u0183n\u1d09\u025f\u1d09\u0254\u0250n\u0287 \u0254\u0265\u0250n\u0183\u01dds, \u026f\u0250\u029e\u01dd sn\u0279\u01dd \u028e\u00f8n n\u00f8\u0287\u1d09\u025f\u028e \u026f\u01dd\u026fb\u01dd\u0279s \u00f8\u025f \u0287\u0265\u01dd \u0287\u01dd\u0250\u026f b\u01dd\u025f\u00f8\u0279\u01dd \u026f\u0250\u029e\u1d09n\u0183 \u0287\u0265\u01dds\u01dd \u0254\u0265\u0250n\u0183\u01dds.", "If you make this %(xblockType)s visible to learners, learners will be able to see its content after the release date has passed and you have published the unit. Only units that are explicitly hidden from learners will remain hidden after you clear this option for the %(xblockType)s.": "\u0197\u025f \u028e\u00f8n \u026f\u0250\u029e\u01dd \u0287\u0265\u1d09s %(xblockType)s \u028c\u1d09s\u1d09bl\u01dd \u0287\u00f8 l\u01dd\u0250\u0279n\u01dd\u0279s, l\u01dd\u0250\u0279n\u01dd\u0279s \u028d\u1d09ll b\u01dd \u0250bl\u01dd \u0287\u00f8 s\u01dd\u01dd \u1d09\u0287s \u0254\u00f8n\u0287\u01ddn\u0287 \u0250\u025f\u0287\u01dd\u0279 \u0287\u0265\u01dd \u0279\u01ddl\u01dd\u0250s\u01dd d\u0250\u0287\u01dd \u0265\u0250s d\u0250ss\u01ddd \u0250nd \u028e\u00f8n \u0265\u0250\u028c\u01dd dnbl\u1d09s\u0265\u01ddd \u0287\u0265\u01dd nn\u1d09\u0287. \u00d8nl\u028e nn\u1d09\u0287s \u0287\u0265\u0250\u0287 \u0250\u0279\u01dd \u01ddxdl\u1d09\u0254\u1d09\u0287l\u028e \u0265\u1d09dd\u01ddn \u025f\u0279\u00f8\u026f l\u01dd\u0250\u0279n\u01dd\u0279s \u028d\u1d09ll \u0279\u01dd\u026f\u0250\u1d09n \u0265\u1d09dd\u01ddn \u0250\u025f\u0287\u01dd\u0279 \u028e\u00f8n \u0254l\u01dd\u0250\u0279 \u0287\u0265\u1d09s \u00f8d\u0287\u1d09\u00f8n \u025f\u00f8\u0279 \u0287\u0265\u01dd %(xblockType)s.", @@ -892,6 +899,7 @@ "In Progress": "\u0197n \u2c63\u0279\u00f8\u0183\u0279\u01ddss", "In order to sign in, you need to activate your account.{line_break}{line_break}We just sent an activation link to {strong_start} {email} {strong_end}. If you do not receive an email, check your spam folders or {anchorStart}contact {platform_name} Support{anchorEnd}.": "\u0197n \u00f8\u0279d\u01dd\u0279 \u0287\u00f8 s\u1d09\u0183n \u1d09n, \u028e\u00f8n n\u01dd\u01ddd \u0287\u00f8 \u0250\u0254\u0287\u1d09\u028c\u0250\u0287\u01dd \u028e\u00f8n\u0279 \u0250\u0254\u0254\u00f8nn\u0287.{line_break}{line_break}W\u01dd \u027ens\u0287 s\u01ddn\u0287 \u0250n \u0250\u0254\u0287\u1d09\u028c\u0250\u0287\u1d09\u00f8n l\u1d09n\u029e \u0287\u00f8 {strong_start} {email} {strong_end}. \u0197\u025f \u028e\u00f8n d\u00f8 n\u00f8\u0287 \u0279\u01dd\u0254\u01dd\u1d09\u028c\u01dd \u0250n \u01dd\u026f\u0250\u1d09l, \u0254\u0265\u01dd\u0254\u029e \u028e\u00f8n\u0279 sd\u0250\u026f \u025f\u00f8ld\u01dd\u0279s \u00f8\u0279 {anchorStart}\u0254\u00f8n\u0287\u0250\u0254\u0287 {platform_name} Sndd\u00f8\u0279\u0287{anchorEnd}.", "In the {linkStart}Course Outline{linkEnd}, use this group to control access to a component.": "\u0197n \u0287\u0265\u01dd {linkStart}\u023b\u00f8n\u0279s\u01dd \u00d8n\u0287l\u1d09n\u01dd{linkEnd}, ns\u01dd \u0287\u0265\u1d09s \u0183\u0279\u00f8nd \u0287\u00f8 \u0254\u00f8n\u0287\u0279\u00f8l \u0250\u0254\u0254\u01ddss \u0287\u00f8 \u0250 \u0254\u00f8\u026fd\u00f8n\u01ddn\u0287.", + "Inactive subscription": "\u0197n\u0250\u0254\u0287\u1d09\u028c\u01dd snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n", "Incorrect url format.": "\u0197n\u0254\u00f8\u0279\u0279\u01dd\u0254\u0287 n\u0279l \u025f\u00f8\u0279\u026f\u0250\u0287.", "Increase indent": "\u0197n\u0254\u0279\u01dd\u0250s\u01dd \u1d09nd\u01ddn\u0287", "Individual Exceptions": "\u0197nd\u1d09\u028c\u1d09dn\u0250l \u0246x\u0254\u01ddd\u0287\u1d09\u00f8ns", @@ -952,6 +960,7 @@ "Last published {lastPublishedStart}{publishedOn}{lastPublishedEnd} by {publishedByStart}{publishedBy}{publishedByEnd}": "\u0141\u0250s\u0287 dnbl\u1d09s\u0265\u01ddd {lastPublishedStart}{publishedOn}{lastPublishedEnd} b\u028e {publishedByStart}{publishedBy}{publishedByEnd}", "Last updated": "\u0141\u0250s\u0287 ndd\u0250\u0287\u01ddd", "Learn More": "\u0141\u01dd\u0250\u0279n M\u00f8\u0279\u01dd", + "Learn more": "\u0141\u01dd\u0250\u0279n \u026f\u00f8\u0279\u01dd", "Learn more about {license_name}": "\u0141\u01dd\u0250\u0279n \u026f\u00f8\u0279\u01dd \u0250b\u00f8n\u0287 {license_name}", "Learners are added to this cohort automatically.": "\u0141\u01dd\u0250\u0279n\u01dd\u0279s \u0250\u0279\u01dd \u0250dd\u01ddd \u0287\u00f8 \u0287\u0265\u1d09s \u0254\u00f8\u0265\u00f8\u0279\u0287 \u0250n\u0287\u00f8\u026f\u0250\u0287\u1d09\u0254\u0250ll\u028e.", "Learners are added to this cohort only when you provide their email addresses or usernames on this page.": "\u0141\u01dd\u0250\u0279n\u01dd\u0279s \u0250\u0279\u01dd \u0250dd\u01ddd \u0287\u00f8 \u0287\u0265\u1d09s \u0254\u00f8\u0265\u00f8\u0279\u0287 \u00f8nl\u028e \u028d\u0265\u01ddn \u028e\u00f8n d\u0279\u00f8\u028c\u1d09d\u01dd \u0287\u0265\u01dd\u1d09\u0279 \u01dd\u026f\u0250\u1d09l \u0250dd\u0279\u01ddss\u01dds \u00f8\u0279 ns\u01dd\u0279n\u0250\u026f\u01dds \u00f8n \u0287\u0265\u1d09s d\u0250\u0183\u01dd.", @@ -1020,6 +1029,7 @@ "Making Visible to Students": "M\u0250\u029e\u1d09n\u0183 V\u1d09s\u1d09bl\u01dd \u0287\u00f8 S\u0287nd\u01ddn\u0287s", "Manage": "M\u0250n\u0250\u0183\u01dd", "Manage Learners": "M\u0250n\u0250\u0183\u01dd \u0141\u01dd\u0250\u0279n\u01dd\u0279s", + "Manage my subscription": "M\u0250n\u0250\u0183\u01dd \u026f\u028e snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n", "Manual": "M\u0250nn\u0250l", "Mark Exam As Completed": "M\u0250\u0279\u029e \u0246x\u0250\u026f \u023as \u023b\u00f8\u026fdl\u01dd\u0287\u01ddd", "Mark as Answer": "M\u0250\u0279\u029e \u0250s \u023ans\u028d\u01dd\u0279", @@ -1037,6 +1047,7 @@ "Minimum Completion:": "M\u1d09n\u1d09\u026fn\u026f \u023b\u00f8\u026fdl\u01dd\u0287\u1d09\u00f8n:", "Minimum Score:": "M\u1d09n\u1d09\u026fn\u026f S\u0254\u00f8\u0279\u01dd:", "Module state successfully deleted.": "M\u00f8dnl\u01dd s\u0287\u0250\u0287\u01dd sn\u0254\u0254\u01ddss\u025fnll\u028e d\u01ddl\u01dd\u0287\u01ddd.", + "Monthly program subscriptions {emDash} more flexible, more affordable": "M\u00f8n\u0287\u0265l\u028e d\u0279\u00f8\u0183\u0279\u0250\u026f snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8ns {emDash} \u026f\u00f8\u0279\u01dd \u025fl\u01ddx\u1d09bl\u01dd, \u026f\u00f8\u0279\u01dd \u0250\u025f\u025f\u00f8\u0279d\u0250bl\u01dd", "More": "M\u00f8\u0279\u01dd", "More opportunities for you": "M\u00f8\u0279\u01dd \u00f8dd\u00f8\u0279\u0287nn\u1d09\u0287\u1d09\u01dds \u025f\u00f8\u0279 \u028e\u00f8n", "More sessions coming soon": "M\u00f8\u0279\u01dd s\u01ddss\u1d09\u00f8ns \u0254\u00f8\u026f\u1d09n\u0183 s\u00f8\u00f8n", @@ -1050,6 +1061,7 @@ "Muted": "Mn\u0287\u01ddd", "My Orders": "M\u028e \u00d8\u0279d\u01dd\u0279s", "My Teams": "M\u028e \u0166\u01dd\u0250\u026fs", + "My programs": "M\u028e d\u0279\u00f8\u0183\u0279\u0250\u026fs", "N/A": "N/\u023a", "Name": "N\u0250\u026f\u01dd", "Name ": "N\u0250\u026f\u01dd ", @@ -1060,10 +1072,12 @@ "Navigate up": "N\u0250\u028c\u1d09\u0183\u0250\u0287\u01dd nd", "Need help logging in?": "N\u01dd\u01ddd \u0265\u01ddld l\u00f8\u0183\u0183\u1d09n\u0183 \u1d09n?", "Need help signing in?": "N\u01dd\u01ddd \u0265\u01ddld s\u1d09\u0183n\u1d09n\u0183 \u1d09n?", + "Need help? Check out the {a_start}Learner Help Center{span_start}{icon}{span_end}{a_end} to troubleshoot issues or contact support": "N\u01dd\u01ddd \u0265\u01ddld? \u023b\u0265\u01dd\u0254\u029e \u00f8n\u0287 \u0287\u0265\u01dd {a_start}\u0141\u01dd\u0250\u0279n\u01dd\u0279 \u0126\u01ddld \u023b\u01ddn\u0287\u01dd\u0279{span_start}{icon}{span_end}{a_end} \u0287\u00f8 \u0287\u0279\u00f8nbl\u01dds\u0265\u00f8\u00f8\u0287 \u1d09ssn\u01dds \u00f8\u0279 \u0254\u00f8n\u0287\u0250\u0254\u0287 sndd\u00f8\u0279\u0287", "Need other help signing in?": "N\u01dd\u01ddd \u00f8\u0287\u0265\u01dd\u0279 \u0265\u01ddld s\u1d09\u0183n\u1d09n\u0183 \u1d09n?", "Needs verified certificate ": "N\u01dd\u01ddds \u028c\u01dd\u0279\u1d09\u025f\u1d09\u01ddd \u0254\u01dd\u0279\u0287\u1d09\u025f\u1d09\u0254\u0250\u0287\u01dd ", "Never published": "N\u01dd\u028c\u01dd\u0279 dnbl\u1d09s\u0265\u01ddd", "Never show assessment results": "N\u01dd\u028c\u01dd\u0279 s\u0265\u00f8\u028d \u0250ss\u01ddss\u026f\u01ddn\u0287 \u0279\u01ddsnl\u0287s", + "New": "N\u01dd\u028d", "New %(item_type)s": "N\u01dd\u028d %(item_type)s", "New Address": "N\u01dd\u028d \u023add\u0279\u01ddss", "New Password": "N\u01dd\u028d \u2c63\u0250ss\u028d\u00f8\u0279d", @@ -1111,6 +1125,7 @@ "Notes": "N\u00f8\u0287\u01dds", "Notes hidden": "N\u00f8\u0287\u01dds \u0265\u1d09dd\u01ddn", "Notes visible": "N\u00f8\u0287\u01dds \u028c\u1d09s\u1d09bl\u01dd", + "Now available for many popular programs, affordable monthly subscription pricing can help you manage your budget more effectively. Subscriptions start at {minSubscriptionPrice}/month USD per program, after a 7-day full access free trial. Cancel at any time.": "N\u00f8\u028d \u0250\u028c\u0250\u1d09l\u0250bl\u01dd \u025f\u00f8\u0279 \u026f\u0250n\u028e d\u00f8dnl\u0250\u0279 d\u0279\u00f8\u0183\u0279\u0250\u026fs, \u0250\u025f\u025f\u00f8\u0279d\u0250bl\u01dd \u026f\u00f8n\u0287\u0265l\u028e snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n d\u0279\u1d09\u0254\u1d09n\u0183 \u0254\u0250n \u0265\u01ddld \u028e\u00f8n \u026f\u0250n\u0250\u0183\u01dd \u028e\u00f8n\u0279 bnd\u0183\u01dd\u0287 \u026f\u00f8\u0279\u01dd \u01dd\u025f\u025f\u01dd\u0254\u0287\u1d09\u028c\u01ddl\u028e. Snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8ns s\u0287\u0250\u0279\u0287 \u0250\u0287 {minSubscriptionPrice}/\u026f\u00f8n\u0287\u0265 \u0244S\u0110 d\u01dd\u0279 d\u0279\u00f8\u0183\u0279\u0250\u026f, \u0250\u025f\u0287\u01dd\u0279 \u0250 7-d\u0250\u028e \u025fnll \u0250\u0254\u0254\u01ddss \u025f\u0279\u01dd\u01dd \u0287\u0279\u1d09\u0250l. \u023b\u0250n\u0254\u01ddl \u0250\u0287 \u0250n\u028e \u0287\u1d09\u026f\u01dd.", "Number Sent": "Nn\u026fb\u01dd\u0279 S\u01ddn\u0287", "Number of Droppable": "Nn\u026fb\u01dd\u0279 \u00f8\u025f \u0110\u0279\u00f8dd\u0250bl\u01dd", "Numbered List (Ctrl+O)": "Nn\u026fb\u01dd\u0279\u01ddd \u0141\u1d09s\u0287 (\u023b\u0287\u0279l+\u00d8)", @@ -1166,8 +1181,11 @@ "Paste row after": "\u2c63\u0250s\u0287\u01dd \u0279\u00f8\u028d \u0250\u025f\u0287\u01dd\u0279", "Paste row before": "\u2c63\u0250s\u0287\u01dd \u0279\u00f8\u028d b\u01dd\u025f\u00f8\u0279\u01dd", "Paste your embed code below:": "\u2c63\u0250s\u0287\u01dd \u028e\u00f8n\u0279 \u01dd\u026fb\u01ddd \u0254\u00f8d\u01dd b\u01ddl\u00f8\u028d:", + "Pasting": "\u2c63\u0250s\u0287\u1d09n\u0183", "Path to Signature Image": "\u2c63\u0250\u0287\u0265 \u0287\u00f8 S\u1d09\u0183n\u0250\u0287n\u0279\u01dd \u0197\u026f\u0250\u0183\u01dd", "Pause": "\u2c63\u0250ns\u01dd", + "Pay {subscriptionPrice} after {trialLength}-day free trial": "\u2c63\u0250\u028e {subscriptionPrice} \u0250\u025f\u0287\u01dd\u0279 {trialLength}-d\u0250\u028e \u025f\u0279\u01dd\u01dd \u0287\u0279\u1d09\u0250l", + "Pay {subscriptionPrice} for all courses in this program": "\u2c63\u0250\u028e {subscriptionPrice} \u025f\u00f8\u0279 \u0250ll \u0254\u00f8n\u0279s\u01dds \u1d09n \u0287\u0265\u1d09s d\u0279\u00f8\u0183\u0279\u0250\u026f", "Photo": "\u2c63\u0265\u00f8\u0287\u00f8", "Photo Captured successfully.": "\u2c63\u0265\u00f8\u0287\u00f8 \u023b\u0250d\u0287n\u0279\u01ddd sn\u0254\u0254\u01ddss\u025fnll\u028e.", "Photo ID": "\u2c63\u0265\u00f8\u0287\u00f8 \u0197\u0110", @@ -1338,6 +1356,8 @@ "Reset Your Password": "\u024c\u01dds\u01dd\u0287 \u024e\u00f8n\u0279 \u2c63\u0250ss\u028d\u00f8\u0279d", "Reset attempts for all students on problem '<%- problem_id %>'?": "\u024c\u01dds\u01dd\u0287 \u0250\u0287\u0287\u01dd\u026fd\u0287s \u025f\u00f8\u0279 \u0250ll s\u0287nd\u01ddn\u0287s \u00f8n d\u0279\u00f8bl\u01dd\u026f '<%- problem_id %>'?", "Responses could not be loaded. Refresh the page and try again.": "\u024c\u01ddsd\u00f8ns\u01dds \u0254\u00f8nld n\u00f8\u0287 b\u01dd l\u00f8\u0250d\u01ddd. \u024c\u01dd\u025f\u0279\u01dds\u0265 \u0287\u0265\u01dd d\u0250\u0183\u01dd \u0250nd \u0287\u0279\u028e \u0250\u0183\u0250\u1d09n.", + "Restart my subscription": "\u024c\u01dds\u0287\u0250\u0279\u0287 \u026f\u028e snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n", + "Restart your subscription for {subscriptionPrice}. Your payment history is still available on the {a_start}Orders and subscriptions{a_end} page": "\u024c\u01dds\u0287\u0250\u0279\u0287 \u028e\u00f8n\u0279 snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n \u025f\u00f8\u0279 {subscriptionPrice}. \u024e\u00f8n\u0279 d\u0250\u028e\u026f\u01ddn\u0287 \u0265\u1d09s\u0287\u00f8\u0279\u028e \u1d09s s\u0287\u1d09ll \u0250\u028c\u0250\u1d09l\u0250bl\u01dd \u00f8n \u0287\u0265\u01dd {a_start}\u00d8\u0279d\u01dd\u0279s \u0250nd snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8ns{a_end} d\u0250\u0183\u01dd", "Restore enrollment code": "\u024c\u01dds\u0287\u00f8\u0279\u01dd \u01ddn\u0279\u00f8ll\u026f\u01ddn\u0287 \u0254\u00f8d\u01dd", "Restore last draft": "\u024c\u01dds\u0287\u00f8\u0279\u01dd l\u0250s\u0287 d\u0279\u0250\u025f\u0287", "Restrict access to:": "\u024c\u01dds\u0287\u0279\u1d09\u0254\u0287 \u0250\u0254\u0254\u01ddss \u0287\u00f8:", @@ -1397,10 +1417,14 @@ "Select a subject for your support request.": "S\u01ddl\u01dd\u0254\u0287 \u0250 snb\u027e\u01dd\u0254\u0287 \u025f\u00f8\u0279 \u028e\u00f8n\u0279 sndd\u00f8\u0279\u0287 \u0279\u01ddbn\u01dds\u0287.", "Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.": "S\u01ddl\u01dd\u0254\u0287 \u0250 \u0287\u1d09\u026f\u01dd \u0250ll\u00f8\u0287\u026f\u01ddn\u0287 \u025f\u00f8\u0279 \u0287\u0265\u01dd \u01ddx\u0250\u026f. \u0197\u025f \u1d09\u0287 \u1d09s \u00f8\u028c\u01dd\u0279 24 \u0265\u00f8n\u0279s, \u0287\u028ed\u01dd \u1d09n \u0287\u0265\u01dd \u0250\u026f\u00f8nn\u0287 \u00f8\u025f \u0287\u1d09\u026f\u01dd. \u024e\u00f8n \u0254\u0250n \u0183\u0279\u0250n\u0287 \u1d09nd\u1d09\u028c\u1d09dn\u0250l l\u01dd\u0250\u0279n\u01dd\u0279s \u01ddx\u0287\u0279\u0250 \u0287\u1d09\u026f\u01dd \u0287\u00f8 \u0254\u00f8\u026fdl\u01dd\u0287\u01dd \u0287\u0265\u01dd \u01ddx\u0250\u026f \u0287\u0265\u0279\u00f8n\u0183\u0265 \u0287\u0265\u01dd \u0197ns\u0287\u0279n\u0254\u0287\u00f8\u0279 \u0110\u0250s\u0265b\u00f8\u0250\u0279d.", "Select all": "S\u01ddl\u01dd\u0254\u0287 \u0250ll", - "Select employment status": "S\u01ddl\u01dd\u0254\u0287 \u01dd\u026fdl\u00f8\u028e\u026f\u01ddn\u0287 s\u0287\u0250\u0287ns", + "Select current industry": "S\u01ddl\u01dd\u0254\u0287 \u0254n\u0279\u0279\u01ddn\u0287 \u1d09ndns\u0287\u0279\u028e", "Select fidelity": "S\u01ddl\u01dd\u0254\u0287 \u025f\u1d09d\u01ddl\u1d09\u0287\u028e", + "Select guardian education": "S\u01ddl\u01dd\u0254\u0287 \u0183n\u0250\u0279d\u1d09\u0250n \u01dddn\u0254\u0250\u0287\u1d09\u00f8n", "Select language": "S\u01ddl\u01dd\u0254\u0287 l\u0250n\u0183n\u0250\u0183\u01dd", + "Select level of education": "S\u01ddl\u01dd\u0254\u0287 l\u01dd\u028c\u01ddl \u00f8\u025f \u01dddn\u0254\u0250\u0287\u1d09\u00f8n", + "Select military status": "S\u01ddl\u01dd\u0254\u0287 \u026f\u1d09l\u1d09\u0287\u0250\u0279\u028e s\u0287\u0250\u0287ns", "Select one or more groups:": "S\u01ddl\u01dd\u0254\u0287 \u00f8n\u01dd \u00f8\u0279 \u026f\u00f8\u0279\u01dd \u0183\u0279\u00f8nds:", + "Select prospective industry": "S\u01ddl\u01dd\u0254\u0287 d\u0279\u00f8sd\u01dd\u0254\u0287\u1d09\u028c\u01dd \u1d09ndns\u0287\u0279\u028e", "Select the course-wide discussion topics that you want to divide.": "S\u01ddl\u01dd\u0254\u0287 \u0287\u0265\u01dd \u0254\u00f8n\u0279s\u01dd-\u028d\u1d09d\u01dd d\u1d09s\u0254nss\u1d09\u00f8n \u0287\u00f8d\u1d09\u0254s \u0287\u0265\u0250\u0287 \u028e\u00f8n \u028d\u0250n\u0287 \u0287\u00f8 d\u1d09\u028c\u1d09d\u01dd.", "Select the time zone for displaying course dates. If you do not specify a time zone, course dates, including assignment deadlines, will be displayed in your browser's local time zone.": "S\u01ddl\u01dd\u0254\u0287 \u0287\u0265\u01dd \u0287\u1d09\u026f\u01dd z\u00f8n\u01dd \u025f\u00f8\u0279 d\u1d09sdl\u0250\u028e\u1d09n\u0183 \u0254\u00f8n\u0279s\u01dd d\u0250\u0287\u01dds. \u0197\u025f \u028e\u00f8n d\u00f8 n\u00f8\u0287 sd\u01dd\u0254\u1d09\u025f\u028e \u0250 \u0287\u1d09\u026f\u01dd z\u00f8n\u01dd, \u0254\u00f8n\u0279s\u01dd d\u0250\u0287\u01dds, \u1d09n\u0254lnd\u1d09n\u0183 \u0250ss\u1d09\u0183n\u026f\u01ddn\u0287 d\u01dd\u0250dl\u1d09n\u01dds, \u028d\u1d09ll b\u01dd d\u1d09sdl\u0250\u028e\u01ddd \u1d09n \u028e\u00f8n\u0279 b\u0279\u00f8\u028ds\u01dd\u0279's l\u00f8\u0254\u0250l \u0287\u1d09\u026f\u01dd z\u00f8n\u01dd.", "Select turnaround": "S\u01ddl\u01dd\u0254\u0287 \u0287n\u0279n\u0250\u0279\u00f8nnd", @@ -1502,6 +1526,7 @@ "Start regenerating certificates for students in this course?": "S\u0287\u0250\u0279\u0287 \u0279\u01dd\u0183\u01ddn\u01dd\u0279\u0250\u0287\u1d09n\u0183 \u0254\u01dd\u0279\u0287\u1d09\u025f\u1d09\u0254\u0250\u0287\u01dds \u025f\u00f8\u0279 s\u0287nd\u01ddn\u0287s \u1d09n \u0287\u0265\u1d09s \u0254\u00f8n\u0279s\u01dd?", "Start search": "S\u0287\u0250\u0279\u0287 s\u01dd\u0250\u0279\u0254\u0265", "Start working toward your next learning goal.": "S\u0287\u0250\u0279\u0287 \u028d\u00f8\u0279\u029e\u1d09n\u0183 \u0287\u00f8\u028d\u0250\u0279d \u028e\u00f8n\u0279 n\u01ddx\u0287 l\u01dd\u0250\u0279n\u1d09n\u0183 \u0183\u00f8\u0250l.", + "Start {trialLength}-day free trial": "S\u0287\u0250\u0279\u0287 {trialLength}-d\u0250\u028e \u025f\u0279\u01dd\u01dd \u0287\u0279\u1d09\u0250l", "Started entrance exam rescore task for student '{student_id}'. Click the 'Show Task Status' button to see the status of the task.": "S\u0287\u0250\u0279\u0287\u01ddd \u01ddn\u0287\u0279\u0250n\u0254\u01dd \u01ddx\u0250\u026f \u0279\u01dds\u0254\u00f8\u0279\u01dd \u0287\u0250s\u029e \u025f\u00f8\u0279 s\u0287nd\u01ddn\u0287 '{student_id}'. \u023bl\u1d09\u0254\u029e \u0287\u0265\u01dd 'S\u0265\u00f8\u028d \u0166\u0250s\u029e S\u0287\u0250\u0287ns' bn\u0287\u0287\u00f8n \u0287\u00f8 s\u01dd\u01dd \u0287\u0265\u01dd s\u0287\u0250\u0287ns \u00f8\u025f \u0287\u0265\u01dd \u0287\u0250s\u029e.", "Started rescore problem task for problem '<%- problem_id %>' and student '<%- student_id %>'. Click the 'Show Task Status' button to see the status of the task.": "S\u0287\u0250\u0279\u0287\u01ddd \u0279\u01dds\u0254\u00f8\u0279\u01dd d\u0279\u00f8bl\u01dd\u026f \u0287\u0250s\u029e \u025f\u00f8\u0279 d\u0279\u00f8bl\u01dd\u026f '<%- problem_id %>' \u0250nd s\u0287nd\u01ddn\u0287 '<%- student_id %>'. \u023bl\u1d09\u0254\u029e \u0287\u0265\u01dd 'S\u0265\u00f8\u028d \u0166\u0250s\u029e S\u0287\u0250\u0287ns' bn\u0287\u0287\u00f8n \u0287\u00f8 s\u01dd\u01dd \u0287\u0265\u01dd s\u0287\u0250\u0287ns \u00f8\u025f \u0287\u0265\u01dd \u0287\u0250s\u029e.", "Started task to override the score for problem '<%- problem_id %>' and student '<%- student_id %>'. Click the 'Show Task Status' button to see the status of the task.": "S\u0287\u0250\u0279\u0287\u01ddd \u0287\u0250s\u029e \u0287\u00f8 \u00f8\u028c\u01dd\u0279\u0279\u1d09d\u01dd \u0287\u0265\u01dd s\u0254\u00f8\u0279\u01dd \u025f\u00f8\u0279 d\u0279\u00f8bl\u01dd\u026f '<%- problem_id %>' \u0250nd s\u0287nd\u01ddn\u0287 '<%- student_id %>'. \u023bl\u1d09\u0254\u029e \u0287\u0265\u01dd 'S\u0265\u00f8\u028d \u0166\u0250s\u029e S\u0287\u0250\u0287ns' bn\u0287\u0287\u00f8n \u0287\u00f8 s\u01dd\u01dd \u0287\u0265\u01dd s\u0287\u0250\u0287ns \u00f8\u025f \u0287\u0265\u01dd \u0287\u0250s\u029e.", @@ -1528,7 +1553,12 @@ "Submit Application": "Snb\u026f\u1d09\u0287 \u023addl\u1d09\u0254\u0250\u0287\u1d09\u00f8n", "Submit enrollment change": "Snb\u026f\u1d09\u0287 \u01ddn\u0279\u00f8ll\u026f\u01ddn\u0287 \u0254\u0265\u0250n\u0183\u01dd", "Submitted": "Snb\u026f\u1d09\u0287\u0287\u01ddd", + "Subscribed": "Snbs\u0254\u0279\u1d09b\u01ddd", "Subscript": "Snbs\u0254\u0279\u1d09d\u0287", + "Subscription trial expires in {remainingDays} day": [ + "Snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n \u0287\u0279\u1d09\u0250l \u01ddxd\u1d09\u0279\u01dds \u1d09n {remainingDays} d\u0250\u028e", + "Snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n \u0287\u0279\u1d09\u0250l \u01ddxd\u1d09\u0279\u01dds \u1d09n {remainingDays} d\u0250\u028es" + ], "Subsection": "Snbs\u01dd\u0254\u0287\u1d09\u00f8n", "Subsection Visibility": "Snbs\u01dd\u0254\u0287\u1d09\u00f8n V\u1d09s\u1d09b\u1d09l\u1d09\u0287\u028e", "Subsection is hidden after course end date": "Snbs\u01dd\u0254\u0287\u1d09\u00f8n \u1d09s \u0265\u1d09dd\u01ddn \u0250\u025f\u0287\u01dd\u0279 \u0254\u00f8n\u0279s\u01dd \u01ddnd d\u0250\u0287\u01dd", @@ -1596,6 +1626,7 @@ "Thank you for submitting a request! We appreciate your patience while we work to review your request.": "\u0166\u0265\u0250n\u029e \u028e\u00f8n \u025f\u00f8\u0279 snb\u026f\u1d09\u0287\u0287\u1d09n\u0183 \u0250 \u0279\u01ddbn\u01dds\u0287! W\u01dd \u0250dd\u0279\u01dd\u0254\u1d09\u0250\u0287\u01dd \u028e\u00f8n\u0279 d\u0250\u0287\u1d09\u01ddn\u0254\u01dd \u028d\u0265\u1d09l\u01dd \u028d\u01dd \u028d\u00f8\u0279\u029e \u0287\u00f8 \u0279\u01dd\u028c\u1d09\u01dd\u028d \u028e\u00f8n\u0279 \u0279\u01ddbn\u01dds\u0287.", "Thank you for submitting your financial assistance application for {course_name}! You can expect a response in 2-4 business days.": "\u0166\u0265\u0250n\u029e \u028e\u00f8n \u025f\u00f8\u0279 snb\u026f\u1d09\u0287\u0287\u1d09n\u0183 \u028e\u00f8n\u0279 \u025f\u1d09n\u0250n\u0254\u1d09\u0250l \u0250ss\u1d09s\u0287\u0250n\u0254\u01dd \u0250ddl\u1d09\u0254\u0250\u0287\u1d09\u00f8n \u025f\u00f8\u0279 {course_name}! \u024e\u00f8n \u0254\u0250n \u01ddxd\u01dd\u0254\u0287 \u0250 \u0279\u01ddsd\u00f8ns\u01dd \u1d09n 2-4 bns\u1d09n\u01ddss d\u0250\u028es.", "Thank you for submitting your photos. We will review them shortly. You can now sign up for any %(platformName)s course that offers verified certificates. Verification is good for one year. After one year, you must submit photos for verification again.": "\u0166\u0265\u0250n\u029e \u028e\u00f8n \u025f\u00f8\u0279 snb\u026f\u1d09\u0287\u0287\u1d09n\u0183 \u028e\u00f8n\u0279 d\u0265\u00f8\u0287\u00f8s. W\u01dd \u028d\u1d09ll \u0279\u01dd\u028c\u1d09\u01dd\u028d \u0287\u0265\u01dd\u026f s\u0265\u00f8\u0279\u0287l\u028e. \u024e\u00f8n \u0254\u0250n n\u00f8\u028d s\u1d09\u0183n nd \u025f\u00f8\u0279 \u0250n\u028e %(platformName)s \u0254\u00f8n\u0279s\u01dd \u0287\u0265\u0250\u0287 \u00f8\u025f\u025f\u01dd\u0279s \u028c\u01dd\u0279\u1d09\u025f\u1d09\u01ddd \u0254\u01dd\u0279\u0287\u1d09\u025f\u1d09\u0254\u0250\u0287\u01dds. V\u01dd\u0279\u1d09\u025f\u1d09\u0254\u0250\u0287\u1d09\u00f8n \u1d09s \u0183\u00f8\u00f8d \u025f\u00f8\u0279 \u00f8n\u01dd \u028e\u01dd\u0250\u0279. \u023a\u025f\u0287\u01dd\u0279 \u00f8n\u01dd \u028e\u01dd\u0250\u0279, \u028e\u00f8n \u026fns\u0287 snb\u026f\u1d09\u0287 d\u0265\u00f8\u0287\u00f8s \u025f\u00f8\u0279 \u028c\u01dd\u0279\u1d09\u025f\u1d09\u0254\u0250\u0287\u1d09\u00f8n \u0250\u0183\u0250\u1d09n.", + "Thank you! You\u2019re helping make edX better for everyone.": "\u0166\u0265\u0250n\u029e \u028e\u00f8n! \u024e\u00f8n\u2019\u0279\u01dd \u0265\u01ddld\u1d09n\u0183 \u026f\u0250\u029e\u01dd \u01dddX b\u01dd\u0287\u0287\u01dd\u0279 \u025f\u00f8\u0279 \u01dd\u028c\u01dd\u0279\u028e\u00f8n\u01dd.", "Thanks for returning to verify your ID in: {courseName}": "\u0166\u0265\u0250n\u029es \u025f\u00f8\u0279 \u0279\u01dd\u0287n\u0279n\u1d09n\u0183 \u0287\u00f8 \u028c\u01dd\u0279\u1d09\u025f\u028e \u028e\u00f8n\u0279 \u0197\u0110 \u1d09n: {courseName}", "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u0166\u0265\u01dd \u0244\u024c\u0141 \u028e\u00f8n \u01ddn\u0287\u01dd\u0279\u01ddd s\u01dd\u01dd\u026fs \u0287\u00f8 b\u01dd \u0250n \u01dd\u026f\u0250\u1d09l \u0250dd\u0279\u01ddss. \u0110\u00f8 \u028e\u00f8n \u028d\u0250n\u0287 \u0287\u00f8 \u0250dd \u0287\u0265\u01dd \u0279\u01ddbn\u1d09\u0279\u01ddd \u026f\u0250\u1d09l\u0287\u00f8: d\u0279\u01dd\u025f\u1d09x?", "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?": "\u0166\u0265\u01dd \u0244\u024c\u0141 \u028e\u00f8n \u01ddn\u0287\u01dd\u0279\u01ddd s\u01dd\u01dd\u026fs \u0287\u00f8 b\u01dd \u0250n \u01ddx\u0287\u01dd\u0279n\u0250l l\u1d09n\u029e. \u0110\u00f8 \u028e\u00f8n \u028d\u0250n\u0287 \u0287\u00f8 \u0250dd \u0287\u0265\u01dd \u0279\u01ddbn\u1d09\u0279\u01ddd \u0265\u0287\u0287d:// d\u0279\u01dd\u025f\u1d09x?", @@ -1631,7 +1662,6 @@ "The grading process is still running. Refresh the page to see updates.": "\u0166\u0265\u01dd \u0183\u0279\u0250d\u1d09n\u0183 d\u0279\u00f8\u0254\u01ddss \u1d09s s\u0287\u1d09ll \u0279nnn\u1d09n\u0183. \u024c\u01dd\u025f\u0279\u01dds\u0265 \u0287\u0265\u01dd d\u0250\u0183\u01dd \u0287\u00f8 s\u01dd\u01dd ndd\u0250\u0287\u01dds.", "The language that team members primarily use to communicate with each other.": "\u0166\u0265\u01dd l\u0250n\u0183n\u0250\u0183\u01dd \u0287\u0265\u0250\u0287 \u0287\u01dd\u0250\u026f \u026f\u01dd\u026fb\u01dd\u0279s d\u0279\u1d09\u026f\u0250\u0279\u1d09l\u028e ns\u01dd \u0287\u00f8 \u0254\u00f8\u026f\u026fnn\u1d09\u0254\u0250\u0287\u01dd \u028d\u1d09\u0287\u0265 \u01dd\u0250\u0254\u0265 \u00f8\u0287\u0265\u01dd\u0279.", "The language used throughout this site. This site is currently available in a limited number of languages. Changing the value of this field will cause the page to refresh.": "\u0166\u0265\u01dd l\u0250n\u0183n\u0250\u0183\u01dd ns\u01ddd \u0287\u0265\u0279\u00f8n\u0183\u0265\u00f8n\u0287 \u0287\u0265\u1d09s s\u1d09\u0287\u01dd. \u0166\u0265\u1d09s s\u1d09\u0287\u01dd \u1d09s \u0254n\u0279\u0279\u01ddn\u0287l\u028e \u0250\u028c\u0250\u1d09l\u0250bl\u01dd \u1d09n \u0250 l\u1d09\u026f\u1d09\u0287\u01ddd nn\u026fb\u01dd\u0279 \u00f8\u025f l\u0250n\u0183n\u0250\u0183\u01dds. \u023b\u0265\u0250n\u0183\u1d09n\u0183 \u0287\u0265\u01dd \u028c\u0250ln\u01dd \u00f8\u025f \u0287\u0265\u1d09s \u025f\u1d09\u01ddld \u028d\u1d09ll \u0254\u0250ns\u01dd \u0287\u0265\u01dd d\u0250\u0183\u01dd \u0287\u00f8 \u0279\u01dd\u025f\u0279\u01dds\u0265.", - "The maximum number of weeks this subsection can be due in is 18 weeks from the learner enrollment date.": "\u0166\u0265\u01dd \u026f\u0250x\u1d09\u026fn\u026f nn\u026fb\u01dd\u0279 \u00f8\u025f \u028d\u01dd\u01dd\u029es \u0287\u0265\u1d09s snbs\u01dd\u0254\u0287\u1d09\u00f8n \u0254\u0250n b\u01dd dn\u01dd \u1d09n \u1d09s 18 \u028d\u01dd\u01dd\u029es \u025f\u0279\u00f8\u026f \u0287\u0265\u01dd l\u01dd\u0250\u0279n\u01dd\u0279 \u01ddn\u0279\u00f8ll\u026f\u01ddn\u0287 d\u0250\u0287\u01dd.", "The minimum completion percentage must be a whole number between 0 and 100.": "\u0166\u0265\u01dd \u026f\u1d09n\u1d09\u026fn\u026f \u0254\u00f8\u026fdl\u01dd\u0287\u1d09\u00f8n d\u01dd\u0279\u0254\u01ddn\u0287\u0250\u0183\u01dd \u026fns\u0287 b\u01dd \u0250 \u028d\u0265\u00f8l\u01dd nn\u026fb\u01dd\u0279 b\u01dd\u0287\u028d\u01dd\u01ddn 0 \u0250nd 100.", "The minimum grade for course credit is not set.": "\u0166\u0265\u01dd \u026f\u1d09n\u1d09\u026fn\u026f \u0183\u0279\u0250d\u01dd \u025f\u00f8\u0279 \u0254\u00f8n\u0279s\u01dd \u0254\u0279\u01ddd\u1d09\u0287 \u1d09s n\u00f8\u0287 s\u01dd\u0287.", "The minimum number of weeks this subsection can be due in is 1 week from the learner enrollment date.": "\u0166\u0265\u01dd \u026f\u1d09n\u1d09\u026fn\u026f nn\u026fb\u01dd\u0279 \u00f8\u025f \u028d\u01dd\u01dd\u029es \u0287\u0265\u1d09s snbs\u01dd\u0254\u0287\u1d09\u00f8n \u0254\u0250n b\u01dd dn\u01dd \u1d09n \u1d09s 1 \u028d\u01dd\u01dd\u029e \u025f\u0279\u00f8\u026f \u0287\u0265\u01dd l\u01dd\u0250\u0279n\u01dd\u0279 \u01ddn\u0279\u00f8ll\u026f\u01ddn\u0287 d\u0250\u0287\u01dd.", @@ -1799,6 +1829,7 @@ "Transcript Turnaround": "\u0166\u0279\u0250ns\u0254\u0279\u1d09d\u0287 \u0166n\u0279n\u0250\u0279\u00f8nnd", "Transcript will be displayed when you start playing the video.": "\u0166\u0279\u0250ns\u0254\u0279\u1d09d\u0287 \u028d\u1d09ll b\u01dd d\u1d09sdl\u0250\u028e\u01ddd \u028d\u0265\u01ddn \u028e\u00f8n s\u0287\u0250\u0279\u0287 dl\u0250\u028e\u1d09n\u0183 \u0287\u0265\u01dd \u028c\u1d09d\u01dd\u00f8.", "Transcripts": "\u0166\u0279\u0250ns\u0254\u0279\u1d09d\u0287s", + "Trial subscription": "\u0166\u0279\u1d09\u0250l snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n", "Try the transaction again in a few minutes.": "\u0166\u0279\u028e \u0287\u0265\u01dd \u0287\u0279\u0250ns\u0250\u0254\u0287\u1d09\u00f8n \u0250\u0183\u0250\u1d09n \u1d09n \u0250 \u025f\u01dd\u028d \u026f\u1d09nn\u0287\u01dds.", "Try using a different browser, such as Google Chrome.": "\u0166\u0279\u028e ns\u1d09n\u0183 \u0250 d\u1d09\u025f\u025f\u01dd\u0279\u01ddn\u0287 b\u0279\u00f8\u028ds\u01dd\u0279, sn\u0254\u0265 \u0250s \u01e4\u00f8\u00f8\u0183l\u01dd \u023b\u0265\u0279\u00f8\u026f\u01dd.", "Turn off transcripts": "\u0166n\u0279n \u00f8\u025f\u025f \u0287\u0279\u0250ns\u0254\u0279\u1d09d\u0287s", @@ -1831,6 +1862,7 @@ "Unlink This Account": "\u0244nl\u1d09n\u029e \u0166\u0265\u1d09s \u023a\u0254\u0254\u00f8nn\u0287", "Unlink your {accountName} account": "\u0244nl\u1d09n\u029e \u028e\u00f8n\u0279 {accountName} \u0250\u0254\u0254\u00f8nn\u0287", "Unlinking": "\u0244nl\u1d09n\u029e\u1d09n\u0183", + "Unlock verified access to all courses for {subscriptionPrice}. Cancel anytime.": "\u0244nl\u00f8\u0254\u029e \u028c\u01dd\u0279\u1d09\u025f\u1d09\u01ddd \u0250\u0254\u0254\u01ddss \u0287\u00f8 \u0250ll \u0254\u00f8n\u0279s\u01dds \u025f\u00f8\u0279 {subscriptionPrice}. \u023b\u0250n\u0254\u01ddl \u0250n\u028e\u0287\u1d09\u026f\u01dd.", "Unmark as Answer": "\u0244n\u026f\u0250\u0279\u029e \u0250s \u023ans\u028d\u01dd\u0279", "Unmute": "\u0244n\u026fn\u0287\u01dd", "Unpin": "\u0244nd\u1d09n", @@ -1851,6 +1883,7 @@ "Upgrade Deadline": "\u0244d\u0183\u0279\u0250d\u01dd \u0110\u01dd\u0250dl\u1d09n\u01dd", "Upgrade to Verified": "\u0244d\u0183\u0279\u0250d\u01dd \u0287\u00f8 V\u01dd\u0279\u1d09\u025f\u1d09\u01ddd", "Upgrade to a Verified Certificate for {courseName}": "\u0244d\u0183\u0279\u0250d\u01dd \u0287\u00f8 \u0250 V\u01dd\u0279\u1d09\u025f\u1d09\u01ddd \u023b\u01dd\u0279\u0287\u1d09\u025f\u1d09\u0254\u0250\u0287\u01dd \u025f\u00f8\u0279 {courseName}", + "Upgrade with a subscription": "\u0244d\u0183\u0279\u0250d\u01dd \u028d\u1d09\u0287\u0265 \u0250 snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n", "Upload": "\u0244dl\u00f8\u0250d", "Upload %(file_name)s": "\u0639\u062d\u0645\u062e\u0634\u064a %(file_name)s", "Upload File": "\u0244dl\u00f8\u0250d F\u1d09l\u01dd", @@ -1929,6 +1962,7 @@ "Very low": "V\u01dd\u0279\u028e l\u00f8\u028d", "Video Capture Error": "V\u1d09d\u01dd\u00f8 \u023b\u0250d\u0287n\u0279\u01dd \u0246\u0279\u0279\u00f8\u0279", "Video ID": "V\u1d09d\u01dd\u00f8 \u0197\u0110", + "Video Sharing": "V\u1d09d\u01dd\u00f8 S\u0265\u0250\u0279\u1d09n\u0183", "Video Source Language": "V\u1d09d\u01dd\u00f8 S\u00f8n\u0279\u0254\u01dd \u0141\u0250n\u0183n\u0250\u0183\u01dd", "Video Status": "V\u1d09d\u01dd\u00f8 S\u0287\u0250\u0287ns", "Video duration is {humanizeDuration}": "V\u1d09d\u01dd\u00f8 dn\u0279\u0250\u0287\u1d09\u00f8n \u1d09s {humanizeDuration}", @@ -1951,6 +1985,8 @@ "View all errors": "V\u1d09\u01dd\u028d \u0250ll \u01dd\u0279\u0279\u00f8\u0279s", "View child items": "V\u1d09\u01dd\u028d \u0254\u0265\u1d09ld \u1d09\u0287\u01dd\u026fs", "View discussion": "V\u1d09\u01dd\u028d d\u1d09s\u0254nss\u1d09\u00f8n", + "View program": "V\u1d09\u01dd\u028d d\u0279\u00f8\u0183\u0279\u0250\u026f", + "View your receipts or modify your subscription on the {a_start}Orders and subscriptions{a_end} page": "V\u1d09\u01dd\u028d \u028e\u00f8n\u0279 \u0279\u01dd\u0254\u01dd\u1d09d\u0287s \u00f8\u0279 \u026f\u00f8d\u1d09\u025f\u028e \u028e\u00f8n\u0279 snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n \u00f8n \u0287\u0265\u01dd {a_start}\u00d8\u0279d\u01dd\u0279s \u0250nd snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8ns{a_end} d\u0250\u0183\u01dd", "View {span_start} {team_name} {span_end}": "V\u1d09\u01dd\u028d {span_start} {team_name} {span_end}", "Viewing %s course": [ "V\u1d09\u01dd\u028d\u1d09n\u0183 %s \u0254\u00f8n\u0279s\u01dd", @@ -1995,10 +2031,6 @@ "What if I can't see the camera image, or if I can't see my photo do determine which side is visible?": "W\u0265\u0250\u0287 \u1d09\u025f \u0197 \u0254\u0250n'\u0287 s\u01dd\u01dd \u0287\u0265\u01dd \u0254\u0250\u026f\u01dd\u0279\u0250 \u1d09\u026f\u0250\u0183\u01dd, \u00f8\u0279 \u1d09\u025f \u0197 \u0254\u0250n'\u0287 s\u01dd\u01dd \u026f\u028e d\u0265\u00f8\u0287\u00f8 d\u00f8 d\u01dd\u0287\u01dd\u0279\u026f\u1d09n\u01dd \u028d\u0265\u1d09\u0254\u0265 s\u1d09d\u01dd \u1d09s \u028c\u1d09s\u1d09bl\u01dd?", "What if I have difficulty holding my ID in position relative to the camera?": "W\u0265\u0250\u0287 \u1d09\u025f \u0197 \u0265\u0250\u028c\u01dd d\u1d09\u025f\u025f\u1d09\u0254nl\u0287\u028e \u0265\u00f8ld\u1d09n\u0183 \u026f\u028e \u0197\u0110 \u1d09n d\u00f8s\u1d09\u0287\u1d09\u00f8n \u0279\u01ddl\u0250\u0287\u1d09\u028c\u01dd \u0287\u00f8 \u0287\u0265\u01dd \u0254\u0250\u026f\u01dd\u0279\u0250?", "What if I have difficulty holding my head in position relative to the camera?": "W\u0265\u0250\u0287 \u1d09\u025f \u0197 \u0265\u0250\u028c\u01dd d\u1d09\u025f\u025f\u1d09\u0254nl\u0287\u028e \u0265\u00f8ld\u1d09n\u0183 \u026f\u028e \u0265\u01dd\u0250d \u1d09n d\u00f8s\u1d09\u0287\u1d09\u00f8n \u0279\u01ddl\u0250\u0287\u1d09\u028c\u01dd \u0287\u00f8 \u0287\u0265\u01dd \u0254\u0250\u026f\u01dd\u0279\u0250?", - "What industry do you currently work in?": "W\u0265\u0250\u0287 \u1d09ndns\u0287\u0279\u028e d\u00f8 \u028e\u00f8n \u0254n\u0279\u0279\u01ddn\u0287l\u028e \u028d\u00f8\u0279\u029e \u1d09n?", - "What industry do you want to work in?": "W\u0265\u0250\u0287 \u1d09ndns\u0287\u0279\u028e d\u00f8 \u028e\u00f8n \u028d\u0250n\u0287 \u0287\u00f8 \u028d\u00f8\u0279\u029e \u1d09n?", - "What is the highest level of education that any of your parents or guardians have achieved?": "W\u0265\u0250\u0287 \u1d09s \u0287\u0265\u01dd \u0265\u1d09\u0183\u0265\u01dds\u0287 l\u01dd\u028c\u01ddl \u00f8\u025f \u01dddn\u0254\u0250\u0287\u1d09\u00f8n \u0287\u0265\u0250\u0287 \u0250n\u028e \u00f8\u025f \u028e\u00f8n\u0279 d\u0250\u0279\u01ddn\u0287s \u00f8\u0279 \u0183n\u0250\u0279d\u1d09\u0250ns \u0265\u0250\u028c\u01dd \u0250\u0254\u0265\u1d09\u01dd\u028c\u01ddd?", - "What is the highest level of education that you have achieved so far?": "W\u0265\u0250\u0287 \u1d09s \u0287\u0265\u01dd \u0265\u1d09\u0183\u0265\u01dds\u0287 l\u01dd\u028c\u01ddl \u00f8\u025f \u01dddn\u0254\u0250\u0287\u1d09\u00f8n \u0287\u0265\u0250\u0287 \u028e\u00f8n \u0265\u0250\u028c\u01dd \u0250\u0254\u0265\u1d09\u01dd\u028c\u01ddd s\u00f8 \u025f\u0250\u0279?", "What was the total combined income, during the last 12 months, of all members of your family? ": "W\u0265\u0250\u0287 \u028d\u0250s \u0287\u0265\u01dd \u0287\u00f8\u0287\u0250l \u0254\u00f8\u026fb\u1d09n\u01ddd \u1d09n\u0254\u00f8\u026f\u01dd, dn\u0279\u1d09n\u0183 \u0287\u0265\u01dd l\u0250s\u0287 12 \u026f\u00f8n\u0287\u0265s, \u00f8\u025f \u0250ll \u026f\u01dd\u026fb\u01dd\u0279s \u00f8\u025f \u028e\u00f8n\u0279 \u025f\u0250\u026f\u1d09l\u028e? ", "What's Your Next Accomplishment?": "W\u0265\u0250\u0287's \u024e\u00f8n\u0279 N\u01ddx\u0287 \u023a\u0254\u0254\u00f8\u026fdl\u1d09s\u0265\u026f\u01ddn\u0287?", "When learners submit an answer to an assessment, they immediately see whether the answer is correct or incorrect, and the score received.": "W\u0265\u01ddn l\u01dd\u0250\u0279n\u01dd\u0279s snb\u026f\u1d09\u0287 \u0250n \u0250ns\u028d\u01dd\u0279 \u0287\u00f8 \u0250n \u0250ss\u01ddss\u026f\u01ddn\u0287, \u0287\u0265\u01dd\u028e \u1d09\u026f\u026f\u01ddd\u1d09\u0250\u0287\u01ddl\u028e s\u01dd\u01dd \u028d\u0265\u01dd\u0287\u0265\u01dd\u0279 \u0287\u0265\u01dd \u0250ns\u028d\u01dd\u0279 \u1d09s \u0254\u00f8\u0279\u0279\u01dd\u0254\u0287 \u00f8\u0279 \u1d09n\u0254\u00f8\u0279\u0279\u01dd\u0254\u0287, \u0250nd \u0287\u0265\u01dd s\u0254\u00f8\u0279\u01dd \u0279\u01dd\u0254\u01dd\u1d09\u028c\u01ddd.", @@ -2050,6 +2082,7 @@ "You don't seem to have Flash installed. Get Flash to continue your verification.": "\u024e\u00f8n d\u00f8n'\u0287 s\u01dd\u01dd\u026f \u0287\u00f8 \u0265\u0250\u028c\u01dd Fl\u0250s\u0265 \u1d09ns\u0287\u0250ll\u01ddd. \u01e4\u01dd\u0287 Fl\u0250s\u0265 \u0287\u00f8 \u0254\u00f8n\u0287\u1d09nn\u01dd \u028e\u00f8n\u0279 \u028c\u01dd\u0279\u1d09\u025f\u1d09\u0254\u0250\u0287\u1d09\u00f8n.", "You don't seem to have a webcam connected.": "\u024e\u00f8n d\u00f8n'\u0287 s\u01dd\u01dd\u026f \u0287\u00f8 \u0265\u0250\u028c\u01dd \u0250 \u028d\u01ddb\u0254\u0250\u026f \u0254\u00f8nn\u01dd\u0254\u0287\u01ddd.", "You have already verified your ID!": "\u024e\u00f8n \u0265\u0250\u028c\u01dd \u0250l\u0279\u01dd\u0250d\u028e \u028c\u01dd\u0279\u1d09\u025f\u1d09\u01ddd \u028e\u00f8n\u0279 \u0197\u0110!", + "You have an active subscription to the {programName} program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.": "\u024e\u00f8n \u0265\u0250\u028c\u01dd \u0250n \u0250\u0254\u0287\u1d09\u028c\u01dd snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n \u0287\u00f8 \u0287\u0265\u01dd {programName} d\u0279\u00f8\u0183\u0279\u0250\u026f bn\u0287 \u0250\u0279\u01dd n\u00f8\u0287 \u01ddn\u0279\u00f8ll\u01ddd \u1d09n \u0250n\u028e \u0254\u00f8n\u0279s\u01dds. \u0246n\u0279\u00f8ll \u1d09n \u0250 \u0279\u01dd\u026f\u0250\u1d09n\u1d09n\u0183 \u0254\u00f8n\u0279s\u01dd \u0250nd \u01ddn\u027e\u00f8\u028e \u028c\u01dd\u0279\u1d09\u025f\u1d09\u01ddd \u0250\u0254\u0254\u01ddss.", "You have been logged out of your account. Click Okay to log in again now. Click Cancel to stay on this page (you must log in again to save your work).": "\u024e\u00f8n \u0265\u0250\u028c\u01dd b\u01dd\u01ddn l\u00f8\u0183\u0183\u01ddd \u00f8n\u0287 \u00f8\u025f \u028e\u00f8n\u0279 \u0250\u0254\u0254\u00f8nn\u0287. \u023bl\u1d09\u0254\u029e \u00d8\u029e\u0250\u028e \u0287\u00f8 l\u00f8\u0183 \u1d09n \u0250\u0183\u0250\u1d09n n\u00f8\u028d. \u023bl\u1d09\u0254\u029e \u023b\u0250n\u0254\u01ddl \u0287\u00f8 s\u0287\u0250\u028e \u00f8n \u0287\u0265\u1d09s d\u0250\u0183\u01dd (\u028e\u00f8n \u026fns\u0287 l\u00f8\u0183 \u1d09n \u0250\u0183\u0250\u1d09n \u0287\u00f8 s\u0250\u028c\u01dd \u028e\u00f8n\u0279 \u028d\u00f8\u0279\u029e).", "You have done a dry run of force publishing the course. Nothing has changed. Had you run it, the following course versions would have been change.": "\u024e\u00f8n \u0265\u0250\u028c\u01dd d\u00f8n\u01dd \u0250 d\u0279\u028e \u0279nn \u00f8\u025f \u025f\u00f8\u0279\u0254\u01dd dnbl\u1d09s\u0265\u1d09n\u0183 \u0287\u0265\u01dd \u0254\u00f8n\u0279s\u01dd. N\u00f8\u0287\u0265\u1d09n\u0183 \u0265\u0250s \u0254\u0265\u0250n\u0183\u01ddd. \u0126\u0250d \u028e\u00f8n \u0279nn \u1d09\u0287, \u0287\u0265\u01dd \u025f\u00f8ll\u00f8\u028d\u1d09n\u0183 \u0254\u00f8n\u0279s\u01dd \u028c\u01dd\u0279s\u1d09\u00f8ns \u028d\u00f8nld \u0265\u0250\u028c\u01dd b\u01dd\u01ddn \u0254\u0265\u0250n\u0183\u01dd.", "You have no handouts defined": "\u024e\u00f8n \u0265\u0250\u028c\u01dd n\u00f8 \u0265\u0250nd\u00f8n\u0287s d\u01dd\u025f\u1d09n\u01ddd", @@ -2117,6 +2150,7 @@ "Your message cannot be blank.": "\u024e\u00f8n\u0279 \u026f\u01ddss\u0250\u0183\u01dd \u0254\u0250nn\u00f8\u0287 b\u01dd bl\u0250n\u029e.", "Your message must have a subject.": "\u024e\u00f8n\u0279 \u026f\u01ddss\u0250\u0183\u01dd \u026fns\u0287 \u0265\u0250\u028c\u01dd \u0250 snb\u027e\u01dd\u0254\u0287.", "Your message must have at least one target.": "\u024e\u00f8n\u0279 \u026f\u01ddss\u0250\u0183\u01dd \u026fns\u0287 \u0265\u0250\u028c\u01dd \u0250\u0287 l\u01dd\u0250s\u0287 \u00f8n\u01dd \u0287\u0250\u0279\u0183\u01dd\u0287.", + "Your next billing date is {currentPeriodEnd}": "\u024e\u00f8n\u0279 n\u01ddx\u0287 b\u1d09ll\u1d09n\u0183 d\u0250\u0287\u01dd \u1d09s {currentPeriodEnd}", "Your policy changes have been saved.": "\u024e\u00f8n\u0279 d\u00f8l\u1d09\u0254\u028e \u0254\u0265\u0250n\u0183\u01dds \u0265\u0250\u028c\u01dd b\u01dd\u01ddn s\u0250\u028c\u01ddd.", "Your post will be discarded.": "\u024e\u00f8n\u0279 d\u00f8s\u0287 \u028d\u1d09ll b\u01dd d\u1d09s\u0254\u0250\u0279d\u01ddd.", "Your profile settings are managed by {enterprise_name}. Contact your administrator or {link_start}edX Support{link_end} for help.": "\u024e\u00f8n\u0279 d\u0279\u00f8\u025f\u1d09l\u01dd s\u01dd\u0287\u0287\u1d09n\u0183s \u0250\u0279\u01dd \u026f\u0250n\u0250\u0183\u01ddd b\u028e {enterprise_name}. \u023b\u00f8n\u0287\u0250\u0254\u0287 \u028e\u00f8n\u0279 \u0250d\u026f\u1d09n\u1d09s\u0287\u0279\u0250\u0287\u00f8\u0279 \u00f8\u0279 {link_start}\u01dddX Sndd\u00f8\u0279\u0287{link_end} \u025f\u00f8\u0279 \u0265\u01ddld.", @@ -2134,6 +2168,10 @@ "Your upload of '{file}' succeeded.": "\u024e\u00f8n\u0279 ndl\u00f8\u0250d \u00f8\u025f '{file}' sn\u0254\u0254\u01dd\u01ddd\u01ddd.", "Your verification status is good until {verificationGoodUntil}.": "\u024e\u00f8n\u0279 \u028c\u01dd\u0279\u1d09\u025f\u1d09\u0254\u0250\u0287\u1d09\u00f8n s\u0287\u0250\u0287ns \u1d09s \u0183\u00f8\u00f8d nn\u0287\u1d09l {verificationGoodUntil}.", "Your video uploads are not complete.": "\u024e\u00f8n\u0279 \u028c\u1d09d\u01dd\u00f8 ndl\u00f8\u0250ds \u0250\u0279\u01dd n\u00f8\u0287 \u0254\u00f8\u026fdl\u01dd\u0287\u01dd.", + "Your {programName} trial will expire in {remainingDays} day at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.": [ + "\u024e\u00f8n\u0279 {programName} \u0287\u0279\u1d09\u0250l \u028d\u1d09ll \u01ddxd\u1d09\u0279\u01dd \u1d09n {remainingDays} d\u0250\u028e \u0250\u0287 {trialEndTime} \u00f8n {trialEndDate} \u0250nd \u0287\u0265\u01dd \u0254\u0250\u0279d \u00f8n \u025f\u1d09l\u01dd \u028d\u1d09ll b\u01dd \u0254\u0265\u0250\u0279\u0183\u01ddd {subscriptionPrice}.", + "\u024e\u00f8n\u0279 {programName} \u0287\u0279\u1d09\u0250l \u028d\u1d09ll \u01ddxd\u1d09\u0279\u01dd \u1d09n {remainingDays} d\u0250\u028es \u0250\u0287 {trialEndTime} \u00f8n {trialEndDate} \u0250nd \u0287\u0265\u01dd \u0254\u0250\u0279d \u00f8n \u025f\u1d09l\u01dd \u028d\u1d09ll b\u01dd \u0254\u0265\u0250\u0279\u0183\u01ddd {subscriptionPrice}." + ], "Your {program} Certificate": "\u024e\u00f8n\u0279 {program} \u023b\u01dd\u0279\u0287\u1d09\u025f\u1d09\u0254\u0250\u0287\u01dd", "Yourself": "\u024e\u00f8n\u0279s\u01ddl\u025f", "Zoom In": "\u01b5\u00f8\u00f8\u026f \u0197n", @@ -2318,6 +2356,7 @@ "{startTag}{requestToken}{endTag}{selector}": "{startTag}{requestToken}{endTag}{selector}", "{start_strong}{total}{end_strong} words submitted in total.": "{start_strong}{total}{end_strong} \u028d\u00f8\u0279ds snb\u026f\u1d09\u0287\u0287\u01ddd \u1d09n \u0287\u00f8\u0287\u0250l.", "{strongStart}Warning: Account deletion is permanent.{strongEnd} Please read the above carefully before proceeding. This is an irreversible action, and {strongStart}you will no longer be able to use the same email on {platformName}.{strongEnd}": "{strongStart}W\u0250\u0279n\u1d09n\u0183: \u023a\u0254\u0254\u00f8nn\u0287 d\u01ddl\u01dd\u0287\u1d09\u00f8n \u1d09s d\u01dd\u0279\u026f\u0250n\u01ddn\u0287.{strongEnd} \u2c63l\u01dd\u0250s\u01dd \u0279\u01dd\u0250d \u0287\u0265\u01dd \u0250b\u00f8\u028c\u01dd \u0254\u0250\u0279\u01dd\u025fnll\u028e b\u01dd\u025f\u00f8\u0279\u01dd d\u0279\u00f8\u0254\u01dd\u01ddd\u1d09n\u0183. \u0166\u0265\u1d09s \u1d09s \u0250n \u1d09\u0279\u0279\u01dd\u028c\u01dd\u0279s\u1d09bl\u01dd \u0250\u0254\u0287\u1d09\u00f8n, \u0250nd {strongStart}\u028e\u00f8n \u028d\u1d09ll n\u00f8 l\u00f8n\u0183\u01dd\u0279 b\u01dd \u0250bl\u01dd \u0287\u00f8 ns\u01dd \u0287\u0265\u01dd s\u0250\u026f\u01dd \u01dd\u026f\u0250\u1d09l \u00f8n {platformName}.{strongEnd}", + "{subscriptionPrice} subscription after trial ends. Cancel anytime.": "{subscriptionPrice} snbs\u0254\u0279\u1d09d\u0287\u1d09\u00f8n \u0250\u025f\u0287\u01dd\u0279 \u0287\u0279\u1d09\u0250l \u01ddnds. \u023b\u0250n\u0254\u01ddl \u0250n\u028e\u0287\u1d09\u026f\u01dd.", "{team_count} Team": [ "{team_count} \u0166\u01dd\u0250\u026f", "{team_count} \u0166\u01dd\u0250\u026fs" diff --git a/cms/static/js/i18n/ru/djangojs.js b/cms/static/js/i18n/ru/djangojs.js index adf8f6849a..6b7dae440a 100644 --- a/cms/static/js/i18n/ru/djangojs.js +++ b/cms/static/js/i18n/ru/djangojs.js @@ -646,7 +646,6 @@ "Expand Instructions": "\u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438", "Explain if other.": "\u0414\u0440\u0443\u0433\u0430\u044f \u043f\u0440\u0438\u0447\u0438\u043d\u0430, \u043f\u043e\u044f\u0441\u043d\u0438\u0442\u0435.", "Explanation": "\u041f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u0435", - "Explore New Programs": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b", "Explore Programs": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b", "Explore your course!": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043a\u0443\u0440\u0441\u0435", "Failed Proctoring": "\u041e\u0442\u043a\u0430\u0437 \u043f\u0440\u0438 \u043e\u0442\u0441\u043c\u043e\u0442\u0440\u0435", diff --git a/cms/static/js/i18n/tr-tr/djangojs.js b/cms/static/js/i18n/tr-tr/djangojs.js index 42e1d9798c..3e52f678f3 100644 --- a/cms/static/js/i18n/tr-tr/djangojs.js +++ b/cms/static/js/i18n/tr-tr/djangojs.js @@ -83,6 +83,7 @@ " to complete and submit the exam.": "ve g\u00f6ndermek zorundas\u0131n\u0131z.", " to learn": " \u00f6\u011frenmeye dair", "${listPrice}": "{listPrice}TL", + "${price}/month {currency}": "${price}/ay {currency}", "%(cohort_name)s (%(user_count)s)": "%(cohort_name)s (%(user_count)s)", "%(comments_count)s %(span_sr_open)scomments %(span_close)s": "%(comments_count)s %(span_sr_open)s yorum %(span_close)s", "%(comments_count)s %(span_sr_open)scomments (%(unread_comments_count)s unread comments)%(span_close)s": "%(comments_count)s %(span_sr_open)s yorum (%(unread_comments_count)s okunmam\u0131\u015f yorum)%(span_close)s", @@ -173,6 +174,7 @@ "Access to this {blockType} is restricted to: {selectedGroupsLabel}": "Bu {blockType} t\u00fcr\u00fcne eri\u015fim \u015funlara k\u0131s\u0131tl\u0131d\u0131r: {selectedGroupsLabel}", "Accomplishments": "Ba\u015far\u0131lar", "Accomplishments Pagination": "Ba\u015far\u0131lar Sayfaland\u0131r\u0131c\u0131s\u0131", + "According to our records, you are not enrolled in any courses included in your {programName} program subscription. Enroll in a course from the {i_start}Program Details{i_end} page.": "Kay\u0131tlar\u0131m\u0131za g\u00f6re, {programName} program aboneli\u011finize dahil olan hi\u00e7bir derse kay\u0131tl\u0131 de\u011filsiniz. {i_start}Program Ayr\u0131nt\u0131lar\u0131{i_end} sayfas\u0131ndan bir derse kaydolun.", "Account": "Hesap", "Account Information": "Hesap Bilgisi", "Account Not Activated": "Hesap Aktif Edilmemi\u015f", @@ -515,6 +517,7 @@ "Copy Component Location": "Bile\u015fen Konumunu Kopyala", "Copy Email To Editor": "Edit\u00f6re e-postay\u0131 kopyala", "Copy row": "Sat\u0131r\u0131 kopyala", + "Copying": "Kopyal\u0131yor", "Correct failed component": "Ba\u015far\u0131s\u0131z bile\u015feni d\u00fczelt", "Cost": "Maliyet", "Could not find Certificate Exception in the allowlist. Please refresh the page and try again": "\u0130zinli listesinde Sertifika \u0130stisnas\u0131 bulunamad\u0131. L\u00fctfen sayfay\u0131 yenileyin ve tekrar deneyin", @@ -646,6 +649,7 @@ "Discussion admins, moderators, and TAs can make their posts visible to all students or specify a single group.": "Tart\u0131\u015fma y\u00f6neticileri, moderat\u00f6rler ve asistanlar g\u00f6nderilerini t\u00fcm \u00f6\u011frencilere g\u00f6r\u00fcn\u00fcr yapabilir ya da belli bir toplulu\u011fa tan\u0131mlayabilirler.", "Discussion topics in the course are not divided.": "Dersteki tart\u0131\u015fma ba\u015fl\u0131klar\u0131 b\u00f6l\u00fcnmemi\u015f.", "Discussions are unified; all learners interact with posts from other learners, regardless of the group they are in.": "Tart\u0131\u015fmalar birle\u015ftirilmi\u015f durumda; t\u00fcm \u00f6\u011frenciler i\u00e7inde olduklar\u0131 gruplardan ba\u011f\u0131ms\u0131z olarak, di\u011fer \u00f6\u011frencilerin g\u00f6nderileriyle etkile\u015fime girebilir.", + "Discussions enabled": "Tart\u0131\u015fmalar etkinle\u015ftirildi", "Display Name": "G\u00f6r\u00fcnen Ad", "Div": "Div", "Divide the selected content-specific discussion topics": "Se\u00e7ili i\u00e7erik-odakl\u0131 tart\u0131\u015fma ba\u015fl\u0131klar\u0131n\u0131 b\u00f6l", @@ -723,6 +727,7 @@ "Ends {end}": "Bitti {end}", "Engage with posts": "G\u00f6nderilerle etkile\u015fimde bulunun", "Enroll Now": "Hemen Kaydol", + "Enroll in a {programName} course": "{programName} dersine kaydol", "Enrolled": "Kay\u0131tland\u0131", "Enrolling you in the selected course": "Se\u00e7ilen derse sizi kay\u0131t ediyor", "Enrollment Date": "Kay\u0131t Tarihi", @@ -782,8 +787,10 @@ "Error resetting problem attempts for problem '<%- problem_id %>' and student '<%- student_id %>'. Make sure that the problem and student identifiers are complete and correct.": "Problem '<%- problem_id %>' ve \u00f6\u011frenci '<%- student_id %>' i\u00e7in problem denemeleri s\u0131f\u0131rlan\u0131rken hata olu\u015ftu. Problemin ve \u00f6\u011frenci tan\u0131mlay\u0131c\u0131lar\u0131n\u0131n eksiksiz ve do\u011fru oldu\u011fundan emin olun.", "Error retrieving grading configuration.": "Notland\u0131rma yap\u0131land\u0131rmas\u0131n\u0131 al\u0131rken hata.", "Error sending email.": "E-posta g\u00f6nderiminde hata.", + "Error starting a task to override score for problem '<%- problem_id %>' for student '<%- student_id %>'. Make sure that the the score and the problem and student identifiers are complete and correct.": "\u00d6\u011frenci '<%- student_id %>' i\u00e7in '<%- problem_id %>' problemini ge\u00e7ersizle\u015ftirmek i\u00e7in bir g\u00f6rev ba\u015flat\u0131l\u0131rken hata olu\u015ftu. Verilen puan\u0131n, problem ile \u00f6\u011frenci tan\u0131mlay\u0131c\u0131lar\u0131n\u0131n eksiksiz ve do\u011fru oldu\u011fundan emin olun.", "Error starting a task to rescore entrance exam for student '{student_id}'. Make sure that entrance exam has problems in it and student identifier is correct.": "'{student_id}' \u00f6\u011frencisi i\u00e7in giri\u015f s\u0131nav\u0131n\u0131 yeniden puanlamak i\u00e7in g\u00f6reve ba\u015flarken hata olu\u015ftu. Giri\u015f s\u0131nav\u0131n\u0131n problemleri ve \u00f6\u011frenci belirleyicisinin do\u011fru oldu\u011fundan emin olun.", "Error starting a task to rescore problem '<%- problem_id %>' for student '<%- student_id %>'. Make sure that the the problem and student identifiers are complete and correct.": "\u00d6\u011frenci '<%- student_id %>' i\u00e7in '<%- problem_id %>' problemini yeniden puanlamak i\u00e7in bir g\u00f6rev ba\u015flat\u0131l\u0131rken hata olu\u015ftu. Problemin ve \u00f6\u011frenci tan\u0131mlay\u0131c\u0131lar\u0131n\u0131n eksiksiz ve do\u011fru oldu\u011fundan emin olun.", + "Error starting a task to rescore problem '<%- problem_id %>'. Make sure that the problem identifier is complete and correct.": " '<%- problem_id %>' problemi i\u00e7in tekrar notland\u0131rma g\u00f6revi ba\u015flat\u0131l\u0131rken hatayla kar\u015f\u0131la\u015f\u0131ld\u0131. Problem numaras\u0131n\u0131n tam ve do\u011fru yaz\u0131ld\u0131\u011f\u0131na emin olunuz.", "Error starting a task to reset attempts for all students on problem '<%- problem_id %>'. Make sure that the problem identifier is complete and correct.": "T\u00fcm \u00f6\u011frencilerin '<%- problem_id %>' problemi i\u00e7in denemelerini s\u0131f\u0131rlamak i\u00e7in g\u00f6rev ba\u015flat\u0131l\u0131rken hatayla kar\u015f\u0131la\u015f\u0131ld\u0131. Problem tan\u0131mlay\u0131c\u0131s\u0131n\u0131n tam ve do\u011fru yaz\u0131ld\u0131\u011f\u0131na emin olunuz.", "Error while fetching student data.": "\u00d6\u011frenci verisi al\u0131n\u0131rken hata olu\u015ftu.", "Error while generating certificates. Please try again.": "Sertifika olu\u015ftururken hata olu\u015ftu. L\u00fctfen tekrar deneyiniz.", @@ -810,7 +817,6 @@ "Explain if other.": "Di\u011fer ise a\u00e7\u0131kla.", "Explanation": "A\u00e7\u0131klama", "Explicitly Hiding from Students": "\u00d6\u011frencilerden A\u00e7\u0131k\u00e7a Gizle", - "Explore New Programs": "Yeni Programlar\u0131 Ke\u015ffedin", "Explore Programs": "Programlar\u0131 Ke\u015ffedin", "Explore courses": "Dersleri ke\u015ffet", "Explore your course!": "E\u011fitiminizi ke\u015ffedin!", @@ -952,6 +958,7 @@ "ID": "ID", "ID-Verification is not required for this Professional Education course.": "Kimlik Do\u011frulama bu Mesleki E\u011fitim dersi i\u00e7in gerekli de\u011fildir.", "Identity Verification In Progress": "S\u00fcre\u00e7 \u0130\u00e7inde Kimlik Do\u011frulama", + "If a learner starts on {startDate}, this subsection will be due on {projectedDueIn}.": "Bir \u00f6\u011frenci {startDate} tarihinde ba\u015flarsa, bu alt b\u00f6l\u00fcm {projectedDueIn} tarihinde sona erecektir.", "If the photos you submit are rejected, try moving the computer or camera orientation to change the lighting angle. The most common reason for rejection is inability to read the text on the ID card.": "G\u00f6nderdi\u011finiz foto\u011fraflar reddedilirse, ayd\u0131nlatma a\u00e7\u0131s\u0131n\u0131 de\u011fi\u015ftirmek i\u00e7in bilgisayar\u0131 veya kamera y\u00f6n\u00fcn\u00fc hareket ettirmeyi deneyin. En yayg\u0131n reddetme nedeni, kimlik kart\u0131ndaki metnin okunamamas\u0131d\u0131r.", "If the subsection does not have a due date, learners always see their scores when they submit answers to assessments.": "Bir alt b\u00f6l\u00fcm\u00fcn son tarihi yoksa, \u00f6\u011frenciler cevaplar\u0131n\u0131 de\u011ferlendirmeye g\u00f6nderdi\u011finde notlar\u0131n\u0131 g\u00f6r\u00fcrler.", "If the unit was previously published and released to learners, any changes you made to the unit when it was hidden will now be visible to learners.": "E\u011fer \u00fcnite \u00f6\u011frencilere daha \u00f6nce a\u00e7\u0131ld\u0131 ve yay\u0131nland\u0131ysa, gizliyken \u00fcnitede yapt\u0131\u011f\u0131n\u0131z t\u00fcm de\u011fi\u015fiklikler \u015fimdi \u00f6\u011frencilere g\u00f6r\u00fcn\u00fcr olacak.", @@ -968,6 +975,7 @@ "If you remove this transcript, the transcript will not be available for any components that use this video.": "Bu altyaz\u0131y\u0131 kald\u0131r\u0131rsan\u0131z, bu videoyu kullanan herhangi bir bile\u015fen i\u00e7in altyaz\u0131 mevcut olmayacak.", "If you remove this transcript, the transcript will not be available for this component.": "Bu altyaz\u0131y\u0131 kald\u0131r\u0131rsan\u0131z, bu bile\u015fen i\u00e7in altyaz\u0131 mevcut olmayacak.", "If you require assistance with taking either photo for submission, contact %(platformName)s support for additional suggestions.": "G\u00f6nderim i\u00e7in foto\u011fraf \u00e7ekme konusunda yard\u0131ma ihtiyac\u0131n\u0131z varsa, ek \u00f6neriler i\u00e7in %(platformName)s deste\u011fine ba\u015fvurun.", + "If you select an option other than \"%(hide_label)s\", published units in this subsection become available to learners unless they are explicitly hidden.": "\"%(hide_label)s\" d\u0131\u015f\u0131nda bir se\u00e7enek belirlerseniz, bu alt b\u00f6l\u00fcmde yay\u0131nlanan \u00fcniteler, a\u00e7\u0131k\u00e7a gizlenmedik\u00e7e \u00f6\u011frencilerin kullan\u0131m\u0131na a\u00e7\u0131l\u0131r.", "If you still wish to continue and delete your account, please enter your account password:": "H\u00e2l\u00e2 devam etmek ve hesab\u0131n\u0131z\u0131 silmek istiyorsan\u0131z, l\u00fctfen hesab\u0131n\u0131z\u0131n parolas\u0131n\u0131 girin:", "If you use the Advanced Editor, this problem will be converted to XML and you will not be able to return to the Simple Editor Interface.\n\nProceed to the Advanced Editor and convert this problem to XML?": "Geli\u015fmi\u015f Edit\u00f6r\u00fc kullan\u0131rsan\u0131z, bu sorun XML'e d\u00f6n\u00fc\u015fecek ve Basit Edit\u00f6r Aray\u00fcz\u00fc'ne geri d\u00f6nemeyeceksiniz.\n\nGeli\u015fmi\u015f Edit\u00f6r'e devam et ve bu sorunu XML'e d\u00f6n\u00fc\u015ft\u00fcr?", "Ignore": "Yoksay", @@ -984,6 +992,7 @@ "Import YouTube Transcript": "YouTube Altyaz\u0131s\u0131n\u0131 \u0130\u00e7e Aktar", "In Progress": "\u0130\u015flem s\u00fcr\u00fcyor", "In order to sign in, you need to activate your account.{line_break}{line_break}We just sent an activation link to {strong_start} {email} {strong_end}. If you do not receive an email, check your spam folders or {anchorStart}contact {platform_name} Support{anchorEnd}.": "Giri\u015f yapmak i\u00e7in, \u00f6ncelikle hesab\u0131n\u0131z\u0131 etkinle\u015ftirmeniz gerekiyor. {line_break}{line_break}Etkinle\u015ftirme ba\u011flant\u0131s\u0131n\u0131{strong_start} {email} {strong_end} adresine g\u00f6nderdik. E-posta almad\u0131ysan\u0131z spam klas\u00f6r\u00fcn\u00fcz\u00fc denetleyin ya da {anchorStart}{platform_name} Destek Ekibi{anchorEnd} ile ileti\u015fime ge\u00e7in.", + "In the {linkStart}Course Outline{linkEnd}, use this group to control access to a component.": "{linkStart}Ders Anahatt\u0131{linkEnd}'nda, bir bile\u015fene eri\u015fimi kontrol etmek i\u00e7in bu grubu kullan\u0131n.", "Incorrect url format.": "Yanl\u0131\u015f url format\u0131.", "Increase indent": "Girintiyi artt\u0131r", "Individual Exceptions": "Bireysel \u0130stisnalar", @@ -1050,6 +1059,7 @@ "Last published {lastPublishedStart}{publishedOn}{lastPublishedEnd} by {publishedByStart}{publishedBy}{publishedByEnd}": "En son {lastPublishedStart}{publishedOn}{lastPublishedEnd} tarihinde {publishedByStart}{publishedBy}{publishedByEnd} taraf\u0131ndan yay\u0131na al\u0131nd\u0131", "Last updated": "Son g\u00fcncelleme", "Learn More": "Daha Fazlas\u0131n\u0131 \u00d6\u011fren", + "Learn more": "Daha fazlas\u0131n\u0131 \u00f6\u011fren", "Learn more about {license_name}": "{license_name} hakk\u0131nda daha fazla \u00f6\u011fren", "Learners are added to this cohort automatically.": "\u00d6\u011frenciler toplulu\u011fa otomatik olarak eklendiler.", "Learners are added to this cohort only when you provide their email addresses or usernames on this page.": "\u00d6\u011frenciler bu toplulu\u011fa sadece siz onlar\u0131n e-postalar\u0131n\u0131 veya kullan\u0131c\u0131 adlar\u0131n\u0131 sa\u011flad\u0131\u011f\u0131n\u0131z zaman eklenecektir.", @@ -1243,6 +1253,7 @@ "Onboarding status question": "Oryantasyon durumu sorusu", "Once in position, use the Take Photo button {icon} to capture your ID": "\u00c7er\u00e7evenin i\u00e7indeyken, Foto\u011fraf \u00c7ek d\u00fc\u011fmesine {icon} basarak kimli\u011finizin foto\u011fraf\u0131n\u0131 \u00e7ekin", "Once in position, use the Take Photo button {icon} to capture your photo": "Y\u00fcz\u00fcn\u00fcz \u00e7er\u00e7evenin i\u00e7indeyken, Foto\u011fraf \u00c7ek d\u00fc\u011fmesine {icon} basarak foto\u011fraf\u0131n\u0131z\u0131 \u00e7ekin", + "Once you complete one of the program requirements you have a program record. This record is marked complete once you meet all program requirements. A program record can be used to continue your learning journey and demonstrate your learning to others.": "Program gereksinimlerinden birini tamamlad\u0131\u011f\u0131n\u0131zda, bir program kayd\u0131n\u0131z olu\u015fur. T\u00fcm program gereksinimlerini kar\u015f\u0131lad\u0131\u011f\u0131n\u0131zda bu kay\u0131t tamamland\u0131 olarak i\u015faretlenir. \u00d6\u011frenme yolculu\u011funuza devam etmek ve \u00f6\u011frendiklerinizi ba\u015fkalar\u0131na g\u00f6stermek i\u00e7in program kayd\u0131 kullan\u0131labilir.", "Once your account is deleted, you cannot use it to take courses on the {platformName} app, {siteName}, or any other site hosted by {platformName}.": "Hesab\u0131n\u0131z bir kere silindi\u011finde, {platformName} uygulamas\u0131, {siteName} sitesi ya da {platformName} taraf\u0131ndan bar\u0131nd\u0131r\u0131lan herhangi bir dersi alamazs\u0131n\u0131z.", "One or more rescheduling tasks failed.": "Bir veya daha fazla yeniden zamanlama g\u00f6revi ba\u015far\u0131s\u0131z oldu.", "Only <%- fileTypes %> files can be uploaded. Please select a file ending in <%- (fileExtensions) %> to upload.": "Sadece <%- fileTypes %> uzant\u0131l\u0131 dosyalar\u0131 y\u00fckleyebilirsiniz. L\u00fctfen sonunda <%- (fileExtensions) %> uzant\u0131s\u0131 olan bir dosya y\u00fckleyin.", @@ -1287,6 +1298,7 @@ "Paste row after": "Sonra sat\u0131r yap\u0131\u015ft\u0131r", "Paste row before": "\u00d6nce sat\u0131r yap\u0131\u015ft\u0131r", "Paste your embed code below:": "Yerle\u015ftirme kodunuzu a\u015fa\u011f\u0131ya yap\u0131\u015ft\u0131r\u0131n:", + "Pasting": "Yap\u0131\u015ft\u0131r\u0131yor", "Path to Signature Image": "\u0130mza G\u00f6rseline Olan Yol", "Pause": "Duraklat", "Peer": "Ki\u015fi", @@ -1318,6 +1330,7 @@ "Please describe this image or agree that it has no contextual value by checking the checkbox.": "L\u00fctfen bu g\u00f6rseli tan\u0131mlay\u0131n ya da onay kutucu\u011funa t\u0131klayarak a\u00e7\u0131klay\u0131c\u0131 bir bilginin bulunmad\u0131\u011f\u0131n\u0131 do\u011frulay\u0131n.", "Please do not use any spaces in this field.": "L\u00fctfen bu alanda bo\u015fluk kullanmay\u0131n\u0131z.", "Please do not use any spaces or special characters in this field.": "L\u00fctfen bu alanda bo\u015fluk ya da herhangi bir \u00f6zel karakter kullanmay\u0131n\u0131z.", + "Please enable discussions for graded units from course authoring app": "L\u00fctfen ders olu\u015fturma uygulamas\u0131ndan not verilen \u00fcniteler i\u00e7in tart\u0131\u015fmalar\u0131 etkinle\u015ftirin", "Please enter a problem location.": "L\u00fctfen bir hata konumu giriniz.", "Please enter a score.": "L\u00fctfen bir not girin.", "Please enter a student email address or username.": "L\u00fctfen \u00f6\u011frenci e-posta adresini veya kullan\u0131c\u0131 ad\u0131n\u0131 giriniz.", @@ -1384,6 +1397,7 @@ "Proctored Option Available": "G\u00f6zetmenli Se\u00e7enek Mevcut", "Proctored Option No Longer Available": "G\u00f6zetmenli Se\u00e7enek Art\u0131k Mevcut De\u011fil", "Proctored exam {exam_name} in {course_name} for user {username}": "{username} i\u00e7in {course_name} dersi {exam_name} g\u00f6zetmenli s\u0131nav\u0131", + "Proctored exams are timed and they record video of each learner taking the exam. The videos are then reviewed to ensure that learners follow all examination rules. Please note that setting this exam as proctored will change the visibility settings to \"Hide content after due date.\"": "G\u00f6zetimli s\u0131navlar s\u00fcrelidir ve s\u0131nava giren her \u00f6\u011frencinin videosunu kaydederler. Ard\u0131ndan, \u00f6\u011frencilerin t\u00fcm s\u0131nav kurallar\u0131na uymas\u0131n\u0131 sa\u011flamak i\u00e7in videolar incelenir. Bu s\u0131nav\u0131 g\u00f6zetimli olarak ayarlaman\u0131n, g\u00f6r\u00fcn\u00fcrl\u00fck ayarlar\u0131n\u0131 \"Son kullanma tarihinden sonra i\u00e7eri\u011fi gizle\" olarak de\u011fi\u015ftirece\u011fini l\u00fctfen unutmay\u0131n.", "Proctoring": "G\u00f6zetmenli", "Proctoring Results For {course_name} {exam_name}": "{course_name} dersi {exam_name} s\u0131nav\u0131 i\u00e7in G\u00f6zetmen Sonu\u00e7lar\u0131", "Proctoring Review In Progress For {course_name} {exam_name}": "{course_name} dersi {exam_name} s\u0131nav\u0131 i\u00e7in G\u00f6zetim \u0130ncelemesi devam ediyor", @@ -1540,9 +1554,13 @@ "Select a subject for your support request.": "Destek talebiniz i\u00e7in bir konu se\u00e7in.", "Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.": "S\u0131nav i\u00e7in ayr\u0131lan s\u00fcreyi se\u00e7in. E\u011fer 24 saatten fazlaysa, s\u00fcreyi yazarak girin. E\u011fitmen Paneli arac\u0131l\u0131\u011f\u0131yla belli \u00f6\u011frencilere s\u0131nav\u0131 tamamlamalar\u0131 i\u00e7in daha fazla s\u00fcre tan\u0131mlayabilirsiniz.", "Select all": "T\u00fcm\u00fcn\u00fc se\u00e7", - "Select employment status": "Meslek durumunu se\u00e7in", + "Select current industry": "Bir sekt\u00f6r se\u00e7in", + "Select guardian education": "Velinizin e\u011fitim seviyesini se\u00e7in", "Select language": "Dil Se\u00e7imi", + "Select level of education": "E\u011fitim seviyesi se\u00e7in", + "Select military status": "Askerlik durumu se\u00e7in", "Select one or more groups:": "Bir ya da daha fazla grup se\u00e7in:", + "Select prospective industry": "Hedef sekt\u00f6r\u00fc se\u00e7in", "Select the course-wide discussion topics that you want to divide.": "B\u00f6lmek istedi\u011finiz ders-geneli tart\u0131\u015fma konular\u0131n\u0131 se\u00e7in.", "Select the time zone for displaying course dates. If you do not specify a time zone, course dates, including assignment deadlines, will be displayed in your browser's local time zone.": "Ders tarihleri g\u00f6sterimi i\u00e7in saat dilimini se\u00e7in. Herhangi bir saat dilimini se\u00e7memeniz durumunda, g\u00f6rev teslimleri d\u00e2hil olmak \u00fczere t\u00fcm ders tarihleri, web taray\u0131c\u0131n\u0131z\u0131n yerel zaman\u0131na g\u00f6re g\u00f6sterilecektir. ", "Selected blocks": "Se\u00e7ili blocklar", @@ -1655,6 +1673,7 @@ "Started": "Ba\u015flad\u0131", "Started entrance exam rescore task for student '{student_id}'. Click the 'Show Task Status' button to see the status of the task.": "'{student_id}' \u00f6\u011frencisi i\u00e7in giri\u015f s\u0131nav\u0131 yeniden puanlama g\u00f6revi ba\u015flat\u0131ld\u0131. G\u00f6revin durumunu g\u00f6rmek i\u00e7in 'G\u00f6rev Durumunu G\u00f6ster' d\u00fc\u011fmesine t\u0131klay\u0131n.", "Started rescore problem task for problem '<%- problem_id %>' and student '<%- student_id %>'. Click the 'Show Task Status' button to see the status of the task.": "<%- problem_id %>' nolu problem ve '<%- student_id %>' nolu \u00f6\u011frenci i\u00e7in problemi yeniden notland\u0131rma g\u00f6revi ba\u015flad\u0131. Bu g\u00f6revin durumunu g\u00f6rmek i\u00e7in 'G\u00f6rev Durumunu G\u00f6r' d\u00fc\u011fmesine bas\u0131n\u0131z.", + "Started task to override the score for problem '<%- problem_id %>' and student '<%- student_id %>'. Click the 'Show Task Status' button to see the status of the task.": "<%- problem_id %>' nolu problem ve '<%- student_id %>' nolu \u00f6\u011frenci i\u00e7in notland\u0131rmay\u0131 ge\u00e7ersizle\u015ftirme g\u00f6revi ba\u015flad\u0131. Bu g\u00f6revin durumunu g\u00f6rmek i\u00e7in 'G\u00f6rev Durumunu G\u00f6r' d\u00fc\u011fmesine bas\u0131n\u0131z.", "Started {start}": "Ba\u015flad\u0131 {start}", "Starting Exam": "S\u0131nav Ba\u015flat\u0131l\u0131yor", "Starts": "Ba\u015flama Tarihi", @@ -1669,6 +1688,7 @@ "Student email or username": "\u00d6\u011frenci e-postas\u0131 ve kullan\u0131c\u0131 ad\u0131", "Student username/email field is required and can not be empty. Kindly fill in username/email and then press \"Add to Exception List\" button.": "\u00d6\u011frenci kullan\u0131c\u0131 ad\u0131/e-posta alan\u0131n\u0131n doldurulmas\u0131 gereklidir ve bo\u015f b\u0131rak\u0131lamaz. L\u00fctfen kullan\u0131c\u0131 ad\u0131/e-posta alan\u0131n\u0131 doldurun ve ard\u0131ndan \"\u0130stisna Listesine Ekle\" tu\u015funa bas\u0131n.", "Student username/email field is required and can not be empty. Kindly fill in username/email and then press \"Invalidate Certificate\" button.": "\u00d6\u011frenci kullan\u0131c\u0131 ad\u0131/e-posta alan\u0131n\u0131n doldurulmas\u0131 gereklidir ve bo\u015f b\u0131rak\u0131lamaz. L\u00fctfen kullan\u0131c\u0131 ad\u0131/e-posta alan\u0131n\u0131 doldurun ve ard\u0131ndan \"Sertifikay\u0131 Ge\u00e7ersizle\u015ftir\" tu\u015funa bas\u0131n.", + "Studio's having trouble parsing the problem component's content": "Studio, sorunlu bile\u015fenin i\u00e7eri\u011fini ayr\u0131\u015ft\u0131rmakta sorun ya\u015f\u0131yor", "Studio's having trouble saving your work": "Studio yapt\u0131\u011f\u0131n\u0131z i\u015fi kaydetme konusunda zorluk ya\u015f\u0131yor", "Studio:": "Studio:", "Style": "Stil", @@ -1680,6 +1700,10 @@ "Submit enrollment change": "Kay\u0131t de\u011fi\u015fimini g\u00f6nder", "Submitted": "Girildi", "Subscript": "Alt simge", + "Subscription trial expires in {remainingDays} day": [ + "Abonelik deneme s\u00fcresi {remainingDays} g\u00fcn i\u00e7inde sona eriyor", + "Abonelik deneme s\u00fcresi {remainingDays} g\u00fcn i\u00e7inde sona eriyor" + ], "Subsection": "Altb\u00f6l\u00fcm", "Subsection Visibility": "Altb\u00f6l\u00fcm G\u00f6r\u00fcn\u00fcrl\u00fc\u011f\u00fc", "Subsection is hidden after course end date": "Altb\u00f6l\u00fcm ders biti\u015f tarihinden sonra gizlenecek", @@ -1751,12 +1775,15 @@ "Thank you for submitting a request! We appreciate your patience while we work to review your request.": "Bize talebinizi iletti\u011finiz i\u00e7in te\u015fekk\u00fcr ederiz! Talebinizi incelemeye \u00e7al\u0131\u015f\u0131rken ge\u00e7en s\u00fcrede sabr\u0131n\u0131z i\u00e7in te\u015fekk\u00fcr ederiz.", "Thank you for submitting your financial assistance application for {course_name}! You can expect a response in 2-4 business days.": "{course_name} i\u00e7in mali yard\u0131m ba\u015fvurunuzu g\u00f6nderdi\u011finiz i\u00e7in te\u015fekk\u00fcrler! 2-4 i\u015f g\u00fcn\u00fc i\u00e7erisinde size geri d\u00f6nece\u011fiz. ", "Thank you for submitting your photos. We will review them shortly. You can now sign up for any %(platformName)s course that offers verified certificates. Verification is good for one year. After one year, you must submit photos for verification again.": "Foto\u011fraflar\u0131n\u0131z\u0131 g\u00f6nderdi\u011finiz i\u00e7in te\u015fekk\u00fcr ederiz. K\u0131sa s\u00fcre i\u00e7inde de\u011ferlendirece\u011fiz. \u015eimdi onayl\u0131 sertifika sunan herhangi bir %(platformName)s derse kay\u0131t olabilirsiniz. Do\u011frulama bir y\u0131l i\u00e7indir. Bir y\u0131ldan sonra yeniden do\u011frulama i\u00e7in foto\u011fraflar\u0131n\u0131z\u0131 tekrar g\u00f6ndermelisiniz.", + "Thank you! You\u2019re helping make edX better for everyone.": "Te\u015fekk\u00fcrler! edX'i herkes i\u00e7in daha iyi bir hale getirmemize yard\u0131m ediyorsunuz.", "Thanks for returning to verify your ID in: {courseName}": "{courseName} dersine kimli\u011finizi do\u011frulama i\u00e7in d\u00f6nd\u00fc\u011f\u00fcn\u00fcz i\u00e7in te\u015fekk\u00fcr ederiz.", "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Girdi\u011finiz URL bir e-posta adresi gibi g\u00f6r\u00fcn\u00fcyor. Gerekli mailto: \u00f6nekini eklemek ister misiniz?", "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?": "Girdi\u011finiz URL harici ba\u011flant\u0131 gibi g\u00f6r\u00fcn\u00fcyor. Gerekli http:// \u00f6nekini eklemek ister misiniz?", "The assignment type must have a name.": "Bu g\u00f6rev t\u00fcr\u00fcn\u00fcn bir ad\u0131 olmal\u0131.", "The certificate available date must be later than the course end date.": "Sertifikan\u0131n yay\u0131n tarihi, dersin biti\u015f tarihinden sonra olmal\u0131d\u0131r.", + "The certificate display behavior must be one of: {behavior_options}": "Sertifika g\u00f6r\u00fcnt\u00fcleme davran\u0131\u015f\u0131 \u015funlardan biri olmal\u0131d\u0131r: {behavior_options}", "The certificate for this learner has been re-validated and the system is re-running the grade for this learner.": "Bu \u00f6\u011frenci i\u00e7in sertifika yeniden de\u011ferlendiriliyor ve sistem bu \u00f6\u011frencinin notlar\u0131n\u0131 yeniden hesapl\u0131yor.", + "The certificates display behavior must be {valid_option} if certificate available date is set.": "Sertifika kullan\u0131labilirlik tarihi ayarlanm\u0131\u015fsa, sertifikalar\u0131n g\u00f6r\u00fcnt\u00fclenme davran\u0131\u015f\u0131 {valid_option} olmal\u0131d\u0131r.", "The cohort cannot be added": "Kohort eklenemiyor", "The cohort cannot be saved": "Kohort kaydedilemiyor", "The combined length of the organization and library code fields cannot be more than <%- limit %> characters.": "Organizasyon ve k\u00fct\u00fcphane kodu alanlar\u0131n\u0131n toplam uzunlu\u011fu <%- limit %> karakterden fazla olamaz.", @@ -1797,6 +1824,7 @@ "The organization that this signatory belongs to, as it should appear on certificates.": "Bu imza sahibine ait olan kurum, sertifikalarda g\u00f6r\u00fcnmeli.", "The page \"{route}\" could not be found.": "\"{route}\" sayfas\u0131 bulunamad\u0131.", "The post you selected has been deleted.": "Se\u00e7ti\u011finiz ileti silindi.", + "The published branch version, {published}, was reset to the draft branch version, {draft}.": "{published} adl\u0131 yay\u0131nlanan geli\u015ftirme dal\u0131 s\u00fcr\u00fcm\u00fc, {draft} olan taslak dal s\u00fcr\u00fcm\u00fcne d\u00f6nd\u00fcr\u00fcld\u00fc.", "The raw error message is:": "\u0130\u015flenmemi\u015f hata mesaj\u0131:", "The refund deadline for this course has passed, so you will not receive a refund.": "Bu ders i\u00e7in iadenin son g\u00fcn\u00fc ge\u00e7ti, bu y\u00fczden iade alamayacaks\u0131n\u0131z.", "The selected content group does not exist": "Se\u00e7ili i\u00e7erik grubu bulunmuyor", @@ -1959,6 +1987,7 @@ "Topic": "Konu", "Topic area": "Ba\u015fl\u0131k alan\u0131", "Topics": "Konular", + "Topics for unpublished units would not be created": "Yay\u0131mlanmam\u0131\u015f \u00fcniteler i\u00e7in konular olu\u015fturulmaz", "Total": "Toplam", "Total Number": "Toplam Say\u0131", "Total Responses": "Toplam Cevaplar", @@ -2098,6 +2127,7 @@ "Very low": "\u00c7ok d\u00fc\u015f\u00fck", "Video Capture Error": "Video Yakalama Hatas\u0131", "Video ID": "Video No", + "Video Sharing": "Video Payla\u015fma", "Video Source Language": "Video Kayna\u011f\u0131 Dili", "Video Status": "Video Durumu", "Video duration is {humanizeDuration}": "Video {humanizeDuration} uzunlu\u011fundad\u0131r", @@ -2122,6 +2152,7 @@ "View child items": "Alt \u00f6\u011feleri g\u00f6r\u00fcnt\u00fcleyin", "View discussion": "Tart\u0131\u015fmay\u0131 g\u00f6r\u00fcnt\u00fcle", "View my exam": "S\u0131nav\u0131m\u0131 g\u00f6r\u00fcnt\u00fcle", + "View program": "Program\u0131 g\u00f6ster", "View {span_start} {team_name} {span_end}": "G\u00f6r\u00fcnt\u00fcle {span_start} {team_name} {span_end}", "Viewing %s course": [ "%s ders g\u00f6r\u00fcnt\u00fcleniyor", @@ -2137,6 +2168,7 @@ "Waiting": "Bekleniyor", "Want to make edX better for everyone?": "edX'i herkes i\u00e7in daha iyi hale getirmek mi istiyorsunuz?", "Warning": "Uyar\u0131", + "Warning: ": "Uyar\u0131:", "Warnings": "Uyar\u0131lar", "We ask you to activate your account to ensure it is really you creating the account and to prevent fraud.": "Bu hesab\u0131 ger\u00e7ekten sizin a\u00e7t\u0131\u011f\u0131n\u0131zdan emin olmak ve olas\u0131 sahtek\u00e2rl\u0131klar\u0131 \u00f6nleyebilmek i\u00e7in sizden bunu rica ediyoruz.", "We couldn't create your account.": "Hesab\u0131n\u0131z\u0131 olu\u015fturamad\u0131k.", @@ -2166,10 +2198,6 @@ "What if I can't see the camera image, or if I can't see my photo do determine which side is visible?": "Ya kamera g\u00f6r\u00fcnt\u00fcs\u00fcn\u00fc g\u00f6remezsem veya hangi taraf\u0131n g\u00f6r\u00fcn\u00fcr oldu\u011funu belirlemeye \u00e7al\u0131\u015f\u0131rken foto\u011fraf\u0131m\u0131 g\u00f6remezsem?", "What if I have difficulty holding my ID in position relative to the camera?": "Kimli\u011fimi kameraya g\u00f6re yerinde tutmakta zorluk \u00e7ekersem ne olur? ", "What if I have difficulty holding my head in position relative to the camera?": "Ba\u015f\u0131m\u0131 kameraya g\u00f6re pozisyonda tutmakta zorluk \u00e7ekiyorsam ne olur? ", - "What industry do you currently work in?": "\u015eu anda hangi sekt\u00f6rde \u00e7al\u0131\u015f\u0131yorsunuz?", - "What industry do you want to work in?": "Gelecekte hangi sekt\u00f6rde \u00e7al\u0131\u015fmak istiyorsunuz?", - "What is the highest level of education that any of your parents or guardians have achieved?": "Ebeveyn ya da velilerinizin \u015fimdiye kadar ula\u015ft\u0131\u011f\u0131 en y\u00fcksek e\u011fitim seviyesi nedir?", - "What is the highest level of education that you have achieved so far?": "\u015eimdiye kadar ula\u015ft\u0131\u011f\u0131n\u0131z en y\u00fcksek e\u011fitim seviyesi nedir?", "What was the total combined income, during the last 12 months, of all members of your family? ": "T\u00fcm aile \u00fcyelerinizin son 12 ayl\u0131k toplam geliri, ne kadard\u0131?", "What's Your Next Accomplishment?": "Bir Sonraki Ba\u015far\u0131n Ne Olacak?", "When learners submit an answer to an assessment, they immediately see whether the answer is correct or incorrect, and the score received.": "\u00d6\u011frenciler bir de\u011ferlendirmeye cevap g\u00f6nderdi\u011finde, de\u011ferlendirmedeki cevaplar\u0131n\u0131n do\u011fru ya da yanl\u0131\u015f olup olmad\u0131\u011f\u0131n\u0131 ve ald\u0131klar\u0131 notu hemen g\u00f6rebilirler.", @@ -2224,6 +2252,7 @@ "You don't seem to have a webcam connected.": "Ba\u011fl\u0131 bir web kameran\u0131z olmad\u0131\u011f\u0131 g\u00f6r\u00fcnmekte.", "You have added a criterion. You will need to select an option for the criterion in the Learner Training step. To do this, click the Assessment Steps tab.": "Bir \u00f6l\u00e7\u00fct eklediniz. \u00d6\u011frenci E\u011fitimi ad\u0131m\u0131nda, \u00f6l\u00e7\u00fct i\u00e7in bir se\u00e7enek belirlemelisiniz. Bunu yapmak i\u00e7in, De\u011ferlendirme Ad\u0131mlar\u0131 sekmesine t\u0131klay\u0131n\u0131z.", "You have already verified your ID!": "Kimli\u011finizi \u00e7oktan do\u011frulad\u0131n\u0131z!", + "You have an active subscription to the {programName} program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.": "{programName} program\u0131na etkin bir aboneli\u011finiz var ancak herhangi bir derse kay\u0131tl\u0131 de\u011filsiniz. Mevcut bir derse kaydolun ve do\u011frulanm\u0131\u015f eri\u015fimin keyfini \u00e7\u0131kar\u0131n.", "You have been logged out of your account. Click Okay to log in again now. Click Cancel to stay on this page (you must log in again to save your work).": "Hesab\u0131n\u0131zdan \u00e7\u0131kt\u0131n\u0131z. Tekrar giri\u015f yapmak i\u00e7in Tamam'a t\u0131klay\u0131n. \u0130ptal'e t\u0131klayarak bu sayfada kalabilirsiniz (\u00c7al\u0131\u015fman\u0131z\u0131 kaydetmek i\u00e7in tekrar giri\u015f yapman\u0131z gerekiyor).", "You have deleted a criterion. The criterion has been removed from the example responses in the Learner Training step.": "Bir \u00f6l\u00e7\u00fct sildiniz. Sistem, \u00d6\u011frenci E\u011fitimi ad\u0131m\u0131ndaki \u00f6rnek cevaplardan \u00f6l\u00e7\u00fct kald\u0131r\u0131ld\u0131.", "You have deleted all the options for this criterion. The criterion has been removed from the sample responses in the Learner Training step.": "Bu \u00f6l\u00e7\u00fct i\u00e7in t\u00fcm se\u00e7enekleri sildiniz. \u00d6\u011frenci E\u011fitimi ad\u0131m\u0131ndaki \u00f6rnek cevaplardan \u00f6l\u00e7\u00fct kald\u0131r\u0131ld\u0131.", @@ -2250,6 +2279,7 @@ "You may be able to complete the image capture procedure without assistance, but it may take a couple of submission attempts to get the camera positioning right. Optimal camera positioning varies with each computer, but generally, the best position for a photo of an ID card is 8-12 inches (20-30 centimeters) from the camera, with the ID card centered relative to the camera. ": "G\u00f6r\u00fcnt\u00fc yakalama s\u00fcrecini yard\u0131m almadan tamamlayabilirsiniz, ancak kameran\u0131n do\u011fru konumland\u0131r\u0131lmas\u0131 i\u00e7in birka\u00e7 g\u00f6nderim denemesi gerekebilir. Optimum kamera konumu her bilgisayara g\u00f6re de\u011fi\u015fir, ancak genellikle bir kimlik kart\u0131n\u0131n foto\u011fraf\u0131 i\u00e7in en iyi konum, kimlik kart\u0131 kameraya g\u00f6re ortalanm\u0131\u015f olarak kameradan 8-12 in\u00e7 (20-30 santimetre) uzakl\u0131kt\u0131r. ", "You must be over 13 to share a full profile. If you are over 13, make sure that you have specified a birth year on the {account_settings_page_link}": "T\u00fcm profili payla\u015fmak i\u00e7in 13 ya\u015f\u0131ndan b\u00fcy\u00fck olman\u0131z gerekmektedir. E\u011fer 13 ya\u015f\u0131ndan b\u00fcy\u00fckseniz, {account_settings_page_link} sayfas\u0131nda do\u011fum y\u0131l\u0131n\u0131z\u0131 belirtti\u011finizden emin olun.", "You must enter a valid email address in order to add a new team member": "Yeni bir ekip \u00fcyesi eklemek i\u00e7in ge\u00e7erli bir e-posta adresi girmelisiniz.", + "You must have at least one undroppable <%- types %> assignment.": "En az bir adet b\u0131rak\u0131lamaz <%- types %> ataman\u0131z olmal\u0131d\u0131r.", "You must provide a learner name.": "Bir \u00f6\u011frenci ismi belirtmelisiniz.", "You must select a session by {expiration_date} to access the course.": "Derse eri\u015fmek i\u00e7in {expiration_date} tarihine kadar oturum se\u00e7melisiniz.", "You must select a session to access the course.": "Derse eri\u015fmek i\u00e7in oturum se\u00e7melisiniz.", @@ -2433,6 +2463,7 @@ "unsubmitted": "g\u00f6nderilmedi", "upload a PDF file or provide the path to a Studio asset file": "Bir PDF dosyas\u0131 y\u00fckle ya da Studio veri dosyas\u0131na olan yolu sa\u011fla", "username or email": "kullan\u0131c\u0131 ad\u0131 veya e-posta", + "weeks from learner enrollment date": "\u00f6\u011frencinin kay\u0131tlanma tarihinden bu yana ge\u00e7en hafta say\u0131s\u0131", "with %(release_date_from)s": "%(release_date_from)s ile", "with %(section_or_subsection)s": "%(section_or_subsection)s ile", "you have less than a minute remaining": "Bir dakikadan az zaman\u0131n\u0131z kald\u0131", @@ -2441,6 +2472,7 @@ "your course": "dersiniz", "{browse_span_start}Browse teams in other topics{span_end} or {search_span_start}search teams{span_end} in this topic. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.": "{browse_span_start}Di\u011fer konulardaki tak\u0131mlara g\u00f6zat{span_end} ya da bu konudaki {search_span_start}tak\u0131mlar\u0131 ara{span_end}. E\u011fer halen kat\u0131lacak bir tak\u0131m bulamad\u0131ysan\u0131z, {create_span_start}bu konuda yeni bir tak\u0131m olu\u015fturun{span_end}. ", "{categoryText} in {parentDisplayname}": "{parentDisplayname} i\u00e7inde {categoryText}", + "{currentCountOpeningTag}{currentCharacterCount}{currentCountClosingTag} of {maxCharacters}": "{currentCountOpeningTag}{currentCharacterCount}{currentCountClosingTag} / {maxCharacters}", "{display_name} Settings": "{display_name} Ayarlar\u0131", "{earned}/{possible} point (graded)": [ "{possible} \u00fczerinden {earned} puan (notland\u0131r\u0131lan)", diff --git a/cms/static/js/i18n/zh-cn/djangojs.js b/cms/static/js/i18n/zh-cn/djangojs.js index 91f0c639cb..cc05f56966 100644 --- a/cms/static/js/i18n/zh-cn/djangojs.js +++ b/cms/static/js/i18n/zh-cn/djangojs.js @@ -701,7 +701,6 @@ "Explain if other.": "\u5982\u5176\u4ed6\u539f\u56e0\uff0c\u8bf7\u89e3\u91ca\u3002", "Explanation": "\u89e3\u91ca", "Explicitly Hiding from Students": "\u660e\u786e\u5bf9\u5b66\u751f\u9690\u85cf", - "Explore New Programs": "\u63a2\u7d22\u65b0\u8bfe\u7a0b", "Explore Programs": "\u641c\u7d22\u8bfe\u7a0b", "Explore courses": "\u63a2\u7d22\u8bfe\u7a0b", "Explore your course!": "\u63a2\u7d22\u60a8\u7684\u8bfe\u7a0b\uff01", @@ -1358,6 +1357,7 @@ "Select all": "\u5168\u9009", "Select fidelity": "\u9009\u62e9\u4fdd\u771f\u5ea6", "Select language": "\u9009\u62e9\u8bed\u8a00", + "Select military status": "\u9009\u62e9\u519b\u7c4d", "Select one or more groups:": "\u9009\u62e9\u4e00\u6216\u8005\u591a\u4e2a\u7fa4\u7ec4", "Select the course-wide discussion topics that you want to divide.": "\u9009\u62e9\u60a8\u60f3\u8981\u533a\u5206\u7684\u8bfe\u7a0b\u8303\u56f4\u5185\u7684\u8ba8\u8bba\u4e3b\u9898\u3002", "Select the time zone for displaying course dates. If you do not specify a time zone, course dates, including assignment deadlines, will be displayed in your browser's local time zone.": "\u8bf7\u9009\u62e9\u7528\u4e8e\u663e\u793a\u8bfe\u7a0b\u65e5\u671f\u7684\u65f6\u533a\u3002\u5982\u679c\u60a8\u4e0d\u8bbe\u7f6e\u65f6\u533a\uff0c\u90a3\u4e48\u5982\u4f5c\u4e1a\u622a\u6b62\u65e5\u671f\u7b49\u8bfe\u7a0b\u65e5\u671f\u4fe1\u606f\u5c06\u6839\u636e\u60a8\u6d4f\u89c8\u5668\u7684\u672c\u5730\u65f6\u533a\u663e\u793a\u3002", diff --git a/cms/static/js/i18n/zh-tw/djangojs.js b/cms/static/js/i18n/zh-tw/djangojs.js index 68b9a845aa..1d78a76044 100644 --- a/cms/static/js/i18n/zh-tw/djangojs.js +++ b/cms/static/js/i18n/zh-tw/djangojs.js @@ -271,7 +271,6 @@ "Everyone who has staff privileges in this course": "\u9019\u9580\u8ab2\u7a0b\u6709\u5de5\u4f5c\u4eba\u54e1\u6b0a\u9650", "Expand All": "\u5c55\u958b\u5168\u90e8", "Explain if other.": "\u82e5\u6709\u5176\u4ed6\u4e8b\u9805\uff0c\u8acb\u8aaa\u660e\u3002", - "Explore New Programs": "\u63a2\u7d22\u65b0\u8a08\u756b", "Explore Programs": "\u63a2\u7d22\u8a08\u756b", "Explore your course!": "\u700f\u89bd\u60a8\u7684\u8ab2\u7a0b\uff01", "File Name": "\u6a94\u6848\u540d\u7a31", diff --git a/cms/static/js/index.js b/cms/static/js/index.js index 7a7d34284b..b27f0e0c0d 100644 --- a/cms/static/js/index.js +++ b/cms/static/js/index.js @@ -2,6 +2,7 @@ define(['domReady', 'jquery', 'underscore', 'js/utils/cancel_on_escape', 'js/vie 'js/views/utils/create_library_utils', 'common/js/components/utils/view_utils'], function(domReady, $, _, CancelOnEscape, CreateCourseUtilsFactory, CreateLibraryUtilsFactory, ViewUtils) { 'use strict'; + var CreateCourseUtils = new CreateCourseUtilsFactory({ name: '.new-course-name', org: '.new-course-org', diff --git a/cms/static/js/maintenance/force_publish_course.js b/cms/static/js/maintenance/force_publish_course.js index c7b29bc8d0..642b5bea4f 100644 --- a/cms/static/js/maintenance/force_publish_course.js +++ b/cms/static/js/maintenance/force_publish_course.js @@ -8,6 +8,7 @@ define([ // jshint ignore:line ], function($, _, gettext, ViewUtils, StringUtils, HtmlUtils) { 'use strict'; + return function(maintenanceViewURL) { var showError; // Reset values @@ -80,4 +81,3 @@ function($, _, gettext, ViewUtils, StringUtils, HtmlUtils) { }); }; }); - diff --git a/cms/static/js/models/course_info.js b/cms/static/js/models/course_info.js index 3f32dcf492..b4e710d503 100644 --- a/cms/static/js/models/course_info.js +++ b/cms/static/js/models/course_info.js @@ -5,8 +5,8 @@ define(['backbone'], function(Backbone) { url: '', defaults: { - updates: null, // UpdateCollection - handouts: null // HandoutCollection + updates: null, // UpdateCollection + handouts: null // HandoutCollection } }); return CourseInfo; diff --git a/cms/static/js/models/group.js b/cms/static/js/models/group.js index 45c1ba89cf..b61a27f467 100644 --- a/cms/static/js/models/group.js +++ b/cms/static/js/models/group.js @@ -3,6 +3,7 @@ define([ 'backbone.associations' ], function(Backbone, _, str, gettext) { 'use strict'; + var Group = Backbone.AssociatedModel.extend({ defaults: function() { return { diff --git a/cms/static/js/models/group_configuration.js b/cms/static/js/models/group_configuration.js index c4d8f9e61d..4fc87c89e8 100644 --- a/cms/static/js/models/group_configuration.js +++ b/cms/static/js/models/group_configuration.js @@ -4,6 +4,7 @@ define([ ], function(Backbone, _, gettext, GroupModel, GroupCollection) { 'use strict'; + var GroupConfiguration = Backbone.AssociatedModel.extend({ defaults: function() { return { diff --git a/cms/static/js/models/location.js b/cms/static/js/models/location.js index e755fb6fef..75b4ecb633 100644 --- a/cms/static/js/models/location.js +++ b/cms/static/js/models/location.js @@ -9,11 +9,11 @@ define(['backbone', 'underscore'], function(Backbone, _) { }, toUrl: function(overrides) { return; - (overrides && overrides.tag ? overrides.tag : this.get('tag')) + '://' + - (overrides && overrides.org ? overrides.org : this.get('org')) + '/' + - (overrides && overrides.course ? overrides.course : this.get('course')) + '/' + - (overrides && overrides.category ? overrides.category : this.get('category')) + '/' + - (overrides && overrides.name ? overrides.name : this.get('name')) + '/'; + (overrides && overrides.tag ? overrides.tag : this.get('tag')) + '://' + + (overrides && overrides.org ? overrides.org : this.get('org')) + '/' + + (overrides && overrides.course ? overrides.course : this.get('course')) + '/' + + (overrides && overrides.category ? overrides.category : this.get('category')) + '/' + + (overrides && overrides.name ? overrides.name : this.get('name')) + '/'; }, _tagPattern: /[^:]+/g, _fieldPattern: new RegExp('[^/]+', 'g'), @@ -39,7 +39,7 @@ define(['backbone', 'underscore'], function(Backbone, _) { category: this.getNextField(payload), name: this.getNextField(payload) }; - } else return null; + } else { return null; } } else { return payload; } diff --git a/cms/static/js/models/metadata.js b/cms/static/js/models/metadata.js index 923c51647b..8ada487032 100644 --- a/cms/static/js/models/metadata.js +++ b/cms/static/js/models/metadata.js @@ -12,7 +12,7 @@ define(['backbone'], function(Backbone) { default_value: null, options: null, type: null, - custom: false // Used only for non-metadata fields + custom: false // Used only for non-metadata fields }, initialize: function() { diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js index 803b307600..302f214fd6 100644 --- a/cms/static/js/models/settings/course_details.js +++ b/cms/static/js/models/settings/course_details.js @@ -3,15 +3,16 @@ define(['backbone', 'underscore', 'gettext', 'js/models/validation_helpers', 'js ], function(Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) { 'use strict'; + var CourseDetails = Backbone.Model.extend({ defaults: { org: '', course_id: '', run: '', language: '', - start_date: null, // maps to 'start' - end_date: null, // maps to 'end' - certificates_display_behavior: "", + start_date: null, // maps to 'start' + end_date: null, // maps to 'end' + certificates_display_behavior: '', certificate_available_date: null, enrollment_start: null, enrollment_end: null, @@ -23,7 +24,7 @@ function(Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) { short_description: '', overview: '', intro_video: null, - effort: null, // an int or null, + effort: null, // an int or null, license: null, course_image_name: '', // the filename course_image_asset_path: '', // the full URL (/c4x/org/course/num/asset/filename) @@ -44,9 +45,9 @@ function(Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) { // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation. var errors = {}; const CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS = { - END: "end", - END_WITH_DATE: "end_with_date", - EARLY_NO_INFO: "early_no_info" + END: 'end', + END_WITH_DATE: 'end_with_date', + EARLY_NO_INFO: 'early_no_info' }; newattrs = DateUtils.convertDateStringsToObjects( @@ -61,14 +62,14 @@ function(Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) { if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) { errors.end_date = gettext('The course end date must be later than the course start date.'); } - if (newattrs.start_date && newattrs.enrollment_start && - newattrs.start_date < newattrs.enrollment_start) { + if (newattrs.start_date && newattrs.enrollment_start + && newattrs.start_date < newattrs.enrollment_start) { errors.enrollment_start = gettext( 'The course start date must be later than the enrollment start date.' ); } - if (newattrs.enrollment_start && newattrs.enrollment_end && - newattrs.enrollment_start >= newattrs.enrollment_end) { + if (newattrs.enrollment_start && newattrs.enrollment_end + && newattrs.enrollment_start >= newattrs.enrollment_end) { errors.enrollment_end = gettext( 'The enrollment start date cannot be after the enrollment end date.' ); @@ -76,22 +77,21 @@ function(Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) { if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) { errors.enrollment_end = gettext('The enrollment end date cannot be after the course end date.'); } - if (this.showCertificateAvailableDate && newattrs.end_date && newattrs.certificate_available_date && - newattrs.certificate_available_date < newattrs.end_date) { + if (this.showCertificateAvailableDate && newattrs.end_date && newattrs.certificate_available_date + && newattrs.certificate_available_date < newattrs.end_date) { errors.certificate_available_date = gettext( 'The certificate available date must be later than the course end date.' ); } - if (this.useV2CertDisplaySettings){ + if (this.useV2CertDisplaySettings) { if ( newattrs.certificates_display_behavior && !(Object.values(CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS).includes(newattrs.certificates_display_behavior)) ) { - errors.certificates_display_behavior = StringUtils.interpolate( gettext( - "The certificate display behavior must be one of: {behavior_options}" + 'The certificate display behavior must be one of: {behavior_options}' ), { behavior_options: Object.values(CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS).join(', ') @@ -100,13 +100,13 @@ function(Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) { } // Throw error if there's a value for certificate_available_date - if( + if ( (newattrs.certificate_available_date && newattrs.certificates_display_behavior != CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS.END_WITH_DATE) || (!newattrs.certificate_available_date && newattrs.certificates_display_behavior == CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS.END_WITH_DATE) - ){ + ) { errors.certificates_display_behavior = StringUtils.interpolate( gettext( - "The certificates display behavior must be {valid_option} if certificate available date is set." + 'The certificates display behavior must be {valid_option} if certificate available date is set.' ), { valid_option: CERTIFICATES_DISPLAY_BEHAVIOR_OPTIONS.END_WITH_DATE @@ -132,7 +132,7 @@ function(Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) { ), range, true); } } - if (!_.isEmpty(errors)) return errors; + if (!_.isEmpty(errors)) { return errors; } // NOTE don't return empty errors as that will be interpreted as an error state }, @@ -141,19 +141,19 @@ function(Backbone, _, gettext, ValidationHelpers, DateUtils, StringUtils) { set_videosource: function(newsource) { // newsource either is