@applica-software-guru/react-admin 1.2.123 → 1.3.126

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/bitbucket-pipelines.yml +3 -3
  2. package/dist/ApplicaAdmin.d.ts +1 -1
  3. package/dist/ApplicaAdmin.d.ts.map +1 -1
  4. package/dist/components/Notification.d.ts +2 -2
  5. package/dist/components/ra-forms/TabbedForm.d.ts +2 -2
  6. package/dist/components/ra-forms/TableForm/TableFormIterator.d.ts +2 -2
  7. package/dist/components/ra-forms/TableForm/TableFormIterator.d.ts.map +1 -1
  8. package/dist/components/ra-lists/Datagrid.d.ts +2 -2
  9. package/dist/components/ra-pages/GenericErrorPage.d.ts +3 -0
  10. package/dist/components/ra-pages/GenericErrorPage.d.ts.map +1 -0
  11. package/dist/components/ra-pages/index.d.ts +5 -5
  12. package/dist/components/ra-pages/index.d.ts.map +1 -1
  13. package/dist/hooks/index.d.ts +1 -0
  14. package/dist/hooks/index.d.ts.map +1 -1
  15. package/dist/hooks/useMemoizedObject.d.ts +10 -0
  16. package/dist/hooks/useMemoizedObject.d.ts.map +1 -0
  17. package/dist/i18n/MissingMessageHandler.d.ts +19 -0
  18. package/dist/i18n/MissingMessageHandler.d.ts.map +1 -0
  19. package/dist/i18n/createI18nProvider.d.ts +8 -5
  20. package/dist/i18n/createI18nProvider.d.ts.map +1 -1
  21. package/dist/i18n/index.d.ts +0 -1
  22. package/dist/i18n/useI18nProvider.d.ts +5 -5
  23. package/dist/i18n/useI18nProvider.d.ts.map +1 -1
  24. package/dist/react-admin.cjs.js +62 -62
  25. package/dist/react-admin.cjs.js.map +1 -1
  26. package/dist/react-admin.es.js +10912 -10864
  27. package/dist/react-admin.es.js.map +1 -1
  28. package/dist/react-admin.umd.js +64 -64
  29. package/dist/react-admin.umd.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/ApplicaAdmin.tsx +20 -44
  32. package/src/assets/genericError.png +0 -0
  33. package/src/components/ra-forms/TableForm/TableFormIterator.tsx +76 -27
  34. package/src/components/ra-forms/TableForm/TableFormIteratorItem.tsx +2 -2
  35. package/src/components/ra-pages/GenericErrorPage.tsx +23 -0
  36. package/src/components/ra-pages/index.ts +5 -5
  37. package/src/hooks/index.jsx +1 -0
  38. package/src/hooks/useMemoizedObject.tsx +27 -0
  39. package/src/i18n/MissingMessageHandler.ts +60 -0
  40. package/src/i18n/createI18nProvider.tsx +97 -0
  41. package/src/i18n/index.jsx +0 -1
  42. package/src/i18n/useI18nProvider.tsx +23 -0
  43. package/dist/i18n/useI18nCatcher.d.ts +0 -28
  44. package/dist/i18n/useI18nCatcher.d.ts.map +0 -1
  45. package/src/i18n/createI18nProvider.jsx +0 -15
  46. package/src/i18n/useI18nCatcher.ts +0 -109
  47. package/src/i18n/useI18nProvider.jsx +0 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applica-software-guru/react-admin",
3
- "version": "1.2.123",
3
+ "version": "1.3.126",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,33 +1,14 @@
1
1
  import { AdminProps, AuthProvider, DataProvider } from 'react-admin';
2
2
  import { AppConfigProvider, MenuConfigProvider, MenuPropTypes, ThemeConfig, ThemeConfigProvider } from './contexts';
3
3
  import { CatchResult, useCliErrorCatcher } from './dev';
4
- import { Layout, LoginPage, MainIcon, Notification, SmallIcon } from './components';
4
+ import { GenericErrorPage, Layout, LoginPage, MainIcon, Notification, SmallIcon } from './components';
5
5
  import React, { useMemo } from 'react';
6
- import { createI18nProvider, useI18nCatcher, useI18nLanguages } from './i18n';
7
-
6
+ import { useI18nProvider } from './i18n';
8
7
  import Admin from './Admin';
