Skip to content

Commit 0b4b7e9

Browse files
authored
fix(localized-rich-text-input): fix omit empty translations, add readme (#1134)
1 parent 956a387 commit 0b4b7e9

File tree

6 files changed

+173
-8
lines changed

6 files changed

+173
-8
lines changed

src/components/inputs/localized-multiline-text-input/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ LocalizedMultilineTextInput.createLocalizedString(languages);
6060
// -> { en: '', de: '' }
6161
```
6262

63-
In case existingTranslations is passed, it will merge an empty localized field with the exisiting translations. Usually this is used to ensure that a localized string contains at least the project's languages.
63+
In case existingTranslations is passed, it will merge an empty localized field with the existing translations. Usually this is used to ensure that a localized string contains at least the project's languages.
6464

6565
```js
6666
const languages = ['en', 'de'];

src/components/inputs/localized-rich-text-input/README.md

+109
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,112 @@ const Input = props => {
2828
)
2929
}
3030
```
31+
32+
#### Properties
33+
34+
| Props | Type | Required | Values | Default | Description |
35+
| ------------------------------- | ---------------- | :------: | ----------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
36+
| `id` | `string` | - | - | - | Used as prefix of HTML `id` property. Each input field id will have the language as a suffix (`${idPrefix}.${lang}`), e.g. `foo.en` |
37+
| `name` | `string` | - | - | - | Used as HTML `name` property for each input field. Each input field name will have the language as a suffix (`${namePrefix}.${lang}`), e.g. `foo.en` |
38+
| `value` | `object` || - | - | Values to use. Keyed by language, the values are the actual values, e.g. `{ en: '<p>Horse</p>', de: '<p>Pferd</p>' }` |
39+
| `autoComplete` | `string` | - | - | - | Used as HTML `autocomplete` property |
40+
| `onChange` | `function` || - | - | Gets called when any input is changed. Is called with the change event of the changed input. |
41+
| `selectedLanguage` | `string` || - | - | Specifies which language will be shown in case the `LocalizedRichTextInput` is collapsed. |
42+
| `onBlur` | `function` | - | - | - | Called when any field is blurred. Is called with the `event` of that field. |
43+
| `onFocus` | `function` | - | - | - | Called when any field is focussed. Is called with the `event` of that field. |
44+
| `defaultExpandMultilineText` | `bool` | - | - | `false` | Expands input components holding multiline values instead of collpasing them by default. |
45+
| `hideLanguageExpansionControls` | `bool` | - | - | `false` | Will hide the language expansion controls when set to `true`. All languages will be shown when set to `true`. |
46+
| `defaultExpandLanguages` | `bool` | - | - | `false` | Controls whether one or all languages are visible by default. Pass `true` to show all languages by default. |
47+
| `isAutofocussed` | `bool` | - | - | `false` | Sets the focus on the first input when `true` is passed. |
48+
| `isDisabled` | `bool` | - | - | `false` | Disables all input fields. |
49+
| `isReadOnly` | `bool` | - | - | `false` | Disables all input fields and shows them in read-only mode. |
50+
| `placeholder` | `object` | - | - | - | Placeholders for each language. Object of the same shape as `value`. |
51+
| `horizontalConstraint` | `object` | - | `m`, `l`, `xl`, `scale` | `scale` | Horizontal size limit of the input fields. |
52+
| `hasError` | `bool` | - | - | - | Will apply the error state to each input without showing any error message. |
53+
| `hasWarning` | `bool` | - | - | - | Will apply the warning state to each input without showing any warning message. |
54+
| `errors` | `objectOf(node)` | - | - | - | Used to show errors underneath the inputs of specific languages. Pass an object whose key is a language and whose value is the error to show for that key. |
55+
| `warnings` | `objectOf(node)` | - | - | - | Used to show warnings underneath the inputs of specific languages. Pass an object whose key is a language and whose value is the warning to show for that key. |
56+
57+
The component forwards all `data` attribute props. It further adds a `-${language}` suffix to the values of the `data-test` and `data-track-component` attributes, e.g `data-test="foo"` will get added to the input for `en` as `data-test="foo-en"`.
58+
59+
Main Functions and use cases are:
60+
61+
- Receiving localized HTML input from user
62+
63+
#### Static Properties
64+
65+
##### `createLocalizedString(languages, existingTranslations)`
66+
67+
This function creates a [localized string](https://docs.commercetools.com/http-api-types.html#localizedstring). It merges the `languages` and the language keys of existing translations to form a localized string holding all languages.
68+
The `existingTranslations` argument is optional. If it is not passed, an empty localized field will be created.
69+
70+
```js
71+
const languages = ['en', 'de'];
72+
LocalizedRichTextInput.createLocalizedString(languages);
73+
// -> { en: '<p></p>', de: '<p></p>' }
74+
```
75+
76+
In case existingTranslations is passed, it will merge an empty localized field with the existing translations. Usually this is used to ensure that a localized string contains at least the project's languages.
77+
78+
```js
79+
const languages = ['en', 'de'];
80+
const existingTranslations = { en: '<p>Tree</p>', ar: '<p>شجرة</p>' };
81+
LocalizedRichTextInput.createLocalizedString(languages, existingTranslations);
82+
// -> { en: 'Tree', de: '', ar: '<p>شجرة</p>' }
83+
```
84+
85+
##### `isEmpty(localizedField)`
86+
87+
Returns `true` when all values in a localized field are empty.
88+
89+
```js
90+
LocalizedRichTextInput.isEmpty({});
91+
// -> true
92+
```
93+
94+
```js
95+
LocalizedRichTextInput.isEmpty({ en: '', de: '<p></p>' });
96+
// -> true
97+
```
98+
99+
```js
100+
LocalizedRichTextInput.isEmpty({ en: '<p>Tree</p>', de: '<p></p>' });
101+
// -> false
102+
```
103+
104+
##### `omitEmptyTranslations(localizedField)`
105+
106+
Omits translations with empty values.
107+
108+
```js
109+
LocalizedRichTextInput.omitEmptyTranslations({ en: '', de: ' ' });
110+
// -> {}
111+
```
112+
113+
```js
114+
LocalizedRichTextInput.omitEmptyTranslations({ en: '<p>Tree</p>', de: '' });
115+
// -> { en: '<p>Tree</p>' }
116+
```
117+
118+
##### `isTouched(touched)`
119+
120+
Expects to be called with an object or `undefined`.
121+
Returns `true` when at least one value is truthy.
122+
123+
##### `RequiredValueErrorMessage`
124+
125+
This field exports a default error message which can be used when the field is
126+
required, but the user provided no value. You can use it as
127+
128+
```js
129+
render (
130+
return (
131+
<div>
132+
<LocalizedRichTextInput hasError={isMissing} />
133+
{
134+
isMissing && <LocalizedRichTextInput.RequiredValueErrorMessage />
135+
}
136+
</div>
137+
)
138+
)
139+
```

src/components/inputs/localized-rich-text-input/localized-rich-text-input.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import {
1111
getHasErrorOnRemainingLanguages,
1212
getHasWarningOnRemainingLanguages,
1313
isTouched,
14-
omitEmptyTranslations,
1514
getId,
1615
getName,
1716
} from '../../../utils/localized';
1817
import RichTextInput from './rich-text-input';
1918
import {
2019
isEmpty,
2120
createLocalizedString,
21+
omitEmptyTranslations,
2222
} from '../../internals/rich-text-utils/localized';
2323
import RequiredValueErrorMessage from './required-value-error-message';
2424

Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
export { createLocalizedString, isEmpty } from './localized';
1+
export {
2+
createLocalizedString,
3+
isEmpty,
4+
omitEmptyTranslations,
5+
} from './localized';

src/components/internals/rich-text-utils/localized/localized.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
1+
import invariant from 'tiny-invariant';
12
import uniq from 'lodash/uniq';
23
import html from '../html';
34
import isRichTextEmpty from '../is-empty';
45

56
const initializeValue = value => html.serialize(html.deserialize(value));
67

7-
export const isEmpty = localizedString => {
8-
if (!localizedString) return true;
9-
return Object.values(localizedString).every(
10-
value => !value || isRichTextEmpty(value)
8+
const isLocalizedHtmlValueEmpty = value => !value || isRichTextEmpty(value);
9+
10+
export const isEmpty = localizedHtmlValue => {
11+
if (!localizedHtmlValue) return true;
12+
return Object.values(localizedHtmlValue).every(isLocalizedHtmlValueEmpty);
13+
};
14+
15+
export const omitEmptyTranslations = localizedString => {
16+
invariant(
17+
typeof localizedString === 'object',
18+
'omitEmptyTranslations must be called with an object'
19+
);
20+
return Object.entries(localizedString).reduce(
21+
(localizedStringWithoutEmptyTranslations, [language, value]) => {
22+
if (!isLocalizedHtmlValueEmpty(value)) {
23+
// eslint-disable-next-line no-param-reassign
24+
localizedStringWithoutEmptyTranslations[language] = value;
25+
}
26+
return localizedStringWithoutEmptyTranslations;
27+
},
28+
{}
1129
);
1230
};
1331

src/components/internals/rich-text-utils/localized/localized.spec.js

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { createLocalizedString, isEmpty } from './localized';
1+
import {
2+
createLocalizedString,
3+
isEmpty,
4+
omitEmptyTranslations,
5+
} from './localized';
26

37
const emptyValue = '<p></p>';
48

@@ -60,3 +64,33 @@ describe('isEmpty', () => {
6064
});
6165
});
6266
});
67+
68+
describe('omitEmptyTranslations', () => {
69+
describe('when called with an empty object', () => {
70+
it('should indicate that there are no defined translations', () => {
71+
expect(omitEmptyTranslations({})).toEqual({});
72+
});
73+
});
74+
describe('when called with all undefined values', () => {
75+
it('should indicate that there are no defined translations', () => {
76+
expect(omitEmptyTranslations({ en: null, de: null })).toEqual({});
77+
});
78+
});
79+
describe('when called with one defined value', () => {
80+
it('should indiciate that there is one defined translations', () => {
81+
expect(
82+
omitEmptyTranslations({ en: emptyValue, de: '<p>Hallo</p>' })
83+
).toEqual({ de: '<p>Hallo</p>' });
84+
});
85+
});
86+
describe('when called with all defined values', () => {
87+
it('should indiciate that there are two defined translations', () => {
88+
expect(
89+
omitEmptyTranslations({
90+
en: '<h1>Poutine</h1>',
91+
de: '<p>Hello World</p>',
92+
})
93+
).toEqual({ en: '<h1>Poutine</h1>', de: '<p>Hello World</p>' });
94+
});
95+
});
96+
});

0 commit comments

Comments
 (0)