Render whole app on language change
## Issues 1. We were manually adding `selectLanguage(state)` as a prop to components used in Settings Page to trigger a render. Flaws: - Unclear that the unused prop is just to trigger a render. - Manually adding on a case-by-case basis will break over time, especially when language can now be changed outside of the Settings Page. 2. The translation file fetching is delayed and also takes time, so the GUI will end up having mixed strings on F5, depending on when the fetch completed. ## Approach Make the app wrapper have a key that's tied to the language and translation data, so the entire app renders when language changes. Seems like a common design in most apps. ## Ticket 921 language refresh / selection issues
This commit is contained in:
parent
3f8dfd5b21
commit
f88bfcd7f3
13 changed files with 18 additions and 30 deletions
|
@ -138,6 +138,7 @@ function App(props: Props) {
|
||||||
const [upgradeNagClosed, setUpgradeNagClosed] = useState(false);
|
const [upgradeNagClosed, setUpgradeNagClosed] = useState(false);
|
||||||
const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false);
|
const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false);
|
||||||
const [retryingSync, setRetryingSync] = useState(false);
|
const [retryingSync, setRetryingSync] = useState(false);
|
||||||
|
const [langRenderKey, setLangRenderKey] = useState(0);
|
||||||
const [sidebarOpen] = usePersistedState('sidebar', true);
|
const [sidebarOpen] = usePersistedState('sidebar', true);
|
||||||
const [seenSunsestMessage, setSeenSunsetMessage] = usePersistedState('lbrytv-sunset', false);
|
const [seenSunsestMessage, setSeenSunsetMessage] = usePersistedState('lbrytv-sunset', false);
|
||||||
const showUpgradeButton =
|
const showUpgradeButton =
|
||||||
|
@ -482,6 +483,11 @@ function App(props: Props) {
|
||||||
|
|
||||||
useDegradedPerformance(setLbryTvApiStatus, user);
|
useDegradedPerformance(setLbryTvApiStatus, user);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// When language is changed or translations are fetched, we render.
|
||||||
|
setLangRenderKey(Date.now());
|
||||||
|
}, [language, languages]);
|
||||||
|
|
||||||
// Require an internal-api user on lbry.tv
|
// Require an internal-api user on lbry.tv
|
||||||
// This also prevents the site from loading in the un-authed state while we wait for internal-apis to return for the first time
|
// This also prevents the site from loading in the un-authed state while we wait for internal-apis to return for the first time
|
||||||
// It's not needed on desktop since there is no un-authed state
|
// It's not needed on desktop since there is no un-authed state
|
||||||
|
@ -510,6 +516,7 @@ function App(props: Props) {
|
||||||
// @endif
|
// @endif
|
||||||
})}
|
})}
|
||||||
ref={appRef}
|
ref={appRef}
|
||||||
|
key={langRenderKey}
|
||||||
onContextMenu={IS_WEB ? undefined : (e) => openContextMenu(e)}
|
onContextMenu={IS_WEB ? undefined : (e) => openContextMenu(e)}
|
||||||
>
|
>
|
||||||
{IS_WEB && lbryTvApiStatus === STATUS_DOWN ? (
|
{IS_WEB && lbryTvApiStatus === STATUS_DOWN ? (
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { selectHasChannels } from 'redux/selectors/claims';
|
||||||
import { selectWalletIsEncrypted } from 'redux/selectors/wallet';
|
import { selectWalletIsEncrypted } from 'redux/selectors/wallet';
|
||||||
import { doWalletStatus } from 'redux/actions/wallet';
|
import { doWalletStatus } from 'redux/actions/wallet';
|
||||||
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { selectLanguage } from 'redux/selectors/settings';
|
|
||||||
|
|
||||||
import SettingAccount from './view';
|
import SettingAccount from './view';
|
||||||
|
|
||||||
|
@ -12,7 +11,6 @@ const select = (state) => ({
|
||||||
walletEncrypted: selectWalletIsEncrypted(state),
|
walletEncrypted: selectWalletIsEncrypted(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
hasChannels: selectHasChannels(state),
|
hasChannels: selectHasChannels(state),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectUser, selectPasswordSetSuccess, selectPasswordSetError } from 'redux/selectors/user';
|
import { selectUser, selectPasswordSetSuccess, selectPasswordSetError } from 'redux/selectors/user';
|
||||||
import { selectLanguage } from 'redux/selectors/settings';
|
|
||||||
import { doUserPasswordSet, doClearPasswordEntry } from 'redux/actions/user';
|
import { doUserPasswordSet, doClearPasswordEntry } from 'redux/actions/user';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import UserSignIn from './view';
|
import UserSignIn from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state) => ({
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
passwordSetSuccess: selectPasswordSetSuccess(state),
|
passwordSetSuccess: selectPasswordSetSuccess(state),
|
||||||
passwordSetError: selectPasswordSetError(state),
|
passwordSetError: selectPasswordSetError(state),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, {
|
export default connect(select, {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { selectLanguage, selectClientSetting } from 'redux/selectors/settings';
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import SettingAppearance from './view';
|
import SettingAppearance from './view';
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ const select = (state) => ({
|
||||||
searchInLanguage: selectClientSetting(state, SETTINGS.SEARCH_IN_LANGUAGE),
|
searchInLanguage: selectClientSetting(state, SETTINGS.SEARCH_IN_LANGUAGE),
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
hideBalance: selectClientSetting(state, SETTINGS.HIDE_BALANCE),
|
hideBalance: selectClientSetting(state, SETTINGS.HIDE_BALANCE),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { doSetAutoLaunch } from 'redux/actions/settings';
|
import { doSetAutoLaunch } from 'redux/actions/settings';
|
||||||
import { selectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import SettingAutoLaunch from './view';
|
import SettingAutoLaunch from './view';
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = (state) => ({
|
||||||
autoLaunch: selectClientSetting(state, SETTINGS.AUTO_LAUNCH),
|
autoLaunch: selectClientSetting(state, SETTINGS.AUTO_LAUNCH),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as SETTINGS from 'constants/settings';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { selectShowMatureContent, selectLanguage, selectClientSetting } from 'redux/selectors/settings';
|
import { selectShowMatureContent, selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
|
||||||
import SettingContent from './view';
|
import SettingContent from './view';
|
||||||
|
@ -21,7 +21,6 @@ const select = (state) => ({
|
||||||
instantPurchaseEnabled: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_ENABLED),
|
instantPurchaseEnabled: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_ENABLED),
|
||||||
instantPurchaseMax: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_MAX),
|
instantPurchaseMax: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_MAX),
|
||||||
enablePublishPreview: selectClientSetting(state, SETTINGS.ENABLE_PUBLISH_PREVIEW),
|
enablePublishPreview: selectClientSetting(state, SETTINGS.ENABLE_PUBLISH_PREVIEW),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -10,12 +10,7 @@ import {
|
||||||
} from 'redux/actions/app';
|
} from 'redux/actions/app';
|
||||||
import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings';
|
import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings';
|
||||||
import { selectAllowAnalytics } from 'redux/selectors/app';
|
import { selectAllowAnalytics } from 'redux/selectors/app';
|
||||||
import {
|
import { selectDaemonSettings, selectFfmpegStatus, selectFindingFFmpeg } from 'redux/selectors/settings';
|
||||||
selectDaemonSettings,
|
|
||||||
selectFfmpegStatus,
|
|
||||||
selectFindingFFmpeg,
|
|
||||||
selectLanguage,
|
|
||||||
} from 'redux/selectors/settings';
|
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
|
||||||
import SettingSystem from './view';
|
import SettingSystem from './view';
|
||||||
|
@ -27,7 +22,6 @@ const select = (state) => ({
|
||||||
walletEncrypted: selectWalletIsEncrypted(state),
|
walletEncrypted: selectWalletIsEncrypted(state),
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
allowAnalytics: selectAllowAnalytics(state),
|
allowAnalytics: selectAllowAnalytics(state),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { selectLanguage, selectClientSetting } from 'redux/selectors/settings';
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
|
|
||||||
import SettingUnauthenticated from './view';
|
import SettingUnauthenticated from './view';
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = (state) => ({
|
||||||
searchInLanguage: selectClientSetting(state, SETTINGS.SEARCH_IN_LANGUAGE),
|
searchInLanguage: selectClientSetting(state, SETTINGS.SEARCH_IN_LANGUAGE),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { doClearClaimSearch } from 'redux/actions/claims';
|
||||||
import { doClearPurchasedUriSuccess } from 'redux/actions/file';
|
import { doClearPurchasedUriSuccess } from 'redux/actions/file';
|
||||||
import { selectFollowedTags } from 'redux/selectors/tags';
|
import { selectFollowedTags } from 'redux/selectors/tags';
|
||||||
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
||||||
import { selectHomepageData, selectLanguage, selectWildWestDisabled } from 'redux/selectors/settings';
|
import { selectHomepageData, selectWildWestDisabled } from 'redux/selectors/settings';
|
||||||
import { doSignOut } from 'redux/actions/app';
|
import { doSignOut } from 'redux/actions/app';
|
||||||
import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
|
import { selectUnseenNotificationCount } from 'redux/selectors/notifications';
|
||||||
import { selectPurchaseUriSuccess } from 'redux/selectors/claims';
|
import { selectPurchaseUriSuccess } from 'redux/selectors/claims';
|
||||||
|
@ -15,7 +15,6 @@ import SideNavigation from './view';
|
||||||
const select = (state) => ({
|
const select = (state) => ({
|
||||||
subscriptions: selectSubscriptions(state),
|
subscriptions: selectSubscriptions(state),
|
||||||
followedTags: selectFollowedTags(state),
|
followedTags: selectFollowedTags(state),
|
||||||
language: selectLanguage(state), // trigger redraw on language change
|
|
||||||
email: selectUserVerifiedEmail(state),
|
email: selectUserVerifiedEmail(state),
|
||||||
purchaseSuccess: selectPurchaseUriSuccess(state),
|
purchaseSuccess: selectPurchaseUriSuccess(state),
|
||||||
unseenCount: selectUnseenNotificationCount(state),
|
unseenCount: selectUnseenNotificationCount(state),
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
selectHashChanged,
|
selectHashChanged,
|
||||||
} from 'redux/selectors/sync';
|
} from 'redux/selectors/sync';
|
||||||
import { doCheckSync, doGetSync } from 'redux/actions/sync';
|
import { doCheckSync, doGetSync } from 'redux/actions/sync';
|
||||||
import { selectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { doSetWalletSyncPreference } from 'redux/actions/settings';
|
import { doSetWalletSyncPreference } from 'redux/actions/settings';
|
||||||
import SyncToggle from './view';
|
import SyncToggle from './view';
|
||||||
import { doGetAndPopulatePreferences } from 'redux/actions/app';
|
import { doGetAndPopulatePreferences } from 'redux/actions/app';
|
||||||
|
@ -20,7 +20,6 @@ const select = (state) => ({
|
||||||
verifiedEmail: selectUserVerifiedEmail(state),
|
verifiedEmail: selectUserVerifiedEmail(state),
|
||||||
getSyncError: selectGetSyncErrorMessage(state),
|
getSyncError: selectGetSyncErrorMessage(state),
|
||||||
getSyncPending: selectGetSyncIsPending(state),
|
getSyncPending: selectGetSyncIsPending(state),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as SETTINGS from 'constants/settings';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
|
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
|
||||||
import { selectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { doSetWalletSyncPreference } from 'redux/actions/settings';
|
import { doSetWalletSyncPreference } from 'redux/actions/settings';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import SyncToggle from './view';
|
import SyncToggle from './view';
|
||||||
|
@ -11,7 +11,6 @@ const select = (state) => ({
|
||||||
syncEnabled: selectClientSetting(state, SETTINGS.ENABLE_SYNC),
|
syncEnabled: selectClientSetting(state, SETTINGS.ENABLE_SYNC),
|
||||||
verifiedEmail: selectUserVerifiedEmail(state),
|
verifiedEmail: selectUserVerifiedEmail(state),
|
||||||
getSyncError: selectGetSyncErrorMessage(state),
|
getSyncError: selectGetSyncErrorMessage(state),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectLanguage, selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import { doOpenModal, doHideModal } from 'redux/actions/app';
|
import { doOpenModal, doHideModal } from 'redux/actions/app';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
|
@ -9,7 +9,6 @@ import analytics from 'analytics';
|
||||||
import Wunderbar from './view';
|
import Wunderbar from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
language: selectLanguage(state),
|
|
||||||
showMature: selectShowMatureContent(state),
|
showMature: selectShowMatureContent(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doEnterSettingsPage, doExitSettingsPage } from 'redux/actions/settings';
|
import { doEnterSettingsPage, doExitSettingsPage } from 'redux/actions/settings';
|
||||||
import { selectDaemonSettings, selectLanguage } from 'redux/selectors/settings';
|
import { selectDaemonSettings } from 'redux/selectors/settings';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
|
||||||
import SettingsPage from './view';
|
import SettingsPage from './view';
|
||||||
|
@ -8,7 +8,6 @@ import SettingsPage from './view';
|
||||||
const select = (state) => ({
|
const select = (state) => ({
|
||||||
daemonSettings: selectDaemonSettings(state),
|
daemonSettings: selectDaemonSettings(state),
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
language: selectLanguage(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
Loading…
Reference in a new issue