Commit 04878e09 authored by Justin Domingue's avatar Justin Domingue Committed by GitHub

feat(i18n): save and load locale from redux store (#1723)

* wrap sock around function component to get re-renders

* removed sourcelocale since we are using custom ids

* load locale data dynamically

* undo dev change

* ran yarn i18n:extract

* store locale in redux

* added support for redux

* restored header

* refactor

* addressed pr feedback

* specify type

* added navigator locale fallback to generic dialect

* make locale array const and typed

* addressed pr feedback

* fixed various

* addressed pr feedback

* make supported locales constant uppercase

* add back toUpperCase removed during refactoring

* removed lingui/detect-locale

* run yarn
parent 14a6953b
export const SUPPORTED_LOCALES = [
'en',
'pseudo-en',
'de',
'es-AR',
'es-US',
'it-IT',
'iw',
'ro',
'ru',
'vi',
'zh-CN',
'zh-TW',
] as const
export type SupportedLocale = typeof SUPPORTED_LOCALES[number]
export const defaultLocale: SupportedLocale = 'en'
import React from 'react' import React, { useEffect } from 'react'
import { i18n } from '@lingui/core' import { i18n } from '@lingui/core'
import { I18nProvider } from '@lingui/react' import { I18nProvider } from '@lingui/react'
import { detect, fromUrl } from '@lingui/detect-locale' import { ReactNode } from 'react'
import { ReactNode, useEffect } from 'react' import useParsedQueryString from 'hooks/useParsedQueryString'
import { useLocale } from 'state/user/hooks'
export const locales = ['en', 'pseudo-en', 'de', 'es-AR', 'es-US', 'it-IT', 'iw', 'ro', 'ru', 'vi', 'zh-CN', 'zh-TW'] import { SupportedLocale, SUPPORTED_LOCALES, defaultLocale } from './constants/locales'
export const defaultLocale = 'en'
function parseLocale(maybeSupportedLocale: string): SupportedLocale | undefined {
const getDetectedLocale = () => { return SUPPORTED_LOCALES.find((locale) => locale === maybeSupportedLocale)
const detected = }
detect(
fromUrl('lang'), // helps local development function navigatorLocale(): SupportedLocale | undefined {
defaultLocale if (!navigator.language) return undefined
) ?? defaultLocale
return locales.includes(detected) ? detected : defaultLocale const [language, region] = navigator.language.split('-')
if (region) {
return parseLocale(`${language}-${region.toUpperCase()}`) ?? parseLocale(language)
}
return parseLocale(language)
} }
export async function dynamicActivate(locale: string) { export async function dynamicActivate(locale: SupportedLocale) {
try {
const { messages } = await import(`@lingui/loader!./locales/${locale}.po`) const { messages } = await import(`@lingui/loader!./locales/${locale}.po`)
i18n.loadLocaleData(locale, { plurals: () => null }) i18n.loadLocaleData(locale, { plurals: () => null })
i18n.load(locale, messages) i18n.load(locale, messages)
i18n.activate(locale) i18n.activate(locale)
} catch (error) {
console.error(`Failed to load locale data for ${locale}`, error)
}
} }
export function LanguageProvider({ children }: { children: ReactNode }) { export function LanguageProvider({ children }: { children: ReactNode }) {
const parsed = useParsedQueryString()
const userLocale = useLocale()
useEffect(() => { useEffect(() => {
dynamicActivate(getDetectedLocale()).catch((error) => { const urlLocale = () => (typeof parsed.lng === 'string' && parseLocale(parsed.lng)) || undefined
console.error('Failed to load locale data', error)
}) dynamicActivate(userLocale ?? urlLocale() ?? navigatorLocale() ?? defaultLocale)
}, []) }, [userLocale, parsed])
return <I18nProvider i18n={i18n}>{children}</I18nProvider> return <I18nProvider i18n={i18n}>{children}</I18nProvider>
} }
...@@ -60,24 +60,24 @@ function Updaters() { ...@@ -60,24 +60,24 @@ function Updaters() {
ReactDOM.render( ReactDOM.render(
<StrictMode> <StrictMode>
<Provider store={store}>
<HashRouter>
<LanguageProvider> <LanguageProvider>
<FixedGlobalStyle /> <FixedGlobalStyle />
<Web3ReactProvider getLibrary={getLibrary}> <Web3ReactProvider getLibrary={getLibrary}>
<Web3ProviderNetwork getLibrary={getLibrary}> <Web3ProviderNetwork getLibrary={getLibrary}>
<Blocklist> <Blocklist>
<Provider store={store}>
<Updaters /> <Updaters />
<ThemeProvider> <ThemeProvider>
<ThemedGlobalStyle /> <ThemedGlobalStyle />
<HashRouter>
<App /> <App />
</HashRouter>
</ThemeProvider> </ThemeProvider>
</Provider>
</Blocklist> </Blocklist>
</Web3ProviderNetwork> </Web3ProviderNetwork>
</Web3ReactProvider> </Web3ReactProvider>
</LanguageProvider> </LanguageProvider>
</HashRouter>
</Provider>
</StrictMode>, </StrictMode>,
document.getElementById('root') document.getElementById('root')
) )
......
import { createAction } from '@reduxjs/toolkit' import { createAction } from '@reduxjs/toolkit'
import { SupportedLocale } from 'constants/locales'
export interface SerializedToken { export interface SerializedToken {
chainId: number chainId: number
...@@ -16,6 +17,7 @@ export interface SerializedPair { ...@@ -16,6 +17,7 @@ export interface SerializedPair {
export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('user/updateMatchesDarkMode') export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('user/updateMatchesDarkMode')
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode') export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode')
export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode') export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode')
export const updateUserLocale = createAction<{ userLocale: SupportedLocale }>('user/updateUserLocale')
export const updateUserSingleHopOnly = createAction<{ userSingleHopOnly: boolean }>('user/updateUserSingleHopOnly') export const updateUserSingleHopOnly = createAction<{ userSingleHopOnly: boolean }>('user/updateUserSingleHopOnly')
export const updateHideClosedPositions = createAction<{ userHideClosedPositions: boolean }>('user/hideClosedPositions') export const updateHideClosedPositions = createAction<{ userHideClosedPositions: boolean }>('user/hideClosedPositions')
export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number | 'auto' }>( export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number | 'auto' }>(
......
...@@ -22,7 +22,9 @@ import { ...@@ -22,7 +22,9 @@ import {
updateUserExpertMode, updateUserExpertMode,
updateUserSingleHopOnly, updateUserSingleHopOnly,
updateUserSlippageTolerance, updateUserSlippageTolerance,
updateUserLocale,
} from './actions' } from './actions'
import { SupportedLocale } from 'constants/locales'
function serializeToken(token: Token): SerializedToken { function serializeToken(token: Token): SerializedToken {
return { return {
...@@ -70,6 +72,24 @@ export function useDarkModeManager(): [boolean, () => void] { ...@@ -70,6 +72,24 @@ export function useDarkModeManager(): [boolean, () => void] {
return [darkMode, toggleSetDarkMode] return [darkMode, toggleSetDarkMode]
} }
export function useLocale(): SupportedLocale | null {
return useSelector<AppState, AppState['user']['userLocale']>((state) => state.user.userLocale)
}
export function useLocaleManager(): [SupportedLocale | null, (newLocale: SupportedLocale) => void] {
const dispatch = useDispatch<AppDispatch>()
const locale = useLocale()
const setLocale = useCallback(
(newLocale: SupportedLocale) => {
dispatch(updateUserLocale({ userLocale: newLocale }))
},
[dispatch]
)
return [locale, setLocale]
}
export function useIsExpertMode(): boolean { export function useIsExpertMode(): boolean {
return useSelector<AppState, AppState['user']['userExpertMode']>((state) => state.user.userExpertMode) return useSelector<AppState, AppState['user']['userExpertMode']>((state) => state.user.userExpertMode)
} }
......
...@@ -16,7 +16,9 @@ import { ...@@ -16,7 +16,9 @@ import {
toggleURLWarning, toggleURLWarning,
updateUserSingleHopOnly, updateUserSingleHopOnly,
updateHideClosedPositions, updateHideClosedPositions,
updateUserLocale,
} from './actions' } from './actions'
import { SupportedLocale } from 'constants/locales'
const currentTimestamp = () => new Date().getTime() const currentTimestamp = () => new Date().getTime()
...@@ -27,6 +29,8 @@ export interface UserState { ...@@ -27,6 +29,8 @@ export interface UserState {
userDarkMode: boolean | null // the user's choice for dark mode or light mode userDarkMode: boolean | null // the user's choice for dark mode or light mode
matchesDarkMode: boolean // whether the dark mode media query matches matchesDarkMode: boolean // whether the dark mode media query matches
userLocale: SupportedLocale | null
userExpertMode: boolean userExpertMode: boolean
userSingleHopOnly: boolean // only allow swaps on direct pairs userSingleHopOnly: boolean // only allow swaps on direct pairs
...@@ -66,6 +70,7 @@ export const initialState: UserState = { ...@@ -66,6 +70,7 @@ export const initialState: UserState = {
userDarkMode: null, userDarkMode: null,
matchesDarkMode: false, matchesDarkMode: false,
userExpertMode: false, userExpertMode: false,
userLocale: null,
userSingleHopOnly: false, userSingleHopOnly: false,
userHideClosedPositions: false, userHideClosedPositions: false,
userSlippageTolerance: 'auto', userSlippageTolerance: 'auto',
...@@ -124,6 +129,10 @@ export default createReducer(initialState, (builder) => ...@@ -124,6 +129,10 @@ export default createReducer(initialState, (builder) =>
state.userExpertMode = action.payload.userExpertMode state.userExpertMode = action.payload.userExpertMode
state.timestamp = currentTimestamp() state.timestamp = currentTimestamp()
}) })
.addCase(updateUserLocale, (state, action) => {
state.userLocale = action.payload.userLocale
state.timestamp = currentTimestamp()
})
.addCase(updateUserSlippageTolerance, (state, action) => { .addCase(updateUserSlippageTolerance, (state, action) => {
state.userSlippageTolerance = action.payload.userSlippageTolerance state.userSlippageTolerance = action.payload.userSlippageTolerance
state.timestamp = currentTimestamp() state.timestamp = currentTimestamp()
......
...@@ -2312,11 +2312,6 @@ ...@@ -2312,11 +2312,6 @@
make-plural "^6.2.2" make-plural "^6.2.2"
messageformat-parser "^4.1.3" messageformat-parser "^4.1.3"
"@lingui/detect-locale@^3.9.0":
version "3.9.0"
resolved "https://registry.npmjs.org/@lingui/detect-locale/-/detect-locale-3.9.0.tgz"
integrity sha512-PUn1LyW2WRIB1NSYBBx1Lf4rz27dPEgPs8EWHklCr1dPxYcW4pMFIHVzcBqLA4j7KtcYexFY7gCLZQDZkOm5VQ==
"@lingui/loader@^3.9.0": "@lingui/loader@^3.9.0":
version "3.9.0" version "3.9.0"
resolved "https://registry.npmjs.org/@lingui/loader/-/loader-3.9.0.tgz" resolved "https://registry.npmjs.org/@lingui/loader/-/loader-3.9.0.tgz"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment