i18n interests, allow for fallbacks (#2692)
parent
4058174678
commit
bb7ce215f7
|
@ -0,0 +1,3 @@
|
||||||
|
export function capitalize(str: string) {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
|
}
|
|
@ -4,11 +4,13 @@ import {View, ViewStyle, TextStyle} from 'react-native'
|
||||||
import {useTheme, atoms as a, native} from '#/alf'
|
import {useTheme, atoms as a, native} from '#/alf'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import {capitalize} from '#/lib/strings/capitalize'
|
||||||
|
|
||||||
import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data'
|
import {Context} from '#/screens/Onboarding/state'
|
||||||
|
|
||||||
export function InterestButton({interest}: {interest: string}) {
|
export function InterestButton({interest}: {interest: string}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
const {interestsDisplayNames} = React.useContext(Context)
|
||||||
const ctx = Toggle.useItemContext()
|
const ctx = Toggle.useItemContext()
|
||||||
|
|
||||||
const styles = React.useMemo(() => {
|
const styles = React.useMemo(() => {
|
||||||
|
@ -72,7 +74,7 @@ export function InterestButton({interest}: {interest: string}) {
|
||||||
native({paddingTop: 2}),
|
native({paddingTop: 2}),
|
||||||
ctx.selected ? styles.textSelected : {},
|
ctx.selected ? styles.textSelected : {},
|
||||||
]}>
|
]}>
|
||||||
{INTEREST_TO_DISPLAY_NAME[interest]}
|
{interestsDisplayNames[interest] || capitalize(interest)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
export const INTEREST_TO_DISPLAY_NAME: {
|
|
||||||
[key: string]: string
|
|
||||||
} = {
|
|
||||||
news: 'News',
|
|
||||||
journalism: 'Journalism',
|
|
||||||
nature: 'Nature',
|
|
||||||
art: 'Art',
|
|
||||||
comics: 'Comics',
|
|
||||||
writers: 'Writers',
|
|
||||||
culture: 'Culture',
|
|
||||||
sports: 'Sports',
|
|
||||||
pets: 'Pets',
|
|
||||||
animals: 'Animals',
|
|
||||||
books: 'Books',
|
|
||||||
education: 'Education',
|
|
||||||
climate: 'Climate',
|
|
||||||
science: 'Science',
|
|
||||||
politics: 'Politics',
|
|
||||||
fitness: 'Fitness',
|
|
||||||
tech: 'Tech',
|
|
||||||
dev: 'Software Dev',
|
|
||||||
comedy: 'Comedy',
|
|
||||||
gaming: 'Video Games',
|
|
||||||
food: 'Food',
|
|
||||||
cooking: 'Cooking',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ApiResponseMap = {
|
|
||||||
interests: string[]
|
|
||||||
suggestedAccountDids: {
|
|
||||||
[key: string]: string[]
|
|
||||||
}
|
|
||||||
suggestedFeedUris: {
|
|
||||||
[key: string]: string[]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,17 +17,14 @@ import {getAgent} from '#/state/session'
|
||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useOnboardingDispatch} from '#/state/shell'
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
|
import {capitalize} from '#/lib/strings/capitalize'
|
||||||
|
|
||||||
import {Context} from '#/screens/Onboarding/state'
|
import {Context, ApiResponseMap} from '#/screens/Onboarding/state'
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
Description,
|
Description,
|
||||||
OnboardingControls,
|
OnboardingControls,
|
||||||
} from '#/screens/Onboarding/Layout'
|
} from '#/screens/Onboarding/Layout'
|
||||||
import {
|
|
||||||
ApiResponseMap,
|
|
||||||
INTEREST_TO_DISPLAY_NAME,
|
|
||||||
} from '#/screens/Onboarding/StepInterests/data'
|
|
||||||
import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton'
|
import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton'
|
||||||
import {IconCircle} from '#/screens/Onboarding/IconCircle'
|
import {IconCircle} from '#/screens/Onboarding/IconCircle'
|
||||||
|
|
||||||
|
@ -36,7 +33,7 @@ export function StepInterests() {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const {state, dispatch} = React.useContext(Context)
|
const {state, dispatch, interestsDisplayNames} = React.useContext(Context)
|
||||||
const [saving, setSaving] = React.useState(false)
|
const [saving, setSaving] = React.useState(false)
|
||||||
const [interests, setInterests] = React.useState<string[]>(
|
const [interests, setInterests] = React.useState<string[]>(
|
||||||
state.interestsStepResults.selectedInterests.map(i => i),
|
state.interestsStepResults.selectedInterests.map(i => i),
|
||||||
|
@ -202,7 +199,9 @@ export function StepInterests() {
|
||||||
<Toggle.Item
|
<Toggle.Item
|
||||||
key={interest}
|
key={interest}
|
||||||
name={interest}
|
name={interest}
|
||||||
label={INTEREST_TO_DISPLAY_NAME[interest]}>
|
label={
|
||||||
|
interestsDisplayNames[interest] || capitalize(interest)
|
||||||
|
}>
|
||||||
<InterestButton interest={interest} />
|
<InterestButton interest={interest} />
|
||||||
</Toggle.Item>
|
</Toggle.Item>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {Loader} from '#/components/Loader'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {useModerationOpts} from '#/state/queries/preferences'
|
import {useModerationOpts} from '#/state/queries/preferences'
|
||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
|
import {capitalize} from '#/lib/strings/capitalize'
|
||||||
|
|
||||||
import {Context} from '#/screens/Onboarding/state'
|
import {Context} from '#/screens/Onboarding/state'
|
||||||
import {
|
import {
|
||||||
|
@ -25,7 +26,6 @@ import {
|
||||||
SuggestedAccountCard,
|
SuggestedAccountCard,
|
||||||
SuggestedAccountCardPlaceholder,
|
SuggestedAccountCardPlaceholder,
|
||||||
} from '#/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard'
|
} from '#/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard'
|
||||||
import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data'
|
|
||||||
import {aggregateInterestItems} from '#/screens/Onboarding/util'
|
import {aggregateInterestItems} from '#/screens/Onboarding/util'
|
||||||
import {IconCircle} from '#/screens/Onboarding/IconCircle'
|
import {IconCircle} from '#/screens/Onboarding/IconCircle'
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ export function Inner({
|
||||||
export function StepSuggestedAccounts() {
|
export function StepSuggestedAccounts() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const {state, dispatch} = React.useContext(Context)
|
const {state, dispatch, interestsDisplayNames} = React.useContext(Context)
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const suggestedDids = React.useMemo(() => {
|
const suggestedDids = React.useMemo(() => {
|
||||||
return aggregateInterestItems(
|
return aggregateInterestItems(
|
||||||
|
@ -93,10 +93,10 @@ export function StepSuggestedAccounts() {
|
||||||
|
|
||||||
const interestsText = React.useMemo(() => {
|
const interestsText = React.useMemo(() => {
|
||||||
const i = state.interestsStepResults.selectedInterests.map(
|
const i = state.interestsStepResults.selectedInterests.map(
|
||||||
i => INTEREST_TO_DISPLAY_NAME[i],
|
i => interestsDisplayNames[i] || capitalize(i),
|
||||||
)
|
)
|
||||||
return i.join(', ')
|
return i.join(', ')
|
||||||
}, [state.interestsStepResults.selectedInterests])
|
}, [state.interestsStepResults.selectedInterests, interestsDisplayNames])
|
||||||
|
|
||||||
const handleContinue = React.useCallback(async () => {
|
const handleContinue = React.useCallback(async () => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
|
import {capitalize} from '#/lib/strings/capitalize'
|
||||||
|
|
||||||
import {Context} from '#/screens/Onboarding/state'
|
import {Context} from '#/screens/Onboarding/state'
|
||||||
import {
|
import {
|
||||||
|
@ -18,14 +19,13 @@ import {
|
||||||
OnboardingControls,
|
OnboardingControls,
|
||||||
} from '#/screens/Onboarding/Layout'
|
} from '#/screens/Onboarding/Layout'
|
||||||
import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard'
|
import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard'
|
||||||
import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data'
|
|
||||||
import {aggregateInterestItems} from '#/screens/Onboarding/util'
|
import {aggregateInterestItems} from '#/screens/Onboarding/util'
|
||||||
import {IconCircle} from '#/screens/Onboarding/IconCircle'
|
import {IconCircle} from '#/screens/Onboarding/IconCircle'
|
||||||
|
|
||||||
export function StepTopicalFeeds() {
|
export function StepTopicalFeeds() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const {state, dispatch} = React.useContext(Context)
|
const {state, dispatch, interestsDisplayNames} = React.useContext(Context)
|
||||||
const [selectedFeedUris, setSelectedFeedUris] = React.useState<string[]>([])
|
const [selectedFeedUris, setSelectedFeedUris] = React.useState<string[]>([])
|
||||||
const [saving, setSaving] = React.useState(false)
|
const [saving, setSaving] = React.useState(false)
|
||||||
const suggestedFeedUris = React.useMemo(() => {
|
const suggestedFeedUris = React.useMemo(() => {
|
||||||
|
@ -38,10 +38,10 @@ export function StepTopicalFeeds() {
|
||||||
|
|
||||||
const interestsText = React.useMemo(() => {
|
const interestsText = React.useMemo(() => {
|
||||||
const i = state.interestsStepResults.selectedInterests.map(
|
const i = state.interestsStepResults.selectedInterests.map(
|
||||||
i => INTEREST_TO_DISPLAY_NAME[i],
|
i => interestsDisplayNames[i] || capitalize(i),
|
||||||
)
|
)
|
||||||
return i.join(', ')
|
return i.join(', ')
|
||||||
}, [state.interestsStepResults.selectedInterests])
|
}, [state.interestsStepResults.selectedInterests, interestsDisplayNames])
|
||||||
|
|
||||||
const saveFeeds = React.useCallback(async () => {
|
const saveFeeds = React.useCallback(async () => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
|
|
||||||
|
@ -13,13 +15,44 @@ import {StepFinished} from '#/screens/Onboarding/StepFinished'
|
||||||
import {StepModeration} from '#/screens/Onboarding/StepModeration'
|
import {StepModeration} from '#/screens/Onboarding/StepModeration'
|
||||||
|
|
||||||
export function Onboarding() {
|
export function Onboarding() {
|
||||||
|
const {_} = useLingui()
|
||||||
const [state, dispatch] = React.useReducer(reducer, {...initialState})
|
const [state, dispatch] = React.useReducer(reducer, {...initialState})
|
||||||
|
|
||||||
|
const interestsDisplayNames = React.useMemo(() => {
|
||||||
|
return {
|
||||||
|
news: _(msg`News`),
|
||||||
|
journalism: _(msg`Journalism`),
|
||||||
|
nature: _(msg`Nature`),
|
||||||
|
art: _(msg`Art`),
|
||||||
|
comics: _(msg`Comics`),
|
||||||
|
writers: _(msg`Writers`),
|
||||||
|
culture: _(msg`Culture`),
|
||||||
|
sports: _(msg`Sports`),
|
||||||
|
pets: _(msg`Pets`),
|
||||||
|
animals: _(msg`Animals`),
|
||||||
|
books: _(msg`Books`),
|
||||||
|
education: _(msg`Education`),
|
||||||
|
climate: _(msg`Climate`),
|
||||||
|
science: _(msg`Science`),
|
||||||
|
politics: _(msg`Politics`),
|
||||||
|
fitness: _(msg`Fitness`),
|
||||||
|
tech: _(msg`Tech`),
|
||||||
|
dev: _(msg`Software Dev`),
|
||||||
|
comedy: _(msg`Comedy`),
|
||||||
|
gaming: _(msg`Video Games`),
|
||||||
|
food: _(msg`Food`),
|
||||||
|
cooking: _(msg`Cooking`),
|
||||||
|
}
|
||||||
|
}, [_])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<OnboardingControls.Provider>
|
<OnboardingControls.Provider>
|
||||||
<Context.Provider
|
<Context.Provider
|
||||||
value={React.useMemo(() => ({state, dispatch}), [state, dispatch])}>
|
value={React.useMemo(
|
||||||
|
() => ({state, dispatch, interestsDisplayNames}),
|
||||||
|
[state, dispatch, interestsDisplayNames],
|
||||||
|
)}>
|
||||||
<Layout>
|
<Layout>
|
||||||
{state.activeStep === 'interests' && <StepInterests />}
|
{state.activeStep === 'interests' && <StepInterests />}
|
||||||
{state.activeStep === 'suggestedAccounts' && (
|
{state.activeStep === 'suggestedAccounts' && (
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import {ApiResponseMap} from '#/screens/Onboarding/StepInterests/data'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
|
||||||
export type OnboardingState = {
|
export type OnboardingState = {
|
||||||
|
@ -59,6 +58,16 @@ export type OnboardingAction =
|
||||||
feedUris: string[]
|
feedUris: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ApiResponseMap = {
|
||||||
|
interests: string[]
|
||||||
|
suggestedAccountDids: {
|
||||||
|
[key: string]: string[]
|
||||||
|
}
|
||||||
|
suggestedFeedUris: {
|
||||||
|
[key: string]: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const initialState: OnboardingState = {
|
export const initialState: OnboardingState = {
|
||||||
hasPrev: false,
|
hasPrev: false,
|
||||||
totalSteps: 7,
|
totalSteps: 7,
|
||||||
|
@ -84,12 +93,41 @@ export const initialState: OnboardingState = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const INTEREST_TO_DISPLAY_NAME_DEFAULTS: {
|
||||||
|
[key: string]: string
|
||||||
|
} = {
|
||||||
|
news: 'News',
|
||||||
|
journalism: 'Journalism',
|
||||||
|
nature: 'Nature',
|
||||||
|
art: 'Art',
|
||||||
|
comics: 'Comics',
|
||||||
|
writers: 'Writers',
|
||||||
|
culture: 'Culture',
|
||||||
|
sports: 'Sports',
|
||||||
|
pets: 'Pets',
|
||||||
|
animals: 'Animals',
|
||||||
|
books: 'Books',
|
||||||
|
education: 'Education',
|
||||||
|
climate: 'Climate',
|
||||||
|
science: 'Science',
|
||||||
|
politics: 'Politics',
|
||||||
|
fitness: 'Fitness',
|
||||||
|
tech: 'Tech',
|
||||||
|
dev: 'Software Dev',
|
||||||
|
comedy: 'Comedy',
|
||||||
|
gaming: 'Video Games',
|
||||||
|
food: 'Food',
|
||||||
|
cooking: 'Cooking',
|
||||||
|
}
|
||||||
|
|
||||||
export const Context = React.createContext<{
|
export const Context = React.createContext<{
|
||||||
state: OnboardingState
|
state: OnboardingState
|
||||||
dispatch: React.Dispatch<OnboardingAction>
|
dispatch: React.Dispatch<OnboardingAction>
|
||||||
|
interestsDisplayNames: {[key: string]: string}
|
||||||
}>({
|
}>({
|
||||||
state: {...initialState},
|
state: {...initialState},
|
||||||
dispatch: () => {},
|
dispatch: () => {},
|
||||||
|
interestsDisplayNames: INTEREST_TO_DISPLAY_NAME_DEFAULTS,
|
||||||
})
|
})
|
||||||
|
|
||||||
export function reducer(
|
export function reducer(
|
||||||
|
|
|
@ -31,7 +31,15 @@ export function aggregateInterestItems(
|
||||||
const selected = interests.length
|
const selected = interests.length
|
||||||
const all = interests
|
const all = interests
|
||||||
.map(i => {
|
.map(i => {
|
||||||
const suggestions = shuffle(map[i])
|
// suggestions from server
|
||||||
|
const rawSuggestions = map[i]
|
||||||
|
|
||||||
|
// safeguard against a missing interest->suggestion mapping
|
||||||
|
if (!rawSuggestions || !rawSuggestions.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const suggestions = shuffle(rawSuggestions)
|
||||||
|
|
||||||
if (selected === 1) {
|
if (selected === 1) {
|
||||||
return suggestions // return all
|
return suggestions // return all
|
||||||
|
|
Loading…
Reference in New Issue