@truedat/core 7.1.8 → 7.2.1
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.
- package/package.json +3 -3
- package/src/i18n/components/LangProvider.js +184 -22
- package/src/i18n/components/LangProviderWrapper.js +49 -0
- package/src/i18n/components/MessageForm.js +3 -5
- package/src/i18n/components/Messages.js +6 -30
- package/src/i18n/components/__tests__/Messages.spec.js +11 -20
- package/src/i18n/components/__tests__/__snapshots__/Messages.spec.js.snap +11 -21
- package/src/i18n/index.js +7 -0
- package/src/services/__tests__/i18nContent.spec.js +164 -0
- package/src/services/i18nContent.js +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "7.1
|
|
3
|
+
"version": "7.2.1",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@testing-library/react": "^12.0.0",
|
|
37
37
|
"@testing-library/react-hooks": "^8.0.1",
|
|
38
38
|
"@testing-library/user-event": "^13.2.1",
|
|
39
|
-
"@truedat/test": "7.1
|
|
39
|
+
"@truedat/test": "7.2.1",
|
|
40
40
|
"babel-jest": "^28.1.0",
|
|
41
41
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
42
42
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -118,5 +118,5 @@
|
|
|
118
118
|
"react-dom": ">= 16.8.6 < 17",
|
|
119
119
|
"semantic-ui-react": ">= 2.0.3 < 2.2"
|
|
120
120
|
},
|
|
121
|
-
"gitHead": "
|
|
121
|
+
"gitHead": "10ba779646ef549e6db1235b3acd6d430526f487"
|
|
122
122
|
}
|
|
@@ -1,39 +1,201 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import React, {
|
|
2
|
+
import React, {
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
createContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
3
10
|
import { IntlProvider } from "react-intl";
|
|
11
|
+
import PropTypes from "prop-types";
|
|
4
12
|
import { Loading } from "@truedat/core/components";
|
|
5
13
|
import { useLocales } from "../../hooks/useLocales";
|
|
6
|
-
import { useMessages } from "../../hooks/useMessages";
|
|
7
14
|
import { getNavigatorLanguage } from "../../services/i18n";
|
|
8
15
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
const defaultContext = {
|
|
17
|
+
defaultLang: "en",
|
|
18
|
+
altLangs: ["es"],
|
|
19
|
+
requiredLangs: ["en"],
|
|
20
|
+
enabledLangs: ["en", "es"],
|
|
21
|
+
isMultilingual: true,
|
|
22
|
+
getMessagesForLang: () => ({}),
|
|
23
|
+
lang: "en",
|
|
24
|
+
loading: false,
|
|
25
|
+
locales: [{ lang: "en", messages: {} }],
|
|
26
|
+
localesError: null,
|
|
27
|
+
mutate: () => {},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const LanguageContext = createContext(defaultContext);
|
|
31
|
+
export const useLanguage = () => useContext(LanguageContext);
|
|
32
|
+
|
|
33
|
+
const LanguageContextProvider = ({ children, defaultMessages }) => {
|
|
34
|
+
const {
|
|
35
|
+
locales,
|
|
36
|
+
mutate,
|
|
37
|
+
error: localesError,
|
|
38
|
+
isValidating: loading,
|
|
39
|
+
} = useLocales();
|
|
40
|
+
|
|
41
|
+
const langsOf = _.map(({ lang }) => lang);
|
|
42
|
+
const enabledLangs = useMemo(
|
|
43
|
+
() =>
|
|
44
|
+
_.flow(
|
|
45
|
+
_.filter(({ is_enabled }) => is_enabled),
|
|
46
|
+
langsOf
|
|
47
|
+
)(locales),
|
|
48
|
+
[locales]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const altLangs = useMemo(
|
|
52
|
+
() =>
|
|
53
|
+
_.flow(
|
|
54
|
+
_.filter(({ is_enabled, is_default }) => is_enabled && !is_default),
|
|
55
|
+
langsOf
|
|
56
|
+
)(locales),
|
|
57
|
+
[locales]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const requiredLangs = useMemo(
|
|
61
|
+
() =>
|
|
62
|
+
_.flow(
|
|
63
|
+
_.filter(({ is_required }) => is_required),
|
|
64
|
+
langsOf
|
|
65
|
+
)(locales),
|
|
66
|
+
[locales]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const defaultLang = useMemo(
|
|
70
|
+
() =>
|
|
71
|
+
_.flow(
|
|
72
|
+
_.find(({ is_default }) => is_default),
|
|
73
|
+
_.prop("lang")
|
|
74
|
+
)(locales),
|
|
75
|
+
[locales]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const mapMessages = _.flow(
|
|
79
|
+
_.map(({ message_id, definition }) => [message_id, definition]),
|
|
80
|
+
_.fromPairs
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const messages = useMemo(
|
|
84
|
+
() =>
|
|
85
|
+
_.flow(
|
|
86
|
+
_.map(({ lang, messages }) => [lang, mapMessages(messages)]),
|
|
87
|
+
_.fromPairs
|
|
88
|
+
)(locales),
|
|
89
|
+
[locales]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const [lang, setLang] = useState(defaultLang);
|
|
93
|
+
const [altLang, setAltLang] = useState();
|
|
94
|
+
|
|
95
|
+
const getMessagesForLang = useCallback(
|
|
96
|
+
(requestedLang) => {
|
|
97
|
+
if (loading || !requestedLang) return null;
|
|
98
|
+
return messages[requestedLang] || defaultMessages[requestedLang];
|
|
99
|
+
},
|
|
100
|
+
[loading, messages, defaultMessages]
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const context = useMemo(
|
|
104
|
+
() => ({
|
|
105
|
+
defaultLang,
|
|
106
|
+
altLangs,
|
|
107
|
+
requiredLangs,
|
|
108
|
+
enabledLangs,
|
|
109
|
+
isMultilingual: enabledLangs.length > 1,
|
|
110
|
+
setAltLang,
|
|
111
|
+
altLang,
|
|
112
|
+
getMessagesForLang,
|
|
113
|
+
lang,
|
|
114
|
+
loading,
|
|
115
|
+
locales,
|
|
116
|
+
localesError,
|
|
117
|
+
mutate,
|
|
118
|
+
}),
|
|
119
|
+
[
|
|
120
|
+
defaultLang,
|
|
121
|
+
altLangs,
|
|
122
|
+
enabledLangs,
|
|
123
|
+
locales,
|
|
124
|
+
enabledLangs,
|
|
125
|
+
altLang,
|
|
126
|
+
requiredLangs,
|
|
127
|
+
lang,
|
|
128
|
+
loading,
|
|
129
|
+
getMessagesForLang,
|
|
130
|
+
localesError,
|
|
131
|
+
]
|
|
132
|
+
);
|
|
133
|
+
|
|
14
134
|
useEffect(() => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
135
|
+
if (enabledLangs.length > 0) {
|
|
136
|
+
const navigatorLang = getNavigatorLanguage(enabledLangs);
|
|
137
|
+
if (navigatorLang && navigatorLang !== lang) {
|
|
138
|
+
setLang(navigatorLang);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, [enabledLangs, lang]);
|
|
142
|
+
|
|
21
143
|
useEffect(() => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
144
|
+
if (enabledLangs.length > 1) {
|
|
145
|
+
const firstNonDefaultLang = _.first(altLangs);
|
|
146
|
+
if (firstNonDefaultLang && !altLang) {
|
|
147
|
+
setAltLang(firstNonDefaultLang);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}, [enabledLangs, altLang]);
|
|
151
|
+
|
|
152
|
+
if (loading || !lang) return <Loading />;
|
|
153
|
+
|
|
154
|
+
if (localesError) return <div>Error loading language data</div>;
|
|
25
155
|
|
|
26
|
-
return
|
|
27
|
-
<
|
|
28
|
-
|
|
156
|
+
return (
|
|
157
|
+
<LanguageContext.Provider value={context}>
|
|
158
|
+
{children}
|
|
159
|
+
</LanguageContext.Provider>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
LanguageContextProvider.propTypes = {
|
|
164
|
+
children: PropTypes.node.isRequired,
|
|
165
|
+
defaultMessages: PropTypes.object.isRequired,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const I18nProvider = ({ children, lang }) => {
|
|
169
|
+
const { lang: contextLang, getMessagesForLang } = useLanguage();
|
|
170
|
+
const currentLang = lang || contextLang;
|
|
171
|
+
|
|
172
|
+
return (
|
|
29
173
|
<IntlProvider
|
|
30
|
-
locale={
|
|
31
|
-
defaultLocale={
|
|
32
|
-
messages={
|
|
174
|
+
locale={currentLang}
|
|
175
|
+
defaultLocale={currentLang}
|
|
176
|
+
messages={getMessagesForLang(currentLang)}
|
|
33
177
|
>
|
|
34
178
|
{children}
|
|
35
179
|
</IntlProvider>
|
|
36
180
|
);
|
|
37
181
|
};
|
|
38
182
|
|
|
183
|
+
I18nProvider.propTypes = {
|
|
184
|
+
children: PropTypes.node.isRequired,
|
|
185
|
+
lang: PropTypes.string,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const LangProvider = ({ children, defaultMessages }) => {
|
|
189
|
+
return (
|
|
190
|
+
<LanguageContextProvider defaultMessages={defaultMessages}>
|
|
191
|
+
<I18nProvider>{children}</I18nProvider>
|
|
192
|
+
</LanguageContextProvider>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
LangProvider.propTypes = {
|
|
197
|
+
children: PropTypes.node.isRequired,
|
|
198
|
+
defaultMessages: PropTypes.object.isRequired,
|
|
199
|
+
};
|
|
200
|
+
|
|
39
201
|
export default LangProvider;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import React, { useMemo, useCallback } from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { LanguageContext } from "./LangProvider";
|
|
5
|
+
|
|
6
|
+
const LangProviderWrapper = ({ children, langs }) => {
|
|
7
|
+
const getMessagesForLang = useCallback(() => ({}));
|
|
8
|
+
|
|
9
|
+
const locales = useMemo(() => {
|
|
10
|
+
return langs.map((lang, idx) => ({
|
|
11
|
+
lang,
|
|
12
|
+
messages: {},
|
|
13
|
+
id: idx + 1,
|
|
14
|
+
is_default: idx === 0,
|
|
15
|
+
is_required: idx < 2,
|
|
16
|
+
is_enabled: true,
|
|
17
|
+
}));
|
|
18
|
+
}, [langs]);
|
|
19
|
+
|
|
20
|
+
const context = useMemo(
|
|
21
|
+
() => ({
|
|
22
|
+
defaultLang: _.first(langs),
|
|
23
|
+
altLangs: langs,
|
|
24
|
+
requiredLangs: langs,
|
|
25
|
+
enabledLangs: langs,
|
|
26
|
+
isMultilingual: langs.length > 1,
|
|
27
|
+
getMessagesForLang,
|
|
28
|
+
lang: _.first(langs),
|
|
29
|
+
loading: false,
|
|
30
|
+
locales,
|
|
31
|
+
localesError: null,
|
|
32
|
+
mutate: () => {},
|
|
33
|
+
}),
|
|
34
|
+
[langs]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<LanguageContext.Provider value={context}>
|
|
39
|
+
{children}
|
|
40
|
+
</LanguageContext.Provider>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
LangProviderWrapper.propTypes = {
|
|
45
|
+
children: PropTypes.node.isRequired,
|
|
46
|
+
langs: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default LangProviderWrapper;
|
|
@@ -5,7 +5,7 @@ import { useForm, Controller } from "react-hook-form";
|
|
|
5
5
|
import { useIntl } from "react-intl";
|
|
6
6
|
import { Button, Form, Segment, Header, Loader } from "semantic-ui-react";
|
|
7
7
|
import { HistoryBackButton } from "@truedat/core/components";
|
|
8
|
-
import {
|
|
8
|
+
import { useLanguage } from "./LangProvider";
|
|
9
9
|
|
|
10
10
|
export const LangForm = ({
|
|
11
11
|
control,
|
|
@@ -74,7 +74,7 @@ LangForm.propTypes = {
|
|
|
74
74
|
};
|
|
75
75
|
|
|
76
76
|
export const MessageForm = ({ onSubmit, isSubmitting }) => {
|
|
77
|
-
const { locales
|
|
77
|
+
const { locales } = useLanguage();
|
|
78
78
|
const { formatMessage } = useIntl();
|
|
79
79
|
const { handleSubmit, control, formState } = useForm({
|
|
80
80
|
mode: "all",
|
|
@@ -85,9 +85,7 @@ export const MessageForm = ({ onSubmit, isSubmitting }) => {
|
|
|
85
85
|
});
|
|
86
86
|
const { errors, isDirty, isValid } = formState;
|
|
87
87
|
|
|
88
|
-
return
|
|
89
|
-
<Loader />
|
|
90
|
-
) : (
|
|
88
|
+
return (
|
|
91
89
|
<Form onSubmit={handleSubmit(onSubmit)}>
|
|
92
90
|
<Controller
|
|
93
91
|
control={control}
|
|
@@ -8,22 +8,20 @@ import {
|
|
|
8
8
|
Divider,
|
|
9
9
|
Icon,
|
|
10
10
|
Segment,
|
|
11
|
-
Dimmer,
|
|
12
|
-
Loader,
|
|
13
11
|
Menu,
|
|
14
12
|
} from "semantic-ui-react";
|
|
15
13
|
import { Link } from "react-router-dom";
|
|
16
14
|
import { SearchInput } from "@truedat/core/components";
|
|
17
15
|
import { lowerDeburrTrim } from "@truedat/core/services/sort";
|
|
18
|
-
import { useLocales } from "@truedat/core/hooks";
|
|
19
16
|
import { I18N_MESSAGES_NEW } from "@truedat/core/routes";
|
|
20
17
|
import { Pagination } from "@truedat/core/components";
|
|
18
|
+
import { useLanguage } from "./LangProvider";
|
|
21
19
|
import MessagesTable from "./MessagesTable";
|
|
22
20
|
import Languages from "./Languages";
|
|
23
21
|
|
|
24
22
|
const ITEMS_PER_PAGE = 30;
|
|
25
23
|
|
|
26
|
-
export function MessagesContent({ locales,
|
|
24
|
+
export function MessagesContent({ locales, mutate }) {
|
|
27
25
|
const [selectedLang, setSelectedLang] = useState(
|
|
28
26
|
_.flow(
|
|
29
27
|
_.find(({ is_default }) => is_default),
|
|
@@ -105,11 +103,7 @@ export function MessagesContent({ locales, loading, mutate, isValidating }) {
|
|
|
105
103
|
})}
|
|
106
104
|
value={filter}
|
|
107
105
|
/>
|
|
108
|
-
<MessagesTable
|
|
109
|
-
messages={paginatedMessages}
|
|
110
|
-
locales={locales}
|
|
111
|
-
loading={loading}
|
|
112
|
-
/>
|
|
106
|
+
<MessagesTable messages={paginatedMessages} locales={locales} />
|
|
113
107
|
<Pagination
|
|
114
108
|
totalPages={totalPages}
|
|
115
109
|
activePage={page}
|
|
@@ -117,11 +111,7 @@ export function MessagesContent({ locales, loading, mutate, isValidating }) {
|
|
|
117
111
|
/>
|
|
118
112
|
</>
|
|
119
113
|
) : (
|
|
120
|
-
<Languages
|
|
121
|
-
locales={_.keyBy("lang")(locales)}
|
|
122
|
-
mutate={mutate}
|
|
123
|
-
isValidating={isValidating}
|
|
124
|
-
/>
|
|
114
|
+
<Languages locales={_.keyBy("lang")(locales)} mutate={mutate} />
|
|
125
115
|
)}
|
|
126
116
|
</>
|
|
127
117
|
);
|
|
@@ -129,15 +119,13 @@ export function MessagesContent({ locales, loading, mutate, isValidating }) {
|
|
|
129
119
|
|
|
130
120
|
MessagesContent.propTypes = {
|
|
131
121
|
locales: PropTypes.array,
|
|
132
|
-
loading: PropTypes.bool,
|
|
133
122
|
mutate: PropTypes.func,
|
|
134
|
-
isValidating: PropTypes.bool,
|
|
135
123
|
};
|
|
136
124
|
|
|
137
125
|
export default function Messages() {
|
|
138
126
|
const { formatMessage } = useIntl();
|
|
139
127
|
|
|
140
|
-
const { locales,
|
|
128
|
+
const { locales, mutate } = useLanguage();
|
|
141
129
|
|
|
142
130
|
return (
|
|
143
131
|
<Segment>
|
|
@@ -157,11 +145,6 @@ export default function Messages() {
|
|
|
157
145
|
</Header.Content>
|
|
158
146
|
</Header>
|
|
159
147
|
<Segment attached="bottom">
|
|
160
|
-
<Dimmer.Dimmable dimmed={loading}>
|
|
161
|
-
<Dimmer active={loading} inverted>
|
|
162
|
-
<Loader />
|
|
163
|
-
</Dimmer>
|
|
164
|
-
</Dimmer.Dimmable>
|
|
165
148
|
<Button
|
|
166
149
|
floated="right"
|
|
167
150
|
primary
|
|
@@ -170,14 +153,7 @@ export default function Messages() {
|
|
|
170
153
|
as={Link}
|
|
171
154
|
to={I18N_MESSAGES_NEW}
|
|
172
155
|
/>
|
|
173
|
-
{
|
|
174
|
-
<MessagesContent
|
|
175
|
-
locales={locales}
|
|
176
|
-
loading={loading}
|
|
177
|
-
mutate={mutate}
|
|
178
|
-
isValidating={isValidating}
|
|
179
|
-
/>
|
|
180
|
-
) : null}
|
|
156
|
+
<MessagesContent locales={locales} mutate={mutate} />
|
|
181
157
|
</Segment>
|
|
182
158
|
</Segment>
|
|
183
159
|
);
|
|
@@ -1,36 +1,27 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { render } from "@truedat/test/render";
|
|
3
|
-
import
|
|
3
|
+
import { LangProviderWrapper } from "@truedat/core/i18n";
|
|
4
4
|
import Messages from "../Messages";
|
|
5
5
|
|
|
6
6
|
const renderOpts = {
|
|
7
|
-
messages: {
|
|
7
|
+
messages: {},
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
jest.mock("@truedat/core/hooks", () => ({
|
|
11
|
-
useLocales: jest.fn(() => ({
|
|
12
|
-
loading: false,
|
|
13
|
-
locales: [
|
|
14
|
-
{
|
|
15
|
-
lang: "es",
|
|
16
|
-
id: 1,
|
|
17
|
-
messages: [
|
|
18
|
-
{
|
|
19
|
-
id: 1,
|
|
20
|
-
message_id: "message_id",
|
|
21
|
-
definition: "definition",
|
|
22
|
-
description: "description",
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
})),
|
|
28
11
|
useMessagePatch: jest.fn(() => ({ trigger: jest.fn() })),
|
|
29
12
|
}));
|
|
30
13
|
|
|
14
|
+
const renderComponent = () =>
|
|
15
|
+
render(
|
|
16
|
+
<LangProviderWrapper langs={["es"]}>
|
|
17
|
+
<Messages />
|
|
18
|
+
</LangProviderWrapper>,
|
|
19
|
+
renderOpts
|
|
20
|
+
);
|
|
21
|
+
|
|
31
22
|
describe("<Messages />", () => {
|
|
32
23
|
it("matches the latest snapshot", () => {
|
|
33
|
-
const { container } =
|
|
24
|
+
const { container } = renderComponent();
|
|
34
25
|
expect(container).toMatchSnapshot();
|
|
35
26
|
});
|
|
36
27
|
});
|
|
@@ -15,32 +15,17 @@ exports[`<Messages /> matches the latest snapshot 1`] = `
|
|
|
15
15
|
<div
|
|
16
16
|
class="content"
|
|
17
17
|
>
|
|
18
|
-
|
|
18
|
+
i18n.messages.header
|
|
19
19
|
<div
|
|
20
20
|
class="sub header"
|
|
21
21
|
>
|
|
22
|
-
|
|
22
|
+
i18n.messages.subheader
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
25
|
</h2>
|
|
26
26
|
<div
|
|
27
27
|
class="ui bottom attached segment"
|
|
28
28
|
>
|
|
29
|
-
<div
|
|
30
|
-
class="dimmable"
|
|
31
|
-
>
|
|
32
|
-
<div
|
|
33
|
-
class="ui inverted dimmer"
|
|
34
|
-
>
|
|
35
|
-
<div
|
|
36
|
-
class="content"
|
|
37
|
-
>
|
|
38
|
-
<div
|
|
39
|
-
class="ui loader"
|
|
40
|
-
/>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
29
|
<a
|
|
45
30
|
class="ui primary right floated button"
|
|
46
31
|
href="/i18n/messages/new"
|
|
@@ -50,7 +35,7 @@ exports[`<Messages /> matches the latest snapshot 1`] = `
|
|
|
50
35
|
aria-hidden="true"
|
|
51
36
|
class="add circle icon"
|
|
52
37
|
/>
|
|
53
|
-
|
|
38
|
+
i18n.actions.createMessage
|
|
54
39
|
</a>
|
|
55
40
|
<div
|
|
56
41
|
class="ui pointing secondary top attached tabular menu"
|
|
@@ -58,7 +43,12 @@ exports[`<Messages /> matches the latest snapshot 1`] = `
|
|
|
58
43
|
<a
|
|
59
44
|
class="item"
|
|
60
45
|
>
|
|
61
|
-
manage
|
|
46
|
+
i18n.messages.locale.manage
|
|
47
|
+
</a>
|
|
48
|
+
<a
|
|
49
|
+
class="active item"
|
|
50
|
+
>
|
|
51
|
+
undefined ( undefined )
|
|
62
52
|
</a>
|
|
63
53
|
</div>
|
|
64
54
|
<div
|
|
@@ -68,7 +58,7 @@ exports[`<Messages /> matches the latest snapshot 1`] = `
|
|
|
68
58
|
class="ui icon input"
|
|
69
59
|
>
|
|
70
60
|
<input
|
|
71
|
-
placeholder="
|
|
61
|
+
placeholder="i18n.messages.search.placeholder"
|
|
72
62
|
type="text"
|
|
73
63
|
value=""
|
|
74
64
|
/>
|
|
@@ -87,7 +77,7 @@ exports[`<Messages /> matches the latest snapshot 1`] = `
|
|
|
87
77
|
<div
|
|
88
78
|
class="content"
|
|
89
79
|
>
|
|
90
|
-
|
|
80
|
+
i18n.messages.empty
|
|
91
81
|
</div>
|
|
92
82
|
</h4>
|
|
93
83
|
</div>
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
splitTranslatableFields,
|
|
3
|
+
formatLocales,
|
|
4
|
+
hasTranslatableFields,
|
|
5
|
+
isTranslatetableField,
|
|
6
|
+
} from "../i18nContent";
|
|
7
|
+
|
|
8
|
+
describe("services: i18nContent", () => {
|
|
9
|
+
describe("splitTranslatableFields", () => {
|
|
10
|
+
it("should split fields into translatable and non-translatable", () => {
|
|
11
|
+
const template = {
|
|
12
|
+
content: [
|
|
13
|
+
{
|
|
14
|
+
fields: [
|
|
15
|
+
{ name: "title", widget: "string" },
|
|
16
|
+
{ name: "description", widget: "enriched_text" },
|
|
17
|
+
{ name: "date", widget: "date" },
|
|
18
|
+
{ name: "notes", widget: "textarea" },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const result = splitTranslatableFields(template);
|
|
25
|
+
|
|
26
|
+
expect(result).toEqual({
|
|
27
|
+
translatable: {
|
|
28
|
+
title: { value: "", origin: "user" },
|
|
29
|
+
description: { value: {}, origin: "user" },
|
|
30
|
+
notes: { value: null, origin: "user" },
|
|
31
|
+
},
|
|
32
|
+
noTranslatable: {
|
|
33
|
+
date: { value: null, origin: "user" },
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should handle empty template", () => {
|
|
39
|
+
const template = { content: [] };
|
|
40
|
+
const result = splitTranslatableFields(template);
|
|
41
|
+
|
|
42
|
+
expect(result).toEqual({
|
|
43
|
+
translatable: {},
|
|
44
|
+
noTranslatable: {},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should handle multiple groups", () => {
|
|
49
|
+
const template = {
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
fields: [{ name: "title", widget: "string" }],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
fields: [
|
|
56
|
+
{ name: "subtitle", widget: "string" },
|
|
57
|
+
{ name: "count", widget: "number" },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const result = splitTranslatableFields(template);
|
|
64
|
+
|
|
65
|
+
expect(result).toEqual({
|
|
66
|
+
translatable: {
|
|
67
|
+
title: { value: "", origin: "user" },
|
|
68
|
+
subtitle: { value: "", origin: "user" },
|
|
69
|
+
},
|
|
70
|
+
noTranslatable: {
|
|
71
|
+
count: { value: null, origin: "user" },
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("formatLocales", () => {
|
|
78
|
+
it("should format locales into default, required and enabled categories", () => {
|
|
79
|
+
const locales = [
|
|
80
|
+
{ lang: "en", is_enabled: true, is_required: true, is_default: true },
|
|
81
|
+
{ lang: "es", is_enabled: true, is_required: true, is_default: false },
|
|
82
|
+
{ lang: "fr", is_enabled: true, is_required: false, is_default: false },
|
|
83
|
+
{
|
|
84
|
+
lang: "de",
|
|
85
|
+
is_enabled: false,
|
|
86
|
+
is_required: false,
|
|
87
|
+
is_default: false,
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const result = formatLocales(locales);
|
|
92
|
+
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
default: [
|
|
95
|
+
{ lang: "en", is_enabled: true, is_required: true, is_default: true },
|
|
96
|
+
],
|
|
97
|
+
required: [
|
|
98
|
+
{
|
|
99
|
+
lang: "es",
|
|
100
|
+
is_enabled: true,
|
|
101
|
+
is_required: true,
|
|
102
|
+
is_default: false,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
enabled: [
|
|
106
|
+
{
|
|
107
|
+
lang: "fr",
|
|
108
|
+
is_enabled: true,
|
|
109
|
+
is_required: false,
|
|
110
|
+
is_default: false,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should sort locales by language code", () => {
|
|
117
|
+
const locales = [
|
|
118
|
+
{ lang: "zh", is_enabled: true, is_required: false, is_default: false },
|
|
119
|
+
{ lang: "en", is_enabled: true, is_required: false, is_default: false },
|
|
120
|
+
{ lang: "ar", is_enabled: true, is_required: false, is_default: false },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const result = formatLocales(locales);
|
|
124
|
+
|
|
125
|
+
expect(result.enabled.map((l) => l.lang)).toEqual(["ar", "en", "zh"]);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("hasTranslatableFields", () => {
|
|
130
|
+
it("should return true if any field is translatable", () => {
|
|
131
|
+
const fields = [
|
|
132
|
+
{ widget: "number" },
|
|
133
|
+
{ widget: "string" },
|
|
134
|
+
{ widget: "date" },
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
expect(hasTranslatableFields(fields)).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should return false if no fields are translatable", () => {
|
|
141
|
+
const fields = [
|
|
142
|
+
{ widget: "number" },
|
|
143
|
+
{ widget: "date" },
|
|
144
|
+
{ widget: "boolean" },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
expect(hasTranslatableFields(fields)).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("isTranslatetableField", () => {
|
|
152
|
+
it("should identify translatable fields", () => {
|
|
153
|
+
expect(isTranslatetableField({ widget: "string" })).toBe(true);
|
|
154
|
+
expect(isTranslatetableField({ widget: "enriched_text" })).toBe(true);
|
|
155
|
+
expect(isTranslatetableField({ widget: "textarea" })).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should identify non-translatable fields", () => {
|
|
159
|
+
expect(isTranslatetableField({ widget: "number" })).toBe(false);
|
|
160
|
+
expect(isTranslatetableField({ widget: "date" })).toBe(false);
|
|
161
|
+
expect(isTranslatetableField({ widget: "boolean" })).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -62,3 +62,13 @@ export const formatLocales = (locales) =>
|
|
|
62
62
|
is_default: false,
|
|
63
63
|
})(localeList),
|
|
64
64
|
}))(locales);
|
|
65
|
+
|
|
66
|
+
export const hasTranslatableFields = (fields) => {
|
|
67
|
+
return _.some((field) => translatableFieldTypes.includes(field.widget))(
|
|
68
|
+
fields
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const isTranslatetableField = (field) => {
|
|
73
|
+
return translatableFieldTypes.includes(field.widget);
|
|
74
|
+
};
|