diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts
index fd3dc446b1..bb452ccbd9 100644
--- a/client/src/redux/prop-types.ts
+++ b/client/src/redux/prop-types.ts
@@ -183,6 +183,17 @@ type Required = {
src: string;
crossDomain?: boolean;
};
+export interface BilibiliIds {
+ aid: string;
+ bvid: string;
+ cid: string;
+}
+
+export interface VideoLocaleIds {
+ espanol?: string;
+ italian?: string;
+ portuguese?: string;
+}
export type ChallengeNodeType = {
block: string;
@@ -213,6 +224,8 @@ export type ChallengeNodeType = {
translationPending: boolean;
url: string;
videoId: string;
+ videoLocaleIds?: VideoLocaleIds;
+ bilibiliIds?: BilibiliIds;
videoUrl: string;
};
diff --git a/client/src/templates/Challenges/components/VideoPlayer.tsx b/client/src/templates/Challenges/components/VideoPlayer.tsx
new file mode 100644
index 0000000000..498671db3f
--- /dev/null
+++ b/client/src/templates/Challenges/components/VideoPlayer.tsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import YouTube from 'react-youtube';
+import envData from '../../../../../config/env.json';
+import type { BilibiliIds, VideoLocaleIds } from '../../../redux/prop-types';
+
+// TODO: pull these types from all-langs
+const { clientLocale } = envData as {
+ clientLocale:
+ | 'english'
+ | 'chinese'
+ | 'chinese-traditional'
+ | 'espanol'
+ | 'italian'
+ | 'portuguese';
+};
+
+interface VideoPlayerProps {
+ videoId: string;
+ videoLocaleIds?: VideoLocaleIds;
+ onVideoLoad: () => void;
+ videoIsLoaded: boolean;
+ bilibiliIds?: BilibiliIds;
+ title: string;
+}
+
+function VideoPlayer({
+ videoId,
+ videoLocaleIds,
+ onVideoLoad,
+ videoIsLoaded,
+ bilibiliIds,
+ title
+}: VideoPlayerProps): JSX.Element {
+ let bilibiliSrc = null;
+
+ if (
+ bilibiliIds &&
+ ['chinese', 'chinese-traditional'].includes(clientLocale)
+ ) {
+ const { aid, bvid, cid } = bilibiliIds;
+ bilibiliSrc = `//player.bilibili.com/player.html?aid=${aid}&bvid=${bvid}&cid=${cid}`;
+ }
+
+ if (videoLocaleIds) {
+ const localeId = videoLocaleIds[clientLocale as keyof VideoLocaleIds];
+ videoId = localeId || videoId;
+ }
+
+ return (
+ <>
+ {bilibiliSrc ? (
+
+ ) : (
+
+ )}
+ >
+ );
+}
+
+export default VideoPlayer;
diff --git a/client/src/templates/Challenges/video/Show.tsx b/client/src/templates/Challenges/video/Show.tsx
index be654f303d..2990c5a5a3 100644
--- a/client/src/templates/Challenges/video/Show.tsx
+++ b/client/src/templates/Challenges/video/Show.tsx
@@ -6,7 +6,6 @@ import Helmet from 'react-helmet';
import { ObserveKeys } from 'react-hotkeys';
import { TFunction, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
-import YouTube from 'react-youtube';
import { bindActionCreators } from 'redux';
import type { Dispatch } from 'redux';
import { createSelector } from 'reselect';
@@ -21,6 +20,7 @@ import {
} from '../../../redux/prop-types';
import ChallengeDescription from '../components/Challenge-Description';
import Hotkeys from '../components/Hotkeys';
+import VideoPlayer from '../components/VideoPlayer';
import ChallengeTitle from '../components/challenge-title';
import CompletionModal from '../components/completion-modal';
import PrismFormatted from '../components/prism-formatted';
@@ -162,7 +162,7 @@ class ShowVideo extends Component {
});
};
- videoIsReady = () => {
+ onVideoLoad = () => {
this.setState({
videoIsLoaded: true
});
@@ -179,6 +179,8 @@ class ShowVideo extends Component {
block,
translationPending,
videoId,
+ videoLocaleIds,
+ bilibiliIds,
question: { text, answers, solution }
}
},
@@ -223,22 +225,13 @@ class ShowVideo extends Component {
) : null}
-
@@ -323,6 +316,16 @@ export const query = graphql`
query VideoChallenge($slug: String!) {
challengeNode(fields: { slug: { eq: $slug } }) {
videoId
+ videoLocaleIds {
+ espanol
+ italian
+ portuguese
+ }
+ bilibiliIds {
+ aid
+ bvid
+ cid
+ }
title
description
challengeType
diff --git a/curriculum/challenges/english/07-scientific-computing-with-python/python-for-everybody/introduction-why-program.md b/curriculum/challenges/english/07-scientific-computing-with-python/python-for-everybody/introduction-why-program.md
index f1549dbb07..ea412476e3 100644
--- a/curriculum/challenges/english/07-scientific-computing-with-python/python-for-everybody/introduction-why-program.md
+++ b/curriculum/challenges/english/07-scientific-computing-with-python/python-for-everybody/introduction-why-program.md
@@ -3,6 +3,14 @@ id: 5e6a54a558d3af90110a60a0
title: 'Introduction: Why Program?'
challengeType: 11
videoId: 3muQV-Im3Z0
+bilibiliIds:
+ aid: 206882253
+ bvid: BV1Fh411z7tr
+ cid: 376314257
+videoLocaleIds:
+ espanol: 3muQV-Im3Z0
+ italian: 3muQV-Im3Z0
+ portuguese: 3muQV-Im3Z0
dashedName: introduction-why-program
---
diff --git a/curriculum/schema/challengeSchema.js b/curriculum/schema/challengeSchema.js
index 157c2fff35..ff9d2de869 100644
--- a/curriculum/schema/challengeSchema.js
+++ b/curriculum/schema/challengeSchema.js
@@ -58,6 +58,22 @@ const schema = Joi.object()
is: challengeTypes.video,
then: Joi.string().required()
}),
+ videoLocaleIds: Joi.when('challengeType', {
+ is: challengeTypes.video,
+ then: Joi.object().keys({
+ espanol: Joi.string(),
+ italian: Joi.string(),
+ portuguese: Joi.string()
+ })
+ }),
+ bilibiliIds: Joi.when('challengeType', {
+ is: challengeTypes.video,
+ then: Joi.object().keys({
+ aid: Joi.number().required(),
+ bvid: Joi.string().required(),
+ cid: Joi.number().required()
+ })
+ }),
question: Joi.object().keys({
text: Joi.string().required(),
answers: Joi.array().items(Joi.string()).required(),