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:
committed by
GitHub
parent
48f2c02c5d
commit
2b6bef08ae
@ -183,6 +183,17 @@ type Required = {
|
|||||||
src: string;
|
src: string;
|
||||||
crossDomain?: boolean;
|
crossDomain?: boolean;
|
||||||
};
|
};
|
||||||
|
export interface BilibiliIds {
|
||||||
|
aid: string;
|
||||||
|
bvid: string;
|
||||||
|
cid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VideoLocaleIds {
|
||||||
|
espanol?: string;
|
||||||
|
italian?: string;
|
||||||
|
portuguese?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type ChallengeNodeType = {
|
export type ChallengeNodeType = {
|
||||||
block: string;
|
block: string;
|
||||||
@ -213,6 +224,8 @@ export type ChallengeNodeType = {
|
|||||||
translationPending: boolean;
|
translationPending: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
videoId: string;
|
videoId: string;
|
||||||
|
videoLocaleIds?: VideoLocaleIds;
|
||||||
|
bilibiliIds?: BilibiliIds;
|
||||||
videoUrl: string;
|
videoUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
78
client/src/templates/Challenges/components/VideoPlayer.tsx
Normal file
78
client/src/templates/Challenges/components/VideoPlayer.tsx
Normal 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;
|
@ -6,7 +6,6 @@ import Helmet from 'react-helmet';
|
|||||||
import { ObserveKeys } from 'react-hotkeys';
|
import { ObserveKeys } from 'react-hotkeys';
|
||||||
import { TFunction, withTranslation } from 'react-i18next';
|
import { TFunction, withTranslation } from 'react-i18next';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import YouTube from 'react-youtube';
|
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
@ -21,6 +20,7 @@ import {
|
|||||||
} from '../../../redux/prop-types';
|
} from '../../../redux/prop-types';
|
||||||
import ChallengeDescription from '../components/Challenge-Description';
|
import ChallengeDescription from '../components/Challenge-Description';
|
||||||
import Hotkeys from '../components/Hotkeys';
|
import Hotkeys from '../components/Hotkeys';
|
||||||
|
import VideoPlayer from '../components/VideoPlayer';
|
||||||
import ChallengeTitle from '../components/challenge-title';
|
import ChallengeTitle from '../components/challenge-title';
|
||||||
import CompletionModal from '../components/completion-modal';
|
import CompletionModal from '../components/completion-modal';
|
||||||
import PrismFormatted from '../components/prism-formatted';
|
import PrismFormatted from '../components/prism-formatted';
|
||||||
@ -162,7 +162,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
videoIsReady = () => {
|
onVideoLoad = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
videoIsLoaded: true
|
videoIsLoaded: true
|
||||||
});
|
});
|
||||||
@ -179,6 +179,8 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
|||||||
block,
|
block,
|
||||||
translationPending,
|
translationPending,
|
||||||
videoId,
|
videoId,
|
||||||
|
videoLocaleIds,
|
||||||
|
bilibiliIds,
|
||||||
question: { text, answers, solution }
|
question: { text, answers, solution }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -223,22 +225,13 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
|||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<YouTube
|
<VideoPlayer
|
||||||
className={
|
bilibiliIds={bilibiliIds}
|
||||||
this.state.videoIsLoaded
|
onVideoLoad={this.onVideoLoad}
|
||||||
? 'display-youtube-video'
|
title={title}
|
||||||
: 'hide-youtube-video'
|
|
||||||
}
|
|
||||||
onReady={this.videoIsReady}
|
|
||||||
opts={{
|
|
||||||
playerVars: {
|
|
||||||
rel: 0
|
|
||||||
},
|
|
||||||
width: 'auto',
|
|
||||||
height: 'auto'
|
|
||||||
}}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
videoId={videoId}
|
videoId={videoId}
|
||||||
|
videoIsLoaded={this.state.videoIsLoaded}
|
||||||
|
videoLocaleIds={videoLocaleIds}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
@ -323,6 +316,16 @@ export const query = graphql`
|
|||||||
query VideoChallenge($slug: String!) {
|
query VideoChallenge($slug: String!) {
|
||||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||||
videoId
|
videoId
|
||||||
|
videoLocaleIds {
|
||||||
|
espanol
|
||||||
|
italian
|
||||||
|
portuguese
|
||||||
|
}
|
||||||
|
bilibiliIds {
|
||||||
|
aid
|
||||||
|
bvid
|
||||||
|
cid
|
||||||
|
}
|
||||||
title
|
title
|
||||||
description
|
description
|
||||||
challengeType
|
challengeType
|
||||||
|
@ -3,6 +3,14 @@ id: 5e6a54a558d3af90110a60a0
|
|||||||
title: 'Introduction: Why Program?'
|
title: 'Introduction: Why Program?'
|
||||||
challengeType: 11
|
challengeType: 11
|
||||||
videoId: 3muQV-Im3Z0
|
videoId: 3muQV-Im3Z0
|
||||||
|
bilibiliIds:
|
||||||
|
aid: 206882253
|
||||||
|
bvid: BV1Fh411z7tr
|
||||||
|
cid: 376314257
|
||||||
|
videoLocaleIds:
|
||||||
|
espanol: 3muQV-Im3Z0
|
||||||
|
italian: 3muQV-Im3Z0
|
||||||
|
portuguese: 3muQV-Im3Z0
|
||||||
dashedName: introduction-why-program
|
dashedName: introduction-why-program
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -58,6 +58,22 @@ const schema = Joi.object()
|
|||||||
is: challengeTypes.video,
|
is: challengeTypes.video,
|
||||||
then: Joi.string().required()
|
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({
|
question: Joi.object().keys({
|
||||||
text: Joi.string().required(),
|
text: Joi.string().required(),
|
||||||
answers: Joi.array().items(Joi.string()).required(),
|
answers: Joi.array().items(Joi.string()).required(),
|
||||||
|
Reference in New Issue
Block a user