Skip to content

Commit

Permalink
Merge pull request #85 from vnscosta/feat/i18n
Browse files Browse the repository at this point in the history
feat: config i18n and language control
  • Loading branch information
hellomuthu23 committed Jul 31, 2023
2 parents 2c28f72 + dc2319d commit 08ba26c
Show file tree
Hide file tree
Showing 16 changed files with 1,859 additions and 1,588 deletions.
9 changes: 9 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"semi": true,
"tabWidth": 2,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all",
"jsxSingleQuote": true,
"bracketSpacing": true
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.57",
"country-flag-icons": "^1.5.7",
"firebase": "^8.3.2",
"i18next": "^23.2.11",
"i18next-browser-languagedetector": "^7.1.0",
"i18next-http-backend": "^2.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-i18next": "^13.0.2",
"react-router-dom": "^5.2.0",
"react-scripts": "5.0.1",
"typescript": "4.1.5",
Expand Down
22 changes: 22 additions & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"toolbar": {
"menu": {
"about": "What is planning poker?",
"guide": "Guide",
"examples": "Examples",
"newSession": "New session",
"joinSession": "Join session"
}
},
"HomePage": {
"heroSection": {
"title": "Free Planning Poker App",
"description": "Free / Open source Planning Poker Web App to estimate user stories for your Agile/Scrum teams. Create a session and invite your team members to estimate user stories efficiently.",
"formNewSession": {
"newSessionHeader": "Create new session",
"sessionNameLabel": "Session name",
"yourNameLabel": "Your name"
}
}
}
}
22 changes: 22 additions & 0 deletions public/locales/pt-BR/translation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"toolbar": {
"menu": {
"about": "Sobre o planning poker",
"guide": "Guia",
"examples": "Exemplos",
"newSession": "Nova sessão",
"joinSession": "Entrar na sessão"
}
},
"HomePage": {
"heroSection": {
"title": "Sistema grátis de Planning Poker",
"description": "Sistema de Planning Poker grátis e open source para estimativa de histórias dos usuários para seu time ágil/scrum. Crie uma sessão e convide membros do seu time para estimar as histórias com eficiência.",
"formNewSession": {
"newSessionHeader": "Criar nova sessão",
"sessionNameLabel": "Nome da sessão",
"yourNameLabel": "Seu nome"
}
}
}
}
1 change: 0 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { CssBaseline } from '@material-ui/core';
import { StylesProvider, ThemeProvider } from '@material-ui/core/styles';
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Toolbar } from './components/Toolbar/Toolbar';
import DeleteOldGames from './pages/DeleteOldGames/DeleteOldGames';
Expand Down
3 changes: 3 additions & 0 deletions src/components/LanguageControl/LanguageControl.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.LanguageControlSelect {
height: 40px;
}
31 changes: 31 additions & 0 deletions src/components/LanguageControl/LanguageControl.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { render, screen, fireEvent, within } from '@testing-library/react';
import '@testing-library/jest-dom'; // for extended matchers
import { LanguageControl } from './LanguageControl';

describe('LanguageControl component', () => {
test('should render with default language', async () => {
render(<LanguageControl />);

const wrapperNode = screen.getByTestId('language-control');
const button = await within(wrapperNode).findByRole('button');

expect(button).toHaveTextContent('🇺🇸');
});

test('should changes language when selecting a flag option', async () => {
render(<LanguageControl />);

const wrapperNode = screen.getByTestId('language-control');
const button = await within(wrapperNode).findByRole('button');

fireEvent.mouseDown(button);

const option = await screen.findByRole('option', {
name: new RegExp('🇧🇷'),
});

fireEvent.click(option);

expect(button).toHaveTextContent('🇧🇷');
});
});
38 changes: 38 additions & 0 deletions src/components/LanguageControl/LanguageControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useState, useEffect } from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import './LanguageControl.css';

import getUnicodeFlagIcon from 'country-flag-icons/unicode';
import { useTranslation } from 'react-i18next';

export const LanguageControl: React.FC = () => {
const { i18n } = useTranslation();
const [language, setLanguage] = useState('en-US');

useEffect(() => {
setLanguage(i18n.language);
}, [i18n.language]);

const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
setLanguage(event.target.value as string);
i18n.changeLanguage(event.target.value as string);
};

