diff --git a/.github/labeler.yml b/.github/labeler.yml index cae5f5099b4..1581cb23b3d 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -23,4 +23,3 @@ - client/i18n/**/* - config/crowdin/**/* - config/i18n/**/* - - tools/crowdin/**/* diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index eb6949ec6f2..c096bb0e5fa 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,13 +1,15 @@ name: CI - Run CodeQL Analysis on: push: - branches: [main] paths-ignore: - 'docs/**' + branches: + - 'main' pull_request: - branches: [main] paths-ignore: - 'docs/**' + branches: + - 'main' permissions: contents: read @@ -32,8 +34,8 @@ jobs: - name: Checkout repository uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3 - name: Setup CodeQL - uses: github/codeql-action/init@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # v2 + uses: github/codeql-action/init@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2 with: languages: ${{ matrix.language }} - name: Perform Analysis - uses: github/codeql-action/analyze@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # v2 + uses: github/codeql-action/analyze@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2 diff --git a/.github/workflows/e2e-mobile.yml b/.github/workflows/e2e-mobile.yml index 0fac7e65a62..4b688f1bc72 100644 --- a/.github/workflows/e2e-mobile.yml +++ b/.github/workflows/e2e-mobile.yml @@ -4,11 +4,14 @@ on: # push: # paths-ignore: # - 'docs/**' - # branches-ignore: - # - 'renovate/**' + # branches: + # - 'main' # pull_request: # paths-ignore: # - 'docs/**' + # branches: + # - 'main' + # - 'next-**' jobs: mobile-test: diff --git a/.github/workflows/e2e-third-party.yml b/.github/workflows/e2e-third-party.yml index ab4d14744ac..411b42b630b 100644 --- a/.github/workflows/e2e-third-party.yml +++ b/.github/workflows/e2e-third-party.yml @@ -5,7 +5,7 @@ name: CI - E2E - 3rd party donation tests on: push: branches: - - 'prod-*' + - 'prod-**' paths-ignore: - 'docs/**' diff --git a/.github/workflows/e2e-web.yml b/.github/workflows/e2e-web.yml index 891c6ce59f3..1d8c8abe740 100644 --- a/.github/workflows/e2e-web.yml +++ b/.github/workflows/e2e-web.yml @@ -3,14 +3,14 @@ on: push: paths-ignore: - 'docs/**' - branches-ignore: - - 'renovate/**' - - 'next-api' + branches: + - 'main' pull_request: paths-ignore: - 'docs/**' - branches-ignore: - - 'next-api' + branches: + - 'main' + - 'next-**' jobs: build-client: diff --git a/.github/workflows/node.js-tests-upcoming.yml b/.github/workflows/node.js-tests-upcoming.yml index a12d2165b13..ffe5f3a6f89 100644 --- a/.github/workflows/node.js-tests-upcoming.yml +++ b/.github/workflows/node.js-tests-upcoming.yml @@ -2,14 +2,20 @@ name: CI - Node.js Test Upcoming env: NODE_OPTIONS: '--max_old_space_size=6144' on: + # Run on push events, but only for the below branches push: branches: - # Treat the below branches as special case for working on workflows - - actions-** - - upcoming-** + - 'main' + - 'prod-**' + # Run on pull requests, but only for the below targets + pull_request: + branches: + - 'main' + - 'next-**' schedule: # run this Action every 14 days - cron: '0 * */14 * *' + # Run on demand workflow_dispatch: permissions: diff --git a/.github/workflows/node.js-tests.yml b/.github/workflows/node.js-tests.yml index c434960e7b4..5dd1891c297 100644 --- a/.github/workflows/node.js-tests.yml +++ b/.github/workflows/node.js-tests.yml @@ -1,14 +1,21 @@ name: CI - Node.js Test Current env: NODE_OPTIONS: '--max_old_space_size=6144' + on: + # Run on push events, but only for the below branches push: - branches-ignore: - - 'renovate/**' - - 'next-api' + branches: + - 'main' + - 'prod-**' + # Run on pull requests, but only for the below targets pull_request: - branches-ignore: - - 'next-api' + branches: + - 'main' + - 'next-**' + # Run on Merge Queue + merge_group: + types: [checks_requested] permissions: contents: read diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 00000000000..54b67b14d53 --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,8 @@ +FROM gitpod/workspace-mongodb:latest + +# from https://www.gitpod.io/docs/introduction/languages/javascript#node-versions +RUN bash -c 'VERSION="lts/*" \ + && source $HOME/.nvm/nvm.sh && nvm install $VERSION \ + && nvm use $VERSION && nvm alias default $VERSION' + +RUN echo "nvm use default &>/dev/null" >> ~/.bashrc.d/51-nvm-fix diff --git a/.gitpod.yml b/.gitpod.yml index d69ba77091e..a312e48f94a 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,4 +1,5 @@ -image: gitpod/workspace-mongodb +image: + file: .gitpod.Dockerfile ports: - port: 27017 # mongodb onOpen: ignore diff --git a/api-server/package.json b/api-server/package.json index 7be433231a7..8b2b9214fea 100644 --- a/api-server/package.json +++ b/api-server/package.json @@ -45,6 +45,7 @@ "dedent": "0.7.0", "dotenv": "6.2.0", "express-flash": "0.0.2", + "express-rate-limit": "^6.7.0", "express-session": "1.17.3", "express-validator": "6.14.1", "helmet": "3.23.3", @@ -60,12 +61,14 @@ "mongodb": "3.6.9", "morgan": "1.10.0", "nanoid": "3.3.4", + "node-fetch": "^2.6.7", "nodemailer-ses-transport": "1.5.1", "passport": "0.4.1", "passport-auth0": "1.4.2", "passport-local": "1.0.0", "passport-mock-strategy": "2.0.0", "query-string": "6.14.0", + "rate-limit-mongo": "^2.3.2", "rx": "4.1.0", "stripe": "8.205.0", "uuid": "3.4.0", diff --git a/api-server/src/common/models/user.js b/api-server/src/common/models/user.js index 31efb6a04d5..028ffcc7dc9 100644 --- a/api-server/src/common/models/user.js +++ b/api-server/src/common/models/user.js @@ -162,6 +162,8 @@ export default function initializeUser(User) { User.definition.properties.rand.default = getRandomNumber; // increase user accessToken ttl to 900 days User.settings.ttl = 900 * 24 * 60 * 60 * 1000; + // Sets ttl to 900 days for mobile login created access tokens + User.settings.maxTTL = 900 * 24 * 60 * 60 * 1000; // username should not be in blocklist User.validatesExclusionOf('username', { @@ -341,6 +343,21 @@ export default function initializeUser(User) { ); }; + User.prototype.mobileLoginByRequest = function mobileLoginByRequest( + req, + res + ) { + return new Promise((resolve, reject) => + this.createAccessToken({}, (err, accessToken) => { + if (err) { + return reject(err); + } + setAccessTokenToResponse({ accessToken }, req, res); + return resolve(accessToken); + }) + ); + }; + User.afterRemote('logout', function ({ req, res }, result, next) { removeCookies(req, res); next(); diff --git a/api-server/src/server/boot/authentication.js b/api-server/src/server/boot/authentication.js index b55a90cdc49..c7589da53ce 100644 --- a/api-server/src/server/boot/authentication.js +++ b/api-server/src/server/boot/authentication.js @@ -2,10 +2,9 @@ import dedent from 'dedent'; import { check } from 'express-validator'; import jwt from 'jsonwebtoken'; import passport from 'passport'; +import fetch from 'node-fetch'; import { isEmail } from 'validator'; - import { jwtSecret } from '../../../../config/secrets'; - import { decodeEmail } from '../../common/utils'; import { createPassportCallbackAuthenticator, @@ -14,7 +13,11 @@ import { } from '../component-passport'; import { wrapHandledError } from '../utils/create-handled-error.js'; import { removeCookies } from '../utils/getSetAccessToken'; -import { ifUserRedirectTo, ifNoUserRedirectHome } from '../utils/middleware'; +import { + ifUserRedirectTo, + ifNoUserRedirectHome, + ifNotMobileRedirect +} from '../utils/middleware'; import { getRedirectParams } from '../utils/redirection'; import { createDeleteUserToken } from '../middlewares/user-token'; @@ -34,6 +37,7 @@ module.exports = function enableAuthentication(app) { // enable loopback access control authentication. see: // loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html app.enableAuth(); + const ifNotMobile = ifNotMobileRedirect(); const ifUserRedirect = ifUserRedirectTo(); const ifNoUserRedirect = ifNoUserRedirectHome(); const devSaveAuthCookies = devSaveResponseAuthCookies(); @@ -87,6 +91,8 @@ module.exports = function enableAuthentication(app) { createGetPasswordlessAuth(app) ); + api.get('/mobile-login', ifNotMobile, ifUserRedirect, mobileLogin(app)); + app.use(api); }; @@ -188,3 +194,53 @@ function createGetPasswordlessAuth(app) { ); }; } + +function mobileLogin(app) { + const { + models: { User } + } = app; + return async function getPasswordlessAuth(req, res, next) { + try { + const auth0Res = await fetch( + `https://${process.env.AUTH0_DOMAIN}/userinfo`, + { + headers: { Authorization: req.headers.authorization } + } + ); + + if (!auth0Res.ok) { + return next( + wrapHandledError(new Error('Invalid Auth0 token'), { + type: 'danger', + message: 'We could not log you in, please try again in a moment.', + status: auth0Res.status + }) + ); + } + + const { email } = await auth0Res.json(); + + if (!isEmail(email)) { + return next( + wrapHandledError(new TypeError('decoded email is invalid'), { + type: 'danger', + message: 'The email is incorrectly formatted', + status: 400 + }) + ); + } + + User.findOne$({ where: { email } }) + .do(async user => { + if (!user) { + user = await User.create({ email }); + } + await user.mobileLoginByRequest(req, res); + res.end(); + }) + .subscribe(() => {}, next); + } catch (err) { + next(err); + } + }; +} diff --git a/api-server/src/server/middleware.json b/api-server/src/server/middleware.json index 69a47f44021..df8d4ae9ff8 100644 --- a/api-server/src/server/middleware.json +++ b/api-server/src/server/middleware.json @@ -39,7 +39,10 @@ "./middlewares/constant-headers": {}, "./middlewares/csp": {}, "./middlewares/flash-cheaters": {}, - "./middlewares/passport-login": {} + "./middlewares/passport-login": {}, + "./middlewares/rate-limit": { + "paths": ["/mobile-login"] + } }, "files": {}, "final:after": { diff --git a/api-server/src/server/middlewares/rate-limit.js b/api-server/src/server/middlewares/rate-limit.js new file mode 100644 index 00000000000..b461039a12c --- /dev/null +++ b/api-server/src/server/middlewares/rate-limit.js @@ -0,0 +1,23 @@ +import rateLimit from 'express-rate-limit'; +import MongoStore from 'rate-limit-mongo'; + +const url = process.env.MONGODB || process.env.MONGOHQ_URL; + +// Rate limit for mobile login +// 10 requests per 15 minute windows +export default function rateLimitMiddleware() { + return rateLimit({ + windowMs: 15 * 60 * 1000, + max: 10, + standardHeaders: true, + legacyHeaders: false, + keyGenerator: req => { + return req.headers['x-forwarded-for'] || 'localhost'; + }, + store: new MongoStore({ + collectionName: 'UserRateLimit', + uri: url, + expireTimeMs: 15 * 60 * 1000 + }) + }); +} diff --git a/api-server/src/server/middlewares/request-authorization.js b/api-server/src/server/middlewares/request-authorization.js index b12858222bf..60aedcdb936 100644 --- a/api-server/src/server/middlewares/request-authorization.js +++ b/api-server/src/server/middlewares/request-authorization.js @@ -26,6 +26,7 @@ const updateHooksRE = /^\/hooks\/update-paypal$/; // note: this would be replaced by webhooks later const donateRE = /^\/donate\/charge-stripe$/; const submitCoderoadChallengeRE = /^\/coderoad-challenge-completed$/; +const mobileLoginRE = /^\/mobile-login\/?$/; const _pathsAllowedREs = [ authRE, @@ -41,7 +42,8 @@ const _pathsAllowedREs = [ unsubscribeRE, updateHooksRE, donateRE, - submitCoderoadChallengeRE + submitCoderoadChallengeRE, + mobileLoginRE ]; export function isAllowedPath(path, pathsAllowedREs = _pathsAllowedREs) { diff --git a/api-server/src/server/utils/middleware.js b/api-server/src/server/utils/middleware.js index 52f5551fc85..61144fae64b 100644 --- a/api-server/src/server/utils/middleware.js +++ b/api-server/src/server/utils/middleware.js @@ -77,6 +77,20 @@ export function ifUserRedirectTo(status) { }; } +export function ifNotMobileRedirect() { + return (req, res, next) => { + // + // Todo: Use the below check once we have done more research on usage + // + // const isMobile = /(iPhone|iPad|Android)/.test(req.headers['user-agent']); + // if (!isMobile) { + // res.json({ error: 'not from mobile' }); + // } else { + // next(); + // } + next(); + }; +} // for use with express-validator error formatter export const createValidatorErrorHandler = (...args) => diff --git a/client/gatsby-browser.js b/client/gatsby-browser.js index 513cc8d8a55..f93c9c10a26 100644 --- a/client/gatsby-browser.js +++ b/client/gatsby-browser.js @@ -6,7 +6,7 @@ import { Provider } from 'react-redux'; import i18n from './i18n/config'; import AppMountNotifier from './src/components/app-mount-notifier'; -import { createStore } from './src/redux/createStore'; +import { createStore } from './src/redux/create-store'; import layoutSelector from './utils/gatsby/layout-selector'; import GrowthBookProvider from './src/components/growth-book/growth-book-wrapper'; diff --git a/client/gatsby-ssr.js b/client/gatsby-ssr.js index e7995a365d0..c0f6e4237c6 100644 --- a/client/gatsby-ssr.js +++ b/client/gatsby-ssr.js @@ -4,7 +4,7 @@ import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import i18n from './i18n/config'; -import { createStore } from './src/redux/createStore'; +import { createStore } from './src/redux/create-store'; import layoutSelector from './utils/gatsby/layout-selector'; import { getheadTagComponents, getPostBodyComponents } from './utils/tags'; import GrowthBookProvider from './src/components/growth-book/growth-book-wrapper'; diff --git a/client/i18n/locales/arabic/intro.json b/client/i18n/locales/arabic/intro.json index 7bd66b174b1..fdc68370e15 100644 --- a/client/i18n/locales/arabic/intro.json +++ b/client/i18n/locales/arabic/intro.json @@ -778,14 +778,6 @@ "أسناد: Rosetta Code" ] }, - "the-odin-project": { - "title": "مشروع أودين", - "intro": ["A description is to be determined"] - }, - "the-odin-project-projects": { - "title": "The Odin Project Projects", - "intro": ["A description is to be determined"] - }, "project-euler": { "title": "مشروع Euler", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "مشروع أودين", + "intro": [ + "The Odin Project is one of those \"What I wish I had when I was learning\" resources. ", + "Not everyone has access to a computer science education or the funds to attend an intensive coding school and neither of those is right for everyone anyway.", + "This project is designed to fill in the gap for people who are trying to hack it on their own but still want a high quality education." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Learn HTML Foundations", + "intro": ["A description is to be determined"] + }, + "top-build-a-recipe-project": { + "title": "Learn HTML Foundations by Building a Recipe Page", + "intro": ["A description is to be determined"] + } + } + }, "misc-text": { "certification": "شهادة {{cert}}", "browse-other": "تصفح الشهادات المجانية الأخرى\n(نوصي بالقيام بها بالترتيب)", diff --git a/client/i18n/locales/arabic/translations.json b/client/i18n/locales/arabic/translations.json index a6f2db3f8b3..0f8b449fc7b 100644 --- a/client/i18n/locales/arabic/translations.json +++ b/client/i18n/locales/arabic/translations.json @@ -15,8 +15,8 @@ "show-cert": "عرض الشهادة", "claim-cert": "المطالبة بالشهادة", "save-progress": "حفظ التقدم", - "accepted-honesty": "لقد قبلت سياسة الصدق الأكاديمي الخاصة بنا.", - "agree": "موافق", + "accepted-honesty": "لقد وافقت على سياستنا للصدق الأكاديمي.", + "agree-honesty": "أوافق على سياسة freeCodeCamp للصدق الأكاديمي.", "save-portfolio": "حفظ عنصر الحافظة هذا", "remove-portfolio": "إزالة عنصر الحافظة هذا", "add-portfolio": "إضافة عنصر حافظة جديد", @@ -302,7 +302,6 @@ "certs": "شهادة {{title}}" }, "editor-tabs": { - "info": "معلومات", "code": "الكود", "tests": "الاختبارات", "restart": "أعد التشغيل", @@ -518,7 +517,7 @@ "opens-new-window": "فتح في نافذة جديدة" }, "flash": { - "honest-first": "للمطالبة بشهادة ، يجب عليك أولاً قبول سياسة الصدق الأكاديمي الخاصة بنا", + "honest-first": "للمطالبة بشهادة، يجب عليك أولاً الموافقة على سياسة للصدق الأكاديمي", "really-weird": "حدث شيء غريب حقاً، إذا حدث مرة أخرى، يرجى النظر في الإبلاغ عنها على https://github.com/freeCodeCamp/freeCodeCamp/issues/new", "not-right": "يبدو ان هناك خطأ ما. لقد تم إنشاء تقرير وتم إخطار فريق freeCodeCamp.org", "went-wrong": "حدث خطأ ما، الرجاء التحقق والمحاولة مرة أخرى", diff --git a/client/i18n/locales/chinese-traditional/intro.json b/client/i18n/locales/chinese-traditional/intro.json index 3c4f393dd4b..6ee34449b97 100644 --- a/client/i18n/locales/chinese-traditional/intro.json +++ b/client/i18n/locales/chinese-traditional/intro.json @@ -778,14 +778,6 @@ "屬性:Rosetta 代碼" ] }, - "the-odin-project": { - "title": "The Odin Project", - "intro": ["A description is to be determined"] - }, - "the-odin-project-projects": { - "title": "The Odin Project Projects", - "intro": ["A description is to be determined"] - }, "project-euler": { "title": "歐拉計劃", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "The Odin Project", + "intro": [ + "The Odin Project is one of those \"What I wish I had when I was learning\" resources. ", + "Not everyone has access to a computer science education or the funds to attend an intensive coding school and neither of those is right for everyone anyway.", + "This project is designed to fill in the gap for people who are trying to hack it on their own but still want a high quality education." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Learn HTML Foundations", + "intro": ["A description is to be determined"] + }, + "top-build-a-recipe-project": { + "title": "Learn HTML Foundations by Building a Recipe Page", + "intro": ["A description is to be determined"] + } + } + }, "misc-text": { "certification": "{{cert}} 認證", "browse-other": "瀏覽我們的其他免費認證\n(我們建議你按順序學習)", diff --git a/client/i18n/locales/chinese-traditional/translations.json b/client/i18n/locales/chinese-traditional/translations.json index 0be7fdf3146..4489ee6239e 100644 --- a/client/i18n/locales/chinese-traditional/translations.json +++ b/client/i18n/locales/chinese-traditional/translations.json @@ -15,8 +15,8 @@ "show-cert": "顯示認證", "claim-cert": "申請認證", "save-progress": "保存進度", - "accepted-honesty": "你已接受我們的《學術誠信條例》", - "agree": "同意", + "accepted-honesty": "You have agreed to our Academic Honesty Policy.", + "agree-honesty": "I agree to freeCodeCamp's Academic Honesty Policy.", "save-portfolio": "保存這個作品集項目", "remove-portfolio": "移除這個作品集項目", "add-portfolio": "增加一個新的作品集項目", @@ -302,7 +302,6 @@ "certs": "{{title}} 認證" }, "editor-tabs": { - "info": "信息", "code": "編程", "tests": "測試", "restart": "重啓", @@ -518,7 +517,7 @@ "opens-new-window": "Opens in new window" }, "flash": { - "honest-first": "申請認證之前,你必須先接受我們的《學術誠信條例》", + "honest-first": "To claim a certification, you must first agree to our academic honesty policy", "really-weird": "出現了一些奇怪的情況。如果再出現這種情況,請考慮在 https://github.com/freeCodeCamp/freeCodeCamp/issues/new 提交 issue。", "not-right": "有些不對勁。已生成報告,通知 freeCodeCamp.org 團隊。", "went-wrong": "出了點問題,請檢查並重試。", diff --git a/client/i18n/locales/chinese/intro.json b/client/i18n/locales/chinese/intro.json index fcb1167338d..ce8beea0d5b 100644 --- a/client/i18n/locales/chinese/intro.json +++ b/client/i18n/locales/chinese/intro.json @@ -778,14 +778,6 @@ "属性:Rosetta 代码" ] }, - "the-odin-project": { - "title": "The Odin Project", - "intro": ["A description is to be determined"] - }, - "the-odin-project-projects": { - "title": "The Odin Project Projects", - "intro": ["A description is to be determined"] - }, "project-euler": { "title": "欧拉计划", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "The Odin Project", + "intro": [ + "The Odin Project is one of those \"What I wish I had when I was learning\" resources. ", + "Not everyone has access to a computer science education or the funds to attend an intensive coding school and neither of those is right for everyone anyway.", + "This project is designed to fill in the gap for people who are trying to hack it on their own but still want a high quality education." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Learn HTML Foundations", + "intro": ["A description is to be determined"] + }, + "top-build-a-recipe-project": { + "title": "Learn HTML Foundations by Building a Recipe Page", + "intro": ["A description is to be determined"] + } + } + }, "misc-text": { "certification": "{{cert}} 认证", "browse-other": "浏览我们的其他免费认证\n(我们建议你按顺序学习)", diff --git a/client/i18n/locales/chinese/translations.json b/client/i18n/locales/chinese/translations.json index 2a0ae67e97f..8d7625d6a4c 100644 --- a/client/i18n/locales/chinese/translations.json +++ b/client/i18n/locales/chinese/translations.json @@ -15,8 +15,8 @@ "show-cert": "显示认证", "claim-cert": "申请认证", "save-progress": "保存进度", - "accepted-honesty": "你已接受我们的《学术诚信条例》", - "agree": "同意", + "accepted-honesty": "You have agreed to our Academic Honesty Policy.", + "agree-honesty": "I agree to freeCodeCamp's Academic Honesty Policy.", "save-portfolio": "保存这个作品集项目", "remove-portfolio": "移除这个作品集项目", "add-portfolio": "增加一个新的作品集项目", @@ -302,7 +302,6 @@ "certs": "{{title}} 认证" }, "editor-tabs": { - "info": "信息", "code": "编程", "tests": "测试", "restart": "重启", @@ -518,7 +517,7 @@ "opens-new-window": "Opens in new window" }, "flash": { - "honest-first": "申请认证之前,你必须先接受我们的《学术诚信条例》", + "honest-first": "To claim a certification, you must first agree to our academic honesty policy", "really-weird": "出现了一些奇怪的情况。如果再出现这种情况,请考虑在 https://github.com/freeCodeCamp/freeCodeCamp/issues/new 提交 issue。", "not-right": "有些不对劲。已生成报告,通知 freeCodeCamp.org 团队。", "went-wrong": "出了点问题,请检查并重试。", diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index 17926a60efb..c1d679e160e 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -778,14 +778,6 @@ "Attribute: Rosetta Code" ] }, - "the-odin-project": { - "title": "The Odin Project", - "intro": ["A description is to be determined"] - }, - "the-odin-project-projects": { - "title": "The Odin Project Projects", - "intro": ["A description is to be determined"] - }, "project-euler": { "title": "Project Euler", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "The Odin Project", + "intro": [ + "The Odin Project is one of those \"What I wish I had when I was learning\" resources. ", + "Not everyone has access to a computer science education or the funds to attend an intensive coding school and neither of those is right for everyone anyway.", + "This project is designed to fill in the gap for people who are trying to hack it on their own but still want a high quality education." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Learn HTML Foundations", + "intro": ["A description is to be determined"] + }, + "top-build-a-recipe-project": { + "title": "Learn HTML Foundations by Building a Recipe Page", + "intro": ["A description is to be determined"] + } + } + }, "misc-text": { "certification": "{{cert}} Certification", "browse-other": "Browse our other free certifications\n(we recommend doing these in order)", diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index e0d4fe5f970..874814e5f31 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -15,8 +15,8 @@ "show-cert": "Show Certification", "claim-cert": "Claim Certification", "save-progress": "Save Progress", - "accepted-honesty": "You have accepted our Academic Honesty Policy.", - "agree": "Agree", + "accepted-honesty": "You have agreed to our Academic Honesty Policy.", + "agree-honesty": "I agree to freeCodeCamp's Academic Honesty Policy.", "save-portfolio": "Save this portfolio item", "remove-portfolio": "Remove this portfolio item", "add-portfolio": "Add a new portfolio Item", @@ -302,7 +302,6 @@ "certs": "{{title}} Certification" }, "editor-tabs": { - "info": "Info", "code": "Code", "tests": "Tests", "restart": "Restart", @@ -518,7 +517,7 @@ "opens-new-window": "Opens in new window" }, "flash": { - "honest-first": "To claim a certification, you must first accept our academic honesty policy", + "honest-first": "To claim a certification, you must first agree to our academic honesty policy", "really-weird": "Something really weird happened, if it happens again, please consider raising an issue on https://github.com/freeCodeCamp/freeCodeCamp/issues/new", "not-right": "Something is not quite right. A report has been generated and the freeCodeCamp.org team have been notified", "went-wrong": "Something went wrong, please check and try again", diff --git a/client/i18n/locales/espanol/intro.json b/client/i18n/locales/espanol/intro.json index 325a88b33da..6779cf07a7f 100644 --- a/client/i18n/locales/espanol/intro.json +++ b/client/i18n/locales/espanol/intro.json @@ -778,14 +778,6 @@ "Atributo: Código Rosetta" ] }, - "the-odin-project": { - "title": "The Odin Project", - "intro": ["A description is to be determined"] - }, - "the-odin-project-projects": { - "title": "The Odin Project Projects", - "intro": ["A description is to be determined"] - }, "project-euler": { "title": "Project Euler", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "The Odin Project", + "intro": [ + "The Odin Project is one of those \"What I wish I had when I was learning\" resources. ", + "Not everyone has access to a computer science education or the funds to attend an intensive coding school and neither of those is right for everyone anyway.", + "This project is designed to fill in the gap for people who are trying to hack it on their own but still want a high quality education." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Learn HTML Foundations", + "intro": ["A description is to be determined"] + }, + "top-build-a-recipe-project": { + "title": "Learn HTML Foundations by Building a Recipe Page", + "intro": ["A description is to be determined"] + } + } + }, "misc-text": { "certification": "Certificación de {{cert}}", "browse-other": "Navega por nuestras otras certificaciones gratuitas\n(recomendamos hacerlo en orden)", diff --git a/client/i18n/locales/espanol/translations.json b/client/i18n/locales/espanol/translations.json index 969ab8661f6..e9b48effd1c 100644 --- a/client/i18n/locales/espanol/translations.json +++ b/client/i18n/locales/espanol/translations.json @@ -11,12 +11,12 @@ "view": "Ver", "view-code": "Mostrar Código", "view-project": "Mostrar Proyecto", - "view-cert-title": "View {{certTitle}}", + "view-cert-title": "Ver {{certTitle}}", "show-cert": "Mostrar certificación", "claim-cert": "Solicitar certificación", "save-progress": "Guardar progreso", "accepted-honesty": "Has aceptado nuestra Política de Honestidad Académica.", - "agree": "Aceptar", + "agree-honesty": "Estoy de acuerdo con la Política de Honestidad Académica de freeCodeCamp.", "save-portfolio": "Guardar este elemento de portafolio", "remove-portfolio": "Eliminar este elemento de portafolio", "add-portfolio": "Agregar un nuevo elemento de portafolio", @@ -53,7 +53,7 @@ "check-code": "Comprueba tu código (Ctrl + Enter)", "check-code-2": "Comprueba tu código", "reset": "Restablecer", - "reset-step": "Reset This Step", + "reset-step": "Restablecer este paso", "help": "Ayuda", "get-help": "Obtener ayuda", "watch-video": "Ver un Video", @@ -159,7 +159,7 @@ "internet": "Tu presencia en Internet", "portfolio": "Ajustes de portafolio", "privacy": "Ajustes de privacidad", - "personal-info": "Personal Information" + "personal-info": "Información Personal" }, "danger": { "heading": "Zona de peligro", @@ -228,12 +228,12 @@ "page-number": "{{pageNumber}} de {{totalPages}}" }, "footer": { - "tax-exempt-status": "freeCodeCamp is a donor-supported tax-exempt 501(c)(3) charitable organization (United States Federal Tax Identification Number: 82-0779546)", + "tax-exempt-status": "freeCodeCamp es una organización benéfica 501(c)(3) exenta de impuestos apoyada por donantes (Número de Identificación Fiscal Federal De Los Estados Unidos: 82-0779546)", "mission-statement": "Nuestra misión: ayudar a las personas a aprender a programar de forma gratuita. Logramos esto mediante la creación de miles de videos, artículos y lecciones de programación interactivas, todos disponibles gratuitamente para el público. También tenemos miles de grupos de estudio de FreeCodeCamp en todo el mundo.", "donation-initiatives": "Las donaciones a freeCodeCamp se destinan a nuestras iniciativas educativas y ayudan a pagar los servidores, los servicios y el personal.", "donate-text": "Puedes <1>hacer una donación deducible de impuestos aquí.", "trending-guides": "Guías de tendencias", - "our-nonprofit": "Our Charity", + "our-nonprofit": "Nuestra Caridad", "links": { "about": "Acerca de", "alumni": "Red de ex-Alumnos", @@ -274,9 +274,9 @@ "add-subtitles": "Ayudar a mejorar o agregar subtítulos", "wrong-answer": "Lo siento, esa no es la respuesta correcta. ¡Vuelve a intentarlo!", "check-answer": "Haz clic en el botón de abajo para verificar tu respuesta.", - "assignment-not-complete": "Please finish the assignments", - "assignments": "Assignments", - "question": "Question", + "assignment-not-complete": "Por favor, completa las tareas", + "assignments": "Asignaciones", + "question": "Pregunta", "solution-link": "Enlace a la solución", "github-link": "Enlace de GitHub", "submit-and-go": "Enviar y pasar a mi siguiente desafío", @@ -302,7 +302,6 @@ "certs": "Certificación {{title}}" }, "editor-tabs": { - "info": "Info", "code": "Código", "tests": "Pruebas", "restart": "Reiniciar", @@ -336,15 +335,15 @@ "sorry-dont-giveup": "Lo sentimos, su código no pasa. No te rindas.", "challenges-completed": "{{completedCount}} de {{totalChallenges}} desafíos completados", "season-greetings-fcc": "Saludos de Temporada de la comunidad freeCodeCamp 🎉", - "if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our charity's mission.", + "if-getting-value": "Si estás obteniendo mucho de freeCodeCamp, ahora es un buen momento para donar con el fin de apoyar nuestra misión sin fines de lucro.", "building-a-university": "Estamos construyendo un programa gratuito de grado universitario en ciencias de la computación", "if-help-university": "Ya hemos hecho un montón de progresos. Apoyamos nuestra caridad con el largo camino por delante." }, "donate": { - "title": "Support our charity", + "title": "Apoya nuestra caridad", "processing": "Estamos procesando tu donación.", "redirecting": "Redirigiendo...", - "thanks": "Thanks for donating", + "thanks": "Gracias por donar", "thank-you": "Gracias por tu apoyo.", "additional": "Puede hacer una donación adicional única de cualquier monto utilizando este enlace: <0>{{url}}", "help-more": "Ayúdanos a hacer más", @@ -364,9 +363,9 @@ "your-donation-2": "Tu donación de ${{usd}} proporcionará {{hours}} horas de aprendizaje a personas de todo el mundo cada mes.", "your-donation-3": "Tu donación de ${{usd}} proporcionará {{hours}} horas de aprendizaje a personas de todo el mundo cada año.", "become-supporter": "Conviértete en un colaborador", - "duration": "Become a one-time supporter of our charity.", - "duration-2": "Become a monthly supporter of our charity.", - "duration-4": "Become a supporter of our charity", + "duration": "Se un benefactor de nuestra organización benéfica con una donación única.", + "duration-2": "Se un benefactor mensual de nuestra organización benéfica.", + "duration-4": "Se un benefactor de nuestra organización benéfica", "nicely-done": "Bien hecho. Acabas de completar {{block}}.", "credit-card": "Tarjeta de crédito", "credit-card-2": "O dona con una tarjeta de crédito:", @@ -380,25 +379,25 @@ "email-receipt": "Correo electrónico (te enviaremos un recibo de donación deducible de impuestos):", "need-help": "¿Necesitas ayuda con tus donaciones actuales o pasadas?", "forward-receipt": "Envía una copia de tu recibo de donación a donors@freecodecamp.org y dinos cómo podemos ayudar.", - "efficiency": "freeCodeCamp is a highly efficient education charity.", + "efficiency": "freeCodeCamp es una organización benéfica educativa de gran eficacia.", "why-donate-1": "Cuando donas a freeCodeCamp, ayudas a las personas a aprender nuevas habilidades y proveer para sus familias", "why-donate-2": "También nos ayudas a crear nuevos recursos para que los utilices y amplíes tus propias habilidades tecnológicas.", "bigger-donation": "¿Quieres hacer una donación más grande de una sola vez, envíanos un cheque o da de otras maneras?", - "other-ways": "Here are many <0>other ways you can support our charity's mission.", + "other-ways": "Aquí hay muchas <0>otras formas de apoyar la misión de nuestra organización benéfica.", "failed-pay": "Oh no. Parece que tu transacción no se realizó. ¿Podrías intentarlo de nuevo?", "try-again": "Por favor, intenta de nuevo.", "card-number": "Tu número de tarjeta:", "expiration": "Fecha de vencimiento:", "secure-donation": "Donación segura", "faq": "Preguntas frecuentes", - "only-you": "Only you can see this message. Congratulations on earning this certification. It's no easy task. Running freeCodeCamp isn't easy either. Nor is it cheap. Help us help you and many other people around the world. Make a tax-deductible supporting donation to our charity today.", + "only-you": "Sólo tu puedes ver este mensaje. Enhorabuena por haber obtenido esta certificación. No es tarea fácil. Administrar freeCodeCamp tampoco es fácil. Tampoco es barato. Ayúdanos a ayudarte a ti y a muchas otras personas de todo el mundo. Haz hoy una donación deducible de impuestos a nuestra organización benéfica.", "get-help": "¿Cómo puedo obtener ayuda con mis donaciones?", "how-transparent": "¿Qué tan transparente es freeCodeCamp.org?", "very-transparent": "Muy Transparente. Tenemos una valoración de transparencia de platino en GuideStar.org.", "download-irs": "Puedes <0>descargar nuestra Carta de Determinación de IRS aquí.", "download-990": "Puedes <0>descargar nuestro 990 más reciente (informe anual de impuestos) aquí.", "how-efficient": "¿Qué tan eficiente es freeCodeCamp?", - "fcc-budget": "freeCodeCamp's budget is much smaller than most comparable charity. We haven't brought in professional fundraisers. Instead, Quincy does everything himself.", + "fcc-budget": "El presupuesto de freeCodeCamp es mucho menor que el de la mayoría de organizaciones benéficas comparables. No hemos contratado a profesionales para recaudar fondos. En su lugar, Quincy lo hace todo él mismo.", "help-millions": "Sin embargo, con un presupuesto de tan sólo unos cientos de miles de dólares al año, hemos podido ayudar a millones de personas.", "how-one-time": "¿Cómo puedo realizar una donación única?", "one-time": "Si prefieres hacer donaciones únicas, puedes apoyar la misión de freeCodeCamp cuando tengas dinero suficiente para hacer una aportación. Puedes usar <0>este enlace para donar cualquier cantidad que te sea cómodo a través de PayPal.", @@ -518,7 +517,7 @@ "opens-new-window": "Opens in new window" }, "flash": { - "honest-first": "Para reclamar una certificación, primero debes aceptar nuestra política de honestidad académica.", + "honest-first": "To claim a certification, you must first agree to our academic honesty policy", "really-weird": "Sucedió algo realmente extraño. Si vuelve a ocurrir, considera hacer un reporte del problema en https://github.com/freeCodeCamp/freeCodeCamp/issues/new", "not-right": "Algo no está bien. Se ha generado un informe y se ha notificado al equipo de freeCodeCamp.org", "went-wrong": "Algo salió mal, verifica e intenta nuevamente", diff --git a/client/i18n/locales/german/intro.json b/client/i18n/locales/german/intro.json index 8a6cdac58c2..bfd275257ae 100644 --- a/client/i18n/locales/german/intro.json +++ b/client/i18n/locales/german/intro.json @@ -778,14 +778,6 @@ "Attribut: Rosetta Code" ] }, - "the-odin-project": { - "title": "The Odin Project", - "intro": ["A description is to be determined"] - }, - "the-odin-project-projects": { - "title": "The Odin Project Projects", - "intro": ["A description is to be determined"] - }, "project-euler": { "title": "Projekt Euler", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "The Odin Project", + "intro": [ + "The Odin Project is one of those \"What I wish I had when I was learning\" resources. ", + "Not everyone has access to a computer science education or the funds to attend an intensive coding school and neither of those is right for everyone anyway.", + "This project is designed to fill in the gap for people who are trying to hack it on their own but still want a high quality education." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Learn HTML Foundations", + "intro": ["A description is to be determined"] + }, + "top-build-a-recipe-project": { + "title": "Learn HTML Foundations by Building a Recipe Page", + "intro": ["A description is to be determined"] + } + } + }, "misc-text": { "certification": "{{cert}} Zertifikat", "browse-other": "Stöbere in unseren anderen kostenlosen Zertifizierungen\n(Wir empfehlen, diese der Reihe nach zu erledigen)", diff --git a/client/i18n/locales/german/translations.json b/client/i18n/locales/german/translations.json index 6026281a64f..452779783dd 100644 --- a/client/i18n/locales/german/translations.json +++ b/client/i18n/locales/german/translations.json @@ -15,8 +15,8 @@ "show-cert": "Zertifikat anzeigen", "claim-cert": "Zertifizierung anfordern", "save-progress": "Fortschritt speichern", - "accepted-honesty": "Du hast unsere Akademische Ehrlichkeitsrichtlinie akzeptiert.", - "agree": "Zustimmen", + "accepted-honesty": "You have agreed to our Academic Honesty Policy.", + "agree-honesty": "I agree to freeCodeCamp's Academic Honesty Policy.", "save-portfolio": "Dieses Portfolioelement speichern", "remove-portfolio": "Dieses Portfolioelement entfernen", "add-portfolio": "Neues Portfolioelement hinzufügen", @@ -302,7 +302,6 @@ "certs": "{{title}} Zertifizierung" }, "editor-tabs": { - "info": "Informationen", "code": "Code", "tests": "Tests", "restart": "Neustart", @@ -518,7 +517,7 @@ "opens-new-window": "Opens in new window" }, "flash": { - "honest-first": "Um eine Zertifizierung zu erlangen, musst du zunächst unsere Richtlinie zur akademischen Ehrlichkeit akzeptieren", + "honest-first": "To claim a certification, you must first agree to our academic honesty policy", "really-weird": "Etwas wirklich Seltsames ist passiert. Wenn es wieder passiert, erwäge bitte, einen Fehler auf https://github.com/freeCodeCamp/freeCodeCamp/issues/new zu melden.", "not-right": "Irgendetwas ist nicht in Ordnung. Es wurde ein Bericht erstellt und das freeCodeCamp.org Team wurde benachrichtigt", "went-wrong": "Etwas ist schief gelaufen, bitte überprüfe und versuche es erneut", diff --git a/client/i18n/locales/italian/intro.json b/client/i18n/locales/italian/intro.json index b5b1cfa3350..8a5f63f04c5 100644 --- a/client/i18n/locales/italian/intro.json +++ b/client/i18n/locales/italian/intro.json @@ -778,14 +778,6 @@ "Fonte: Codice Rosetta" ] }, - "the-odin-project": { - "title": "The Odin Project", - "intro": ["A description is to be determined"] - }, - "the-odin-project-projects": { - "title": "The Odin Project Projects", - "intro": ["A description is to be determined"] - }, "project-euler": { "title": "Progetto Eulero", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "The Odin Project", + "intro": [ + "The Odin Project is one of those \"What I wish I had when I was learning\" resources. ", + "Not everyone has access to a computer science education or the funds to attend an intensive coding school and neither of those is right for everyone anyway.", + "This project is designed to fill in the gap for people who are trying to hack it on their own but still want a high quality education." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Learn HTML Foundations", + "intro": ["A description is to be determined"] + }, + "top-build-a-recipe-project": { + "title": "Learn HTML Foundations by Building a Recipe Page", + "intro": ["A description is to be determined"] + } + } + }, "misc-text": { "certification": "Certificazione {{cert}}", "browse-other": "Sfoglia le altre nostre certificazioni gratuite\n(consigliamo di seguirle in ordine)", diff --git a/client/i18n/locales/italian/translations.json b/client/i18n/locales/italian/translations.json index 5a582964c31..bb22c1ac79f 100644 --- a/client/i18n/locales/italian/translations.json +++ b/client/i18n/locales/italian/translations.json @@ -15,8 +15,8 @@ "show-cert": "Mostra la Certificazione", "claim-cert": "Richiedi la Certificazione", "save-progress": "Salva l'avanzamento", - "accepted-honesty": "Hai accettato la nostra Politica di Onestà Accademica.", - "agree": "Accetta", + "accepted-honesty": "You have agreed to our Academic Honesty Policy.", + "agree-honesty": "I agree to freeCodeCamp's Academic Honesty Policy.", "save-portfolio": "Salva questo elemento del portfolio", "remove-portfolio": "Rimuovi questo elemento del portfolio", "add-portfolio": "Aggiungi un nuovo elemento nel portfolio", @@ -302,7 +302,6 @@ "certs": "Certificazione {{title}}" }, "editor-tabs": { - "info": "Informazioni", "code": "Codice", "tests": "Test", "restart": "Inizia da capo", @@ -518,7 +517,7 @@ "opens-new-window": "Apri in una nuova finestra" }, "flash": { - "honest-first": "Per richiedere una certificazione, è necessario prima accettare la nostra politica di onestà accademica", + "honest-first": "To claim a certification, you must first agree to our academic honesty policy", "really-weird": "È successo qualcosa di veramente strano, se succede di nuovo, ti preghiamo di considerare di sollevare un problema su https://github.com/freeCodeCamp/freeCodeCamp/issues/new", "not-right": "Qualcosa non è del tutto giusto. Un rapporto è stato generato e il team freeCodeCamp.org è stato avvisato", "went-wrong": "Qualcosa è andato storto, controlla e riprova", diff --git a/client/i18n/locales/japanese/intro.json b/client/i18n/locales/japanese/intro.json index 9af30b573f4..140caa5a7d9 100644 --- a/client/i18n/locales/japanese/intro.json +++ b/client/i18n/locales/japanese/intro.json @@ -778,14 +778,6 @@ "作: Rosetta Code" ] }, - "the-odin-project": { - "title": "The Odin Project", - "intro": ["A description is to be determined"] - }, - "the-odin-project-projects": { - "title": "The Odin Project Projects", - "intro": ["A description is to be determined"] - }, "project-euler": { "title": "プロジェクト・オイラー", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "The Odin Project", + "intro": [ + "The Odin Project is one of those \"What I wish I had when I was learning\" resources. ", + "Not everyone has access to a computer science education or the funds to attend an intensive coding school and neither of those is right for everyone anyway.", + "This project is designed to fill in the gap for people who are trying to hack it on their own but still want a high quality education." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Learn HTML Foundations", + "intro": ["A description is to be determined"] + }, + "top-build-a-recipe-project": { + "title": "Learn HTML Foundations by Building a Recipe Page", + "intro": ["A description is to be determined"] + } + } + }, "misc-text": { "certification": "{{cert}} 認定", "browse-other": "他の無料の認定講座を閲覧する\n(上から順に受講することをお勧めします)", diff --git a/client/i18n/locales/japanese/translations.json b/client/i18n/locales/japanese/translations.json index 778950dbae5..fa2cb2b43c2 100644 --- a/client/i18n/locales/japanese/translations.json +++ b/client/i18n/locales/japanese/translations.json @@ -15,8 +15,8 @@ "show-cert": "認定証を表示", "claim-cert": "認定証を取得", "save-progress": "進行状況を保存", - "accepted-honesty": "学問的誠実性ポリシーに同意しました。", - "agree": "同意する", + "accepted-honesty": "You have agreed to our Academic Honesty Policy.", + "agree-honesty": "I agree to freeCodeCamp's Academic Honesty Policy.", "save-portfolio": "このポートフォリオアイテムを保存", "remove-portfolio": "このポートフォリオアイテムを削除", "add-portfolio": "新規ポートフォリオアイテムを追加", @@ -302,7 +302,6 @@ "certs": "{{title}} 認定講座" }, "editor-tabs": { - "info": "詳細", "code": "コード", "tests": "テスト", "restart": "リスタート", @@ -518,7 +517,7 @@ "opens-new-window": "新しいウィンドウで開く" }, "flash": { - "honest-first": "認定証を請求するには、まず学問的誠実性ポリシーに同意する必要があります。", + "honest-first": "To claim a certification, you must first agree to our academic honesty policy", "really-weird": "予期しない問題が発生しました。この問題が何度も発生するようであれば、https://github.com/freeCodeCamp/freeCodeCamp/issues/new への Issue 登録をご検討ください。", "not-right": "問題が発生しました。レポートが生成され、freeCodeCamp.org チームへ通知されました。", "went-wrong": "問題が発生しました。ご確認の上もう一度お試しください。", diff --git a/client/i18n/locales/portuguese/intro.json b/client/i18n/locales/portuguese/intro.json index 230c7ef5be1..a0262686a7b 100644 --- a/client/i18n/locales/portuguese/intro.json +++ b/client/i18n/locales/portuguese/intro.json @@ -778,14 +778,6 @@ "Attribute: Rosetta Code" ] }, - "the-odin-project": { - "title": "The Odin Project", - "intro": ["Uma descrição deve ser determinada"] - }, - "the-odin-project-projects": { - "title": "Os projetos do The Odin Project", - "intro": ["Uma descrição deve ser determinada"] - }, "project-euler": { "title": "Projeto Euler", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "The Odin Project", + "intro": [ + "The Odin Project é um daqueles recursos do tipo \"O que eu gostaria de ter visto quando estava aprendendo\". ", + "Nem todas as pessoas têm acesso à educação em ciência da computação ou aos fundos necessários para frequentar uma escola de programação intensiva. De qualquer modo, não necessariamente, essas duas sejam a solução final para todos que queiram aprender.", + "Este projeto destina-se a preencher a lacuna para aquelas pessoas que tentam buscar suas próprias soluções, mas que continuam procurando uma educação de alta qualidade." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Aprenda o básico de HTML", + "intro": ["Uma descrição deve ser determinada"] + }, + "top-build-a-recipe-project": { + "title": "Aprenda o básico de HTML criando uma página de receitas", + "intro": ["Uma descrição deve ser determinada"] + } + } + }, "misc-text": { "certification": "Certificação {{cert}}", "browse-other": "Navegue por nossas outras certificações gratuitas\n(recomendamos fazer isto em sequência)", diff --git a/client/i18n/locales/portuguese/translations.json b/client/i18n/locales/portuguese/translations.json index 1d3aaf2d105..8d45abc16c5 100644 --- a/client/i18n/locales/portuguese/translations.json +++ b/client/i18n/locales/portuguese/translations.json @@ -15,8 +15,8 @@ "show-cert": "Exibir certificado", "claim-cert": "Solicitar certificação", "save-progress": "Salvar progresso", - "accepted-honesty": "Você aceitou nossa política de honestidade acadêmica.", - "agree": "Aceitar", + "accepted-honesty": "Você concordou com nossa política de honestidade acadêmica.", + "agree-honesty": "Concordo com a Política de Honestidade Acadêmica do FreeCodeCamp.", "save-portfolio": "Salvar esse item de portfólio", "remove-portfolio": "Remover este item de portfólio", "add-portfolio": "Adicionar um novo item de portfólio", @@ -302,7 +302,6 @@ "certs": "Certificação {{title}}" }, "editor-tabs": { - "info": "Informações", "code": "Código", "tests": "Testes", "restart": "Reiniciar", @@ -518,7 +517,7 @@ "opens-new-window": "Abre em uma nova janela" }, "flash": { - "honest-first": "Para solicitar uma certificação, você precisa primeiro aceitar nossa política de honestidade acadêmica", + "honest-first": "Para solicitar uma certificação, você precisa primeiro concordar com nossa política de honestidade acadêmica", "really-weird": "Algo realmente estranho aconteceu. Se acontecer novamente, considere apresentar um problema pelo endereço https://github.com/freeCodeCamp/freeCodeCamp/issues/new", "not-right": "Algo não está certo. Um relatório foi gerado e a equipe do freeCodeCamp.org foi notificada", "went-wrong": "Algo deu errado. Verifique e tente novamente", diff --git a/client/i18n/locales/ukrainian/intro.json b/client/i18n/locales/ukrainian/intro.json index 8ebbe1516d3..ec06abf9f61 100644 --- a/client/i18n/locales/ukrainian/intro.json +++ b/client/i18n/locales/ukrainian/intro.json @@ -778,14 +778,6 @@ "Атрибут: Rosetta Code" ] }, - "the-odin-project": { - "title": "Проєкт «Odin»", - "intro": ["Опис буде надано пізніше"] - }, - "the-odin-project-projects": { - "title": "Проєкти «Odin»", - "intro": ["Опис буде надано пізніше"] - }, "project-euler": { "title": "Проєкт «Ейлер»", "intro": [ @@ -795,6 +787,24 @@ } } }, + "the-odin-project": { + "title": "Проєкт «Odin»", + "intro": [ + "Проєкт «Odin» є одним з тих ресурсів, про які хотіли б дізнатися ще коли навчались. ", + "Не кожен має доступ до технологічної освіти або коштів, необхідних для відвідування інтенсивної школи. Однак це не остаточне рішення для тих, хто хоче вчитися.", + "Цей проєкт розроблений, щоб заповнити прогалину для людей, які намагаються вчитись самостійно, але все ж таки хочуть високоякісну освіту." + ], + "blocks": { + "top-learn-html-foundations": { + "title": "Вивчіть основи HTML", + "intro": ["Опис буде надано пізніше"] + }, + "top-build-a-recipe-project": { + "title": "Вивчіть основи HTML, побудувавши сторінку з рецептами", + "intro": ["Опис буде надано пізніше"] + } + } + }, "misc-text": { "certification": "Сертифікація «{{cert}}»", "browse-other": "Перегляньте інші безоплатні сертифікації\n(ми рекомендуємо виконувати їх послідовно)", diff --git a/client/i18n/locales/ukrainian/translations.json b/client/i18n/locales/ukrainian/translations.json index 8f33c528b3c..6d28efd4d02 100644 --- a/client/i18n/locales/ukrainian/translations.json +++ b/client/i18n/locales/ukrainian/translations.json @@ -15,8 +15,8 @@ "show-cert": "Показати сертифікацію", "claim-cert": "Отримати сертифікацію", "save-progress": "Зберегти прогрес", - "accepted-honesty": "Ви прийняли нашу Політику академічної доброчесності.", - "agree": "Прийняти", + "accepted-honesty": "Ви погодились з нашою політикою академічної доброчесності.", + "agree-honesty": "Я погоджуюсь з політикою академічної доброчесності freeCodeCamp.", "save-portfolio": "Зберегти цей елемент портфоліо", "remove-portfolio": "Видалити цей елемент портфоліо", "add-portfolio": "Додати новий елемент портфоліо", @@ -302,7 +302,6 @@ "certs": "Сертифікація «{{title}}»" }, "editor-tabs": { - "info": "Інформація", "code": "Код", "tests": "Тести", "restart": "Перезапустити", @@ -518,7 +517,7 @@ "opens-new-window": "Відкривається у новому вікні" }, "flash": { - "honest-first": "Щоб отримати сертифікацію, ви повинні спочатку прийняти нашу політику академічної доброчесності", + "honest-first": "Щоб отримати сертифікацію, ви повинні спочатку погодитись з нашою політикою академічної доброчесності", "really-weird": "Щось пішло не так. Якщо це повториться, будь ласка, повідомте про це за посиланням: https://github.com/freeCodeCamp/freeCodeCamp/issues/new", "not-right": "Щось пішло не так. Звіт було сформовано і команду freeCodeCamp.org вже сповістили.", "went-wrong": "Щось пішло не так. Будь ласка, перевірте та повторіть спробу.", diff --git a/client/i18n/schema-validation.ts b/client/i18n/schema-validation.ts index 02c0e86a1b6..d9ccd807d23 100644 --- a/client/i18n/schema-validation.ts +++ b/client/i18n/schema-validation.ts @@ -215,7 +215,7 @@ const schemaValidation = ( if ( fileName === 'motivation' && !(fileJson.motivationalQuotes as MotivationalQuotes).every( - (object: object) => + object => Object.prototype.hasOwnProperty.call(object, 'quote') && Object.prototype.hasOwnProperty.call(object, 'author') ) diff --git a/client/package.json b/client/package.json index 5ca557ff12f..bc3884d4532 100644 --- a/client/package.json +++ b/client/package.json @@ -39,16 +39,16 @@ "@babel/preset-env": "7.20.2", "@babel/preset-react": "7.18.6", "@babel/standalone": "7.20.15", - "@fortawesome/fontawesome-svg-core": "6.2.1", - "@fortawesome/free-brands-svg-icons": "6.2.1", - "@fortawesome/free-solid-svg-icons": "6.2.1", + "@fortawesome/fontawesome-svg-core": "6.3.0", + "@fortawesome/free-brands-svg-icons": "6.3.0", + "@fortawesome/free-solid-svg-icons": "6.3.0", "@fortawesome/react-fontawesome": "0.2.0", "@freecodecamp/curriculum-helpers": "1.1.0", "@freecodecamp/loop-protect": "3.0.0", "@freecodecamp/react-bootstrap": "0.32.3", - "@freecodecamp/react-calendar-heatmap": "1.0.0", + "@freecodecamp/react-calendar-heatmap": "1.1.0", "@freecodecamp/strip-comments": "3.0.1", - "@growthbook/growthbook-react": "0.11.1", + "@growthbook/growthbook-react": "0.11.2", "@loadable/component": "5.15.3", "@reach/router": "1.3.4", "@sentry/gatsby": "6.19.7", @@ -133,8 +133,8 @@ }, "devDependencies": { "@babel/types": "7.20.7", - "@codesee/babel-plugin-instrument": "0.494.0", - "@codesee/tracker": "0.494.0", + "@codesee/babel-plugin-instrument": "0.498.0", + "@codesee/tracker": "0.498.0", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "12.1.5", "@types/react-gtm-module": "2.0.1", diff --git a/client/src/__mocks__/fileMock.ts b/client/src/__mocks__/file-mock.ts similarity index 100% rename from client/src/__mocks__/fileMock.ts rename to client/src/__mocks__/file-mock.ts diff --git a/client/src/__mocks__/styleMock.ts b/client/src/__mocks__/style-mock.ts similarity index 100% rename from client/src/__mocks__/styleMock.ts rename to client/src/__mocks__/style-mock.ts diff --git a/client/src/assets/icons/API-icon.tsx b/client/src/assets/icons/api.tsx similarity index 100% rename from client/src/assets/icons/API-icon.tsx rename to client/src/assets/icons/api.tsx diff --git a/client/src/assets/icons/certification-icon.tsx b/client/src/assets/icons/certification.tsx similarity index 100% rename from client/src/assets/icons/certification-icon.tsx rename to client/src/assets/icons/certification.tsx diff --git a/client/src/assets/icons/D3-icon.tsx b/client/src/assets/icons/d3.tsx similarity index 100% rename from client/src/assets/icons/D3-icon.tsx rename to client/src/assets/icons/d3.tsx diff --git a/client/src/assets/icons/Database-icon.tsx b/client/src/assets/icons/database.tsx similarity index 100% rename from client/src/assets/icons/Database-icon.tsx rename to client/src/assets/icons/database.tsx diff --git a/client/src/assets/icons/FreeCodeCamp-logo.tsx b/client/src/assets/icons/freecodecamp.tsx similarity index 100% rename from client/src/assets/icons/FreeCodeCamp-logo.tsx rename to client/src/assets/icons/freecodecamp.tsx diff --git a/client/src/assets/icons/index.tsx b/client/src/assets/icons/index.tsx index 4adcec517a3..839f2090965 100644 --- a/client/src/assets/icons/index.tsx +++ b/client/src/assets/icons/index.tsx @@ -1,17 +1,18 @@ import React from 'react'; import { SuperBlocks } from '../../../../config/certification-settings'; -import APIIcon from './API-icon'; -import D3Icon from './D3-icon'; -import DatabaseIcon from './Database-icon'; -import JavaScriptIcon from './JavaScript-icon'; -import ReactIcon from './React-icon'; -import TensorflowIcon from './Tensorflow-icon'; +import APIIcon from './api'; +import D3Icon from './d3'; +import DatabaseIcon from './database'; +import JavaScriptIcon from './javascript'; +import ReactIcon from './react'; +import TensorflowIcon from './tensorflow'; import Algorithm from './algorithm'; import Analytics from './analytics'; import Clipboard from './clipboard'; -import PythonIcon from './python-icon'; +import PythonIcon from './python'; import ResponsiveDesign from './responsive-design'; import Shield from './shield'; +import VikingHelmet from './viking-helmet'; const iconMap = { [SuperBlocks.RespWebDesignNew]: ResponsiveDesign, @@ -27,7 +28,8 @@ const iconMap = { [SuperBlocks.DataAnalysisPy]: Analytics, [SuperBlocks.InfoSec]: Shield, [SuperBlocks.MachineLearningPy]: TensorflowIcon, - [SuperBlocks.CodingInterviewPrep]: Algorithm + [SuperBlocks.CodingInterviewPrep]: Algorithm, + [SuperBlocks.TheOdinProject]: VikingHelmet }; const generateIconComponent = ( diff --git a/client/src/assets/icons/inputReset.tsx b/client/src/assets/icons/input-reset.tsx similarity index 100% rename from client/src/assets/icons/inputReset.tsx rename to client/src/assets/icons/input-reset.tsx diff --git a/client/src/assets/icons/JavaScript-icon.tsx b/client/src/assets/icons/javascript.tsx similarity index 100% rename from client/src/assets/icons/JavaScript-icon.tsx rename to client/src/assets/icons/javascript.tsx diff --git a/client/src/assets/icons/Magnifier.tsx b/client/src/assets/icons/magnifier.tsx similarity index 100% rename from client/src/assets/icons/Magnifier.tsx rename to client/src/assets/icons/magnifier.tsx diff --git a/client/src/assets/icons/python-icon.tsx b/client/src/assets/icons/python.tsx similarity index 100% rename from client/src/assets/icons/python-icon.tsx rename to client/src/assets/icons/python.tsx diff --git a/client/src/assets/icons/React-icon.tsx b/client/src/assets/icons/react.tsx similarity index 100% rename from client/src/assets/icons/React-icon.tsx rename to client/src/assets/icons/react.tsx diff --git a/client/src/assets/icons/Tensorflow-icon.tsx b/client/src/assets/icons/tensorflow.tsx similarity index 100% rename from client/src/assets/icons/Tensorflow-icon.tsx rename to client/src/assets/icons/tensorflow.tsx diff --git a/client/src/assets/icons/viking-helmet.tsx b/client/src/assets/icons/viking-helmet.tsx new file mode 100644 index 00000000000..ab29caa29f3 --- /dev/null +++ b/client/src/assets/icons/viking-helmet.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +function VikingHelmet( + props: JSX.IntrinsicAttributes & React.SVGProps +): JSX.Element { + return ( + <> + + + + + ); +} + +VikingHelmet.displayName = 'VikingHelmet'; + +export default VikingHelmet; diff --git a/client/src/client-only-routes/show-certification.tsx b/client/src/client-only-routes/show-certification.tsx index 6ac89c242ed..6edff2f96a5 100644 --- a/client/src/client-only-routes/show-certification.tsx +++ b/client/src/client-only-routes/show-certification.tsx @@ -9,7 +9,7 @@ import { createSelector } from 'reselect'; import envData from '../../../config/env.json'; import { getLangCode } from '../../../config/i18n'; -import FreeCodeCampLogo from '../assets/icons/FreeCodeCamp-logo'; +import FreeCodeCampLogo from '../assets/icons/freecodecamp'; import DonateForm from '../components/Donation/donate-form'; import { createFlashMessage } from '../components/Flash/redux'; diff --git a/client/src/components/Header/components/nav-logo.tsx b/client/src/components/Header/components/nav-logo.tsx index 94111b149c7..393b8f32df0 100644 --- a/client/src/components/Header/components/nav-logo.tsx +++ b/client/src/components/Header/components/nav-logo.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import FreeCodeCampLogo from '../../../assets/icons/FreeCodeCamp-logo'; +import FreeCodeCampLogo from '../../../assets/icons/freecodecamp'; const NavLogo = (): JSX.Element => { const { t } = useTranslation(); diff --git a/client/src/components/Intro/intro.test.tsx b/client/src/components/Intro/intro.test.tsx index d1000de9fa0..61a55fe76f0 100644 --- a/client/src/components/Intro/intro.test.tsx +++ b/client/src/components/Intro/intro.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import renderer from 'react-test-renderer'; -import { createStore } from '../../redux/createStore'; +import { createStore } from '../../redux/create-store'; import Intro from '.'; diff --git a/client/src/components/SolutionViewer/SolutionViewer.tsx b/client/src/components/SolutionViewer/SolutionViewer.tsx index 255d9bf607d..841146b438b 100644 --- a/client/src/components/SolutionViewer/SolutionViewer.tsx +++ b/client/src/components/SolutionViewer/SolutionViewer.tsx @@ -9,7 +9,7 @@ type Props = { }; type Solution = Pick; -function SolutionViewer({ challengeFiles, solution }: Props) { +function SolutionViewer({ challengeFiles, solution }: Props): JSX.Element { const isLegacy = !challengeFiles || !challengeFiles.length; const solutions = isLegacy ? [ diff --git a/client/src/components/app-mount-notifier.test.tsx b/client/src/components/app-mount-notifier.test.tsx index 94121daba6c..215d99c94c0 100644 --- a/client/src/components/app-mount-notifier.test.tsx +++ b/client/src/components/app-mount-notifier.test.tsx @@ -5,7 +5,7 @@ import { Provider } from 'react-redux'; import { i18nextCodes } from '../../../config/i18n'; import i18nTestConfig from '../../i18n/config-for-tests'; -import { createStore } from '../redux/createStore'; +import { createStore } from '../redux/create-store'; import AppMountNotifier from './app-mount-notifier'; jest.unmock('react-i18next'); diff --git a/client/src/components/formHelpers/__snapshots__/block-save-wrapper.test.tsx.snap b/client/src/components/formHelpers/__snapshots__/block-save-wrapper.test.tsx.snap deleted file mode 100644 index b088aec7c3c..00000000000 --- a/client/src/components/formHelpers/__snapshots__/block-save-wrapper.test.tsx.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` snapshot 1`] = ` -
-
-
-`; diff --git a/client/src/components/formHelpers/block-save-wrapper.test.tsx b/client/src/components/formHelpers/block-save-wrapper.test.tsx deleted file mode 100644 index 25e9acff3bc..00000000000 --- a/client/src/components/formHelpers/block-save-wrapper.test.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { render } from '@testing-library/react'; -import React from 'react'; - -import BlockSaveWrapper from './block-save-wrapper'; - -test(' snapshot', () => { - const { container } = render(); - - expect(container).toMatchSnapshot(); -}); diff --git a/client/src/components/formHelpers/block-save-wrapper.tsx b/client/src/components/formHelpers/block-save-wrapper.tsx deleted file mode 100644 index 79186be5deb..00000000000 --- a/client/src/components/formHelpers/block-save-wrapper.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -const style = { - padding: '0 15px' -}; - -function BlockSaveWrapper({ - children -}: { - children?: React.ReactElement | null; -}): JSX.Element { - return
{children}
; -} - -BlockSaveWrapper.displayName = 'BlockSaveWrapper'; - -export default BlockSaveWrapper; diff --git a/client/src/components/formHelpers/form-fields.tsx b/client/src/components/formHelpers/form-fields.tsx index ef308ae6794..2d737f5da6c 100644 --- a/client/src/components/formHelpers/form-fields.tsx +++ b/client/src/components/formHelpers/form-fields.tsx @@ -1,6 +1,5 @@ import { Alert, - Col, ControlLabel, FormControl, FormGroup, @@ -80,7 +79,7 @@ function FormFields(props: FormFieldsProps): JSX.Element { ) : null; }; return ( -
+ <> {formFields .filter(formField => !ignored.includes(formField.name)) .map(({ name, label }) => ( @@ -93,35 +92,33 @@ function FormFields(props: FormFieldsProps): JSX.Element { name in placeholders ? placeholders[name] : ''; const isURL = types[name] === 'url'; return ( - - - {type === 'hidden' ? null : ( - {label} - )} - - {nullOrWarning( - value as string, - !pristine && error, - isURL, - name - )} - - + + {type === 'hidden' ? null : ( + {label} + )} + + {nullOrWarning( + value as string, + !pristine && error, + isURL, + name + )} + ); }} ))} -
+ ); } diff --git a/client/src/components/formHelpers/form.tsx b/client/src/components/formHelpers/form.tsx index 64c174decee..ff4d6f87a51 100644 --- a/client/src/components/formHelpers/form.tsx +++ b/client/src/components/formHelpers/form.tsx @@ -12,7 +12,6 @@ import { import FormFields, { FormOptions } from './form-fields'; import { default as BlockSaveButton } from './block-save-button'; -import { default as BlockSaveWrapper } from './block-save-wrapper'; type URLValues = { [key: string]: string; @@ -105,15 +104,13 @@ function DynamicForm({ style={{ width: '100%' }} > - - {hideButton ? null : ( - - {buttonText ? buttonText : null} - - )} - + {hideButton ? null : ( + + {buttonText ? buttonText : null} + + )} )} diff --git a/client/src/components/formHelpers/index.tsx b/client/src/components/formHelpers/index.tsx index d1f6f04d5e8..37b3b9ef31b 100644 --- a/client/src/components/formHelpers/index.tsx +++ b/client/src/components/formHelpers/index.tsx @@ -1,5 +1,4 @@ import { default as Form, ValidatedValues } from './form'; export { default as BlockSaveButton } from './block-save-button'; -export { default as BlockSaveWrapper } from './block-save-wrapper'; export { Form, ValidatedValues }; diff --git a/client/src/components/helpers/full-width-row.tsx b/client/src/components/helpers/full-width-row.tsx index e91da137a59..707b6621b62 100644 --- a/client/src/components/helpers/full-width-row.tsx +++ b/client/src/components/helpers/full-width-row.tsx @@ -6,15 +6,16 @@ interface FullWidthRowProps { className?: string; } -const FullWidthRow = ({ children, className }: FullWidthRowProps) => { - return ( - - - {children} - - - ); -}; +const FullWidthRow = ({ + children, + className +}: FullWidthRowProps): JSX.Element => ( + + + {children} + + +); FullWidthRow.displayName = 'FullWidthRow'; diff --git a/client/src/components/layouts/default.tsx b/client/src/components/layouts/default.tsx index 071611f499d..a86eae022a8 100644 --- a/client/src/components/layouts/default.tsx +++ b/client/src/components/layouts/default.tsx @@ -1,10 +1,10 @@ -import React, { Component, ReactNode } from 'react'; +import React, { ReactNode, useEffect } from 'react'; import Helmet from 'react-helmet'; import { TFunction, withTranslation } from 'react-i18next'; -// import TagManager from 'react-gtm-module'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { createSelector } from 'reselect'; +import { useStaticQuery, graphql } from 'gatsby'; import latoBoldURL from '../../../static/fonts/lato/Lato-Bold.woff'; import latoLightURL from '../../../static/fonts/lato/Lato-Light.woff'; @@ -17,7 +17,8 @@ import { isBrowser } from '../../../utils'; import { fetchUser, onlineStatusChange, - serverStatusChange + serverStatusChange, + updateAllChallengesInfo } from '../../redux/actions'; import { isSignedInSelector, @@ -26,7 +27,13 @@ import { isServerOnlineSelector, userFetchStateSelector } from '../../redux/selectors'; -import { UserFetchState, User } from '../../redux/prop-types'; + +import { + UserFetchState, + User, + AllChallengeNode, + CertificateNode +} from '../../redux/prop-types'; import BreadCrumb from '../../templates/Challenges/components/bread-crumb'; import Flash from '../Flash'; import { flashMessageSelector, removeFlashMessage } from '../Flash/redux'; @@ -41,6 +48,7 @@ import './fonts.css'; import './global.css'; import './variables.css'; import './rtl-layout.css'; +import { Themes } from '../settings/theme'; const mapStateToProps = createSelector( isSignedInSelector, @@ -76,7 +84,8 @@ const mapDispatchToProps = (dispatch: Dispatch) => fetchUser, removeFlashMessage, onlineStatusChange, - serverStatusChange + serverStatusChange, + updateAllChallengesInfo }, dispatch ); @@ -100,55 +109,54 @@ const getSystemTheme = () => : 'light-palette' }`; -class DefaultLayout extends Component { - static displayName = 'DefaultLayout'; - - componentDidMount() { - const { isSignedIn, fetchUser } = this.props; +function DefaultLayout({ + children, + hasMessage, + fetchState, + flashMessage, + isOnline, + isServerOnline, + isSignedIn, + removeFlashMessage, + showFooter = true, + isChallenge = false, + block, + superBlock, + t, + theme = Themes.Default, + user, + fetchUser, + updateAllChallengesInfo +}: DefaultLayoutProps): JSX.Element { + const { challengeEdges, certificateNodes } = useGetAllBlockIds(); + useEffect(() => { + // componentDidMount + updateAllChallengesInfo({ challengeEdges, certificateNodes }); if (!isSignedIn) { fetchUser(); } - window.addEventListener('online', this.updateOnlineStatus); - window.addEventListener('offline', this.updateOnlineStatus); - } + window.addEventListener('online', updateOnlineStatus); + window.addEventListener('offline', updateOnlineStatus); - componentWillUnmount() { - window.removeEventListener('online', this.updateOnlineStatus); - window.removeEventListener('offline', this.updateOnlineStatus); - } + return () => { + // componentWillUnmount. + window.removeEventListener('online', updateOnlineStatus); + window.removeEventListener('offline', updateOnlineStatus); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - updateOnlineStatus = () => { - const { onlineStatusChange } = this.props; + const updateOnlineStatus = () => { const isOnline = isBrowser() && 'navigator' in window ? window.navigator.onLine : null; return typeof isOnline === 'boolean' ? onlineStatusChange(isOnline) : null; }; - render() { - const { - children, - hasMessage, - fetchState, - flashMessage, - isOnline, - isServerOnline, - isSignedIn, - removeFlashMessage, - showFooter = true, - isChallenge = false, - block, - superBlock, - t, - theme = 'default', - user - } = this.props; - - const useSystemTheme = fetchState.complete && isSignedIn === false; - - if (fetchState.pending) { - return ; - } + const useSystemTheme = fetchState.complete && isSignedIn === false; + if (fetchState.pending) { + return ; + } else { return (
{ } } +// TODO: get challenge nodes directly rather than wrapped in edges +const useGetAllBlockIds = () => { + const { + allChallengeNode: { edges: challengeEdges }, + allCertificateNode: { nodes: certificateNodes } + }: { + allChallengeNode: AllChallengeNode; + allCertificateNode: { nodes: CertificateNode[] }; + } = useStaticQuery(graphql` + query getBlockNode { + allChallengeNode( + sort: { + fields: [ + challenge___superOrder + challenge___order + challenge___challengeOrder + ] + } + ) { + edges { + node { + challenge { + block + id + } + } + } + } + allCertificateNode { + nodes { + challenge { + certification + tests { + id + } + } + } + } + } + `); + + return { challengeEdges, certificateNodes }; +}; + +DefaultLayout.displayName = 'DefaultLayout'; + export default connect( mapStateToProps, mapDispatchToProps diff --git a/client/src/components/profile/__snapshots__/profile.test.tsx.snap b/client/src/components/profile/__snapshots__/profile.test.tsx.snap index bcb90fcb3d5..33e47cfaedf 100644 --- a/client/src/components/profile/__snapshots__/profile.test.tsx.snap +++ b/client/src/components/profile/__snapshots__/profile.test.tsx.snap @@ -232,7 +232,7 @@ exports[` renders correctly 1`] = ` xmlns="http://www.w3.org/2000/svg" > diff --git a/client/src/components/profile/components/time-line.test.tsx b/client/src/components/profile/components/time-line.test.tsx index 5d0dd9a1da1..2fb3e4c8669 100644 --- a/client/src/components/profile/components/time-line.test.tsx +++ b/client/src/components/profile/components/time-line.test.tsx @@ -4,7 +4,7 @@ import { useStaticQuery } from 'gatsby'; import React from 'react'; import { render, screen } from '../../../../utils/test-utils'; -import { createStore } from '../../../redux/createStore'; +import { createStore } from '../../../redux/create-store'; import TimeLine from './time-line'; const store = createStore(); diff --git a/client/src/components/profile/components/time-line.tsx b/client/src/components/profile/components/time-line.tsx index b9c19a0d426..38f65146227 100644 --- a/client/src/components/profile/components/time-line.tsx +++ b/client/src/components/profile/components/time-line.tsx @@ -14,7 +14,7 @@ import { getTitleFromId } from '../../../../../utils'; import { regeneratePathAndHistory } from '../../../../../utils/polyvinyl'; -import CertificationIcon from '../../../assets/icons/certification-icon'; +import CertificationIcon from '../../../assets/icons/certification'; import { CompletedChallenge } from '../../../redux/prop-types'; import ProjectPreviewModal from '../../../templates/Challenges/components/project-preview-modal'; import { openModal } from '../../../templates/Challenges/redux/actions'; diff --git a/client/src/components/profile/components/timeline-buttons.test.js b/client/src/components/profile/components/timeline-buttons.test.js index 33337e2aa7b..6a4b619bb1b 100644 --- a/client/src/components/profile/components/timeline-buttons.test.js +++ b/client/src/components/profile/components/timeline-buttons.test.js @@ -3,7 +3,7 @@ import React from 'react'; import renderer from 'react-test-renderer'; import { Provider } from 'react-redux'; -import { createStore } from '../../../redux/createStore'; +import { createStore } from '../../../redux/create-store'; import completedChallenges from '../../../__mocks__/completed-challenges.json'; import Timeline from './time-line'; diff --git a/client/src/components/search/searchBar/search-bar-optimized.tsx b/client/src/components/search/searchBar/search-bar-optimized.tsx index b7464973424..68be861c940 100644 --- a/client/src/components/search/searchBar/search-bar-optimized.tsx +++ b/client/src/components/search/searchBar/search-bar-optimized.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import Magnifier from '../../../assets/icons/Magnifier'; -import InputReset from '../../../assets/icons/inputReset'; +import Magnifier from '../../../assets/icons/magnifier'; +import InputReset from '../../../assets/icons/input-reset'; import { searchPageUrl } from '../../../utils/algolia-locale-setup'; type Props = { diff --git a/client/src/components/seo/index.tsx b/client/src/components/seo/index.tsx index 9abb02284cf..25b40ca884a 100644 --- a/client/src/components/seo/index.tsx +++ b/client/src/components/seo/index.tsx @@ -17,10 +17,23 @@ interface SiteData { }; } +interface Item { + '@type': 'Course'; + url: string; + name: string; + description?: string; + provider: { + '@type': 'Organization'; + name: string; + sameAs: string; + nonprofitStatus: string; + }; +} + interface ListItem { '@type': 'ListItem'; position: number; - item: object; + item: Item; } interface StructuredData { diff --git a/client/src/components/settings/__snapshots__/Honesty.test.tsx.snap b/client/src/components/settings/__snapshots__/Honesty.test.tsx.snap index 9e6d5accbde..86dcb3e0089 100644 --- a/client/src/components/settings/__snapshots__/Honesty.test.tsx.snap +++ b/client/src/components/settings/__snapshots__/Honesty.test.tsx.snap @@ -2,7 +2,6 @@ exports[` snapshot when isHonest is false: Honesty 1`] = `
@@ -14,16 +13,16 @@ exports[` snapshot when isHonest is false: Honesty 1`] = > -
@@ -31,7 +30,6 @@ exports[` snapshot when isHonest is false: Honesty 1`] = exports[` snapshot when isHonest is true: HonestyAccepted 1`] = `
@@ -43,18 +41,16 @@ exports[` snapshot when isHonest is true: HonestyAccepted > -
diff --git a/client/src/components/settings/about.tsx b/client/src/components/settings/about.tsx index 4b3f1aa8d16..ab8f1b96dfa 100644 --- a/client/src/components/settings/about.tsx +++ b/client/src/components/settings/about.tsx @@ -48,6 +48,18 @@ type AboutState = { isPictureUrlValid: boolean; }; +const ShowImageValidationWarning = ({ + alertContent +}: { + alertContent: string; +}) => { + return ( + + {alertContent} + + ); +}; + class AboutSettings extends Component { validationImage: HTMLImageElement; static displayName: string; @@ -79,7 +91,6 @@ class AboutSettings extends Component { picture === formValues.picture && about === formValues.about ) { - // eslint-disable-next-line react/no-did-update-set-state return this.setState({ originalValues: { name, @@ -170,21 +181,6 @@ class AboutSettings extends Component { })); }; - showImageValidationWarning = () => { - const { t } = this.props; - if (this.state.isPictureUrlValid === false) { - return ( - - - {t('validation.url-not-image')} - - - ); - } else { - return true; - } - }; - handleAboutChange = (e: React.FormEvent) => { const value = (e.target as HTMLInputElement).value.slice(0); return this.setState(state => ({ @@ -210,9 +206,9 @@ class AboutSettings extends Component { toggleKeyboardShortcuts } = this.props; return ( -
+ <> -
+ {t('settings.headings.personal-info')}
@@ -246,7 +242,11 @@ class AboutSettings extends Component { type='url' value={picture} /> - {this.showImageValidationWarning()} + {!this.state.isPictureUrlValid && ( + + )} @@ -279,7 +279,7 @@ class AboutSettings extends Component { toggleKeyboardShortcuts={toggleKeyboardShortcuts} /> -
+ ); } } diff --git a/client/src/components/settings/certification.test.tsx b/client/src/components/settings/certification.test.tsx index 195b64dd920..2c6e033ace1 100644 --- a/client/src/components/settings/certification.test.tsx +++ b/client/src/components/settings/certification.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; -import { createStore } from '../../redux/createStore'; +import { createStore } from '../../redux/create-store'; import { CertificationSettings } from './certification'; diff --git a/client/src/components/settings/honesty.css b/client/src/components/settings/honesty.css index 4eab7d1efad..979df5c6c86 100644 --- a/client/src/components/settings/honesty.css +++ b/client/src/components/settings/honesty.css @@ -1,3 +1,14 @@ +#honesty-policy + :is( + button[aria-disabled='true'], + button[aria-disabled='true']:is(:focus, :hover) + ) { + background-color: var(--quaternary-background); + color: var(--secondary-color); + opacity: 0.65; + cursor: not-allowed; +} + .honesty-panel p { margin-inline: 10px; font-family: 'Lato', sans-serif; @@ -7,11 +18,6 @@ padding-top: 15px; } -.honesty-policy .disabled-agreed p { - margin-top: 0; - margin-bottom: 0; -} - .honesty-panel .btn-invert { color: var(--primary-background); } diff --git a/client/src/components/settings/honesty.tsx b/client/src/components/settings/honesty.tsx index c8db8c62c9d..b8c54e31d96 100644 --- a/client/src/components/settings/honesty.tsx +++ b/client/src/components/settings/honesty.tsx @@ -15,33 +15,25 @@ type HonestyProps = { const Honesty = ({ isHonest, updateIsHonest }: HonestyProps): JSX.Element => { const { t } = useTranslation(); - const button = isHonest ? ( - - ) : ( - - ); + const buttonText = isHonest + ? t('buttons.accepted-honesty') + : t('buttons.agree-honesty'); + return ( -
+
{t('settings.headings.honesty')} -
- {button} +
); diff --git a/client/src/components/solution-display-widget/index.tsx b/client/src/components/solution-display-widget/index.tsx index 50694807034..b720163ae25 100644 --- a/client/src/components/solution-display-widget/index.tsx +++ b/client/src/components/solution-display-widget/index.tsx @@ -22,7 +22,7 @@ export function SolutionDisplayWidget({ showUserCode, showProjectPreview, displayContext -}: Props) { +}: Props): JSX.Element | null { const { id, solution, githubLink } = completedChallenge; const { t } = useTranslation(); const viewText = t('buttons.view'); diff --git a/client/src/pages/learn/the-odin-project/index.md b/client/src/pages/learn/the-odin-project/index.md new file mode 100644 index 00000000000..e600d4a7334 --- /dev/null +++ b/client/src/pages/learn/the-odin-project/index.md @@ -0,0 +1,9 @@ +--- +title: The Odin Project +superBlock: the-odin-project +certification: the-odin-project +--- + +## The Odin project + +The Odin Project is one of those "What I wish I had when I was learning" resources. Not everyone has access to a computer science education or the funds to attend an intensive coding school and neither of those is right for everyone anyway. This project is designed to fill in the gap for people who are trying to hack it on their own but still want a high quality education. diff --git a/client/src/pages/learn/the-odin-project/top-build-a-recipe-page-project/index.md b/client/src/pages/learn/the-odin-project/top-build-a-recipe-page-project/index.md new file mode 100644 index 00000000000..39bc4dc70b8 --- /dev/null +++ b/client/src/pages/learn/the-odin-project/top-build-a-recipe-page-project/index.md @@ -0,0 +1,9 @@ +--- +title: The Odin Project +superBlock: the-odin-project +certification: the-odin-project +--- + +## The Odin project + +Description is to be determined diff --git a/client/src/pages/learn/the-odin-project/top-learn-html-foundations/index.md b/client/src/pages/learn/the-odin-project/top-learn-html-foundations/index.md new file mode 100644 index 00000000000..39bc4dc70b8 --- /dev/null +++ b/client/src/pages/learn/the-odin-project/top-learn-html-foundations/index.md @@ -0,0 +1,9 @@ +--- +title: The Odin Project +superBlock: the-odin-project +certification: the-odin-project +--- + +## The Odin project + +Description is to be determined diff --git a/client/src/redux/action-types.js b/client/src/redux/action-types.js index 7aa77c37257..c4c3e7b12c5 100644 --- a/client/src/redux/action-types.js +++ b/client/src/redux/action-types.js @@ -27,6 +27,7 @@ export const actionTypes = createTypes( 'updateDonationFormState', 'updateUserToken', 'postChargeProcessing', + 'updateAllChallengesInfo', ...createAsyncTypes('fetchUser'), ...createAsyncTypes('postCharge'), ...createAsyncTypes('fetchProfileForUser'), diff --git a/client/src/redux/actions.js b/client/src/redux/actions.js index 43c1d3923b9..38608a046bc 100644 --- a/client/src/redux/actions.js +++ b/client/src/redux/actions.js @@ -53,6 +53,10 @@ export const fetchUser = createAction(actionTypes.fetchUser); export const fetchUserComplete = createAction(actionTypes.fetchUserComplete); export const fetchUserError = createAction(actionTypes.fetchUserError); +export const updateAllChallengesInfo = createAction( + actionTypes.updateAllChallengesInfo +); + export const postCharge = createAction(actionTypes.postCharge); export const postChargeProcessing = createAction( actionTypes.postChargeProcessing diff --git a/client/src/redux/cookieValues.js b/client/src/redux/cookie-values.js similarity index 100% rename from client/src/redux/cookieValues.js rename to client/src/redux/cookie-values.js diff --git a/client/src/redux/createStore.js b/client/src/redux/create-store.js similarity index 86% rename from client/src/redux/createStore.js rename to client/src/redux/create-store.js index 75e19ea5017..93918f35dea 100644 --- a/client/src/redux/createStore.js +++ b/client/src/redux/create-store.js @@ -5,9 +5,9 @@ import createSagaMiddleware from 'redux-saga'; import envData from '../../../config/env.json'; import { isBrowser } from '../../utils'; -import rootEpic from './rootEpic'; -import rootReducer from './rootReducer'; -import rootSaga from './rootSaga'; +import rootEpic from './root-epic'; +import rootReducer from './root-reducer'; +import rootSaga from './root-saga'; const { environment } = envData; @@ -47,8 +47,8 @@ export const createStore = () => { epicMiddleware.run(rootEpic); if (module.hot) { // Enable Webpack hot module replacement for reducers - module.hot.accept('./rootReducer', () => { - const nextRootReducer = require('./rootReducer'); + module.hot.accept('./root-reducer', () => { + const nextRootReducer = require('./root-reducer'); store.replaceReducer(nextRootReducer); }); } diff --git a/client/src/redux/fetch-user-saga.js b/client/src/redux/fetch-user-saga.js index 20b4d65ddae..826cccc0697 100644 --- a/client/src/redux/fetch-user-saga.js +++ b/client/src/redux/fetch-user-saga.js @@ -7,7 +7,7 @@ import { fetchUserComplete, fetchUserError } from './actions'; -import { jwt } from './cookieValues'; +import { jwt } from './cookie-values'; function* fetchSessionUser() { if (!jwt) { diff --git a/client/src/redux/index.js b/client/src/redux/index.js index 9dbaf84a81e..a6328ee5980 100644 --- a/client/src/redux/index.js +++ b/client/src/redux/index.js @@ -56,6 +56,10 @@ const initialState = { userFetchState: { ...defaultFetchState }, + allChallengesInfo: { + challengeEdges: [], + certificateNodes: [] + }, userProfileFetchState: { ...defaultFetchState }, @@ -153,7 +157,10 @@ export const reducer = handleActions( ...state, donationFormState: { ...defaultDonationFormState, error: payload } }), - + [actionTypes.updateAllChallengesInfo]: (state, { payload }) => ({ + ...state, + allChallengesInfo: { ...payload } + }), [actionTypes.fetchUser]: state => ({ ...state, userFetchState: { ...defaultFetchState } diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index e7444fcb018..062b201a3eb 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -142,6 +142,19 @@ export type ChallengeNode = { }; }; +export type CertificateNode = { + challenge: { + // TODO: use enum + certification: string; + tests: { id: string }[]; + }; +}; + +export type AllChallengesInfo = { + challengeEdges: { node: ChallengeNode }[]; + certificateNodes: CertificateNode[]; +}; + export type AllChallengeNode = { edges: [ { diff --git a/client/src/redux/rootEpic.js b/client/src/redux/root-epic.js similarity index 84% rename from client/src/redux/rootEpic.js rename to client/src/redux/root-epic.js index dd956348596..81a0a6d7712 100644 --- a/client/src/redux/rootEpic.js +++ b/client/src/redux/root-epic.js @@ -1,7 +1,7 @@ import { combineEpics } from 'redux-observable'; import { epics as challengeEpics } from '../templates/Challenges/redux'; -import { epics as appEpics } from './'; +import { epics as appEpics } from '.'; const rootEpic = combineEpics(...appEpics, ...challengeEpics); diff --git a/client/src/redux/rootReducer.js b/client/src/redux/root-reducer.js similarity index 95% rename from client/src/redux/rootReducer.js rename to client/src/redux/root-reducer.js index 6c20a0672dd..056083926f1 100644 --- a/client/src/redux/rootReducer.js +++ b/client/src/redux/root-reducer.js @@ -16,7 +16,7 @@ import { import { ns as appNameSpace } from './action-types'; import { ns as settingsNameSpace, reducer as settings } from './settings'; import { FlashApp as flashNameSpace } from './types'; -import { reducer as app } from './'; +import { reducer as app } from '.'; export default combineReducers({ [appNameSpace]: app, diff --git a/client/src/redux/rootSaga.js b/client/src/redux/root-saga.js similarity index 89% rename from client/src/redux/rootSaga.js rename to client/src/redux/root-saga.js index ec83d4a5b6e..f2d293bebd3 100644 --- a/client/src/redux/rootSaga.js +++ b/client/src/redux/root-saga.js @@ -3,7 +3,7 @@ import { all } from 'redux-saga/effects'; import { sagas as challengeSagas } from '../templates/Challenges/redux'; import errorSagas from './error-saga'; import { sagas as settingsSagas } from './settings'; -import { sagas as appSagas } from './'; +import { sagas as appSagas } from '.'; export default function* rootSaga() { yield all([...errorSagas, ...appSagas, ...challengeSagas, ...settingsSagas]); diff --git a/client/src/redux/selectors.js b/client/src/redux/selectors.js index 9faca5b8eab..eb6c71697c5 100644 --- a/client/src/redux/selectors.js +++ b/client/src/redux/selectors.js @@ -202,6 +202,8 @@ export const certificatesByNameSelector = username => state => { }; export const userFetchStateSelector = state => state[MainApp].userFetchState; +export const allChallengesInfoSelector = state => + state[MainApp].allChallengesInfo; export const userProfileFetchStateSelector = state => state[MainApp].userProfileFetchState; export const usernameSelector = state => state[MainApp].appUsername; diff --git a/client/src/resources/honesty-policy.tsx b/client/src/resources/honesty-policy.tsx index dea6b82c460..2a3a3f489ed 100644 --- a/client/src/resources/honesty-policy.tsx +++ b/client/src/resources/honesty-policy.tsx @@ -3,7 +3,7 @@ import { Trans, useTranslation } from 'react-i18next'; const HonestyPolicy = (): JSX.Element => { const { t } = useTranslation(); - const email = 'team@freecodecamp.org'; + const email = 'support@freecodecamp.org'; return ( <> diff --git a/client/src/templates/Challenges/classic/mobile-layout.tsx b/client/src/templates/Challenges/classic/mobile-layout.tsx index efa5a2c2c8f..04965a795e1 100644 --- a/client/src/templates/Challenges/classic/mobile-layout.tsx +++ b/client/src/templates/Challenges/classic/mobile-layout.tsx @@ -39,17 +39,17 @@ class MobileLayout extends Component { currentTab: this.props.hasEditableBoundaries ? Tab.Editor : Tab.Instructions }; - switchTab = (tab: Tab) => { + switchTab = (tab: Tab): void => { this.setState({ currentTab: tab }); }; - handleKeyDown = () => this.props.updateUsingKeyboardInTablist(true); + handleKeyDown = (): void => this.props.updateUsingKeyboardInTablist(true); - handleClick = () => this.props.updateUsingKeyboardInTablist(false); + handleClick = (): void => this.props.updateUsingKeyboardInTablist(false); - render() { + render(): JSX.Element { const { currentTab } = this.state; const { hasEditableBoundaries, @@ -87,7 +87,7 @@ class MobileLayout extends Component { {!hasEditableBoundaries && ( {instructions} @@ -103,7 +103,7 @@ class MobileLayout extends Component { {testOutput} diff --git a/client/src/templates/Challenges/components/completion-modal.tsx b/client/src/templates/Challenges/components/completion-modal.tsx index 95e64be8f98..de8c7076b8c 100644 --- a/client/src/templates/Challenges/components/completion-modal.tsx +++ b/client/src/templates/Challenges/components/completion-modal.tsx @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ import { Button, Modal } from '@freecodecamp/react-bootstrap'; -import { useStaticQuery, graphql } from 'gatsby'; import { noop } from 'lodash-es'; import React, { Component } from 'react'; import { TFunction, withTranslation } from 'react-i18next'; @@ -13,8 +12,11 @@ import { dasherize } from '../../../../../utils/slugs'; import { isFinalProject } from '../../../../utils/challenge-types'; import Login from '../../../components/Header/components/Login'; import { executeGA, allowBlockDonationRequests } from '../../../redux/actions'; -import { isSignedInSelector } from '../../../redux/selectors'; -import { AllChallengeNode, ChallengeFiles } from '../../../redux/prop-types'; +import { + isSignedInSelector, + allChallengesInfoSelector +} from '../../../redux/selectors'; +import { AllChallengesInfo, ChallengeFiles } from '../../../redux/prop-types'; import { closeModal, submitChallenge } from '../redux/actions'; import { @@ -34,6 +36,7 @@ const mapStateToProps = createSelector( completedChallengesIds, isCompletionModalOpenSelector, isSignedInSelector, + allChallengesInfoSelector, successMessageSelector, ( challengeFiles: ChallengeFiles, @@ -45,6 +48,7 @@ const mapStateToProps = createSelector( completedChallengesIds: string[], isOpen: boolean, isSignedIn: boolean, + allChallengesInfo: AllChallengesInfo, message: string ) => ({ challengeFiles, @@ -54,6 +58,7 @@ const mapStateToProps = createSelector( completedChallengesIds, isOpen, isSignedIn, + allChallengesInfo, message }) ); @@ -118,6 +123,7 @@ interface CompletionModalsProps { id: string; isOpen: boolean; isSignedIn: boolean; + allChallengesInfo: AllChallengesInfo; message: string; submitChallenge: () => void; superBlock: string; @@ -324,58 +330,13 @@ interface Options { isFinalProjectBlock: boolean; } -interface CertificateNode { - challenge: { - // TODO: use enum - certification: string; - tests: { id: string }[]; - }; -} - const useCurrentBlockIds = ( + allChallengesInfo: AllChallengesInfo, block: string, certification: string, options?: Options ) => { - const { - allChallengeNode: { edges: challengeEdges }, - allCertificateNode: { nodes: certificateNodes } - }: { - allChallengeNode: AllChallengeNode; - allCertificateNode: { nodes: CertificateNode[] }; - } = useStaticQuery(graphql` - query getCurrentBlockNodes { - allChallengeNode( - sort: { - fields: [ - challenge___superOrder - challenge___order - challenge___challengeOrder - ] - } - ) { - edges { - node { - challenge { - block - id - } - } - } - } - allCertificateNode { - nodes { - challenge { - certification - tests { - id - } - } - } - } - } - `); - + const { challengeEdges, certificateNodes } = allChallengesInfo; const currentCertificateIds = certificateNodes .filter( node => dasherize(node.challenge.certification) === certification @@ -390,6 +351,7 @@ const useCurrentBlockIds = ( const CompletionModal = (props: CompletionModalsProps) => { const currentBlockIds = useCurrentBlockIds( + props.allChallengesInfo, props.block || '', props.certification || '', // eslint-disable-next-line @typescript-eslint/no-unsafe-call diff --git a/client/src/templates/Challenges/projects/tool-panel.tsx b/client/src/templates/Challenges/projects/tool-panel.tsx index 46a1e3cb820..d7cca68c119 100644 --- a/client/src/templates/Challenges/projects/tool-panel.tsx +++ b/client/src/templates/Challenges/projects/tool-panel.tsx @@ -6,8 +6,6 @@ import { bindActionCreators, Dispatch } from 'redux'; import { openModal } from '../redux/actions'; -import './tool-panel.css'; - const mapStateToProps = () => ({}); const mapDispatchToProps = (dispatch: Dispatch) => @@ -30,7 +28,7 @@ function ToolPanel({ t }: ToolPanelProps): JSX.Element { return ( -
+ <> {guideUrl && ( -
+ ); } diff --git a/client/src/templates/Challenges/rechallenge/builders.ts b/client/src/templates/Challenges/rechallenge/builders.ts index 9b0fd91f86e..f174c79acb9 100644 --- a/client/src/templates/Challenges/rechallenge/builders.ts +++ b/client/src/templates/Challenges/rechallenge/builders.ts @@ -10,7 +10,7 @@ export function concatHtml({ required = [], template, contents -}: ConcatHTMLOptions) { +}: ConcatHTMLOptions): string { const embedSource = template ? _template(template) : ({ source }: { source: ConcatHTMLOptions['contents'] }) => source; diff --git a/client/src/templates/Challenges/utils/build.ts b/client/src/templates/Challenges/utils/build.ts index 81d04296ab2..6951043d988 100644 --- a/client/src/templates/Challenges/utils/build.ts +++ b/client/src/templates/Challenges/utils/build.ts @@ -107,11 +107,13 @@ const buildFunctions = { [challengeTypes.multifileCertProject]: buildDOMChallenge }; -export function canBuildChallenge(challengeData: BuildChallengeData) { +export function canBuildChallenge(challengeData: BuildChallengeData): boolean { const { challengeType } = challengeData; return Object.prototype.hasOwnProperty.call(buildFunctions, challengeType); } +// TODO: Figure out and (hopefully) simplify the return type. +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export async function buildChallenge( challengeData: BuildChallengeData, options: BuildOptions @@ -131,6 +133,8 @@ const testRunners = { [challengeTypes.pythonProject]: getDOMTestRunner, [challengeTypes.multifileCertProject]: getDOMTestRunner }; +// TODO: Figure out and (hopefully) simplify the return type. +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function getTestRunner( buildData: BuildChallengeData, runnerConfig: TestRunnerConfig, @@ -185,10 +189,16 @@ async function getDOMTestRunner( runTestInTestFrame(document, testString, testTimeout); } +type BuildResult = { + challengeType: number; + build: string; + sources: Source | undefined; +}; + export function buildDOMChallenge( { challengeFiles, required = [], template = '' }: BuildChallengeData, { usesTestRunner } = { usesTestRunner: false } -) { +): Promise | undefined { const finalRequires = [...required]; if (usesTestRunner) finalRequires.push(...frameRunner); @@ -225,7 +235,7 @@ export function buildDOMChallenge( export function buildJSChallenge( { challengeFiles }: { challengeFiles: ChallengeFiles }, options: BuildOptions -) { +): Promise | undefined { const pipeLine = composeFunctions(...getTransformers(options)); const finalFiles = challengeFiles?.map(pipeLine); @@ -262,7 +272,7 @@ export function updatePreview( buildData: BuildChallengeData, document: Document, proxyLogger: ProxyLogger -) { +): void { if ( buildData.challengeType === challengeTypes.html || buildData.challengeType === challengeTypes.multifileCertProject @@ -293,7 +303,7 @@ function getDocumentTitle(buildData: BuildChallengeData) { export function updateProjectPreview( buildData: BuildChallengeData, document: Document -) { +): void { if ( buildData.challengeType === challengeTypes.html || buildData.challengeType === challengeTypes.multifileCertProject @@ -309,7 +319,7 @@ export function updateProjectPreview( } } -export function challengeHasPreview({ challengeType }: ChallengeMeta) { +export function challengeHasPreview({ challengeType }: ChallengeMeta): boolean { return ( challengeType === challengeTypes.html || challengeType === challengeTypes.modern || @@ -317,13 +327,15 @@ export function challengeHasPreview({ challengeType }: ChallengeMeta) { ); } -export function isJavaScriptChallenge({ challengeType }: ChallengeMeta) { +export function isJavaScriptChallenge({ + challengeType +}: ChallengeMeta): boolean { return ( challengeType === challengeTypes.js || challengeType === challengeTypes.jsProject ); } -export function isLoopProtected(challengeMeta: ChallengeMeta) { +export function isLoopProtected(challengeMeta: ChallengeMeta): boolean { return challengeMeta.superBlock !== 'coding-interview-prep'; } diff --git a/client/src/templates/Challenges/utils/frame.ts b/client/src/templates/Challenges/utils/frame.ts index 906df13cc46..4e1a29b8074 100644 --- a/client/src/templates/Challenges/utils/frame.ts +++ b/client/src/templates/Challenges/utils/frame.ts @@ -132,11 +132,15 @@ const createHeader = (id = mainPreviewId) => ` `; +type TestResult = + | { pass: boolean } + | { err: { message: string; stack?: string } }; + export const runTestInTestFrame = async function ( document: Document, test: string, timeout: number -) { +): Promise { const { contentDocument: frame } = document.getElementById( testId ) as HTMLIFrameElement; @@ -309,7 +313,7 @@ export const createMainPreviewFramer = ( document: Document, proxyLogger: ProxyLogger, frameTitle: string -) => +): ((args: Context) => void) => createFramer( document, mainPreviewId, @@ -322,7 +326,7 @@ export const createMainPreviewFramer = ( export const createProjectPreviewFramer = ( document: Document, frameTitle: string -) => +): ((args: Context) => void) => createFramer( document, projectPreviewId, @@ -336,7 +340,8 @@ export const createTestFramer = ( document: Document, proxyLogger: ProxyLogger, frameReady: () => void -) => createFramer(document, testId, initTestFrame, proxyLogger, frameReady); +): ((args: Context) => void) => + createFramer(document, testId, initTestFrame, proxyLogger, frameReady); const createFramer = ( document: Document, diff --git a/client/src/templates/Challenges/utils/index.ts b/client/src/templates/Challenges/utils/index.ts index 81f0fb5146b..f4e3a5aa511 100644 --- a/client/src/templates/Challenges/utils/index.ts +++ b/client/src/templates/Challenges/utils/index.ts @@ -35,7 +35,7 @@ export function transformEditorLink(url: string): string { export function enhancePrismAccessibility( prismEnv: Prism.hooks.ElementHighlightedEnvironment -) { +): void { const langs: { [key: string]: string } = { js: 'JavaScript', javascript: 'JavaScript', diff --git a/client/src/templates/Introduction/super-block-intro.tsx b/client/src/templates/Introduction/super-block-intro.tsx index e48882d03ad..e9ecc075298 100644 --- a/client/src/templates/Introduction/super-block-intro.tsx +++ b/client/src/templates/Introduction/super-block-intro.tsx @@ -210,16 +210,17 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => { /> ))} - {superBlock !== SuperBlocks.CodingInterviewPrep && ( -
- -
- )} + {superBlock !== SuperBlocks.CodingInterviewPrep && + superBlock !== SuperBlocks.TheOdinProject && ( +
+ +
+ )}
{!isSignedIn && !signInLoading && (
diff --git a/client/src/utils/ajax.ts b/client/src/utils/ajax.ts index fbf3b19459a..b284b6924d8 100644 --- a/client/src/utils/ajax.ts +++ b/client/src/utils/ajax.ts @@ -136,6 +136,8 @@ function parseApiResponseToClientUser(data: ApiUser): UserResponse { }; } +// TODO: this at least needs a few aliases so it's human readable +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function mapFilesToChallengeFiles( fileContainer: ({ files: (File & { key: string })[] } & Rest)[] = [] ) { diff --git a/client/src/utils/challenge-request-helpers.ts b/client/src/utils/challenge-request-helpers.ts index 3451f372437..d4017894beb 100644 --- a/client/src/utils/challenge-request-helpers.ts +++ b/client/src/utils/challenge-request-helpers.ts @@ -13,11 +13,25 @@ interface StandardizeRequestBodyArgs { challengeType: number; } +interface File { + contents: string; + ext: string; + history: string[]; + key: string; + name: string; +} + +interface Body { + id: string; + files?: File[]; + challengeType: number; +} + export function standardizeRequestBody({ id, challengeFiles = [], challengeType -}: StandardizeRequestBodyArgs) { +}: StandardizeRequestBodyArgs): Body { return { id, files: challengeFiles?.map(({ fileKey, contents, ext, name, history }) => { @@ -33,12 +47,12 @@ export function standardizeRequestBody({ }; } -export function getStringSizeInBytes(str = '') { +export function getStringSizeInBytes(str = ''): number { const stringSizeInBytes = new Blob([JSON.stringify(str)]).size; return stringSizeInBytes; } -export function bodySizeFits(bodySizeInBytes: number) { +export function bodySizeFits(bodySizeInBytes: number): boolean { return bodySizeInBytes <= MAX_BODY_SIZE; } diff --git a/client/src/utils/report-error.ts b/client/src/utils/report-error.ts index d58bc024ba5..4d4bc6cdc43 100644 --- a/client/src/utils/report-error.ts +++ b/client/src/utils/report-error.ts @@ -1,4 +1,4 @@ -import * as Sentry from '@sentry/gatsby'; +import { captureException } from '@sentry/gatsby'; import envData from '../../../config/env.json'; const { sentryClientDSN } = envData; @@ -11,5 +11,5 @@ export function reportClientSideError( ): string | void { return sentryClientDSN === null ? console.error(`Client: ${message}`, e) - : Sentry.captureException(e); + : captureException(e); } diff --git a/client/src/utils/solution-display-type.ts b/client/src/utils/solution-display-type.ts index 8c1be9198b3..101553f5286 100644 --- a/client/src/utils/solution-display-type.ts +++ b/client/src/utils/solution-display-type.ts @@ -2,12 +2,20 @@ import type { CompletedChallenge } from '../redux/prop-types'; import { challengeTypes } from '../../utils/challenge-types'; import { maybeUrlRE } from '.'; +// eslint-disable-next-line @typescript-eslint/naming-convention +type DisplayType = + | 'none' + | 'showMultifileProjectSolution' + | 'showUserCode' + | 'showProjectAndGithubLinks' + | 'showProjectLink'; + export const getSolutionDisplayType = ({ solution, githubLink, challengeFiles, challengeType -}: CompletedChallenge) => { +}: CompletedChallenge): DisplayType => { if (challengeFiles?.length) return challengeType === challengeTypes.multifileCertProject ? 'showMultifileProjectSolution' diff --git a/client/src/utils/superblock-map-titles.ts b/client/src/utils/superblock-map-titles.ts index fa2ec32c994..dae9af0dcba 100644 --- a/client/src/utils/superblock-map-titles.ts +++ b/client/src/utils/superblock-map-titles.ts @@ -9,9 +9,12 @@ enum SuperBlockI18nKeys { // the key above is used to create the last word for superBlock titles used on // the map and window. e.g. 'Certification' in Responsive Web Design // Certification -const superBlocksWithoutLastWord = [SuperBlocks.CodingInterviewPrep]; +const superBlocksWithoutLastWord = [ + SuperBlocks.CodingInterviewPrep, + SuperBlocks.TheOdinProject +]; -export function getSuperBlockTitleForMap(superBlock: SuperBlocks) { +export function getSuperBlockTitleForMap(superBlock: SuperBlocks): string { const i18nSuperBlock = i18next.t(`intro:${superBlock}.title`); return superBlocksWithoutLastWord.includes(superBlock) diff --git a/client/utils/gatsby/layout-selector.test.tsx b/client/utils/gatsby/layout-selector.test.tsx index ac769f44d1a..f56a25a68fb 100644 --- a/client/utils/gatsby/layout-selector.test.tsx +++ b/client/utils/gatsby/layout-selector.test.tsx @@ -6,7 +6,7 @@ import ShallowRenderer from 'react-test-renderer/shallow'; import FourOhFourPage from '../../src/pages/404'; import Certification from '../../src/pages/certification'; import Learn from '../../src/pages/learn'; -import { createStore } from '../../src/redux/createStore'; +import { createStore } from '../../src/redux/create-store'; import layoutSelector from './layout-selector'; jest.mock('../../src/analytics'); diff --git a/client/utils/help-category-map.json b/client/utils/help-category-map.json index f4f141c7b83..40ea39ab04e 100644 --- a/client/utils/help-category-map.json +++ b/client/utils/help-category-map.json @@ -42,8 +42,8 @@ "algorithms": "JavaScript", "data-structures": "JavaScript", "take-home-projects": "JavaScript", - "the-odin-project": "HTML-CSS", - "the-odin-project-projects": "HTML-CSS", + "top-learn-html-foundations": "HTML-CSS", + "top-build-a-recipe-project": "HTML-CSS", "rosetta-code": "JavaScript", "project-euler": "JavaScript", "scientific-computing-with-python": "Python", diff --git a/config/certification-settings.ts b/config/certification-settings.ts index aea15811b79..dd7f943c707 100644 --- a/config/certification-settings.ts +++ b/config/certification-settings.ts @@ -31,7 +31,8 @@ export enum SuperBlocks { DataAnalysisPy = 'data-analysis-with-python', InfoSec = 'information-security', MachineLearningPy = 'machine-learning-with-python', - CodingInterviewPrep = 'coding-interview-prep' + CodingInterviewPrep = 'coding-interview-prep', + TheOdinProject = 'the-odin-project' } export const certIds = { diff --git a/config/donation-settings.ts b/config/donation-settings.ts index 4196a53f475..0ade9659fc6 100644 --- a/config/donation-settings.ts +++ b/config/donation-settings.ts @@ -80,8 +80,22 @@ export const paypalConfigTypes = { } }; +type DonationAmount = 500 | 1000 | 2000 | 3000 | 4000 | 5000; + +interface OneTimeConfig { + amount: DonationAmount; + duration: 'one-time'; + planId: null; +} + +interface SubscriptionConfig { + amount: DonationAmount; + duration: 'month'; + planId: string; +} + export const paypalConfigurator = ( - donationAmount: 500 | 1000 | 2000 | 3000 | 4000 | 5000, + donationAmount: DonationAmount, donationDuration: 'one-time' | 'month', paypalConfig: { month: { @@ -93,7 +107,7 @@ export const paypalConfigurator = ( 5000: { planId: string }; }; } -) => { +): OneTimeConfig | SubscriptionConfig => { if (donationDuration === 'one-time') { return { amount: donationAmount, duration: donationDuration, planId: null }; } diff --git a/config/i18n.ts b/config/i18n.ts index 9fe7a6311d2..786c9a54261 100644 --- a/config/i18n.ts +++ b/config/i18n.ts @@ -68,7 +68,7 @@ export const i18nextCodes = { }; // These are for the language selector dropdown menu in the footer -export const LangNames = { +export const LangNames: { [key: string]: string } = { [Languages.English]: 'English', [Languages.Espanol]: 'Español', [Languages.Chinese]: '中文(简体字)', @@ -111,7 +111,7 @@ export const rtlLangs = ['arabic']; // locale is sourced from a JSON file, so we use getLangCode to // find the associated enum values -export function getLangCode(locale: PropertyKey) { +export function getLangCode(locale: PropertyKey): string { if (isPropertyOf(LangCodes, locale)) return LangCodes[locale]; throw new Error(`${String(locale)} is not a valid locale`); } diff --git a/config/superblock-order.test.ts b/config/superblock-order.test.ts index 233f7c4d60c..cf21825a48b 100644 --- a/config/superblock-order.test.ts +++ b/config/superblock-order.test.ts @@ -150,6 +150,7 @@ describe("'superBlockOrder' helper functions", () => { SuperBlocks.MachineLearningPy, SuperBlocks.CodingInterviewPrep, SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject, SuperBlocks.RespWebDesign ]; expect(learnSuperBlocks).toStrictEqual(test); @@ -188,7 +189,8 @@ describe("'superBlockOrder' helper functions", () => { SuperBlocks.InfoSec, SuperBlocks.MachineLearningPy, SuperBlocks.CodingInterviewPrep, - SuperBlocks.JsAlgoDataStructNew + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject ]; expect(notAuditedSuperBlocks).toStrictEqual(test); expect(notAuditedSuperBlocks.length).toEqual(test.length); diff --git a/config/superblock-order.ts b/config/superblock-order.ts index 8be124ca9e5..8857c06a228 100644 --- a/config/superblock-order.ts +++ b/config/superblock-order.ts @@ -78,7 +78,8 @@ export const defaultSuperBlockOrder: SuperBlocks[] = [ SuperBlocks.DataAnalysisPy, SuperBlocks.InfoSec, SuperBlocks.MachineLearningPy, - SuperBlocks.CodingInterviewPrep + SuperBlocks.CodingInterviewPrep, + SuperBlocks.TheOdinProject ]; /* @@ -124,7 +125,10 @@ export const superBlockOrder: SuperBlockOrder = { SuperBlocks.CodingInterviewPrep ], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] }, [TranslationStates.NotAudited]: { @@ -173,7 +177,10 @@ export const superBlockOrder: SuperBlockOrder = { SuperBlocks.CodingInterviewPrep ], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [] } } @@ -216,7 +223,10 @@ export const superBlockOrder: SuperBlockOrder = { SuperBlocks.CodingInterviewPrep ], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [] } } @@ -259,7 +269,10 @@ export const superBlockOrder: SuperBlockOrder = { SuperBlocks.CodingInterviewPrep ], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [] } } @@ -301,7 +314,10 @@ export const superBlockOrder: SuperBlockOrder = { [TranslationStates.NotAudited]: { [SuperBlockStates.Current]: [], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [] } } @@ -343,7 +359,10 @@ export const superBlockOrder: SuperBlockOrder = { [TranslationStates.NotAudited]: { [SuperBlockStates.Current]: [], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [] } } @@ -384,7 +403,10 @@ export const superBlockOrder: SuperBlockOrder = { [TranslationStates.NotAudited]: { [SuperBlockStates.Current]: [SuperBlocks.CodingInterviewPrep], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [] } } @@ -427,7 +449,10 @@ export const superBlockOrder: SuperBlockOrder = { [TranslationStates.NotAudited]: { [SuperBlockStates.Current]: [], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [] } } @@ -471,7 +496,10 @@ export const superBlockOrder: SuperBlockOrder = { SuperBlocks.CodingInterviewPrep ], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [] } } @@ -514,7 +542,10 @@ export const superBlockOrder: SuperBlockOrder = { SuperBlocks.CodingInterviewPrep ], [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], + [SuperBlockStates.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] } } @@ -545,11 +576,17 @@ function shouldShowSuperblocks({ return true; } +type Config = { + language: string; + showNewCurriculum?: string; + showUpcomingChanges?: string; +}; + export function getLearnSuperBlocks({ language = 'english', showNewCurriculum = 'false', showUpcomingChanges = 'false' -}) { +}: Config): SuperBlocks[] { const learnSuperBlocks: SuperBlocks[] = []; Object.values(TranslationStates).forEach(translationState => { @@ -577,7 +614,7 @@ export function getAuditedSuperBlocks({ language = 'english', showNewCurriculum = 'false', showUpcomingChanges = 'false' -}) { +}: Config): SuperBlocks[] { const auditedSuperBlocks: SuperBlocks[] = []; Object.values(SuperBlockStates).forEach(superBlockState => { @@ -603,7 +640,7 @@ export function getNotAuditedSuperBlocks({ language = 'english', showNewCurriculum = 'false', showUpcomingChanges = 'false' -}) { +}: Config): SuperBlocks[] { const notAuditedSuperBlocks: SuperBlocks[] = []; Object.values(SuperBlockStates).forEach(superBlockState => { diff --git a/curriculum/challenges/_meta/the-odin-project-projects/meta.json b/curriculum/challenges/_meta/top-build-a-recipe-project/meta.json similarity index 60% rename from curriculum/challenges/_meta/the-odin-project-projects/meta.json rename to curriculum/challenges/_meta/top-build-a-recipe-project/meta.json index edd3de40ae3..df2b93d2096 100644 --- a/curriculum/challenges/_meta/the-odin-project-projects/meta.json +++ b/curriculum/challenges/_meta/top-build-a-recipe-project/meta.json @@ -1,12 +1,11 @@ { - "name": "The Odin Project Projects", + "name": "TOP build a recipe project", "isUpcomingChange": true, - "dashedName": "the-odin-project-projects", - "order": 6, + "order": 1, "time": "", "template": "", "required": [], - "superBlock": "coding-interview-prep", + "superBlock": "the-odin-project", "challengeOrder": [ [ "6391d1a4f7ac71efd0621380", diff --git a/curriculum/challenges/_meta/the-odin-project/meta.json b/curriculum/challenges/_meta/top-learn-html-foundations/meta.json similarity index 95% rename from curriculum/challenges/_meta/the-odin-project/meta.json rename to curriculum/challenges/_meta/top-learn-html-foundations/meta.json index 44d7f47d50b..b15490e569c 100644 --- a/curriculum/challenges/_meta/the-odin-project/meta.json +++ b/curriculum/challenges/_meta/top-learn-html-foundations/meta.json @@ -1,12 +1,11 @@ { - "name": "The Odin Project", + "name": "TOP Learn HTML Foundations", "isUpcomingChange": true, - "dashedName": "the-odin-project", - "order": 5, + "order": 0, "time": "", "template": "", "required": [], - "superBlock": "coding-interview-prep", + "superBlock": "the-odin-project", "challengeOrder": [ [ "6374f208de18c50e48ba767b", diff --git a/curriculum/challenges/arabic/00-certifications/data-visualization-certification/data-visualization-certification.yml b/curriculum/challenges/arabic/00-certifications/data-visualization-certification/data-visualization-certification.yml index 1a22cbbd33d..d77cefc4226 100644 --- a/curriculum/challenges/arabic/00-certifications/data-visualization-certification/data-visualization-certification.yml +++ b/curriculum/challenges/arabic/00-certifications/data-visualization-certification/data-visualization-certification.yml @@ -7,7 +7,7 @@ isPrivate: true tests: - id: bd7168d8c242eddfaeb5bd13 - title: التصوير المرئي للبيانات باستخدام مخطط بياني للأعمدة + title: التصوير المرئي للبيانات باستخدام مخطط الأعمدة - id: bd7178d8c242eddfaeb5bd13 title: التصوير المرئي للبيانات باستخدام مخطط التشتت diff --git a/curriculum/challenges/arabic/00-certifications/legacy-data-visualization-certification/legacy-data-visualization-certification.yml b/curriculum/challenges/arabic/00-certifications/legacy-data-visualization-certification/legacy-data-visualization-certification.yml index 04ac3369a4f..19885830ef3 100644 --- a/curriculum/challenges/arabic/00-certifications/legacy-data-visualization-certification/legacy-data-visualization-certification.yml +++ b/curriculum/challenges/arabic/00-certifications/legacy-data-visualization-certification/legacy-data-visualization-certification.yml @@ -22,7 +22,7 @@ tests: title: بناء لعبة Roguelike Dungeon Crawler - id: bd7168d8c242eddfaeb5bd13 - title: التصوير المرئي للبيانات باستخدام مخطط بياني للأعمدة + title: التصوير المرئي للبيانات باستخدام مخطط الأعمدة - id: bd7178d8c242eddfaeb5bd13 title: التصوير المرئي للبيانات باستخدام مخطط التشتت diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/add-a-text-alternative-to-images-for-visually-impaired-accessibility.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/add-a-text-alternative-to-images-for-visually-impaired-accessibility.md index 27f9c91bb75..a957e919b1a 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/add-a-text-alternative-to-images-for-visually-impaired-accessibility.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/add-a-text-alternative-to-images-for-visually-impaired-accessibility.md @@ -21,7 +21,7 @@ dashedName: add-a-text-alternative-to-images-for-visually-impaired-accessibility # --instructions-- -كامبر القط هو نينجا في البرمجة وهو أيضا نينجا حقيقي، يقوم ببناء صفحة ويب لمشاركة معرفتة. الصورة الشخصية التي يريد استخدامها تظهر مهاراته ويجب أن تحظي بتقدير جميع زوار الموقع. أضف خاصية (attribute) `alt` في الوسم (tag) `img` لتظهر أن كامبر القط يجيد لعبة الكاراتية. (خاصية الصورة `src` لا ترتبط بملف فعلي، لذا يجب أن ترى نص `alt` في العرض.) +Camper Cat هو نينجا في البرمجة وهو أيضا نينجا حقيقي، يقوم ببناء صفحة ويب لمشاركة معرفتة. الصورة الشخصية التي يريد استخدامها تظهر مهاراته ويجب أن تحظي بتقدير جميع زوار الموقع. أضف خاصية (attribute) `alt` في الوسم (tag) `img` لتظهر أن Camper Cat يجيد لعبة الكاراتية. (خاصية الصورة `src` لا ترتبط بملف فعلي، لذا يجب أن ترى نص `alt` في العرض.) # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/add-an-accessible-date-picker.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/add-an-accessible-date-picker.md index f54b40bdd31..e434da4f1de 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/add-an-accessible-date-picker.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/add-an-accessible-date-picker.md @@ -24,29 +24,29 @@ dashedName: add-an-accessible-date-picker # --instructions-- -يقوم Camper Cat بإعداد بطولة Mortal Kombat ويريد أن يطلب من منافسيه معرفة التاريخ الأفضل. أضف وسم `input` مع سمة `type` من نوع `date` ، وسمة `id` بقيمة `pickdate`، و سمة `name` بقيمة `date`. +يقوم Camper Cat بإعداد بطولة Mortal Kombat ويريد أن يطلب من منافسيه معرفة التاريخ الأفضل. أضف علامة `input` مع سمة `type` من نوع `date`، و مع سمة `id` بقيمة `pickdate`، و أيضا سمة `name` بقيمة `date`. # --hints-- -يجب أن يحتوي الكود علي وسم `input` واحد لحقل محدد التاريخ. +يجب أن يحتوي الكود علي علامة `input` واحد لخانة محدد التاريخ. ```js assert($('input').length == 2); ``` -يجب أن يحتوي وسم `input` على سمة `type` بقيمة `date`. +يجب أن يحتوي علامة `input` على سمة `type` بقيمة `date`. ```js assert($('input').attr('type') == 'date'); ``` -يجب أن يحتوي وسم `input` على سمة `id` بقيمة `pickdate`. +يجب أن يحتوي علامة `input` على سمة `id` بقيمة `pickdate`. ```js assert($('input').attr('id') == 'pickdate'); ``` -يجب أن يحتوي وسم `input` على سمة `name` بقيمة `date`. +يجب أن يحتوي علامة `input` على سمة `name` بقيمة `date`. ```js assert($('input').attr('name') == 'date'); diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/avoid-colorblindness-issues-by-carefully-choosing-colors-that-convey-information.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/avoid-colorblindness-issues-by-carefully-choosing-colors-that-convey-information.md index 2f21941dc92..ee510da38e3 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/avoid-colorblindness-issues-by-carefully-choosing-colors-that-convey-information.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/avoid-colorblindness-issues-by-carefully-choosing-colors-that-convey-information.md @@ -19,7 +19,7 @@ dashedName: >- # --instructions-- -تختبر Camper Cat أنماطًا مختلفة لزر مهم ، ولكن اللون الأصفر للخلفة (`#FFFF33`) `background-color` و الاخضر (`#33FF33`) `color`لون النص هي درجات مجاورة على عجلة الألوان ولا يمكن تمييزها فعليًا عند بعض المصابين بعمى الألوان. (خفتهم المتشابهة تفشل أيضًا في فحص نسبة التباين). غيّر لون النص `color` إلى اللون الأزرق المظلم (`#003366`) لحل كلتا المشكلتين. +يختبر Camper Cat أنماطًا مختلفة لزر مهم، ولكن اللون الأصفر (`#FFFF33`) للخلفية (`background-color`) و الاخضر (`#33FF33`) للون النص (`color`) هي درجات مجاورة على عجلة الألوان ولا يمكن لبعض المصابين بعمى الألوان التمييز ببنهم. (خفتهم المتشابهة تفشل أيضًا في فحص نسبة التباين). غيّر لون النص `color` إلى اللون الأزرق المظلم (`#003366`) لحل كلتا المشكلتين. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/avoid-colorblindness-issues-by-using-sufficient-contrast.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/avoid-colorblindness-issues-by-using-sufficient-contrast.md index 133f49e990f..afad525b088 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/avoid-colorblindness-issues-by-using-sufficient-contrast.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/avoid-colorblindness-issues-by-using-sufficient-contrast.md @@ -19,7 +19,7 @@ dashedName: avoid-colorblindness-issues-by-using-sufficient-contrast # --instructions-- -يقوم Camper Cat بتجربة استخدام الألوان لنص مدونته وخلفيتها ، ولكن دمجة الحالي لللون الأخضر للخلفية `background-color` مع لون النص الماروني `color` له نسبة تباين 2.5:1. يمكنك بسهولة ضبط إضاءة الألوان بما اته عرفها، باستخدام خاصية في الـ CSS و هي `hsl()` (التي تمثل hue بمعني التدرج و saturation بمعني التشبع و lightness بمعني الخفة) بتغيير الوسيطة (argument) الثالثة. زيادة قيمة الخفة lightness للون الخلفية `background-color` من 35% إلى 55%، وتقليل قيمة الخفة لـ `color` من 20% إلى 15%. وهذا ما يحسّن التباين إلى 5.9:1. +يقوم Camper Cat بتجربة استخدام الألوان لنص مدونته وخلفيتها، ولكن دمجة لألون الأخضر للخلفية `background-color` مع لون النص الماروني (maroon) الحالي `color` له نسبة تباين 2.5:1. يمكنك بسهولة ضبط إضاءة الألوان بما اته عرفها، باستخدام خاصية في الـ CSS و هي `hsl()` (التي تمثل hue بمعني التدرج و saturation بمعني التشبع و lightness بمعني الخفة) بتغيير الوسيطة (argument) الثالثة. زيادة قيمة الخفة lightness للون الخلفية `background-color` من 35% إلى 55%، وتقليل قيمة الخفة لـ `color` من 20% إلى 15%. وهذا ما يحسّن التباين إلى 5.9:1. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/give-links-meaning-by-using-descriptive-link-text.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/give-links-meaning-by-using-descriptive-link-text.md index f282a55ad02..6da59306862 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/give-links-meaning-by-using-descriptive-link-text.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/give-links-meaning-by-using-descriptive-link-text.md @@ -15,7 +15,7 @@ Screen reader users have various options for what type of content their device r # --instructions-- -The link text that Camper Cat is using is not very descriptive without the surrounding context. انقل علامات الرابط (`a`) بحيث تلتف حول النص `information about batteries` بدلاً من `Click here`. +نص الرابط الذي يستخدمه Camper Cat ليس وصفياً جداً بدون السياق المحيط. انقل علامات الرابط (`a`) بحيث تلتف حول النص `information about batteries` بدلاً من `Click here`. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-accessibility-of-audio-content-with-the-audio-element.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-accessibility-of-audio-content-with-the-audio-element.md index 8e5ff50fc46..0a9674923a4 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-accessibility-of-audio-content-with-the-audio-element.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-accessibility-of-audio-content-with-the-audio-element.md @@ -26,7 +26,7 @@ Here's an example: # --instructions-- -Time to take a break from Camper Cat and meet fellow camper Zersiax (@zersiax), a champion of accessibility and a screen reader user. To hear a clip of his screen reader in action, add an `audio` element after the `p`. Include the `controls` attribute. Then place a `source` tag inside the `audio` tags with the `src` attribute set to `https://s3.amazonaws.com/freecodecamp/screen-reader.mp3` and `type` attribute set to `"audio/mpeg"`. +حان الوقت لأخذ استراحة من Camper Cat و مقابلة الزميل camper Zersiax (@zersiax), بطل ال accessibility و مستخدم لتقنية قارئ الشاشة (screen reader). To hear a clip of his screen reader in action, add an `audio` element after the `p`. Include the `controls` attribute. Then place a `source` tag inside the `audio` tags with the `src` attribute set to `https://s3.amazonaws.com/freecodecamp/screen-reader.mp3` and `type` attribute set to `"audio/mpeg"`. **Note:** The audio clip may sound fast and be difficult to understand, but that is a normal speed for screen reader users. diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-chart-accessibility-with-the-figure-element.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-chart-accessibility-with-the-figure-element.md index d677e66b940..168b6351e7e 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-chart-accessibility-with-the-figure-element.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-chart-accessibility-with-the-figure-element.md @@ -27,7 +27,7 @@ Here's an example - note that the `figcaption` goes inside the `figure` tags and # --instructions-- -Camper Cat is hard at work creating a stacked bar chart showing the amount of time per week to spend training in stealth, combat, and weapons. Help him structure his page better by changing the `div` tag he used to a `figure` tag, and the `p` tag that surrounds the caption to a `figcaption` tag. +Camper Cat شاق في العمل لإنشاء مخطط أعمدة مكدسة يبين مقدار الوقت في الأسبوع لقضاء التدريب على السرقة، القتال والأسلحة. Help him structure his page better by changing the `div` tag he used to a `figure` tag, and the `p` tag that surrounds the caption to a `figcaption` tag. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-form-field-accessibility-with-the-label-element.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-form-field-accessibility-with-the-label-element.md index 55492a9a50e..8348eb4e910 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-form-field-accessibility-with-the-label-element.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-form-field-accessibility-with-the-label-element.md @@ -26,7 +26,7 @@ The value of the `for` attribute must be the same as the value of the `id` attri # --instructions-- -Camper Cat expects a lot of interest in his thoughtful blog posts and wants to include an email sign up form. Add a `for` attribute on the email `label` that matches the `id` on its `input` field. +تنتظر Camper Cat الكثير من الاهتمام في مشاركاته المدونة المدروسة وترغب في تضمين نموذج تسجيل البريد الإلكتروني. Add a `for` attribute on the email `label` that matches the `id` on its `input` field. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-readability-with-high-contrast-text.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-readability-with-high-contrast-text.md index 8bf9b62cf57..b3d9a57f918 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-readability-with-high-contrast-text.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/improve-readability-with-high-contrast-text.md @@ -15,7 +15,7 @@ The Web Content Accessibility Guidelines (WCAG) recommend at least a 4.5 to 1 co # --instructions-- -Camper Cat's choice of light gray text on a white background for his recent blog post has a 1.5:1 contrast ratio, making it hard to read. Change the `color` of the text from the current gray (`#D3D3D3`) to a darker gray (`#636363`) to improve the contrast ratio to 6:1. +اختيار Camper Cat للنص الرمادي الخفيف على خلفية بيضاء لمشاركته الأخيرة يحتوي على 1.5:1 نسبة التباين، مما يجعل من الصعب قراءتها. Change the `color` of the text from the current gray (`#D3D3D3`) to a darker gray (`#636363`) to improve the contrast ratio to 6:1. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/jump-straight-to-the-content-using-the-main-element.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/jump-straight-to-the-content-using-the-main-element.md index 83031edde59..02ebc2ac766 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/jump-straight-to-the-content-using-the-main-element.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/jump-straight-to-the-content-using-the-main-element.md @@ -19,7 +19,7 @@ The `main` tag also has an embedded landmark feature that assistive technology c # --instructions-- -Camper Cat has some big ideas for his ninja weapons page. Help him set up his markup by adding opening and closing `main` tags between the `header` and `footer` (covered in other challenges). Keep the `main` tags empty for now. +Camper Cat لديه بعض الأفكار الكبيرة لصفحة أسلحة النينجا. Help him set up his markup by adding opening and closing `main` tags between the `header` and `footer` (covered in other challenges). Keep the `main` tags empty for now. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/know-when-alt-text-should-be-left-blank.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/know-when-alt-text-should-be-left-blank.md index 695445ea37e..7b46cb25d31 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/know-when-alt-text-should-be-left-blank.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/know-when-alt-text-should-be-left-blank.md @@ -23,7 +23,7 @@ Background images usually fall under the 'decorative' label as well. However, th # --instructions-- -Camper Cat has coded a skeleton page for the blog part of his website. He's planning to add a visual break between his two articles with a decorative image of a samurai sword. Add an `alt` attribute to the `img` tag and set it to an empty string. (Note that the image `src` doesn't link to an actual file - don't worry that there are no swords showing in the display.) +قام Camper Cat ببرمجة هيكل الصفحة لمدونة موقعه. He's planning to add a visual break between his two articles with a decorative image of a samurai sword. Add an `alt` attribute to the `img` tag and set it to an empty string. (Note that the image `src` doesn't link to an actual file - don't worry that there are no swords showing in the display.) # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-elements-only-visible-to-a-screen-reader-by-using-custom-css.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-elements-only-visible-to-a-screen-reader-by-using-custom-css.md index 63bfea4b75c..f3c52234204 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-elements-only-visible-to-a-screen-reader-by-using-custom-css.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-elements-only-visible-to-a-screen-reader-by-using-custom-css.md @@ -35,7 +35,7 @@ Here's an example of the CSS rules that accomplish this: # --instructions-- -Camper Cat created a really cool stacked bar chart for his training page, and put the data into a table for his visually impaired users. The table already has an `sr-only` class, but the CSS rules aren't filled in yet. Give the `position` an `absolute` value, the `left` a `-10000px` value, and the `width` and `height` both `1px` values. +أنشأ Camper Cat مخطط أعمدة مكدسة رائع جدا لصفحة التدريب، و وضع البيانات في جدول لمستخدميه ذوي الإعاقة البصرية. The table already has an `sr-only` class, but the CSS rules aren't filled in yet. Give the `position` an `absolute` value, the `left` a `-10000px` value, and the `width` and `height` both `1px` values. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-links-navigable-with-html-access-keys.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-links-navigable-with-html-access-keys.md index ba72f6d7976..a738ee9f87e 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-links-navigable-with-html-access-keys.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-links-navigable-with-html-access-keys.md @@ -21,7 +21,7 @@ Here's an example: # --instructions-- -Camper Cat wants the links around the two blog article titles to have keyboard shortcuts so his site's users can quickly navigate to the full story. Add an `accesskey` attribute to both links and set the first one to `g` (for Garfield) and the second one to `c` (for Chuck Norris). +يريد Camper Cat أن يكون للرابطان حول عنوان مقالات المدونة اختصارات لوحة المفاتيح (keyboard shortcuts) حتى يتمكن مستخدمو موقعه من الانتقال بسرعة إلى القصة الكاملة. Add an `accesskey` attribute to both links and set the first one to `g` (for Garfield) and the second one to `c` (for Chuck Norris). # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-footer-landmark.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-footer-landmark.md index 3330ea186d7..f536b53a165 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-footer-landmark.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-footer-landmark.md @@ -13,7 +13,7 @@ Similar to `header` and `nav`, the `footer` element has a built-in landmark feat # --instructions-- -Camper Cat's training page is making good progress. Change the `div` he used to wrap his copyright information at the bottom of the page to a `footer` element. +صفحة تدريب Camper Cat تحرز تقدما جيدا. Change the `div` he used to wrap his copyright information at the bottom of the page to a `footer` element. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-header-landmark.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-header-landmark.md index babce017089..5c4b0840354 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-header-landmark.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-header-landmark.md @@ -9,11 +9,11 @@ dashedName: make-screen-reader-navigation-easier-with-the-header-landmark # --description-- -عنصر الـ HTML5 التالي الذي يضيف المعنى الدلالي ويحسن إمكانية الوصول هو عنصر الـ `header`. يستخدم لإحتواء المعلومات التمهيدية أو روابط التنقل لعنصره الاساسي ويعمل بشكل جيد مع المحتوى المتكرر في الأعلى على الصفحات متعددة. +عنصر الـ HTML5 التالي الذي يضيف المعنى الدلالي ويحسن إمكانية الوصول هو عنصر `header`. يستخدم لاحتواء المعلومات التمهيدية أو روابط التنقل لعنصره آلأساسي ويعمل بشكل جيد مع المحتوى المتكرر في الأعلى على الصفحات متعددة. `header` يشاركك الميزة البارزة المدمجة التي رأيتها مع `main`، مما يسمح للتكنولوجيات المساعدة بالانتقال بسرعة إلى ذلك المحتوى. -**ملاحظة:** عنصر الـ `header` يستخدم في عنصر `body` بمستند HTML الخاص بك. وهذا يختلف عن عن عنصر الـ `head` الذي يحتوي على عنوان الصفحة، معلومات تعريفية، و الخ. +**ملاحظة:** عنصر `header` يستخدم في عنصر `body` بمستند HTML الخاص بك. وهذا يختلف عن عن عنصر الـ `head` الذي يحتوي على عنوان الصفحة، معلومات تعريفية، و الخ. # --instructions-- @@ -21,13 +21,13 @@ dashedName: make-screen-reader-navigation-easier-with-the-header-landmark # --hints-- -يجب أن يحتوي الكود الخاص بك على `header` واحد فقط. +يجب أن يحتوي الكود الخاص بك على علامة `header` واحدة فقط. ```js assert($('header').length == 1); ``` -يجب أن يحتوي عنصر `header` عنصر الـ `h1`. +يجب أن يحتوي عنصر `header` على عنصر `h1`. ```js assert($('header').children('h1').length == 1); diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-nav-landmark.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-nav-landmark.md index 906d7dd5c83..f7386921714 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-nav-landmark.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/make-screen-reader-navigation-easier-with-the-nav-landmark.md @@ -15,7 +15,7 @@ If there are repeated site links at the bottom of the page, it isn't necessary t # --instructions-- -Camper Cat included navigation links at the top of his training page, but wrapped them in a `div`. Change the `div` to a `nav` tag to improve the accessibility on his page. +تضمن Camper Cat وصلات تنقل من الجزء العلوي من صفحة التدريب، لكنه لفها في `div`. Change the `div` to a `nav` tag to improve the accessibility on his page. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/standardize-times-with-the-html5-datetime-attribute.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/standardize-times-with-the-html5-datetime-attribute.md index d9318d50f7b..3892d3ff1bc 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/standardize-times-with-the-html5-datetime-attribute.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/standardize-times-with-the-html5-datetime-attribute.md @@ -19,7 +19,7 @@ Here's an example: # --instructions-- -Camper Cat's Mortal Kombat survey results are in! Wrap a `time` tag around the text `Thursday, September 15th` and add a `datetime` attribute to it set to `2016-09-15`. +صدرت نتائج استقصاء Camper Cat في Mortal Kombat! Wrap a `time` tag around the text `Thursday, September 15th` and add a `datetime` attribute to it set to `2016-09-15`. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/use-headings-to-show-hierarchical-relationships-of-content.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/use-headings-to-show-hierarchical-relationships-of-content.md index fbb95eaf6b1..4feaf983881 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/use-headings-to-show-hierarchical-relationships-of-content.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/use-headings-to-show-hierarchical-relationships-of-content.md @@ -23,7 +23,7 @@ One final point, each page should always have one (and only one) `h1` element, w # --instructions-- -Camper Cat wants a page on his site dedicated to becoming a ninja. Help him fix the headings so his markup gives semantic meaning to the content, and shows the proper parent-child relationships of his sections. Change all the `h5` tags to the proper heading level to indicate they are subsections of the `h2` ones. Use `h3` tags for the purpose. +يريد Camper Cat صفحة مخصصة على موقعه الإلكتروني لتعليم النينجا. Help him fix the headings so his markup gives semantic meaning to the content, and shows the proper parent-child relationships of his sections. Change all the `h5` tags to the proper heading level to indicate they are subsections of the `h2` ones. Use `h3` tags for the purpose. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/use-tabindex-to-add-keyboard-focus-to-an-element.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/use-tabindex-to-add-keyboard-focus-to-an-element.md index 0ae730fc94b..cfd48fbd829 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/use-tabindex-to-add-keyboard-focus-to-an-element.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/use-tabindex-to-add-keyboard-focus-to-an-element.md @@ -21,7 +21,7 @@ Certain elements, such as links and form controls, automatically receive keyboar # --instructions-- -Camper Cat created a new survey to collect information about his users. He knows input fields automatically get keyboard focus, but he wants to make sure his keyboard users pause at the instructions while tabbing through the items. Add a `tabindex` attribute to the `p` tag and set its value to `0`. Bonus - using `tabindex` also enables the CSS pseudo-class `:focus` to work on the `p` tag. +أنشأ Camper Cat استبيانا جديدا لجمع المعلومات عن مستخدمي موقعه. He knows input fields automatically get keyboard focus, but he wants to make sure his keyboard users pause at the instructions while tabbing through the items. Add a `tabindex` attribute to the `p` tag and set its value to `0`. Bonus - using `tabindex` also enables the CSS pseudo-class `:focus` to work on the `p` tag. # --hints-- diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/wrap-content-in-the-article-element.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/wrap-content-in-the-article-element.md index f6eaf6e3697..0ec49e8d271 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/wrap-content-in-the-article-element.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/wrap-content-in-the-article-element.md @@ -9,7 +9,7 @@ dashedName: wrap-content-in-the-article-element # --description-- -`article` هو عنصر آخر من عناصر HTML5 الجديدة التي تضيف المعنى الدلالي الي الـ markup. `article` هو عنصر تقسيم ويستخدم لتغليف المحتوي القائم بذاته. يعمل الوسم بشكل جيد مع إدخالات المدونة أو مشاركات المنتدى أو المقالات الإخبارية. +`article` هو عنصر آخر من عناصر HTML5 الجديدة التي تضيف المعنى الدلالي الي الـ markup. `article` هو عنصر تقسيم ويستخدم لتغليف المحتوي القائم بذاته. يعمل العلامة بشكل جيد مع إدخالات المدونة أو مشاركات المنتدى أو المقالات الإخبارية. تحديد فيما إذا كان المحتوى يستطيع أن يكون مستقلا هو عادة حكم شخصي، ولكن هناك عدة اختبارات بسيطة يمكنك استخدامها. اسأل نفسك إذا كنت قد قمت بإزالة كل السياق المحيط، هل سيظل المحتوى منطقياً؟ وبالمثل بالنسبة للنص، هل سيظل المحتوى صحيحا إذا كان في RSS feed ؟ @@ -21,17 +21,17 @@ dashedName: wrap-content-in-the-article-element # --instructions-- -استخدم Camper Cat وسوم `article` لتغليف المشاركات على صفحة مدونته، لكنه نسي استخدامها حول المشاركة العلوية. قم بتغيير وسم `div` لاستخدام وسم `article` بدلاً منه. +استخدم Camper Cat علامات `article` لتغليف المشاركات على صفحة مدونته، لكنه نسي استخدامها حول المشاركة العلوية. قم بتغيير علامة `div` لاستخدام علامة `article` بدلاً منه. # --hints-- -الكود الخاص بك يجب أن يحتوي على ثلاث وسوم `article`. +الكود الخاص بك يجب أن يحتوي على ثلاث علامات `article`. ```js assert($('article').length == 3); ``` -يجب ألا يحتوي الكود الخاص بك علي اي وسوم `div`. +يجب ألا يحتوي الكود الخاص بك على أي علامة `div`. ```js assert($('div').length == 0); diff --git a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/wrap-radio-buttons-in-a-fieldset-element-for-better-accessibility.md b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/wrap-radio-buttons-in-a-fieldset-element-for-better-accessibility.md index dab2077c235..794a8db4a3d 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/wrap-radio-buttons-in-a-fieldset-element-for-better-accessibility.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/applied-accessibility/wrap-radio-buttons-in-a-fieldset-element-for-better-accessibility.md @@ -1,6 +1,6 @@ --- id: 587d778b367417b2b2512aa7 -title: Wrap Radio Buttons in a fieldset Element for Better Accessibility +title: تغليف Radio Buttons داخل fieldset لتحسينا لإمكانيه الوصول challengeType: 0 videoUrl: 'https://scrimba.com/c/cVJVefw' forumTopicId: 301030 @@ -9,13 +9,13 @@ dashedName: wrap-radio-buttons-in-a-fieldset-element-for-better-accessibility # --description-- -The next form topic covers the accessibility of radio buttons. Each choice is given a `label` with a `for` attribute tying to the `id` of the corresponding item as covered in the last challenge. Since radio buttons often come in a group where the user must choose one, there's a way to semantically show the choices are part of a set. +يغطي الموضوع التالي إمكانية الوصول إلى أزرار الاختيار في النموذج. كل اختيار يعطى `label` وله سمة `for` مرتبطة بـسمة `id` في العنصر المقابل, كما هو مشمول في التحدي الأخير. نظرًا لأن أزرار الراديو (radio buttons) غالبا ما تأتي في مجموعات حيث يجب على المستخدم أن يختار زر واحد فقط من المجموعة، هناك طريقة لإظهار الخيارات بشكل لُغَوي أنها فعلا جزء من نفس المجموعة. -The `fieldset` tag surrounds the entire grouping of radio buttons to achieve this. It often uses a `legend` tag to provide a description for the grouping, which screen readers read for each choice in the `fieldset` element. +يحيط علامة (tag) `fieldset` بكامل مجموعة الأزرار الراديو ( radio buttons) لتحقيق ذلك. غالبا ما تستخدم علامة (tag) `legend` لتقديم وصف للمجموعة، أي قارئ الشاشة (screen readers) سيقرأ كل اختيار في عنصر `fieldset`. -The `fieldset` wrapper and `legend` tag are not necessary when the choices are self-explanatory, like a gender selection. Using a `label` with the `for` attribute for each radio button is sufficient. +العلامة `fieldset` و `legend` ليست ضرورية عندما تكون الاختيارات غنية عن التفسير، مثل اختيار نوع الجنس. استخدام `label` مع سمة `for` لكل زر راديو هو كاف. -Here's an example: +إليك مثال: ```html @@ -33,17 +33,17 @@ Here's an example: # --instructions-- -Camper Cat wants information about the ninja level of his users when they sign up for his email list. He's added a set of radio buttons and learned from our last lesson to use `label` tags with `for` attributes for each choice. Go Camper Cat! However, his code still needs some help. Change the `div` tag surrounding the radio buttons to a `fieldset` tag, and change the `p` tag inside it to a `legend`. +يريد Camper Cat معلومات عن مستوى النينجا لمستخدميه عند تسجيلهم لقائمة بريده الإلكتروني. لقد أضاف مجموعة من أزرار الراديو وتعلمنا من درسنا الأخير استخدام علامات `label` مع سمات `for` لكل اختيار. اذهب يا Camper Cat! However, his code still needs some help. تغيير علامة `div` الذي يحيط بأزرار الراديو إلى علامة `fieldset`، و قم بتغيير علامة `p` بداخلها إلى `legend`. # --hints-- -Your code should have a `fieldset` tag around the radio button set. +يجب أن يكون الكود الخاص بك فيه علامة `fieldset` حول مجموعة أزرار الراديو. ```js assert($('fieldset').length == 1); ``` -The `fieldset` element should have a closing tag. +يجب أن يكون لعلامة `fieldset` علامة اغلاق أيضا. ```js assert( @@ -52,19 +52,19 @@ assert( ); ``` -Your code should have a `legend` tag around the text asking what level ninja a user is. +يجب أن يحتوي الكود الخاصة بك على علامة `legend` حول النص الذي يسأل عن مستوى النينجا للمستخدم. ```js assert($('legend').length == 1); ``` -Your code should not have any `div` tags. +يجب ألا يحتوي الكود الخاص بك على أي علامة `div`. ```js assert($('div').length == 0); ``` -Your code should no longer have a `p` tag around the text asking what level ninja a user is. +يجب ألا يحتوي الكود الخاصة بك على علامة `p` حول النص الذي يسأل عن مستوى النينجا للمستخدم. ```js assert($('p').length == 4); diff --git a/curriculum/challenges/arabic/01-responsive-web-design/basic-html-and-html5/create-a-set-of-checkboxes.md b/curriculum/challenges/arabic/01-responsive-web-design/basic-html-and-html5/create-a-set-of-checkboxes.md index 43bc88a6169..6c0f2f8caeb 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/basic-html-and-html5/create-a-set-of-checkboxes.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/basic-html-and-html5/create-a-set-of-checkboxes.md @@ -43,7 +43,7 @@ Each of your three checkbox elements should be nested in its own `label` element assert($('label > input[type="checkbox"]:only-child').length > 2); ``` -Make sure each of your `label` elements has a closing tag. +تأكد أن كل `label` له علامة إغلاق. ```js assert( diff --git a/curriculum/challenges/arabic/01-responsive-web-design/basic-html-and-html5/link-to-external-pages-with-anchor-elements.md b/curriculum/challenges/arabic/01-responsive-web-design/basic-html-and-html5/link-to-external-pages-with-anchor-elements.md index b9d927d2707..80bdf98afd4 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/basic-html-and-html5/link-to-external-pages-with-anchor-elements.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/basic-html-and-html5/link-to-external-pages-with-anchor-elements.md @@ -17,27 +17,27 @@ You can use `a` (*anchor*) elements to link to content outside of your web page. this links to freecodecamp.org ``` -Then your browser will display the text `this links to freecodecamp.org` as a link you can click. And that link will take you to the web address `https://www.freecodecamp.org`. +ثم يعرض متصفّحك نص: `this links to freecodecamp.org` على هيئة رابط يمكنك النقر عليه. And that link will take you to the web address `https://www.freecodecamp.org`. # --instructions-- -قم بإنشاء عنصر `a` الذي يربط بـ `https://www.freecatphotoapp.com` والذي يحتوي على "صور قطط" كنص الرابط. +Create an `a` element that links to `https://www.freecatphotoapp.com` and has "cat photos" as its anchor text. # --hints-- -يجب أن يحتوي عنصر `a` الخاص بك على نص الرابط `cat photos`. +Your `a` element should have the anchor text of `cat photos`. ```js assert(/cat photos/gi.test($('a').text())); ``` -You need an `a` element that links to `https://www.freecatphotoapp.com` +تحتاج إلى عنصر `a` يربط بـعنوان `https://www.freecatphotoapp.com` ```js assert(/^https?:\/\/(www\.)?freecatphotoapp\.com\/?$/i.test($('a').attr('href'))); ``` -Your `a` element should have a closing tag. +عنصر `a` الخاص بك لا بد أن يكون له علامة إغلاق (closing tag). ```js assert( diff --git a/curriculum/challenges/arabic/01-responsive-web-design/css-flexbox/use-the-flex-shrink-property-to-shrink-items.md b/curriculum/challenges/arabic/01-responsive-web-design/css-flexbox/use-the-flex-shrink-property-to-shrink-items.md index fc9b59ab23f..5541b9cc8fe 100644 --- a/curriculum/challenges/arabic/01-responsive-web-design/css-flexbox/use-the-flex-shrink-property-to-shrink-items.md +++ b/curriculum/challenges/arabic/01-responsive-web-design/css-flexbox/use-the-flex-shrink-property-to-shrink-items.md @@ -1,6 +1,6 @@ --- id: 587d78ad367417b2b2512afb -title: Use the flex-shrink Property to Shrink Items +title: استعمال خاصية flex-shrink لتقليص الأصناف challengeType: 0 videoUrl: 'https://scrimba.com/p/pVaDAv/cd3PBfr' forumTopicId: 301113 @@ -9,11 +9,11 @@ dashedName: use-the-flex-shrink-property-to-shrink-items # --description-- -So far, all the properties in the challenges apply to the flex container (the parent of the flex items). However, there are several useful properties for the flex items. +وتنطبق حتى الآن جميع الخواص في التحديات على الحاوية المرنة ( flex container وهو أصل الأصناف المرنة). غير أن هناك عدة خصائص مفيدة للأصناف المرنة (flex items). The first is the `flex-shrink` property. When it's used, it allows an item to shrink if the flex container is too small. Items shrink when the width of the parent container is smaller than the combined widths of all the flex items within it. -The `flex-shrink` property takes numbers as values. The higher the number, the more it will shrink compared to the other items in the container. For example, if one item has a `flex-shrink` value of `1` and the other has a `flex-shrink` value of `3`, the one with the value of `3` will shrink three times as much as the other. +الخاصية `flex-shrink` تأخذ الأرقام كقيم. وكلما ارتفع العدد، سيتقلص العدد مقارنة بالاصناف الأخرى في الحاوية. For example, if one item has a `flex-shrink` value of `1` and the other has a `flex-shrink` value of `3`, the one with the value of `3` will shrink three times as much as the other. # --instructions-- diff --git a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/create-decimal-numbers-with-javascript.md b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/create-decimal-numbers-with-javascript.md index aa074aa8881..20160798630 100644 --- a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/create-decimal-numbers-with-javascript.md +++ b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/create-decimal-numbers-with-javascript.md @@ -11,11 +11,11 @@ dashedName: create-decimal-numbers-with-javascript يمكننا تخزين أرقام عشرية في المتغيرات أيضا. يشار أحياناً إلى الأرقام العشرية على أنها أرقام نُقَط عائمة (floating point) أو عائمات (floats). -**ملاحظة:** عندما حساب الأرقام، يتم حسابها بدقة محدودة. وقد تؤدي العمليات التي تستخدم نُقَط عائمة إلى نتائج مختلفة عن النتائج المرجوة. إذا حصلت على واحدة من هذه النتائج، أفتح موضوع في منتدى freeCodeCamp. +**ملاحظة:** عندما تحسب الأرقام، يتم حسابها بدقة محدودة. وقد تؤدي العمليات التي تستخدم نُقَط عائمة إلى نتائج مختلفة عن النتائج المرغوبة. إذا حصلت على واحدة من هذه النتائج، أفتح موضوع في منتدى freeCodeCamp. # --instructions-- -أنشئ متغير `myDecimal` وإعطائه قيمة عشرية بجزء كسري (على سبيل المثال `5.7`). +أنشئ متغير `myDecimal` واعطه قيمة عشرية بجزء كسري (على سبيل المثال `5.7`). # --hints-- @@ -25,7 +25,7 @@ dashedName: create-decimal-numbers-with-javascript assert(typeof myDecimal === 'number'); ``` -يجب أن يحتوي `myDecimal` على نقطة عشرية +يجب أن يحتوي `myDecimal` على نقطة عشرية (decimal point) ```js assert(myDecimal % 1 != 0); diff --git a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/decrement-a-number-with-javascript.md b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/decrement-a-number-with-javascript.md index ea0af524bf3..b17352c4795 100644 --- a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/decrement-a-number-with-javascript.md +++ b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/decrement-a-number-with-javascript.md @@ -21,7 +21,7 @@ i--; i = i - 1; ``` -**ملاحظة:** السطر بِرُمَّته يصبح `i--;`، وإلغاء الحاجة إلى علامة المساواة. +**ملاحظة:** السطر بكامله يصبح `i--;`، مما يزيل الحاجة إلى علامة المساواة. # --instructions-- diff --git a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/increment-a-number-with-javascript.md b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/increment-a-number-with-javascript.md index cc741af7a36..d5cea9e9c23 100644 --- a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/increment-a-number-with-javascript.md +++ b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/basic-javascript/increment-a-number-with-javascript.md @@ -21,7 +21,7 @@ i++; i = i + 1; ``` -**ملاحظة:** السطر بأكمله يصبح `i++;`، مما يزيل الحاجة إلى علامة المساواة (equal sign). +**ملاحظة:** السطر بالأكمل يصبح `i++;`، مما يزيل الحاجة إلى علامة المساواة (equal sign). # --instructions-- diff --git a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/functional-programming/use-the-map-method-to-extract-data-from-an-array.md b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/functional-programming/use-the-map-method-to-extract-data-from-an-array.md index 82e08157c9b..788d4ee7593 100644 --- a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/functional-programming/use-the-map-method-to-extract-data-from-an-array.md +++ b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/functional-programming/use-the-map-method-to-extract-data-from-an-array.md @@ -33,7 +33,7 @@ const names = users.map(user => user.name); console.log(names); ``` -ستعرض وحدة التحكم القيمة `[ 'John', 'Amy', 'camperCat' ]`. +سيعرض الكونسول القيمة `[ 'John', 'Amy', 'camperCat' ]`. # --instructions-- diff --git a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/functional-programming/use-the-reduce-method-to-analyze-data.md b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/functional-programming/use-the-reduce-method-to-analyze-data.md index 048e3aae50c..7d563d8038c 100644 --- a/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/functional-programming/use-the-reduce-method-to-analyze-data.md +++ b/curriculum/challenges/arabic/02-javascript-algorithms-and-data-structures/functional-programming/use-the-reduce-method-to-analyze-data.md @@ -47,7 +47,7 @@ const usersObj = users.reduce((obj, user) => { console.log(usersObj); ``` -ستعرض وحدة التحكم القيمة `{ John: 34, Amy: 20, camperCat: 10 }`. +سيعرض الكونسول القيمة `{ John: 34, Amy: 20, camperCat: 10 }`. # --instructions-- diff --git a/curriculum/challenges/arabic/03-front-end-development-libraries/react/render-a-class-component-to-the-dom.md b/curriculum/challenges/arabic/03-front-end-development-libraries/react/render-a-class-component-to-the-dom.md index d34c6fdd953..5a74017ecd3 100644 --- a/curriculum/challenges/arabic/03-front-end-development-libraries/react/render-a-class-component-to-the-dom.md +++ b/curriculum/challenges/arabic/03-front-end-development-libraries/react/render-a-class-component-to-the-dom.md @@ -10,7 +10,7 @@ dashedName: render-a-class-component-to-the-dom يمكنك تذكر استخدام API ReactDOM في تحدي سابق لإضافة عناصر JSX إلى DOM. وستبدو عملية تقديم مكونات React متشابهة جدا. لقد ركزت التحديات القليلة الماضية على المكونات (components) والتكوين (composition)، لذلك تم التقديم لك خلف الكواليس. ومع ذلك، لن يقدم أي من رمز React الذي تكتبه إلى DOM دون أستدعاء API ReactDOM. -إليك تذكير بكفية كتابة الصيغة: `ReactDOM.render(componentToRender, targetNode)`. الحَجَّة الأولى هي عنصر React الذي تريد أنتاجه. الحجة الثانية هي نقطة التواصل DOM التي تريد أن تجعل ذلك المكون داخلها. +إليك تذكير بكفية كتابة الصيغة: `ReactDOM.render(componentToRender, targetNode)`. الحَجَّة الأولى هي عنصر React الذي تريد أنتاجه. الحجة الثانية هي DOM node التي تريد أن تجعل ذلك المكون داخلها. يتم تمرير مكونات React إلى `ReactDOM.render()` بشكل مختلف قليلا عن عناصر JSX. بالنسبة لعناصر JSX ، يمكنك تمرير اسم العنصر الذي تريد أنتاجه. لكن، بالنسبة لمكونات React، تحتاج إلى استخدام نفس الجملة كما لو كنت تقدم مكوناً متداخلاً، على سبيل المثال `ReactDOM.render(, targetNode)`. أنت تستخدم هذه الجملة لكل من مكونات فئة ES6 والمكونات الوظيفية ES6. diff --git a/curriculum/challenges/arabic/03-front-end-development-libraries/react/render-html-elements-to-the-dom.md b/curriculum/challenges/arabic/03-front-end-development-libraries/react/render-html-elements-to-the-dom.md index 092b3ab7208..80f52e03146 100644 --- a/curriculum/challenges/arabic/03-front-end-development-libraries/react/render-html-elements-to-the-dom.md +++ b/curriculum/challenges/arabic/03-front-end-development-libraries/react/render-html-elements-to-the-dom.md @@ -10,13 +10,13 @@ dashedName: render-html-elements-to-the-dom حتى الآن، تعلمت أن JSX أداة مناسبة لكتابة HTML مقروءة داخل JavaScript. مع React، يمكننا تقديم JSX قاصدًا إلى HTML DOM باستخدام React الذي يقدم API المعروف باسم ReactDOM. -ReactDOM يوفر طريقة بسيطة لتقديم عناصر React إلى DOM التي تبدو مثل: `ReactDOM.render(componentToRender, targetNode)`, حيث الحَجَّة الأولى هي عنصر React أو المكون الذي تريد أن تنتجه، والحجة الثانية هي نقطة التواصل DOM التي تريد تقديم المكون إليها. +ReactDOM يوفر طريقة بسيطة لتقديم عناصر React إلى DOM التي تبدو مثل: `ReactDOM.render(componentToRender, targetNode)`, حيث الحَجَّة الأولى هي عنصر React أو المكون الذي تريد أن تنتجه، والحجة الثانية هي node DOM التي تريد تقديم المكون إليها. كما تتوقع، `ReactDOM.render()` يجب أن يستدعى بعد إعلانات عناصر JSX، تماما مثل كيفية الإعلان عن المتغيرات قبل استخدامها. # --instructions-- -يحتوي محرر التعليمات البرمجية على مكون JSX بسيط. استخدم طريقة `ReactDOM.render()` لإضافة هذا المكون إلى الصفحة. يمكنك تمرير عناصر JSX المحددة قاصدًا كالحجة الأولى واستخدام `document.getElementById()` لتحديد نقطة التواصل DOM لتقديمها إليها. هناك `div` مع `id='challenge-node'` متاح لك للاستخدام. تيقن من عدم تغيير ثابت `JSX`. +يحتوي محرر التعليمات البرمجية على مكون JSX بسيط. استخدم طريقة `ReactDOM.render()` لإضافة هذا المكون إلى الصفحة. يمكنك تمرير عناصر JSX المحددة قاصدًا كالحجة الأولى واستخدام `document.getElementById()` لتحديد DOM node لتقديمها إليها. هناك `div` مع `id='challenge-node'` متاح لك للاستخدام. تيقن من عدم تغيير ثابت `JSX`. # --hints-- @@ -38,7 +38,7 @@ assert(JSX.props.children[0].type === 'h1'); assert(JSX.props.children[1].type === 'p'); ``` -يجب أن ينتج عنصر JSX المقدم إلى نقطة التواصل DOM مع معرف `challenge-node`. +يجب أن ينتج عنصر JSX المقدم إلى DOM node مع معرف `challenge-node`. ```js assert( diff --git a/curriculum/challenges/arabic/04-data-visualization/data-visualization-projects/visualize-data-with-a-bar-chart.md b/curriculum/challenges/arabic/04-data-visualization/data-visualization-projects/visualize-data-with-a-bar-chart.md index 4e648ec9b72..2c19676c986 100644 --- a/curriculum/challenges/arabic/04-data-visualization/data-visualization-projects/visualize-data-with-a-bar-chart.md +++ b/curriculum/challenges/arabic/04-data-visualization/data-visualization-projects/visualize-data-with-a-bar-chart.md @@ -1,6 +1,6 @@ --- id: bd7168d8c242eddfaeb5bd13 -title: Visualize Data with a Bar Chart +title: التمثيل البياني باستخدام مخطط الأعمدة challengeType: 3 forumTopicId: 301464 dashedName: visualize-data-with-a-bar-chart @@ -8,7 +8,7 @@ dashedName: visualize-data-with-a-bar-chart # --description-- -**Objective:** Build an app that is functionally similar to this: https://bar-chart.freecodecamp.rocks. +**متطلبات:** قم ببناء تطبيق يُشبه في وظيفته https://bar-chart.freecodecamp.rocks. Fulfill the below user stories and get all of the tests to pass. Use whichever libraries or APIs you need. Give it your own personal style. @@ -16,13 +16,13 @@ You can use HTML, JavaScript, CSS, and the D3 svg-based visualization library. T **User Story #1:** My chart should have a title with a corresponding `id="title"`. -**User Story #2:** My chart should have a `g` element x-axis with a corresponding `id="x-axis"`. +**قصة المستخدم الثانية 2#:**: يجب أن يحتوي المخطط علي عنصر `g` علي المحور x (الأفقي) مع `id="x-axis"`. -**User Story #3:** My chart should have a `g` element y-axis with a corresponding `id="y-axis"`. +**قصة المُستخدم الثالثة 3#:** يجب أن يحتوى المخطط علي عُنصر `g` علي المحور y (العمودي) مع `id="y-axis"`. **User Story #4:** Both axes should contain multiple tick labels, each with a corresponding `class="tick"`. -**User Story #5:** My chart should have a `rect` element for each data point with a corresponding `class="bar"` displaying the data. +**قصة المٌستخدم الخامسة 5#:** يَجب أن يحتوي المخطط على عُنصر من نوع `rect` لكل معلومة مع عرض للبيانات بواسطة سمة `class="bar"`. **User Story #6:** Each bar should have the properties `data-date` and `data-gdp` containing `date` and `GDP` values. diff --git a/curriculum/challenges/arabic/04-data-visualization/data-visualization-projects/visualize-data-with-a-scatterplot-graph.md b/curriculum/challenges/arabic/04-data-visualization/data-visualization-projects/visualize-data-with-a-scatterplot-graph.md index 7f1d00bb3d0..b6dbb4fad07 100644 --- a/curriculum/challenges/arabic/04-data-visualization/data-visualization-projects/visualize-data-with-a-scatterplot-graph.md +++ b/curriculum/challenges/arabic/04-data-visualization/data-visualization-projects/visualize-data-with-a-scatterplot-graph.md @@ -8,19 +8,19 @@ dashedName: visualize-data-with-a-scatterplot-graph # --description-- -**Objective:** Build an app that is functionally similar to this: https://scatterplot-graph.freecodecamp.rocks. +**متطلبات:** كم ببناء تطبيق يشبه في وظيفته https://scatterplot-graph.freecodecamp.rocks. Fulfill the below user stories and get all of the tests to pass. Use whichever libraries or APIs you need. Give it your own personal style. -يمكنك استخدام HTML و JavaScript و CSS و مكتبة التصوير المستندة D3. تطلب الاختبارات إنشاء محاور (axes) باستخدام خاصية المحور في D3، الذي يؤدي تِلْقائيًا إلى وضع علامات على طول المحور. وهذه العلامات لازمة لاجتياز اختبارات D3, لأن تُستخدم مواقعها لتحديد محاذاة العناصر المرسومة بيانيٍ. ستجد معلومات حول إنشاء محاور في . يتم الاستفسار عن العناصر المطلوبة DOM (غير متغيرة) في لحظة إجراء كل اختبار. إذا كنت تستخدم framework (مثل Vue على سبيل المثال)، قد تكون نتائج الاختبار غير دقيقة للمحتوى الديناميكي. ونأمل أن نستوعبها في المستقبل، ولكن هذه frameworks غير مدعومة حاليا لمشاريع D3. +يمكنك استخدام HTML و JavaScript و CSS و مكتبة التصوير المستندة D3. تطلب الاختبارات إنشاء المحاور (axes) باستخدام خاصية axis في D3، الذي يؤدي تِلْقائيًا إلى وضع علامات (ticks) على طول المحور. وهذه العلامات لازمة لاجتياز اختبارات D3, لأن مواقعها تُستخدم لتحديد محاذاة العناصر المرسومة بيانيٍ. ستجد معلومات حول إنشاء المحاور في . يتم الاستفسار عن العناصر المطلوبة DOM (non-virtual) في لحظة إجراء كل اختبار. إذا كنت تستخدم framework (مثل Vue على سبيل المثال)، قد تكون نتائج الاختبار غير دقيقة للمحتوى الديناميكي. ونأمل أن نستوعبها في المستقبل، ولكن هذه frameworks غير مدعومة حاليا لمشاريع D3. **قصة المستخدم 1:** يمكنني أن أرى عنصر title مع موافقه `id="title"`. -**قصة المستخدم 2:** يمكنني رؤية محور أفقي (x-axis) يوافق بمتغير يحتوي على `id="x-axis"`. +**قصة المستخدم 2:** يمكنني رؤية محور أفقي (x-axis) يحتوي على سمة `id="x-axis"`. -**قصة المستخدم 3:** يمكنني رؤية محور رئسي (y-axis) يوافق بمتغير يحتوي على `id="y-axis"`. +**قصة المستخدم 3:** يمكنني رؤية محور رئسي (y-axis) يحتوي على `id="y-axis"`. -**قصة المستخدم 4:** يمكنني أن أرى نقاط (dots) ولكل منها فئة (class) يسمى `dot`، التي تمثل البيانات التي يتم تخطيطها. +**قصة المستخدم 4:** يمكنني أن أرى نقاط (dots) ولكل منها فئة (class) `dot`، التي تمثل البيانات التي يتم رسمها. **قصة المستخدم 5:** كل نقطة يجب أن تحتوي على الخصائص `data-xvalue` و `data-yvalue` التي توافق القيم `x` و `y`. @@ -30,25 +30,25 @@ Fulfill the below user stories and get all of the tests to pass. Use whichever l **قصة المستخدم 8:** يجب أن توافق `data-yvalue` ونقطتها الموافقة مع النقطة/القيمة الموافقة على محور أفقي (y-axis). -**قصة المستخدم 9:** يمكننك رأيه تسميات علامة (tick) متعددة على المحور الرأسي (y-axis) مع تنسيق الوقت `%M:%S`. +**قصة المستخدم 9:** يمكننك رؤية تسميات علامة (tick) متعددة على المحور الرأسي (y-axis) مع تنسيق الوقت `%M:%S`. -**قصة المستخدم 10:** يمكننك رأيه تسميات علامة (tick) متعددة على المحور الأفقي (x-axis) الذي يظهر السنة. +**قصة المستخدم 10:** يمكننك رؤية تسميات علامة (tick) متعددة على المحور الأفقي (x-axis) الذي يظهر السنة. -**قصة المستخدم 11:** يمكننك رأيه نطاق (range) تسميات (labels) المحور الأفقي (x-axis) يقع ضمن نطاق بيانات المحور الأفقي (x-axis) فعلاً. +**قصة المستخدم 11:** يمكننك رؤية نطاق (range) تسميات (labels) المحور الأفقي (x-axis) يقع ضمن نطاق بيانات المحور الأفقي (x-axis) فعلاً. -**قصة المستخدم 12:** يمكننك رأيه نطاق (range) تسميات (labels) المحور الرأسي (y-axis) تقع ضمن نطاق بيانات المحور الرأسي (y-axis) فعلاً. +**قصة المستخدم 12:** يمكننك رؤية نطاق (range) تسميات (labels) المحور الرأسي (y-axis) تقع ضمن نطاق بيانات المحور الرأسي (y-axis) فعلاً. -**قصة المستخدم 13:** يمكننك رؤية legend تحتوي على نص وصفي يحتوي على `id="legend"`. +**قصة المستخدم 13:** يمكننك رؤية legend تحتوي على نص وصفي وله سمة `id="legend"`. -**قصة المستخدم 14:** يمكننك تحريك الفأرة (mouse) فوق منطقة ورأيه أدوات نصيحة (tooltip) موافق `id="tooltip"`، التي تعرض المزيد من المعلومات حول المنطقة. +**قصة المستخدم 14:** عند تحريك الفأر (mouse) فوق منطقة سترى أدوات نصيحة (tooltip) لها سمة `id="tooltip"`، وهي تعرض المزيد من المعلومات عن المنطقة. **قصة المستخدم 15:** يجب أن تحتوي تلميح أدواتك (tooltip) على خاصية `data-year` التي تتوافق مع `data-xvalue` في المنطقة النشطة. -إليك مجموعة البيانات التي ستحتاج إلى إكمال هذا المشروع: `https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/cyclist-data.json` +إليك مجموعة البيانات التي ستحتاج لإكمال هذا المشروع: `https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/cyclist-data.json` -You can build your project by using this CodePen template and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js` +يمكنك بناء مشروعك عن طريق استخدام هذا نموذج CodePen والنقر على `Save` لإنشاء pen خاص بك. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js` -Once you're done, submit the URL to your working project with all its tests passing. +بمجرد أن تنتهي، ارسل عنوان URL لمشروعك مع اجتياز جميع الاختبارات. # --solutions-- diff --git a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-a-hover-effect-to-a-d3-element.md b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-a-hover-effect-to-a-d3-element.md index 5e51c08ff03..2162ce83531 100644 --- a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-a-hover-effect-to-a-d3-element.md +++ b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-a-hover-effect-to-a-d3-element.md @@ -1,6 +1,6 @@ --- id: 587d7faa367417b2b2512bd4 -title: أضف تأثير عند تحرك المستخدم فوق عنصر (Hover Effect) إلى عنصر D3 +title: أضف تأثير عندما يحوم الماوس فوق عنصر (Hover Effect) D3 challengeType: 6 forumTopicId: 301469 dashedName: add-a-hover-effect-to-a-d3-element @@ -8,7 +8,7 @@ dashedName: add-a-hover-effect-to-a-d3-element # --description-- -من الممكن إضافة التأثيرات (effects) التي توضح العمود (bar) عندما يتحرك المستخدم فوقه (hovers) بالفأرة. حتى الآن، طبقت تصميم (style) لمستطيلات بواسطة طرق (methods) في D3 و SVG الموجودة داخلهم، ولكن يمكنك استعمال CSS أيضا. +من الممكن إضافة التأثيرات (effects) التي توضح العمود (bar) عندما يحوم الماوس فوقه (hovers). حتى الآن، طبقت تصميم (style) المستطيلات بواسطة طرق مدمجة (built-in methods) في D3 و SVG، ولكن يمكنك استعمال CSS أيضا. يمكنك تعيين فئة (class) من CSS إلى عناصر SVG مع طريقة (method) تسمى `attr()`. ثم تحتوي فئة الزائفة (pseudo-class) المسمى `:hover` على قواعد التصميم (style) الجديدة لأي تأثيرات عند تحرك المستخدم فوق عنصر (hover). diff --git a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-a-tooltip-to-a-d3-element.md b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-a-tooltip-to-a-d3-element.md index 4cca0a103d8..784787f50a1 100644 --- a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-a-tooltip-to-a-d3-element.md +++ b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-a-tooltip-to-a-d3-element.md @@ -8,13 +8,13 @@ dashedName: add-a-tooltip-to-a-d3-element # --description-- -يظهر أداة لتلميح (tooltip) المزيد من معلومات العنصر في صفحة عندما ينتقل المستخدم فوق (hovers) هذا العنصر. هناك عدة طرق لإضافة أداة لتلميح (tooltip) لعرض بيانات، هذا التحدي يستخدم عنصر `title` في SVG. +يظهر أداة لتلميح (tooltip) المزيد من معلومات العنصر في صفحة عندما ينتقل المستخدم فوق (hovers) هذا العنصر. هناك عدة طرق لإضافة تلميح لعرض البيانات. هذا التحدي يستخدم عنصر `title` في SVG. يرافق `title` الطريقة (method) المسمى `text()` لإضافة البيانات بشكل ديناميكي إلى الأعمدة (bars). # --instructions-- -أربط عنصر `title` تحت كل نقطة التواصل (node) المسمى `rect`. ثم فعّيل الطريقة (method) المسمى `text()` مع وظيفة تعيد تفعيل (callback function) بحيث يعرض النص قيمة البيانات. +أربط عنصر `title` تحت كل node المسمى `rect`. ثم فعّيل الطريقة (method) المسمى `text()` مع وظيفة تعيد تفعيل (callback function) بحيث يعرض النص قيمة البيانات. # --hints-- @@ -24,55 +24,55 @@ dashedName: add-a-tooltip-to-a-d3-element assert($('title').length == 9); ``` -يجب أن يحتوي أول عنصر `title` على أداة لتلميح (tooltip) بنص `12`. +يجب أن يحتوي أول عنصر `title` على تلميح بنص `12`. ```js assert($('title').eq(0).text() == '12'); ``` -يجب أن يحتوي ثاني عنصر `title` على أداة لتلميح (tooltip) بنص `31`. +يجب أن يحتوي ثاني عنصر `title` على تلميح بنص `31`. ```js assert($('title').eq(1).text() == '31'); ``` -يجب أن يحتوي ثالث عنصر `title` على أداة لتلميح (tooltip) بنص `22`. +يجب أن يحتوي ثالث عنصر `title` على تلميح بنص `22`. ```js assert($('title').eq(2).text() == '22'); ``` -يجب أن يحتوي رابع عنصر `title` على أداة لتلميح (tooltip) بنص `17`. +يجب أن يحتوي رابع عنصر `title` على تلميح بنص `17`. ```js assert($('title').eq(3).text() == '17'); ``` -يجب أن يحتوي خامس عنصر `title` على أداة لتلميح (tooltip) بنص `25`. +يجب أن يحتوي خامس عنصر `title` على تلميح بنص `25`. ```js assert($('title').eq(4).text() == '25'); ``` -يجب أن يحتوي سادس عنصر `title` على أداة لتلميح (tooltip) بنص `18`. +يجب أن يحتوي سادس عنصر `title` على تلميح بنص `18`. ```js assert($('title').eq(5).text() == '18'); ``` -يجب أن يحتوي سابع عنصر `title` على أداة لتلميح (tooltip) بنص `29`. +يجب أن يحتوي سابع عنصر `title` على تلميح بنص `29`. ```js assert($('title').eq(6).text() == '29'); ``` -يجب أن يحتوي ثامن عنصر `title` على أداة لتلميح (tooltip) بنص `14`. +يجب أن يحتوي ثامن عنصر `title` على تلميح بنص `14`. ```js assert($('title').eq(7).text() == '14'); ``` -يجب أن يحتوي تاسع عنصر `title` على أداة لتلميح (tooltip) بنص `9`. +يجب أن يحتوي تاسع عنصر `title` على تلميح بنص `9`. ```js assert($('title').eq(8).text() == '9'); diff --git a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-attributes-to-the-circle-elements.md b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-attributes-to-the-circle-elements.md index 0412b2cc619..42b50539688 100644 --- a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-attributes-to-the-circle-elements.md +++ b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-attributes-to-the-circle-elements.md @@ -8,17 +8,17 @@ dashedName: add-attributes-to-the-circle-elements # --description-- -أنشئت في التحدي السابق عناصر `circle` لكل نقطة في `dataset`، وربطهم بلوحة (canvas) في SVG. لكن D3 يحتاج إلى مزيد من المعلومات حول موضع (position) وحجم (size) كل `circle` لعرضها بشكل صحيح. +أنشئت في التحدي السابق عناصر `circle` لكل نقطة في `dataset`، وأضفتهم بلوحة (canvas) SVG. لكن D3 يحتاج إلى مزيد من المعلومات حول موقع (position) وحجم (size) كل `circle` لعرضها بشكل صحيح. -لدي `circle` في SVG ثلاث سمات (attributes) رئيسية. تكون السمات (attributes) المسمى `cx` و `cy` إحداثيات. ويخبروا D3 أين يضع (position) *مركز (center)* للشكل على اللوحة (canvas) من SVG. تغير سمة (attribute) نصف قطر (radius) (تكتب مثل: `r`) يأثر على حجم `circle`. +لدي `circle` في SVG ثلاث سمات (attributes) رئيسية. تكون السمات (attributes) المسمى `cx` و `cy` إحداثيات. ويخبروا D3 أين تضع *وَسَط (center)* الشكل على لوحة SVG. تحدد سمة نصف قطر (radius) (وتكتب: `r`) حجم الدائرة (`circle`). -مثل مقياس الإحداثيات `y` داخل `rect`، تقاس `cy` داخل `circle` من الجزء العلوي للوحة (canvas) في SVG، ليس من الأسفل. +مثل مقياس الإحداثيات `rect` داخل `y`، تقاس `cy` داخل `circle` من الجزء العلوي للوحة (canvas) SVG، ليس من الأسفل. -تقدر السمات (attributes) الثلاث جميعها استخدام وظيفة تعيد تفعيل (callback function) لتحديد قيمها بشكل ديناميكي. تذكر أن جميع الطرق (methods) مرتبطة بوظيفة `data(dataset)` تفعّل مرة واحدة لكل عنصر في `dataset`. The `d` parameter in the callback function refers to the current item in `dataset`, which is an array for each point. You use bracket notation, like `d[0]`, to access the values in that array. +تستطيع جميع السمات (attributes) الثلاث باستخدام وظيفة لإعادة التفعيل (callback function) لتحديد قيمها بشكل ديناميكي. تذكر أن جميع الطرق (methods) المسلسلة بعد وظيفة `data(dataset)` تفعّل مرة واحدة لكل عنصر في `dataset`. تشير `d` في وظيفة إعادة التفعيل إلى العنصر الحالي في `dataset`، وهو قائمة لكل نقطة. استخدم رمز الأقواس، مثل `d[0]`، للوصول إلى القيم في تلك القائمة. # --instructions-- -Add `cx`, `cy`, and `r` attributes to the `circle` elements. The `cx` value should be the first number in the array for each item in `dataset`. The `cy` value should be based off the second number in the array, but make sure to show the chart right-side-up and not inverted. The `r` value should be `5` for all circles. +أضف السمات `cx` و `cy` و `r` إلى العناصر المسمى `circle`. The `cx` value should be the first number in the array for each item in `dataset`. The `cy` value should be based off the second number in the array, but make sure to show the chart right-side-up and not inverted. The `r` value should be `5` for all circles. # --hints-- diff --git a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-document-elements-with-d3.md b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-document-elements-with-d3.md index 56bcac531c5..2cf381b4368 100644 --- a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-document-elements-with-d3.md +++ b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/add-document-elements-with-d3.md @@ -10,13 +10,13 @@ dashedName: add-document-elements-with-d3 يحتوي D3 على عدة طرق (methods) التي تسمح لك بإضافة وتغيير العناصر في وثيقتك. -تختار طريقة (method) تسمى `select()` عنصراً واحداً من الوثيقة. إنها تأخذ وسيط (argument) كاسم العنصر الذي تريده وتنتج نقطة لتواصل (node) من HTML للعنصر الأول في المستند الذي يطابق الاسم. Here's an example: +تختار طريقة (method) `select()` عنصراً واحداً من الوثيقة. إنها تأخذ اسم العنصر الذي تريده كمعطى (argument)، وتنتج HTML node لأول عنصر في المستند يطابق الاسم. Here's an example: ```js const anchor = d3.select("a"); ``` -يجد المثال السابق أول علامة رابط (anchor) في الصفحة ويحفظ نقطتها لتواصل (node) من HTML في المتغير `anchor`. يمكنك استخدام طريقة الاختيار (select) مع الطرق (methods) أخرى. The `d3` part of the example is a reference to the D3 object, which is how you access D3 methods. +يجد المثال السابق أول علامة رابط (anchor) في الصفحة ويحفظ HTML node لها في المتغير `anchor`. يمكنك استخدام طريقة الاختيار (selection) مع طرق (methods) أخرى. The `d3` part of the example is a reference to the D3 object, which is how you access D3 methods. Two other useful methods are `append()` and `text()`. diff --git a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/change-the-presentation-of-a-bar-chart.md b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/change-the-presentation-of-a-bar-chart.md index dd3fa7ae7aa..28e459bccdf 100644 --- a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/change-the-presentation-of-a-bar-chart.md +++ b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/change-the-presentation-of-a-bar-chart.md @@ -1,6 +1,6 @@ --- id: 587d7fa8367417b2b2512bca -title: تغيير مخطط بياني شريطي (Bar Chart) +title: تغيير التقديم لمخطط الأعمدة (Bar Chart) challengeType: 6 forumTopicId: 301481 dashedName: change-the-presentation-of-a-bar-chart @@ -8,17 +8,17 @@ dashedName: change-the-presentation-of-a-bar-chart # --description-- -وأنشأ التحدي السابق مخطط بياني شريطي (bar chart)، ولكن هناك بعض التغييرات في التنسيق الذي يمكن أن تحسن: +وأنشأ التحدي السابق مخطط أعمدة (bar chart)، ولكن هناك بعض التغييرات في التنسيق الذي يمكن أن تحسنه: -1) إضافة مساحة بين كل شريط لفصله بصرياً، عن طريق إضافة هامش (margin) إلى CSS لفئة تسمى `bar` +1) إضافة مساحة بين كل عمود لفصله بصرياً، عن طريق إضافة هامش (margin) إلى CSS لفئة `bar` -2) زيادة ارتفاع (height) الأشرطة لإظهار الفرق في القيم بشكل أفضل، عن طريق ضرب (multiplying) القيمة في عدد لزيادة الارتفاع +2) زيادة ارتفاع (height) الأعمدة لإظهار الفرق في القيم بشكل أفضل، عن طريق ضرب (multiplying) القيمة في عدد لزيادة الارتفاع # --instructions-- -أولاً، أضف `margin` بقيمة `2px` إلى قئة `bar` في علامة `style`. بعد ذلك، غيّر وظيفة تعيد تفعيل في `style()` بحيث تنتج `10` أضعاف قيمة البيانات الأصلية (بالإضافة إلى `px`). +أولاً، أضف `margin` بقيمة `2px` إلى فئة `bar` في علامة `style`. بعد ذلك، غيّر وظيفة إعادة التفعيل في طريقة `style()` بحيث تنتج `10` أضعاف قيمة البيانات الأصلية (بالإضافة إلى نص `px`). -**ملاحظة:** ضرب كل نقطة بيانات *بنفس* الثابت فقط يغيّر الحجم. إنه مثل تكبير الصورة، الذي لا يغير المقصود من البيانات الأساسية. +**ملاحظة:** ضرب كل نقطة بيانات *بنفس* الرَّقَم الثابت يغيّر المقياس فقط. إنه مثل تكبير الصورة، الذي لا يغير المقصود من البيانات الأساسية. # --hints-- diff --git a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/create-a-linear-scale-with-d3.md b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/create-a-linear-scale-with-d3.md index a5f0ad3b674..8430009c519 100644 --- a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/create-a-linear-scale-with-d3.md +++ b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/create-a-linear-scale-with-d3.md @@ -8,13 +8,13 @@ dashedName: create-a-linear-scale-with-d3 # --description-- -يملأ البيانات المرسومة كل من مخطط بياني شريطي وانسياب (النِّقَاط المبعثرة) على لوحة SVG قاصدًا. بيد أنه إذا كان ارتفاع الشريط أو إحدى نِقَاط البيانات أكبر من الارتفاع أو العرض SVG، ستذهب خارج نطاق SVG. +رَسَم البيانات كلا من المخطوطان, أعمدة وانسياب (النِّقَاط المبعثرة), مباشرا على لوحة SVG. ولكن إذا كان ارتفاع العمود أو إحدى نِقَاط البيانات أكبر من ارتفاع أو عرض مساحة SVG، سيقع خارج نطاق منطقة SVG. -في D3، هناك مقاييس للمساعدة في ملأ البيانات. تكون `scales` وظائف (functions) تخبر البرنامَج كيفية ملأ مجموعة من نِقَاط البيانات الخام على البكسلات (px) في لوحة SVG. +في D3، هناك مقاييس (scales) للمساعدة في تخطيط البيانات. تكون `scales` وظائف (functions) تخبر البرنامَج كيفية ملأ مجموعة من نِقَاط البيانات الخام على عدد البكسلس (pixels) في لوحة SVG. -على سبيل المثال، قل أن لديك لوحة SVG بحجم 100x500، وأنت تريد ملأ الناتج إجمال المحلي (Gross Domestic Product) لعدد من الدول. ومجموعة الأعداد ستكون في حدود المليار أو تريليون دولار. يمكنك توفير D3 من المقياس لمعرفة كيفية وضع قيم الناتج المحلي الإجمالي الكبيرة في تلك المساحة 100x500. +على سبيل المثال، قل أن لديك لوحة SVG بحجم 100x500، وتريد رسم الناتج المحلي الإجمالي (Gross Domestic Product) لعدد من الدول. ومجموعة الأعداد ستكون في حدود المليار أو تريليون دولار. يمكنك توفير D3 من مقياس (scale) لمعرفة كيف توضع قيم الناتج المحلي الإجمالي (GDP) الكبيرة في تلك المساحة 100x500. -من غير المحتمل أن تملأ البيانات الخام كما هي. قبل الملأ، عيّن المقياس لكامل مجموعتك للبيانات. بحيث أن قيم `x` و `y` تتناسب مع عرض وطول اللوحة. +من غير المحتمل أن ترسم البيانات الخام كما هي. قبل الرسم، عيّن المقياس لكامل مجموعة البيانات. بحيث أن قيم `x` و `y` تتناسب مع عرض وطول اللوحة. ولدى D3 عدة أنواع من المقاييس. للحصول على مقياس خطي (linear scale) (يستخدم عادة مع البيانات الكمية (quantitative data))، هناك طريقة في D3 تسمى `scaleLinear()`: @@ -22,11 +22,11 @@ dashedName: create-a-linear-scale-with-d3 const scale = d3.scaleLinear() ``` -بشكل افتراضي، يستخدم المقياس عَلاقة الهُوِيَّة (identity relationship). تطابق قيمة المدخل (Input) قيمة المخرج (output). يشمل تحد أخر كيفية تغيير ذلك. +بشكل افتراضي، يستخدم المقياس عَلاقة الهُوِيَّة (identity relationship). تطابق قيمة المدخل (input) قيمة المخرج (output). يشمل تحدي أخر كيفية تغيير ذلك. # --instructions-- -تغيير متغير (variable) مسمى `scale` لإنشاء مقياس خطي (linear scale). ثم عيّن متغير (variable) مسمى `output` إلى scale, الذي فعلته بالإدخال وسيط بقيمة `50`. +غيّر المتغير `scale` لإنشاء مقياس خطي (linear scale). ثم عيّن متغير `output` إلى وظيفة scale, الذي فعَِلت بإدخال معطى بقيمة `50`. # --hints-- @@ -36,13 +36,13 @@ const scale = d3.scaleLinear() assert($('h2').text() == '50'); ``` -يجب أن يستخدم كودك طريقة (method) تسمى `scaleLinear()`. +يجب أن يستخدم كودك طريقة `scaleLinear()`. ```js assert(code.match(/\.scaleLinear/g)); ``` -يجب أن يُفاعِل متغير `output` وظيفة `scale` مع وسيط `50`. +يجب أن يُفعَِل متغير `output` وظيفة `scale` مع معطى `50`. ```js assert(output == 50 && code.match(/scale\(\s*?50\s*?\)/g)); diff --git a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/dynamically-set-the-coordinates-for-each-bar.md b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/dynamically-set-the-coordinates-for-each-bar.md index 01bdb04afa2..aea916fbe20 100644 --- a/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/dynamically-set-the-coordinates-for-each-bar.md +++ b/curriculum/challenges/arabic/04-data-visualization/data-visualization-with-d3/dynamically-set-the-coordinates-for-each-bar.md @@ -1,6 +1,6 @@ --- id: 587d7fa9367417b2b2512bce -title: تعيين الإحداثيات ديناميكيا لكل شريط +title: تعيين الإحداثيات ديناميكيا لكل عمود challengeType: 6 forumTopicId: 301487 dashedName: dynamically-set-the-coordinates-for-each-bar @@ -8,13 +8,13 @@ dashedName: dynamically-set-the-coordinates-for-each-bar # --description-- -التحدي السابق أنشئت وألحقت مستطيل بعنصر `svg` لكل نقطة في `dataset` لتستعرض شريط. لسوء الحظ، كانوا مكدسين بعضَهم فوق بعض. +The last challenge created and appended a rectangle to the `svg` element for each point in `dataset` to represent a bar. لسوء الحظ، كانوا مكدسين بعضَهم فوق بعض. -يمكنك تحكم في موضع المستطيل بواسطة أستخدام سمات (attributes) تسمى `x` و `y`. لتخبر D3 أين يبدأ في رسم الشكل في منطقة `svg`. قام التحدي السابق بتحديدهم إلى صفر، لذلك تم وضع كل عمود (bar) في الزاوية العلوية اليسرى. +التحكم في موقع المستطيل يكون بواسطة سمات (attributes) `x` و `y`. فانهم يخبرون D3 أين يبدأ في رسم الشكل في منطقة `svg`. قام التحدي السابق بتحديدهم إلى صفر، لذلك تم وضع كل عمود (bar) في الزاوية العلوية اليسرى. -للحصول على مخطط بياني شريطي (bar chart)، يجب أن تجلس جميع الشريط على نفس المستوى العمودي، مما يعني أن قيمة `y` تبقى هي نفسها (عند 0) لجميع الأشرطة. ومع ذلك، تحتاج قيمة `x` إلى التغيير عند إضافة الشريط جديد. تذكر أن أكبر قيم من `x` تدفع العناصر إلى أقصى اليمين. عندما تمر عبر عناصر القائمة في `dataset`، يجب أن تزيد قيمة `x`. +للحصول على مخطط أعمدة (bar chart)، يجب أن تجلس جميع الأعمدة على نفس المستوى العمودي، مما يعني أن قيمة `y` تبقى هي نفسها (عند 0) لجميع الأعمدة. أما قيمة `x` عليها أن نتغير عندما تضيف أعمدة جديدة. تذكر أن القيم الأكبر في `x` تدفع العناصر أبعد إلى اليمين. عندما تمر عبر عناصر القائمة في `dataset`، يجب أن تزيد قيمة `x`. -تقبل طريقة (method) تسمى `attr()` في D3 الوظيفة تعيد تفعيل (callback functon) التي تعيين تلك السمة ديناميكيا. وظيفة تعيد تفعيل (callback functon) تأخذ حجيتين (arguments)، واحد لنقطة البيانات نفسها (عادة `d`) وواحد لترتيب نقطة البيانات في القائمة (array). أما الحِجَّة (argument) الثانية تدل على الترتيب فهي حِجَّة اختيارية. إليك التنسيق: +تقبل طريقة (method) `attr()` في D3 وظيفة إعادة التفعيل (callback functon) التي تعيين تلك السمة ديناميكيا. تأخذ وظيفة إعادة التفعيل (callback functon) معطيين (arguments)، واحد لنقطة البيانات نفسها (عادة `d`) وواحد لرقم ترتيب (index) نقطة البيانات في القائمة (array). أما المعطى (argument) الثاني لرقم الترتيب فهو معطى اختياري. إليك التنسيق: ```js selection.attr("property", (d, i) => { @@ -22,13 +22,13 @@ selection.attr("property", (d, i) => { }) ``` -من المهم مُراعاةٌ أنك لا تحتاج إلى كتابة حلقة (loop) نوعها `for` أو استخدام `forEach()` لتكرار العناصر في مجموعة البيانات (data set). تذكر أن طريقة `data()` تحلل مجموعة البيانات (data set), وأي طريقة تتبع `data()` يتم تشغيلها مرة واحدة لكل عنصر في مجموعة البيانات. +من المهم ملاحظة أنك لن تحتاج إلى كتابة حلقة (loop) نوعها `for` أو `forEach()` لتعيد على العناصر في مجموعة البيانات (data set). تذكر أن طريقة `data()` تحلل مجموعة البيانات (data set), وأي طريقة مسلسة تتبع `data()` سيتم تشغيلها مرة واحدة لكل عنصر في مجموعة البيانات. # --instructions-- -غيّر سمة `x` في وظيفة تعيد تفعيل بحيث ترجع الترتيب 30 مرة. +غيّر وظيفة إعادة التفعيل لسمة `x` بحيث تنتج رَقَم الترتيب 30 مرة. -**ملاحظة:** كل عمود له عرض (width) بقيمة 25، لذا زيادة كل قيمة `x` بمقدار 30 تضيف بعض المساحة بين الأشرطة. أي قيمة أكبر من 25 ستنجح في هذا المثال. +**ملاحظة:** كل عمود له عرض (width) بقيمة 25، لذا زيادة كل قيمة `x` بمقدار 30 ستضيف بعض المساحة بين الأشرطة. أي قيمة أكبر من 25 ستنجح في هذا المثال. # --hints-- diff --git a/curriculum/challenges/arabic/04-data-visualization/json-apis-and-ajax/handle-click-events-with-javascript-using-the-onclick-property.md b/curriculum/challenges/arabic/04-data-visualization/json-apis-and-ajax/handle-click-events-with-javascript-using-the-onclick-property.md index b2bf1b3f2de..479f3844675 100644 --- a/curriculum/challenges/arabic/04-data-visualization/json-apis-and-ajax/handle-click-events-with-javascript-using-the-onclick-property.md +++ b/curriculum/challenges/arabic/04-data-visualization/json-apis-and-ajax/handle-click-events-with-javascript-using-the-onclick-property.md @@ -1,6 +1,6 @@ --- id: 587d7fad367417b2b2512be1 -title: التعامل مع أحداث النقر (Handle Click Events) مع JavaScript باستخدام خاصية عند النقر (onclick) +title: التعامل مع أحداث النقر (Handle Click Events) مع JavaScript باستخدام خاصية onclick عند النقر challengeType: 6 forumTopicId: 301503 dashedName: handle-click-events-with-javascript-using-the-onclick-property @@ -8,7 +8,7 @@ dashedName: handle-click-events-with-javascript-using-the-onclick-property # --description-- -تريد تنفذ كودك بمجرد الانتهاء من تحميل الصفحة, مرة واحدة فقط. لهذا الغرض، يمكنك إرفاق حدث (event) من JavaScript لمستند مسمى `DOMContentLoaded`. إليك كود الذي يفعل ذلك: +تريد أن ينفذ كودك فقط عندما ينتهي تحميل الصفحة. لهذا الغرض، يمكنك إرفاق حدث (event) من JavaScript لمستند مسمى `DOMContentLoaded`. إليك الكود الذي يفعل ذلك: ```js document.addEventListener('DOMContentLoaded', function() { @@ -24,11 +24,11 @@ document.getElementById('getMessage').onclick = function(){}; # --instructions-- -أضف معالج أحداث النقر داخل وظيفة `DOMContentLoaded` للعنصر مع معرف `getMessage`. +أضف معالج الأحداث (event handler) داخل وظيفة `DOMContentLoaded` للعنصر الذي له id قيمته `getMessage`. # --hints-- -يجب أن تستخدم كودك طريقة `document.getElementById` لتحديد عنصر `getMessage`. +يجب أن يستخدم كودك طريقة `document.getElementById` لتحديد عنصر `getMessage`. ```js assert(code.match(/document\s*\.getElementById\(\s*?('|")getMessage\1\s*?\)/g)); diff --git a/curriculum/challenges/arabic/07-scientific-computing-with-python/scientific-computing-with-python-projects/budget-app.md b/curriculum/challenges/arabic/07-scientific-computing-with-python/scientific-computing-with-python-projects/budget-app.md index 85a86b395a9..a233fad342e 100644 --- a/curriculum/challenges/arabic/07-scientific-computing-with-python/scientific-computing-with-python-projects/budget-app.md +++ b/curriculum/challenges/arabic/07-scientific-computing-with-python/scientific-computing-with-python-projects/budget-app.md @@ -42,9 +42,9 @@ Transfer to Clothing -50.00 Total: 923.96 ``` -Besides the `Category` class, create a function (outside of the class) called `create_spend_chart` that takes a list of categories as an argument. يجب أن تنتج مخطط بياني للأعمدة بصيغة string. +Besides the `Category` class, create a function (outside of the class) called `create_spend_chart` that takes a list of categories as an argument. يجب أن تنتج مخطط أعمدة بصيغة string. -The chart should show the percentage spent in each category passed in to the function. The percentage spent should be calculated only with withdrawals and not with deposits. Down the left side of the chart should be labels 0 - 100. كل عمود من مخطط بياني للأعمدة يتكون من رمز " o". The height of each bar should be rounded down to the nearest 10. The horizontal line below the bars should go two spaces past the final bar. Each category name should be written vertically below the bar. There should be a title at the top that says "Percentage spent by category". +The chart should show the percentage spent in each category passed in to the function. The percentage spent should be calculated only with withdrawals and not with deposits. Down the left side of the chart should be labels 0 - 100. يتكون كل عمود من مخطط ألأعمدة من الحرف " o". The height of each bar should be rounded down to the nearest 10. The horizontal line below the bars should go two spaces past the final bar. Each category name should be written vertically below the bar. There should be a title at the top that says "Percentage spent by category". This function will be tested with up to four categories. diff --git a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5dfa30b9eacea3f48c6300ad.md b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5dfa30b9eacea3f48c6300ad.md index dc1c868f2ae..07e756d1788 100644 --- a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5dfa30b9eacea3f48c6300ad.md +++ b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5dfa30b9eacea3f48c6300ad.md @@ -7,13 +7,13 @@ dashedName: step-15 # --description-- -In previous steps you used an anchor element to turn text into a link. Other types of content can also be turned into a link by wrapping it in anchor tags. +في الخطوات السابقة كنت تستخدم عنصر الرابط لتحويل النص إلى رابط. ويمكن أيضا تحويل أنواع أخرى من المحتوى إلى رابط عن طريق وضعه بداخل عنصر الرابط. -Turn the image into a link by surrounding it with necessary element tags. Use `https://freecatphotoapp.com` as the anchor's `href` attribute value. +حول الصورة إلى رابط عن طريق إحاطتها بعلامات العنصر المناسب. استخدم `https://freecatphotoapp.com` كقيمة السمة `href`. # --hints-- -You should have an `img` element with an `src` value of `https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg`. You may have accidentally deleted it. +يجب أن يكون لديك عنصر `img` بقيمة `src` من `https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg`. ربما حذفتها عن طريق الخطأ. ```js assert( @@ -23,37 +23,37 @@ assert( ); ``` -Your anchor (`a`) element should have an opening tag. Opening tags have this syntax: ``. +يجب أن يحتوي العنصر (`a`) على علامة الفتح. تكتب علامات الفتح هكذا: ``. ```js assert(document.querySelectorAll('a').length >= 2); ``` -You should only add one opening anchor (`a`) tag. Please remove any extras. +يجب عليك إضافة علامة فتح واحد فقط لـ (`a`). الرجاء إزالة أي زيادات. ```js assert(document.querySelectorAll('a').length === 2); ``` -Your anchor (`a`) element should have a closing tag. Closing tags have a `/` just after the `<` character. +يجب أن يحتوي العنصر (`a`) على علامة غلق. يجب أن تأتي علامات الغلق بـ `/` بعد رمز `<` مباشرة. ```js assert(code.match(/<\/a>/g).length >= 2); ``` -You should only add one closing anchor (`a`) tag. Please remove any extras. +يجب عليك إضافة علامة غلق واحد فقط لـ (`a`). الرجاء إزالة أي زيادات. ```js assert(code.match(/<\/a>/g).length === 2); ``` -Your anchor (`a`) element does not have an `href` attribute. Check that there is a space after the opening tag's name and/or there are spaces before all attribute names. +ليس لدي عنصرك (`a`) سمة `href`. تيقن من وجود مسافة بعد اسم علامة الفتح و/أو أن هناك مسافات قبل جميع أسماء السمات. ```js assert(document.querySelector('a').hasAttribute('href')); ``` -Your anchor (`a`) element should link to `https://freecatphotoapp.com`. You have either omitted the URL or have a typo. +يجب أن يربط عنصرك (`a`) إلى `https://freecatphotoapp.com`. إما أنك حذفت الرابط (URL) أو لديك خطأ إملائي. ```js assert( @@ -62,7 +62,7 @@ assert( ); ``` -Your `img` element should be nested within the anchor (`a`) element. The entire `img` element should be inside the opening and closing tags of the anchor (`a`) element. +يجب أن يدخل عنصرك `img` داخل عنصر الرابط (`a`). يجب أن يكون عنصر `img` كله داخل العلامات فتح و العلامات غلق لعنصر الرابط (`a`). ```js assert(document.querySelector('img').parentNode.nodeName === 'A'); diff --git a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5ef9b03c81a63668521804d2.md b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5ef9b03c81a63668521804d2.md index d55f677627d..e43c148527e 100644 --- a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5ef9b03c81a63668521804d2.md +++ b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5ef9b03c81a63668521804d2.md @@ -55,7 +55,7 @@ assert.deepStrictEqual( ); ``` -You should only have one `ol` element. +يجب أن يكون لديك عنصر `ol` واحد فقط. ```js assert([...document.querySelectorAll('ol')].length == 1); diff --git a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5ef9b03c81a63668521804e5.md b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5ef9b03c81a63668521804e5.md index d8ddc5519a8..f648de78ef9 100644 --- a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5ef9b03c81a63668521804e5.md +++ b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5ef9b03c81a63668521804e5.md @@ -22,7 +22,7 @@ assert( ); ``` -Your first radio button, with the `id` set to `indoor`, should have the `checked` attribute. +زرك الراديو الأول، مع `id` بقيمة `indoor`، يجب أن يكون `checked`. ```js assert($('input[type="radio"]')[0].hasAttribute('checked')); diff --git a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5efc4f528d6a74d05e68af74.md b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5efc4f528d6a74d05e68af74.md index d36e2c1257a..7042c4ca854 100644 --- a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5efc4f528d6a74d05e68af74.md +++ b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5efc4f528d6a74d05e68af74.md @@ -9,7 +9,7 @@ dashedName: step-56 هناك طريقة أخرى لربط نص عنصر `input` بالعنصر نفسه. يمكنك دمج النص ضمن عنصر `label` وإضافة سمة `for` بنفس القيمة كسمة `id` لعنصر `input`. -Associate the text `Loving` with the checkbox by nesting only the text `Loving` in a `label` element and giving it an appropriate `for` attribute. +اربط النص `Loving` مع checkbox بواسطة إدخال النص `Loving` فقط في عنصر `label` وإعطائه السمة `for`. # --hints-- diff --git a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md index 984e967ff1c..47fae3090d3 100644 --- a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md +++ b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md @@ -7,7 +7,7 @@ dashedName: step-43 # --description-- -بعد آخر عنصر `.divider`، أنشئ عنصر `p` وأعطيه النص `Total Fat 8g 10%`. قم بتغليف `Total Fat` في `span` مع `class` تم تعيينه إلى `bold`. غلف `10%` داخل عنصر `span` مع `class` بقيمة `bold`. أخيراً، أدخل عنصر `span` الذي يحتوي على نص `Total Fat` مع نص `8g` في عنصر `span` إضافي للمحاذاة. +بعد آخر عنصر `.divider`، أنشئ عنصر `p` وأعطيه النص `Total Fat 8g 10%`. Wrap the text `Total Fat` in a `span` element with the `class` of `bold`. Wrap the text `10%` in another `span` element with the `class` of `bold`. أخيراً، أدخل عنصر `span` الذي يحتوي على نص `Total Fat` مع نص `8g` في عنصر `span` إضافي للمحاذاة. # --hints-- diff --git a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f7de4487b64919bb4aa5e.md b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f7de4487b64919bb4aa5e.md index bbafe28bd97..dc8634ecfa5 100644 --- a/curriculum/challenges/arabic/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f7de4487b64919bb4aa5e.md +++ b/curriculum/challenges/arabic/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f7de4487b64919bb4aa5e.md @@ -7,7 +7,7 @@ dashedName: step-53 # --description-- -بعد آخر `.divider`، قم بإنشاء عنصر `p` آخر مع النص `Cholesterol 0mg 0%`. قم بتغليف النص `Cholesterol` في عنصر `span`، واعطي عنصر `span` هذا السمة `class` بقيمة `bold`. غلف نص `0%` داخل `span` أخرى، مع `class` بقيمة `bold`. Finally, nest the `Cholesterol` and `0mg` `span` elements inside an additional `span` element for alignment. +بعد آخر `.divider`، قم بإنشاء عنصر `p` آخر مع النص `Cholesterol 0mg 0%`. قم بتغليف النص `Cholesterol` في عنصر `span`، واعطي عنصر `span` هذا السمة `class` بقيمة `bold`. غلف نص `0%` داخل `span` أخرى، مع `class` بقيمة `bold`. أخيرا، أدخل عناصر `span` التي تحتوي على `Cholesterol` و `0mg` داخل عنصر `span` إضافي للمحاذاة. # --hints-- diff --git a/curriculum/challenges/chinese-traditional/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md b/curriculum/challenges/chinese-traditional/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md index fc6a81f51f8..980f0672685 100644 --- a/curriculum/challenges/chinese-traditional/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md +++ b/curriculum/challenges/chinese-traditional/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md @@ -7,7 +7,7 @@ dashedName: step-43 # --description-- -在最後一個 `.divider` 元素之後,創建一個 `p` 元素併爲其指定文本 `Total Fat 8g 10%`。 將 `Total Fat` 包裹在 `span` 元素中,並將 `class` 設置爲 `bold`。 將 `10%` 包裹在另一個 `span` 元素中,並將 `class` 設置爲 `bold`。 最後:嵌套 `Total Fat` `span` 元素和文本 `8g` 在另一個 `span` 元素中,以實現對齊。 +在最後一個 `.divider` 元素之後,創建一個 `p` 元素併爲其指定文本 `Total Fat 8g 10%`。 Wrap the text `Total Fat` in a `span` element with the `class` of `bold`. Wrap the text `10%` in another `span` element with the `class` of `bold`. 最後:嵌套 `Total Fat` `span` 元素和文本 `8g` 在另一個 `span` 元素中,以實現對齊。 # --hints-- diff --git a/curriculum/challenges/chinese/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md b/curriculum/challenges/chinese/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md index 3c5555b0ce1..c1c455124b8 100644 --- a/curriculum/challenges/chinese/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md +++ b/curriculum/challenges/chinese/14-responsive-web-design-22/learn-typography-by-building-a-nutrition-label/615f6b2d164f81809efd9bdc.md @@ -7,7 +7,7 @@ dashedName: step-43 # --description-- -在最后一个 `.divider` 元素之后,创建一个 `p` 元素并为其指定文本 `Total Fat 8g 10%`。 将 `Total Fat` 包裹在 `span` 元素中,并将 `class` 设置为 `bold`。 将 `10%` 包裹在另一个 `span` 元素中,并将 `class` 设置为 `bold`。 最后:嵌套 `Total Fat` `span` 元素和文本 `8g` 在另一个 `span` 元素中,以实现对齐。 +在最后一个 `.divider` 元素之后,创建一个 `p` 元素并为其指定文本 `Total Fat 8g 10%`。 Wrap the text `Total Fat` in a `span` element with the `class` of `bold`. Wrap the text `10%` in another `span` element with the `class` of `bold`. 最后:嵌套 `Total Fat` `span` 元素和文本 `8g` 在另一个 `span` 元素中,以实现对齐。 # --hints-- diff --git a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/escape-sequences-in-strings.md b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/escape-sequences-in-strings.md index b41d20aed01..2a932a7708f 100644 --- a/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/escape-sequences-in-strings.md +++ b/curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/escape-sequences-in-strings.md @@ -9,12 +9,7 @@ dashedName: escape-sequences-in-strings # --description-- -Quotes are not the only characters that can be escaped inside a string. There are two reasons to use escaping characters: - -1. To allow you to use characters you may not otherwise be able to type out, such as a newline. -2. To allow you to represent multiple quotes in a string without JavaScript misinterpreting what you mean. - -We learned this in the previous challenge. +Quotes are not the only characters that can be escaped inside a string. Escape sequences allow you to use characters you may not otherwise be able to use in a string.
CodeOutput
\'single quote
\"double quote
\\backslash
\nnewline
\ttab
\rcarriage return
\bword boundary
\fform feed
diff --git a/curriculum/challenges/english/04-data-visualization/data-visualization-projects/visualize-data-with-a-bar-chart.md b/curriculum/challenges/english/04-data-visualization/data-visualization-projects/visualize-data-with-a-bar-chart.md index 4e648ec9b72..a86369073dd 100644 --- a/curriculum/challenges/english/04-data-visualization/data-visualization-projects/visualize-data-with-a-bar-chart.md +++ b/curriculum/challenges/english/04-data-visualization/data-visualization-projects/visualize-data-with-a-bar-chart.md @@ -24,17 +24,17 @@ You can use HTML, JavaScript, CSS, and the D3 svg-based visualization library. T **User Story #5:** My chart should have a `rect` element for each data point with a corresponding `class="bar"` displaying the data. -**User Story #6:** Each bar should have the properties `data-date` and `data-gdp` containing `date` and `GDP` values. +**User Story #6:** Each `.bar` should have the properties `data-date` and `data-gdp` containing `date` and `GDP` values. -**User Story #7:** The bar elements' `data-date` properties should match the order of the provided data. +**User Story #7:** The `.bar` elements' `data-date` properties should match the order of the provided data. -**User Story #8:** The bar elements' `data-gdp` properties should match the order of the provided data. +**User Story #8:** The `.bar` elements' `data-gdp` properties should match the order of the provided data. -**User Story #9:** Each bar element's height should accurately represent the data's corresponding `GDP`. +**User Story #9:** Each `.bar` element's height should accurately represent the data's corresponding `GDP`. -**User Story #10:** The `data-date` attribute and its corresponding bar element should align with the corresponding value on the x-axis. +**User Story #10:** The `data-date` attribute and its corresponding `.bar` element should align with the corresponding value on the x-axis. -**User Story #11:** The `data-gdp` attribute and its corresponding bar element should align with the corresponding value on the y-axis. +**User Story #11:** The `data-gdp` attribute and its corresponding `.bar` element should align with the corresponding value on the y-axis. **User Story #12:** I can mouse over an area and see a tooltip with a corresponding `id="tooltip"` which displays more information about the area. diff --git a/curriculum/challenges/english/04-data-visualization/data-visualization-with-d3/add-a-tooltip-to-a-d3-element.md b/curriculum/challenges/english/04-data-visualization/data-visualization-with-d3/add-a-tooltip-to-a-d3-element.md index 28ee20400f9..e16937cbff9 100644 --- a/curriculum/challenges/english/04-data-visualization/data-visualization-with-d3/add-a-tooltip-to-a-d3-element.md +++ b/curriculum/challenges/english/04-data-visualization/data-visualization-with-d3/add-a-tooltip-to-a-d3-element.md @@ -8,7 +8,7 @@ dashedName: add-a-tooltip-to-a-d3-element # --description-- -A tooltip shows more information about an item on a page when the user hovers over that item. There are several ways to add a tooltip to a visualization, this challenge uses the SVG `title` element. +A tooltip shows more information about an item on a page when the user hovers over that item. There are several ways to add a tooltip to a visualization. This challenge uses the SVG `title` element. `title` pairs with the `text()` method to dynamically add data to the bars. diff --git a/curriculum/challenges/english/04-data-visualization/data-visualization-with-d3/change-styles-based-on-data.md b/curriculum/challenges/english/04-data-visualization/data-visualization-with-d3/change-styles-based-on-data.md index 1ed6ae7d5e2..8e2005e381f 100644 --- a/curriculum/challenges/english/04-data-visualization/data-visualization-with-d3/change-styles-based-on-data.md +++ b/curriculum/challenges/english/04-data-visualization/data-visualization-with-d3/change-styles-based-on-data.md @@ -8,8 +8,7 @@ dashedName: change-styles-based-on-data # --description-- -D3 is about visualization and presentation of data. It's likely you'll want to change the styling of elements based on the data. You can use a callback function in the `style()` method to change the styling for different elements. - +D3 is about visualization and presentation of data. It's likely you'll want to change the styling of elements based on the data. For example, you may want to color a data point blue if it has a value less than 20, and red otherwise. You can use a callback function in the `style()` method and include the conditional logic. The callback function uses the `d` parameter to represent the data point: ```js diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication-iii.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication-iii.md index aef68751990..10be5ceeb48 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication-iii.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication-iii.md @@ -61,7 +61,7 @@ async (getUserInput) => { ); assert.match( data, - /GitHubStrategy[^]*return cb/gi, + /GitHubStrategy[^]*cb/gi, 'Strategy should return the callback function "cb"' ); } diff --git a/curriculum/challenges/english/14-responsive-web-design-22/learn-accessibility-by-building-a-quiz/614202874ca576084fca625f.md b/curriculum/challenges/english/14-responsive-web-design-22/learn-accessibility-by-building-a-quiz/614202874ca576084fca625f.md index 98425cf93a9..1c61c6f1a5b 100644 --- a/curriculum/challenges/english/14-responsive-web-design-22/learn-accessibility-by-building-a-quiz/614202874ca576084fca625f.md +++ b/curriculum/challenges/english/14-responsive-web-design-22/learn-accessibility-by-building-a-quiz/614202874ca576084fca625f.md @@ -7,9 +7,9 @@ dashedName: step-16 # --description-- -Every `region` role requires a visible label, which should be referenced by the `aria-labelledby` attribute. +Every `region` role requires a label, which helps screen reader users understand the purpose of the region. One method for adding a label is to add a heading element inside the region and then reference it with the `aria-labelledby` attribute. -To the `section` elements, give the following `aria-labelledby` attributes: +Add the following `aria-labelledby` attributes to the `section` elements: - `student-info` - `html-questions` diff --git a/curriculum/challenges/english/14-responsive-web-design-22/learn-html-forms-by-building-a-registration-form/60fad1cafcde010995e15306.md b/curriculum/challenges/english/14-responsive-web-design-22/learn-html-forms-by-building-a-registration-form/60fad1cafcde010995e15306.md index 0f68b139a27..ec598b5f119 100644 --- a/curriculum/challenges/english/14-responsive-web-design-22/learn-html-forms-by-building-a-registration-form/60fad1cafcde010995e15306.md +++ b/curriculum/challenges/english/14-responsive-web-design-22/learn-html-forms-by-building-a-registration-form/60fad1cafcde010995e15306.md @@ -103,23 +103,23 @@ You should not give any of the `fieldset` elements a `name` attribute. --fcc-editable-region--
- - - - + + + +