Skip to content

Commit

Permalink
feat(project): setup react-i18next
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jun 9, 2021
1 parent 0e4ef25 commit 6fb4692
Show file tree
Hide file tree
Showing 24 changed files with 230 additions and 58 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
"@types/jwplayer": "^8.2.6",
"@types/react-virtualized": "^9.21.11",
"classnames": "^2.3.1",
"i18next": "^20.3.1",
"i18next-browser-languagedetector": "^6.1.1",
"lodash.merge": "^4.6.2",
"memoize-one": "^5.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-i18next": "^11.10.0",
"react-query": "^3.13.10",
"react-router-dom": "^5.2.0",
"react-virtualized": "^9.22.3",
Expand Down
35 changes: 20 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React, { Component } from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { I18nextProvider, getI18n } from 'react-i18next';

import Root from './components/Root/Root';
import ConfigProvider from './providers/ConfigProvider';
import QueryProvider from './providers/QueryProvider';
import UIStateProvider from './providers/uiStateProvider';
import './i18n/config';

import './styles/main.scss';

interface State {
Expand All @@ -16,25 +19,27 @@ class App extends Component {
error: null,
};

componentDidCatch(error: Error) {
componentDidCatch (error: Error) {
this.setState({ error });
}

render() {
render () {
return (
<QueryProvider>
<ConfigProvider
configLocation={window.configLocation}
onLoading={(isLoading: boolean) => console.info(`Loading config: ${isLoading}`)}
onValidationError={(error: Error) => console.error(`Config ${error}`)}
>
<UIStateProvider>
<Router>
<Root error={this.state.error} />
</Router>
</UIStateProvider>
</ConfigProvider>
</QueryProvider>
<I18nextProvider i18n={getI18n()}>
<QueryProvider>
<ConfigProvider
configLocation={window.configLocation}
onLoading={(isLoading: boolean) => console.info(`Loading config: ${isLoading}`)}
onValidationError={(error: Error) => console.error(`Config ${error}`)}
>
<UIStateProvider>
<Router>
<Root error={this.state.error} />
</Router>
</UIStateProvider>
</ConfigProvider>
</QueryProvider>
</I18nextProvider>
);
}
}
Expand Down
53 changes: 53 additions & 0 deletions src/__mocks__/react-i18next.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const React = require('react');
const reactI18next = require('react-i18next');

const hasChildren = node => node && (node.children || (node.props && node.props.children));

const getChildren = node =>
node && node.children ? node.children : node.props && node.props.children;

const renderNodes = reactNodes => {
if (typeof reactNodes === 'string') {
return reactNodes;
}

return Object.keys(reactNodes).map((key, i) => {
const child = reactNodes[key];
const isElement = React.isValidElement(child);

if (typeof child === 'string') {
return child;
}
if (hasChildren(child)) {
const inner = renderNodes(getChildren(child));
return React.cloneElement(child, { ...child.props, key: i }, inner);
}
if (typeof child === 'object' && !isElement) {
return Object.keys(child).reduce((str, childKey) => `${str}${child[childKey]}`, '');
}

return child;
});
};

const useMock = [k => k, {}];
useMock.t = k => k;
useMock.i18n = {};

module.exports = {
// this mock makes sure any components using the translate HoC receive the t function as a prop
// eslint-disable-next-line react/display-name
withTranslation: () => Component => props => <Component t={k => k} {...props} />,
Trans: ({ children }) =>
Array.isArray(children) ? renderNodes(children) : renderNodes([children]),
Translation: ({ children }) => children(k => k, { i18n: {} }),
useTranslation: () => useMock,

// mock if needed
I18nextProvider: reactI18next.I18nextProvider,
initReactI18next: reactI18next.initReactI18next,
setDefaults: reactI18next.setDefaults,
getDefaults: reactI18next.getDefaults,
setI18n: reactI18next.setI18n,
getI18n: reactI18next.getI18n,
};
4 changes: 3 additions & 1 deletion src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { KeyboardEvent, memo } from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';

import { formatDurationTag } from '../../utils/formatting';

Expand Down Expand Up @@ -28,6 +29,7 @@ function Card({
featured = false,
disabled = false,
}: CardProps): JSX.Element {
const { t } = useTranslation('common');
const cardClassName = classNames(styles.card, { [styles.featured]: featured, [styles.disabled]: disabled });
const posterClassNames = classNames(styles.poster, styles[`aspect${posterAspect.replace(':', '')}`]);
const metaData = () => {
Expand All @@ -48,7 +50,7 @@ function Card({
(event.key === 'Enter' || event.key === ' ') && !disabled && onClick && onClick()
}
role="button"
aria-label={`Play ${title}`}
aria-label={t('play_item', { title })}
>
<div className={posterClassNames} style={{ backgroundImage: posterSource ? `url(${posterSource})` : '' }}>
<div className={styles.meta}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Filter/__snapshots__/Filter.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ exports[`<Filter> renders Filter 1`] = `
class="header"
>
<div
aria-label="close menu"
aria-label="close"
class="iconButton"
role="button"
tabindex="0"
Expand Down
5 changes: 4 additions & 1 deletion src/components/FilterModal/FilterModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Fragment, ReactNode } from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';

import Close from '../../icons/Close';
import IconButton from '../../components/IconButton/IconButton';
Expand All @@ -14,6 +15,8 @@ type Props = {
};

const FilterModal: React.FC<Props> = ({ isOpen, onClose, name, children }) => {
const { t } = useTranslation('common');

return (
<Fragment>
<div
Expand All @@ -22,7 +25,7 @@ const FilterModal: React.FC<Props> = ({ isOpen, onClose, name, children }) => {
})}
>
<div className={styles.header}>
<IconButton aria-label="close menu" onClick={onClose}>
<IconButton aria-label={t('close')} onClick={onClose}>
<Close />
</IconButton>
<h4>{name}</h4>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ exports[`<FilterModal> renders and matches snapshot 1`] = `
class="header"
>
<div
aria-label="close menu"
aria-label="close"
class="iconButton"
role="button"
tabindex="0"
Expand Down
11 changes: 7 additions & 4 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';

import ButtonLink from '../ButtonLink/ButtonLink';
import Logo from '../Logo/Logo';
Expand All @@ -18,19 +19,21 @@ type Props = {
};

const Header: React.FC<Props> = ({ headerType = 'static', onMenuButtonClick, playlistMenuItems, logoSrc }) => {
const { t } = useTranslation('menu');

return (
<header className={classNames(styles.header, styles[headerType])}>
<div className={styles.container}>
<div className={styles.menu}>
<IconButton aria-label="open menu" onClick={onMenuButtonClick}>
<IconButton aria-label={t('open_menu')} onClick={onMenuButtonClick}>
<Menu />
</IconButton>
</div>
{logoSrc && <Logo src={logoSrc} />}
<nav className={styles.nav} aria-label="menu">
<ButtonLink label="Home" to="/" />
<nav className={styles.nav} aria-label={t('menu')}>
<ButtonLink label={t('home')} to="/" />
{playlistMenuItems}
<ButtonLink label="Settings" to="/u" />
<ButtonLink label={t('settings')} to="/u" />
</nav>
<div className={styles.search}></div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/Header/__snapshots__/Header.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ exports[`<Header /> renders header 1`] = `
class="menu"
>
<div
aria-label="open menu"
aria-label="open_menu"
class="iconButton"
role="button"
tabindex="0"
Expand Down Expand Up @@ -46,7 +46,7 @@ exports[`<Header /> renders header 1`] = `
href="/"
>
<span>
Home
home
</span>
</a>
<a
Expand All @@ -63,7 +63,7 @@ exports[`<Header /> renders header 1`] = `
href="/u"
>
<span>
Settings
settings
</span>
</a>
</nav>
Expand Down
12 changes: 6 additions & 6 deletions src/components/Layout/__snapshots__/Layout.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exports[`<Layout /> renders layout 1`] = `
class="menu"
>
<div
aria-label="open menu"
aria-label="open_menu"
class="iconButton"
role="button"
tabindex="0"
Expand Down Expand Up @@ -47,15 +47,15 @@ exports[`<Layout /> renders layout 1`] = `
href="/"
>
<span>
Home
home
</span>
</a>
<a
class="link"
href="/u"
>
<span>
Settings
settings
</span>
</a>
</nav>
Expand All @@ -74,7 +74,7 @@ exports[`<Layout /> renders layout 1`] = `
class="heading"
>
<div
aria-label="close menu"
aria-label="close_menu"
class="iconButton"
role="button"
tabindex="-1"
Expand Down Expand Up @@ -108,7 +108,7 @@ exports[`<Layout /> renders layout 1`] = `
tabindex="-1"
>
<span>
Home
home
</span>
</a>
<hr
Expand All @@ -120,7 +120,7 @@ exports[`<Layout /> renders layout 1`] = `
tabindex="-1"
>
<span>
Settings
settings
</span>
</a>
</nav>
Expand Down
12 changes: 6 additions & 6 deletions src/components/Root/__snapshots__/Root.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exports[`<Root /> renders and matches snapshot 1`] = `
class="menu"
>
<div
aria-label="open menu"
aria-label="open_menu"
class="iconButton"
role="button"
tabindex="0"
Expand Down Expand Up @@ -46,15 +46,15 @@ exports[`<Root /> renders and matches snapshot 1`] = `
href="/"
>
<span>
Home
home
</span>
</a>
<a
class="link"
href="/u"
>
<span>
Settings
settings
</span>
</a>
</nav>
Expand All @@ -73,7 +73,7 @@ exports[`<Root /> renders and matches snapshot 1`] = `
class="heading"
>
<div
aria-label="close menu"
aria-label="close_menu"
class="iconButton"
role="button"
tabindex="-1"
Expand Down Expand Up @@ -106,7 +106,7 @@ exports[`<Root /> renders and matches snapshot 1`] = `
tabindex="-1"
>
<span>
Home
home
</span>
</a>
<hr
Expand All @@ -118,7 +118,7 @@ exports[`<Root /> renders and matches snapshot 1`] = `
tabindex="-1"
>
<span>
Settings
settings
</span>
</a>
</nav>
Expand Down
6 changes: 4 additions & 2 deletions src/components/Shelf/Shelf.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import type { Playlist, PlaylistItem } from 'types/playlist';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';

import Card from '../Card/Card';
import TileDock from '../TileDock/TileDock';
Expand Down Expand Up @@ -45,6 +46,7 @@ const Shelf: React.FC<ShelfProps> = ({
loading = false,
}: ShelfProps) => {
const breakpoint: Breakpoint = useBreakpoint();
const { t } = useTranslation('common')
const [didSlideBefore, setDidSlideBefore] = useState(false);
const tilesToShow: number = featured ? featuredTileBreakpoints[breakpoint] : tileBreakpoints[breakpoint];
const isLargeScreen = breakpoint >= Breakpoint.md;
Expand Down Expand Up @@ -74,7 +76,7 @@ const Shelf: React.FC<ShelfProps> = ({
})}
role="button"
tabIndex={didSlideBefore ? 0 : -1}
aria-label="Slide left"
aria-label={t('slide_left')}
onKeyDown={(event: React.KeyboardEvent) =>
(event.key === 'Enter' || event.key === ' ') && handleSlide(doSlide)
}
Expand All @@ -88,7 +90,7 @@ const Shelf: React.FC<ShelfProps> = ({
className={classNames(styles.chevron)}
role="button"
tabIndex={0}
aria-label="Slide right"
aria-label={t('slide_right')}
onKeyDown={(event: React.KeyboardEvent) =>
(event.key === 'Enter' || event.key === ' ') && handleSlide(doSlide)
}
Expand Down
Loading

0 comments on commit 6fb4692

Please sign in to comment.