feat: localize videos (#42869)

* refactor: separate out VideoPlayer component

* feat: support bilibili videos

* feat(client): allow localized videos to be shown

* fix: remove add subtitles CTA

* feat: add locale ids for Why Program?
This commit is contained in:
Oliver Eyton-Williams
2021-09-27 11:26:38 +02:00
committed by GitHub
parent 48f2c02c5d
commit 2b6bef08ae
5 changed files with 135 additions and 17 deletions

View File

@ -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;
};

View File

@ -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 ? (
<iframe
frameBorder='no'
scrolling='no'
src={bilibiliSrc}
title={title}
/>
) : (
<YouTube
className={
videoIsLoaded ? 'display-youtube-video' : 'hide-youtube-video'
}
onReady={onVideoLoad}
opts={{
playerVars: {
rel: 0
},
width: 'auto',
height: 'auto'
}}
videoId={videoId}
/>
)}
</>
);
}
export default VideoPlayer;

View File

@ -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<ShowVideoProps, ShowVideoState> {
});
};
videoIsReady = () => {
onVideoLoad = () => {
this.setState({
videoIsLoaded: true
});
@ -179,6 +179,8 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
block,
translationPending,
videoId,
videoLocaleIds,
bilibiliIds,
question: { text, answers, solution }
}
},
@ -223,22 +225,13 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
<Loader />
</div>
) : null}
<YouTube
className={
this.state.videoIsLoaded
? 'display-youtube-video'
: 'hide-youtube-video'
}
onReady={this.videoIsReady}
opts={{
playerVars: {
rel: 0
},
width: 'auto',
height: 'auto'
}}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
<VideoPlayer
bilibiliIds={bilibiliIds}
onVideoLoad={this.onVideoLoad}
title={title}
videoId={videoId}
videoIsLoaded={this.state.videoIsLoaded}
videoLocaleIds={videoLocaleIds}
/>
</div>
</Col>
@ -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

View File

@ -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
---

View File

@ -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(),