Compare commits

...

69 Commits

Author SHA1 Message Date
David Joy
e80898bb32 Bumping to 1.8.2 2021-02-04 12:11:52 -05:00
David Joy
0e17437d04 Bumping frontend-platform to latest. 2021-01-05 16:36:17 -05:00
edX Transifex Bot
65b6b30f01 fix(i18n): update translations 2021-01-03 16:13:58 -05:00
edX Transifex Bot
00be4845b8 fix(i18n): update translations 2020-12-27 16:13:45 -05:00
visortelle
4a3270d0a8 Edit translation message on account page. (#361)
* Edit translation message on account page. 

Change the translation message from "Spoken languages" to "Spoken language". 
The plural form can confuse a user because select input allows selecting only a single value.

* Edit translation message on account page.

* Force travis build

* Revert src/i18n/messages content

according to https://github.com/edx/frontend-app-account/pull/361#issuecomment-749222209

* Revert "Revert src/i18n/messages content"

This reverts commit eff1daac03.

* Revert src/i18n/messages content

according to
https://github.com/edx/frontend-app-account/pull/361#issuecomment-749222209
2020-12-22 13:36:09 -05:00
edX Transifex Bot
fa91f25ad3 fix(i18n): update translations 2020-12-20 16:13:32 -05:00
Bianca Severino
f7bb06e109 Merge pull request #368 from edx/bseverino/idv-example-card
[MST-494] Clarify photo ID instructions with example image
2020-12-16 15:25:52 -05:00
Bianca Severino
340ec87522 Add example img for ID card and update copy 2020-12-16 13:37:31 -05:00
Renovate Bot
9e6a74c633 fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.13 2020-12-16 01:10:38 +00:00
Adam Stankiewicz
7c05188e5f fix: bump header and footer versions to use logo from config (#365) 2020-12-15 18:42:53 -05:00
Bianca Severino
a19c37fa85 Merge pull request #364 from edx/bseverino/idv-errors
[MST-502] Add more descriptive errors for IDV submission
2020-12-15 13:14:16 -05:00
Bianca Severino
600a2b8fe2 Add more descriptive errors for IDV submission 2020-12-14 14:29:21 -05:00
Bianca Severino
402dbae44f Merge pull request #360 from edx/bseverino/parse-query-string
[MST-567] Allow decoding of query string in IDV flow
2020-12-14 11:40:46 -05:00
edX Transifex Bot
75fddb9b69 fix(i18n): update translations 2020-12-13 16:03:56 -05:00
Bianca Severino
6fff8630d4 Allow parsing of special characters in querystring 2020-12-11 13:13:51 -05:00
Bianca Severino
06b4fc641e Merge pull request #359 from edx/bseverino/remove-course-key
Remove use of course key temporarily
2020-12-09 16:49:22 -05:00
Bianca Severino
19d5411402 Remove course key 2020-12-09 16:43:08 -05:00
Bianca Severino
14160eaeb3 Merge pull request #358 from edx/bseverino/parse-course-key
Parse course_id query string correctly
2020-12-07 16:56:41 -05:00
Bianca Severino
b06138f96d Parse course_id query string correctly 2020-12-07 16:20:44 -05:00
Bianca Severino
01d0b3645b Merge pull request #357 from edx/revert-355-revert-351-bseverino/idv-block-audit
Revert "Revert "Prevent IDV access from audit courses""
2020-12-07 10:30:22 -05:00
Bianca Severino
0a4dcb4eaa Revert "Revert "Prevent IDV access from audit courses"" 2020-12-07 10:18:40 -05:00
edX Transifex Bot
37e338df0f fix(i18n): update translations 2020-12-06 16:13:59 -05:00
Bianca Severino
6a0e27c816 Merge pull request #355 from edx/revert-351-bseverino/idv-block-audit
Revert "Prevent IDV access from audit courses"
2020-12-04 13:05:15 -05:00
Bianca Severino
289341dd91 Revert "Prevent IDV access from audit courses" 2020-12-04 11:59:52 -05:00
edX Transifex Bot
45f9a15885 fix(i18n): update translations 2020-11-22 16:03:56 -05:00
Bianca Severino
f982187be6 Merge pull request #351 from edx/bseverino/idv-block-audit
Prevent IDV access from audit courses
2020-11-16 15:13:04 -05:00
Bianca Severino
9d0d03c402 Prevent IDV access from audit learners 2020-11-16 14:20:39 -05:00
morenol
8602561e55 fix: Update frontend-build (#340)
Update frontend-platform
2020-11-10 17:18:02 -05:00
Renovate Bot
b310574f18 fix(deps): update font awesome 2020-11-10 02:00:05 +00:00
Renovate Bot
73badaf916 fix(deps): update dependency universal-cookie to v4.0.4 2020-11-10 00:58:25 +00:00
Renovate Bot
f02cc43078 chore(deps): update dependency enzyme-adapter-react-16 to v1.15.5 2020-11-09 23:36:50 +00:00
edX Transifex Bot
bd5c2343be fix(i18n): update translations 2020-11-08 16:03:56 -05:00
alangsto
6349d487e4 Merge pull request #343 from edx/alangsto/camera_permissions
Ensure that first stream is stopped
2020-11-05 14:40:40 -05:00
Alie Langston
9c6137e668 testing to see if we need to request front/back camera
stopping initial stream
2020-11-05 14:30:39 -05:00
alangsto
2cf01270d7 Merge pull request #342 from edx/alangsto/fix_camera_access
Remove browser based resolution
2020-11-04 13:15:26 -05:00
Alie Langston
403df8926d removed browser based resolution, and instead optimized photo for all browsers
completed comment
2020-11-04 13:01:14 -05:00
Bianca Severino
4bd96c70af Merge pull request #341 from edx/bseverino/name-change-fix
Fix onSubmit for IDV name change form
2020-10-27 12:32:30 -04:00
Bianca Severino
d2a835f560 Fix submit functionality on name change form 2020-10-27 12:13:47 -04:00
Bianca Severino
d0b5d54d0a Merge pull request #339 from edx/bseverino/id-resolution
Increase camera resolution to 1080p for ID verification
2020-10-20 14:17:51 -04:00
Bianca Severino
26299eed65 Increase camera resolution to 1080p 2020-10-20 13:53:01 -04:00
adeelehsan
648bea8d84 Merge pull request #338 from edx/aehsan/suppressred_pii_for_accounts
suppressed pii for account
2020-10-09 01:59:13 +05:00
adeelehsan
7409f02056 suppressed pii for account 2020-10-09 00:54:07 +05:00
alangsto
f5dd409816 Merge pull request #337 from edx/alangsto/add_tracking_events
Add tracking events for face detection
2020-10-01 15:32:36 -04:00
Alie Langston
6ddac11dc0 added tracking events
added testing
2020-10-01 14:19:07 -04:00
adeelehsan
7019aea4fb Merge pull request #333 from edx/aehsan/van-23/suppressed_pii_for_accounts
suppressed pii for hot jar
2020-09-25 17:33:37 +05:00
adeelehsan
5424434599 suppressed pii for hot jar
VAN-23
2020-09-25 17:22:13 +05:00
edX Transifex Bot
8ca9dc78a9 fix(i18n): update translations 2020-09-20 17:06:06 -04:00
alangsto
30e25b96bb Merge pull request #332 from edx/alangsto/add_realtime_feedback
Add realtime feedback for screenreaders during face detection
2020-09-16 15:31:00 -04:00
Alie Langston
1d01abc7da added feedback for screenreaders
moved settings state back to original

fixed status updates

updated message for more clarity

renamed variables for clarity

added comment

fixed variables

fixed variable again

decreased delay between feedbacks

updated comment
2020-09-16 15:22:04 -04:00
alangsto
917152df22 Merge pull request #330 from edx/alangsto/remove_survey
Removed SurveyMonkey survey
2020-09-15 16:35:45 -04:00
Alie Langston
961c0feb78 removed SurveyMonkey survey 2020-09-15 16:04:46 -04:00
edX Transifex Bot
7d57d86729 fix(i18n): update translations 2020-09-13 17:04:11 -04:00
alangsto
34c5de1340 Merge pull request #324 from edx/alangsto/add_idv_survey
Added SurveyMonkey Survey to IDV submitted page
2020-09-10 15:42:59 -04:00
Alie Langston
81d604d046 adding second round of survey to IDV summary page 2020-09-10 15:22:21 -04:00
Michael Roytman
e6f7e83cf5 Merge pull request #322 from edx/mroytman/use-better-camera-mobile
use 'environment' facing mode when using the camera in the ID photo c…
2020-09-10 14:45:30 -04:00
Michael Roytman
a970e17070 use 'environment' facing mode when using the camera in the ID photo context; this will open the rear facing camera on mobile 2020-09-10 14:33:42 -04:00
alangsto
f471ae0aa7 Merge pull request #323 from edx/alangsto/add_error_messaging
Added error messaging for image upload and idv submission
2020-09-10 14:29:20 -04:00
Alie Langston
b9efe6faee added error messaging for image upload and idv submission
updated testing

wrapped button click

moved maximum file size to a const variable
2020-09-10 14:23:35 -04:00
edX Transifex Bot
2dbccec1f1 fix(i18n): update translations 2020-09-06 17:03:58 -04:00
Michael Roytman
9f38b975d9 Merge pull request #318 from edx/mroytman/fix-iOS-camera-bug-popout
add playsInline attribute to video element to ensure it does not pop out into full screen video on iOS Safari
2020-09-04 14:54:29 -04:00
Michael Roytman
ae355cefcf add playsInline attribute to video element to ensure it does not pop out into full screen video on iOS Safari 2020-09-04 14:03:29 -04:00
alangsto
d63dfc929f Merge pull request #317 from edx/alangsto/fix_camera_resolution
Added logic for adjusting image resolution
2020-09-03 14:11:13 -04:00
Alie Langston
64be9edeac adjusted size factor based on camera resolution
added additional check so that tests pass

updates for requested changes
2020-09-03 14:05:36 -04:00
alangsto
5f4f82eae1 Merge pull request #315 from edx/alangsto/blazeface_fix
Fix for blazeface/camera issue
2020-09-02 10:43:40 -04:00
Alie Langston
c8c7352549 updated camera and canvas ratio to match, and updated ranges for landmarks 2020-09-02 10:34:23 -04:00
Renovate Bot
88206e4282 fix(deps): update dependency @tensorflow/tfjs-core to v1.6.1 2020-09-01 22:56:01 +00:00
Renovate Bot
d8e23b1a02 fix(deps): update dependency @tensorflow/tfjs-converter to v1.6.1 2020-09-01 21:58:04 +00:00
alangsto
5db21d2483 Merge pull request #302 from edx/alangsto/add_object_tracking
added object tracking
2020-09-01 16:48:39 -04:00
Alie Langston
526d6114f2 added object tracking
moved load of library

updated test

removed async

trying to retest

Retesting

added test back

fixed errors due to next button

removed try catch so errors occur

added try catch back

added in ignore

readded libraries

stops detection when photo is taken, stops erroring issue

added help text

added spinner and better mocked blazeface

moved img element back to correct place

updated for requested changes

updates for requested changes

added timeout for test

updated blazeface to pull from forked repo, and added changes based on accessibility feedback
2020-09-01 15:54:20 -04:00
39 changed files with 10695 additions and 6058 deletions

5
.env
View File

@@ -16,4 +16,7 @@ SEGMENT_KEY=null
SITE_NAME=null
SUPPORT_URL=null
USER_INFO_COOKIE_NAME=null
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=null
LOGO_URL=''
LOGO_TRADEMARK_URL=''
LOGO_WHITE_URL=''
FAVICON_URL=''

View File

@@ -20,4 +20,7 @@ USER_INFO_COOKIE_NAME='edx-user-info'
# Temporary, Remove this once we are ready to release the feature.
COACHING_ENABLED=true
ENABLE_DEMOGRAPHICS_COLLECTION=true
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=null='http://localhost:8080'
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico

View File

@@ -18,3 +18,7 @@ SUPPORT_URL='http://localhost:18000/support'
USER_INFO_COOKIE_NAME='edx-user-info'
COACHING_ENABLED=''
ENABLE_DEMOGRAPHICS_COLLECTION=''
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico

14451
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -29,23 +29,26 @@
"ie 11"
],
"dependencies": {
"@edx/frontend-component-footer": "10.0.11",
"@edx/frontend-component-header": "2.0.5",
"@edx/frontend-platform": "1.1.14",
"@edx/frontend-component-footer": "10.1.1",
"@edx/frontend-component-header": "2.2.1",
"@edx/frontend-platform": "1.8.2",
"@edx/paragon": "9.1.1",
"@fortawesome/fontawesome-svg-core": "1.2.30",
"@fortawesome/fontawesome-svg-core": "1.2.32",
"@fortawesome/free-brands-svg-icons": "5.8.2",
"@fortawesome/free-regular-svg-icons": "5.7.2",
"@fortawesome/free-solid-svg-icons": "5.8.2",
"@fortawesome/react-fontawesome": "0.1.11",
"@fortawesome/react-fontawesome": "0.1.13",
"@tensorflow-models/blazeface": "git+https://github.com/alangsto/blazeface.git",
"@tensorflow/tfjs-converter": "1.6.1",
"@tensorflow/tfjs-core": "1.6.1",
"babel-polyfill": "6.26.0",
"bowser": "^2.10.0",
"bowser": "2.10.0",
"classnames": "2.2.6",
"font-awesome": "4.7.0",
"form-urlencoded": "4.0.1",
"formdata-polyfill": "3.0.20",
"history": "4.10.1",
"jslib-html5-camera-photo": "^3.1.3",
"jslib-html5-camera-photo": "3.1.3",
"lodash.camelcase": "4.3.0",
"lodash.debounce": "4.0.8",
"lodash.findindex": "4.6.0",
@@ -54,7 +57,7 @@
"lodash.merge": "4.6.2",
"lodash.omit": "4.5.0",
"lodash.pick": "4.4.0",
"lodash.pickby": "^4.6.0",
"lodash.pickby": "4.6.0",
"lodash.snakecase": "4.1.1",
"memoize-one": "5.1.1",
"newrelic": "5.13.1",
@@ -74,18 +77,18 @@
"redux-saga": "1.1.3",
"redux-thunk": "2.3.0",
"reselect": "4.0.0",
"universal-cookie": "4.0.3"
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@edx/frontend-build": "2.0.6",
"@testing-library/jest-dom": "^5.11.2",
"@testing-library/react": "^10.4.7",
"@edx/frontend-build": "5.3.2",
"@testing-library/jest-dom": "5.11.2",
"@testing-library/react": "10.4.7",
"codecov": "3.7.2",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.15.4",
"enzyme-adapter-react-16": "1.15.5",
"es-check": "5.0.0",
"husky": "3.0.9",
"jest": "^26.1.0",
"jest": "26.1.0",
"react-test-renderer": "16.8.6",
"reactifex": "1.1.1",
"redux-mock-store": "1.5.4"

View File

@@ -4,7 +4,7 @@
<title>Account | edX</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="<%=webpackConfig.output.publicPath%>favicon.ico" type="image/x-icon" />
</head>
<body>
<div id="root"></div>

View File

@@ -279,18 +279,18 @@ const messages = defineMessages({
},
'account.settings.field.language.proficiencies': {
id: 'account.settings.field.language.proficiencies',
defaultMessage: 'Spoken languages',
description: 'Label for account settings spoken languages field.',
defaultMessage: 'Spoken language',
description: 'Label for account settings spoken language field.',
},
'account.settings.field.language.proficiencies.empty': {
id: 'account.settings.field.language.proficiencies.empty',
defaultMessage: 'Add a spoken language',
description: 'Placeholder for empty account settings spoken languages field.',
description: 'Placeholder for empty account settings spoken language field.',
},
'account.settings.field.language_proficiencies.options.empty': {
id: 'account.settings.field.language_proficiencies.options.empty',
defaultMessage: 'Select a Language',
description: 'Option for an empty value on account settings spoken languages field.',
description: 'Option for an empty value on account settings spoken language field.',
},
'account.settings.field.time.zone': {

View File

@@ -106,6 +106,7 @@ function EditableField(props) {
>
<label className="h6 d-block" htmlFor={id}>{label}</label>
<Input
data-hj-suppress
name={name}
id={id}
type={type}
@@ -155,7 +156,7 @@ function EditableField(props) {
</Button>
) : null}
</div>
<p>{renderValue(value)}</p>
<p data-hj-suppress>{renderValue(value)}</p>
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
</div>
),

View File

@@ -109,6 +109,7 @@ function EmailField(props) {
>
<label className="h6 d-block" htmlFor={id}>{label}</label>
<Input
data-hj-suppress
name={name}
id={id}
type="email"
@@ -156,7 +157,7 @@ function EmailField(props) {
</Button>
) : null}
</div>
<p>{renderValue()}</p>
<p data-hj-suppress>{renderValue()}</p>
{renderConfirmationMessage() || <p className="small text-muted mt-n2">{helpText}</p>}
</div>
),

View File

@@ -74,7 +74,9 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -136,7 +138,9 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
<button
className="btn btn-link p-0"
onBlur={[Function]}
@@ -206,7 +210,9 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -268,7 +274,9 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -330,7 +338,9 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -392,7 +402,9 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -454,7 +466,9 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -516,7 +530,9 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -578,7 +594,9 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -679,7 +697,9 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -741,7 +761,9 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
<button
className="btn btn-link p-0"
onBlur={[Function]}
@@ -811,7 +833,9 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -873,7 +897,9 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -935,7 +961,9 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -997,7 +1025,9 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1059,7 +1089,9 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1121,7 +1153,9 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1183,7 +1217,9 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1305,7 +1341,9 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1367,7 +1405,9 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Hispanic, Latin, or Spanish origin, White
</p>
<p
@@ -1429,7 +1469,9 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1491,7 +1533,9 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1553,7 +1597,9 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1615,7 +1661,9 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1677,7 +1725,9 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1739,7 +1789,9 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1801,7 +1853,9 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1888,7 +1942,9 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -1950,7 +2006,9 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Asian
</p>
<p
@@ -2012,7 +2070,9 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2074,7 +2134,9 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2136,7 +2198,9 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2198,7 +2262,9 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2260,7 +2326,9 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2322,7 +2390,9 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2384,7 +2454,9 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2471,7 +2543,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2533,7 +2607,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
<button
className="btn btn-link p-0"
onBlur={[Function]}
@@ -2603,7 +2679,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2665,7 +2743,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2727,7 +2807,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2789,7 +2871,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2851,7 +2935,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Other: test
</p>
<p
@@ -2913,7 +2999,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -2975,7 +3063,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -3062,7 +3152,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer to self describe: test
</p>
<p
@@ -3124,7 +3216,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
<button
className="btn btn-link p-0"
onBlur={[Function]}
@@ -3194,7 +3288,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -3256,7 +3352,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -3318,7 +3416,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -3380,7 +3480,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -3442,7 +3544,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -3504,7 +3608,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p
@@ -3566,7 +3672,9 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p>
<p
data-hj-suppress={true}
>
Prefer not to respond
</p>
<p

View File

@@ -1,274 +1,297 @@
{
"account.settings.message.duplicate.tpa.provider": "The {provider} account you selected is already linked to another edX account.",
"account.settings.message.managed.settings": "Your profile settings are managed by {managerTitle}. Contact your administrator or {support} for help.",
"account.settings.message.managed.settings.support": "support",
"account.settings.page.heading": "Account Settings",
"account.settings.loading.message": "Loading...",
"account.settings.loading.error": "Error: {error}",
"account.settings.banner.beta.language": "You have set your language to {beta_language}, which is currently not fully translated. You can help us translate this language fully by joining the Transifex community and adding translations from English for learners that speak {beta_language}.",
"account.settings.banner.beta.language.action.switch.back": "Switch Back to {previous_language}",
"account.settings.banner.beta.language.action.help.translate": "Help Translate into {beta_language}",
"account.settings.section.account.information": "Account Information",
"account.settings.section.account.information.description": "These settings include basic information about your account.",
"account.settings.section.profile.information": "Profile Information",
"account.settings.section.demographics.information": "Optional Information",
"account.settings.section.site.preferences": "Site Preferences",
"account.settings.section.linked.accounts": "Linked Accounts",
"account.settings.section.linked.accounts.description": "You can link your identity accounts to simplify signing in to edX.",
"account.settings.field.username": "Username",
"account.settings.field.username.help.text": "The name that identifies you on edX. You cannot change your username.",
"account.settings.field.full.name": "Full name",
"account.settings.field.full.name.empty": "Add name",
"account.settings.field.full.name.help.text": "The name that is used for ID verification and that appears on your certificates.",
"account.settings.field.email": "Email address (Sign in)",
"account.settings.field.email.empty": "Add email address",
"account.settings.field.email.confirmation": "Weve sent a confirmation message to {value}. Click the link in the message to update your email address.",
"account.settings.field.email.help.text": "You receive messages from edX and course teams at this address.",
"account.settings.field.secondary.email": "Recovery email address",
"account.settings.field.secondary.email.empty": "Add a recovery email address",
"account.settings.field.secondary.email.confirmation": "Weve sent a confirmation message to {value}. Click the link in the message to update your recovery email address.",
"account.settings.email.field.confirmation.header": "Pending confirmation",
"account.settings.field.dob": "Year of birth",
"account.settings.field.dob.empty": "Add year of birth",
"account.settings.field.year_of_birth.options.empty": "Select a year of birth",
"account.settings.field.country": "Country",
"account.settings.field.country.empty": "Add country",
"account.settings.field.country.options.empty": "Select a Country",
"account.settings.field.state": "State",
"account.settings.field.state.empty": "Add state",
"account.settings.field.state.options.empty": "Select a State",
"account.settings.field.site.language": "Site language",
"account.settings.field.site.language.help.text": "The language used throughout this site. This site is currently available in a limited number of languages.",
"account.settings.field.education": "Education",
"account.settings.field.education.empty": "Add level of education",
"account.settings.field.education.levels.empty": "Select a level of education",
"account.settings.field.education.levels.p": "Doctorate",
"account.settings.field.education.levels.m": "Master's or professional degree",
"account.settings.field.education.levels.b": "Bachelor's Degree",
"account.settings.field.education.levels.a": "Associate's degree",
"account.settings.field.education.levels.hs": "Secondary/high school",
"account.settings.field.education.levels.jhs": "Junior secondary/junior high/middle school",
"account.settings.field.education.levels.el": "Elementary/primary school",
"account.settings.field.education.levels.none": "No formal education",
"account.settings.field.education.levels.o": "Other education",
"account.settings.field.gender": "Gender",
"account.settings.field.gender.empty": "Add gender",
"account.settings.field.gender.options.empty": "Select a gender",
"account.settings.field.gender.options.f": "Female",
"account.settings.field.gender.options.m": "Male",
"account.settings.field.gender.options.o": "Other",
"account.settings.field.language.proficiencies": "Spoken languages",
"account.settings.field.language.proficiencies.empty": "Add a spoken language",
"account.settings.field.language_proficiencies.options.empty": "Select a Language",
"account.settings.field.time.zone": "Time zone",
"account.settings.field.time.zone.empty": "Set time zone",
"account.settings.field.time.zone.description": "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 browsers local time zone.",
"account.settings.field.time.zone.default": "Default (Local Time Zone)",
"account.settings.field.time.zone.all": "All time zones",
"account.settings.field.time.zone.country": "Country time zones",
"account.settings.section.social.media": "Social Media Links",
"account.settings.section.social.media.description": "Optionally, link your personal accounts to the social media icons on your edX profile.",
"account.settings.field.social.platform.name.linkedin": "LinkedIn",
"account.settings.field.social.platform.name.linkedin.empty": "Add LinkedIn profile",
"account.settings.jump.nav.delete.account": "Delete My Account",
"account.settings.field.social.platform.name.twitter": "Twitter",
"account.settings.field.social.platform.name.twitter.empty": "Add Twitter profile",
"account.settings.field.social.platform.name.facebook": "Facebook",
"account.settings.field.social.platform.name.facebook.empty": "Add Facebook profile",
"account.settings.editable.field.action.save": "Save",
"account.settings.editable.field.action.cancel": "Cancel",
"account.settings.editable.field.action.edit": "Edit",
"account.settings.static.field.empty": "No value set. Contact your {enterprise} administrator to make changes.",
"account.settings.static.field.empty.no.admin": "No value set.",
"account.settings.coaching.consent.welcome.header": "Lets get started.",
"account.settings.coaching.consent.welcome.subheader": "We're here for you from start to finish",
"account.settings.coaching.consent.description": "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.",
"account.settings.coaching.consent.accept-coaching": "Sign up for coaching",
"account.settings.coaching.consent.decline-coaching": "I prefer not to be contacted with free coaching services",
"account.settings.coaching.consent.label.name": "Please confirm your name",
"account.settings.coaching.consent.label.phone-number": "Enter your mobile number",
"account.settings.coaching.consent.success.header": "Success!",
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
"account.settings.coaching.consent.success.continue": "Start my course",
"account.settings.coaching.managed.support": "support",
"account.settings.coaching.managed.alert": "Your name is managed by {managerTitle}. Contact your administrator for help.",
"account.settings.field.phone_number": "Phone Number",
"account.settings.field.phone_number.empty": "Add a phone number",
"account.settings.field.coaching_consent": "Coaching consent",
"account.settings.field.coaching_consent.tooltip": "MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text STOP at anytime to opt-out of messages.",
"account.settings.field.coaching_consent.error": "A valid US phone number is required to opt into coaching",
"account.settings.delete.account.before.proceeding": "Before proceeding, please {actionLink}.",
"account.settings.delete.account.header": "Delete My Account",
"account.settings.delete.account.subheader": "We're sorry to see you go!",
"account.settings.delete.account.text.1": "Please note: Deletion of your account and personal data is permanent and cannot be undone. edX will not be able to recover your account or the data that is deleted.",
"account.settings.delete.account.text.2": "Once your account is deleted, you cannot use it to take courses on the edX app, edx.org, or any other site hosted by edX. This includes access to edx.org from your employers or universitys system and access to private sites offered by MIT Open Learning, Wharton Executive Education, and Harvard Medical School.",
"account.settings.delete.account.text.3.link": "follow the instructions for printing or downloading a certificate",
"account.settings.delete.account.text.warning": "Warning: Account deletion is permanent. Please read the above carefully before proceeding. This is an irreversible action, and you will no longer be able to use the same email on edX.",
"account.settings.delete.account.text.change.instead": "Want to change your email, name, or password instead?",
"account.settings.delete.account.button": "Delete My Account",
"account.settings.delete.account.please.activate": "activate your account",
"account.settings.delete.account.please.unlink": "unlink all social media accounts",
"account.settings.delete.account.modal.header": "Are you sure?",
"account.settings.delete.account.modal.text.1": "You have selected \"Delete My Account\". Deletion of your account and personal data is permanent and cannot be undone. edX will not be able to recover your account or the data that is deleted.",
"account.settings.delete.account.modal.text.2": "If you proceed, you will be unable to use this account to take courses on the edX app, edx.org, or any other site hosted by edX. This includes access to edx.org from your employer's or university's system and access to private sites offered by MIT Open Learning, Wharton Executive Education, and Harvard Medical School.",
"account.settings.delete.account.modal.enter.password": "If you still wish to continue and delete your account, please enter your account password:",
"account.settings.delete.account.modal.confirm.delete": "Yes, Delete",
"account.settings.delete.account.modal.confirm.cancel": "Cancel",
"account.settings.delete.account.error.unable.to.delete": "Unable to delete account",
"account.settings.delete.account.error.no.password": "A password is required",
"account.settings.delete.account.error.invalid.password": "Password is incorrect",
"account.settings.delete.account.error.unable.to.delete.details": "Sorry, there was an error trying to process your request. Please try again later.",
"account.settings.delete.account.modal.after.header": "We're sorry to see you go! Your account will be deleted shortly.",
"account.settings.delete.account.modal.after.text": "Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.",
"account.settings.delete.account.modal.after.button": "Close",
"account.settings.delete.account.text.3": "You may also lose access to verified certificates and other program credentials like MicroMasters certificates. If you want to make a copy of these for your records before proceeding with deletion, {actionLink}.",
"account.settings.message.demographics.service.issue": "An error occurred attempting to retrieve or save your account information. Please try again later.",
"account.settings.field.demographics.gender": "Gender identity",
"account.settings.field.demographics.gender.empty": "Add gender identity",
"account.settings.field.demographics.gender.options.empty": "Select a gender identity",
"account.settings.field.demographics.gender_description": "Gender identity description",
"account.settings.field.demographics.gender_description.empty": "Enter description",
"account.settings.field.demographics.ethnicity": "Race/Ethnicity identity",
"account.settings.field.demographics.ethnicity.empty": "Add race/ethnicity identity",
"account.settings.field.demographics.ethnicity.options.empty": "Select all that apply",
"account.settings.field.demographics.income": "Family income",
"account.settings.field.demographics.income.empty": "Add family income",
"account.settings.field.demographics.income.options.empty": "Select a family income range",
"account.settings.field.demographics.military_history": "U.S. Military status",
"account.settings.field.demographics.military_history.empty": "Add military status",
"account.settings.field.demographics.military_history.options.empty": "Select military status",
"account.settings.field.demographics.learner_education_level": "Your education level",
"account.settings.field.demographics.learner_education_level.empty": "Add education level",
"account.settings.field.demographics.parent_education_level": "Parents/Guardians education level",
"account.settings.field.demographics.parent_education_level.empty": "Add education level",
"account.settings.field.demographics.education_level.options.empty": "Select education level",
"account.settings.field.demographics.work_status": "Employment status",
"account.settings.field.demographics.work_status.empty": "Add employment status",
"account.settings.field.demographics.work_status.options.empty": "Select employment status",
"account.settings.field.demographics.work_status_description": "Employment status description",
"account.settings.field.demographics.work_status_description.empty": "Enter description",
"account.settings.field.demographics.current_work_sector": "Current work industry",
"account.settings.field.demographics.current_work_sector.empty": "Add work industry",
"account.settings.field.demographics.future_work_sector": "Future work industry",
"account.settings.field.demographics.future_work_sector.empty": "Add work industry",
"account.settings.field.demographics.work_sector.options.empty": "Select work industry",
"account.settings.section.demographics.why": "Why does edX collect this information?",
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"account.settings.editable.field.password.reset.button.confirmation.support.link": "technical support",
"account.settings.editable.field.password.reset.button.confirmation": "We've sent a message to {email}. Click the link in the message to reset your password. Didn't receive the message? Contact {technicalSupportLink}.",
"account.settings.editable.field.password.reset.button": "Reset Password",
"account.settings.editable.field.password.reset.button.forbidden": "Your previous request is in progress, please try again in few moments.",
"account.settings.editable.field.password.reset.label": "Password",
"account.settings.sso.link.account": "Sign in with {name}",
"account.settings.sso.account.connected": "Linked",
"account.settings.sso.account.disconnect.error": "There was a problem disconnecting this account. Contact support if the problem persists.",
"account.settings.sso.unlink.account": "Unlink {name} account",
"account.settings.sso.no.providers": "No accounts can be linked at this time.",
"id.verification.existing.request.denied.text": "You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}.",
"id.verification.next": "Next",
"id.verification.requirements.title": "Photo Verification Requirements",
"id.verification.requirements.description": "In order to complete Photo Verification online, you will need the following:",
"id.verification.requirements.card.device.title": "Device with Camera",
"id.verification.requirements.card.device.allow": "Allow",
"id.verification.requirements.card.id.title": "Photo Identification",
"id.verification.requirements.card.id.text": "You need a valid ID that contains your full name and photo.",
"id.verification.privacy.title": "Privacy Information",
"id.verification.privacy.need.photo.question": "Why does edX need my photo?",
"id.verification.privacy.need.photo.answer": "We use your verification photos to confirm your identity and ensure the validity of your certificate.",
"id.verification.privacy.do.with.photo.question": "What does edX do with this photo?",
"id.verification.privacy.do.with.photo.answer": "We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on edX after the verification process is complete.",
"id.verification.existing.request.title": "Identity Verification",
"id.verification.existing.request.pending.text": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.photo.take": "Take Photo",
"id.verification.photo.retake": "Retake Photo",
"id.verification.camera.access.title": "Camera Permissions",
"id.verification.camera.access.title.success": "Camera Access Enabled",
"id.verification.camera.access.title.failed": "Camera Access Failed",
"id.verification.camera.access.click.allow": "Please make sure to click \"Allow\"",
"id.verification.camera.access.enable": "Enable Camera",
"id.verification.camera.access.problems": "Having problems?",
"id.verification.camera.access.skip": "Skip and upload image files instead",
"id.verification.camera.access.success": "Looks like your camera is working and ready.",
"id.verification.camera.access.failure": "It looks like we're unable to access your camera. You will need to upload image files of you and your photo id.",
"id.verification.camera.access.failure.temporary": "It looks like we're unable to access your camera. Please verify that your webcam is connected and that you have allowed your browser to access it.",
"id.verification.camera.access.failure.temporary.chrome": "To enable camera access in Chrome:",
"id.verification.camera.access.failure.temporary.chrome.step1": "Open Chrome.",
"id.verification.camera.access.failure.temporary.chrome.step2": "Navigate to More > Settings.",
"id.verification.camera.access.failure.temporary.chrome.step2.windows": "For Windows: Alt+F, Alt+E, or F10 followed by the spacebar",
"id.verification.camera.access.failure.temporary.chrome.step2.mac": "For Mac: Command+,",
"id.verification.camera.access.failure.temporary.chrome.step3": "Under the \"Privacy and security\" tab, select \"Site Settings\" and then \"Camera.\"",
"id.verification.camera.access.failure.temporary.chrome.step4": "Under \"Blocked,\" find \"edx.org\" and select it.",
"id.verification.camera.access.failure.temporary.chrome.step5": "In the \"Permissions\" section, update the camera permissions to \"Allow.\"",
"id.verification.camera.access.failure.temporary.ie11": "To enable camera access in Internet Explorer:",
"id.verification.camera.access.failure.temporary.ie11.step1": "Open the Flash Player Settings Manager by navigating to Windows Settings > Control Panel > Flash Player.",
"id.verification.camera.access.failure.temporary.ie11.step2": "Select the \"Camera and Mic\" tab, and then select the \"Camera and Microphone Settings by Site\" button.",
"id.verification.camera.access.failure.temporary.ie11.step3": "Choose \"edx.org\" from the list of websites and change the permissions by selecting \"Allow\" in the dropdown menu.",
"id.verification.camera.access.failure.temporary.firefox": "To enable camera access in Firefox:",
"id.verification.camera.access.failure.temporary.firefox.step1": "Open Firefox.",
"id.verification.camera.access.failure.temporary.firefox.step2": "Enter \"about:preferences\" in the URL bar.",
"id.verification.camera.access.failure.temporary.firefox.step3": "Select the \"Privacy & Security\" tab, and navigate to the \"Permissions\" section.",
"id.verification.camera.access.failure.temporary.firefox.step4": "Next to \"Camera,\" select the \"Settings…\" button.",
"id.verification.camera.access.failure.temporary.firefox.step5": "In the search bar, enter \"edx.org.\"",
"id.verification.camera.access.failure.temporary.firefox.step6": "In the status column for \"edx.org,\" select \"Allow\" from the drop down.",
"id.verification.camera.access.failure.temporary.firefox.step7": "Select \"Save Changes.\"",
"id.verification.camera.access.failure.temporary.safari": "To enable camera access in Safari:",
"id.verification.camera.access.failure.temporary.safari.step1": "Open Safari.",
"id.verification.camera.access.failure.temporary.safari.step2": "Click on the Safari app menu, then select \"Preferences.\" You can also use Command+, as a keyboard shortcut.",
"id.verification.camera.access.failure.temporary.safari.step3": "Select the \"Websites\" tab and then select \"Camera.\"",
"id.verification.camera.access.failure.temporary.safari.step4": "Select \"edx.org\" and change the camera permissions to \"Allow.\"",
"id.verification.photo.tips.title": "Helpful Photo Tips",
"id.verification.photo.tips.description": "Next, we'll need you to take a photo of your face. Please review the helpful tips below.",
"id.verification.photo.tips.list.title": "Photo Tips",
"id.verification.photo.tips.list.description": "To take a successful photo, make sure that:",
"id.verification.photo.tips.list.well.lit": "Your face is well-lit.",
"id.verification.photo.tips.list.inside.frame": "Your entire face fits inside the frame.",
"id.verification.portrait.photo.title.camera": "Take a Photo of Yourself",
"id.verification.portrait.photo.title.upload": "Upload a Photo of Yourself",
"id.verification.portrait.photo.preview.alt": "Preview of photo of user's face.",
"id.verification.portrait.photo.instructions.camera": "When your face is in position, use the Take Photo button below to take your photo.",
"id.verification.portrait.photo.instructions.upload": "Please upload a portrait photo. Ensure your entire face fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.camera.help.sight.question": "What if I can't see the camera image or if I can't see my photo to determine which side is visible?",
"id.verification.camera.help.sight.answer.portrait": "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 headshot is approximately 12-18 inches (30-45 centimeters) from the camera, with your head centered relative to the computer screen. If the photos you submit are rejected, try moving the computer or camera orientation to change the lighting angle.",
"id.verification.camera.help.sight.answer.id": "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. 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.",
"id.verification.camera.help.difficulty.question.portrait": "What if I have difficulty holding my head in position relative to the camera?",
"id.verification.camera.help.difficulty.question.id": "What if I have difficulty holding my ID in position relative to the camera?",
"id.verification.camera.help.difficulty.answer": "If you require assistance with taking a photo for submission, contact edX support for additional suggestions.",
"id.verification.id.photo.unclear.question": "Is your ID image not clear or too blurry?",
"id.verification.id.tips.title": "Helpful ID Tips",
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid ID that includes your name. Please have your ID ready.",
"id.verification.id.tips.list.well.lit": "Your ID is well-lit.",
"id.verification.id.tips.list.clear": "Ensure that you can see your photo and clearly read your name.",
"id.verification.id.photo.title.camera": "Take a Photo of Your ID",
"id.verification.id.photo.title.upload": "Upload a Photo of Your ID",
"id.verification.id.photo.preview.alt": "Preview of photo ID.",
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.account.name.title": "Account Name Check",
"id.verification.account.name.instructions": "The name on your account and the name on your ID must be an exact match. If not, please click \"No\" to update your account name.",
"id.verification.account.name.radio.label": "Does the name on your ID match the Account Name below?",
"id.verification.account.name.radio.yes": "Yes",
"id.verification.account.name.radio.no": "No",
"id.verification.account.name.error": "Please update account name to match the name on your ID.",
"id.verification.account.name.warning.prefix": "Please Note:",
"id.verification.account.name.settings": "Account Settings",
"id.verification.account.name.label": "Account Name",
"id.verification.account.name.photo.alt": "Photo of your ID to be submitted.",
"id.verification.account.name.save": "Save and Next",
"id.verification.review.title": "Review Your Photos",
"id.verification.review.description": "Make sure we can verify your identity with the photos and information you have provided.",
"id.verification.review.portrait.label": "Your Portrait",
"id.verification.review.portrait.alt": "Photo of your face to be submitted.",
"id.verification.review.portrait.retake": "Retake Portrait Photo",
"id.verification.review.id.label": "Your Photo ID",
"id.verification.review.id.alt": "Photo of your ID to be submitted.",
"id.verification.review.id.retake": "Retake ID Photo",
"id.verification.review.confirm": "Submit",
"id.verification.submitted.title": "Identity Verification in Progress",
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.",
"id.verification.return.dashboard": "Return to Your Dashboard",
"id.verification.return.course": "Return to Course",
"id.verification.request.camera.access.instructions": "In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. {clickAllow}",
"id.verification.requirements.card.device.text": "You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click {allow}.",
"id.verification.account.name.edit": "Edit{sr}"
"account.settings.message.duplicate.tpa.provider": "إنّ حساب {provider} الذي اخترتَه مرتبط مسبقًا بحساب آخر في edX. ",
"account.settings.message.managed.settings": "تتم إدارة إعدادات الملف الشخصي بواسطة {ManagerTitle}. اتصل بالمسؤول أو {support} للحصول على المساعدة.",
"account.settings.message.managed.settings.support": "الدعم",
"account.settings.page.heading": "إعدادات الحساب",
"account.settings.loading.message": "جاري التحميل...",
"account.settings.loading.error": "خطأ: {error}",
"account.settings.banner.beta.language": "لقد قمت بتعيين لغتك الى {beta_language}، والتي لم تتم ترجمتها بالكامل حاليًا. يمكنك مساعدتنا في ترجمة هذه اللغة بالكامل من خلال الانضمام إلى مجتمع Transifex وإضافة ترجمات من الإنجليزية للمتعلمين الذين يتحدثون {beta_language}.",
"account.settings.banner.beta.language.action.switch.back": "العودة لـِ {previous_language}",
"account.settings.banner.beta.language.action.help.translate": "المساعدة في الترجمة إلى {beta_language}",
"account.settings.section.account.information": "معلومات الحساب",
"account.settings.section.account.information.description": "تتضمن هذه الإعدادات معلومات أساسية عن حسابك.",
"account.settings.section.profile.information": "معلومات الملف الشخصي",
"account.settings.section.demographics.information": "معلومات اختيارية",
"account.settings.section.site.preferences": "تفضيلات الموقع",
"account.settings.section.linked.accounts": "الحسابات المرتبطة",
"account.settings.section.linked.accounts.description": "يمكنك ربط حساباتك الشخصية لتسهيل عملية تسجيل دخولك إلى edX.",
"account.settings.field.username": "اسم المستخدم",
"account.settings.field.username.help.text": "اسم المستخدم الخاص بك الذي يميزك في edX. لا يمكنك تغيير اسم المستخدم الخاص بك لاحقاً.",
"account.settings.field.full.name": "الاسم الكامل",
"account.settings.field.full.name.empty": "إضافة اسم",
"account.settings.field.full.name.help.text": "الاسم المستخدم للتحقق من هويتك والذي سوف يظهر على الشهادات الخاصة بك.",
"account.settings.field.email": "البريد الالكتروني (الدخول)",
"account.settings.field.email.empty": "إضافة عنوان البريد الإلكتروني",
"account.settings.field.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر فوق الرابط في الرسالة لتحديث عنوان بريدك الإلكتروني.",
"account.settings.field.email.help.text": "أنت تتلقى رسائل من edX وفرق المساق على هذا العنوان.",
"account.settings.field.secondary.email": "عنوان البريد الإلكتروني للاسترداد",
"account.settings.field.secondary.email.empty": "إضافة عنوان البريد الإلكتروني للاسترداد",
"account.settings.field.secondary.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر فوق الرابط في الرسالة لتحديث عنوان بريد الاسترداد الإلكتروني.",
"account.settings.email.field.confirmation.header": "تعليق عملية التأكيد",
"account.settings.field.dob": "سنة الميلاد",
"account.settings.field.dob.empty": "إضافة سنة الميلاد",
"account.settings.field.year_of_birth.options.empty": "اختر سنة ميلاد",
"account.settings.field.country": "الدولة",
"account.settings.field.country.empty": "إضافة البلد ",
"account.settings.field.country.options.empty": "اختر دولة",
"account.settings.field.state": "الحالة",
"account.settings.field.state.empty": "إضافة منطقة",
"account.settings.field.state.options.empty": "اختر منطقة",
"account.settings.field.site.language": "لغة الموقع",
"account.settings.field.site.language.help.text": "اللغة المستخدمة في كافة أقسام هذا الموقع. يتوفّر هذا الموقع حاليًا بعدد محدود من اللغات.",
"account.settings.field.education": "المستوى التعليمي",
"account.settings.field.education.empty": "إضافة المستوى التعليمي",
"account.settings.field.education.levels.empty": "اختر المستوى التعليمي",
"account.settings.field.education.levels.p": "دكتوراه",
"account.settings.field.education.levels.m": "ماجستير أو شهادة مهنيّة",
"account.settings.field.education.levels.b": "بكالوريوس",
"account.settings.field.education.levels.a": "زمالة",
"account.settings.field.education.levels.hs": "شهادة الثانوية العامة",
"account.settings.field.education.levels.jhs": "شهادة الثانوية الصغرى/الإعدادية/المرحلة المتوسّطة",
"account.settings.field.education.levels.el": "شهادة المدرسة الابتدائية",
"account.settings.field.education.levels.none": "لا يوجد تعليم رسمي",
"account.settings.field.education.levels.o": "نوع آخر من التعليم",
"account.settings.field.gender": "الجنس",
"account.settings.field.gender.empty": "إضافة الجنس",
"account.settings.field.gender.options.empty": "اختر جنسًا",
"account.settings.field.gender.options.f": "أنثى",
"account.settings.field.gender.options.m": "ذكر",
"account.settings.field.gender.options.o": "آخر",
"account.settings.field.language.proficiencies": "لغة التحدّث",
"account.settings.field.language.proficiencies.empty": "إضافة لغة التحدث",
"account.settings.field.language_proficiencies.options.empty": "اختر لغة",
"account.settings.field.time.zone": "المنطقة الزمنية",
"account.settings.field.time.zone.empty": "ضبط المنطقة الزمنية",
"account.settings.field.time.zone.description": "حدد المنطقة الزمنية لعرض تواريخ المساق. إذا لم تحدد منطقة زمنية، فسيتم عرض تواريخ المساق، بما في ذلك المواعيد النهائية للواجب في المنطقة الزمنية المحلية في المتصفح.",
"account.settings.field.time.zone.default": "الافتراضي (منطقة التوقيت المحلي)",
"account.settings.field.time.zone.all": "جميع المناطق الزمنية",
"account.settings.field.time.zone.country": "المنطقة الزمنية للدولة",
"account.settings.section.social.media": "روابط منصات التواصل الإجتماعي",
"account.settings.section.social.media.description": "اختياريا، قم بربط حساباتك الشخصية بأيقونات منصات التواصل الاجتماعي في ملف التعريف الخاص بك.",
"account.settings.field.social.platform.name.linkedin": "لينكد إن",
"account.settings.field.social.platform.name.linkedin.empty": "إضافة عنوان ملف لينكد إن ",
"account.settings.jump.nav.delete.account": "احذف حسابي",
"account.settings.field.social.platform.name.twitter": "تويتر",
"account.settings.field.social.platform.name.twitter.empty": "إضافة عنوان صفحة تويتر",
"account.settings.field.social.platform.name.facebook": "فيسبوك",
"account.settings.field.social.platform.name.facebook.empty": "إضافة عنوان صفحة فيسبوك",
"account.settings.editable.field.action.save": "حفظ",
"account.settings.editable.field.action.cancel": "إلغاء",
"account.settings.editable.field.action.edit": "تحرير",
"account.settings.static.field.empty": "لم يتم تحديد قيمة، فضلًا اتصل بمدير {enterprise} لتعيين بعض التغييرات.",
"account.settings.static.field.empty.no.admin": "لم يتم تحديد قيمة",
"account.settings.coaching.consent.welcome.header": "لنبدأ",
"account.settings.coaching.consent.welcome.subheader": "نحن هنا لأجلك من البداية حتى النهاية",
"account.settings.coaching.consent.description": "تتضمن برامج البكالوريوس التدريب الذي يركز على مهنتك وتعليمك وكيفية تحقيق نتائج مبهرة من خلال التواصل الشخصي مع خبراء متمرسين. إذا كنت مهتمًا، فقدّم المعلومات أدناه وانقر فوق \"إرسال\"، وسيتصل بك شريكنا في التدريب عبر البريد الإلكتروني و/أو الرسائل النصية لمساعدتك على المضي قدمًا. تنطبق الشروط والأحكام.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* يتم تضمين خدمات التدريب بدون أي تكلفة إضافية للمتعلمين الذين لديهم أرقام هواتف أمريكية. يتضمن التدريب رسائل نصية متكررة. قد تنطبق أسعار الرسائل والبيانات. إيقاف النص لإلغاء الاشتراك.",
"account.settings.coaching.consent.accept-coaching": "سجّل للاستفادة من خدمات التدريب",
"account.settings.coaching.consent.decline-coaching": "أفضّل عدم الاتصال بخدمات التدريب المجانية",
"account.settings.coaching.consent.label.name": "يرجى تأكيد الاسم",
"account.settings.coaching.consent.label.phone-number": "فضلًا أدخل رقم الهاتف ",
"account.settings.coaching.consent.success.header": "تمت العملية بنجاح",
"account.settings.coaching.consent.success.message": "لقد اشتركت في التدريب. يمكنك توقع استلام رسالة عبر البريد الإلكتروني أو الرسائل القصيرة في الأيام المقبلة.",
"account.settings.coaching.consent.success.continue": "البدء في مساقي",
"account.settings.coaching.managed.support": "الدعم",
"account.settings.coaching.managed.alert": "تتم إدارة اسمك بواسطة {ManagerTitle}. اتصل بالمسؤول للحصول على المساعدة.",
"account.settings.field.phone_number": "رقم الهاتف",
"account.settings.field.phone_number.empty": "إضافة رقم الهاتف",
"account.settings.field.coaching_consent": "اتفاقية التدريب",
"account.settings.field.coaching_consent.tooltip": "تتضمن برامج البكالوريوس التدريب القائم على الرسائل النصية الذي يساعدك على إقران التجارب التعليمية مع أهدافك المهنية من خلال النصائح الشخصية. يتم تضمين خدمات التدريب بدون أي تكلفة إضافية، وهي متوفرة للمتعلمين الذين لديهم أرقام هواتف نقالة أمريكية. تنطبق أسعار المراسلة القياسية. أرسل \"قف\" في أي وقت لإلغاء الرسائل.",
"account.settings.field.coaching_consent.error": "مطلوب رقم هاتف أمريكي صالح للدخول في التدريب",
"account.settings.delete.account.before.proceeding": "قبل المتابعة، يرجى {actionLink}.",
"account.settings.delete.account.header": "احذف حسابي",
"account.settings.delete.account.subheader": "نأسف لذهابك!",
"account.settings.delete.account.text.1": "يرجى ملاحظة: إن حذف حسابك والبيانات الشخصية قرار نهائي ولا يمكن التراجع عنه. لن تتمكن edX من استعادة حسابك أو البيانات التي تم حذفها.",
"account.settings.delete.account.text.2": "بمجرد حذف حسابك ، لا يمكنك استخدامه لأخذ دورات تدريبية على تطبيق edX أو edx.org أو أي موقع آخر تستضيفه edX. وهذا يشمل الوصول إلى edx.org من نظام صاحب العمل أو الجامعة والوصول إلى المواقع الخاصة التي تقدمها MIT Open Learning و Wharton Executive Education و Harvard Medical School.",
"account.settings.delete.account.text.3.link": "اتبع الإرشادات التالية لطباعة أو تحميل شهادة.",
"account.settings.delete.account.text.warning": "تحذير: حذف الحساب قرار نهائي. يرجى قراءة ما سبق بعناية قبل المتابعة. هذا إجراء لا رجعة فيه، ولن تتمكن بعد الآن من استخدام البريد الإلكتروني نفسه على edX.",
"account.settings.delete.account.text.change.instead": "هل تريد تغيير البريد الإلكتروني أو الاسم أو كلمة المرور بدلاً من ذلك؟",
"account.settings.delete.account.button": "احذف حسابي",
"account.settings.delete.account.please.activate": "تنشيط حسابك",
"account.settings.delete.account.please.unlink": "إلغاء ربط جميع حسابات التواصل الاجتماعي",
"account.settings.delete.account.modal.header": "هل أنت متأكد؟",
"account.settings.delete.account.modal.text.1": "لقد حددت \"حذف حسابي\". إن حذف حسابك وبياناتك الشخصية قرار نهائي لا يمكن التراجع عنه. لن تتمكن edX من استعادة حسابك أو البيانات التي تم حذفها.",
"account.settings.delete.account.modal.text.2": "عند المتابعة، فلن تتمكن من استخدام هذا الحساب للحصول على مساقات على تطبيق edX أو edx.org أو أي موقع آخر يستضيفه edX. وهذا يشمل الوصول إلى edx.org من نظام صاحب العمل أو الجامعة والوصول إلى المواقع الخاصة التي يقدمها معهد ماساتشوستس للتكنولوجيا التعليم المفتوح، وارتون التعليم التنفيذي، وكلية هارفارد الطبية.",
"account.settings.delete.account.modal.enter.password": "إذا كنت لا تزال ترغب في المتابعة وحذف حسابك ، فيرجى إدخال كلمة مرور حسابك:",
"account.settings.delete.account.modal.confirm.delete": "تعم، أحذف",
"account.settings.delete.account.modal.confirm.cancel": "إلغاء",
"account.settings.delete.account.error.unable.to.delete": "تعذر حذف الحساب",
"account.settings.delete.account.error.no.password": "كلمة المرور مطلوبة",
"account.settings.delete.account.error.invalid.password": "كلمة المرور المدخلة غير صحيحة",
"account.settings.delete.account.error.unable.to.delete.details": "عذراً ، حدث خطأ أثناء محاولة معالجة طلبك. الرجاء معاودة المحاولة في وقت لاحق.",
"account.settings.delete.account.modal.after.header": "نأسف لذهابك! سيتم حذف حسابك قريبا.",
"account.settings.delete.account.modal.after.text": "قد يستغرق حذف الحساب ، بما في ذلك الإزالة من قوائم البريد الإلكتروني ، بضعة أسابيع حتى تتم معالجته بالكامل من خلال نظامنا. إذا كنت ترغب في إلغاء الاشتراك في رسائل البريد الإلكتروني قبل ذلك الحين ، يرجى إلغاء الاشتراك من تذييل أي بريد إلكتروني.",
"account.settings.delete.account.modal.after.button": "إغلاق ",
"account.settings.delete.account.text.3": "قد تفقد أيضًا إمكانية الوصول إلى الشهادات التي تم التحقق منها وبيانات اعتماد البرامج الأخرى مثل شهادات MicroMasters. إذا أردت عمل نسخة من هذه السجلات قبل متابعة الحذف، {actionLink}.",
"account.settings.message.demographics.service.issue": "حدث خطأ أثناء محاولة استرداد معلومات حسابك أو حفظها. يرجى المحاولة مرة أخرى لاحقًا.",
"account.settings.field.demographics.gender": "هوية الجنس",
"account.settings.field.demographics.gender.empty": "إضافة هوية الجنس",
"account.settings.field.demographics.gender.options.empty": "حدد هوية الجنس",
"account.settings.field.demographics.gender_description": "وصف هوية الجنس",
"account.settings.field.demographics.gender_description.empty": "أدخل وصفًا",
"account.settings.field.demographics.ethnicity": "هوية العرق/ الأصل",
"account.settings.field.demographics.ethnicity.empty": "إضافة هوية العرق/الأصل",
"account.settings.field.demographics.ethnicity.options.empty": "فضلًا اختر جميع ما ينطبق",
"account.settings.field.demographics.income": "الدخل المادي الأسري",
"account.settings.field.demographics.income.empty": "إضافة الدخل المادي الأسري",
"account.settings.field.demographics.income.options.empty": "حدد نطاق الدخل المادي للأسرة",
"account.settings.field.demographics.military_history": "حالة الخدمة العسكرية في الولايات المتحدة الأمريكية",
"account.settings.field.demographics.military_history.empty": "إضافة حالة الخدمة العسكرية",
"account.settings.field.demographics.military_history.options.empty": "اختر حالة الخدمة العسكرية",
"account.settings.field.demographics.learner_education_level": "مؤهلك التعليمي",
"account.settings.field.demographics.learner_education_level.empty": "إضافة مؤهلك التعليمي",
"account.settings.field.demographics.parent_education_level": "مؤهل الوالدين/الآوصياء التعليمي",
"account.settings.field.demographics.parent_education_level.empty": "إضافة المؤهل التعليمي",
"account.settings.field.demographics.education_level.options.empty": "حدد موهلاً تعليميًا",
"account.settings.field.demographics.work_status": "الحالة الوظيفية",
"account.settings.field.demographics.work_status.empty": "إضافة الحالة الوظيفية",
"account.settings.field.demographics.work_status.options.empty": "فضلًا حدد حالتك الوظيفية",
"account.settings.field.demographics.work_status_description": "وصف الحالة الوظيفية",
"account.settings.field.demographics.work_status_description.empty": "أدخل وصفًا",
"account.settings.field.demographics.current_work_sector": "مجال العمل الحالي",
"account.settings.field.demographics.current_work_sector.empty": "إضافة مجال العمل",
"account.settings.field.demographics.future_work_sector": "مجال العمل المستقبلي",
"account.settings.field.demographics.future_work_sector.empty": "إضافة مجال العمل",
"account.settings.field.demographics.work_sector.options.empty": "حدد مجال العمل",
"account.settings.section.demographics.why": "لماذا تجمع edX هذه المعلومات؟",
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في نص الرابط. الرجاء التحقق من الرابط والمحاولة مجددا.",
"account.settings.editable.field.password.reset.button.confirmation.support.link": "الدعم الفني",
"account.settings.editable.field.password.reset.button.confirmation": "لقد أرسلنا رسالة إلى {email}. انقر فوق الرابط في الرسالة لإعادة تعيين كلمة المرور. إذا لم يتم استلام الرسالة؟ اتصل بـ {technicalSupportLink}.",
"account.settings.editable.field.password.reset.button": "تغيير كلمة المرور",
"account.settings.editable.field.password.reset.button.forbidden": "طلبك السابق قيد التقدم، يرجى إعادة المحاولة بعد لحظات قليلة.",
"account.settings.editable.field.password.reset.label": "كلمة المرور",
"account.settings.sso.link.account": "تسجيل الدخول كـ {name}",
"account.settings.sso.account.connected": "مربوط",
"account.settings.sso.account.disconnect.error": "حدثت مشكلة أثناء قطع اتصال هذا الحساب، اتصل بالدعم عند استمرار المشكلة.",
"account.settings.sso.unlink.account": "إلغاء ربط حساب {name} ",
"account.settings.sso.no.providers": "لا يمكن ربط أية حسابات حاليًا",
"id.verification.access.blocked.denied": "لا يمكنك التحقق من هويتك في الوقت الحالي. إذا لم تقم بعد بتنشيط حسابك، فيرجى التحقق من مجلد البريد المهمل للحصول على رسالة التفعيل من {email}.",
"id.verification.next": "التالي",
"id.verification.example.card.alt": "مثال على بطاقة هوية صحيحة بالاسم الكامل وصورة.",
"id.verification.requirements.title": "متطلبات التحقق من الصورة",
"id.verification.requirements.description": "يجب عليك اتباع الآتي لإكمال عملية التحقق الإلكتروني من هويتك:",
"id.verification.requirements.card.device.title": "جهاز مزود بكاميرا",
"id.verification.requirements.card.device.allow": "موافق",
"id.verification.requirements.card.id.title": "صورة التحقق من الشخصية.",
"id.verification.requirements.card.id.text": "تحتاج إلى بطاقة هوية صحيحة للتحقق تحوي اسمك الكامل وصورتك.",
"id.verification.privacy.title": "بيانات الخصوصية.",
"id.verification.privacy.need.photo.question": "لماذا تحتاج edX إلى صورتي؟ ",
"id.verification.privacy.need.photo.answer": "نستخدم صور التحقق الخاصة بك لتأكيد هويتك والتأكد من صحة شهادتك.",
"id.verification.privacy.do.with.photo.question": "ما الذي تفعله edX بهذه الصورة؟",
"id.verification.privacy.do.with.photo.answer": "سنقوم بتشفير صورتك بأمان وإرسالها لخدمة التحقق للمراجعة . لن يتم حفظ صورتك ومعلوماتك أو عرضها في أي مكان على edX بعد اكتمال عملية التحقق.",
"id.verification.access.blocked.title": "التحقق من الهوية",
"id.verification.access.blocked.enrollment": "أنت الآن ملتحق بمساق يتطلب التحقق من الهوية.",
"id.verification.access.blocked.pending": "لقد قمت بالفعل بإرسال معلومات التحقق الخاصة بك. ستصلك رسالة على لوحة المعلومات عند اكتمال عملية التحقق (عادةً خلال 5 أيام).",
"id.verification.photo.take": "التقاط صورة ",
"id.verification.photo.retake": "إعادة التقاط الصورة",
"id.verification.photo.enable.detection": "تمكين خاصية التعرف على الوجه",
"id.verification.photo.enable.detection.portrait.help.text": "عند تحديد هذا الخيار، فسيظهر مربع حول وجهك. يمكن رؤية وجهك بوضوح إذا كان المربع المحيط به أزرق. إذا لم يكن وجهك في وضع جيد أو إذا لم يكن قابلاً للاكتشاف، فسيكون المربع باللون الأحمر.",
"id.verification.photo.enable.detection.id.help.text": "عند تحديد هذا الخيار، فسيظهر مربع حول صورة وجهك في بطاقة الهوية. يمكن رؤية وجهك بوضوح إذا كان المربع المحيط به أزرق. إذا لم يكن وجهك في وضع جيد أو إذا لم يكن قابلاً للاكتشاف، فسيكون المربع باللون الأحمر.",
"id.verification.photo.feedback.correct": "وضع الوجه جيد.",
"id.verification.photo.feedback.two.faces": "تم تحديد أكثر من وجه.",
"id.verification.photo.feedback.no.faces": "لم يتم تحديد أي وجه.",
"id.verification.photo.feedback.top.left": "وضع خاطئ. أعلى اليسار.",
"id.verification.photo.feedback.top.center": "وضع خاطئ. أعلى الوسط.",
"id.verification.photo.feedback.top.right": "وضع خاطئ. أعلى اليمين.",
"id.verification.photo.feedback.center.left": "وضع خاطئ. وسط اليسار.",
"id.verification.photo.feedback.center.center": "وضع خاطئ. قريب جدًا من الكاميرا.",
"id.verification.photo.feedback.center.right": "وضع خاطئ. وسط اليمين.",
"id.verification.photo.feedback.bottom.left": "وضع خاطئ. أسفل اليسار.",
"id.verification.photo.feedback.bottom.center": "وضع خاطئ. أسفل الوسط.",
"id.verification.photo.feedback.bottom.right": "وضع خاطئ. أسفل اليمين.",
"id.verification.camera.access.title": "صلاحيات الكاميرا",
"id.verification.camera.access.title.success": "تمكين الوصول للكاميرا",
"id.verification.camera.access.title.failed": "تعذّر الوصول للكاميرا.",
"id.verification.camera.access.click.allow": "فضلًا تأكد من اختيار الأمر \"السماح\"",
"id.verification.camera.access.enable": "تمكين الكاميرا",
"id.verification.camera.access.problems": "هل تواجه أية مشكلة؟",
"id.verification.camera.access.skip": "قم بتخطي ملفات الصور وتحميلها بدلاً من ذلك",
"id.verification.camera.access.success": "يبدو أن الكاميرا تعمل وجاهزة.",
"id.verification.camera.access.failure": "يبدو أننا غير قادرين على الوصول إلى الكاميرا. ستحتاج إلى تحميل ملفات الصور الخاصة بك و معرّف الصور الخاص بك.",
"id.verification.camera.access.failure.temporary": "يبدو أننا غير قادرين على الوصول إلى الكاميرا. يرجى التحقق من أن كاميرا الويب متصلة ومن أنك سمحت للمتصفح بالوصول إليها.",
"id.verification.camera.access.failure.temporary.chrome": "تمكين الوصول للكاميرا في متصفّح كروم: ",
"id.verification.camera.access.failure.temporary.chrome.step1": "فتح متصفّح كروم.",
"id.verification.camera.access.failure.temporary.chrome.step2": "توجه إلى المزيد > الإعدادات.",
"id.verification.camera.access.failure.temporary.chrome.step2.windows": "لنظام ويندوز: Alt+F أو Alt+E أو F10 متبوعاً بـ SPACEBAR",
"id.verification.camera.access.failure.temporary.chrome.step2.mac": "لنظام ماك: Command+,",
"id.verification.camera.access.failure.temporary.chrome.step3": "ضمن علامة التبويب \"الخصوصية والأمان\"، حدد \"إعدادات الموقع\" ثم \"الكاميرا\".",
"id.verification.camera.access.failure.temporary.chrome.step4": "ضمن \"قائمة الحظر\"، ابحث عن \"edx.org\" وحدده.",
"id.verification.camera.access.failure.temporary.chrome.step5": "في القسم \"الصلاحيات\"، قم بتحديث صلاحيات الكاميرا إلى \"السماح\".",
"id.verification.camera.access.failure.temporary.ie11": "تمكين الوصول للكاميرا في متصفح انترنت إكسبلورر:",
"id.verification.camera.access.failure.temporary.ie11.step1": "افتح إدارة إعدادات Flash Player عن طريق الانتقال إلى إعدادات Windows > لوحة التحكم > Flash Player.",
"id.verification.camera.access.failure.temporary.ie11.step2": "حدد علامة التبويب \"Camera and Mic\" (الكاميرا والميكروفون)، ثم حدد الزر \"Camera and Microphone Settings by Site\" (إعدادات الكاميرا والميكروفون حسب الموقع).",
"id.verification.camera.access.failure.temporary.ie11.step3": "اختر \"edx.org\" من قائمة مواقع ويب وقم بتغيير الصلاحيات من خلال تحديد \"السماح\" في القائمة المنسدلة.",
"id.verification.camera.access.failure.temporary.firefox": "لتمكين الوصول للكاميرا في متصفّح فايرفوكس: ",
"id.verification.camera.access.failure.temporary.firefox.step1": "فتح متصفّح فايرفوكس.",
"id.verification.camera.access.failure.temporary.firefox.step2": "أدخل \"about:preferences\" في شريط عنوان الرابط.",
"id.verification.camera.access.failure.temporary.firefox.step3": "حدد علامة التبويب \"الخصوصية والأمان\"، وانتقل إلى القسم \"الصلاحيات\".",
"id.verification.camera.access.failure.temporary.firefox.step4": "بجوار \"Camera\" (الكاميرا)، حدد \"Settings…\" (الإعدادات…) الزر.",
"id.verification.camera.access.failure.temporary.firefox.step5": "في شريط البحث أدخل \"edx.org.\"",
"id.verification.camera.access.failure.temporary.firefox.step6": "في عمود الحالة لـ \"edx.org,\" حدد \"السماح\" من القائمة المنسدلة.",
"id.verification.camera.access.failure.temporary.firefox.step7": "اختر \"حفظ التغييرات.\"",
"id.verification.camera.access.failure.temporary.safari": "تمكين الوصول للكاميرا في متصفّح سفاري: ",
"id.verification.camera.access.failure.temporary.safari.step1": "فتح متصفّح سفاري.",
"id.verification.camera.access.failure.temporary.safari.step2": "انقر فوق قائمة تطبيق سفاري، ثم حدد \"Preferences\" (التفضيلات). يمكنك أيضاً استخدام الأمرCommand+ كاختصار للوحة المفاتيح",
"id.verification.camera.access.failure.temporary.safari.step3": "حدد علامة التبويب \"مواقع ويب\" ثم حدد \"كاميرا\".",
"id.verification.camera.access.failure.temporary.safari.step4": "حدد \"edx.org\" وقم بتغيير صلاحيات الكاميرا إلى \"السماح\".",
"id.verification.photo.tips.title": "تلميحات مفيدة للصورة",
"id.verification.photo.tips.description": "بعد ذلك، سنحتاج منك التقاط صورة لوجهك. يرجى مراجعة التلميحات المفيدة أدناه.",
"id.verification.photo.tips.list.title": "تلميحات الصورة",
"id.verification.photo.tips.list.description": "لالتقاط صورة ناجحة، يُرجى التأكّد ممّا يلي:",
"id.verification.photo.tips.list.well.lit": "أنّ الإضاءة جيّدة على وجهك.",
"id.verification.photo.tips.list.inside.frame": "أنّ وجهك داخل إطار الصورة بالكامل.",
"id.verification.portrait.photo.title.camera": "التقط صورة لنفسك",
"id.verification.portrait.photo.title.upload": "ارفع صورتك",
"id.verification.portrait.photo.preview.alt": "معاينة صورة وجه المستخدم.",
"id.verification.portrait.photo.instructions.camera": "عندما يكون وجهك في موضعه، استخدم زر التقاط صورة أدناه لالتقاط الصورة.",
"id.verification.portrait.photo.instructions.upload": "يرجى تحميل صورة شخصية. تأكد من أن وجهك بالكامل يتناسب مع الإطار ومن أن الإضاءة جيدة. (الصيغ المدعومة: .jpg، .jpeg، .png)",
"id.verification.camera.help.sight.question": "ماذا لو لم أتمكن من رؤية صورة الكاميرا ؟ أو إذا لم أتمكن من رؤية صورتي لتحديد أي جانب مرئي؟",
"id.verification.camera.help.sight.answer.portrait": "قد تتمكن من إكمال إجراء التقاط الصور من دون مساعدة، ولكن قد يتطلب الأمر بضع محاولات ضبط وضع الكاميرا بشكل صحيح. يختلف وضع الكاميرا المثالي باختلاف الكمبيوتر، ولكن بشكل عام، يكون أفضل موضع للتصوير في الرأس هو 12 إلى 18 بوصة (30-45 سم) تقريبًا من الكاميرا، مع وضع الرأس في المنتصف بالنسبة إلى شاشة الكمبيوتر. إذا تم رفض الصور التي ترسلها، فحاول تحريك اتجاه الكمبيوتر أو الكاميرا لتغيير زاوية الإضاءة.",
"id.verification.camera.help.sight.answer.id": "قد تتمكن من إكمال إجراء التقاط الصور من دون مساعدة، ولكن قد يتطلب الأمر بضع محاولات لضبط وضع الكاميرا بشكل صحيح. يختلف الوضع الأمثل للكاميرا باختلاف جهاز الكمبيوتر، ولكن بشكل عام، يكون أفضل وضع لصورة بطاقة تعريف من 8 إلى 12 بوصة (من 20 إلى 30 سم) عن الكاميرا، مع وضع بطاقة الهوية في الوسط بالنسبة للكاميرا. إذا تم رفض الصور التي ترسلها، فحاول تحريك اتجاه الكمبيوتر أو الكاميرا لتغيير زاوية الإضاءة. إن السبب الأكثر شيوعاً للرفض هو عدم القدرة على قراءة النص الموجود على بطاقة الهوية.",
"id.verification.camera.help.difficulty.question.portrait": "ماذا لو واجهت صعوبة في تثبيت رأسي في الموضع المناسب للكاميرا؟",
"id.verification.camera.help.difficulty.question.id": "ماذا لو واجهت صعوبة في تثبيت بطاقة هويتي في الموضع المناسب للكاميرا؟",
"id.verification.camera.help.difficulty.answer": "إذا كنت بحاجة إلى المساعدة في التقاط صورة للتقديم، فاتصل بدعم edX للحصول على اقتراحات إضافية.",
"id.verification.id.photo.unclear.question": "هل صورة بطاقة الهوية غير واضحة أو ضبابية؟",
"id.verification.id.tips.title": "تلميحات مفيدة للهوية ",
"id.verification.id.tips.description": "بعد ذلك، سنكون بحاجة إلى التقاط صورة لبطاقة هوية صالحة تتضمن اسمك الكامل وصورتك. يرجى تجهيز بطاقة هويتك.",
"id.verification.id.tips.list.well.lit": "إضاءة بطاقة هويتك جيدة.",
"id.verification.id.tips.list.clear": "تأكد من قدرتك على رؤية صورتك وقراءة اسمك بوضوح.",
"id.verification.id.photo.title.camera": "التقط صورة لبطاقتك الشخصية",
"id.verification.id.photo.title.upload": "حمّل صورة لهويتك",
"id.verification.id.photo.preview.alt": "معاينة صورة الهوية.",
"id.verification.id.photo.instructions.camera": "عندما تكون بطاقة هويتك في موضعها، استخدم زر التقاط صورة أدناه لالتقاط الصورة.",
"id.verification.id.photo.instructions.upload": "يرجى تحميل صورة بطاقة هوية. تأكد من أن صورة البطاقة بالكامل تتناسب مع الإطار ومن أن الإضاءة جيدة. يجب أن يكون حجم الصورة أقل من 10 ميغابايت (الصيغ المدعومة: .jpg، .jpeg، .png)",
"id.verification.id.photo.instructions.upload.error": "الملف الذي حددته كبير جداً. يرجى إعادة المحاولة باستخدام ملف ذو حجم أقل من 10 ميجابايت.",
"id.verification.account.name.title": "التحقق من اسم الحساب",
"id.verification.account.name.instructions": "يجب أن يكون الاسم الموجود على حسابك والاسم الموجود على المعرّف الخاص بك متطابقًا تمامًا. إذا لم يكن الأمر كذلك، فيرجى النقر فوق \"لا\" لتحديث اسم حسابك.",
"id.verification.account.name.radio.label": "هل يتطابق الاسم الموجود على هويتك مع اسم الحساب أدناه؟",
"id.verification.account.name.radio.yes": "نعم",
"id.verification.account.name.radio.no": "لا",
"id.verification.account.name.error": "يرجى تحديث اسم الحساب لمطابقة الاسم على بطاقة الهوية.",
"id.verification.account.name.warning.prefix": "يُرجى الملاحظة:",
"id.verification.account.name.settings": "إعدادات الحساب",
"id.verification.account.name.label": "اسم الحساب",
"id.verification.account.name.photo.alt": "صورة من هويتك للتقديم.",
"id.verification.account.name.save": "حفظ ثم التالي",
"id.verification.review.title": "مراجعة صورك",
"id.verification.review.description": "يُرجى التأكّد من أنّ الصور والمعلومات التي قدّمتها تمكّننا من التحقّق من هويّتك. ",
"id.verification.review.portrait.label": "صورتك الشخصية",
"id.verification.review.portrait.alt": "صورة شخصية للتقديم.",
"id.verification.review.portrait.retake": "إعادة التقاط الصورة شخصية",
"id.verification.review.id.label": "معرّف صورتك",
"id.verification.review.id.alt": "صورة من هويتك للتقديم.",
"id.verification.review.id.retake": "إعادة التقاط صورة الهوية",
"id.verification.review.confirm": "إرسال",
"id.verification.submission.alert.error.face": "مطلوب صورة لوجهك. يرجى إعادة التقاط الصورة الشخصية.",
"id.verification.submission.alert.error.id": "مطلوب صورة لبطاقة هويتك. يرجى إعادة التقاط صورة بطاقة الهوية.",
"id.verification.submission.alert.error.name": "مطلوب اسم حساب صالح. يرجى تحديث اسم حسابك لمطابقة الاسم على هويتك.",
"id.verification.review.error": "صفحة فريق دعم edX ",
"id.verification.submitted.title": "جارِ التحقق من الهوية",
"id.verification.submitted.text": "لقد تلقينا معلوماتك وجاري الآن العمل على التحقق من هويتك. ستصلك رسالة على لوحة المعلومات عند اكتمال عملية التحقق (عادةً خلال 5 أيام). في غضون ذلك، لا يزال بإمكانك الوصول إلى كل محتوى المساق المتوفر.",
"id.verification.return.dashboard": "العودة إلى لوحة المعلومات",
"id.verification.return.course": "العودة للمساق",
"id.verification.request.camera.access.instructions": "لالتقاط صورة باستخدام كاميرا الويب، قد تتلقى طلب المتصفح للوصول إلى الكاميرا. {clickAllow}",
"id.verification.requirements.card.device.text": "أنت بحاجة إلى جهاز مزود بكاميرا. إذا تلقيت طلب المتصفح للوصول إلى الكاميرا، فيرجى التأكد من النقر فوق {السماح}.",
"idv.submission.alert.error": "\nواجهنا خطأ فني أثناء محاولة رفع طلب التحقق من الهوية\nقد تكون هذه مشكلة مؤقتة، لذا يرجى المحاولة مرة أخرى بعد بضع دقائق\nعند استمرار المشكلة يرجى الانتقال إلى {support_link} للحصول على المساعدة",
"id.verification.account.name.edit": "تعديل{sr}"
}

View File

@@ -11,7 +11,7 @@
"account.settings.section.account.information": "Información de la cuenta",
"account.settings.section.account.information.description": "Estas configuraciones incluyen información básica sobre tu cuenta.",
"account.settings.section.profile.information": "Información del perfil",
"account.settings.section.demographics.information": "Optional Information",
"account.settings.section.demographics.information": "Información opcional",
"account.settings.section.site.preferences": "Preferencias del sitio",
"account.settings.section.linked.accounts": "Cuentas vinculadas",
"account.settings.section.linked.accounts.description": "Puedes vincular tus cuentas de redes sociales para simplificar el proceso de iniciar sesión en edX.",
@@ -57,7 +57,7 @@
"account.settings.field.gender.options.f": "Femenino",
"account.settings.field.gender.options.m": "Masculino",
"account.settings.field.gender.options.o": "Otro",
"account.settings.field.language.proficiencies": "Idiomas hablados",
"account.settings.field.language.proficiencies": "Spoken language",
"account.settings.field.language.proficiencies.empty": "Agregar un idioma hablado",
"account.settings.field.language_proficiencies.options.empty": "Selecciona lenguaje",
"account.settings.field.time.zone": "Zona horaria",
@@ -89,7 +89,7 @@
"account.settings.coaching.consent.label.name": "Por favor confirme su nombre",
"account.settings.coaching.consent.label.phone-number": "Ingrese su número de teléfono móvil",
"account.settings.coaching.consent.success.header": "¡Éxito!",
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
"account.settings.coaching.consent.success.message": "Estás inscrito para coaching. Puedes esperar un mensaje por correo electrónico o SMS en los próximos días.",
"account.settings.coaching.consent.success.continue": "Iniciar mi curso",
"account.settings.coaching.managed.support": "soporte",
"account.settings.coaching.managed.alert": "{ManagerTitle} administra su Nombre. Póngase en contacto con su administrador para obtener ayuda.",
@@ -117,43 +117,43 @@
"account.settings.delete.account.modal.confirm.cancel": "Cancelar",
"account.settings.delete.account.error.unable.to.delete": "No es posible eliminar esta cuenta",
"account.settings.delete.account.error.no.password": "Se requiere una contraseña",
"account.settings.delete.account.error.invalid.password": "Password is incorrect",
"account.settings.delete.account.error.invalid.password": "Contraseña incorrecta",
"account.settings.delete.account.error.unable.to.delete.details": "Ocurrió un error al procesar tu solicitud. Por favor, intente nuevamente más tarde.",
"account.settings.delete.account.modal.after.header": "¡Sentimos que te vayas! Tu cuenta será eliminada en breve.",
"account.settings.delete.account.modal.after.text": "La eliminación de cuenta, incluyendo la eliminación de las listas de correo electrónico, puede tardar unas semanas en procesarse totalmente en nuestro sistema. Si quieres renunciar a recibir correos antes de que la eliminación se haya completado, por favor date de baja mediante el enlace que aparece al final de los correos.",
"account.settings.delete.account.modal.after.button": "Cerrar",
"account.settings.delete.account.text.3": "Puede que también pierdas el acceso a los certificados verificados y otros certificados de programas como los de los MicroMasters. Si quieres hacer una copia de dichos certificados para tus archivos antes de proceder a la eliminación, {actionLink}.",
"account.settings.message.demographics.service.issue": "An error occurred attempting to retrieve or save your account information. Please try again later.",
"account.settings.field.demographics.gender": "Gender identity",
"account.settings.field.demographics.gender.empty": "Add gender identity",
"account.settings.field.demographics.gender.options.empty": "Select a gender identity",
"account.settings.field.demographics.gender_description": "Gender identity description",
"account.settings.field.demographics.gender_description.empty": "Enter description",
"account.settings.field.demographics.ethnicity": "Race/Ethnicity identity",
"account.settings.field.demographics.ethnicity.empty": "Add race/ethnicity identity",
"account.settings.field.demographics.ethnicity.options.empty": "Select all that apply",
"account.settings.field.demographics.income": "Family income",
"account.settings.field.demographics.income.empty": "Add family income",
"account.settings.field.demographics.income.options.empty": "Select a family income range",
"account.settings.field.demographics.military_history": "U.S. Military status",
"account.settings.field.demographics.military_history.empty": "Add military status",
"account.settings.field.demographics.military_history.options.empty": "Select military status",
"account.settings.field.demographics.learner_education_level": "Your education level",
"account.settings.field.demographics.learner_education_level.empty": "Add education level",
"account.settings.field.demographics.parent_education_level": "Parents/Guardians education level",
"account.settings.field.demographics.parent_education_level.empty": "Add education level",
"account.settings.field.demographics.education_level.options.empty": "Select education level",
"account.settings.field.demographics.work_status": "Employment status",
"account.settings.field.demographics.work_status.empty": "Add employment status",
"account.settings.field.demographics.work_status.options.empty": "Select employment status",
"account.settings.field.demographics.work_status_description": "Employment status description",
"account.settings.field.demographics.work_status_description.empty": "Enter description",
"account.settings.field.demographics.current_work_sector": "Current work industry",
"account.settings.field.demographics.current_work_sector.empty": "Add work industry",
"account.settings.field.demographics.future_work_sector": "Future work industry",
"account.settings.field.demographics.future_work_sector.empty": "Add work industry",
"account.settings.field.demographics.work_sector.options.empty": "Select work industry",
"account.settings.section.demographics.why": "Why does edX collect this information?",
"account.settings.message.demographics.service.issue": "Ocurrió un error al intentar recuperar o guardar la información de tu cuenta. Por favor inténtalo más tarde.",
"account.settings.field.demographics.gender": "Identidad de género",
"account.settings.field.demographics.gender.empty": "Añade identidad de género",
"account.settings.field.demographics.gender.options.empty": "Selecciona una identidad de género",
"account.settings.field.demographics.gender_description": "Descripción de identidad de género",
"account.settings.field.demographics.gender_description.empty": "Ingresa descripción",
"account.settings.field.demographics.ethnicity": "Identidad étnica/raza",
"account.settings.field.demographics.ethnicity.empty": "Añade identidad étnica/raza",
"account.settings.field.demographics.ethnicity.options.empty": "Selecciona todas las que apliquen",
"account.settings.field.demographics.income": "Ingreso familiar",
"account.settings.field.demographics.income.empty": "Añade ingreso familiar",
"account.settings.field.demographics.income.options.empty": "Selecciona un rango de ingreso familiar",
"account.settings.field.demographics.military_history": "Estatus militar en EE.UU.",
"account.settings.field.demographics.military_history.empty": "Añade estatus militar",
"account.settings.field.demographics.military_history.options.empty": "Selecciona estatus militar",
"account.settings.field.demographics.learner_education_level": "Tu nivel educacional",
"account.settings.field.demographics.learner_education_level.empty": "Añade nivel educacional",
"account.settings.field.demographics.parent_education_level": "Nivel educacional de padres/tutores",
"account.settings.field.demographics.parent_education_level.empty": "Añade nivel educacional",
"account.settings.field.demographics.education_level.options.empty": "Selecciona nivel educacional",
"account.settings.field.demographics.work_status": "Estatus laboral",
"account.settings.field.demographics.work_status.empty": "Añade estatus laboral",
"account.settings.field.demographics.work_status.options.empty": "Selecciona estatus laboral",
"account.settings.field.demographics.work_status_description": "Descripción estatus laboral",
"account.settings.field.demographics.work_status_description.empty": "Ingresa descripción",
"account.settings.field.demographics.current_work_sector": "Área profesional actual",
"account.settings.field.demographics.current_work_sector.empty": "Añade área profesional",
"account.settings.field.demographics.future_work_sector": "Área profesional futura",
"account.settings.field.demographics.future_work_sector.empty": "Añade área profesional",
"account.settings.field.demographics.work_sector.options.empty": "Selecciona área profesional",
"account.settings.section.demographics.why": "¿Por qué edX obtiene esta información?",
"error.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, comprueba la URL y vuelve a intentarlo.",
"account.settings.editable.field.password.reset.button.confirmation.support.link": "soporte técnico",
"account.settings.editable.field.password.reset.button.confirmation": "Hemos mandado un mensaje a {email}. Haz clic en el enlace en el mensaje para restablecer tu contraseña. ¿No recibiste el mensaje? Contáctate con {technicalSupportLink}.",
@@ -165,110 +165,133 @@
"account.settings.sso.account.disconnect.error": "Hubo un problema al desconectar esta Cuenta. Si el problema persiste, contacte soporte.",
"account.settings.sso.unlink.account": "Desvincular la cuenta de {accountName} ",
"account.settings.sso.no.providers": "No se pueden vincular cuentas en este momento.",
"id.verification.existing.request.denied.text": "You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}.",
"id.verification.next": "Next",
"id.verification.requirements.title": "Photo Verification Requirements",
"id.verification.requirements.description": "In order to complete Photo Verification online, you will need the following:",
"id.verification.requirements.card.device.title": "Device with Camera",
"id.verification.requirements.card.device.allow": "Allow",
"id.verification.requirements.card.id.title": "Photo Identification",
"id.verification.requirements.card.id.text": "You need a valid ID that contains your full name and photo.",
"id.verification.privacy.title": "Privacy Information",
"id.verification.privacy.need.photo.question": "Why does edX need my photo?",
"id.verification.privacy.need.photo.answer": "We use your verification photos to confirm your identity and ensure the validity of your certificate.",
"id.verification.privacy.do.with.photo.question": "What does edX do with this photo?",
"id.verification.privacy.do.with.photo.answer": "We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on edX after the verification process is complete.",
"id.verification.existing.request.title": "Identity Verification",
"id.verification.existing.request.pending.text": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.photo.take": "Take Photo",
"id.verification.photo.retake": "Retake Photo",
"id.verification.camera.access.title": "Camera Permissions",
"id.verification.access.blocked.denied": "You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}.",
"id.verification.next": "Siguiente",
"id.verification.example.card.alt": "Example of a valid identification card with a full name and photo.",
"id.verification.requirements.title": "Requerimientos de verificación por foto",
"id.verification.requirements.description": "Para completar la verificación por foto en línea, necesitarás lo siguiente:",
"id.verification.requirements.card.device.title": "Dispositivo con cámara",
"id.verification.requirements.card.device.allow": "Permitir",
"id.verification.requirements.card.id.title": "Identificación por foto",
"id.verification.requirements.card.id.text": "You need a valid identification card that contains your full name and photo.",
"id.verification.privacy.title": "Información de privacidad",
"id.verification.privacy.need.photo.question": "¿Por qué edX necesita mi foto?",
"id.verification.privacy.need.photo.answer": "Utilizamos tus fotos de verificación para confirmar tu identidad y garantizar la validez de tu certificado.",
"id.verification.privacy.do.with.photo.question": "¿Qué hace edX con esta foto?",
"id.verification.privacy.do.with.photo.answer": "Encriptamos de forma segura tu foto y la enviamos a nuestro servicio de autorización para su revisión. Tu foto e información no se guardan ni se ven en ninguna parte de edX después de que se completa el proceso de verificación.",
"id.verification.access.blocked.title": "Verificación de identidad",
"id.verification.access.blocked.enrollment": "You are not currently enrolled in a course that requires identity verification.",
"id.verification.access.blocked.pending": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.photo.take": "Tomar la foto",
"id.verification.photo.retake": "Tomar nuevamente la foto",
"id.verification.photo.enable.detection": "Habilitar la detección de rostro",
"id.verification.photo.enable.detection.portrait.help.text": "Si está marcada, aparecerá un cuadro alrededor de tu cara. Tu rostro se puede ver claramente si el cuadro que lo rodea es azul. Si Tu cara no está en una buena posición o es indetectable, el cuadro será rojo.",
"id.verification.photo.enable.detection.id.help.text": "Si está marcada, aparecerá una casilla alrededor de la cara de tu documento de identificación. La cara se puede ver claramente si la caja que la rodea es azul. Si la cara no está en una buena posición o es indetectable, el cuadro será rojo.",
"id.verification.photo.feedback.correct": "La cara está en una buena posición.",
"id.verification.photo.feedback.two.faces": "Más de un rostro detectado.",
"id.verification.photo.feedback.no.faces": "No se detectó el rostro.",
"id.verification.photo.feedback.top.left": "Posición incorrecta. Arriba a la izquierda.",
"id.verification.photo.feedback.top.center": "Posición incorrecta. Centro Superior.",
"id.verification.photo.feedback.top.right": "Posición incorrecta. Parte superior derecha.",
"id.verification.photo.feedback.center.left": "Posición incorrecta. Centro izquierda.",
"id.verification.photo.feedback.center.center": "Posición incorrecta. Demasiado cerca de la cámara.",
"id.verification.photo.feedback.center.right": "Posición incorrecta. Centro derecha.",
"id.verification.photo.feedback.bottom.left": "Posición incorrecta. Abajo a la izquierda.",
"id.verification.photo.feedback.bottom.center": "Posición incorrecta. Parte inferior central.",
"id.verification.photo.feedback.bottom.right": "Posición incorrecta. Abajo a la derecha.",
"id.verification.camera.access.title": "Permisos de la cámara",
"id.verification.camera.access.title.success": "Camera Access Enabled",
"id.verification.camera.access.title.failed": "Camera Access Failed",
"id.verification.camera.access.click.allow": "Please make sure to click \"Allow\"",
"id.verification.camera.access.enable": "Enable Camera",
"id.verification.camera.access.problems": "Having problems?",
"id.verification.camera.access.skip": "Skip and upload image files instead",
"id.verification.camera.access.success": "Looks like your camera is working and ready.",
"id.verification.camera.access.failure": "It looks like we're unable to access your camera. You will need to upload image files of you and your photo id.",
"id.verification.camera.access.failure.temporary": "It looks like we're unable to access your camera. Please verify that your webcam is connected and that you have allowed your browser to access it.",
"id.verification.camera.access.failure.temporary.chrome": "To enable camera access in Chrome:",
"id.verification.camera.access.failure.temporary.chrome.step1": "Open Chrome.",
"id.verification.camera.access.failure.temporary.chrome.step2": "Navigate to More > Settings.",
"id.verification.camera.access.failure.temporary.chrome.step2.windows": "For Windows: Alt+F, Alt+E, or F10 followed by the spacebar",
"id.verification.camera.access.failure.temporary.chrome.step2.mac": "For Mac: Command+,",
"id.verification.camera.access.failure.temporary.chrome.step3": "Under the \"Privacy and security\" tab, select \"Site Settings\" and then \"Camera.\"",
"id.verification.camera.access.failure.temporary.chrome.step4": "Under \"Blocked,\" find \"edx.org\" and select it.",
"id.verification.camera.access.failure.temporary.chrome.step5": "In the \"Permissions\" section, update the camera permissions to \"Allow.\"",
"id.verification.camera.access.failure.temporary.ie11": "To enable camera access in Internet Explorer:",
"id.verification.camera.access.failure.temporary.ie11.step1": "Open the Flash Player Settings Manager by navigating to Windows Settings > Control Panel > Flash Player.",
"id.verification.camera.access.failure.temporary.ie11.step2": "Select the \"Camera and Mic\" tab, and then select the \"Camera and Microphone Settings by Site\" button.",
"id.verification.camera.access.failure.temporary.ie11.step3": "Choose \"edx.org\" from the list of websites and change the permissions by selecting \"Allow\" in the dropdown menu.",
"id.verification.camera.access.failure.temporary.firefox": "To enable camera access in Firefox:",
"id.verification.camera.access.failure.temporary.firefox.step1": "Open Firefox.",
"id.verification.camera.access.failure.temporary.firefox.step2": "Enter \"about:preferences\" in the URL bar.",
"id.verification.camera.access.failure.temporary.firefox.step3": "Select the \"Privacy & Security\" tab, and navigate to the \"Permissions\" section.",
"id.verification.camera.access.failure.temporary.firefox.step4": "Next to \"Camera,\" select the \"Settings…\" button.",
"id.verification.camera.access.failure.temporary.firefox.step5": "In the search bar, enter \"edx.org.\"",
"id.verification.camera.access.failure.temporary.firefox.step6": "In the status column for \"edx.org,\" select \"Allow\" from the drop down.",
"id.verification.camera.access.failure.temporary.firefox.step7": "Select \"Save Changes.\"",
"id.verification.camera.access.failure.temporary.safari": "To enable camera access in Safari:",
"id.verification.camera.access.failure.temporary.safari.step1": "Open Safari.",
"id.verification.camera.access.failure.temporary.safari.step2": "Click on the Safari app menu, then select \"Preferences.\" You can also use Command+, as a keyboard shortcut.",
"id.verification.camera.access.failure.temporary.safari.step3": "Select the \"Websites\" tab and then select \"Camera.\"",
"id.verification.camera.access.failure.temporary.safari.step4": "Select \"edx.org\" and change the camera permissions to \"Allow.\"",
"id.verification.photo.tips.title": "Helpful Photo Tips",
"id.verification.photo.tips.description": "Next, we'll need you to take a photo of your face. Please review the helpful tips below.",
"id.verification.photo.tips.list.title": "Photo Tips",
"id.verification.photo.tips.list.description": "To take a successful photo, make sure that:",
"id.verification.photo.tips.list.well.lit": "Your face is well-lit.",
"id.verification.photo.tips.list.inside.frame": "Your entire face fits inside the frame.",
"id.verification.portrait.photo.title.camera": "Take a Photo of Yourself",
"id.verification.portrait.photo.title.upload": "Upload a Photo of Yourself",
"id.verification.portrait.photo.preview.alt": "Preview of photo of user's face.",
"id.verification.portrait.photo.instructions.camera": "When your face is in position, use the Take Photo button below to take your photo.",
"id.verification.portrait.photo.instructions.upload": "Please upload a portrait photo. Ensure your entire face fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.camera.help.sight.question": "What if I can't see the camera image or if I can't see my photo to determine which side is visible?",
"id.verification.camera.help.sight.answer.portrait": "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 headshot is approximately 12-18 inches (30-45 centimeters) from the camera, with your head centered relative to the computer screen. If the photos you submit are rejected, try moving the computer or camera orientation to change the lighting angle.",
"id.verification.camera.help.sight.answer.id": "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. 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.",
"id.verification.camera.help.difficulty.question.portrait": "What if I have difficulty holding my head in position relative to the camera?",
"id.verification.camera.help.difficulty.question.id": "What if I have difficulty holding my ID in position relative to the camera?",
"id.verification.camera.help.difficulty.answer": "If you require assistance with taking a photo for submission, contact edX support for additional suggestions.",
"id.verification.id.photo.unclear.question": "Is your ID image not clear or too blurry?",
"id.verification.id.tips.title": "Helpful ID Tips",
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid ID that includes your name. Please have your ID ready.",
"id.verification.id.tips.list.well.lit": "Your ID is well-lit.",
"id.verification.id.tips.list.clear": "Ensure that you can see your photo and clearly read your name.",
"id.verification.id.photo.title.camera": "Take a Photo of Your ID",
"id.verification.id.photo.title.upload": "Upload a Photo of Your ID",
"id.verification.id.photo.preview.alt": "Preview of photo ID.",
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.account.name.title": "Account Name Check",
"id.verification.account.name.instructions": "The name on your account and the name on your ID must be an exact match. If not, please click \"No\" to update your account name.",
"id.verification.account.name.radio.label": "Does the name on your ID match the Account Name below?",
"id.verification.account.name.radio.yes": "Yes",
"id.verification.camera.access.click.allow": "Por favor asegúrate de hacer clic en \"Permitir\"",
"id.verification.camera.access.enable": "Habilitar cámara",
"id.verification.camera.access.problems": "¿Tienes problemas?",
"id.verification.camera.access.skip": "Omitir y cargar un archivo de imagen",
"id.verification.camera.access.success": "Parece que tu cámara está funcionando y está lista.",
"id.verification.camera.access.failure": "Parece que no podemos acceder a tu cámara. Deberás cargar archivos de imagen de tu rostro y tu identificación con foto.",
"id.verification.camera.access.failure.temporary": "Parece que no podemos acceder a tu cámara. Verifica que tu cámara web esté conectada y que permitiste que tu navegador acceda a ella.",
"id.verification.camera.access.failure.temporary.chrome": "Para habilitar el acceso a la cámara en Chrome:",
"id.verification.camera.access.failure.temporary.chrome.step1": "Abrir Chrome.",
"id.verification.camera.access.failure.temporary.chrome.step2": "Navega a Más > Configuración.",
"id.verification.camera.access.failure.temporary.chrome.step2.windows": "Para Windows: Alt + F, Alt + E o F10 seguido de la barra espaciadora",
"id.verification.camera.access.failure.temporary.chrome.step2.mac": "Para Mac: Comando +,",
"id.verification.camera.access.failure.temporary.chrome.step3": "En la pestaña \"Privacidad y seguridad\", selecciona \"Configuración del sitio\" y luego \"Cámara.\"",
"id.verification.camera.access.failure.temporary.chrome.step4": "En \"Bloqueado\", busca \"edx.org\" y selecciónalo.",
"id.verification.camera.access.failure.temporary.chrome.step5": "En la sección \"Permisos\", actualiza los permisos de la cámara a \"Permitir\".",
"id.verification.camera.access.failure.temporary.ie11": "Para habilitar el acceso a la cámara en Internet Explorer:",
"id.verification.camera.access.failure.temporary.ie11.step1": "Abre el Administrador de configuración de Flash Player navegando a Configuración de Windows > Panel de control > Flash Player.",
"id.verification.camera.access.failure.temporary.ie11.step2": "Selecciona la pestaña \"Cámara y micrófono,\" y luego selecciona el botón \"Configuración de cámara y micrófono por sitio.\"",
"id.verification.camera.access.failure.temporary.ie11.step3": "Elige \"edx.org\" de la lista de sitios web y cambia los permisos seleccionando \"Permitir\" en el menú desplegable.",
"id.verification.camera.access.failure.temporary.firefox": "Para habilitar el acceso a la cámara en Firefox:",
"id.verification.camera.access.failure.temporary.firefox.step1": "Abrir Firefox.",
"id.verification.camera.access.failure.temporary.firefox.step2": "Coloca \"about:preferences\" en la barra del URL.",
"id.verification.camera.access.failure.temporary.firefox.step3": "Selecciona la pestaña \"Privacidad y seguridad\" y navega hasta la sección \"Permisos.\"",
"id.verification.camera.access.failure.temporary.firefox.step4": "Junto a \"Cámara,\" selecciona el botón \"Configuración...\"",
"id.verification.camera.access.failure.temporary.firefox.step5": "En la barra de búsqueda, ingresa \"edx.org\".",
"id.verification.camera.access.failure.temporary.firefox.step6": "En la columna de estado de \"edx.org\", selecciona \"Permitir\" en el menú desplegable.",
"id.verification.camera.access.failure.temporary.firefox.step7": "Selecciona \"Guardar cambios.\"",
"id.verification.camera.access.failure.temporary.safari": "Para habilitar el acceso a la cámara en Safari:",
"id.verification.camera.access.failure.temporary.safari.step1": "Abrir Safari.",
"id.verification.camera.access.failure.temporary.safari.step2": "Haz clic en el menú de la aplicación Safari y luego selecciona \"Preferencias\". También puedes utilizar Command +, como método abreviado de teclado.",
"id.verification.camera.access.failure.temporary.safari.step3": "Selecciona la pestaña \"Sitios web\" y luego selecciona \"Cámara\".",
"id.verification.camera.access.failure.temporary.safari.step4": "Selecciona \"edx.org\" y cambia los permisos de la cámara a \"Permitir.\"",
"id.verification.photo.tips.title": "Consejos útiles de fotos",
"id.verification.photo.tips.description": "A continuación, necesitaremos que tomes una foto de tu rostro. Por favor, revisa los siguientes consejos útiles.",
"id.verification.photo.tips.list.title": "Consejos para fotos",
"id.verification.photo.tips.list.description": "Para tomar la foto correctamente, asegúrate de: ",
"id.verification.photo.tips.list.well.lit": "El rostro esté bien iluminado",
"id.verification.photo.tips.list.inside.frame": "Tu cara está completamente dentro del marco de la foto.",
"id.verification.portrait.photo.title.camera": "Toma una foto de ti mismo",
"id.verification.portrait.photo.title.upload": "Sube una foto tuya",
"id.verification.portrait.photo.preview.alt": "Previsualización de la foto con el rostro del usuario",
"id.verification.portrait.photo.instructions.camera": "Cuando tu rostro esté en posición, usa el botón Tomar foto a continuación para tomar tu foto.",
"id.verification.portrait.photo.instructions.upload": "Sube una foto de retrato. Asegúrate de que toda tu cara quede dentro del marco y esté bien iluminada. (Formatos admitidos: .jpg, .jpeg, .png)",
"id.verification.camera.help.sight.question": "¿Qué pasa si no puedo ver la imagen de la cámara o si no puedo ver mi foto para determinar qué lado es visible?",
"id.verification.camera.help.sight.answer.portrait": "Es posible que puedas completar el procedimiento de captura de imágenes sin ayuda, pero es posible que necesites un par de intentos de envío para que la cámara se coloque correctamente. La posición óptima de la cámara varía con cada computadora, pero generalmente la mejor distancia para una foto de rostro es aproximadamente a 12 a 18 pulgadas (30 a 45 centímetros) de la cámara, con la cabeza centrada en relación con la pantalla de la computadora. Si las fotos que envías son rechazadas, intenta mover la computadora o la orientación de la cámara para cambiar el ángulo de iluminación.",
"id.verification.camera.help.sight.answer.id": "Es posible que puedas completar el procedimiento de captura de imágenes sin ayuda, pero es posible que necesites un par de intentos de envío para que la cámara se coloque correctamente. El posicionamiento óptimo de la cámara varía con cada computadora pero, generalmente, la mejor distancia para una foto de un documento de identificación es a 8 a 12 pulgadas (20 a 30 centímetros) de la cámara, con el documento de identificación centrado en relación con la cámara. Si las fotos que envías son rechazadas, intenta mover la computadora o la orientación de la cámara para cambiar el ángulo de iluminación. La razón más común de rechazo es la imposibilidad de leer el texto del documento de identidad.",
"id.verification.camera.help.difficulty.question.portrait": "¿Qué sucede si tengo dificultades para mantener la cabeza en posición con respecto a la cámara?",
"id.verification.camera.help.difficulty.question.id": "¿Qué sucede si tengo dificultades para mantener mi identificación en posición con respecto a la cámara?",
"id.verification.camera.help.difficulty.answer": "Si necesitas ayuda para tomar una foto para enviarla, comunícate con soporte de edX para obtener sugerencias adicionales.",
"id.verification.id.photo.unclear.question": "¿La imagen de tu identificación no es clara o está demasiado borrosa?",
"id.verification.id.tips.title": "Consejos útiles para la verificación",
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid identification card that includes your full name and photo. Please have your ID ready.",
"id.verification.id.tips.list.well.lit": "Tu identificación está bien iluminada.",
"id.verification.id.tips.list.clear": "Asegúrate de que puedes ver tu foto y leer claramente tu nombre.",
"id.verification.id.photo.title.camera": "Toma una Foto de tu Identificación",
"id.verification.id.photo.title.upload": "Carga una foto de tu identificación",
"id.verification.id.photo.preview.alt": "Previsualización de Foto ID",
"id.verification.id.photo.instructions.camera": "Cuando tu identificación esté en su lugar, usa el botón Tomar foto a continuación para tomar tu foto.",
"id.verification.id.photo.instructions.upload": "Sube una foto de identificación. Asegúrate de que toda la identificación quepa dentro del marco y esté bien iluminada. El tamaño del archivo debe ser inferior a 10 MB. (Formatos admitidos: .jpg, .jpeg, .png)",
"id.verification.id.photo.instructions.upload.error": "El archivo que has seleccionado es demasiado grande. Vuelve a intentarlo con un archivo de menos de 10 MB.",
"id.verification.account.name.title": "Verificación de nombre de cuenta",
"id.verification.account.name.instructions": "El nombre de tu cuenta y el nombre de tu identificación deben coincidir exactamente. De lo contrario, haz clic en \"No\" para actualizar el nombre de tu cuenta.",
"id.verification.account.name.radio.label": "¿El nombre de tu identificación coincide con el nombre de la cuenta a continuación?",
"id.verification.account.name.radio.yes": "Si",
"id.verification.account.name.radio.no": "No",
"id.verification.account.name.error": "Please update account name to match the name on your ID.",
"id.verification.account.name.warning.prefix": "Please Note:",
"id.verification.account.name.settings": "Account Settings",
"id.verification.account.name.label": "Account Name",
"id.verification.account.name.photo.alt": "Photo of your ID to be submitted.",
"id.verification.account.name.save": "Save and Next",
"id.verification.review.title": "Review Your Photos",
"id.verification.review.description": "Make sure we can verify your identity with the photos and information you have provided.",
"id.verification.review.portrait.label": "Your Portrait",
"id.verification.review.portrait.alt": "Photo of your face to be submitted.",
"id.verification.review.portrait.retake": "Retake Portrait Photo",
"id.verification.review.id.label": "Your Photo ID",
"id.verification.review.id.alt": "Photo of your ID to be submitted.",
"id.verification.review.id.retake": "Retake ID Photo",
"id.verification.review.confirm": "Submit",
"id.verification.submitted.title": "Identity Verification in Progress",
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.",
"id.verification.return.dashboard": "Return to Your Dashboard",
"id.verification.return.course": "Return to Course",
"id.verification.request.camera.access.instructions": "In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. {clickAllow}",
"id.verification.requirements.card.device.text": "You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click {allow}.",
"id.verification.account.name.edit": "Edit{sr}"
"id.verification.account.name.error": "Actualiza el nombre de la cuenta para que coincida con el nombre de tu identificación.",
"id.verification.account.name.warning.prefix": "Ten en cuenta:",
"id.verification.account.name.settings": "Configuración de cuenta",
"id.verification.account.name.label": "Nombre de la cuenta",
"id.verification.account.name.photo.alt": "Foto de tu identificación a enviar.",
"id.verification.account.name.save": "Guardar y siguiente",
"id.verification.review.title": "Revisar tus fotos",
"id.verification.review.description": "Asegúrate de que podamos verificar tu identidad con las imágenes y la información suministradas.",
"id.verification.review.portrait.label": "Tu Retrato",
"id.verification.review.portrait.alt": "Foto de tu rostro a enviar.",
"id.verification.review.portrait.retake": "Retomar Foto de Retrato",
"id.verification.review.id.label": "Tu Foto de identificación",
"id.verification.review.id.alt": "Foto de tu identificación a enviar.",
"id.verification.review.id.retake": "Retomar Foto de identificación",
"id.verification.review.confirm": "Enviar",
"id.verification.submission.alert.error.face": "A photo of your face is required. Please retake your portrait photo.",
"id.verification.submission.alert.error.id": "A photo of your ID card is required. Please retake your ID photo.",
"id.verification.submission.alert.error.name": "A valid account name is required. Please update your account name to match the name on your ID.",
"id.verification.review.error": "Página de soporte de edX",
"id.verification.submitted.title": "Verificación de identidad en progreso.",
"id.verification.submitted.text": "Hemos recibido tu información y estamos verificando tu identidad. Verás un mensaje en tu tablero cuando se complete el proceso de verificación (generalmente en un periodo de 5 días). Mientras tanto, aún puedes acceder a todo el contenido del curso disponible.",
"id.verification.return.dashboard": "Volver al panel principal",
"id.verification.return.course": "Regresar al curso",
"id.verification.request.camera.access.instructions": "Para tomar una foto con tu cámara web, es posible que recibas un aviso del navegador para acceder a tu cámara. {clickAllow}",
"id.verification.requirements.card.device.text": "Necesitas un dispositivo que tenga una cámara. Si has recibido un aviso del navegador para habilitar acceso a tu cámara, por favor asegúrate de seleccionar [allow].",
"idv.submission.alert.error": "\n We encountered a technical error while trying to submit ID verification.\n This might be a temporary issue, so please try again in a few minutes.\n If the problem persists, please go to {support_link} for help.\n ",
"id.verification.account.name.edit": "Editar{sr}"
}

View File

@@ -57,7 +57,7 @@
"account.settings.field.gender.options.f": "Female",
"account.settings.field.gender.options.m": "Male",
"account.settings.field.gender.options.o": "Other",
"account.settings.field.language.proficiencies": "Spoken languages",
"account.settings.field.language.proficiencies": "Spoken language",
"account.settings.field.language.proficiencies.empty": "Add a spoken language",
"account.settings.field.language_proficiencies.options.empty": "Select a Language",
"account.settings.field.time.zone": "Time zone",
@@ -165,23 +165,40 @@
"account.settings.sso.account.disconnect.error": "There was a problem disconnecting this account. Contact support if the problem persists.",
"account.settings.sso.unlink.account": "Unlink {name} account",
"account.settings.sso.no.providers": "No accounts can be linked at this time.",
"id.verification.existing.request.denied.text": "You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}.",
"id.verification.access.blocked.denied": "You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}.",
"id.verification.next": "Next",
"id.verification.example.card.alt": "Example of a valid identification card with a full name and photo.",
"id.verification.requirements.title": "Photo Verification Requirements",
"id.verification.requirements.description": "In order to complete Photo Verification online, you will need the following:",
"id.verification.requirements.card.device.title": "Device with Camera",
"id.verification.requirements.card.device.allow": "Allow",
"id.verification.requirements.card.id.title": "Photo Identification",
"id.verification.requirements.card.id.text": "You need a valid ID that contains your full name and photo.",
"id.verification.requirements.card.id.text": "You need a valid identification card that contains your full name and photo.",
"id.verification.privacy.title": "Privacy Information",
"id.verification.privacy.need.photo.question": "Why does edX need my photo?",
"id.verification.privacy.need.photo.answer": "We use your verification photos to confirm your identity and ensure the validity of your certificate.",
"id.verification.privacy.do.with.photo.question": "What does edX do with this photo?",
"id.verification.privacy.do.with.photo.answer": "We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on edX after the verification process is complete.",
"id.verification.existing.request.title": "Identity Verification",
"id.verification.existing.request.pending.text": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.access.blocked.title": "Identity Verification",
"id.verification.access.blocked.enrollment": "You are not currently enrolled in a course that requires identity verification.",
"id.verification.access.blocked.pending": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.photo.take": "Take Photo",
"id.verification.photo.retake": "Retake Photo",
"id.verification.photo.enable.detection": "Enable Face Detection",
"id.verification.photo.enable.detection.portrait.help.text": "If checked, a box will appear around your face. Your face can be seen clearly if the box around it is blue. If your face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.enable.detection.id.help.text": "If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.feedback.correct": "Face is in a good position.",
"id.verification.photo.feedback.two.faces": "More than one face detected.",
"id.verification.photo.feedback.no.faces": "No face detected.",
"id.verification.photo.feedback.top.left": "Incorrect position. Top left.",
"id.verification.photo.feedback.top.center": "Incorrect position. Top center.",
"id.verification.photo.feedback.top.right": "Incorrect position. Top right.",
"id.verification.photo.feedback.center.left": "Incorrect position. Center left.",
"id.verification.photo.feedback.center.center": "Incorrect position. Too close to camera.",
"id.verification.photo.feedback.center.right": "Incorrect position. Center right.",
"id.verification.photo.feedback.bottom.left": "Incorrect position. Bottom left.",
"id.verification.photo.feedback.bottom.center": "Incorrect position. Bottom center.",
"id.verification.photo.feedback.bottom.right": "Incorrect position. Bottom right.",
"id.verification.camera.access.title": "Camera Permissions",
"id.verification.camera.access.title.success": "Camera Access Enabled",
"id.verification.camera.access.title.failed": "Camera Access Failed",
@@ -236,14 +253,15 @@
"id.verification.camera.help.difficulty.answer": "If you require assistance with taking a photo for submission, contact edX support for additional suggestions.",
"id.verification.id.photo.unclear.question": "Is your ID image not clear or too blurry?",
"id.verification.id.tips.title": "Helpful ID Tips",
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid ID that includes your name. Please have your ID ready.",
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid identification card that includes your full name and photo. Please have your ID ready.",
"id.verification.id.tips.list.well.lit": "Your ID is well-lit.",
"id.verification.id.tips.list.clear": "Ensure that you can see your photo and clearly read your name.",
"id.verification.id.photo.title.camera": "Take a Photo of Your ID",
"id.verification.id.photo.title.upload": "Upload a Photo of Your ID",
"id.verification.id.photo.preview.alt": "Preview of photo ID.",
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.id.photo.instructions.upload.error": "The file you have selected is too large. Please try again with a file less than 10MB.",
"id.verification.account.name.title": "Account Name Check",
"id.verification.account.name.instructions": "The name on your account and the name on your ID must be an exact match. If not, please click \"No\" to update your account name.",
"id.verification.account.name.radio.label": "Does the name on your ID match the Account Name below?",
@@ -264,11 +282,16 @@
"id.verification.review.id.alt": "Photo of your ID to be submitted.",
"id.verification.review.id.retake": "Retake ID Photo",
"id.verification.review.confirm": "Submit",
"id.verification.submission.alert.error.face": "A photo of your face is required. Please retake your portrait photo.",
"id.verification.submission.alert.error.id": "A photo of your ID card is required. Please retake your ID photo.",
"id.verification.submission.alert.error.name": "A valid account name is required. Please update your account name to match the name on your ID.",
"id.verification.review.error": "edX Support Page",
"id.verification.submitted.title": "Identity Verification in Progress",
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.",
"id.verification.return.dashboard": "Return to Your Dashboard",
"id.verification.return.course": "Return to Course",
"id.verification.request.camera.access.instructions": "In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. {clickAllow}",
"id.verification.requirements.card.device.text": "You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click {allow}.",
"idv.submission.alert.error": "\n We encountered a technical error while trying to submit ID verification.\n This might be a temporary issue, so please try again in a few minutes.\n If the problem persists, please go to {support_link} for help.\n ",
"id.verification.account.name.edit": "Edit{sr}"
}

View File

@@ -57,7 +57,7 @@
"account.settings.field.gender.options.f": "Female",
"account.settings.field.gender.options.m": "Male",
"account.settings.field.gender.options.o": "Other",
"account.settings.field.language.proficiencies": "Spoken languages",
"account.settings.field.language.proficiencies": "Spoken language",
"account.settings.field.language.proficiencies.empty": "Add a spoken language",
"account.settings.field.language_proficiencies.options.empty": "Select a Language",
"account.settings.field.time.zone": "Time zone",
@@ -165,23 +165,40 @@
"account.settings.sso.account.disconnect.error": "There was a problem disconnecting this account. Contact support if the problem persists.",
"account.settings.sso.unlink.account": "Unlink {name} account",
"account.settings.sso.no.providers": "No accounts can be linked at this time.",
"id.verification.existing.request.denied.text": "You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}.",
"id.verification.access.blocked.denied": "You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}.",
"id.verification.next": "Next",
"id.verification.example.card.alt": "Example of a valid identification card with a full name and photo.",
"id.verification.requirements.title": "Photo Verification Requirements",
"id.verification.requirements.description": "In order to complete Photo Verification online, you will need the following:",
"id.verification.requirements.card.device.title": "Device with Camera",
"id.verification.requirements.card.device.allow": "Allow",
"id.verification.requirements.card.id.title": "Photo Identification",
"id.verification.requirements.card.id.text": "You need a valid ID that contains your full name and photo.",
"id.verification.requirements.card.id.text": "You need a valid identification card that contains your full name and photo.",
"id.verification.privacy.title": "Privacy Information",
"id.verification.privacy.need.photo.question": "Why does edX need my photo?",
"id.verification.privacy.need.photo.answer": "We use your verification photos to confirm your identity and ensure the validity of your certificate.",
"id.verification.privacy.do.with.photo.question": "What does edX do with this photo?",
"id.verification.privacy.do.with.photo.answer": "We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on edX after the verification process is complete.",
"id.verification.existing.request.title": "Identity Verification",
"id.verification.existing.request.pending.text": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.access.blocked.title": "Identity Verification",
"id.verification.access.blocked.enrollment": "You are not currently enrolled in a course that requires identity verification.",
"id.verification.access.blocked.pending": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.photo.take": "Take Photo",
"id.verification.photo.retake": "Retake Photo",
"id.verification.photo.enable.detection": "Enable Face Detection",
"id.verification.photo.enable.detection.portrait.help.text": "If checked, a box will appear around your face. Your face can be seen clearly if the box around it is blue. If your face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.enable.detection.id.help.text": "If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.feedback.correct": "Face is in a good position.",
"id.verification.photo.feedback.two.faces": "More than one face detected.",
"id.verification.photo.feedback.no.faces": "No face detected.",
"id.verification.photo.feedback.top.left": "Incorrect position. Top left.",
"id.verification.photo.feedback.top.center": "Incorrect position. Top center.",
"id.verification.photo.feedback.top.right": "Incorrect position. Top right.",
"id.verification.photo.feedback.center.left": "Incorrect position. Center left.",
"id.verification.photo.feedback.center.center": "Incorrect position. Too close to camera.",
"id.verification.photo.feedback.center.right": "Incorrect position. Center right.",
"id.verification.photo.feedback.bottom.left": "Incorrect position. Bottom left.",
"id.verification.photo.feedback.bottom.center": "Incorrect position. Bottom center.",
"id.verification.photo.feedback.bottom.right": "Incorrect position. Bottom right.",
"id.verification.camera.access.title": "Camera Permissions",
"id.verification.camera.access.title.success": "Camera Access Enabled",
"id.verification.camera.access.title.failed": "Camera Access Failed",
@@ -236,14 +253,15 @@
"id.verification.camera.help.difficulty.answer": "If you require assistance with taking a photo for submission, contact edX support for additional suggestions.",
"id.verification.id.photo.unclear.question": "Is your ID image not clear or too blurry?",
"id.verification.id.tips.title": "Helpful ID Tips",
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid ID that includes your name. Please have your ID ready.",
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid identification card that includes your full name and photo. Please have your ID ready.",
"id.verification.id.tips.list.well.lit": "Your ID is well-lit.",
"id.verification.id.tips.list.clear": "Ensure that you can see your photo and clearly read your name.",
"id.verification.id.photo.title.camera": "Take a Photo of Your ID",
"id.verification.id.photo.title.upload": "Upload a Photo of Your ID",
"id.verification.id.photo.preview.alt": "Preview of photo ID.",
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.id.photo.instructions.upload.error": "The file you have selected is too large. Please try again with a file less than 10MB.",
"id.verification.account.name.title": "Account Name Check",
"id.verification.account.name.instructions": "The name on your account and the name on your ID must be an exact match. If not, please click \"No\" to update your account name.",
"id.verification.account.name.radio.label": "Does the name on your ID match the Account Name below?",
@@ -264,11 +282,16 @@
"id.verification.review.id.alt": "Photo of your ID to be submitted.",
"id.verification.review.id.retake": "Retake ID Photo",
"id.verification.review.confirm": "Submit",
"id.verification.submission.alert.error.face": "A photo of your face is required. Please retake your portrait photo.",
"id.verification.submission.alert.error.id": "A photo of your ID card is required. Please retake your ID photo.",
"id.verification.submission.alert.error.name": "A valid account name is required. Please update your account name to match the name on your ID.",
"id.verification.review.error": "edX Support Page",
"id.verification.submitted.title": "Identity Verification in Progress",
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.",
"id.verification.return.dashboard": "Return to Your Dashboard",
"id.verification.return.course": "Return to Course",
"id.verification.request.camera.access.instructions": "In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. {clickAllow}",
"id.verification.requirements.card.device.text": "You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click {allow}.",
"idv.submission.alert.error": "\n We encountered a technical error while trying to submit ID verification.\n This might be a temporary issue, so please try again in a few minutes.\n If the problem persists, please go to {support_link} for help.\n ",
"id.verification.account.name.edit": "Edit{sr}"
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import messages from './IdVerification.messages';
import { ERROR_REASONS } from './IdVerificationContext';
function AccessBlocked({ error, intl }) {
const handleMessage = () => {
if (error === ERROR_REASONS.COURSE_ENROLLMENT) {
return <p>{intl.formatMessage(messages['id.verification.access.blocked.enrollment'])}</p>;
} else if (error === ERROR_REASONS.EXISTING_REQUEST) {
return <p>{intl.formatMessage(messages['id.verification.access.blocked.pending'])}</p>;
}
return (
<FormattedMessage
id="id.verification.access.blocked.denied"
defaultMessage="You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}."
description="Text that displays when user is denied from making a request, and to check their email for an activation email."
values={{
email: <strong>no-reply@registration.edx.org</strong>,
}}
/>
);
};
return (
<div>
<h3 aria-level="1" tabIndex="-1">
{intl.formatMessage(messages['id.verification.access.blocked.title'])}
</h3>
{handleMessage()}
<div className="action-row">
<a className="btn btn-primary mt-3" href={`${getConfig().LMS_BASE_URL}/dashboard`}>
{intl.formatMessage(messages['id.verification.return.dashboard'])}
</a>
</div>
</div>
);
}
AccessBlocked.propTypes = {
intl: intlShape.isRequired,
error: PropTypes.string.isRequired,
};
export default injectIntl(AccessBlocked);

View File

@@ -1,7 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import * as blazeface from '@tensorflow-models/blazeface';
import CameraPhoto, { FACING_MODES } from 'jslib-html5-camera-photo';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Form, Spinner } from '@edx/paragon';
import shutter from './data/camera-shutter.base64.json';
import messages from './IdVerification.messages';
@@ -11,27 +14,220 @@ class Camera extends React.Component {
super(props, context);
this.cameraPhoto = null;
this.videoRef = React.createRef();
this.canvasRef = React.createRef();
this.setDetection = this.setDetection.bind(this);
this.state = {
trackedObject: null,
dataUri: '',
videoHasLoaded: false,
shouldDetect: false,
isFinishedLoadingDetection: true,
shouldGiveFeedback: true,
feedback: '',
};
}
componentDidMount() {
this.cameraPhoto = new CameraPhoto(this.videoRef.current);
this.cameraPhoto.startCamera(FACING_MODES.USER, { width: 1280 });
this.cameraPhoto.startCamera(
this.props.isPortrait ? FACING_MODES.USER : FACING_MODES.ENVIRONMENT,
{ width: 640, height: 480 },
);
}
async componentWillUnmount() {
this.cameraPhoto.stopCamera();
}
sendEvent() {
let eventName = 'edx.id_verification';
if (this.props.isPortrait) {
eventName += '.user_photo';
} else {
eventName += '.id_photo';
}
if (this.state.shouldDetect) {
eventName += '.face_detection_enabled';
} else {
eventName += '.face_detection_disabled';
}
sendTrackEvent(eventName);
}
setDetection() {
this.setState(
{ shouldDetect: !this.state.shouldDetect },
() => {
if (this.state.shouldDetect) {
this.setState({ isFinishedLoadingDetection: false });
this.startDetection();
}
this.sendEvent();
},
);
}
startDetection() {
setTimeout(() => {
if (this.state.videoHasLoaded) {
const loadModelPromise = blazeface.load();
Promise.all([loadModelPromise])
.then((values) => {
this.setState({ isFinishedLoadingDetection: true });
this.detectFromVideoFrame(values[0], this.videoRef.current);
});
} else {
this.setState({ isFinishedLoadingDetection: true });
this.setState({ shouldDetect: false });
// TODO: add error message
}
}, 1000);
}
detectFromVideoFrame = (model, video) => {
model.estimateFaces(video).then((predictions) => {
if (this.state.shouldDetect && !this.state.dataUri) {
this.showDetections(predictions);
requestAnimationFrame(() => {
this.detectFromVideoFrame(model, video);
});
}
});
};
showDetections = (predictions) => {
let canvasContext;
if (predictions.length > 0) {
canvasContext = this.canvasRef.current.getContext('2d');
canvasContext.clearRect(0, 0, canvasContext.canvas.width, canvasContext.canvas.height);
}
// predictions is an array of objects describing each detected face
predictions.forEach((prediction) => {
const start = [prediction.topLeft[0], prediction.topLeft[1]];
const end = [prediction.bottomRight[0], prediction.bottomRight[1]];
const size = [end[0] - start[0], end[1] - start[1]];
// landmarks is an array of points representing each facial landmark (i.e. right eye, left eye, nose, etc.)
const features = prediction.landmarks;
let isInPosition = true;
// for each of the landmarks, determine if it is in position
for (let j = 0; j < features.length; j++) {
const x = features[j][0];
const y = features[j][1];
let isInRange;
if (this.props.isPortrait) {
isInRange = this.isInRangeForPortrait(x, y);
} else {
isInRange = this.isInRangeForID(x, y);
}
// if it is not in range, give feedback depending on which feature is out of range
isInPosition = isInPosition && isInRange;
}
// draw a box depending on if all landmarks are in position
if (isInPosition) {
canvasContext.strokeStyle = '#00ffff';
canvasContext.lineWidth = 6;
canvasContext.strokeRect(start[0], start[1], size[0], size[1]);
// give positive feedback here if user is in correct position
this.giveFeedback(predictions.length, [], true);
} else {
canvasContext.fillStyle = 'rgba(255, 51, 0, 0.75)';
canvasContext.fillRect(start[0], start[1], size[0], size[1]);
this.giveFeedback(predictions.length, features[0], false);
}
});
if (predictions.length === 0) {
this.giveFeedback(predictions.length, [], false);
}
}
giveFeedback(numFaces, rightEye, isCorrect) {
if (this.state.shouldGiveFeedback) {
const currentFeedback = this.state.feedback;
let newFeedback = '';
if (numFaces === 1) {
// only give feedback if one face is detected otherwise
// it would be difficult to tell a user which face to move
if (isCorrect) {
newFeedback = this.props.intl.formatMessage(messages['id.verification.photo.feedback.correct']);
} else {
// give feedback based on where user is
newFeedback = this.props.intl.formatMessage(messages[this.getGridPosition(rightEye)]);
}
} else if (numFaces > 1) {
newFeedback = this.props.intl.formatMessage(messages['id.verification.photo.feedback.two.faces']);
} else {
newFeedback = this.props.intl.formatMessage(messages['id.verification.photo.feedback.no.faces']);
}
if (currentFeedback !== newFeedback) {
// only update status if it is different, so we don't overload the user with status updates
this.setState({ feedback: newFeedback });
}
// turn off feedback for one to ensure that instructions aren't disruptive/interrupting
this.setState({ shouldGiveFeedback: false });
setTimeout(() => {
this.setState({ shouldGiveFeedback: true });
}, 1000);
}
}
getGridPosition(coordinates) {
// Used to determine where a face is (i.e. top-left, center-right, bottom-center, etc.)
const x = coordinates[0];
const y = coordinates[1];
let messageBase = 'id.verification.photo.feedback';
const heightUpperLimit = 320;
const heightMiddleLimit = 160;
if (y < heightMiddleLimit) {
messageBase += '.top';
} else if (y < heightUpperLimit && y >= heightMiddleLimit) {
messageBase += '.center';
} else {
messageBase += '.bottom';
}
const widthRightLimit = 213;
const widthMiddleLimit = 427;
if (x < widthRightLimit) {
messageBase += '.right';
} else if (x >= widthRightLimit && x < widthMiddleLimit) {
messageBase += '.center';
} else {
messageBase += '.left';
}
return messageBase;
}
isInRangeForPortrait(x, y) {
return x > 47 && x < 570 && y > 100 && y < 410;
}
isInRangeForID(x, y) {
return x > 120 && x < 470 && y > 120 && y < 350;
}
setVideoHasLoaded() {
this.setState({ videoHasLoaded: 'true' });
}
takePhoto() {
if (this.state.dataUri) {
return this.reset();
}
const config = {
sizeFactor: 1,
sizeFactor: this.getSizeFactor(),
};
this.playShutterClick();
@@ -40,13 +236,40 @@ class Camera extends React.Component {
this.props.onImageCapture(dataUri);
}
getSizeFactor() {
let sizeFactor = 1;
const settings = this.cameraPhoto.getCameraSettings();
if (settings) {
const videoWidth = settings.width;
const videoHeight = settings.height;
// need to multiply by 3 because each pixel contains 3 bytes
const currentSize = videoWidth * videoHeight * 3;
// chose a limit of 9,999,999 (bytes) so that result will
// always be less than 10MB
const ratio = 9999999 / currentSize;
if (ratio < 1) {
// if the current resolution creates an image larger than 10 MB, adjust sizeFactor (resolution)
// to ensure that image will have a file size of less than 10 MB.
sizeFactor = ratio;
} else if (videoWidth === 640 && videoHeight === 480) {
// otherwise increase the resolution to try and prevent blurry images.
sizeFactor = 2;
}
}
return sizeFactor;
}
playShutterClick() {
const audio = new Audio('data:audio/mp3;base64,' + shutter.base64);
const audio = new Audio(`data:audio/mp3;base64,${shutter.base64}`);
audio.play();
}
reset() {
this.setState({ dataUri: '' });
if (this.state.shouldDetect) {
this.startDetection();
}
}
render() {
@@ -54,21 +277,59 @@ class Camera extends React.Component {
? 'do-transition camera-flash'
: 'camera-flash';
return (
<div className='camera-outer-wrapper shadow'>
<div className='camera-wrapper'>
<div className="camera-outer-wrapper shadow">
<Form.Group style={{ textAlign: 'left', padding: '0.5rem', marginBottom: '0.5rem' }} >
<Form.Check
id="videoDetection"
name="videoDetection"
label={this.props.intl.formatMessage(messages['id.verification.photo.enable.detection'])}
aria-describedby="videoDetectionHelpText"
checked={this.state.shouldDetect}
onChange={this.setDetection}
style={{ padding: '0rem', marginLeft: '1.25rem', float: this.state.isFinishedLoadingDetection ? 'none' : 'left' }}
/>
{!this.state.isFinishedLoadingDetection && <Spinner animation="border" variant="primary" style={{ marginLeft: '0.5rem' }} data-testid="spinner" />}
<Form.Text id="videoDetectionHelpText" data-testid="videoDetectionHelpText">
{this.props.isPortrait
? this.props.intl.formatMessage(messages['id.verification.photo.enable.detection.portrait.help.text'])
: this.props.intl.formatMessage(messages['id.verification.photo.enable.detection.id.help.text'])}
</Form.Text>
</Form.Group>
<div className="camera-wrapper">
<div className={cameraFlashClass} />
<video
ref={this.videoRef}
autoPlay={true}
className='camera-video'
style={{ display: this.state.dataUri ? 'none' : 'block' }}
data-testid="video"
autoPlay
className="camera-video"
onLoadedData={() => { this.setVideoHasLoaded(); }}
style={{
display: this.state.dataUri ? 'none' : 'block',
WebkitTransform: 'scaleX(-1)',
transform: 'scaleX(-1)',
}}
playsInline
/>
<canvas
ref={this.canvasRef}
data-testid="detection-canvas"
className="canvas-video"
style={{
display: !this.state.shouldDetect || this.state.dataUri ? 'none' : 'block',
WebkitTransform: 'scaleX(-1)',
transform: 'scaleX(-1)',
}}
width="640"
height="480"
/>
<img
alt='imgCamera'
data-hj-suppress
alt="imgCamera"
src={this.state.dataUri}
className='camera-video'
className="camera-video"
style={{ display: this.state.dataUri ? 'block' : 'none' }}
/>
<div role="status" className="sr-only">{this.state.feedback}</div>
</div>
<button
className={`btn camera-btn ${
@@ -76,7 +337,7 @@ class Camera extends React.Component {
'btn-outline-primary'
: 'btn-primary'
}`}
accessKey='c'
accessKey="c"
onClick={() => {
this.takePhoto();
}}
@@ -93,6 +354,7 @@ class Camera extends React.Component {
Camera.propTypes = {
intl: intlShape.isRequired,
onImageCapture: PropTypes.func.isRequired,
isPortrait: PropTypes.bool.isRequired,
};
export default injectIntl(Camera);

View File

@@ -35,7 +35,7 @@ function CameraHelpWithUpload(props) {
<p>
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.upload'])}
</p>
<ImageFileUpload onFileChange={setAndTrackIdPhotoFile} />
<ImageFileUpload onFileChange={setAndTrackIdPhotoFile} intl={props.intl} />
</Collapsible>
</div>
);

View File

@@ -1,37 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import messages from './IdVerification.messages';
function ExistingRequest(props) {
return (
<div>
<h3 aria-level="1" tabIndex="-1">
{props.intl.formatMessage(messages['id.verification.existing.request.title'])}
</h3>
{props.status === 'pending' || props.status === 'approved'
? <p>{props.intl.formatMessage(messages['id.verification.existing.request.pending.text'])}</p>
: <FormattedMessage
id="id.verification.existing.request.denied.text"
defaultMessage="You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}."
description="Text that displays when user is denied from making a request, and to check their email for an activation email."
values={{
email: <strong>no-reply@registration.edx.org</strong>,
}}
/>
}
<div className="action-row">
<a className="btn btn-primary mt-3" href={`${getConfig().LMS_BASE_URL}/dashboard`}>
{props.intl.formatMessage(messages['id.verification.return.dashboard'])}
</a>
</div>
</div>
);
}
ExistingRequest.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(ExistingRequest);

View File

@@ -6,6 +6,11 @@ const messages = defineMessages({
defaultMessage: 'Next',
description: 'Next button.',
},
'id.verification.example.card.alt': {
id: 'id.verification.example.card.alt',
defaultMessage: 'Example of a valid identification card with a full name and photo.',
description: 'Alt text for an example identification card.'
},
'id.verification.requirements.title': {
id: 'id.verification.requirements.title',
defaultMessage: 'Photo Verification Requirements',
@@ -33,7 +38,7 @@ const messages = defineMessages({
},
'id.verification.requirements.card.id.text': {
id: 'id.verification.requirements.card.id.text',
defaultMessage: 'You need a valid ID that contains your full name and photo.',
defaultMessage: 'You need a valid identification card that contains your full name and photo.',
description: 'Text that explains that the user needs a photo ID.',
},
'id.verification.privacy.title': {
@@ -61,13 +66,18 @@ const messages = defineMessages({
defaultMessage: 'We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on edX after the verification process is complete.',
description: 'Answering what edX does with the verification photo.',
},
'id.verification.existing.request.title': {
id: 'id.verification.existing.request.title',
'id.verification.access.blocked.title': {
id: 'id.verification.access.blocked.title',
defaultMessage: 'Identity Verification',
description: 'Title for text that displays when user has already made a request.',
description: 'Title for text that displays when a user is blocked from ID verification.',
},
'id.verification.existing.request.pending.text': {
id: 'id.verification.existing.request.pending.text',
'id.verification.access.blocked.enrollment': {
id: 'id.verification.access.blocked.enrollment',
defaultMessage: 'You are not currently enrolled in a course that requires identity verification.',
description: 'Text that displays when user is trying to verify while not enrolled in a course that requires ID verification.',
},
'id.verification.access.blocked.pending': {
id: 'id.verification.access.blocked.pending',
defaultMessage: 'You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).',
description: 'Text that displays when user has a pending or approved request.',
},
@@ -81,6 +91,81 @@ const messages = defineMessages({
defaultMessage: 'Retake Photo',
description: 'Button to retake photo.',
},
'id.verification.photo.enable.detection': {
id: 'id.verification.photo.enable.detection',
defaultMessage: 'Enable Face Detection',
description: 'Text label for the checkbox to enable face detection.',
},
'id.verification.photo.enable.detection.portrait.help.text': {
id: 'id.verification.photo.enable.detection.portrait.help.text',
defaultMessage: 'If checked, a box will appear around your face. Your face can be seen clearly if the box around it is blue. If your face is not in a good position or undetectable, the box will be red.',
description: 'Help text that appears for enabling face detection on the portrait photo panel.',
},
'id.verification.photo.enable.detection.id.help.text': {
id: 'id.verification.photo.enable.detection.id.help.text',
defaultMessage: 'If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.',
description: 'Help text that appears for enabling face detection on the portrait photo panel.',
},
'id.verification.photo.feedback.correct': {
id: 'id.verification.photo.feedback.correct',
defaultMessage: 'Face is in a good position.',
description: 'Text for screen reader when user\'s face is in a good position.',
},
'id.verification.photo.feedback.two.faces': {
id: 'id.verification.photo.feedback.two.faces',
defaultMessage: 'More than one face detected.',
description: 'Text for screen reader when more than one face detected.',
},
'id.verification.photo.feedback.no.faces': {
id: 'id.verification.photo.feedback.no.faces',
defaultMessage: 'No face detected.',
description: 'Text for screen reader when no face detected.',
},
'id.verification.photo.feedback.top.left': {
id: 'id.verification.photo.feedback.top.left',
defaultMessage: 'Incorrect position. Top left.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.top.center': {
id: 'id.verification.photo.feedback.top.center',
defaultMessage: 'Incorrect position. Top center.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.top.right': {
id: 'id.verification.photo.feedback.top.right',
defaultMessage: 'Incorrect position. Top right.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.center.left': {
id: 'id.verification.photo.feedback.center.left',
defaultMessage: 'Incorrect position. Center left.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.center.center': {
id: 'id.verification.photo.feedback.center.center',
defaultMessage: 'Incorrect position. Too close to camera.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.center.right': {
id: 'id.verification.photo.feedback.center.right',
defaultMessage: 'Incorrect position. Center right.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.bottom.left': {
id: 'id.verification.photo.feedback.bottom.left',
defaultMessage: 'Incorrect position. Bottom left.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.bottom.center': {
id: 'id.verification.photo.feedback.bottom.center',
defaultMessage: 'Incorrect position. Bottom center.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.bottom.right': {
id: 'id.verification.photo.feedback.bottom.right',
defaultMessage: 'Incorrect position. Bottom right.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.camera.access.title': {
id: 'id.verification.camera.access.title',
defaultMessage: 'Camera Permissions',
@@ -353,7 +438,7 @@ const messages = defineMessages({
},
'id.verification.id.tips.description': {
id: 'id.verification.id.tips.description',
defaultMessage: 'Next, we\'ll need you to take a photo of a valid ID that includes your name. Please have your ID ready.',
defaultMessage: 'Next, we\'ll need you to take a photo of a valid identification card that includes your full name and photo. Please have your ID ready.',
description: 'Description for the ID Tips page.',
},
'id.verification.id.tips.list.well.lit': {
@@ -388,9 +473,14 @@ const messages = defineMessages({
},
'id.verification.id.photo.instructions.upload': {
id: 'id.verification.id.photo.instructions.upload',
defaultMessage: 'Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)',
defaultMessage: 'Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. (Supported formats: .jpg, .jpeg, .png)',
description: 'Instructions for ID photo upload.',
},
'id.verification.id.photo.instructions.upload.error': {
id: 'id.verification.id.photo.instructions.upload.error',
defaultMessage: 'The file you have selected is too large. Please try again with a file less than 10MB.',
description: 'Error message for file upload that is larger than 10MB.',
},
'id.verification.account.name.title': {
id: 'id.verification.account.name.title',
defaultMessage: 'Account Name Check',
@@ -491,6 +581,26 @@ const messages = defineMessages({
defaultMessage: 'Submit',
description: 'Button to confirm all information is correct and submit.',
},
'id.verification.submission.alert.error.face': {
id: 'id.verification.submission.alert.error.face',
defaultMessage: 'A photo of your face is required. Please retake your portrait photo.',
description: 'Error message displayed when the user\'s portrait photo is missing.',
},
'id.verification.submission.alert.error.id': {
id: 'id.verification.submission.alert.error.id',
defaultMessage: 'A photo of your ID card is required. Please retake your ID photo.',
description: 'Error message displayed when the user\'s ID photo is missing.',
},
'id.verification.submission.alert.error.name': {
id: 'id.verification.submission.alert.error.name',
defaultMessage: 'A valid account name is required. Please update your account name to match the name on your ID.',
description: 'Error message displayed when the user\'s account name is missing.',
},
'id.verification.review.error': {
id: 'id.verification.review.error',
defaultMessage: 'edX Support Page',
description: 'Text linking to the support page.',
},
'id.verification.submitted.title': {
id: 'id.verification.submitted.title',
defaultMessage: 'Identity Verification in Progress',

View File

@@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
import { AppContext } from '@edx/frontend-platform/react';
import { hasGetUserMediaSupport } from './getUserMediaShim';
import { getExistingIdVerification } from './data/service';
import { getExistingIdVerification, getEnrollments } from './data/service';
import PageLoading from '../account-settings/PageLoading';
import ExistingRequest from './ExistingRequest';
import AccessBlocked from './AccessBlocked';
const IdVerificationContext = React.createContext({});
@@ -16,6 +16,14 @@ const MEDIA_ACCESS = {
GRANTED: 'granted',
};
const ERROR_REASONS = {
COURSE_ENROLLMENT: 'course_enrollment',
EXISTING_REQUEST: 'existing_request',
CANNOT_VERIFY: 'cannot_verify',
};
const VERIFIED_MODES = ['verified', 'professional', 'masters', 'executive_education'];
function IdVerificationContextProvider({ children }) {
const [existingIdVerification, setExistingIdVerification] = useState(null);
const [facePhotoFile, setFacePhotoFile] = useState(null);
@@ -25,6 +33,8 @@ function IdVerificationContextProvider({ children }) {
const [mediaAccess, setMediaAccess] = useState(hasGetUserMediaSupport ?
MEDIA_ACCESS.PENDING :
MEDIA_ACCESS.UNSUPPORTED);
const [canVerify, setCanVerify] = useState(true);
const [error, setError] = useState('');
const { authenticatedUser } = useContext(AppContext);
const contextValue = {
@@ -45,9 +55,9 @@ function IdVerificationContextProvider({ children }) {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
setMediaAccess(MEDIA_ACCESS.GRANTED);
setMediaStream(stream);
// If we would like to stop the stream immediately. I guess we can leave it open
// const tracks = stream.getTracks();
// tracks.forEach(track => track.stop());
// stop the stream, as we are not using it yet
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
} catch (err) {
setMediaAccess(MEDIA_ACCESS.DENIED);
}
@@ -58,27 +68,52 @@ function IdVerificationContextProvider({ children }) {
tracks.forEach(track => track.stop());
setMediaStream(null);
}
}
},
};
// Call verification status endpoint to check whether we can verify.
useEffect(() => {
// Call verification status endpoint to check whether we can verify.
(async () => {
const existingIdV = await getExistingIdVerification();
setExistingIdVerification(existingIdV);
})();
}, []);
useEffect(() => {
// Check whether the learner is enrolled in a verified course mode.
(async () => {
/* eslint-disable arrow-body-style */
const enrollments = await getEnrollments();
const verifiedEnrollments = enrollments.filter((enrollment) => {
return VERIFIED_MODES.includes(enrollment.mode);
});
if (verifiedEnrollments.length === 0) {
setCanVerify(false);
setError(ERROR_REASONS.COURSE_ENROLLMENT);
}
})();
}, []);
useEffect(() => {
// Check for an existing verification attempt
if (existingIdVerification && !existingIdVerification.canVerify) {
const { status } = existingIdVerification;
setCanVerify(false);
if (status === 'pending' || status === 'approved') {
setError(ERROR_REASONS.EXISTING_REQUEST);
} else {
setError(ERROR_REASONS.CANNOT_VERIFY);
}
}
}, [existingIdVerification]);
// If we are waiting for verification status endpoint, show spinner.
if (!existingIdVerification) {
return <PageLoading srMessage="Loading verification status" />;
}
if (!existingIdVerification.canVerify) {
const { status } = existingIdVerification;
return (
<ExistingRequest status={status} />
);
if (!canVerify) {
return <AccessBlocked error={error} />;
}
return (
@@ -95,4 +130,5 @@ export {
IdVerificationContext,
IdVerificationContextProvider,
MEDIA_ACCESS,
ERROR_REASONS,
};

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Route, Switch, Redirect, useRouteMatch, useLocation } from 'react-router-dom';
import qs from 'qs';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Modal, Button } from '@edx/paragon';
import { idVerificationSelector } from './data/selectors';
@@ -29,7 +30,13 @@ function IdVerificationPage(props) {
// Course run key is passed as a query string
useEffect(() => {
if (search) {
sessionStorage.setItem('courseRunKey', search.substring(1));
const parsed = qs.parse(search, {
ignoreQueryPrefix: true,
interpretNumericEntities: true
});
if (parsed.hasOwnProperty('course_id') && parsed.course_id) {
sessionStorage.setItem('courseRunKey', parsed.course_id);
}
}
}, [search]);
@@ -80,11 +87,8 @@ function IdVerificationPage(props) {
</>
);
}
IdVerificationPage.propTypes = {
intl: intlShape.isRequired,
};
export default connect(idVerificationSelector, {
})(injectIntl(IdVerificationPage));

View File

@@ -1,28 +1,52 @@
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { Alert } from '@edx/paragon';
import messages from './IdVerification.messages';
export default function ImageFileUpload({ onFileChange, intl }) {
const [fileTooLargeError, setFileTooLargeError] = useState(false);
const maxFileSize = 10000000;
export default function ImageFileUpload({ onFileChange }) {
const handleChange = useCallback((e) => {
if (e.target.files.length === 0) {
return;
}
const fileObject = e.target.files[0];
const fileReader = new FileReader();
fileReader.addEventListener('load', () => onFileChange(fileReader.result));
fileReader.readAsDataURL(fileObject);
if (fileObject.size < maxFileSize) {
const fileReader = new FileReader();
fileReader.addEventListener('load', () => onFileChange(fileReader.result));
fileReader.readAsDataURL(fileObject);
} else {
setFileTooLargeError(true);
}
}, []);
return (
<input
type="file"
accept="image/*"
data-testid="fileUpload"
onChange={handleChange}
/>
<>
<input
type="file"
accept="image/*"
data-testid="fileUpload"
onChange={handleChange}
/>
{fileTooLargeError && (
<Alert
id="fileTooLargeError"
variant="danger"
tabIndex="-1"
style={{ marginTop: '1rem' }}
>
{intl.formatMessage(messages['id.verification.id.photo.instructions.upload.error'])}
</Alert>
)}
</>
);
}
ImageFileUpload.propTypes = {
onFileChange: PropTypes.func.isRequired,
intl: intlShape.isRequired,
};

View File

@@ -5,7 +5,7 @@ export default function ImagePreview({ src, alt, id }) {
return (
<div id={id} className="image-preview">
<img style={{ objectFit: 'contain' }} src={src} alt={alt} />
<img data-hj-suppress style={{ objectFit: 'contain' }} src={src} alt={alt} />
</div>
);

View File

@@ -1,5 +1,8 @@
.page__id-verification {
.verification-panel {
img {
max-width: 100%;
}
.card.accent {
border-top: solid 4px theme-color('warning');
}
@@ -8,12 +11,11 @@
max-width: 20rem;
img {
display: block;
max-width: 100%;
max-height: 10rem;
}
}
.form-check {
padding: 0.5rem 0.5rem 1rem;
padding: 0.5rem 0.5rem 1rem;
.form-check-label {
margin-left: 0.5rem;
padding-top: 0.2rem;
@@ -52,6 +54,14 @@
width: 100%;
}
.canvas-video {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.camera-btn {
margin: 10px;
}
@@ -68,4 +78,4 @@
opacity: 0;
background: white;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

@@ -29,6 +29,25 @@ export async function getExistingIdVerification() {
}
}
/**
* Get the learner's enrollments. Used to check whether the learner is enrolled
* in a verified course mode.
*
* Returns an array: [{...data, mode: String}]
*/
export async function getEnrollments() {
const url = `${getConfig().LMS_BASE_URL}/api/enrollment/v1/enrollment`;
const requestConfig = {
headers: { Accept: 'application/json' },
};
try {
const { data } = await getAuthenticatedHttpClient().get(url, requestConfig);
return data;
} catch (e) {
return [];
}
}
/**
* Submit ID verifiction to LMS.
*
@@ -45,7 +64,6 @@ export async function submitIdVerification(verificationData) {
facePhotoFile: 'face_image',
idPhotoFile: 'photo_id_image',
idPhotoName: 'full_name',
courseRunKey: 'course_key',
};
const postData = {};
// Don't include blank/null/undefined values.
@@ -66,6 +84,9 @@ export async function submitIdVerification(verificationData) {
await getAuthenticatedHttpClient().post(url, urlEncodedPostData, requestConfig);
return { success: true, message: null };
} catch (e) {
return { success: false, message: String(e) }; // TODO: is String(e) right?
return {
success: false,
status: e.customAttributes.httpErrorStatus,
message: String(e) };
}
}

View File

@@ -1,6 +1,6 @@
import React, { useContext, useState, useEffect, useRef } from 'react';
import { Form } from '@edx/paragon';
import { Link } from 'react-router-dom';
import { Link, useHistory } from 'react-router-dom';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -11,6 +11,7 @@ import { IdVerificationContext } from '../IdVerificationContext';
import messages from '../IdVerification.messages';
function GetNameIdPanel(props) {
const { push } = useHistory();
const panelSlug = 'get-name-id';
const [nameMatches, setNameMatches] = useState(true);
const nameInputRef = useRef();
@@ -42,6 +43,15 @@ function GetNameIdPanel(props) {
}
}, [nameMatches, blankName]);
const handleSubmit = (e) => {
e.preventDefault();
// If the input is empty, or if no changes have been made to the
// mismatching name, the user should not be able to proceed.
if (!invalidName && !blankName) {
push(nextPanelSlug);
}
};
return (
<BasePanel
name={panelSlug}
@@ -51,7 +61,7 @@ function GetNameIdPanel(props) {
{props.intl.formatMessage(messages['id.verification.account.name.instructions'])}
</p>
<Form>
<Form onSubmit={handleSubmit}>
<Form.Group>
<Form.Label htmlFor="nameMatchesYes">
{props.intl.formatMessage(messages['id.verification.account.name.radio.label'])}

View File

@@ -6,6 +6,7 @@ import { useNextPanelSlug } from '../routing-utilities';
import BasePanel from './BasePanel';
import CameraHelp from '../CameraHelp';
import messages from '../IdVerification.messages';
import exampleCard from '../assets/example-card.png';
function IdContextPanel(props) {
const panelSlug = 'id-context';
@@ -24,7 +25,7 @@ function IdContextPanel(props) {
<p>
{props.intl.formatMessage(messages['id.verification.photo.tips.list.description'])}
</p>
<ul className="mb-0">
<ul>
<li>
{props.intl.formatMessage(messages['id.verification.id.tips.list.well.lit'])}
</li>
@@ -32,6 +33,10 @@ function IdContextPanel(props) {
{props.intl.formatMessage(messages['id.verification.id.tips.list.clear'])}
</li>
</ul>
<img
src={exampleCard}
alt={props.intl.formatMessage(messages['id.verification.example.card.alt'])}
/>
</div>
</div>
<CameraHelp isOpen />

View File

@@ -8,6 +8,7 @@ import BasePanel from './BasePanel';
import { IdVerificationContext } from '../IdVerificationContext';
import messages from '../IdVerification.messages';
import exampleCard from '../assets/example-card.png';
function ReviewRequirementsPanel(props) {
const { userId } = useContext(IdVerificationContext);
@@ -54,6 +55,10 @@ function ReviewRequirementsPanel(props) {
</h6>
<p className="mb-0">
{props.intl.formatMessage(messages['id.verification.requirements.card.id.text'])}
<img
src={exampleCard}
alt={props.intl.formatMessage(messages['id.verification.example.card.alt'])}
/>
</p>
</div>
</div>

View File

@@ -10,17 +10,9 @@ import messages from '../IdVerification.messages';
function SubmittedPanel(props) {
const { userId } = useContext(IdVerificationContext);
const [returnUrl, setReturnUrl] = useState('dashboard');
const [returnText, setReturnText] = useState('id.verification.return.dashboard');
const panelSlug = 'submitted';
// If the user accessed IDV through a course,
// link back to that course rather than the dashboard
useEffect(() => {
if (sessionStorage.getItem('courseRunKey')) {
setReturnUrl(`courses/${sessionStorage.getItem('courseRunKey')}`);
setReturnText('id.verification.return.course');
}
sendTrackEvent('edx.id_verification.submitted', {
category: 'id_verification',
user_id: userId,
@@ -37,10 +29,10 @@ function SubmittedPanel(props) {
</p>
<a
className="btn btn-primary"
href={`${getConfig().LMS_BASE_URL}/${returnUrl}`}
href={`${getConfig().LMS_BASE_URL}/dashboard`}
data-testid="return-button"
>
{props.intl.formatMessage(messages[returnText])}
{props.intl.formatMessage(messages['id.verification.return.dashboard'])}
</a>
</BasePanel>
);

View File

@@ -1,6 +1,6 @@
import React, { useState, useContext } from 'react';
import { history } from '@edx/frontend-platform';
import { Input, Button, Spinner } from '@edx/paragon';
import { Input, Button, Spinner, Alert } from '@edx/paragon';
import { Link } from 'react-router-dom';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
@@ -25,6 +25,7 @@ function SummaryPanel(props) {
} = useContext(IdVerificationContext);
const nameToBeUsed = idPhotoName || nameOnAccount || '';
const [isSubmitting, setIsSubmitting] = useState(false);
const [submissionError, setSubmissionError] = useState(null);
function SubmitButton() {
async function handleClick() {
@@ -39,6 +40,10 @@ function SummaryPanel(props) {
if (result.success) {
stopUserMedia();
history.push(nextPanelSlug);
} else {
stopUserMedia();
setIsSubmitting(false);
setSubmissionError(result);
}
}
return (
@@ -54,11 +59,43 @@ function SummaryPanel(props) {
);
}
function getError() {
if (submissionError.status === 400) {
if (submissionError.message.includes('face_image')) {
return props.intl.formatMessage(messages['id.verification.submission.alert.error.face']);
} else if (submissionError.message.includes('Photo ID image')) {
return props.intl.formatMessage(messages['id.verification.submission.alert.error.id']);
} else if (submissionError.message.includes('Name')) {
return props.intl.formatMessage(messages['id.verification.submission.alert.error.name']);
}
}
return (
<FormattedMessage
id="idv.submission.alert.error"
defaultMessage={`
We encountered a technical error while trying to submit ID verification.
This might be a temporary issue, so please try again in a few minutes.
If the problem persists, please go to {support_link} for help.
`}
values={{ support_link: <Alert.Link href="https://support.edx.org/hc/en-us">{props.intl.formatMessage(messages['id.verification.review.error'])}</Alert.Link> }}
/>
);
}
return (
<BasePanel
name={panelSlug}
title={props.intl.formatMessage(messages['id.verification.review.title'])}
>
{submissionError &&
<Alert
variant="danger"
data-testid="submission-error"
dismissible
onClose={() => setSubmissionError(null)}
>
{getError()}
</Alert>}
<p>
{props.intl.formatMessage(messages['id.verification.review.description'])}
</p>

View File

@@ -24,7 +24,7 @@ function TakeIdPhotoPanel(props) {
<p>
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.camera'])}
</p>
<Camera onImageCapture={setIdPhotoFile} />
<Camera onImageCapture={setIdPhotoFile} isPortrait={false} />
</div>
<CameraHelp />
<div className="action-row" style={{ visibility: idPhotoFile ? 'unset' : 'hidden' }}>

View File

@@ -33,7 +33,7 @@ function TakePortraitPhotoPanel(props) {
<p>
{props.intl.formatMessage(messages['id.verification.portrait.photo.instructions.camera'])}
</p>
<Camera onImageCapture={setFacePhotoFile} />
<Camera onImageCapture={setFacePhotoFile} isPortrait />
</div>
) : (
<div>

View File

@@ -5,29 +5,31 @@ import { render, cleanup, act, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import '@edx/frontend-platform/analytics';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import ExistingRequest from '../ExistingRequest';
const IntlExistingRequest = injectIntl(ExistingRequest);
import { ERROR_REASONS } from '../IdVerificationContext';
import AccessBlocked from '../AccessBlocked';
const IntlAccessBlocked = injectIntl(AccessBlocked);
const history = createMemoryHistory();
describe('ExistingRequest', () => {
describe('AccessBlocked', () => {
const defaultProps = {
intl: {},
status: '',
error: '',
};
afterEach(() => {
cleanup();
});
it('renders correctly when status is pending', async () => {
defaultProps.status = 'pending';
it('renders correctly when there is an existing request', async () => {
defaultProps.error = ERROR_REASONS.EXISTING_REQUEST;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IntlExistingRequest {...defaultProps} />
<IntlAccessBlocked {...defaultProps} />
</IntlProvider>
</Router>
)));
@@ -37,29 +39,29 @@ describe('ExistingRequest', () => {
expect(text).toBeInTheDocument();
});
it('renders correctly when status is approved', async () => {
defaultProps.status = 'approved';
it('renders correctly when learner is not enrolled in a verified course mode', async () => {
defaultProps.error = ERROR_REASONS.COURSE_ENROLLMENT;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IntlExistingRequest {...defaultProps} />
<IntlAccessBlocked {...defaultProps} />
</IntlProvider>
</Router>
)));
const text = screen.getByText(/You have already submitted your verification information./);
const text = screen.getByText(/You are not currently enrolled in a course that requires identity verification./);
expect(text).toBeInTheDocument();
});
it('renders correctly when status is denied', async () => {
defaultProps.status = 'denied';
defaultProps.error = ERROR_REASONS.CANNOT_VERIFY;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IntlExistingRequest {...defaultProps} />
<IntlAccessBlocked {...defaultProps} />
</IntlProvider>
</Router>
)));

View File

@@ -4,10 +4,16 @@ import { createMemoryHistory } from 'history';
import '@testing-library/jest-dom/extend-expect';
import { render, cleanup, screen, act, fireEvent } from '@testing-library/react';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import * as blazeface from '@tensorflow-models/blazeface';
import * as analytics from '@edx/frontend-platform/analytics';
import { IdVerificationContext } from '../IdVerificationContext';
import Camera from '../Camera';
jest.mock('jslib-html5-camera-photo');
jest.mock('@tensorflow-models/blazeface');
jest.mock('@edx/frontend-platform/analytics');
analytics.sendTrackEvent = jest.fn();
window.HTMLMediaElement.prototype.play = () => {};
@@ -19,6 +25,13 @@ describe('SubmittedPanel', () => {
const defaultProps = {
intl: {},
onImageCapture: jest.fn(),
isPortrait: true,
};
const idProps = {
intl: {},
onImageCapture: jest.fn(),
isPortrait: false,
};
const contextValue = {};
@@ -42,4 +55,130 @@ describe('SubmittedPanel', () => {
fireEvent.click(button);
expect(defaultProps.onImageCapture).toHaveBeenCalled();
});
it('shows correct help text for portrait photo capture', async () => {
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCamera {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const helpText = screen.getByTestId('videoDetectionHelpText');
expect(helpText.textContent).toEqual(expect.stringContaining('Your face can be seen clearly'));
});
it('shows correct help text for id photo capture', async () => {
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCamera {...idProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const helpText = screen.getByTestId('videoDetectionHelpText');
expect(helpText.textContent).toEqual(expect.stringContaining('The face can be seen clearly'));
});
it('shows spinner when loading face detection', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCamera {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
await fireEvent.loadedData(screen.queryByTestId('video'));
const checkbox = await screen.findByLabelText('Enable Face Detection');
fireEvent.click(checkbox);
expect(screen.queryByTestId('spinner')).toBeDefined();
});
it('canvas is visible when detection is enabled', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCamera {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
await fireEvent.loadedData(screen.queryByTestId('video'));
expect(screen.queryByTestId('detection-canvas')).toHaveStyle('display:none');
const checkbox = await screen.findByLabelText('Enable Face Detection');
await fireEvent.click(checkbox);
expect(screen.queryByTestId('detection-canvas')).toHaveStyle('display:block');
});
it('blazeface is called when detection is enabled', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCamera {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
await fireEvent.loadedData(screen.queryByTestId('video'));
const checkbox = await screen.findByLabelText('Enable Face Detection');
await fireEvent.click(checkbox);
setTimeout(() => { expect(blazeface.load).toHaveBeenCalled(); }, 2000);
});
it('sends tracking events on portrait photo page', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCamera {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
await fireEvent.loadedData(screen.queryByTestId('video'));
const checkbox = await screen.findByLabelText('Enable Face Detection');
await fireEvent.click(checkbox);
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.id_verification.user_photo.face_detection_enabled');
await fireEvent.click(checkbox);
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.id_verification.user_photo.face_detection_disabled');
});
it('sends tracking events on id photo page', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCamera {...idProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
await fireEvent.loadedData(screen.queryByTestId('video'));
const checkbox = await screen.findByLabelText('Enable Face Detection');
await fireEvent.click(checkbox);
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.id_verification.id_photo.face_detection_enabled');
await fireEvent.click(checkbox);
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.id_verification.id_photo.face_detection_disabled');
});
});

View File

@@ -3,11 +3,12 @@ import { render, cleanup, act } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import { getExistingIdVerification } from '../data/service';
import { getExistingIdVerification, getEnrollments } from '../data/service';
import { IdVerificationContextProvider } from '../IdVerificationContext';
jest.mock('../data/service', () => ({
getExistingIdVerification: jest.fn(),
getEnrollments: jest.fn(() => []),
}));
describe('IdVerificationContext', () => {
@@ -20,7 +21,7 @@ describe('IdVerificationContext', () => {
cleanup();
});
it('renders correctly and calls getExistingIdVerification', async () => {
it('renders correctly and calls getExistingIdVerification + getEnrollments', async () => {
await act(async () => render((
<AppContext.Provider value={{ authenticatedUser: { userId: 3 } }}>
<IntlProvider locale="en">
@@ -29,5 +30,6 @@ describe('IdVerificationContext', () => {
</AppContext.Provider>
)));
expect(getExistingIdVerification).toHaveBeenCalled();
expect(getEnrollments).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,86 @@
import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import configureStore from 'redux-mock-store';
import { render, act } from '@testing-library/react';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import IdVerificationPage from '../IdVerificationPage';
import * as selectors from '../data/selectors';
jest.mock('../data/selectors', () => {
return jest.fn().mockImplementation(() => ({ idVerificationSelector: () => ({}) }));
});
jest.mock('../IdVerificationContext', () => {
const IdVerificationContextProvider = jest.fn(({ children }) => children)
return { IdVerificationContextProvider };
});
jest.mock('../panels/ReviewRequirementsPanel');
jest.mock('../panels/RequestCameraAccessPanel');
jest.mock('../panels/PortraitPhotoContextPanel');
jest.mock('../panels/TakePortraitPhotoPanel');
jest.mock('../panels/IdContextPanel');
jest.mock('../panels/GetNameIdPanel');
jest.mock('../panels/TakeIdPhotoPanel');
jest.mock('../panels/SummaryPanel');
jest.mock('../panels/SubmittedPanel');
const IntlIdVerificationPage = injectIntl(IdVerificationPage);
const mockStore = configureStore();
const history = createMemoryHistory();
describe('IdVerificationPage', () => {
selectors.mockClear();
jest.spyOn(Storage.prototype, 'setItem')
const store = mockStore();
const props = {
intl: {},
};
it('does not store irrelevant query params', async () => {
history.push('/?test=irrelevant')
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<Provider store={store}>
<IntlIdVerificationPage {...props} />
</Provider>
</IntlProvider>
</Router>
)));
expect(sessionStorage.setItem).toHaveBeenCalledTimes(0);
})
it('does not store empty course_id', async () => {
history.push('/?course_id=')
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<Provider store={store}>
<IntlIdVerificationPage {...props} />
</Provider>
</IntlProvider>
</Router>
)));
expect(sessionStorage.setItem).toHaveBeenCalledTimes(0);
})
it('decodes and stores course_id', async () => {
history.push('/?course_id=course-v1%3AedX%2BDemoX%2BDemo_Course')
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<Provider store={store}>
<IntlIdVerificationPage {...props} />
</Provider>
</IntlProvider>
</Router>
)));
expect(sessionStorage.setItem).toHaveBeenCalledWith(
'courseRunKey',
'course-v1:edX+DemoX+Demo_Course'
);
});
});

View File

@@ -47,19 +47,4 @@ describe('SubmittedPanel', () => {
const button = await screen.findByTestId('return-button');
expect(button).toHaveTextContent(/Return to Your Dashboard/);
});
it('links to course with courseRunKey', async () => {
Storage.prototype.getItem = jest.fn(() => 'courseRunKey');
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlSubmittedPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const button = await screen.findByTestId('return-button');
expect(button).toHaveTextContent(/Return to Course/);
});
});

View File

@@ -5,7 +5,7 @@ import { render, cleanup, act, screen, fireEvent, waitFor } from '@testing-libra
import '@edx/frontend-platform/analytics';
import '@testing-library/jest-dom/extend-expect';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { submitIdVerification } from '../../data/service';
import * as dataService from '../../data/service';
import { IdVerificationContext } from '../../IdVerificationContext';
import SummaryPanel from '../../panels/SummaryPanel';
@@ -13,9 +13,8 @@ jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
}));
jest.mock('../../data/service', () => ({
submitIdVerification: jest.fn(() => ({ success: true, message: null })),
}));
jest.mock('../../data/service');
dataService.submitIdVerification = jest.fn().mockReturnValue({ success: true });
const IntlSummaryPanel = injectIntl(SummaryPanel);
@@ -34,7 +33,7 @@ describe('SummaryPanel', () => {
stopUserMedia: jest.fn(),
};
beforeEach(async () => {
const getPanel = async () => {
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
@@ -44,13 +43,14 @@ describe('SummaryPanel', () => {
</IntlProvider>
</Router>
)));
});
};
afterEach(() => {
cleanup();
});
it('routes back to TakePortraitPhotoPanel', async () => {
await getPanel();
const button = await screen.findByTestId('portrait-retake');
fireEvent.click(button);
expect(history.location.pathname).toEqual('/take-portrait-photo');
@@ -58,6 +58,7 @@ describe('SummaryPanel', () => {
});
it('routes back to TakeIdPhotoPanel', async () => {
await getPanel();
const button = await screen.findByTestId('id-retake');
fireEvent.click(button);
expect(history.location.pathname).toEqual('/take-id-photo');
@@ -65,6 +66,7 @@ describe('SummaryPanel', () => {
});
it('allows user to upload ID photo', async () => {
await getPanel();
const collapsible = await screen.getAllByRole('button', { 'aria-expanded': false })[0];
fireEvent.click(collapsible);
const uploadButton = await screen.getByTestId('fileUpload');
@@ -72,9 +74,61 @@ describe('SummaryPanel', () => {
});
it('submits', async () => {
await getPanel();
const button = await screen.findByTestId('submit-button');
fireEvent.click(button);
expect(submitIdVerification).toHaveBeenCalled();
await waitFor(() => expect(contextValue.stopUserMedia).toHaveBeenCalled())
expect(dataService.submitIdVerification).toHaveBeenCalled();
await waitFor(() => expect(contextValue.stopUserMedia).toHaveBeenCalled());
});
it('shows error when cannot submit', async () => {
dataService.submitIdVerification = jest.fn().mockReturnValue({ success: false });
await getPanel();
const button = await screen.findByTestId('submit-button');
await act(async () => fireEvent.click(button));
expect(dataService.submitIdVerification).toHaveBeenCalled();
const error = await screen.getByTestId('submission-error');
expect(error).toBeDefined();
});
it('displays correct error for missing portrait photo', async () => {
dataService.submitIdVerification = jest.fn().mockReturnValue({
success: false,
status: 400,
message: 'Missing required parameter face_image',
});
await getPanel();
const button = await screen.findByTestId('submit-button');
await act(async () => fireEvent.click(button));
const error = await screen.getByTestId('submission-error');
expect(error).toHaveTextContent('A photo of your face is required. Please retake your portrait photo.');
});
it('displays correct error for missing id photo', async () => {
dataService.submitIdVerification = jest.fn().mockReturnValue({
success: false,
status: 400,
message: 'Photo ID image is required if the user does not have an initial verification attempt.',
});
await getPanel();
const button = await screen.findByTestId('submit-button');
await act(async () => fireEvent.click(button));
const error = await screen.getByTestId('submission-error');
expect(error).toHaveTextContent('A photo of your ID card is required. Please retake your ID photo.');
});
it('displays correct error for missing account name', async () => {
dataService.submitIdVerification = jest.fn().mockReturnValue({
success: false,
status: 400,
message: 'Name must be at least 1 character long.',
});
await getPanel();
const button = await screen.findByTestId('submit-button');
await act(async () => fireEvent.click(button));
const error = await screen.getByTestId('submission-error');
expect(error).toHaveTextContent(
'A valid account name is required. Please update your account name to match the name on your ID.'
);
});
});