From 436c2ed78379e6dee283e1bf1b510b3e72a5b0ff Mon Sep 17 00:00:00 2001 From: Mesteche Date: Thu, 27 Feb 2020 15:36:55 +0000 Subject: [PATCH 1/9] feat: organize langs like our component hierarchy --- .storybook/LangSwitcher.jsx | 22 ++++++++++++++ .storybook/config.js | 6 ++-- .storybook/locales/en.js | 34 ---------------------- .storybook/locales/index.js | 3 -- src/Molecules/DateRange/index.js | 2 ++ src/Molecules/DateRange/locales/en.js | 6 ++++ src/Molecules/DateRange/locales/fr.js | 6 ++++ src/Molecules/DateRange/locales/index.js | 4 +++ src/Molecules/index.js | 3 ++ src/Molecules/locales/en.js | 5 ++++ src/Molecules/locales/fr.js | 5 ++++ src/Molecules/locales/index.js | 4 +++ src/index.js | 3 ++ src/locales/en.js | 37 ++++++++++++++++++++++++ src/locales/fr.js | 37 ++++++++++++++++++++++++ src/locales/index.js | 4 +++ 16 files changed, 141 insertions(+), 40 deletions(-) create mode 100644 .storybook/LangSwitcher.jsx delete mode 100644 .storybook/locales/en.js delete mode 100644 .storybook/locales/index.js create mode 100644 src/Molecules/DateRange/locales/en.js create mode 100644 src/Molecules/DateRange/locales/fr.js create mode 100644 src/Molecules/DateRange/locales/index.js create mode 100644 src/Molecules/locales/en.js create mode 100644 src/Molecules/locales/fr.js create mode 100644 src/Molecules/locales/index.js create mode 100644 src/locales/en.js create mode 100644 src/locales/fr.js create mode 100644 src/locales/index.js diff --git a/.storybook/LangSwitcher.jsx b/.storybook/LangSwitcher.jsx new file mode 100644 index 0000000..be17dc1 --- /dev/null +++ b/.storybook/LangSwitcher.jsx @@ -0,0 +1,22 @@ +import React, { useState, useMemo } from 'react' +import { TransProvider } from '../src/Atoms/Trans' + +const LangSwitcher = ({ langs = {}, children }) => { + const availableLangs = useMemo(() => Object.keys(langs), []) + const [lang, setLang] = useState(availableLangs[0]) + + return ( + + +
+ {children} +
+
+ ) +} + +export default LangSwitcher diff --git a/.storybook/config.js b/.storybook/config.js index cfed3e5..ee5acf7 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -9,18 +9,18 @@ import { withThemesProvider } from 'storybook-addon-styled-component-theme' import { ThemeProvider } from '@material-ui/core/styles' import { MuiPickersUtilsProvider } from '../src/Atoms/DateTimePicker' -import { TransProvider } from '../src/Atoms/Trans' import results from '../.jest-test-results.json' import { muiRg6Theme } from './themes' import { rg6, dark } from '../src/themes' -import { en } from './locales' +import LangSwitcher from './LangSwitcher' +import { locales } from '../src' addDecorator(withKnobs) addDecorator(withA11y) addDecorator(withThemesProvider([rg6, dark])) addDecorator(withTests({ results })) -addDecorator(storyFn => {storyFn()}) addDecorator(storyFn => {storyFn()}) addDecorator(storyFn => {storyFn()}) +addDecorator(storyFn => {storyFn()}) // automatically import all files ending in *.stories.jsx configure(require.context('../src', true, /\.stories\.jsx$/), module) diff --git a/.storybook/locales/en.js b/.storybook/locales/en.js deleted file mode 100644 index dfa1835..0000000 --- a/.storybook/locales/en.js +++ /dev/null @@ -1,34 +0,0 @@ -export default { - global: { - dateFormat: 'dd/MM/yyyy hh:mm', - no_results: 'No results found', - filter: { - clearCurrent: 'Clear current filters', - }, - action: { - chooseOption: 'Choose an option', - cancel: 'Cancel', - add: 'Add', - }, - pagination: { - perPage: '%count% per page', - }, - action: { - remove: 'Supprimer', - }, - export: { - title: 'Export', - description: 'You will receive an email containing a link that contains the export', - filename: 'File name', - defaultFilename: 'My export', - format: 'File format', - actionExport: 'Export' - }, - editColumns: { - title: 'Edit columns', - description: 'You can hide/show the columns as well as change the display order', - enabledColumns: 'Enabled columns', - disabledColumns: 'Disabled columns', - } - } -} diff --git a/.storybook/locales/index.js b/.storybook/locales/index.js deleted file mode 100644 index e31d752..0000000 --- a/.storybook/locales/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import en from './en' - -export { en } diff --git a/src/Molecules/DateRange/index.js b/src/Molecules/DateRange/index.js index a5d3aba..d115397 100644 --- a/src/Molecules/DateRange/index.js +++ b/src/Molecules/DateRange/index.js @@ -1,3 +1,5 @@ import DateRange from './DateRange' +import * as locales from './locales' +export { locales } export default DateRange diff --git a/src/Molecules/DateRange/locales/en.js b/src/Molecules/DateRange/locales/en.js new file mode 100644 index 0000000..a1e6be3 --- /dev/null +++ b/src/Molecules/DateRange/locales/en.js @@ -0,0 +1,6 @@ +const locale = { + startDate: 'Start date', + endDate: 'End date', +} + +export default locale diff --git a/src/Molecules/DateRange/locales/fr.js b/src/Molecules/DateRange/locales/fr.js new file mode 100644 index 0000000..0e45b2a --- /dev/null +++ b/src/Molecules/DateRange/locales/fr.js @@ -0,0 +1,6 @@ +const locale = { + startDate: 'Date de début', + endDate: 'Date de fin', +} + +export default locale diff --git a/src/Molecules/DateRange/locales/index.js b/src/Molecules/DateRange/locales/index.js new file mode 100644 index 0000000..0d882f7 --- /dev/null +++ b/src/Molecules/DateRange/locales/index.js @@ -0,0 +1,4 @@ +import en from './en' +import fr from './fr' + +export { en, fr } diff --git a/src/Molecules/index.js b/src/Molecules/index.js index 3c2f534..914afc8 100644 --- a/src/Molecules/index.js +++ b/src/Molecules/index.js @@ -1,3 +1,6 @@ +import * as locales from './locales' + +export { locales } export { default as Pagination } from './Pagination' export { default as FormControl, diff --git a/src/Molecules/locales/en.js b/src/Molecules/locales/en.js new file mode 100644 index 0000000..4ded47d --- /dev/null +++ b/src/Molecules/locales/en.js @@ -0,0 +1,5 @@ +import { en as dateRange } from '../DateRange/locales' + +const locale = { dateRange } + +export default locale diff --git a/src/Molecules/locales/fr.js b/src/Molecules/locales/fr.js new file mode 100644 index 0000000..03a100c --- /dev/null +++ b/src/Molecules/locales/fr.js @@ -0,0 +1,5 @@ +import { fr as dateRange } from '../DateRange/locales' + +const locale = { dateRange } + +export default locale diff --git a/src/Molecules/locales/index.js b/src/Molecules/locales/index.js new file mode 100644 index 0000000..0d882f7 --- /dev/null +++ b/src/Molecules/locales/index.js @@ -0,0 +1,4 @@ +import en from './en' +import fr from './fr' + +export { en, fr } diff --git a/src/index.js b/src/index.js index 58ff210..728a04a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,6 @@ +import * as locales from './locales' +export { locales } + export { Button } from './Atoms' export { Pagination } from './Molecules' export { Input } from './Atoms' diff --git a/src/locales/en.js b/src/locales/en.js new file mode 100644 index 0000000..3fb4d26 --- /dev/null +++ b/src/locales/en.js @@ -0,0 +1,37 @@ +import { en as molecules } from '../Molecules/locales' + +const locale = { + molecules, + global: { + dateFormat: 'dd/MM/yyyy hh:mm', + no_results: 'No results found', + filter: { + clearCurrent: 'Clear current filters', + }, + action: { + chooseOption: 'Choose an option', + cancel: 'Cancel', + add: 'Add', + remove: 'Remove', + }, + pagination: { + perPage: '%count% per page', + }, + export: { + title: 'Export', + description: 'You will receive an email containing a link that contains the export', + filename: 'File name', + defaultFilename: 'My export', + format: 'File format', + actionExport: 'Export', + }, + editColumns: { + title: 'Edit columns', + description: 'You can hide/show the columns as well as change the display order', + enabledColumns: 'Enabled columns', + disabledColumns: 'Disabled columns', + }, + }, +} + +export default locale diff --git a/src/locales/fr.js b/src/locales/fr.js new file mode 100644 index 0000000..2f6edb7 --- /dev/null +++ b/src/locales/fr.js @@ -0,0 +1,37 @@ +import { fr as molecules } from '../Molecules/locales' + +const locale = { + molecules, + global: { + dateFormat: 'dd/MM/yyyy hh:mm', + no_results: 'Aucun résultat', + filter: { + clearCurrent: 'Clear current filters', + }, + action: { + chooseOption: 'Choisissez une option', + cancel: 'Anuler', + add: 'Ajouter', + remove: 'Supprimer', + }, + pagination: { + perPage: '%count% par page', + }, + export: { + title: 'Export', + description: 'Vous recevrez un email contenant un lien vers l\'export', + filename: 'Nom du fichier', + defaultFilename: 'Mon export', + format: 'Format de fichier', + actionExport: 'Exporter', + }, + editColumns: { + title: 'Edition des colonnes', + description: 'Vous pouvez afficher/cacher les colonnes ou changer l\'ordre des colonnes', + enabledColumns: 'Colonnes affichées', + disabledColumns: 'Colonnes cachées', + }, + }, +} + +export default locale diff --git a/src/locales/index.js b/src/locales/index.js new file mode 100644 index 0000000..0d882f7 --- /dev/null +++ b/src/locales/index.js @@ -0,0 +1,4 @@ +import en from './en' +import fr from './fr' + +export { en, fr } From 1445ac1d41d24bf9b75f806577d204399c143145 Mon Sep 17 00:00:00 2001 From: Mesteche Date: Fri, 28 Feb 2020 08:46:59 +0000 Subject: [PATCH 2/9] chore: relocate export translations --- src/Molecules/Export/Export.jsx | 10 +++++----- src/Molecules/Export/Export.test.jsx | 12 ++++++------ src/Molecules/Export/Formats/Formats.jsx | 2 +- src/Molecules/Export/locales/en.js | 10 ++++++++++ src/Molecules/Export/locales/fr.js | 10 ++++++++++ src/Molecules/Export/locales/index.js | 4 ++++ src/Molecules/locales/en.js | 6 +++++- src/Molecules/locales/fr.js | 6 +++++- src/Organisms/EnhancedList/EnhancedList.jsx | 2 +- src/locales/en.js | 8 -------- src/locales/fr.js | 8 -------- 11 files changed, 47 insertions(+), 31 deletions(-) create mode 100644 src/Molecules/Export/locales/en.js create mode 100644 src/Molecules/Export/locales/fr.js create mode 100644 src/Molecules/Export/locales/index.js diff --git a/src/Molecules/Export/Export.jsx b/src/Molecules/Export/Export.jsx index 75e4003..07843c6 100644 --- a/src/Molecules/Export/Export.jsx +++ b/src/Molecules/Export/Export.jsx @@ -20,12 +20,12 @@ const Actions = ({ onClose, onExport, disabled }) => <> const Export = ({ - descriptionText = , + descriptionText = , onExport = () => {}, value = {}, onChange = () => {}, @@ -36,7 +36,7 @@ const Export = ({ ...props }) => { const t = useTranslation() - const { defaultName = t('global.export.defaultFilename') } = props + const { defaultName = t('molecules.export.defaultFilename') } = props const { format = '', filename = defaultName } = value const onFileNameChange = event => onChange({ filename: event.target.value, format }) @@ -45,13 +45,13 @@ const Export = ({ - + {descriptionText} - + diff --git a/src/Molecules/Export/Export.test.jsx b/src/Molecules/Export/Export.test.jsx index 54f9c41..7b1bf14 100644 --- a/src/Molecules/Export/Export.test.jsx +++ b/src/Molecules/Export/Export.test.jsx @@ -29,7 +29,7 @@ it('should not call onExport when the filename is empty', () => { , ) - userEvent.click(getByText('global.export.actionExport')) + userEvent.click(getByText('molecules.export.actionExport')) expect(onExport).toHaveBeenCalledTimes(0) }) @@ -40,7 +40,7 @@ it('should not call onExport when the format is empty', () => { , ) - userEvent.click(getByText('global.export.actionExport')) + userEvent.click(getByText('molecules.export.actionExport')) expect(onExport).toHaveBeenCalledTimes(0) }) @@ -51,7 +51,7 @@ it('should call onExport when the filename and format are filled', () => { , ) - userEvent.click(getByText('global.export.actionExport')) + userEvent.click(getByText('molecules.export.actionExport')) expect(onExport).toHaveBeenCalled() }) @@ -67,7 +67,7 @@ it('should call onExport when filename and format are filled in but disabled is />, ) - userEvent.click(getByText('global.export.actionExport')) + userEvent.click(getByText('molecules.export.actionExport')) expect(onExport).toHaveBeenCalledTimes(0) }) @@ -83,8 +83,8 @@ it('should call onChange when filename has changed', async () => { const { getByLabelText } = render() - fireEvent.change(getByLabelText(/global\.export\.filename/), { target: { value: 'test' } }) - userEvent.selectOptions(getByLabelText(/global\.export\.format/), 'json') + fireEvent.change(getByLabelText(/molecules\.export\.filename/), { target: { value: 'test' } }) + userEvent.selectOptions(getByLabelText(/molecules\.export\.format/), 'json') expect(onChange).toHaveBeenNthCalledWith(1, { filename: 'test', format: 'xls' }) expect(onChange).toHaveBeenNthCalledWith(2, { filename: 'test', format: 'json' }) diff --git a/src/Molecules/Export/Formats/Formats.jsx b/src/Molecules/Export/Formats/Formats.jsx index 5f3b2df..32f41e3 100644 --- a/src/Molecules/Export/Formats/Formats.jsx +++ b/src/Molecules/Export/Formats/Formats.jsx @@ -9,7 +9,7 @@ const Formats = ({ formats, value, onChange }) => formats.length > 0 && - + diff --git a/src/Molecules/Export/locales/en.js b/src/Molecules/Export/locales/en.js new file mode 100644 index 0000000..047470c --- /dev/null +++ b/src/Molecules/Export/locales/en.js @@ -0,0 +1,10 @@ +const locale = { + title: 'Export', + description: 'You will receive an email containing a link that contains the export', + filename: 'File name', + defaultFilename: 'My export', + format: 'File format', + actionExport: 'Export', +} + +export default locale diff --git a/src/Molecules/Export/locales/fr.js b/src/Molecules/Export/locales/fr.js new file mode 100644 index 0000000..0bd2479 --- /dev/null +++ b/src/Molecules/Export/locales/fr.js @@ -0,0 +1,10 @@ +const locale = { + title: 'Export', + description: 'Vous allez recevoir un email cotenant un lien vers le fichier exporté', + filename: 'Nom du fichier', + defaultFilename: 'Mon export', + format: 'Format de fichier', + actionExport: 'Exporter', +} + +export default locale diff --git a/src/Molecules/Export/locales/index.js b/src/Molecules/Export/locales/index.js new file mode 100644 index 0000000..0d882f7 --- /dev/null +++ b/src/Molecules/Export/locales/index.js @@ -0,0 +1,4 @@ +import en from './en' +import fr from './fr' + +export { en, fr } diff --git a/src/Molecules/locales/en.js b/src/Molecules/locales/en.js index 4ded47d..26ae5ae 100644 --- a/src/Molecules/locales/en.js +++ b/src/Molecules/locales/en.js @@ -1,5 +1,9 @@ import { en as dateRange } from '../DateRange/locales' +import { en as exportLocales } from '../Export/locales' -const locale = { dateRange } +const locale = { + dateRange, + export: exportLocales, +} export default locale diff --git a/src/Molecules/locales/fr.js b/src/Molecules/locales/fr.js index 03a100c..bbd318d 100644 --- a/src/Molecules/locales/fr.js +++ b/src/Molecules/locales/fr.js @@ -1,5 +1,9 @@ import { fr as dateRange } from '../DateRange/locales' +import { fr as exportLocales } from '../Export/locales' -const locale = { dateRange } +const locale = { + dateRange, + export: exportLocales, +} export default locale diff --git a/src/Organisms/EnhancedList/EnhancedList.jsx b/src/Organisms/EnhancedList/EnhancedList.jsx index 0f67a68..857492f 100644 --- a/src/Organisms/EnhancedList/EnhancedList.jsx +++ b/src/Organisms/EnhancedList/EnhancedList.jsx @@ -115,7 +115,7 @@ const EnhancedList = ({ {!!Export && - + setExportAnchorEl(event.currentTarget)} /> } diff --git a/src/locales/en.js b/src/locales/en.js index 3fb4d26..3b1f0ed 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -17,14 +17,6 @@ const locale = { pagination: { perPage: '%count% per page', }, - export: { - title: 'Export', - description: 'You will receive an email containing a link that contains the export', - filename: 'File name', - defaultFilename: 'My export', - format: 'File format', - actionExport: 'Export', - }, editColumns: { title: 'Edit columns', description: 'You can hide/show the columns as well as change the display order', diff --git a/src/locales/fr.js b/src/locales/fr.js index 2f6edb7..de31dbb 100644 --- a/src/locales/fr.js +++ b/src/locales/fr.js @@ -17,14 +17,6 @@ const locale = { pagination: { perPage: '%count% par page', }, - export: { - title: 'Export', - description: 'Vous recevrez un email contenant un lien vers l\'export', - filename: 'Nom du fichier', - defaultFilename: 'Mon export', - format: 'Format de fichier', - actionExport: 'Exporter', - }, editColumns: { title: 'Edition des colonnes', description: 'Vous pouvez afficher/cacher les colonnes ou changer l\'ordre des colonnes', From 97e5d0b652b434624ea99137ff961661dcd3cf68 Mon Sep 17 00:00:00 2001 From: Mesteche Date: Fri, 28 Feb 2020 10:16:01 +0000 Subject: [PATCH 3/9] feat: add deepMerge to utils --- src/utils.js | 14 ++++++++++++++ src/utils.test.js | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index e8e59f6..34cfea5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -6,3 +6,17 @@ export const groupBy = key => arrayOfObjects => arrayOfObjects.reduce( }), {}, ) + +const areObjects = (...things) => things.reduce( + (result, thing) => result && typeof thing === 'object', + true, +) + +export const deepMerge = (source, overrides = {}) => Object.entries(overrides).reduce( + (source, [k, v]) => ( + source[k] === v ? source : // nothing to override + areObjects(source[k], v) ? { ...source, [k]: deepMerge(source[k], v) } : // go deeper + { ...source, [k]: v } // simple override + ), + source, +) diff --git a/src/utils.test.js b/src/utils.test.js index 1a37409..2338802 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { pipe, groupBy } from './utils' +import { pipe, groupBy, deepMerge } from './utils' describe('pipe', () => { const multiply = x => y => y * x @@ -100,3 +100,35 @@ describe('groupBy', () => { }) }) }) + +// eslint-disable-next-line max-lines-per-function +describe('deepMerge', () => { + it('groups objects by values of a given key', () => { + const source = { + override: { + key: 'source', + keep: true, + }, + keep: true, + } + const override = { + override: { + key: 'override', + add: true, + }, + add: true, + } + + const result = deepMerge(source, override) + + expect(result).toEqual({ + override: { + key: 'override', + keep: true, + add: true, + }, + keep: true, + add: true, + }) + }) +}) From 2c3934f7229b93a143528c7bb62bade9a4c1e383 Mon Sep 17 00:00:00 2001 From: Mesteche Date: Fri, 28 Feb 2020 10:17:37 +0000 Subject: [PATCH 4/9] feat: make translations easily overridable --- src/Atoms/Trans/useTranslation.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Atoms/Trans/useTranslation.js b/src/Atoms/Trans/useTranslation.js index 189a3c5..a1aece9 100644 --- a/src/Atoms/Trans/useTranslation.js +++ b/src/Atoms/Trans/useTranslation.js @@ -1,11 +1,23 @@ -import { createContext, useContext } from 'react' +import React, { createContext, useContext, useMemo } from 'react' +import { deepMerge } from '../../utils' const Context = createContext() -export const { Provider } = Context +const { Provider: BaseProvider } = Context + +export const Provider = ({ value, ...props }) => { + const translations = useContext(Context) || {} + const merged = useMemo( + () => deepMerge(translations, value), + [translations, value], + ) + + return +} export default () => { const translations = useContext(Context) || {} return (transKey = '', parameters = {}) => { + console.info(transKey.split('.'), translations) let translated = transKey .split('.') .reduce( From 936702e4216b0729ebf255fdbd315a5bb7841f2f Mon Sep 17 00:00:00 2001 From: Mesteche Date: Fri, 28 Feb 2020 10:19:34 +0000 Subject: [PATCH 5/9] chore: add exemple of local translations override --- src/Atoms/Trans/Trans.stories.jsx | 63 ++++++++-- src/Molecules/Export/Export.stories.jsx | 118 +++++++++++------- src/Molecules/Export/Formats/locales/en.js | 5 + src/Molecules/Export/Formats/locales/fr.js | 5 + src/Molecules/Export/Formats/locales/index.js | 4 + src/Molecules/Export/locales/en.js | 4 +- src/Molecules/Export/locales/fr.js | 4 +- src/locales/en.js | 1 + src/locales/fr.js | 1 + 9 files changed, 148 insertions(+), 57 deletions(-) create mode 100644 src/Molecules/Export/Formats/locales/en.js create mode 100644 src/Molecules/Export/Formats/locales/fr.js create mode 100644 src/Molecules/Export/Formats/locales/index.js diff --git a/src/Atoms/Trans/Trans.stories.jsx b/src/Atoms/Trans/Trans.stories.jsx index 811868f..e36af59 100644 --- a/src/Atoms/Trans/Trans.stories.jsx +++ b/src/Atoms/Trans/Trans.stories.jsx @@ -1,6 +1,7 @@ import React from 'react' -import Trans, { TransProvider } from './index' +import Trans, { TransProvider, useTranslation } from './index' +import FlexBox from '../../Templates/FlexBox' import markdown from './README.md' @@ -8,20 +9,60 @@ export default { title: 'Atoms/Trans', } -const translations = { - global: { - welcome: 'Hello there !', - }, +export const LocalWrapper = ({ translations = {}, ...props }) => { + const t = useTranslation() + const lang = t('lang') + + return } -export const trans = () => ( - - -
- -
+const Value = () =>
scope: scope
+ +const FirstChild = () => ( + + + + + ) +const SecondChild = () => ( + + + +
local.value: local.value
+
keep: keep
+
+
+) + +export const trans = () => <> + + + + + + +
keep: keep
+
local.value outscope
+
+
+ + trans.story = { parameters: { notes: { markdown }, diff --git a/src/Molecules/Export/Export.stories.jsx b/src/Molecules/Export/Export.stories.jsx index 4a933e7..38bd644 100644 --- a/src/Molecules/Export/Export.stories.jsx +++ b/src/Molecules/Export/Export.stories.jsx @@ -1,10 +1,10 @@ import React, { useState } from 'react' import { action } from '@storybook/addon-actions' -import { } from '@storybook/addon-knobs' import Export from './index' import FormControl, { FormLabel, FormControlLabel } from '../../Molecules/FormControl' import Radio from '../../Atoms/Radio' +import { TransProvider, useTranslation } from '../../Atoms/Trans' import RadioGroup from '@material-ui/core/RadioGroup' import markdown from './README.md' @@ -23,57 +23,87 @@ const exportFormats = [ ] const extraOptions = [ - { label: 'Choose red pill', value: 'red' }, - { label: 'Choose blue pill', value: 'blue' }, + { label: 'extraOptions.redPill', value: 'red' }, + { label: 'extraOptions.bluePill', value: 'blue' }, ] -const ExtraOptions = ({ value, onChange }) => - - - Extra option - - { - onChange(event.target.value) - action('extra option changed')(event.target.value) - }} - > - {extraOptions.map(({ label, value }) => - } - label={label} - />, - )} - - +const ExtraOptions = ({ value, onChange }) => { + const t = useTranslation() + return ( + + {t('extraOptions.label')} + { + onChange(event.target.value) + action('extra option changed')(event.target.value) + }} + > + {extraOptions.map(({ label, value }) => + } + label={t(label)} + />, + )} + + + ) +} + +const customLocales = { + en: { + extraOptions: { + label: 'Extra option', + redPill: 'Choose red pill', + bluePill: 'Choose blue pill', + }, + }, + fr: { + extraOptions: { + label: 'Option supplémentaire', + redPill: 'Prendre la pilule rouge', + bluePill: 'prendre la pilule bleue', + }, + }, +} + +const Wrapper = ({ ...props }) => { + const t = useTranslation() + const lang = t('lang') + + return +} export const exportStory = () => { const [value, setValue] = useState({ format: '' }) - return { - setValue({ ...value, ...newValue }) - action('export value changed')(newValue) - }} - onClose={action('export canceled')} - onExport={action('export launched')} - formats={exportFormats} - extraOptions={ - { - setValue({ ...value, extraOption }) + return ( + + { + setValue({ ...value, ...newValue }) + action('export value changed')(newValue) }} + onClose={action('export canceled')} + onExport={action('export launched')} + formats={exportFormats} + extraOptions={ + { + setValue({ ...value, extraOption }) + }} + /> + } /> - } - /> + + ) } exportStory.story = { diff --git a/src/Molecules/Export/Formats/locales/en.js b/src/Molecules/Export/Formats/locales/en.js new file mode 100644 index 0000000..e4169a8 --- /dev/null +++ b/src/Molecules/Export/Formats/locales/en.js @@ -0,0 +1,5 @@ +const locale = { + label: 'File format', +} + +export default locale diff --git a/src/Molecules/Export/Formats/locales/fr.js b/src/Molecules/Export/Formats/locales/fr.js new file mode 100644 index 0000000..f46fd31 --- /dev/null +++ b/src/Molecules/Export/Formats/locales/fr.js @@ -0,0 +1,5 @@ +const locale = { + label: 'Format de fichier', +} + +export default locale diff --git a/src/Molecules/Export/Formats/locales/index.js b/src/Molecules/Export/Formats/locales/index.js new file mode 100644 index 0000000..0d882f7 --- /dev/null +++ b/src/Molecules/Export/Formats/locales/index.js @@ -0,0 +1,4 @@ +import en from './en' +import fr from './fr' + +export { en, fr } diff --git a/src/Molecules/Export/locales/en.js b/src/Molecules/Export/locales/en.js index 047470c..b401528 100644 --- a/src/Molecules/Export/locales/en.js +++ b/src/Molecules/Export/locales/en.js @@ -1,9 +1,11 @@ +import { en as format } from '../Formats/locales' + const locale = { + format, title: 'Export', description: 'You will receive an email containing a link that contains the export', filename: 'File name', defaultFilename: 'My export', - format: 'File format', actionExport: 'Export', } diff --git a/src/Molecules/Export/locales/fr.js b/src/Molecules/Export/locales/fr.js index 0bd2479..8c67683 100644 --- a/src/Molecules/Export/locales/fr.js +++ b/src/Molecules/Export/locales/fr.js @@ -1,9 +1,11 @@ +import { fr as format } from '../Formats/locales' + const locale = { + format, title: 'Export', description: 'Vous allez recevoir un email cotenant un lien vers le fichier exporté', filename: 'Nom du fichier', defaultFilename: 'Mon export', - format: 'Format de fichier', actionExport: 'Exporter', } diff --git a/src/locales/en.js b/src/locales/en.js index 3b1f0ed..ed8b4d9 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -1,6 +1,7 @@ import { en as molecules } from '../Molecules/locales' const locale = { + lang: 'en', molecules, global: { dateFormat: 'dd/MM/yyyy hh:mm', diff --git a/src/locales/fr.js b/src/locales/fr.js index de31dbb..c2e8da6 100644 --- a/src/locales/fr.js +++ b/src/locales/fr.js @@ -1,6 +1,7 @@ import { fr as molecules } from '../Molecules/locales' const locale = { + lang: 'fr', molecules, global: { dateFormat: 'dd/MM/yyyy hh:mm', From 3640d4536bdfabfb9f1d746a7da48e94fe783d7b Mon Sep 17 00:00:00 2001 From: Mesteche Date: Fri, 28 Feb 2020 12:34:05 +0000 Subject: [PATCH 6/9] fix: don't export non-story --- src/Atoms/Trans/Trans.stories.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Atoms/Trans/Trans.stories.jsx b/src/Atoms/Trans/Trans.stories.jsx index e36af59..ffe6fdc 100644 --- a/src/Atoms/Trans/Trans.stories.jsx +++ b/src/Atoms/Trans/Trans.stories.jsx @@ -9,7 +9,7 @@ export default { title: 'Atoms/Trans', } -export const LocalWrapper = ({ translations = {}, ...props }) => { +const LocalWrapper = ({ translations = {}, ...props }) => { const t = useTranslation() const lang = t('lang') From dd54b334377c23b8fb235ec55f9ce1577ad1b663 Mon Sep 17 00:00:00 2001 From: Mesteche Date: Fri, 28 Feb 2020 12:40:30 +0000 Subject: [PATCH 7/9] fix: mutualize translations in story --- src/Atoms/Trans/Trans.stories.jsx | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Atoms/Trans/Trans.stories.jsx b/src/Atoms/Trans/Trans.stories.jsx index ffe6fdc..545ee51 100644 --- a/src/Atoms/Trans/Trans.stories.jsx +++ b/src/Atoms/Trans/Trans.stories.jsx @@ -16,12 +16,12 @@ const LocalWrapper = ({ translations = {}, ...props }) => { return } -const Value = () =>
scope: scope
+const TransWrapper = ({ transKey }) =>
{transKey}: {transKey}
const FirstChild = () => ( - + ) @@ -32,9 +32,9 @@ const SecondChild = () => ( en: { scope: 'second child', local: { value: 'some specific local value can be declared' } }, fr: { scope: 'deuxième enfant', local: { value: 'une valeur locale peut être déclarée' } }, }}> - -
local.value: local.value
-
keep: keep
+ + + ) @@ -45,20 +45,21 @@ export const trans = () => <> en: { scope: 'parent', keep: 'not overridden value still reachable', - outscope: 'isn\'t accessible from outside it\'s scope so fallback is used', + outscope: 'Not accessible from outside it\'s scope so fallback is used', }, fr: { scope: 'parent', keep: 'valeur non surchargée toujours accessibles', - outscope: 'n\'est pas accessible en dehors de son scope le fallback est donc utilisé', + outscope: 'Non accessible en dehors de son scope le fallback est donc utilisé', }, }}> - + - -
keep: keep
-
local.value outscope
+ + + outscope + From 755d242c75914a07bd9370521ff47dcdf984e258 Mon Sep 17 00:00:00 2001 From: Estephe Bouvet Date: Sun, 1 Mar 2020 23:21:02 +0100 Subject: [PATCH 8/9] feat: make JSX usable in variables values --- src/Atoms/Trans/Trans.stories.jsx | 17 +++++++----- src/Atoms/Trans/useTranslation.js | 45 +++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/Atoms/Trans/Trans.stories.jsx b/src/Atoms/Trans/Trans.stories.jsx index 545ee51..733eef6 100644 --- a/src/Atoms/Trans/Trans.stories.jsx +++ b/src/Atoms/Trans/Trans.stories.jsx @@ -16,7 +16,7 @@ const LocalWrapper = ({ translations = {}, ...props }) => { return } -const TransWrapper = ({ transKey }) =>
{transKey}: {transKey}
+const TransWrapper = ({ transKey, ...props }) =>
{transKey}: {transKey}
const FirstChild = () => ( @@ -43,16 +43,19 @@ export const trans = () => <> + variables} /> diff --git a/src/Atoms/Trans/useTranslation.js b/src/Atoms/Trans/useTranslation.js index a1aece9..ad7d5d3 100644 --- a/src/Atoms/Trans/useTranslation.js +++ b/src/Atoms/Trans/useTranslation.js @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useMemo } from 'react' +import React, { createContext, useContext, useMemo, Fragment } from 'react' import { deepMerge } from '../../utils' const Context = createContext() const { Provider: BaseProvider } = Context @@ -17,18 +17,35 @@ export default () => { const translations = useContext(Context) || {} return (transKey = '', parameters = {}) => { - console.info(transKey.split('.'), translations) - let translated = transKey - .split('.') - .reduce( - (acc, k) => acc[k] || transKey, - translations, - ) - - Object.entries(parameters).forEach(([key, value]) => { - translated = translated.replace(new RegExp(`%${key}%`, 'g'), value) - }) - - return translated + const translation = transKey.split('.').reduce( + (acc, k) => acc[k] || transKey, + translations, + ) + + const params = Object.entries(parameters) + const children = params.length ? replace(translation, params) : [translation] + + return ( + !children.some(child => typeof child === 'object') ? children.join('') : + React.createElement(Fragment, null, ...children) + ) + } +} + +export const replace = (str, parameters) => { + const mapReplace = parameters.reduce( + (acc, [k, v]) => Object.assign(acc, { [`%${k}%`]: v }), + {}, + ) + const re = new RegExp(Object.keys(mapReplace).join('|'), 'g') + + const children = [] + let match + let lastIndex = 0 + while ((match = re.exec(str)) != null) { + children.push(str.slice(lastIndex, match.index)) + children.push(mapReplace[match[0]]) + lastIndex = re.lastIndex } + return children } From 46465f610ba6c074742bae54be291e20291bbfcc Mon Sep 17 00:00:00 2001 From: Estephe Bouvet Date: Wed, 4 Mar 2020 08:25:31 +0100 Subject: [PATCH 9/9] refacto: refine translation processing --- src/Atoms/Trans/useTranslation.js | 73 +++++++++++++++++++------------ 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/Atoms/Trans/useTranslation.js b/src/Atoms/Trans/useTranslation.js index ad7d5d3..c9a8e28 100644 --- a/src/Atoms/Trans/useTranslation.js +++ b/src/Atoms/Trans/useTranslation.js @@ -13,39 +13,58 @@ export const Provider = ({ value, ...props }) => { return } -export default () => { - const translations = useContext(Context) || {} +const preProcessor = ({ translations }) => (key = '') => ( + key.split('.').reduce( + (acc, k) => (acc[k] || key), + translations, + ) +) - return (transKey = '', parameters = {}) => { - const translation = transKey.split('.').reduce( - (acc, k) => acc[k] || transKey, - translations, - ) - - const params = Object.entries(parameters) - const children = params.length ? replace(translation, params) : [translation] - - return ( - !children.some(child => typeof child === 'object') ? children.join('') : - React.createElement(Fragment, null, ...children) - ) +const processor = ({ translations = {}, ...context }) => { + const params = Object.entries(context) + if (!params.length) { + return translation => [translation] } -} - -export const replace = (str, parameters) => { - const mapReplace = parameters.reduce( + const mapReplace = params.reduce( (acc, [k, v]) => Object.assign(acc, { [`%${k}%`]: v }), {}, ) const re = new RegExp(Object.keys(mapReplace).join('|'), 'g') - const children = [] - let match - let lastIndex = 0 - while ((match = re.exec(str)) != null) { - children.push(str.slice(lastIndex, match.index)) - children.push(mapReplace[match[0]]) - lastIndex = re.lastIndex + return function * (translation = '') { + let lastIndex = 0 + for (const match of translation.matchAll(re)) { + yield translation.slice(lastIndex, match.index) + yield mapReplace[match[0]] + lastIndex = match.index + match[0].length + } + yield translation.slice(lastIndex) + } +} + +const postProcessor = () => (processedTranslation = []) => { + let result = '' + for (const chunk of processedTranslation) { + console.info('chunk', chunk) + if (!['number', 'string'].includes(typeof chunk)) { + return React.createElement(Fragment, null, result, chunk, ...processedTranslation) + } + result = result.concat(chunk) + } + return result +} + +export default () => { + const translations = useContext(Context) || {} + + return (transKey = '', parameters = {}) => { + const context = { translations, ...parameters } + + const preProcessed = preProcessor(context)(transKey) + const processed = processor(context)(preProcessed) + const postProcessed = postProcessor(context)(processed) + + console.info(transKey, postProcessed) + return postProcessed } - return children }