return (
<FormControl variant='outlined'>
<Select
value={language}
onChange={handleChange}
displayEmpty
inputProps={{ 'aria-label': 'Change language' }}
className='LanguageControlSelect'
data-testid='language-control'
>
<MenuItem value={'en-US'}>{getUnicodeFlagIcon('US')}</MenuItem>
<MenuItem value={'pt-BR'}>{getUnicodeFlagIcon('BR')}</MenuItem>
</Select>
</FormControl>
);
};
27 changes: 18 additions & 9 deletions src/components/Poker/CreateGame/CreateGame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ import { useHistory } from 'react-router-dom';
import { addNewGame } from '../../../service/games';
import { GameType, NewGame } from '../../../types/game';
import './CreateGame.css';
import { useTranslation } from 'react-i18next';

const gameNameConfig: Config = {
dictionaries: [colors, animals],
separator: ' ',
style: 'capital',
}
};
const userNameConfig: Config = {
dictionaries: [starWars]
}
dictionaries: [starWars],
};

export const CreateGame = () => {
const history = useHistory();
Expand All @@ -33,6 +34,7 @@ export const CreateGame = () => {
const [gameType, setGameType] = useState(GameType.Fibonacci);
const [hasDefaults, setHasDefaults] = useState({ game: true, name: true });
const [loading, setLoading] = useState(false);
const { t } = useTranslation();

const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
Expand All @@ -44,7 +46,7 @@ export const CreateGame = () => {
createdAt: new Date(),
};
const newGameId = await addNewGame(game);
if(newGameId){
if (newGameId) {
setLoading(false);
}
history.push(`/game/${newGameId}`);
Expand All @@ -71,15 +73,15 @@ export const CreateGame = () => {
<Card variant='outlined' className='CreateGameCard'>
<CardHeader
className='CreateGameCardHeader'
title='Create New Session'
title={t('HomePage.heroSection.formNewSession.newSessionHeader')}
titleTypographyProps={{ variant: 'h4' }}
/>
<CardContent className='CreateGameCardContent'>
<TextField
className='CreateGameTextField'
required
id='filled-required'
label='Session Name'
label={t('HomePage.heroSection.formNewSession.sessionNameLabel')}
placeholder='Enter a session name'
value={gameName || ''}
onClick={() => emptyGameName()}
Expand All @@ -90,7 +92,7 @@ export const CreateGame = () => {
className='CreateGameTextField'
required
id='filled-required'
label='Your Name'
label={t('HomePage.heroSection.formNewSession.yourNameLabel')}
placeholder='Enter your name'
value={createdBy || ''}
onClick={() => emptyCreatorName()}
Expand All @@ -105,7 +107,7 @@ export const CreateGame = () => {
event: ChangeEvent<{
name?: string | undefined;
value: any;
}>
}>,
) => setGameType(event.target.value)}
>
<FormControlLabel
Expand All @@ -126,7 +128,14 @@ export const CreateGame = () => {
</RadioGroup>
</CardContent>
<CardActions className='CreateGameCardAction'>
<Button type='submit' variant='contained' color='primary' className='CreateGameButton' data-testid="loading" disabled={loading}>
<Button
type='submit'
variant='contained'
color='primary'
className='CreateGameButton'
data-testid='loading'
disabled={loading}
>
Create
</Button>
</CardActions>
Expand Down
9 changes: 5 additions & 4 deletions src/components/Toolbar/Toolbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Toolbar } from './Toolbar';
import { useTranslation } from 'react-i18next';

Check warning on line 7 in src/components/Toolbar/Toolbar.test.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

'useTranslation' is defined but never used

const mockHistoryPush = jest.fn();

Expand Down Expand Up @@ -37,23 +38,23 @@ describe('Toolbar component', () => {
});
it('should render Create new session button', () => {
render(<Toolbar />);
const newSession = screen.getByText('New Session');
const newSession = screen.getByTestId('toolbar.menu.newSession');
expect(newSession).toBeInTheDocument();
});
it('should render Join session button', () => {
render(<Toolbar />);
const newSession = screen.getByText('Join Session');
const newSession = screen.getByTestId('toolbar.menu.joinSession');
expect(newSession).toBeInTheDocument();
});
it('should navigate to Home page when New Session button is clicked', () => {
render(<Toolbar />);
const newSession = screen.getByText('New Session');
const newSession = screen.getByTestId('toolbar.menu.newSession');
userEvent.click(newSession);
expect(mockHistoryPush).toBeCalledWith('/');
});
it('should navigate to Join session page when Join Session button is clicked', () => {
render(<Toolbar />);
const newSession = screen.getByText('Join Session');
const newSession = screen.getByTestId('toolbar.menu.joinSession');
userEvent.click(newSession);
expect(mockHistoryPush).toBeCalledWith('/join');
});
Expand Down
30 changes: 20 additions & 10 deletions src/components/Toolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import SearchOutlinedIcon from '@material-ui/icons/SearchOutlined';
import React from 'react';
import { useHistory } from 'react-router-dom';
import './Toolbar.css';
import { useTranslation } from 'react-i18next';
import { LanguageControl } from '../LanguageControl/LanguageControl';
export const title = 'Planning Poker';

export const Toolbar = () => {
const history = useHistory();
const isSmallScreen = useMediaQuery((theme: any) => theme.breakpoints.down('xs'));
const { t } = useTranslation();

return (
<Slide direction='down' in={true} timeout={800}>
Expand All @@ -31,52 +34,59 @@ export const Toolbar = () => {
</div>
<div>
<Button
title='About section'
title={t('toolbar.menu.about')}
startIcon={<InfoOutlinedIcon />}
color='inherit'
onClick={() => history.push('/about-planning-poker')}
>
{!isSmallScreen ? 'What is planning poker?' : null}
{!isSmallScreen ? t('toolbar.menu.about') : null}
</Button>
<Button
title='Guide'
title={t('toolbar.menu.guide')}
startIcon={<SearchOutlinedIcon />}
color='inherit'
onClick={() => history.push('/guide')}
>
{!isSmallScreen ? 'Guide' : null}
{!isSmallScreen ? t('toolbar.menu.guide') : null}
</Button>
<Button
title='Example'
title={t('toolbar.menu.examples')}
startIcon={<BookOutlinedIcon />}
color='inherit'
onClick={() => history.push('/examples')}
>
{!isSmallScreen ? 'Examples' : null}
{!isSmallScreen ? t('toolbar.menu.examples') : null}
</Button>
<Button
title='New Session'
title={t('toolbar.menu.newSession')}
startIcon={<AddCircleOutlineIcon />}
color='inherit'
onClick={() => history.push('/')}
data-testid='toolbar.menu.newSession'
>
{!isSmallScreen ? 'New Session' : null}
{!isSmallScreen ? t('toolbar.menu.newSession') : null}
</Button>
<Button
title={t('toolbar.menu.joinSession')}
startIcon={<MergeTypeOutlinedIcon />}
size={isSmallScreen ? 'small' : 'large'}
color='inherit'
onClick={() => history.push('/join')}
data-testid='toolbar.menu.joinSession'
>
{!isSmallScreen ? 'Join Session' : null}
{!isSmallScreen ? t('toolbar.menu.joinSession') : null}
</Button>

<Button
id='github-button'
color='inherit'
onClick={() => (window.location.href = 'https://github.com/hellomuthu23/planning-poker')}
onClick={() =>
(window.location.href = 'https://github.com/hellomuthu23/planning-poker')
}
>
<GithubIcon></GithubIcon>
</Button>
{!isSmallScreen && <LanguageControl />}
</div>
</div>
</AppToolbar>
Expand Down
21 changes: 21 additions & 0 deletions src/config/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en-US',
debug: false,
react: {
useSuspense: false,
},
interpolation: {
escapeValue: false,
},
});

export default i18n;
6 changes: 4 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
import './styles/styles.css'
import './styles/styles.css';
import reportWebVitals from './reportWebVitals';
import './service/analytics';
ReactDOM.render(<App/>, document.getElementById('root'));
import './config/i18n';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
Expand Down
Loading

0 comments on commit 08ba26c

Please sign in to comment.