Skip to content

Commit

Permalink
fix user preference for language and sync with local storage
Browse files Browse the repository at this point in the history
  • Loading branch information
nemesis09 committed Sep 2, 2021
1 parent e0dec06 commit 43114c4
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 89 deletions.
5 changes: 2 additions & 3 deletions frontend/packages/ceph-storage-plugin/src/utils/common.tsx
@@ -1,12 +1,11 @@
import { TFunction } from 'i18next';
import * as React from 'react';

import { getLastLanguage } from '@console/app/src/components/user-preferences/language/getLastLanguage';
import { humanizePercentage } from '@console/internal/components/utils';

import { StorageClusterKind } from '../types';

const getLocale = () => localStorage.getItem('bridge/language');

export const checkArbiterCluster = (storageCluster: StorageClusterKind): boolean =>
storageCluster?.spec?.arbiter?.enable;

Expand All @@ -23,7 +22,7 @@ export const toList = (text: string[]): React.ReactNode => text.map((s) => <li k
export const calcPercentage = (value: number, total: number) =>
humanizePercentage((value * 100) / total).string;

export const twelveHoursdateTimeNoYear = new Intl.DateTimeFormat(getLocale() || undefined, {
export const twelveHoursdateTimeNoYear = new Intl.DateTimeFormat(getLastLanguage() || undefined, {
month: 'short',
day: 'numeric',
hour: 'numeric',
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/console-app/locales/en/console-app.json
Expand Up @@ -226,6 +226,7 @@
"Guided tour": "Guided tour",
"Step {{stepNumber, number}}/{{totalSteps, number}}": "Step {{stepNumber, number}}/{{totalSteps, number}}",
"guided tour {{step, number}}": "guided tour {{step, number}}",
"Use the default browser language setting.": "Use the default browser language setting.",
"Select a language": "Select a language",
"Search project": "Search project",
"Search namespace": "Search namespace",
Expand Down
@@ -0,0 +1,10 @@
import * as React from 'react';
import { usePreferredLanguage, useLanguage } from '../user-preferences/language';

const DetectLanguage: React.MemoExoticComponent<() => any> = React.memo(() => {
const [preferredLanguage, , preferredLanguageLoaded] = usePreferredLanguage();
useLanguage(preferredLanguage, preferredLanguageLoaded);
return null;
});

export default DetectLanguage;
Expand Up @@ -18,6 +18,7 @@ import {
} from '@console/shared';
import { useTelemetry } from '@console/shared/src/hooks/useTelemetry';
import { useUserSettings } from '@console/shared/src/hooks/useUserSettings';
import { getLastLanguage } from '../../user-preferences/language/getLastLanguage';

export { QuickStartContext };
export { QuickStartContextProvider };
Expand All @@ -26,7 +27,7 @@ export const getProcessedResourceBundle = (resourceBundle, lng) => {
const params = new URLSearchParams(window.location.search);
const pseudolocalizationEnabled = params.get('pseudolocalization') === 'true';

const language = lng || localStorage.getItem('bridge/language') || 'en';
const language = lng || getLastLanguage() || 'en';
let consoleBundle = resourceBundle;
if (pseudolocalizationEnabled && language === 'en') {
consoleBundle = {};
Expand Down Expand Up @@ -174,7 +175,7 @@ export const useValuesForQuickStartContext = (): QuickStartContextValues => {
[activeQuickStartID, setAllQuickStartStates, fireTelemetryEvent],
);

const language = localStorage.getItem('bridge/language') || 'en';
const language = getLastLanguage() || 'en';
const resourceBundle = i18n.getResourceBundle(language, 'console-app');
const processedResourceBundle = getProcessedResourceBundle(resourceBundle, language);

Expand Down
@@ -0,0 +1,3 @@
.co-user-preference-language-checkbox {
margin-bottom: var(--pf-global--spacer--sm) !important;
}
@@ -1,17 +1,16 @@
import * as React from 'react';
import { QuickStartContext, QuickStartContextValues } from '@patternfly/quickstarts';
import { Skeleton, SelectOption, Select, SelectVariant } from '@patternfly/react-core';
import { Skeleton, SelectOption, Select, SelectVariant, Checkbox } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { getProcessedResourceBundle } from '../../quick-starts/utils/quick-start-context';
import { supportedLocales } from './const';
import { useLanguage } from './useLanguage';
import { usePreferredLanguage } from './usePreferredLanguage';

import './LanguageDropdown.scss';

const LanguageDropdown: React.FC = () => {
const { i18n, t } = useTranslation();
const { setResourceBundle } = React.useContext<QuickStartContextValues>(QuickStartContext);
const { t } = useTranslation();
const [preferredLanguage, setPreferredLanguage, preferredLanguageLoaded] = usePreferredLanguage();
const [dropdownOpen, setDropdownOpen] = React.useState(false);

const selectOptions: JSX.Element[] = React.useMemo(
() =>
Object.keys(supportedLocales).map((lang) => (
Expand All @@ -22,54 +21,76 @@ const LanguageDropdown: React.FC = () => {
[],
);

const selectedLanguage =
preferredLanguage ||
// handles languages we support, languages we don't support, and subsets of languages we support (such as en-us, zh-cn, etc.)
i18n.languages.find((lang) => supportedLocales[lang]);
const [isUsingDefault, setIsUsingDefault] = React.useState<boolean>(!preferredLanguage);
const checkboxLabel: string = t('console-app~Use the default browser language setting.');

const onToggle = (isOpen: boolean) => setDropdownOpen(isOpen);
const onSelect = (_, selection: string) => {
if (selection !== preferredLanguage) {
i18n.changeLanguage(selection);
setPreferredLanguage(selection);
}
setDropdownOpen(false);
};

React.useEffect(() => {
const onLanguageChange = (lng: string) => {
// Update language resource of quick starts components
const resourceBundle = i18n.getResourceBundle(lng, 'console-app');
const processedBundle = getProcessedResourceBundle(resourceBundle, lng);
setResourceBundle(processedBundle, lng);
};
i18n.on('languageChanged', onLanguageChange);
const onUsingDefault = (checked: boolean) => {
setIsUsingDefault(checked);
if (checked) {
setPreferredLanguage(null);
}
};

useLanguage(preferredLanguage, preferredLanguageLoaded); // sync the preferred language with local storage and set the console language

return () => {
i18n.off('languageChanged', onLanguageChange);
};
});
React.useEffect(() => {
if (preferredLanguageLoaded) {
setIsUsingDefault(!preferredLanguage);
}
// run this hook only after resources have loaded
// to set the using default language checkbox when the form loads
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [preferredLanguageLoaded]);

return preferredLanguageLoaded ? (
<Select
variant={SelectVariant.single}
isOpen={dropdownOpen}
selections={selectedLanguage}
toggleId={'console.preferredLanguage'}
onToggle={onToggle}
onSelect={onSelect}
placeholderText={t('console-app~Select a language')}
data-test={'dropdown console.preferredLanguage'}
maxHeight={300}
>
{selectOptions}
</Select>
<>
<Checkbox
id="default-language-checkbox"
label={checkboxLabel}
isChecked={isUsingDefault}
onChange={onUsingDefault}
aria-label={checkboxLabel}
data-test="checkbox console.preferredLanguage"
className="co-user-preference-language-checkbox"
/>
<Select
variant={SelectVariant.single}
isOpen={dropdownOpen}
selections={preferredLanguage}
toggleId={'console.preferredLanguage'}
onToggle={onToggle}
onSelect={onSelect}
placeholderText={t('console-app~Select a language')}
aria-label={t('console-app~Select a language')}
data-test="dropdown console.preferredLanguage"
maxHeight={300}
isDisabled={isUsingDefault}
>
{selectOptions}
</Select>
</>
) : (
<Skeleton
height="30px"
width="100%"
data-test={'dropdown skeleton console.preferredLanguage'}
/>
<>
<Skeleton
height="15px"
width="100%"
data-test="checkbox skeleton console.preferredLanguage"
className="co-user-preference-language-checkbox"
/>
<Skeleton
height="30px"
width="100%"
data-test="dropdown skeleton console.preferredLanguage"
/>
</>
);
};

Expand Down
@@ -1,6 +1,7 @@
import * as React from 'react';
import { Select } from '@patternfly/react-core';
import { Checkbox, Select } from '@patternfly/react-core';
import { shallow, ShallowWrapper } from 'enzyme';
import { getLastLanguage } from '../getLastLanguage';
import LanguageDropdown from '../LanguageDropdown';
import { usePreferredLanguage } from '../usePreferredLanguage';

Expand All @@ -19,25 +20,25 @@ jest.mock('react-i18next', () => {
useTranslation: () => ({
t: (key: string) => key,
i18n: {
changeLanguage: jest.fn(),
getResourceBundle: jest.fn(),
on: jest.fn(),
off: jest.fn(),
languages: ['en'],
changeLanguage: jest.fn(),
},
}),
};
});

jest.mock('../../../quick-starts/utils/quick-start-context', () => ({
getProcessedResourceBundle: jest.fn(),
}));

jest.mock('../usePreferredLanguage', () => ({
usePreferredLanguage: jest.fn(),
}));

jest.mock('../getLastLanguage', () => ({
getLastLanguage: jest.fn(),
}));

const usePreferredLanguageMock = usePreferredLanguage as jest.Mock;
const getLastLanguageMock = getLastLanguage as jest.Mock;
const preferredLanguageValue = 'ja';

describe('LanguageDropdown', () => {
Expand All @@ -49,26 +50,43 @@ describe('LanguageDropdown', () => {

it('should render skeleton if user preferences have not loaded', () => {
usePreferredLanguageMock.mockReturnValue(['', jest.fn(), false]);
getLastLanguageMock.mockReturnValue(['']);
spyOn(React, 'useContext').and.returnValue({ getProcessedResourceBundle: jest.fn() });
wrapper = shallow(<LanguageDropdown />);
expect(
wrapper.find('[data-test="dropdown skeleton console.preferredLanguage"]').exists(),
).toBeTruthy();
});

it('should render select with value corresponding to preferred language if user preferences have loaded and preferred language is defined', () => {
it('should render checkbox in checked state and select in disabled state if user preferences have loaded and preferred language is not defined', () => {
usePreferredLanguageMock.mockReturnValue([undefined, jest.fn(), true]);
getLastLanguageMock.mockReturnValue(['']);
spyOn(React, 'useContext').and.returnValue({ getProcessedResourceBundle: jest.fn() });
wrapper = shallow(<LanguageDropdown />);
expect(wrapper.find('[data-test="checkbox console.preferredLanguage"]').exists()).toBeTruthy();
expect(wrapper.find(Checkbox).props().isChecked).toBe(true);
expect(wrapper.find('[data-test="dropdown console.preferredLanguage"]').exists()).toBeTruthy();
expect(wrapper.find(Select).props().isDisabled).toBe(true);
});

it('should render checkbox in unchecked state and select in enabled state if user preferences have loaded and preferred language is defined', () => {
usePreferredLanguageMock.mockReturnValue([preferredLanguageValue, jest.fn(), true]);
getLastLanguageMock.mockReturnValue(['']);
spyOn(React, 'useContext').and.returnValue({ getProcessedResourceBundle: jest.fn() });
wrapper = shallow(<LanguageDropdown />);
expect(wrapper.find('[data-test="checkbox console.preferredLanguage"]').exists()).toBeTruthy();
expect(wrapper.find(Checkbox).props().isChecked).toBe(false);
expect(wrapper.find('[data-test="dropdown console.preferredLanguage"]').exists()).toBeTruthy();
expect(wrapper.find(Select).props().selections).toEqual(preferredLanguageValue);
expect(wrapper.find(Select).props().isDisabled).toBe(false);
});

it('should render select with value from i18next languages if user preferences have loaded but preferred language is not defined', () => {
usePreferredLanguageMock.mockReturnValue([undefined, jest.fn(), true]);
it('should render select with value corresponding to preferred language if user preferences have loaded and preferred language is defined', () => {
usePreferredLanguageMock.mockReturnValue([preferredLanguageValue, jest.fn(), true]);
getLastLanguageMock.mockReturnValue(['']);
spyOn(React, 'useContext').and.returnValue({ getProcessedResourceBundle: jest.fn() });
wrapper = shallow(<LanguageDropdown />);
expect(wrapper.find('[data-test="checkbox console.preferredLanguage"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test="dropdown console.preferredLanguage"]').exists()).toBeTruthy();
expect(wrapper.find(Select).props().selections).toEqual('en');
expect(wrapper.find(Select).props().selections).toEqual(preferredLanguageValue);
});
});
Expand Up @@ -4,3 +4,5 @@ export const supportedLocales = {
ko: '한국어',
ja: '日本語',
};

export const LAST_LANGUAGE_LOCAL_STORAGE_KEY = 'bridge/last-language';
@@ -0,0 +1,3 @@
import { LAST_LANGUAGE_LOCAL_STORAGE_KEY } from './const';

export const getLastLanguage = (): string => localStorage.getItem(LAST_LANGUAGE_LOCAL_STORAGE_KEY);
@@ -1 +1,3 @@
export { default as LanguageDropdown } from './LanguageDropdown';
export * from './usePreferredLanguage';
export * from './useLanguage';
@@ -0,0 +1,38 @@
import * as React from 'react';
import { QuickStartContext, QuickStartContextValues } from '@patternfly/quickstarts';
import { useTranslation } from 'react-i18next';
import { getProcessedResourceBundle } from '../../quick-starts/utils/quick-start-context';
import { LAST_LANGUAGE_LOCAL_STORAGE_KEY } from './const';
import { getLastLanguage } from './getLastLanguage';

export const useLanguage = (preferredLanguage: string, preferredLanguageLoaded: boolean) => {
const { i18n } = useTranslation();
const { setResourceBundle } = React.useContext<QuickStartContextValues>(QuickStartContext);

React.useEffect(() => {
const onLanguageChange = (lng: string) => {
if (setResourceBundle) {
// Update language resource of quick starts components
const resourceBundle = i18n.getResourceBundle(lng, 'console-app');
const processedBundle = getProcessedResourceBundle(resourceBundle, lng);
setResourceBundle(processedBundle, lng);
}
};
const preferredLanguageInStorage: string = getLastLanguage();

i18n.on('languageChanged', onLanguageChange);

if (preferredLanguageLoaded && preferredLanguage !== preferredLanguageInStorage) {
if (preferredLanguage) {
localStorage.setItem(LAST_LANGUAGE_LOCAL_STORAGE_KEY, preferredLanguage);
i18n.changeLanguage(preferredLanguage);
} else {
preferredLanguageInStorage && localStorage.removeItem(LAST_LANGUAGE_LOCAL_STORAGE_KEY);
}
}

return () => {
i18n.off('languageChanged', onLanguageChange);
};
}, [i18n, preferredLanguage, preferredLanguageLoaded, setResourceBundle]);
};
@@ -1,3 +1,4 @@
import * as React from 'react';
import { useUserSettingsCompatibility } from '@console/shared/src/hooks/useUserSettingsCompatibility';

const PREFERRED_LANGUAGE_USER_SETTING_KEY = 'console.preferredLanguage';
Expand All @@ -11,4 +12,6 @@ export const usePreferredLanguage = (): [
useUserSettingsCompatibility<string>(
PREFERRED_LANGUAGE_USER_SETTING_KEY,
PREFERRED_LANGUAGE_LOCAL_STORAGE_KEY,
null,
true,
);

0 comments on commit 43114c4

Please sign in to comment.