Skip to content

Commit

Permalink
Add i18n support (en and fr bundled for now)
Browse files Browse the repository at this point in the history
  • Loading branch information
paradoxxxzero committed May 4, 2018
1 parent a8e7a63 commit daad756
Show file tree
Hide file tree
Showing 22 changed files with 211 additions and 109 deletions.
3 changes: 2 additions & 1 deletion src/Field.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Field extends React.Component {
item,
transientItem,
fields,
i18n,
refs,
errors,
focused,
Expand All @@ -54,7 +55,6 @@ class Field extends React.Component {
if (!transientItem) {
throw new Error('Field must be used inside Form')
}

const itemValue = get(item, name)
const transientValue = get(transientItem, name)
const modified = itemValue !== transientValue
Expand All @@ -81,6 +81,7 @@ class Field extends React.Component {
type={type}
ref={ref => (refs[name] = ref)}
readOnly={readOnly}
i18n={i18n}
className={b.e('field').m({ type, focus, modified })}
onFocus={e => handleFocus(name, e)}
onBlur={e => handleBlur(name, e)}
Expand Down
29 changes: 27 additions & 2 deletions src/Formol.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import SelectMenuField from './fields/SelectMenuField'
import SwitchField from './fields/SwitchField'
import TextareaField from './fields/TextareaField'
import { FormolContext } from './FormolContext'
import en from './i18n/en'
import fr from './i18n/fr'
import { block } from './utils'
import {
alignKeysRec,
Expand All @@ -37,7 +39,7 @@ export default class Formol extends React.Component {
calendar: CalendarField,
file: FileField,
files: FileField,
'password-strengh': PasswordField,
'password-strength': PasswordField,
select: SelectField,
'select-menu': SelectMenuField,
switch: SwitchField,
Expand All @@ -46,23 +48,31 @@ export default class Formol extends React.Component {
radios: RadiosField,
checkboxes: CheckboxesField,
}

static i18n = {
en,
fr,
}

static defaultProps = {
item: {},
fields: {},
i18n: 'en',
getPk: item => item,
onError: console.error.bind(console),
}

constructor(props) {
super(props)
const { item, fields, readOnly } = props
const { item, fields, i18n, readOnly } = props
this.ref = {}
this.state = {
disablePrompt: false,
context: {
item,
transientItem: this.fromItem(item),
fields: { ...Formol.defaultFields, ...fields },
i18n: Formol.i18n[i18n],
refs: this.ref,
errors: {},
focused: null,
Expand All @@ -84,6 +94,21 @@ export default class Formol extends React.Component {
transientItem: this.fromItem(nextProps.item),
})
}
if (nextProps.readOnly !== this.props.readOnly) {
this.setContextState({
readOnly: nextProps.readOnly,
})
}
if (nextProps.fields !== this.props.fields) {
this.setContextState({
fields: { ...Formol.defaultFields, ...nextProps.fields },
})
}
if (nextProps.i18n !== this.props.i18n) {
this.setContextState({
i18n: Formol.i18n[nextProps.i18n],
})
}
}

setContextState(context) {
Expand Down
20 changes: 14 additions & 6 deletions src/async/CalendarField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React from 'react'
import DayPickerInput from 'react-day-picker/DayPickerInput'

import { block } from '../utils'
import CalendarLocale, { locale } from '../utils/locale'
import { CalendarFr, localeFr } from './CalendarFieldLocale'

const isDate = d => d instanceof Date && !isNaN(d.valueOf())
const datePattern = /^([0-2][0-9]|30|31)\/(0[0-9]|10|11|12)\/[0-9]{4}$/
Expand All @@ -16,7 +16,8 @@ const voidIfNaN = d => (isNaN(d.valueOf()) ? void 0 : d)
@block
export default class CalendarField extends React.Component {
handleChange(newDate) {
const { onChange } = this.props
const { i18n, onChange } = this.props
const locale = i18n.calendar.locale === 'fr' ? localeFr : void 0
onChange(
isDate(newDate)
? format(newDate, 'YYYY-MM-DD', { locale })
Expand All @@ -27,13 +28,16 @@ export default class CalendarField extends React.Component {
render(b) {
const {
className,
i18n,
placeholder,
format: userFormat,
value,
readOnly,
disabled,
...inputProps
} = this.props
const dateFormat = userFormat || 'DD/MM/YYYY'
const locale = i18n.calendar.locale === 'fr' ? localeFr : void 0
const dateFormat = userFormat || i18n.calendar.dateFormat
const maybeDate = parse(value, 'YYYY-MM-DD', new Date(), { locale })
const date = isDate(maybeDate) ? maybeDate : value
if (readOnly || disabled) {
Expand All @@ -57,20 +61,24 @@ export default class CalendarField extends React.Component {
overlay: b.e('overlay').mix('DayPickerInput-Overlay').s,
}}
value={date}
placeholder="jj/mm/aaaa"
placeholder={placeholder || dateFormat}
format={dateFormat}
formatDate={(value_, format_) => format(value_, format_, { locale })}
parseDate={(value_, format_) =>
datePattern.exec(value_)
? voidIfNaN(parse(value_, format_, new Date(), { locale }))
: void 0
}
dayPickerProps={{ locale: 'fr', localeUtils: CalendarLocale }}
dayPickerProps={
i18n.calendar.locale === 'fr'
? { locale: 'fr', localeUtils: CalendarFr }
: {}
}
onDayChange={o => this.handleChange(o)}
inputProps={{
...inputProps,
pattern: datePattern.source,
title: 'La date doit être au format jj/mm/aaaa',
title: i18n.calendar.dateError,
className: b.e('field').mix(className),
}}
/>
Expand Down
42 changes: 42 additions & 0 deletions src/async/CalendarFieldLocale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { format, parse, toDate } from 'date-fns'
import localeFr from 'date-fns/locale/fr'

export { localeFr }

export const localeDateFormatFr = (date, dateFormat = 'D MMMM YYYY') =>
format(toDate(date), dateFormat, { locale: localeFr })

export const formatDayFr = day => localeDateFormatFr(day, 'ddd ll')

export const formatMonthTitleFr = date => localeDateFormatFr(date, 'MMMM YYYY')

export const formatWeekdayShortFr = day =>
localeFr.localize.weekday(day, { type: 'short' }).replace(/.$/, '')

export const formatWeekdayLongFr = day =>
localeFr.localize.weekday(day, { type: 'long' })

// Fix this when date-fns has a first day of week
export const getFirstDayOfWeekFr = () => 1

export const getMonthsFr = () =>
Array(12)
.fill()
.map((_, i) => localeFr.localize.months(i, { type: 'long' }))

export const formatDateFr = (date, format = 'L') =>
localeDateFormatFr(date, format)

export const parseDateFr = (str, format = 'L') =>
parse(str, format, new Date(), { locale: localeFr })

export const CalendarFr = {
formatDay: formatDayFr,
formatMonthTitle: formatMonthTitleFr,
formatWeekdayShort: formatWeekdayShortFr,
formatWeekdayLong: formatWeekdayLongFr,
getFirstDayOfWeek: getFirstDayOfWeekFr,
getMonths: getMonthsFr,
formatDate: formatDateFr,
parseDate: parseDateFr,
}
11 changes: 5 additions & 6 deletions src/async/FileField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default class FileField extends React.Component {
}

async handleDrop(accepted, rejected, { target }) {
const { multiple, rejectedMessage, onChange } = this.props
const { multiple, rejectedMessage, i18n, onChange } = this.props
const value = multiple ? this.props.value || [] : []
accepted = await Promise.all(
accepted.map(async file => ({
Expand Down Expand Up @@ -86,9 +86,7 @@ export default class FileField extends React.Component {
if (erroredFiles.length) {
const err =
rejectedMessage ||
(multiple
? 'Certains de vos fichier ne sont pas valides.'
: 'Veuillez sélectionner un fichier valide.')
(multiple ? i18n.file.rejectedMultiple : i18n.file.rejected)
this.setState({ error: err })
target.setCustomValidity(err)
} else {
Expand All @@ -104,8 +102,9 @@ export default class FileField extends React.Component {
render(b) {
const {
name,
accept,
value,
i18n,
accept,
placeholder,
onChange,
className,
Expand Down Expand Up @@ -160,7 +159,7 @@ export default class FileField extends React.Component {
) : null
if (readOnly || disabled) {
return (
<div className={b.mix(className)}>{preview || 'Aucun fichier'}</div>
<div className={b.mix(className)}>{preview || i18n.file.noFile}</div>
)
}

Expand Down
4 changes: 3 additions & 1 deletion src/async/HTMLField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,14 @@ export default class HTMLField extends React.Component {
render(b) {
const {
className,
i18n,
readOnly,
onFocus,
onBlur,
onKeyDown,
required,
toolbar,
placeholder,
} = this.props
const { editorState } = this.state
const HTMLToolbar = toolbar || {
Expand All @@ -108,7 +110,7 @@ export default class HTMLField extends React.Component {
onEditorStateChange={state => this.onChange(state)}
onFocus={e => onFocus(e)}
onKeyDown={e => onKeyDown(e)}
placeholder="Entrez votre texte ici…"
placeholder={placeholder || i18n.html.placeholder}
readOnly={readOnly}
required={required}
toolbar={HTMLToolbar}
Expand Down
16 changes: 8 additions & 8 deletions src/async/PasswordField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ import { block } from '../utils'
@block
export default class PasswordField extends React.Component {
render(b) {
const { className, name, value, onChange, ...props } = this.props
const { className, name, value, i18n, onChange, ...props } = this.props
return (
<ReactPasswordStrength
changeCallback={({ password, isValid }) =>
onChange(isValid ? password : false)
onChange(isValid ? password : void 0)
}
defaultValue={value}
className={b.mix(className).s}
inputProps={{ name, ...props, type: 'password' }}
scoreWords={[
'très peu sécurisé',
'peu sécurisé',
'correct',
'sécurisé',
'très sécurisé',
i18n.passwordStrength.weak,
i18n.passwordStrength.okay,
i18n.passwordStrength.good,
i18n.passwordStrength.strong,
i18n.passwordStrength.stronger,
]}
tooShortWord="trop court"
tooShortWord={i18n.passwordStrength.tooshort}
/>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/async/SelectMenuField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default class SelectMenuField extends React.Component {

render(b) {
const {
i18n,
className,
multiple,
readOnly,
Expand Down
3 changes: 2 additions & 1 deletion src/fields/BooleanField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import React from 'react'
// eslint-disable-next-line react/prefer-stateless-function
export default class BooleanField extends React.Component {
render() {
const { value, readOnly, onChange, ...props } = this.props
// eslint-disable-next-line no-unused-vars
const { value, i18n, readOnly, onChange, ...props } = this.props
if (readOnly) {
props.disabled = true
}
Expand Down
5 changes: 3 additions & 2 deletions src/fields/CheckboxesField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export default class CheckboxesField extends React.Component {
static formolFieldLabelElement = 'div'

render(b) {
const { onChange, readOnly, ...props } = this.props
// eslint-disable-next-line no-unused-vars
const { i18n, type, onChange, readOnly, ...props } = this.props
if (readOnly) {
props.disabled = true
}
Expand All @@ -22,8 +23,8 @@ export default class CheckboxesField extends React.Component {
checked ? [...value, choice] : value.filter(val => val !== choice)
)
}
{...props}
type="checkbox"
{...props}
/>
)
}
Expand Down
3 changes: 2 additions & 1 deletion src/fields/InputField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export default class InputField extends React.Component {
static defaultProps = { type: 'text' }

render() {
const { onChange, ...props } = this.props
// eslint-disable-next-line no-unused-vars
const { i18n, onChange, ...props } = this.props
return <input {...props} onChange={e => onChange(e.target.value)} />
}
}
3 changes: 2 additions & 1 deletion src/fields/NumberField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import InputField from './InputField'
// eslint-disable-next-line react/prefer-stateless-function
export default class NumberField extends React.Component {
render() {
const { onChange, ...props } = this.props
// eslint-disable-next-line no-unused-vars
const { i18n, onChange, ...props } = this.props
return <InputField {...props} onChange={v => onChange(parseInt(v))} />
}
}
3 changes: 2 additions & 1 deletion src/fields/RadiosField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export default class RadiosField extends React.Component {
static formolFieldLabelElement = 'div'

render(b) {
const { onChange, readOnly, ...props } = this.props
// eslint-disable-next-line no-unused-vars
const { i18n, onChange, readOnly, ...props } = this.props
if (readOnly) {
props.disabled = true
}
Expand Down
7 changes: 5 additions & 2 deletions src/fields/SelectField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import { cleanProps, normalizeChoices } from '../utils'
export default class SelectField extends React.Component {
render() {
const choices = normalizeChoices(this.props)
const { onChange, ...props } = this.props
// TODO: handle no readonly: disabled={readOnly /* There's no readOnly */}
// eslint-disable-next-line no-unused-vars
const { i18n, readOnly, onChange, ...props } = this.props
if (readOnly) {
props.disabled = true
}
return (
<select onChange={e => onChange(e.target.value)} {...cleanProps(props)}>
{choices.every(([k]) => k) && <option value="" />}
Expand Down

0 comments on commit daad756

Please sign in to comment.