9
- import { I18nCatcherBodyBuilderResultProps } from './i18n/useI18nCatcher';
10
8
  import { MenuProps } from './types';
11
9
  import PropTypes from 'prop-types';
12
10
  import { QueryClient } from 'react-query';
13
-
14
- const languageResponseMapper = (response: any) =>
15
- response.reduce(
16
- (messages: any, message: any) => ({
17
- ...messages,
18
- [message.lang]: {
19
- ...(messages[message.lang] || {}),
20
- [message.code]: message.text
21
- }
22
- }),
23
- {}
24
- );
25
-
26
- const i18nBodyRequestMapper = (lang: string, message: string): I18nCatcherBodyBuilderResultProps => ({
27
- lang,
28
- code: message,
29
- text: message
30
- });
11
+ import { ThemeCustomization } from './themes';
31
12
 
32
13
  const queryClient = new QueryClient({
33
14
  defaultOptions: {
@@ -194,19 +175,6 @@ const ApplicaAdmin = ({
194
175
  enablePasswordRecover,
195
176
  ...props
196
177
  }: ApplicaAdminProps) => {
197
- const languages = useI18nLanguages({
198
- apiUrl,
199
- endpoint: '/i18n/messages',
200
- mapper: languageResponseMapper
201
- });
202
-
203
- useI18nCatcher({
204
- bodyBuilder: i18nBodyRequestMapper,
205
- apiUrl,
206
- endpoint: '/i18n/message',
207
- loading: languages?.loading,
208
- enabled: development
209
- });
210
178
  useCliErrorCatcher({
211
179
  apiUrl,
212
180
  endpoint: '/cli/error',
@@ -261,17 +229,25 @@ const ApplicaAdmin = ({
261
229
  },
262
230
  [logoMain, logoIcon, name, version, notification, enableNotification]
263
231
  );
232
+ const i18nProvider = useI18nProvider({
233
+ apiUrl: apiUrl,
234
+ defaultLocale: defaultLocale,
235
+ allowMissing: development,
236
+ createMissing: development
237
+ });
264
238
 
265
- if (languages?.loading) {
266
- return null;
239
+ if (i18nProvider === undefined) {
240
+ return <></>;
241
+ } else if (i18nProvider.error) {
242
+ return (
243
+ <ThemeConfigProvider initialConfig={themeConfig}>
244
+ <ThemeCustomization themeOverrides={theme}>
245
+ <GenericErrorPage />
246
+ </ThemeCustomization>
247
+ </ThemeConfigProvider>
248
+ );
267
249
  }
268
250
 
269
- const i18nProivder = createI18nProvider({
270
- languages,
271
- defaultLocale,
272
- allowMissing: development
273
- });
274
-
275
251
  return (
276
252
  <AppConfigProvider>
277
253
  <MenuConfigProvider menu={menu}>
@@ -283,7 +259,7 @@ const ApplicaAdmin = ({
283
259
  queryClient={queryClient}
284
260
  dataProvider={dataProvider}
285
261
  authProvider={authProvider}
286
- i18nProvider={i18nProivder}
262
+ i18nProvider={i18nProvider}
287
263
  loginPage={login}
288
264
  {...props}
289
265
  />
Binary file
@@ -15,14 +15,14 @@ import {
15
15
  TableRow,
16
16
  Typography
17
17
  } from '@mui/material';
18
- import { RaRecord, useRecordContext, useTranslate, useTranslateLabel } from 'ra-core';
18
+ import { FormDataConsumer, RaRecord, useRecordContext, useTranslate, useTranslateLabel } from 'ra-core';
19
19
  import { styled, useTheme } from '@mui/material/styles';
20
20
 
21
21
  import { PlusCircleOutlined } from '@ant-design/icons';
22
22
  import PropTypes from 'prop-types';
23
23
  import { TableFormIteratorContext } from './TableFormIteratorContext';
24
24
  import { TableFormIteratorItem } from './TableFormIteratorItem';
25
- import { UseFieldArrayReturn } from 'react-hook-form';
25
+ import { UseFieldArrayReturn, useFormContext } from 'react-hook-form';
26
26
  import get from 'lodash/get';
27
27
 
28
28
  /**
@@ -44,13 +44,27 @@ import get from 'lodash/get';
44
44
  * @returns
45
45
  */
46
46
  const TableFormIterator = (props: TableFormIteratorProps) => {
47
- const { children, resource, source, label, disableActions = false, disableAdd = false, disableRemove = false, className, empty, template = {} } = props;
47
+ const {
48
+ children,
49
+ resource,
50
+ source,
51
+ label,
52
+ disableActions = false,
53
+ disableAdd = false,
54
+ disableRemove = false,
55
+ className,
56
+ empty,
57
+ template = {}
58
+ } = props;
48
59
  const [confirmIsOpen, setConfirmIsOpen] = useState<boolean>(false);
49
- const { fields, remove, replace } = useArrayInput(props);
60
+ const { fields, remove, replace, append } = useArrayInput(props);
61
+ const { resetField } = useFormContext();
62
+
50
63
  const theme = useTheme();
51
64
  const translate = useTranslate();
52
65
  const record = useRecordContext(props);
53
- const initialDefaultValue = useRef({});
66
+ const initialDefaultValue = useRef(template || {});
67
+
54
68
  const translateLabel = useTranslateLabel();
55
69
  const removeField = useCallback(
56
70
  (index: number) => {
@@ -66,6 +80,50 @@ const TableFormIterator = (props: TableFormIteratorProps) => {
66
80
  for (const k in initialDefaultValue.current) initialDefaultValue.current[k] = null;
67
81
  }
68
82
 
83
+ const addField = useCallback(
84
+ (item: any = undefined) => {
85
+ let defaultValue = item;
86
+ if (item == null) {
87
+ defaultValue = initialDefaultValue.current;
88
+ if (
89
+ Children.count(children) === 1 &&
90
+ React.isValidElement(Children.only(children)) &&
91
+ // @ts-ignore
92
+ !Children.only(children).props.source &&
93
+ // Make sure it's not a FormDataConsumer
94
+ // @ts-ignore
95
+ Children.map(children, (input) => React.isValidElement(input) && input.type !== FormDataConsumer).some(Boolean)
96
+ ) {
97
+ // ArrayInput used for an array of scalar values
98
+ // (e.g. tags: ['foo', 'bar'])
99
+ defaultValue = '';
100
+ } else {
101
+ // ArrayInput used for an array of objects
102
+ // (e.g. authors: [{ firstName: 'John', lastName: 'Doe' }, { firstName: 'Jane', lastName: 'Doe' }])
103
+ defaultValue = defaultValue || ({} as Record<string, unknown>);
104
+ Children.forEach(children, (input) => {
105
+ if (React.isValidElement(input) && input.type !== FormDataConsumer && input.props.source) {
106
+ defaultValue[input.props.source] = input.props.defaultValue ?? null;
107
+ }
108
+ });
109
+ }
110
+ }
111
+ const newField = { ...defaultValue, ...template };
112
+ append(newField);
113
+ // Make sure the newly added inputs are not considered dirty by react-hook-form
114
+ resetField(`${source}.${fields.length}`, { defaultValue });
115
+ },
116
+ [append, children, resetField, source, fields.length, template]
117
+ );
118
+
119
+ // add field and call the onClick event of the button passed as addButton prop
120
+ const handleAddButtonClick = (originalOnClickHandler: any) => (event: MouseEvent) => {
121
+ addField();
122
+ if (originalOnClickHandler) {
123
+ originalOnClickHandler(event);
124
+ }
125
+ };
126
+
69
127
  const handleArrayClear = useCallback(() => {
70
128
  replace([]);
71
129
  setConfirmIsOpen(false);
@@ -95,7 +153,7 @@ const TableFormIterator = (props: TableFormIteratorProps) => {
95
153
  source,
96
154
  disableAdd,
97
155
  template,
98
- addButton: props?.addButton,
156
+ onClick: handleAddButtonClick((props?.addButton as any)?.props?.onClick)
99
157
  })}
100
158
 
101
159
  <TableContainer component={Paper} sx={{ mt: 2, border: `1px solid ${tableBorderColor}` }}>
@@ -177,34 +235,25 @@ const TableFormIterator = (props: TableFormIteratorProps) => {
177
235
  };
178
236
 
179
237
  const AddTableRow = (props: any) => {
180
- const { label, disableAdd, template = {} } = props;
181
- const { append } = useArrayInput(props);
238
+ const { label, disableAdd, onClick } = props;
182
239
  const theme = useTheme();
183
240
  // @ts-ignore
184
241
  const tableBorderColor = theme.palette.mode === 'dark' ? theme.palette.grey.A400 : theme.palette.grey.A800;
185
242
  const iconColor = theme.palette.mode === 'light' ? '#000000' : '#FFFFFF';
186
243
 
187
- const handleAddButtonClick = useCallback((
188
- originalOnClickHandler: React.MouseEventHandler<Element>
189
- ) => (event: React.MouseEvent<Element, MouseEvent>) => {
190
- append(template);
191
- originalOnClickHandler && originalOnClickHandler(event);
192
- }, [append, template]);
193
-
194
244
  return (
195
245
  <Stack justifyContent={'space-between'} alignItems={'center'} flexDirection={'row'}>
196
- <Typography>{label}</Typography>
197
- {!disableAdd && (
198
- React.isValidElement(props?.addButton) ?
246
+ {label !== false && <Typography>{label}</Typography>}
247
+ {!disableAdd &&
248
+ (React.isValidElement(props?.addButton) ? (
199
249
  React.cloneElement(props?.addButton, {
200
- onClick: handleAddButtonClick(
201
- props?.addButton?.props.onClick
202
- ),
203
- }) :
204
- <IconButton size="small" color="secondary" sx={{ border: `1px solid ${tableBorderColor}` }} onClick={() => append(template)}>
250
+ onClick: onClick
251
+ })
252
+ ) : (
253
+ <IconButton size="small" color="secondary" sx={{ border: `1px solid ${tableBorderColor}` }} onClick={onClick}>
205
254
  <PlusCircleOutlined style={{ color: iconColor }} />
206
255
  </IconButton>
207
- )}
256
+ ))}
208
257
  </Stack>
209
258
  );
210
259
  };
@@ -222,7 +271,7 @@ const StyledTableFormIterator = styled(TableFormIterator, {
222
271
  }));
223
272
 
224
273
  AddTableRow.propTypes = {
225
- label: PropTypes.string,
274
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
226
275
  source: PropTypes.string,
227
276
  defaultValue: PropTypes.any
228
277
  };
@@ -237,7 +286,7 @@ TableFormIterator.propTypes = {
237
286
  formState: PropTypes.object,
238
287
  record: PropTypes.object,
239
288
  source: PropTypes.string,
240
- label: PropTypes.string,
289
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
241
290
  resource: PropTypes.string,
242
291
  translate: PropTypes.func,
243
292
  disableAdd: PropTypes.bool,
@@ -261,7 +310,7 @@ export interface TableFormIteratorProps extends Partial<UseFieldArrayReturn> {
261
310
  submitFailed?: boolean;
262
311
  };
263
312
  record?: RaRecord;
264
- label?: string;
313
+ label?: string | boolean;
265
314
  resource?: string;
266
315
  source?: string;
267
316
  sx?: SxProps;
@@ -47,14 +47,14 @@ export const TableFormIteratorItem = React.forwardRef((props: TableFormIteratorI
47
47
  resource,
48
48
  disabled,
49
49
  ...inputProps,
50
- label: false
50
+ label: ''
51
51
  })}
52
52
  </TableCell>
53
53
  );
54
54
  })}
55
55
 
56
56
  {!disableActions && (
57
- <TableCell sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: 0, pt: 2 }}>
57
+ <TableCell sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: 0, pt: 2.5 }}>
58
58
  <ActionsMenu horizontal>
59
59
  <Typography color="error" onClick={() => setConfirmIsOpen(true)}>
60
60
  {translate('ra.action.delete')}
@@ -0,0 +1,23 @@
1
+ import { useCallback } from 'react';
2
+ import { Button, Container, Stack, Typography } from '@mui/material';
3
+ //@ts-ignore
4
+ import imgSrc from '../../assets/genericError.png';
5
+
6
+ function GenericErrorPage() {
7
+ const onReload = useCallback(() => location.reload(), []);
8
+
9
+ return (
10
+ <Container>
11
+ <Stack alignItems="center" px={2} py={{ xs: 4, sm: 8 }} spacing={2} textAlign="center">
12
+ <img src={imgSrc} style={{ width: '100%', maxWidth: 450 }} />
13
+ <Typography variant="h2">Ops! An error occurred...</Typography>
14
+ <Typography>We&#39;re fixing the problem. Please try again later.</Typography>
15
+ <Button onClick={onReload} variant="contained">
16
+ Reload
17
+ </Button>
18
+ </Stack>
19
+ </Container>
20
+ );
21
+ }
22
+
23
+ export default GenericErrorPage;
@@ -1,5 +1,5 @@
1
- import ActivatePage from './ActivatePage';
2
- import LoginPage from './LoginPage';
3
- import RecoverPage from './RecoverPage';
4
- import RegisterPage from './RegisterPage';
5
- export { ActivatePage, LoginPage, RecoverPage, RegisterPage };
1
+ export { default as ActivatePage } from './ActivatePage';
2
+ export { default as LoginPage } from './LoginPage';
3
+ export { default as RecoverPage } from './RecoverPage';
4
+ export { default as RegisterPage } from './RegisterPage';
5
+ export { default as GenericErrorPage } from './GenericErrorPage';
@@ -8,3 +8,4 @@ import useThemeConfig from './useThemeConfig';
8
8
  export { useAppConfig, useMenu, useBreadcrumbs, useLocalStorage, useThemeConfig, useResourceTitle, useMenuConfig };
9
9
  export { useSx } from './mui';
10
10
  export { useRefDimensions } from './useRefDimensions';
11
+ export { useMemoizedObject } from './useMemoizedObject';
@@ -0,0 +1,27 @@
1
+ import _ from 'lodash';
2
+ import { useEffect, useState } from 'react';
3
+
4
+ /**
5
+ * Questo hook memoizza in automatico un oggetto utilizzando il'equality check del suo valore come criterio di discriminazione.
6
+ * NOTA BENE!!! L'equality check di un oggetto JS è un discorso estremamente complesso.
7
+ * Pertanto questo hook va usato con estrema parsimonia, solo quando strettamente necessario e solo se si padroneggia a pieno il tema di cui sopra e le sue implicazioni nelle logiche di render condizionale di React
8
+ * @param object
9
+ * @returns
10
+ */
11
+ function useMemoizedObject<T extends object>(object: T): T {
12
+ if (!_.isPlainObject(object)) {
13
+ throw new Error('[useMemoizedObject]: param must be a plain JS object');
14
+ }
15
+
16
+ const [memoObj, setMemoObj] = useState(object);
17
+
18
+ useEffect(() => {
19
+ if (!_.isEqual(object, memoObj)) {
20
+ setMemoObj(object);
21
+ }
22
+ }, [object, memoObj]);
23
+
24
+ return memoObj;
25
+ }
26
+
27
+ export { useMemoizedObject };
@@ -0,0 +1,60 @@
1
+ import _ from 'lodash';
2
+
3
+ type IMissingMessageHandlerConstructorData = {
4
+ apiUrl: string;
5
+ };
6
+
7
+ type IMissingMessageHandler = {
8
+ handleMessage(data: { code: string; lang: string }): void;
9
+ };
10
+
11
+ class MissingMessageHandler implements IMissingMessageHandler {
12
+ #headers: Headers;
13
+ #apiUrl: string;
14
+ #handledMessages: Array<{ code: string; lang: string }> = [];
15
+
16
+ constructor(data: IMissingMessageHandlerConstructorData) {
17
+ const { apiUrl } = data ?? {};
18
+ if (!_.isString(apiUrl) || _.isEmpty(apiUrl)) {
19
+ throw new Error('[MissingMessageHandler] constructor: please provide a valid apiUrl');
20
+ }
21
+ this.#apiUrl = apiUrl;
22
+ this.#headers = new Headers({ Accept: 'application/json', 'Content-Type': 'application/json' });
23
+ }
24
+
25
+ handleMessage(data: { code: string; lang: string }): void {
26
+ if (!_.isPlainObject(data)) {
27
+ throw new Error('[MissingMessageHandler] handleMessage: please provide valid message data');
28
+ }
29
+
30
+ const { code, lang } = data;
31
+
32
+ if (!_.isString(code) || _.isEmpty(code)) {
33
+ throw new Error('[MissingMessageHandler] handleMessage: please provide valid code');
34
+ }
35
+
36
+ if (!_.isString(lang) || _.isEmpty(lang)) {
37
+ throw new Error('[MissingMessageHandler] handleMessage: please provide valid lang');
38
+ }
39
+
40
+ if (_.some(this.#handledMessages, (message) => _.isEqual(message, { code, lang }))) {
41
+ return;
42
+ } else {
43
+ this.#handledMessages.push({ code, lang });
44
+ fetch(`${this.#apiUrl}/i18n/message`, { method: 'put', headers: this.#headers, body: JSON.stringify({ code, lang, text: code }) })
45
+ .then((response) => response.json())
46
+ .then(() => {
47
+ //eslint-disable-next-line
48
+ console.log(`[MissingMessageHandler] handleMessage: created message for lang ${lang} and code ${code}`);
49
+ })
50
+ .catch((e) => {
51
+ //eslint-disable-next-line
52
+ console.error(e);
53
+ this.#handledMessages = _.reject(this.#handledMessages, (m) => _.isEqual(m, { code, lang }));
54
+ });
55
+ return;
56
+ }
57
+ }
58
+ }
59
+
60
+ export default MissingMessageHandler;
@@ -0,0 +1,97 @@
1
+ import { Locale, TranslationMessages } from 'ra-core';
2
+ import polyglotI18nProvider from 'ra-i18n-polyglot';
3
+ import _ from 'lodash';
4
+ import MissingMessageHandler from './MissingMessageHandler';
5
+
6
+ type IMessage = {
7
+ id: string;
8
+ code: string;
9
+ lang: string;
10
+ text: string;
11
+ };
12
+ type IDictionary = { [key: string]: string };
13
+ type ITranslations = { [key: string]: IDictionary };
14
+ type ICreateI18nProviderData = {
15
+ apiUrl: string;
16
+ defaultLocale: string;
17
+ allowMissing?: boolean;
18
+ createMissing?: boolean;
19
+ };
20
+ type IPolyglotOptions = {
21
+ phrases?: IDictionary;
22
+ locale?: string;
23
+ allowMissing?: boolean;
24
+ onMissingKey?: (key: string, options: IPolyglotOptions, locale: string) => string;
25
+ interpolation?: {
26
+ prefix?: string;
27
+ suffix?: string;
28
+ };
29
+ };
30
+
31
+ function createI18nProvider(data: ICreateI18nProviderData): Promise<any> {
32
+ const { apiUrl, defaultLocale, allowMissing = false, createMissing = false } = data,
33
+ defaultLanguages: Array<Locale> = [{ locale: defaultLocale, name: defaultLocale }],
34
+ headers = new Headers({ Accept: 'application/json', 'Content-Type': 'application/json' }),
35
+ missingMessageHandler = new MissingMessageHandler({ apiUrl: apiUrl }),
36
+ getLanguages = (): Promise<Locale> => {
37
+ return fetch(`${apiUrl}/i18n/languages`, { headers: headers, method: 'get' })
38
+ .then((response) => response.json())
39
+ .then((response) => {
40
+ const { responseCode, locales } = response;
41
+ return responseCode !== 'ok' ? defaultLanguages : locales;
42
+ })
43
+ .catch(() => {
44
+ return Promise.resolve(defaultLanguages);
45
+ });
46
+ },
47
+ getMessages = (): Promise<ITranslations> => {
48
+ return fetch(`${apiUrl}/i18n/messages`, { headers: headers, method: 'get' })
49
+ .then((response) => response.json())
50
+ .then((response: Array<IMessage>): ITranslations => {
51
+ return response.reduce(
52
+ (messages: ITranslations, message: IMessage) => ({
53
+ ...messages,
54
+ [message.lang]: {
55
+ ...(messages[message.lang] || {}),
56
+ [message.code]: message.text
57
+ }
58
+ }),
59
+ {}
60
+ );
61
+ });
62
+ },
63
+ onMissingKey = (key: string, options: IPolyglotOptions, locale: string): string => {
64
+ if (!allowMissing) {
65
+ //eslint-disable-next-line
66
+ console.error(`Warning: Missing translation for key: "${key}"`);
67
+ }
68
+ if (createMissing) {
69
+ if (!_.isString(key)) {
70
+ //eslint-disable-next-line
71
+ console.error('[createI18nProvider] onMissingKey: provided key is not a string');
72
+ } else if (key.match(new RegExp(/[\s[\]]/, 'gm'))) {
73
+ //eslint-disable-next-line
74
+ console.error(`[createI18nProvider] onMissingKey: illegal character in key ${key}`);
75
+ } else {
76
+ missingMessageHandler.handleMessage({ code: key, lang: locale });
77
+ }
78
+ }
79
+ return key;
80
+ };
81
+
82
+ return Promise.all([getLanguages(), getMessages()]).then((responses) => {
83
+ const [languages, messages] = responses;
84
+ function getMessages(locale: string): TranslationMessages {
85
+ //@ts-ignore
86
+ return _.get(messages, locale, {});
87
+ }
88
+ return polyglotI18nProvider(getMessages, defaultLocale, languages, {
89
+ allowMissing: allowMissing || createMissing,
90
+ onMissingKey: onMissingKey
91
+ });
92
+ });
93
+ }
94
+
95
+ export type { ICreateI18nProviderData };
96
+
97
+ export default createI18nProvider;
@@ -1,4 +1,3 @@
1
1
  export { default as createI18nProvider } from './createI18nProvider';
2
- export { default as useI18nCatcher } from './useI18nCatcher';
3
2
  export { default as useI18nLanguages } from './useI18nLanguages';
4
3
  export { default as useI18nProvider } from './useI18nProvider';
@@ -0,0 +1,23 @@
1
+ import { useEffect, useState } from 'react';
2
+ import createI18nProvider, { ICreateI18nProviderData } from './createI18nProvider';
3
+ import { useMemoizedObject } from '../hooks';
4
+ import { I18nProvider } from 'ra-core';
5
+
6
+ function useI18nProvider(data: ICreateI18nProviderData): undefined | I18nProvider | { error: boolean } {
7
+ const [provider, setProvider] = useState<undefined | I18nProvider | { error: boolean }>(),
8
+ memoizedData = useMemoizedObject(data);
9
+
10
+ useEffect(() => {
11
+ createI18nProvider(memoizedData)
12
+ .then((provider) => {
13
+ setProvider(provider);
14
+ })
15
+ .catch(() => {
16
+ setProvider({ error: true });
17
+ });
18
+ }, [memoizedData]);
19
+
20
+ return provider;
21
+ }
22
+
23
+ export default useI18nProvider;
@@ -1,28 +0,0 @@
1
- export type I18nCatcherBodyBuilderResultProps = {
2
- lang: string;
3
- code: string;
4
- text: string;
5
- } | {
6
- message: {
7
- lang: string;
8
- code: string;
9
- text: string;
10
- };
11
- };
12
- export type I18nCatcherBodyBuilderProps = (lang: string, message: string) => I18nCatcherBodyBuilderResultProps;
13
- export type UseI18nCatcherProps = {
14
- apiUrl: string;
15
- enabled?: boolean;
16
- endpoint?: string;
17
- loading?: boolean;
18
- bodyBuilder: I18nCatcherBodyBuilderProps;
19
- };
20
- /**
21
- * Hook che consente di catturare ed inoltrare al server API le stringhe non tradotte.
22
- *
23
- * @param {UseI18nCatcherProps}
24
- * @returns {boolean}
25
- */
26
- declare const useI18nCatcher: ({ apiUrl, enabled, endpoint, loading, bodyBuilder }: UseI18nCatcherProps) => boolean;
27
- export default useI18nCatcher;
28
- //# sourceMappingURL=useI18nCatcher.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useI18nCatcher.d.ts","sourceRoot":"","sources":["../../../src/i18n/useI18nCatcher.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,iCAAiC,GACzC;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,GACD;IACE,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,CAAC;AAEN,MAAM,MAAM,2BAA2B,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,iCAAiC,CAAC;AA0B/G,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,2BAA2B,CAAC;CAC1C,CAAC;AAEF;;;;;GAKG;AACH,QAAA,MAAM,cAAc,wDAUjB,mBAAmB,YAmCrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -1,15 +0,0 @@
1
- import { get } from 'lodash';
2
- import polyglotI18nProvider from 'ra-i18n-polyglot';
3
-
4
- const createI18nProvider = ({ languages, defaultLocale, allowMissing = false }) =>
5
- polyglotI18nProvider(
6
- (locale) => {
7
- const messages = get(languages, locale, {});
8
- return messages;
9
- },
10
- defaultLocale,
11
- [{ locale: defaultLocale }],
12
- { allowMissing }
13
- );
14
-
15
- export default createI18nProvider;