--- a/client/package.json Tue Nov 06 16:19:26 2018 +0100
+++ b/client/package.json Thu Nov 08 16:03:28 2018 +0100
@@ -11,6 +11,7 @@
"bootstrap": "^4.1.3",
"connected-react-router": "^4.5.0",
"i18next": "^11.6.0",
+ "i18next-browser-languagedetector": "^2.2.3",
"immutable": "^3.8.2",
"jquery": "^3.3.1",
"jwt-decode": "^2.2.0",
@@ -19,10 +20,12 @@
"moment": "^2.18.1",
"npm": "^6.4.1",
"popper.js": "^1.14.4",
+ "prop-types": "^15.6.2",
"qs": "^6.5.0",
"ramda": "^0.25.0",
"react": "^16.5.2",
"react-dom": "^16.5.2",
+ "react-i18next": "^8.3.6",
"react-modal": "^3.5.1",
"react-overlays": "^0.8.3",
"react-portal": "^4.1.5",
--- a/client/src/components/CreateGroup.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/CreateGroup.js Thu Nov 08 16:03:28 2018 +0100
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
+import { Trans } from 'react-i18next';
import '../App.css';
import Navbar from './Navbar';
import * as authActions from '../actions/authActions';
@@ -79,22 +80,22 @@
<div className="col-lg-6 offset-md-5">
<div className="panel-login panel panel-default d-flex justify-content-end">
<div className="card-header bg-primary w-50">
- <h5 className="text-center text-secondary font-weight-bold">Nouveau groupe</h5>
+ <h5 className="text-center text-secondary font-weight-bold text-capitalize"><Trans i18nKey="create_group.new_group">nouveau groupe</Trans></h5>
<form className="mt-3" onSubmit={this.submit.bind(this)}>
<div className="form-group mb-2" /*validationState={ errorMessages && ('name' in errorMessages) ? 'error' : null }*/>
- <label className="col-from-label text-secondary font-weight-bold mt-2">Nom</label>
+ <label className="col-from-label text-secondary font-weight-bold mt-2 text-capitalize"><Trans i18nKey="common.name">nom</Trans></label>
<input className="form-control bg-secondary text-primary border-0 w-100" type="text" onChange={this.handleInputChange} name="name" placeholder="Entrez un nom de groupe"/>
{/* { this.renderErrorMessage(errorMessages, 'name') } */}
</div>
<div className="form-group mb-2" /*validationState={ errorMessages && ('description' in errorMessages) ? 'error' : null }*/>
- <label className="col-form-label text-secondary font-weight-bold mt-2">Description</label>
+ <label className="col-form-label text-secondary font-weight-bold mt-2 text-capitalize"><Trans i18nKey="common.description">description</Trans></label>
<textarea className="form-control bg-secondary text-primary border-0 w-100" type="textarea" onChange={this.handleInputChange} name="description" placeholder="Entrez une description de groupe"></textarea>
{/* { this.renderErrorMessage(errorMessages, 'description') } */}
</div>
{/* { this.renderNonFieldErrors(errorMessages) } */}
<div className="text-center">
- <button type="submit" value="Submit" className="btn btn-secondary btn-lg text-primary font-weight-bold m-3" disabled={okDisabled} onClick={this.submit}>Créer</button>
- <button type="button" className="btn btn-irinotes-form text-muted btn-lg font-weight-bold" onClick={this.cancel}>Annuler</button>
+ <button type="submit" value="Submit" className="btn btn-secondary btn-lg text-primary font-weight-bold m-3 text-capitalize" disabled={okDisabled} onClick={this.submit}><Trans i18nKey="common.create">Créer</Trans></button>
+ <button type="button" className="btn btn-irinotes-form text-muted btn-lg font-weight-bold text-capitalize" onClick={this.cancel}><Trans i18nKey="common.cancel">annuler</Trans></button>
</div>
</form>
</div>
--- a/client/src/components/CreateSession.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/CreateSession.js Thu Nov 08 16:03:28 2018 +0100
@@ -2,10 +2,11 @@
import Modal from 'react-modal';
import PropTypes from 'prop-types';
import uuidV1 from 'uuid/v1';
+import { withNamespaces } from 'react-i18next';
import '../App.css';
import './CreateSession.css';
-export default class CreateSession extends Component {
+class CreateSession extends Component {
static propTypes = {
history: PropTypes.object.isRequired,
@@ -69,9 +70,10 @@
render() {
+ const t = this.props.t;
return (
<div className="container-fluid">
- <a className="nav-link" onClick={this.openSessionModal}>Nouvelle session</a>
+ <a className="nav-link" onClick={this.openSessionModal}>{t('create_session.new_session')}</a>
<Modal
className="Modal__Bootstrap modal-dialog ml-5 mt-5 fixed-top w-100"
isOpen={this.state.modalIsOpen}
@@ -81,7 +83,7 @@
<div className="modal-body mt-3">
<form onSubmit={ e => { e.preventDefault() } }>
<div className="form-group">
- <label className="col-form-label text-secondary font-weight-bold pt-3">Titre</label>
+ <label className="col-form-label text-secondary font-weight-bold pt-3 text-capitalize">{t('common.title')}</label>
<input className="form-control text-primary w-100"
name="title"
onChange={ this.onChange }
@@ -90,7 +92,7 @@
/>
</div>
<div className="form-group">
- <label className="col-form-label text-secondary font-weight-bold pt-3 mt-3">Description</label>
+ <label className="col-form-label text-secondary font-weight-bold pt-3 mt-3 text-capitalize">{t('common.description')}</label>
<textarea className="form-control text-primary w-100"
type="textarea"
name="description"
@@ -99,14 +101,14 @@
></textarea>
</div>
<div className="form-group">
- <label className="col-form-label text-secondary font-weight-bold mt-5 ml-5" onClick={this.toggleProtocol}>Protocole de la prise de note {this.state.protocolOpen?<span className="material-icons protocol-toggle"></span>:<span className="material-icons protocol-toggle"></span>}</label>
+ <label className="col-form-label text-secondary font-weight-bold mt-5 ml-5" onClick={this.toggleProtocol}>{t('create_session.protocol')} {this.state.protocolOpen?<span className="material-icons protocol-toggle"></span>:<span className="material-icons protocol-toggle"></span>}</label>
<div className={ "collapse" + (this.state.protocolOpen?'in':'')} >
{/* <pre>{JSON.stringify(this.props.currentSession.protocol, null, 2)}</pre> */}
<pre className=" protocol text-secondary">{JSON.stringify(this.getGroupProtocol(), null, 2)}</pre>
</div>
</div>
<div className="text-center">
- <button id="create-button" type="submit" className="btn btn-secondary btn-lg text-primary font-weight-bold m-3" onClick={this.createSession}>Commencer</button>
+ <button id="create-button" type="submit" className="btn btn-secondary btn-lg text-primary font-weight-bold m-3 text-capitalize" onClick={this.createSession}>{t('common.begin')}</button>
</div>
</form>
</div>
@@ -116,3 +118,5 @@
);
}
}
+
+export default withNamespaces()(CreateSession);
--- a/client/src/components/Login.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/Login.js Thu Nov 08 16:03:28 2018 +0100
@@ -1,6 +1,8 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
+import { Trans } from 'react-i18next';
+
import '../App.css';
import './Login.css';
// import Navbar from './Navbar';
@@ -51,17 +53,26 @@
}
renderNonFieldErrors(errorMessages) {
- console.log(errorMessages);
+
+ if (errorMessages && errorMessages.error) {
+ return (
+ <div className="alert alert-danger mt-4" role="alert">
+ <Trans i18nKey="login.login_error">Unable to log in.</Trans>
+ </div>
+ )
+ }
+
const errors = R.path(['data','non_field_errors'], errorMessages);
if (errors) {
return (
- <div className="alert alert-danger" role="alert">
+ <div className="alert alert-danger mt-4" role="alert">
{ errors.map((message, key) =>
- <p key={ key }>{ message }</p>
+ <p key={ key }><Trans i18nKey="login.credentials_error">{ message }</Trans></p>
) }
</div>
)
}
+
}
render() {
@@ -79,24 +90,24 @@
<h4 className="text-center card-title font-weight-bold text-lg">IRI Notes</h4>
<form className="pt-3 ml-5 pl-5" onSubmit={this.submit}>
<div className="form-group mb-2 ml-3 w-75" >
- <label className="col-form-label text-primary font-weight-bold mt-2">Nom d'utilisateur</label>
+ <label className="col-form-label text-primary font-weight-bold mt-2 text-capitalize"><Trans i18nKey="common.username">Nom d'utilisateur</Trans></label>
<input className="form-control bg-irinotes-form border-0 text-muted" type="text" onChange={this.handleInputChange} name="username" />
{/* { this.renderErrorMessage(errorMessages, 'username') } */}
</div>
<div className="form-group mb-2 ml-3 w-75">
- <label className="col-form-label text-primary font-weight-bold mt-2">Mot de passe</label>
+ <label className="col-form-label text-primary font-weight-bold mt-2 text-capitalize"><Trans i18nKey="common.password">Mot de passe</Trans></label>
<input className="form-control bg-irinotes-form border-0 text-muted" type="password" onChange={this.handleInputChange} name="password" />
{/* { this.renderErrorMessage(errorMessages, 'password') } */}
</div>
{ this.renderNonFieldErrors(errorMessages) }
<div className="text-center mr-5 pr-5">
- <button type="submit" className="btn btn-primary btn-lg text-secondary font-weight-bold mt-3">Se connecter</button>
+ <button type="submit" className="btn btn-primary btn-lg text-secondary font-weight-bold mt-3 text-capitalize"><Trans i18nKey='common.connect'>Se connecter</Trans></button>
</div>
</form>
</div>
</div>
<p className="text-center">
- <a className="text-muted" href="/register" onClick={ this.onClickRegister }>Pas encore inscrit ? Créer un compte.</a>
+ <a className="text-muted" href="/register" onClick={ this.onClickRegister }><Trans i18nKey='login.registration_message'>Pas encore inscrit ? Créer un compte.</Trans></a>
</p>
</div>
</div>
--- a/client/src/components/Navbar.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/Navbar.js Thu Nov 08 16:03:28 2018 +0100
@@ -5,6 +5,8 @@
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { bindActionCreators } from 'redux';
+import { withNamespaces, Trans } from 'react-i18next';
+
// import logo from './logo.svg';
import Modal from 'react-modal';
import * as authActions from '../actions/authActions';
@@ -60,8 +62,7 @@
const OffLineMessage = ({isAuthenticated}) => {
if (!isAuthenticated) {
return (
- <span className="sticky-top text-warning text-right float-right mr-4 offline-message">Vous êtes en mode Offline. N'oubliez pas de
- vous connecter ou de créer un compte pour sauvegarder vos sessions</span>
+ <span className="sticky-top text-warning text-right float-right mr-4 offline-message"><Trans i18nKey="navbar.offline_message"></Trans></span>
);
}
return (
@@ -146,6 +147,7 @@
}
render() {
+ const t = this.props.t;
return (
<div>
<nav className="navbar navbar-expand-lg navbar-light bg-primary sticky-top">
@@ -162,7 +164,7 @@
<div className="collapse navbar-collapse text-center" id="navbarSupportedContent">
<ul className="navbar-nav mr-auto">
<li className="nav-item text-secondary">
- <a className="nav-link " onClick={this.onClickSessions} href="/sessions">Sessions</a>
+ <a className="nav-link text-capitalize" onClick={this.onClickSessions} href="/sessions">{t('common.session', {count: 2})}</a>
{/* <CreateSession
history={this.props.history}
group={this.props.currentGroup}
@@ -197,12 +199,12 @@
<span id="logout-close-modal-button" className="material-icons p-0 text-right" onClick={ this.handleModalCloseRequest }>close</span>
<div className="modal-body text-center">
<span className="material-icons modal-warning text-info pb-5">warning</span>
- <p className="modal-text">
+ <p className="modal-text"><Trans i18nKey="navbar.logout_modal">
Certaines sessions n'ont pas encore été sauvegardées.
<br />
Si vous continuez, elles seront perdues.
- </p>
- <button type="button" className="btn btn-danger text-secondary font-weight-bold py-1 px-2 mb-3" id="logout-modal-button" onClick={ this.confirmLogout }>Confirmer</button>
+ </Trans></p>
+ <button type="button" className="btn btn-danger text-secondary font-weight-bold py-1 px-2 mb-3 text-capitalize" id="logout-modal-button" onClick={ this.confirmLogout }>{t('common.confirm')}</button>
</div>
</div>
</Modal>
@@ -239,4 +241,4 @@
}
}
-export default connect(mapStateToProps, mapDispatchToProps)(withRouter(AppNavbar));
+export default withNamespaces()(connect(mapStateToProps, mapDispatchToProps)(withRouter(AppNavbar)));
--- a/client/src/components/NavbarLogin.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/NavbarLogin.js Thu Nov 08 16:03:28 2018 +0100
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
+import { Trans } from 'react-i18next';
export default class LoginNav extends Component {
@@ -54,15 +55,15 @@
<span className="caret"></span>
</a>
<div className={`dropdown-menu dropdown-menu-right bg-primary border-0 ${this.state.showDropdown?'show':''}`} aria-labelledby="navbarDropdown">
- <a className="dropdown-item bg-primary text-secondary font-weight-bold" onClick={this.onClickSettings}>Paramètres</a>
- <a className="dropdown-item bg-primary text-secondary font-weight-bold" onClick={onLogout}>Se déconnecter</a>
+ <a className="dropdown-item bg-primary text-secondary font-weight-bold text-capitalize" onClick={this.onClickSettings}><Trans i18nKey='common.parameters'>Paramètres</Trans></a>
+ <a className="dropdown-item bg-primary text-secondary font-weight-bold text-capitalize" onClick={onLogout}><Trans i18nKey='common.disconnect'>Se déconnecter</Trans></a>
</div>
</li>
);
} else {
return (
<li className="nav-item">
- <a className="nav-link" onClick={this.onClickLogin} href="/login">Se connecter</a>
+ <a className="nav-link text-capitalize" onClick={this.onClickLogin} href="/login"><Trans i18nKey='common.connect'>Se connecter</Trans></a>
</li>
);
}
--- a/client/src/components/Note.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/Note.js Thu Nov 08 16:03:28 2018 +0100
@@ -6,6 +6,18 @@
class Note extends Component {
+ constructor(props) {
+ super(props);
+ this.editorInst = React.createRef();
+ }
+
+ get editor() {
+ if(this.editorInst && this.editorInst.current) {
+ return this.editorInst.current;
+ }
+ return null;
+ }
+
onClickDelete = (e) => {
e.preventDefault();
e.stopPropagation();
@@ -15,10 +27,10 @@
onClickButton = (e) => {
- const plain = this.refs.editor.asPlain();
- const raw = this.refs.editor.asRaw();
- const html = this.refs.editor.asHtml();
- const categories = this.refs.editor.asCategories();
+ const plain = this.editor.asPlain();
+ const raw = this.editor.asRaw();
+ const html = this.editor.asHtml();
+ const categories = this.editor.asCategories();
// const marginComment = this.marginComment.value;
const data = {
@@ -43,7 +55,7 @@
if (this.props.isEditing) {
return (
<div className="note-content w-100 pl-2 pt-2">
- <SlateEditor ref="editor"
+ <SlateEditor editorRef={this.editorInst}
onButtonClick={ this.onClickButton }
note={ this.props.note }
annotationCategories={ this.props.annotationCategories } />
--- a/client/src/components/NoteInput.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/NoteInput.js Thu Nov 08 16:03:28 2018 +0100
@@ -6,6 +6,14 @@
class NoteInput extends Component {
+ constructor(props) {
+ super(props);
+ this.editorInst = React.createRef();
+ }
+
+ get editor() {
+ return this.editorInst.current;
+ }
state = {
buttonDisabled: false,
startedAt: null,
@@ -22,10 +30,10 @@
onAddNoteClick = () => {
- const plain = this.refs.editor.asPlain();
- const raw = this.refs.editor.asRaw();
- const html = this.refs.editor.asHtml();
- const categories = this.refs.editor.asCategories();
+ const plain = this.editor.asPlain();
+ const raw = this.editor.asRaw();
+ const html = this.editor.asHtml();
+ const categories = this.editor.asCategories();
// const marginComment = this.marginComment.value;
this.props.addNote(this.props.session, {
@@ -40,8 +48,8 @@
});
- this.refs.editor.clear();
- setTimeout(() => this.refs.editor.focus(), 250);
+ this.editor.clear();
+ setTimeout(() => this.editor.focus(), 250);
}
onCheckboxChange = (e) => {
@@ -49,8 +57,10 @@
}
componentDidMount() {
- const text = this.refs.editor.asPlain();
- this.setState({ buttonDisabled: text.length === 0 });
+ if(this.editor) {
+ const text = this.editor.asPlain();
+ this.setState({ buttonDisabled: text.length === 0 });
+ }
}
render() {
@@ -58,7 +68,7 @@
<form>
<div className="editor mb-3">
<div className="editor-left sticky-bottom px-2">
- <SlateEditor ref="editor"
+ <SlateEditor editorRef={this.editorInst}
onChange={this.onEditorChange}
onEnterKeyDown={this.onAddNoteClick}
onButtonClick={this.onAddNoteClick}
--- a/client/src/components/NotesList.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/NotesList.js Thu Nov 08 16:03:28 2018 +0100
@@ -3,6 +3,7 @@
import Modal from 'react-modal';
import Note from './Note';
import './NoteList.css';
+import { Trans } from 'react-i18next';
class NotesList extends Component {
constructor(props) {
@@ -103,8 +104,8 @@
<div id="delete-note-modal" className="modal-content">
<span id="delete-note-close-modal-button" className="material-icons text-right" onClick={ this.handleModalCloseRequest }>close</span>
<div className="modal-body text-center">
- <span className="modal-text">Supprimer cette note ?</span>
- <button type="button" className="btn btn-danger text-secondary font-weight-bold py-1 px-2 ml-3" id="delete-note-modal-button" onClick={ this.deleteNote }>Supprimer</button>
+ <span className="modal-text"><Trans i18nKey='notes_list.delete_note_msg'>Supprimer cette note ?</Trans></span>
+ <button type="button" className="btn btn-danger text-secondary font-weight-bold py-1 px-2 ml-3" id="delete-note-modal-button" onClick={ this.deleteNote }><Trans i18nKey='common.delete'>Supprimer</Trans></button>
</div>
</div>
</Modal>
--- a/client/src/components/Session.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/Session.js Thu Nov 08 16:03:28 2018 +0100
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
+import { Trans } from 'react-i18next';
import '../App.css';
import './Session.css';
import Navbar from './Navbar';
@@ -44,7 +45,7 @@
if (this.state.screenSummary === 0) {
return (
<div>
- <a onClick={this.toggleScreenSummary} className ="text-primary">Afficher le protocole d'annotation</a>
+ <a onClick={this.toggleScreenSummary} className ="text-primary"><Trans i18nKey="session.protocol_display">Afficher le protocole d'annotation</Trans></a>
<SessionSummary notes={this.props.notes} />
</div>
);
@@ -53,7 +54,7 @@
if (this.state.screenSummary === 1) {
return (
<div>
- <a onClick={this.toggleScreenSummary} className ="text-primary">Afficher le résumé de la session</a>
+ <a onClick={this.toggleScreenSummary} className ="text-primary"><Trans i18nKey="session.summary_display">Afficher le résumé de la session</Trans></a>
<ProtocolSummary />
</div>
);
--- a/client/src/components/SessionList.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/SessionList.js Thu Nov 08 16:03:28 2018 +0100
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
+import { Trans } from 'react-i18next';
import Modal from 'react-modal';
import moment from 'moment';
import '../App.css';
@@ -67,7 +68,7 @@
if (this.props.sessions.length === 0) {
return (
- <h1 className="text-primary text-center mt-5 pt-5">vous n'avez créé aucune session pour le moment</h1>
+ <h1 className="text-primary text-center mt-5 pt-5"><Trans i18nKey="session_list.no_session">vous n'avez créé aucune session pour le moment</Trans></h1>
);
}
}
@@ -121,8 +122,8 @@
<div id="delete-session-modal" className="modal-content">
<span id="delete-session-close-modal-button" className="material-icons text-right" onClick={ this.handleModalCloseRequest }>close</span>
<div className="modal-body text-center">
- <span className="modal-text">Supprimer cette session ?</span>
- <button type="button" className="btn btn-danger text-secondary font-weight-bold py-1 px-2 ml-3" id="delete-session-modal-button" onClick={ this.deleteSession }>Supprimer</button>
+ <span className="modal-text"><Trans i18nKey="session_list.delete_modal_message">Supprimer cette session ?</Trans></span>
+ <button type="button" className="btn btn-danger text-secondary font-weight-bold py-1 px-2 ml-3 text-capitalize" id="delete-session-modal-button" onClick={ this.deleteSession }><Trans i18nKey="common.delete">Supprimer</Trans></button>
</div>
</div>
</Modal>
--- a/client/src/components/SlateEditor/index.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/components/SlateEditor/index.js Thu Nov 08 16:03:28 2018 +0100
@@ -1,11 +1,13 @@
-import { Value } from 'slate'
-import Plain from 'slate-plain-serializer'
-import { Editor } from 'slate-react'
-import React from 'react'
-import { Portal } from 'react-portal'
-import HtmlSerializer from './HtmlSerializer'
-import AnnotationPlugin from './AnnotationPlugin'
-import CategoriesTooltip from './CategoriesTooltip'
+import { Value } from 'slate';
+import Plain from 'slate-plain-serializer';
+import { Editor } from 'slate-react';
+import React from 'react';
+import { Portal } from 'react-portal';
+import { Trans, withNamespaces } from 'react-i18next';
+import * as R from 'ramda';
+import HtmlSerializer from './HtmlSerializer';
+import AnnotationPlugin from './AnnotationPlugin';
+import CategoriesTooltip from './CategoriesTooltip';
import './SlateEditor.css';
import { now } from '../../utils';
import { defaultAnnotationsCategories } from '../../constants';
@@ -114,6 +116,8 @@
isCheckboxChecked: false,
enterKeyValue: 0,
};
+
+ this.editor = React.createRef();
}
componentDidMount = () => {
@@ -155,10 +159,11 @@
Object.assign(newState, { startedAt: now() });
}
+ const oldState = R.clone(this.state);
this.setState(newState)
if (typeof this.props.onChange === 'function') {
- this.props.onChange(newState);
+ this.props.onChange(R.clone(this.state), oldState, newState);
}
}
@@ -172,7 +177,7 @@
hasMark = type => {
const { value } = this.state
return value.activeMarks.some(mark => mark.type === type)
-}
+ }
/**
* Check if the any of the currently selected blocks are of `type`.
@@ -184,7 +189,7 @@
hasBlock = type => {
const { value } = this.state
return value.blocks.some(node => node.type === type)
-}
+ }
asPlain = () => {
return Plain.serialize(this.state.value);
@@ -213,10 +218,12 @@
}
focus = () => {
- this.refs.editor.focus();
+ if(this.editor.current) {
+ this.editor.current.focus();
+ }
}
- /**
+ /**
* When a mark button is clicked, toggle the current mark.
*
* @param {Event} e
@@ -516,17 +523,18 @@
return (
<div className="checkbox float-right">
<label className="mr-2">
- <input type="checkbox" checked={this.props.isChecked} onChange={this.onCheckboxChange} /><small className="text-muted ml-1"> Appuyer sur <kbd className="bg-irinotes-form text-muted ml-1">Entrée</kbd> pour ajouter une note</small>
+ <input type="checkbox" checked={this.props.isChecked} onChange={this.onCheckboxChange} /><small className="text-muted ml-1"><Trans i18nKey="slate_editor.press_enter_msg">Appuyer sur <kbd className="bg-irinotes-form text-muted ml-1">Entrée</kbd> pour ajouter une note</Trans></small>
</label>
</div>
)
}
renderToolbarButtons = () => {
+ const t = this.props.t;
return (
<div>
- <button type="button" id="btn-editor" className="btn btn-primary btn-sm text-secondary font-weight-bold float-right" disabled={this.props.isButtonDisabled} onClick={this.onButtonClick}>
- { this.props.note ? 'Sauvegarder' : 'Ajouter' }
+ <button type="button" id="btn-editor" className="btn btn-primary btn-sm text-secondary font-weight-bold float-right text-capitalize" disabled={this.props.isButtonDisabled} onClick={this.onButtonClick}>
+ { this.props.note ? t('common.save') : t('common.add') }
</button>
{ !this.props.note && this.renderToolbarCheckbox() }
</div>
@@ -627,13 +635,14 @@
*/
renderEditor = () => {
+ const t = this.props.t;
return (
<div className="editor-slatejs p-2">
{this.renderHoveringMenu()}
<Editor
- ref="editor"
+ ref={this.editor}
spellCheck
- placeholder={'Votre espace de prise de note...'}
+ placeholder={t('slate_editor.placeholder')}
schema={schema}
plugins={plugins}
value={this.state.value}
@@ -691,4 +700,12 @@
* Export.
*/
-export default SlateEditor
+export default withNamespaces("", {
+ innerRef: (ref) => {
+ const editorRef = (ref && ref.props) ? ref.props.editorRef : null;
+ if(editorRef && editorRef.hasOwnProperty('current')) {
+ editorRef.current = ref;
+ }
+ }
+})(SlateEditor);
+// export default SlateEditor;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/i18n.js Thu Nov 08 16:03:28 2018 +0100
@@ -0,0 +1,31 @@
+import i18n from "i18next";
+import { reactI18nextModule } from "react-i18next";
+import detector from "i18next-browser-languagedetector";
+
+import translationFR from "./locales/fr/translation.json";
+import translationEN from "./locales/en/translation.json";
+
+// the translations
+const resources = {
+ fr: {
+ translation: translationFR
+ },
+ en: {
+ translation: translationEN
+ }
+};
+
+i18n
+ .use(detector)
+ .use(reactI18nextModule) // passes i18n down to react-i18next
+ .init({
+ resources,
+ debug: true,
+ fallbackLng: "fr",
+
+ interpolation: {
+ escapeValue: false // react already safes from xss
+ }
+ });
+
+export default i18n;
--- a/client/src/index.js Tue Nov 06 16:19:26 2018 +0100
+++ b/client/src/index.js Thu Nov 08 16:03:28 2018 +0100
@@ -20,6 +20,8 @@
import config from './config';
import AuthenticatedRoute from './misc/AuthenticatedRoute';
+import './i18n';
+
const history = createHistory({
basename: config.basename
});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/locales/en/translation.json Thu Nov 08 16:03:28 2018 +0100
@@ -0,0 +1,52 @@
+{
+ "create_session": {
+ "new_session": "New session",
+ "protocol": "Session protocol"
+ },
+ "create_group": {
+ "new_group": "new group"
+ },
+ "navbar": {
+ "offline_message": "you are offline. To save your sessions, do not forget to connect or to create a new account.",
+ "logout_modal": "Some sessions were not saved.<1></1>If you continue, they will be lost."
+ },
+ "login": {
+ "registration_message": "No account yet? Please register.",
+ "credentials_error": "Unable to log in with provided credentials.",
+ "login_error": "Unable to log in."
+ },
+ "session": {
+ "protocol_display": "Display the annotation protocol",
+ "summary_display": "Display the session summary"
+ },
+ "session_list": {
+ "no_session": "you didn't create any session.",
+ "delete_modal_message": "Delete this session?"
+ },
+ "notes_list": {
+ "delete_note_msg": "Delete this note?"
+ },
+ "slate_editor": {
+ "press_enter_msg": "Press <1>Enter</1> to add a note",
+ "placeholder": "Type your note here..."
+ },
+ "common": {
+ "title": "title",
+ "description": "description",
+ "begin": "begin",
+ "session": "session",
+ "session_plural": "sessions",
+ "confirm": "confirm",
+ "connect": "connect",
+ "disconnect": "disconnect",
+ "parameters": "parameters",
+ "username": "username",
+ "password": "password",
+ "delete": "delete",
+ "name": "name",
+ "create": "create",
+ "cancel": "cancel",
+ "add": "add",
+ "save": "save"
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/locales/fr/translation.json Thu Nov 08 16:03:28 2018 +0100
@@ -0,0 +1,52 @@
+{
+ "create_session": {
+ "new_session": "Nouvelle session",
+ "protocol": "Protocole de la prise de note"
+ },
+ "create_group": {
+ "new_group": "nouveau groupe"
+ },
+ "navbar": {
+ "offline_message": "Vous êtes en mode Offline. N'oubliez pas de vous connecter ou de créer un compte pour sauvegarder vos sessions",
+ "logout_modal": "Certaines sessions n'ont pas encore été sauvegardées.<1></1>Si vous continuez, elles seront perdues."
+ },
+ "login": {
+ "registration_message": "Pas encore inscrit ? Créer un compte.",
+ "credentials_error": "Impossible de se connecter avec les identifiants fournis.",
+ "login_error": "Impossible de se connecter."
+ },
+ "notes_list": {
+ "delete_note_msg": "Supprimer cette note ?"
+ },
+ "session": {
+ "protocol_display": "Afficher le protocole d'annotation",
+ "summary_display": "Afficher le résumé de la session"
+ },
+ "session_list": {
+ "no_session": "vous n'avez créé aucune session pour le moment",
+ "delete_modal_message": "Supprimer cette session ?"
+ },
+ "slate_editor": {
+ "press_enter_msg": "Appuyer sur <1>Entrée</1> pour ajouter une note",
+ "placeholder": "Votre espace de prise de note..."
+ },
+ "common": {
+ "title": "titre",
+ "description": "description",
+ "begin" : "commencer",
+ "session": "session",
+ "session_plural": "sessions",
+ "confirm": "confirmer",
+ "connect": "se connecter",
+ "disconnect": "se déconnecter",
+ "parameters": "paramètres",
+ "username": "nom d'utilisateur",
+ "password": "mot de passe",
+ "delete": "supprimer",
+ "name": "nom",
+ "create": "créer",
+ "cancel": "annuler",
+ "add": "ajouter",
+ "save": "sauvegarder"
+ }
+}
--- a/client/yarn.lock Tue Nov 06 16:19:26 2018 +0100
+++ b/client/yarn.lock Thu Nov 08 16:03:28 2018 +0100
@@ -1957,6 +1957,11 @@
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
+core-js@^1.0.0:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
+ integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
+
core-js@^2.4.0, core-js@^2.5.0:
version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
@@ -2012,6 +2017,14 @@
safe-buffer "^5.0.1"
sha.js "^2.4.8"
+create-react-context@0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
+ integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==
+ dependencies:
+ fbjs "^0.8.0"
+ gud "^1.0.0"
+
cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -3202,6 +3215,19 @@
dependencies:
bser "^2.0.0"
+fbjs@^0.8.0:
+ version "0.8.17"
+ resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
+ integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
+ dependencies:
+ core-js "^1.0.0"
+ isomorphic-fetch "^2.1.1"
+ loose-envify "^1.0.0"
+ object-assign "^4.1.0"
+ promise "^7.1.1"
+ setimmediate "^1.0.5"
+ ua-parser-js "^0.7.18"
+
figgy-pudding@^3.0.0, figgy-pudding@^3.1.0, figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
@@ -3666,6 +3692,11 @@
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
+gud@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0"
+ integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==
+
gzip-size@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520"
@@ -3801,6 +3832,13 @@
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
+hoist-non-react-statics@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz#fba3e7df0210eb9447757ca1a7cb607162f0a364"
+ integrity sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==
+ dependencies:
+ react-is "^16.3.2"
+
hoist-non-react-statics@^2.5.0:
version "2.5.5"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
@@ -3865,6 +3903,13 @@
relateurl "0.2.x"
uglify-js "3.4.x"
+html-parse-stringify2@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a"
+ integrity sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=
+ dependencies:
+ void-elements "^2.0.1"
+
html-webpack-plugin@2.29.0:
version "2.29.0"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.29.0.tgz#e987f421853d3b6938c8c4c8171842e5fd17af23"
@@ -3977,6 +4022,11 @@
dependencies:
ms "^2.0.0"
+i18next-browser-languagedetector@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.2.3.tgz#4196a9964b6d51b76254706a267ba746c9ca19de"
+ integrity sha512-sJZ2n9Vgax0vGer23hJMwyO3FRO7P0dq2DXZPXWE329g3snfJUcw+S24Mp3lqJaxL/0McDu4BD75ds6pzIfhhw==
+
i18next@^11.6.0:
version "11.10.2"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-11.10.2.tgz#e5f10346f6320ecf15595419926c25255381a56c"
@@ -4443,7 +4493,7 @@
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-root/-/is-root-1.0.0.tgz#07b6c233bc394cd9d02ba15c966bd6660d6342d5"
-is-stream@^1.0.0, is-stream@^1.1.0:
+is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -4524,6 +4574,14 @@
resolved "https://registry.yarnpkg.com/isomorphic-base64/-/isomorphic-base64-1.0.2.tgz#f426aae82569ba8a4ec5ca73ad21a44ab1ee7803"
integrity sha1-9Caq6CVpuopOxcpzrSGkSrHueAM=
+isomorphic-fetch@^2.1.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
+ integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
+ dependencies:
+ node-fetch "^1.0.1"
+ whatwg-fetch ">=0.10.0"
+
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -5721,6 +5779,14 @@
json-parse-better-errors "^1.0.0"
safe-buffer "^5.1.1"
+node-fetch@^1.0.1:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
+ integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
+ dependencies:
+ encoding "^0.1.11"
+ is-stream "^1.0.1"
+
node-forge@0.7.5:
version "0.7.5"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
@@ -6991,6 +7057,13 @@
dependencies:
asap "~2.0.3"
+promise@^7.1.1:
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
+ integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
+ dependencies:
+ asap "~2.0.3"
+
promzard@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee"
@@ -7244,6 +7317,16 @@
version "4.0.1"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89"
+react-i18next@^8.3.6:
+ version "8.3.6"
+ resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-8.3.6.tgz#0e36f0b3906ddaa3002dfabc24b047caf6b7fb88"
+ integrity sha512-yhEYij0ssBG+m9n8l3ir8mHq4y+IsGN6Nd2CHg0XRFxF+Jj/Vje4Vjm2j2MReAncgs4i7uTnCKWVMUZAuWmX+g==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+ create-react-context "0.2.3"
+ hoist-non-react-statics "3.0.1"
+ html-parse-stringify2 "2.0.1"
+
react-immutable-proptypes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4"
@@ -8041,7 +8124,7 @@
is-plain-object "^2.0.3"
split-string "^3.0.1"
-setimmediate@^1.0.4:
+setimmediate@^1.0.4, setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
@@ -8875,6 +8958,11 @@
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+ua-parser-js@^0.7.18:
+ version "0.7.19"
+ resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
+ integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
+
uglify-js@3.4.x, uglify-js@^3.0.13, uglify-js@^3.1.4:
version "3.4.9"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
@@ -9122,6 +9210,11 @@
dependencies:
indexof "0.0.1"
+void-elements@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
+ integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
+
walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
@@ -9277,6 +9370,11 @@
version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
+whatwg-fetch@>=0.10.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
+ integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
+
whatwg-url@^4.3.0:
version "4.8.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0"