Skip to content

Commit 826ef49

Browse files
refactor(i18n): respect lazy loaded translations (#541)
1 parent a38f098 commit 826ef49

File tree

16 files changed

+202
-161
lines changed

16 files changed

+202
-161
lines changed

.storybook/main.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ module.exports = {
2121
// You can change the configuration based on that.
2222
// 'PRODUCTION' is used when building the static version of storybook.
2323

24+
config.module.rules.push({
25+
test: /assets\/.*\.json$/,
26+
use: 'file-loader',
27+
type: 'javascript/auto'
28+
});
29+
2430
const tsLoader = {
2531
test: /\.tsx?$/,
2632
include: PATHS.packages,

.storybook/preview.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ const ThemeContainer = ({ theme, contentDensity, children, direction }) => {
3737
}
3838
}, [contentDensity]);
3939

40-
useEffect(() => {
41-
document.querySelector('html').setAttribute('dir', direction.toLowerCase());
42-
}, [direction]);
40+
// useEffect(() => {
41+
// document.querySelector('html').setAttribute('dir', direction.toLowerCase());
42+
// }, [direction]);
4343

4444
useEffect(() => {
4545
setTheme(theme);
Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,40 @@
11
import { fetchI18nBundle, getI18nBundle } from '@ui5/webcomponents-base/dist/i18nBundle';
22
import { useEffect, useState } from 'react';
33

4+
type TextWithDefault = { key: string; defaultText: string };
5+
type TextWithPlaceholders = [TextWithDefault, ...string[]];
6+
47
interface I18nBundle {
5-
getText: (textObj: { key: string; defaultText: string }, ...args: any) => string;
8+
getText: (textObj: TextWithDefault, ...args: any) => string;
69
}
710

8-
export const useI18nBundle = (bundleName): I18nBundle => {
9-
const [bundle, setBundle] = useState(getI18nBundle(bundleName));
11+
const resolveTranslations = (bundle, texts) => {
12+
return texts.map((text) => {
13+
if (Array.isArray(text)) {
14+
const [key, ...replacements] = text;
15+
return bundle.getText(key, replacements);
16+
}
17+
return bundle.getText(text);
18+
});
19+
};
20+
21+
export const useI18nText = (bundleName: string, ...texts: (TextWithDefault | TextWithPlaceholders)[]): string[] => {
22+
const i18nBundle: I18nBundle = getI18nBundle(bundleName);
23+
const [translations, setTranslations] = useState(resolveTranslations(i18nBundle, texts));
1024

1125
useEffect(() => {
1226
const fetchAndLoadBundle = async () => {
1327
await fetchI18nBundle(bundleName);
14-
setBundle(getI18nBundle(bundleName));
28+
setTranslations((prev) => {
29+
const next = resolveTranslations(i18nBundle, texts);
30+
if (prev.length === next.length && prev.every((translation, index) => next[index] === translation)) {
31+
return prev;
32+
}
33+
return next;
34+
});
1535
};
1636
fetchAndLoadBundle();
17-
}, []);
37+
}, [fetchI18nBundle, bundleName, texts]);
1838

19-
return bundle;
39+
return translations;
2040
};

packages/base/src/lib/hooks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useConsolidatedRef } from '../hooks/useConsolidatedRef';
2-
import { useI18nBundle } from '../hooks/useI18nBundle';
2+
import { useI18nText } from '../hooks/useI18nBundle';
33
import { usePassThroughHtmlProps } from '../hooks/usePassThroughHtmlProps';
44
import { useViewportRange } from '../hooks/useViewportRange';
55

6-
export { useConsolidatedRef, usePassThroughHtmlProps, useViewportRange, useI18nBundle };
6+
export { useConsolidatedRef, usePassThroughHtmlProps, useViewportRange, useI18nText };

packages/main/src/components/AnalyticalCardHeader/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
2-
import { useI18nBundle, usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/hooks';
2+
import { useI18nText, usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/hooks';
33
import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper';
44
import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils';
55
import { DEVIATION, TARGET } from '@ui5/webcomponents-react/dist/assets/i18n/i18n-defaults';
@@ -126,7 +126,7 @@ export const AnalyticalCardHeader: FC<AnalyticalCardHeaderPropTypes> = forwardRe
126126

127127
const passThroughProps = usePassThroughHtmlProps(props, ['onHeaderPress']);
128128

129-
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
129+
const [targetText, deviationText] = useI18nText('@ui5/webcomponents-react', TARGET, DEVIATION);
130130

131131
return (
132132
<div
@@ -172,7 +172,7 @@ export const AnalyticalCardHeader: FC<AnalyticalCardHeaderPropTypes> = forwardRe
172172
className={classes.targetAndDeviationColumn}
173173
wrap={FlexBoxWrap.NoWrap}
174174
>
175-
<span>{i18nBundle.getText(TARGET)}</span>
175+
<span>{targetText}</span>
176176
<span className={classes.targetAndDeviationValue}>{target}</span>
177177
</FlexBox>
178178
)}
@@ -182,7 +182,7 @@ export const AnalyticalCardHeader: FC<AnalyticalCardHeaderPropTypes> = forwardRe
182182
className={classes.targetAndDeviationColumn}
183183
wrap={FlexBoxWrap.NoWrap}
184184
>
185-
<span>{i18nBundle.getText(DEVIATION)}</span>
185+
<span>{deviationText}</span>
186186
<span className={classes.targetAndDeviationValue}>{deviation}</span>
187187
</FlexBox>
188188
)}

packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderModal.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import '@ui5/webcomponents-icons/dist/icons/decline';
2-
import { useI18nBundle } from '@ui5/webcomponents-react-base/lib/hooks';
2+
import { useI18nText } from '@ui5/webcomponents-react-base/lib/hooks';
33
import { ThemingParameters } from '@ui5/webcomponents-react-base/lib/ThemingParameters';
44
import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils';
55
import {
@@ -38,7 +38,14 @@ export const ColumnHeaderModal = forwardRef((props: ColumnHeaderModalProperties,
3838

3939
const { Filter } = column;
4040

41-
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
41+
const [clearSortingText, sortAscendingText, sortDescendingText, groupText, ungroupText] = useI18nText(
42+
'@ui5/webcomponents-react',
43+
CLEAR_SORTING,
44+
SORT_ASCENDING,
45+
SORT_DESCENDING,
46+
GROUP,
47+
UNGROUP
48+
);
4249

4350
const handleSort = useCallback(
4451
(e) => {
@@ -112,22 +119,22 @@ export const ColumnHeaderModal = forwardRef((props: ColumnHeaderModalProperties,
112119
<List onItemClick={handleSort}>
113120
{isSortedAscending && (
114121
<StandardListItem type={ListItemTypes.Active} icon="decline" data-sort="clear">
115-
{i18nBundle.getText(CLEAR_SORTING)}
122+
{clearSortingText}
116123
</StandardListItem>
117124
)}
118125
{showSort && !isSortedAscending && (
119126
<StandardListItem type={ListItemTypes.Active} icon="sort-ascending" data-sort="asc">
120-
{i18nBundle.getText(SORT_ASCENDING)}
127+
{sortAscendingText}
121128
</StandardListItem>
122129
)}
123130
{showSort && !isSortedDescending && (
124131
<StandardListItem type={ListItemTypes.Active} icon="sort-descending" data-sort="desc">
125-
{i18nBundle.getText(SORT_DESCENDING)}
132+
{sortDescendingText}
126133
</StandardListItem>
127134
)}
128135
{isSortedDescending && (
129136
<StandardListItem type={ListItemTypes.Active} icon="decline" data-sort="clear">
130-
{i18nBundle.getText(CLEAR_SORTING)}
137+
{clearSortingText}
131138
</StandardListItem>
132139
)}
133140
{showFilter && !column.isGrouped && (
@@ -145,7 +152,7 @@ export const ColumnHeaderModal = forwardRef((props: ColumnHeaderModalProperties,
145152
)}
146153
{showGroup && (
147154
<StandardListItem type={ListItemTypes.Active} icon="group-2" data-sort={'group'}>
148-
{i18nBundle.getText(column.isGrouped ? UNGROUP : GROUP)}
155+
{column.isGrouped ? ungroupText : groupText}
149156
</StandardListItem>
150157
)}
151158
</List>

packages/main/src/components/FilterBar/FilterDialog.tsx

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import '@ui5/webcomponents-icons/dist/icons/search';
22
import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
3-
import { useI18nBundle } from '@ui5/webcomponents-react-base/lib/hooks';
3+
import { useI18nText } from '@ui5/webcomponents-react-base/lib/hooks';
44
import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils';
55
import {
66
BASIC,
@@ -59,7 +59,24 @@ export const FilterDialog = (props) => {
5959
const dialogRefs = useRef({});
6060
const dialogRef = useRef();
6161

62-
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
62+
const [
63+
basicText,
64+
cancelText,
65+
clearText,
66+
restoreText,
67+
saveText,
68+
searchForFiltersText,
69+
showOnFilterBarText
70+
] = useI18nText(
71+
'@ui5/webcomponents-react',
72+
BASIC,
73+
CANCEL,
74+
CLEAR,
75+
RESTORE,
76+
SAVE,
77+
SEARCH_FOR_FILTERS,
78+
SHOW_ON_FILTER_BAR
79+
);
6380

6481
useEffect(() => {
6582
if (open) {
@@ -132,11 +149,11 @@ export const FilterDialog = (props) => {
132149
Go
133150
</Button>
134151
)}
135-
{showClearButton && <Button onClick={handleClearFilters}>{i18nBundle.getText(CLEAR)}</Button>}
136-
{showRestoreButton && <Button onClick={handleRestore}>{i18nBundle.getText(RESTORE)}</Button>}
137-
<Button onClick={handleSave}>{i18nBundle.getText(SAVE)}</Button>
152+
{showClearButton && <Button onClick={handleClearFilters}>{clearText}</Button>}
153+
{showRestoreButton && <Button onClick={handleRestore}>{restoreText}</Button>}
154+
<Button onClick={handleSave}>{saveText}</Button>
138155
<Button design={ButtonDesign.Transparent} onClick={handleCancel}>
139-
{i18nBundle.getText(CANCEL)}
156+
{cancelText}
140157
</Button>
141158
</FlexBox>
142159
),
@@ -162,11 +179,7 @@ export const FilterDialog = (props) => {
162179
<FlexBox direction={FlexBoxDirection.Column} className={classes.header}>
163180
<Title level={TitleLevel.H4}>Filters</Title>
164181
{showSearch && (
165-
<Input
166-
placeholder={i18nBundle.getText(SEARCH_FOR_FILTERS)}
167-
onInput={handleSearch}
168-
icon={<Icon name="search" />}
169-
/>
182+
<Input placeholder={searchForFiltersText} onInput={handleSearch} icon={<Icon name="search" />} />
170183
)}
171184
</FlexBox>
172185
),
@@ -244,9 +257,9 @@ export const FilterDialog = (props) => {
244257
<div className={classes.groupContainer} key={item}>
245258
<FlexBox justifyContent={FlexBoxJustifyContent.SpaceBetween} alignItems={FlexBoxAlignItems.Center}>
246259
<Title level={TitleLevel.H5} className={index === 0 ? classes.groupTitle : ''}>
247-
{item === 'default' ? i18nBundle.getText(BASIC) : item}
260+
{item === 'default' ? basicText : item}
248261
</Title>
249-
{index === 0 && <Text wrapping={false}>{i18nBundle.getText(SHOW_ON_FILTER_BAR)}</Text>}
262+
{index === 0 && <Text wrapping={false}>{showOnFilterBarText}</Text>}
250263
</FlexBox>
251264
<div className={classes.filters}>{filters}</div>
252265
</div>

packages/main/src/components/FilterBar/index.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
2-
import { useI18nBundle } from '@ui5/webcomponents-react-base/lib/hooks';
2+
import { useI18nText } from '@ui5/webcomponents-react-base/lib/hooks';
33
import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper';
44
import { usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/usePassThroughHtmlProps';
55
import { enrichEventWithDetails } from '@ui5/webcomponents-react-base/lib/Utils';
@@ -117,7 +117,15 @@ const FilterBar: FC<FilterBarPropTypes> = forwardRef((props: FilterBarPropTypes,
117117
const [toggledFilters, setToggledFilters] = useState({});
118118
const prevVisibleInFilterBarProps = useRef({});
119119

120-
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
120+
const [clearText, restoreText, showFilterBarText, hideFilterBarText, goText, filtersText] = useI18nText(
121+
'@ui5/webcomponents-react',
122+
CLEAR,
123+
RESTORE,
124+
SHOW_FILTER_BAR,
125+
HIDE_FILTER_BAR,
126+
GO,
127+
FILTERS
128+
);
121129

122130
useEffect(() => {
123131
if (showFilterConfiguration) {
@@ -337,20 +345,20 @@ const FilterBar: FC<FilterBarPropTypes> = forwardRef((props: FilterBarPropTypes,
337345
<div className={classes.headerRowRight}>
338346
{showClearOnFB && (
339347
<Button onClick={onClear} design={ButtonDesign.Transparent}>
340-
{i18nBundle.getText(CLEAR)}
348+
{clearText}
341349
</Button>
342350
)}
343351
{showRestoreOnFB && (
344352
<Button onClick={handleFBRestore} design={ButtonDesign.Transparent}>
345-
{i18nBundle.getText(RESTORE)}
353+
{restoreText}
346354
</Button>
347355
)}
348356
<Button onClick={handleToggle} design={ButtonDesign.Transparent} className={classes.showFiltersBtn}>
349-
{showFilters ? i18nBundle.getText(HIDE_FILTER_BAR) : i18nBundle.getText(SHOW_FILTER_BAR)}
357+
{showFilters ? hideFilterBarText : showFilterBarText}
350358
</Button>
351359
{showFilterConfiguration && (
352360
<Button onClick={handleDialogOpen}>
353-
{`${i18nBundle.getText(FILTERS)}${
361+
{`${filtersText}${
354362
activeFiltersCount && parseInt(activeFiltersCount as string) > 0
355363
? ` (${activeFiltersCount})`
356364
: ''
@@ -359,7 +367,7 @@ const FilterBar: FC<FilterBarPropTypes> = forwardRef((props: FilterBarPropTypes,
359367
)}
360368
{showGoOnFB && (
361369
<Button onClick={onGo} design={ButtonDesign.Emphasized}>
362-
{i18nBundle.getText(GO)}
370+
{goText}
363371
</Button>
364372
)}
365373
</div>

packages/main/src/components/Loader/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createComponentStyles } from '@ui5/webcomponents-react-base/lib/createComponentStyles';
2-
import { useI18nBundle } from '@ui5/webcomponents-react-base/lib/hooks';
32
import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper';
43
import { usePassThroughHtmlProps } from '@ui5/webcomponents-react-base/lib/usePassThroughHtmlProps';
4+
import { useI18nText } from '@ui5/webcomponents-react-base/lib/hooks';
55
import { PLEASE_WAIT } from '@ui5/webcomponents-react/dist/assets/i18n/i18n-defaults';
66
import { LoaderType } from '@ui5/webcomponents-react/lib/LoaderType';
77
import React, { CSSProperties, FC, forwardRef, RefObject, useEffect, useMemo, useState } from 'react';
@@ -55,7 +55,7 @@ const Loader: FC<LoaderProps> = forwardRef((props: LoaderProps, ref: RefObject<H
5555

5656
const passThroughProps = usePassThroughHtmlProps(props);
5757

58-
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
58+
const [pleaseWait] = useI18nText('@ui5/webcomponents-react', PLEASE_WAIT);
5959

6060
if (!isVisible) {
6161
return null;
@@ -68,7 +68,7 @@ const Loader: FC<LoaderProps> = forwardRef((props: LoaderProps, ref: RefObject<H
6868
data-component-name="Loader"
6969
aria-busy="true"
7070
role="progressbar"
71-
title={tooltip || i18nBundle.getText(PLEASE_WAIT)}
71+
title={tooltip || pleaseWait}
7272
slot={slot}
7373
style={inlineStyles}
7474
{...passThroughProps}

packages/main/src/components/MessageBox/MessageBox.jss.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ const style = {
1414
verticalAlign: 'middle'
1515
}
1616
},
17-
// justifyContent: 'center',
1817
padding: '0.25rem 1rem',
1918
boxSizing: 'border-box',
2019
borderBottom: `1px solid var(--messageBoxBorderColor)`,
2120
color: ThemingParameters.sapContent_LabelColor,
2221
fontSize: '1rem',
22+
'& ui5-icon:first-child': {
23+
padding: '0 0.25rem',
24+
width: '1rem',
25+
height: '1rem'
26+
},
2327
'&[data-type="Error"]': {
2428
'--sapPageFooter_BorderColor': ThemingParameters.sapErrorBorderColor,
2529
'--messageBoxBorderColor': ThemingParameters.sapErrorBorderColor,
@@ -63,11 +67,6 @@ const style = {
6367
}
6468
}
6569
},
66-
icon: {
67-
marginRight: '0.5rem',
68-
fontSize: '1rem',
69-
width: '1rem'
70-
},
7170
content: {
7271
padding: '1rem'
7372
},
@@ -77,8 +76,8 @@ const style = {
7776
alignItems: 'center',
7877
justifyContent: 'flex-end',
7978
padding: '0 0.5rem',
80-
'& >*:not(:last-child)': {
81-
marginRight: '0.5rem'
79+
'& > *': {
80+
margin: '0 0.25rem'
8281
},
8382
'& > ui5-button': {
8483
display: 'flex'

0 commit comments

Comments
 (0)