Improve the language behaviors around the PWI (#3545)
* Handle leftnav overflow with longer languages' copy * Update the language dropdown to set ALL language prefs * Add hackfix to language cachebusting on PWI * Reset feeds on language changezio/stable
parent
23056daa29
commit
0b43d728e4
|
@ -1,16 +1,19 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {resetPostsFeedQueries} from '#/state/queries/post-feed'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||
|
||||
export function AppLanguageDropdown() {
|
||||
const t = useTheme()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const langPrefs = useLanguagePrefs()
|
||||
const setLangPrefs = useLanguagePrefsApi()
|
||||
const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage)
|
||||
|
@ -21,8 +24,13 @@ export function AppLanguageDropdown() {
|
|||
if (sanitizedLang !== value) {
|
||||
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
|
||||
}
|
||||
setLangPrefs.setPrimaryLanguage(value)
|
||||
setLangPrefs.setContentLanguage(value)
|
||||
|
||||
// reset feeds to refetch content
|
||||
resetPostsFeedQueries(queryClient)
|
||||
},
|
||||
[sanitizedLang, setLangPrefs],
|
||||
[sanitizedLang, setLangPrefs, queryClient],
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {resetPostsFeedQueries} from '#/state/queries/post-feed'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
@ -11,6 +13,7 @@ import {Text} from '#/components/Typography'
|
|||
export function AppLanguageDropdown() {
|
||||
const t = useTheme()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const langPrefs = useLanguagePrefs()
|
||||
const setLangPrefs = useLanguagePrefsApi()
|
||||
|
||||
|
@ -24,8 +27,13 @@ export function AppLanguageDropdown() {
|
|||
if (sanitizedLang !== value) {
|
||||
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
|
||||
}
|
||||
setLangPrefs.setPrimaryLanguage(value)
|
||||
setLangPrefs.setContentLanguage(value)
|
||||
|
||||
// reset feeds to refetch content
|
||||
resetPostsFeedQueries(queryClient)
|
||||
},
|
||||
[sanitizedLang, setLangPrefs],
|
||||
[sanitizedLang, setLangPrefs, queryClient],
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedGetFeed as GetCustomFeed,
|
||||
AtpAgent,
|
||||
} from '@atproto/api'
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
import {getAgent} from '#/state/session'
|
||||
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {getAgent} from '#/state/session'
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
|
||||
export class CustomFeedAPI implements FeedAPI {
|
||||
constructor(public params: GetCustomFeed.QueryParams) {}
|
||||
|
@ -29,14 +31,17 @@ export class CustomFeedAPI implements FeedAPI {
|
|||
limit: number
|
||||
}): Promise<FeedAPIResponse> {
|
||||
const contentLangs = getContentLanguages().join(',')
|
||||
const res = await getAgent().app.bsky.feed.getFeed(
|
||||
{
|
||||
...this.params,
|
||||
cursor,
|
||||
limit,
|
||||
},
|
||||
{headers: {'Accept-Language': contentLangs}},
|
||||
)
|
||||
const agent = getAgent()
|
||||
const res = agent.session
|
||||
? await getAgent().app.bsky.feed.getFeed(
|
||||
{
|
||||
...this.params,
|
||||
cursor,
|
||||
limit,
|
||||
},
|
||||
{headers: {'Accept-Language': contentLangs}},
|
||||
)
|
||||
: await loggedOutFetch({...this.params, cursor, limit})
|
||||
if (res.success) {
|
||||
// NOTE
|
||||
// some custom feeds fail to enforce the pagination limit
|
||||
|
@ -55,3 +60,59 @@ export class CustomFeedAPI implements FeedAPI {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HACK
|
||||
// we want feeds to give language-specific results immediately when a
|
||||
// logged-out user changes their language. this comes with two problems:
|
||||
// 1. not all languages have content, and
|
||||
// 2. our public caching layer isnt correctly busting against the accept-language header
|
||||
// for now we handle both of these with a manual workaround
|
||||
// -prf
|
||||
async function loggedOutFetch({
|
||||
feed,
|
||||
limit,
|
||||
cursor,
|
||||
}: {
|
||||
feed: string
|
||||
limit: number
|
||||
cursor?: string
|
||||
}) {
|
||||
let contentLangs = getContentLanguages().join(',')
|
||||
|
||||
// manually construct fetch call so we can add the `lang` cache-busting param
|
||||
let res = await AtpAgent.fetch!(
|
||||
`https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${
|
||||
cursor ? `&cursor=${cursor}` : ''
|
||||
}&limit=${limit}&lang=${contentLangs}`,
|
||||
'GET',
|
||||
{'Accept-Language': contentLangs},
|
||||
undefined,
|
||||
)
|
||||
if (res.body?.feed?.length) {
|
||||
return {
|
||||
success: true,
|
||||
data: res.body,
|
||||
}
|
||||
}
|
||||
|
||||
// no data, try again with language headers removed
|
||||
res = await AtpAgent.fetch!(
|
||||
`https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${
|
||||
cursor ? `&cursor=${cursor}` : ''
|
||||
}&limit=${limit}`,
|
||||
'GET',
|
||||
{'Accept-Language': ''},
|
||||
undefined,
|
||||
)
|
||||
if (res.body?.feed?.length) {
|
||||
return {
|
||||
success: true,
|
||||
data: res.body,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
data: {feed: []},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react'
|
||||
import * as persisted from '#/state/persisted'
|
||||
|
||||
import {AppLanguage} from '#/locale/languages'
|
||||
import * as persisted from '#/state/persisted'
|
||||
|
||||
type SetStateCb = (
|
||||
s: persisted.Schema['languagePrefs'],
|
||||
|
@ -9,6 +10,7 @@ type StateContext = persisted.Schema['languagePrefs']
|
|||
type ApiContext = {
|
||||
setPrimaryLanguage: (code2: string) => void
|
||||
setPostLanguage: (commaSeparatedLangCodes: string) => void
|
||||
setContentLanguage: (code2: string) => void
|
||||
toggleContentLanguage: (code2: string) => void
|
||||
togglePostLanguage: (code2: string) => void
|
||||
savePostLanguageToHistory: () => void
|
||||
|
@ -21,6 +23,7 @@ const stateContext = React.createContext<StateContext>(
|
|||
const apiContext = React.createContext<ApiContext>({
|
||||
setPrimaryLanguage: (_: string) => {},
|
||||
setPostLanguage: (_: string) => {},
|
||||
setContentLanguage: (_: string) => {},
|
||||
toggleContentLanguage: (_: string) => {},
|
||||
togglePostLanguage: (_: string) => {},
|
||||
savePostLanguageToHistory: () => {},
|
||||
|
@ -53,6 +56,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||
setPostLanguage(commaSeparatedLangCodes: string) {
|
||||
setStateWrapped(s => ({...s, postLanguage: commaSeparatedLangCodes}))
|
||||
},
|
||||
setContentLanguage(code2: string) {
|
||||
setStateWrapped(s => ({...s, contentLanguages: [code2]}))
|
||||
},
|
||||
toggleContentLanguage(code2: string) {
|
||||
setStateWrapped(s => {
|
||||
const exists = s.contentLanguages.includes(code2)
|
||||
|
|
|
@ -459,6 +459,14 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) {
|
|||
}
|
||||
}
|
||||
|
||||
export function resetPostsFeedQueries(queryClient: QueryClient, timeout = 0) {
|
||||
setTimeout(() => {
|
||||
queryClient.resetQueries({
|
||||
predicate: query => query.queryKey[0] === RQKEY_ROOT,
|
||||
})
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
export function resetProfilePostsQueries(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
|
|
|
@ -48,7 +48,13 @@ let NavSignupCard = ({}: {}): React.ReactNode => {
|
|||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={{flexDirection: 'row', paddingTop: 12, gap: 8}}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
paddingTop: 12,
|
||||
gap: 8,
|
||||
}}>
|
||||
<Button
|
||||
onPress={showCreateAccount}
|
||||
accessibilityHint={_(msg`Sign up`)}
|
||||
|
|
Loading…
Reference in New Issue