Skip to content

Commit a2ece5f

Browse files
committed
i18n
1 parent 6276dc9 commit a2ece5f

File tree

12 files changed

+76
-22
lines changed

12 files changed

+76
-22
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# react-html5video
22

3-
A customizeable HoC (Higher Order Component) for HTML5 Video that allows custom and configurable controls.
3+
A customizeable HoC (Higher Order Component) for HTML5 Video that allows custom and configurable controls with i18n and a11y.
44

55
V2 API has changed and is not backwards compatible. You can find the old documentation [here](https://github.com/mderrick/react-html5video/blob/master/README.md).
66

@@ -45,6 +45,20 @@ render() {
4545
}
4646
```
4747

48+
#### a11y* and i18n
49+
50+
The custom controls provided are built using `<button>` and `<input type="range">` which means basic keyboard controls are available when they are focused. For example, you can and hit the space bar on mute, play and fullscreen buttons as well as seek using the arrow keys when focused on the slider. `aria-label` attributes for screen readers have been used where user interaction is required. Try tabbing through the [demo](http://mderrick.github.io/react-html5video/) with [Vox](http://www.chromevox.com/) enabled.
51+
52+
You can change translate the `aria-label` values for all controls like so:
53+
54+
```js
55+
<Video copy={{ key: value }}>
56+
```
57+
58+
The default english `copy` can be found in [here](https://github.com/mderrick/react-html5video/blob/v2/src/DefaultPlayer/copy.js).
59+
60+
*Disclaimer: Unfortuantely I don't much experience with a11y but I have tried to use some of the features from [PayPal's accessible HTML5 player](https://github.com/paypal/accessible-html5-video-player). If anyone has further input on this please raise an issue or a pull request.
61+
4862
### Advanced Usage
4963

5064
If you want to get creative and create your own video player then you will need to use the higher order component. The HoC connects a React component to all the [HTML5 video attributes](https://developer.mozilla.org/en/docs/Web/HTML/Element/video) and the [HTMLMediaElement](https://developer.mozilla.org/en/docs/Web/API/HTMLMediaElement) of the first video it finds in the component it is wrapping.
@@ -74,7 +88,7 @@ const MyVideoPlayer = ({ video, videoEl, children, ...restProps }) => (
7488
export default videoConnect(MyVideoPlayer)
7589
```
7690

77-
The above will simply print out the properties of the HTML5 `<video>` within `MyVideoPlayer`. Now you have these properties and the HTMLMediaElement itself available in your component, it is up to you to create your new custom controls using them. See the default player as an example.
91+
The above will simply print out the properties of the HTML5 `<video>` within `MyVideoPlayer`. Now you have these properties and the HTMLMediaElement itself available in your component, it is up to you to create your new custom controls using them. See the [default player](https://github.com/mderrick/react-html5video/blob/v2/src/DefaultPlayer/DefaultPlayer.js) as an example.
7892

7993
#### API
8094

src/DefaultPlayer/Captions/Captions.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import styles from './Captions.css';
33
import ClosedCaptionIcon from './../Icon/closed_caption.svg';
44

5-
export default ({ textTracks, onClick, onItemClick, className }) => {
5+
export default ({ textTracks, onClick, onItemClick, className, ariaLabel }) => {
66
return (
77
<div className={[
88
styles.component,
@@ -11,7 +11,7 @@ export default ({ textTracks, onClick, onItemClick, className }) => {
1111
<button
1212
type="button"
1313
onClick={onClick}
14-
aria-label="Captions"
14+
aria-label={ariaLabel}
1515
className={styles.button}>
1616
<ClosedCaptionIcon
1717
className={styles.icon}

src/DefaultPlayer/Captions/Captions.test.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ describe('Captions', () => {
77
let component;
88

99
beforeAll(() => {
10-
component = shallow(<Captions />);
10+
component = shallow(
11+
<Captions ariaLabel="Captions" />
12+
);
1113
});
1214

1315
it('should accept a className prop and append it to the components class', () => {
@@ -22,4 +24,9 @@ describe('Captions', () => {
2224
expect(component.prop('className'))
2325
.toContain(newClassNameString);
2426
});
27+
28+
it('has correct aria-label', () => {
29+
expect(component.find('button').prop('aria-label'))
30+
.toEqual('Captions');
31+
});
2532
});

src/DefaultPlayer/DefaultPlayer.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { PropTypes } from 'react';
22
import videoConnect from './../video/video';
3+
import copy from './copy';
34
import {
45
setVolume,
56
showTrack,
@@ -21,6 +22,7 @@ import Fullscreen from './Fullscreen/Fullscreen';
2122
import Overlay from './Overlay/Overlay';
2223

2324
export const DefaultPlayer = ({
25+
copy,
2426
video,
2527
style,
2628
controls,
@@ -56,17 +58,21 @@ export const DefaultPlayer = ({
5658
case 'Seek':
5759
return <Seek
5860
key={i}
61+
ariaLabel={copy.seek}
5962
className={styles.seek}
6063
onChange={onSeekChange}
6164
{...video} />;
6265
case 'PlayPause':
6366
return <PlayPause
6467
key={i}
68+
ariaLabelPlay={copy.play}
69+
ariaLabelPause={copy.pause}
6570
onClick={onPlayPauseClick}
6671
{...video} />;
6772
case 'Fullscreen':
6873
return <Fullscreen
6974
key={i}
75+
ariaLabel={copy.fullscreen}
7076
onClick={onFullscreenClick}
7177
{...video} />;
7278
case 'Time':
@@ -76,14 +82,17 @@ export const DefaultPlayer = ({
7682
case 'Volume':
7783
return <Volume
7884
key={i}
79-
onChange={onVolumeChange}
8085
onClick={onVolumeClick}
86+
onChange={onVolumeChange}
87+
ariaLabelMute={copy.mute}
88+
ariaLabelUnmute={copy.unmute}
8189
{...video} />;
8290
case 'Captions':
8391
return video.textTracks && video.textTracks.length
8492
? <Captions
8593
key={i}
8694
onClick={onCaptionsClick}
95+
ariaLabel={copy.captions}
8796
onItemClick={onCaptionsItemClick}
8897
{...video}/>
8998
: null;
@@ -100,13 +109,15 @@ export const DefaultPlayer = ({
100109
const controls = ['PlayPause', 'Seek', 'Time', 'Volume', 'Fullscreen', 'Captions'];
101110

102111
DefaultPlayer.defaultProps = {
103-
video: {},
104-
controls
112+
copy,
113+
controls,
114+
video: {}
105115
};
106116

107117
DefaultPlayer.propTypes = {
108-
video: PropTypes.object.isRequired,
109-
controls: PropTypes.arrayOf(PropTypes.oneOf(controls))
118+
copy: PropTypes.object.isRequired,
119+
controls: PropTypes.arrayOf(PropTypes.oneOf(controls)),
120+
video: PropTypes.object.isRequired
110121
};
111122

112123
export default videoConnect(

src/DefaultPlayer/Fullscreen/Fullscreen.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import styles from './Fullscreen.css';
33
import FullscreenIcon from './../Icon/fullscreen.svg';
44

5-
export default ({ onClick, className }) => {
5+
export default ({ onClick, className, ariaLabel }) => {
66
return (
77
<div className={[
88
styles.component,
@@ -11,7 +11,7 @@ export default ({ onClick, className }) => {
1111
<button
1212
type="button"
1313
onClick={onClick}
14-
aria-label="Fullscreen"
14+
aria-label={ariaLabel}
1515
className={styles.button}>
1616
<FullscreenIcon
1717
fill="#fff"

src/DefaultPlayer/Fullscreen/Fullscreen.test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ describe('Fullscreen', () => {
77
let component;
88

99
beforeAll(() => {
10-
component = shallow(<Fullscreen />);
10+
component = shallow(
11+
<Fullscreen ariaLabel="Fullscreen" />
12+
);
1113
});
1214

1315
it('should accept a className prop and append it to the components class', () => {

src/DefaultPlayer/PlayPause/PlayPause.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styles from './PlayPause.css';
33
import PlayArrow from './../Icon/play_arrow.svg';
44
import Pause from './../Icon/pause.svg';
55

6-
export default ({ onClick, paused, className }) => {
6+
export default ({ onClick, paused, className, ariaLabelPlay, ariaLabelPause }) => {
77
return (
88
<div className={[
99
styles.component,
@@ -13,8 +13,8 @@ export default ({ onClick, paused, className }) => {
1313
className={styles.button}
1414
onClick={onClick}
1515
aria-label={ paused
16-
? 'Play'
17-
: 'Pause' }
16+
? ariaLabelPlay
17+
: ariaLabelPause }
1818
type="button">
1919
{ paused
2020
? <PlayArrow

src/DefaultPlayer/PlayPause/PlayPause.test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ describe('PlayPause', () => {
99
let component;
1010

1111
beforeAll(() => {
12-
component = shallow(<PlayPause />);
12+
component = shallow(
13+
<PlayPause
14+
ariaLabelPlay="Play"
15+
ariaLabelPause="Pause" />
16+
);
1317
});
1418

1519
it('should accept a className prop and append it to the components class', () => {

src/DefaultPlayer/Seek/Seek.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import styles from './Seek.css';
33

4-
export default ({ onChange, percentagePlayed, percentageBuffered, className }) => {
4+
export default ({ onChange, percentagePlayed, percentageBuffered, className, ariaLabel }) => {
55
return (
66
<div className={[
77
styles.component,
@@ -25,6 +25,7 @@ export default ({ onChange, percentagePlayed, percentageBuffered, className }) =
2525
type="range"
2626
orient="horizontal"
2727
onChange={onChange}
28+
aria-label={ariaLabel}
2829
className={styles.input}
2930
value={percentagePlayed} />
3031
</div>

src/DefaultPlayer/Volume/Volume.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styles from './Volume.css';
33
import VolumeOff from './../Icon/volume_off.svg';
44
import VolumeUp from './../Icon/volume_up.svg';
55

6-
export default ({ onChange, onClick, volume, muted, className }) => {
6+
export default ({ onChange, onClick, volume, muted, className, ariaLabelMute, ariaLabelUnmute }) => {
77
const volumeValue = muted
88
? 0
99
: +volume;
@@ -15,8 +15,8 @@ export default ({ onChange, onClick, volume, muted, className }) => {
1515
].join(' ')}>
1616
<button
1717
aria-label={isSilent
18-
? 'Unmute'
19-
: 'Mute'}
18+
? ariaLabelUnmute
19+
: ariaLabelMute}
2020
className={styles.button}
2121
onClick={onClick}
2222
type="button">

0 commit comments

Comments
 (0)