...
 
Commits (5)
{
"presets": ["react-app"],
"plugins": [
["react-intl", {
"messagesDir": "tmp/messages",
"enforceDescriptions": false
}]
]
}
......@@ -9,6 +9,9 @@
# production
/build
# i18n (message extraction)
/tmp
# misc
.DS_Store
.env.local
......
This diff is collapsed.
......@@ -7,6 +7,7 @@
"@material-ui/lab": "^1.0.0-alpha.9",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-intl": "^2.4.0",
"react-redux": "^5.0.7",
"react-scripts": "1.1.4",
"redux": "^4.0.0",
......@@ -17,6 +18,14 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"lang:extract": "NODE_ENV=production babel ./src --out-file /dev/null",
"lang:check": "NODE_ENV=production node scripts/translationRunner.js"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-plugin-react-intl": "^2.4.0",
"babel-preset-react-app": "^3.1.2",
"react-intl-translations-manager": "^5.0.3"
}
}
// translationRunner.js
const manageTranslations = require('react-intl-translations-manager').default;
// es2015 import
// import manageTranslations from 'react-intl-translations-manager';
manageTranslations({
messagesDirectory: 'tmp/messages/',
translationsDirectory: 'src/locales/',
languages: ['fr'] // any language you need
});
......@@ -2,6 +2,10 @@ import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import grey from '@material-ui/core/colors/grey';
import {
defineMessages,
FormattedMessage,
} from 'react-intl';
const styles = {
......@@ -11,6 +15,14 @@ const styles = {
};
const messages = defineMessages({
text: {
id: "alreadytalkedbutton.text",
defaultMessage: "This person has already spoken",
},
});
class AlreadyTalkedButton extends Component {
render() {
......@@ -24,7 +36,7 @@ class AlreadyTalkedButton extends Component {
className={classes.button}
disabled={this.props.disabled}
>
Cette personne a déjà parlé
<FormattedMessage {...messages.text} />
</Button>
);
}
......
......@@ -3,6 +3,11 @@ import { withStyles } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import {
injectIntl,
defineMessages,
FormattedMessage,
} from 'react-intl';
import ConnectedSpeakerButton from '../containers/ConnectedSpeakerButton';
import ConnectedAlreadyTalkedButton from '../containers/ConnectedAlreadyTalkedButton';
import ConnectedTimer from '../containers/ConnectedTimer';
......@@ -28,6 +33,21 @@ const styles = theme => ({
});
const messages = defineMessages({
title: {
id: "app.title",
defaultMessage: "Who's talking?",
},
cisdude: {
id: "app.cisdude",
defaultMessage: "A cis dude",
},
notcisdude: {
id: "app.notcisdude",
defaultMessage: "Not a cis dude",
},
});
class App extends Component {
render() {
......@@ -46,21 +66,21 @@ class App extends Component {
component="h3"
align="center"
>
Qui parle ?
<FormattedMessage {...messages.title} />
</Typography>
</Grid>
<Grid item>
<ConnectedSpeakerButton
category="man"
isNew={true}
text="Un mec cis"
text={this.props.intl.formatMessage(messages.cisdude)}
/>
</Grid>
<Grid item>
<ConnectedSpeakerButton
category="other"
isNew={true}
text="Pas un mec cis"
text={this.props.intl.formatMessage(messages.notcisdude)}
/>
</Grid>
<Grid item xs={12}>
......@@ -101,4 +121,4 @@ class App extends Component {
}
}
export default withStyles(styles)(App);
export default injectIntl(withStyles(styles)(App));
......@@ -2,6 +2,46 @@ import React, { Component } from 'react';
import IconButton from '@material-ui/core/IconButton';
import Icon from '@material-ui/core/Icon';
import Tooltip from '@material-ui/core/Tooltip';
import {
injectIntl,
defineMessages,
} from 'react-intl';
const messages = defineMessages({
save: {
id: "exportbutton.save",
defaultMessage: "Save",
},
date: {
id: "exportbutton.date",
defaultMessage: "Date",
},
timeMan: {
id: "exportbutton.timeMan",
defaultMessage: "Cis dude speaking time",
},
timeOther: {
id: "exportbutton.timeOther",
defaultMessage: "Other speaking time",
},
speechesMan: {
id: "exportbutton.speechesMan",
defaultMessage: "Cis dude speeches",
},
speechesOther: {
id: "exportbutton.speechesOther",
defaultMessage: "Other speeches",
},
speakersMan: {
id: "exportbutton.speakersMan",
defaultMessage: "Number of cis dudes speaking",
},
speakersOther: {
id: "exportbutton.speakersOther",
defaultMessage: "Number of other speakers",
},
});
class ExportButton extends Component {
......@@ -13,13 +53,13 @@ class ExportButton extends Component {
);
let data = [
toCSVLine([
"Date",
"Temps parlé par des mecs cis",
"Temps parlé par les autres",
"Interventions de mecs cis",
"Interventions des autres",
"Nombre d'intervenants mecs cis",
"Nombre d'autres intervenants",
this.props.intl.formatMessage(messages.date),
this.props.intl.formatMessage(messages.timeMan),
this.props.intl.formatMessage(messages.timeOther),
this.props.intl.formatMessage(messages.speechesMan),
this.props.intl.formatMessage(messages.speechesOther),
this.props.intl.formatMessage(messages.speakersMan),
this.props.intl.formatMessage(messages.speakersOther),
]),
toCSVLine([
now.toISOString(),
......@@ -51,7 +91,7 @@ class ExportButton extends Component {
render() {
return (
<Tooltip
title="Sauvegarder"
title={this.props.intl.formatMessage(messages.save)}
placement="left"
>
<IconButton
......@@ -65,5 +105,4 @@ class ExportButton extends Component {
}
}
export default ExportButton;
export default injectIntl(ExportButton);
......@@ -2,6 +2,10 @@ import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import {
defineMessages,
FormattedMessage,
} from 'react-intl';
const styles = theme => ({
......@@ -14,6 +18,22 @@ const styles = theme => ({
});
const messages = defineMessages({
text1: {
id: "footer.text1",
defaultMessage: "Very heavily inspired by",
},
text2: {
id: "footer.text2",
defaultMessage: "Actually, it's the same thing with more stats.",
},
viewCode: {
id: "footer.viewCode",
defaultMessage: "See the code",
},
});
class Footer extends Component {
render() {
......@@ -26,12 +46,12 @@ class Footer extends Component {
paragraph={true}
align="center"
>
Très largement inspiré de
{" "}
<a href="http://arementalkingtoomuch.com">AreMenTalkingTooMuch</a>.
En fait, c'est pareil mais avec quelques stats en plus.
{" "}
<a href="https://gitlab.com/abompard/men-talk-too-much">Voir le code</a>.
<FormattedMessage {...messages.text1} /> {" "}
<a href="http://arementalkingtoomuch.com">AreMenTalkingTooMuch</a>. {" "}
<FormattedMessage {...messages.text2} /> {" "}
<a href="https://gitlab.com/abompard/men-talk-too-much">
<FormattedMessage {...messages.viewCode} />
</a>.
</Typography>
</Grid>
</Grid>
......
......@@ -2,6 +2,10 @@ import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import {
defineMessages,
FormattedMessage,
} from 'react-intl';
const styles = theme => ({
......@@ -12,6 +16,14 @@ const styles = theme => ({
});
const messages = defineMessages({
title: {
id: "header.title",
defaultMessage: "Men talk too much, prove it.",
},
});
class Header extends Component {
render() {
......@@ -23,7 +35,7 @@ class Header extends Component {
align="center"
className={classes.title}
>
Les hommes parlent trop, prouvez-le.
<FormattedMessage {...messages.title} />
</Typography>
</Grid>
);
......
......@@ -6,6 +6,11 @@ import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import Icon from '@material-ui/core/Icon';
import {
injectIntl,
defineMessages,
FormattedMessage,
} from 'react-intl';
import Duration from './Duration';
import ExportButton from './ExportButton';
......@@ -17,6 +22,38 @@ const styles = {
};
const messages = defineMessages({
nobody: {
id: "stats.nobody",
defaultMessage: "Nobody has spoken yet.",
},
header: {
id: "stats.header",
defaultMessage: "Cis dudes have talked:",
},
timePercent: {
id: "stats.timePercent",
defaultMessage: "{value}% of the time",
},
speechesPercent: {
id: "stats.speechesPercent",
defaultMessage: "{value}% of the opportunities",
},
speakersPercent: {
id: "stats.speakersPercent",
defaultMessage: "{value}% of the speakers",
},
speechesDuration: {
id: "stats.speechesDuration",
defaultMessage: "Mean speech duration:",
},
forOthers: {
id: "stats.forOthers",
defaultMessage: "For others:",
},
});
class Stats extends Component {
render() {
......@@ -44,7 +81,7 @@ class StatsValues extends Component {
if (totalSpeakers === 0) {
return (
<Typography component="p">
Personne n'a encore parlé.
<FormattedMessage {...messages.nobody} />
</Typography>
);
}
......@@ -54,7 +91,7 @@ class StatsValues extends Component {
variant="subheading"
component="h3"
>
Les mecs cis ont parlé :
<FormattedMessage {...messages.header} />
</Typography>
<List
dense
......@@ -66,7 +103,7 @@ class StatsValues extends Component {
<Icon>schedule</Icon>
</ListItemIcon>
<ListItemText
primary={`${timePercent}% du temps`}
primary={this.props.intl.formatMessage(messages.timePercent, {value: timePercent})}
secondary={
<span>
(
......@@ -84,7 +121,7 @@ class StatsValues extends Component {
<Icon>pan_tool</Icon>
</ListItemIcon>
<ListItemText
primary={`À ${speechesPercent}% des occasions`}
primary={this.props.intl.formatMessage(messages.speechesPercent, {value: speechesPercent})}
secondary={`(${this.props.stats.man.speeches}/${totalSpeeches})`}
/>
</ListItem>
......@@ -94,7 +131,7 @@ class StatsValues extends Component {
<Icon>face</Icon>
</ListItemIcon>
<ListItemText
primary={`${speakersPercent}% des intervenant⋅e⋅s`}
primary={this.props.intl.formatMessage(messages.speakersPercent, {value: speakersPercent})}
secondary={`(${this.props.stats.man.speakers}/${totalSpeakers})`}
/>
</ListItem>
......@@ -106,7 +143,7 @@ class StatsValues extends Component {
<ListItemText
primary={
<span>
Durée moyenne des interventions : {" "}
<FormattedMessage {...messages.speechesDuration} /> {" "}
{ this.props.stats.man.speeches > 0 ?
<Duration value={manMeanTime} />
: "?"
......@@ -115,7 +152,7 @@ class StatsValues extends Component {
}
secondary={
<span>
(pour les autres : {" "}
(<FormattedMessage {...messages.forOthers} /> {" "}
{ this.props.stats.other.speeches > 0 ?
<Duration value={otherMeanTime} />
: "?"
......@@ -143,4 +180,4 @@ class StatsValues extends Component {
}
}
export default withStyles(styles)(Stats);
export default injectIntl(withStyles(styles)(Stats));
......@@ -5,6 +5,11 @@ import Modal from '@material-ui/core/Modal';
import Button from '@material-ui/core/Button';
import Icon from '@material-ui/core/Icon';
import TextField from '@material-ui/core/TextField';
import {
injectIntl,
defineMessages,
FormattedMessage,
} from 'react-intl';
const styles = theme => ({
......@@ -33,6 +38,30 @@ const styles = theme => ({
});
const messages = defineMessages({
nolimit: {
id: "timerlimitedit.nolimit",
defaultMessage: "no limit",
},
buttontext: {
id: "timerlimitedit.buttontext",
defaultMessage: "{min}:{sec} max",
},
modaltitle: {
id: "timerlimitedit.modaltitle",
defaultMessage: "Maximum speech time",
},
minutes: {
id: "timerlimitedit.minutes",
defaultMessage: "Minutes",
},
seconds: {
id: "timerlimitedit.seconds",
defaultMessage: "Seconds",
},
});
class TimerLimitEdit extends Component {
state = {
......@@ -81,14 +110,18 @@ class TimerLimitEdit extends Component {
size="small"
>
<Icon style={{marginRight: "0.3em"}}>alarm</Icon>
{ (minutes === "0" && seconds === "00") ? "sans limite" :
` ${minutes}:${seconds} max`
{ (minutes === "0" && seconds === "00") ?
<FormattedMessage {...messages.nolimit} />
:
<FormattedMessage
{...messages.buttontext}
values={{min: minutes, sec:seconds}}
/>
}
</Button>
</Typography>
<Modal
aria-labelledby="Timer limit edition"
aria-describedby="Edit the maximum speaking time"
aria-labelledby={this.props.intl.formatMessage(messages.modaltitle)}
open={this.state.open}
onClose={this.handleClose}
>
......@@ -98,12 +131,12 @@ class TimerLimitEdit extends Component {
id="modal-title"
className={classes.title}
>
Temps de parole maximum
<FormattedMessage {...messages.modaltitle} />
</Typography>
<TextField
id="maxminutes"
name="minutes"
label="Minutes"
label={this.props.intl.formatMessage(messages.minutes)}
type="number"
className={classes.textField}
InputLabelProps={{
......@@ -115,7 +148,7 @@ class TimerLimitEdit extends Component {
<TextField
id="maxseconds"
name="seconds"
label="Secondes"
label={this.props.intl.formatMessage(messages.seconds)}
type="number"
className={classes.textField}
InputLabelProps={{
......@@ -134,4 +167,4 @@ class TimerLimitEdit extends Component {
}
}
export default withStyles(styles)(TimerLimitEdit);
export default injectIntl(withStyles(styles)(TimerLimitEdit));
......@@ -5,13 +5,44 @@ import configureStore from './store';
import App from './components/App';
//import registerServiceWorker from './registerServiceWorker';
import { addLocaleData } from 'react-intl';
import en from 'react-intl/locale-data/en';
import fr from 'react-intl/locale-data/fr';
import { IntlProvider } from 'react-intl';
import messages_fr from "./locales/fr.json";
addLocaleData([...en, ...fr]);
const localeData = {
'fr': messages_fr,
};
// Define user's language. Different browsers have the user locale defined
// on different fields on the `navigator` object, so we make sure to account
// for these different by checking all of them
const language =
(navigator.languages && navigator.languages[0]) ||
navigator.language ||
navigator.userLanguage;
// Split locales with a region code
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
// Try full locale, try locale without region code, fallback to 'en'
const messages =
localeData[languageWithoutRegionCode] ||
localeData[language] ||
localeData.en;
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<App />
<IntlProvider locale={navigator.language} messages={messages}>
<App />
</IntlProvider>
</Provider>,
document.getElementById('root')
);
......
{
"alreadytalkedbutton.text": "Cette personne a déjà parlé",
"app.cisdude": "Un mec cis",
"app.notcisdude": "Pas un mec cis",
"app.title": "Qui parle ?",
"exportbutton.date": "Date",
"exportbutton.save": "Sauvegarder",
"exportbutton.speakersMan": "Nombre d'intervenants mecs cis",
"exportbutton.speakersOther": "Nombre d'autres intervenants",
"exportbutton.speechesMan": "Interventions de mecs cis",
"exportbutton.speechesOther": "Interventions des autres",
"exportbutton.timeMan": "Temps parlé par des mecs cis",
"exportbutton.timeOther": "Temps parlé par des autres",
"footer.text1": "Très largement inspiré de",
"footer.text2": "En fait, c'est pareil mais avec quelques stats en plus.",
"footer.viewCode": "Voir le code",
"header.title": "Les hommes parlent trop, prouvez-le.",
"stats.forOthers": "pour les autres :",
"stats.header": "Les mecs cis ont parlé :",
"stats.nobody": "Personne n'a encore parlé.",
"stats.speakersPercent": "{value}% des intervenant⋅e⋅s",
"stats.speechesDuration": "Durée moyenne des interventions :",
"stats.speechesPercent": "À {value}% des occasions",
"stats.timePercent": "{value}% du temps",
"timerlimitedit.buttontext": "{min}:{sec} max",
"timerlimitedit.minutes": "Minutes",
"timerlimitedit.modaltitle": "Temps de parole maximum",
"timerlimitedit.nolimit": "sans limite",
"timerlimitedit.seconds": "Secondes"
}
\ No newline at end of file
[
"exportbutton.date",
"timerlimitedit.buttontext",
"timerlimitedit.minutes"
]
\ No newline at end of file