Bluesky app fork with some witchin' additions 馃挮
at feat/tealfm 176 lines 5.5 kB view raw
1import React from 'react' 2 3import {type AppLanguage} from '#/locale/languages' 4import * as persisted from '#/state/persisted' 5import {AnalyticsContext, utils} from '#/analytics' 6 7type SetStateCb = ( 8 s: persisted.Schema['languagePrefs'], 9) => persisted.Schema['languagePrefs'] 10type StateContext = persisted.Schema['languagePrefs'] 11type ApiContext = { 12 setPrimaryLanguage: (code2: string) => void 13 setPostLanguage: (commaSeparatedLangCodes: string) => void 14 setContentLanguage: (code2: string) => void 15 toggleContentLanguage: (code2: string) => void 16 togglePostLanguage: (code2: string) => void 17 savePostLanguageToHistory: () => void 18 setAppLanguage: (code2: AppLanguage) => void 19} 20 21const stateContext = React.createContext<StateContext>( 22 persisted.defaults.languagePrefs, 23) 24stateContext.displayName = 'LanguagePrefsStateContext' 25const apiContext = React.createContext<ApiContext>({ 26 setPrimaryLanguage: (_: string) => {}, 27 setPostLanguage: (_: string) => {}, 28 setContentLanguage: (_: string) => {}, 29 toggleContentLanguage: (_: string) => {}, 30 togglePostLanguage: (_: string) => {}, 31 savePostLanguageToHistory: () => {}, 32 setAppLanguage: (_: AppLanguage) => {}, 33}) 34apiContext.displayName = 'LanguagePrefsApiContext' 35 36export function Provider({children}: React.PropsWithChildren<{}>) { 37 const [state, setState] = React.useState(persisted.get('languagePrefs')) 38 39 const setStateWrapped = React.useCallback( 40 (fn: SetStateCb) => { 41 const s = fn(persisted.get('languagePrefs')) 42 setState(s) 43 persisted.write('languagePrefs', s) 44 }, 45 [setState], 46 ) 47 48 React.useEffect(() => { 49 return persisted.onUpdate('languagePrefs', nextLanguagePrefs => { 50 setState(nextLanguagePrefs) 51 }) 52 }, [setStateWrapped]) 53 54 const api = React.useMemo( 55 () => ({ 56 setPrimaryLanguage(code2: string) { 57 setStateWrapped(s => ({...s, primaryLanguage: code2})) 58 }, 59 setPostLanguage(commaSeparatedLangCodes: string) { 60 setStateWrapped(s => ({...s, postLanguage: commaSeparatedLangCodes})) 61 }, 62 setContentLanguage(code2: string) { 63 setStateWrapped(s => ({...s, contentLanguages: [code2]})) 64 }, 65 toggleContentLanguage(code2: string) { 66 setStateWrapped(s => { 67 const exists = s.contentLanguages.includes(code2) 68 const next = exists 69 ? s.contentLanguages.filter(lang => lang !== code2) 70 : s.contentLanguages.concat(code2) 71 return { 72 ...s, 73 contentLanguages: next, 74 } 75 }) 76 }, 77 togglePostLanguage(code2: string) { 78 setStateWrapped(s => { 79 const exists = hasPostLanguage(state.postLanguage, code2) 80 let next = s.postLanguage 81 82 if (exists) { 83 next = toPostLanguages(s.postLanguage) 84 .filter(lang => lang !== code2) 85 .join(',') 86 } else { 87 // sort alphabetically for deterministic comparison in context menu 88 next = toPostLanguages(s.postLanguage) 89 .concat([code2]) 90 .sort((a, b) => a.localeCompare(b)) 91 .join(',') 92 } 93 94 return { 95 ...s, 96 postLanguage: next, 97 } 98 }) 99 }, 100 /** 101 * Saves whatever language codes are currently selected into a history array, 102 * which is then used to populate the language selector menu. 103 */ 104 savePostLanguageToHistory() { 105 // filter out duplicate `this.postLanguage` if exists, and prepend 106 // value to start of array 107 setStateWrapped(s => ({ 108 ...s, 109 postLanguageHistory: [s.postLanguage] 110 .concat( 111 s.postLanguageHistory.filter( 112 commaSeparatedLangCodes => 113 commaSeparatedLangCodes !== s.postLanguage, 114 ), 115 ) 116 .slice(0, 6), 117 })) 118 }, 119 setAppLanguage(code2: AppLanguage) { 120 setStateWrapped(s => ({...s, appLanguage: code2})) 121 }, 122 }), 123 [state, setStateWrapped], 124 ) 125 126 return ( 127 <stateContext.Provider value={state}> 128 <apiContext.Provider value={api}> 129 <AnalyticsContext 130 metadata={utils.useMeta({ 131 preferences: { 132 appLanguage: state.appLanguage, 133 contentLanguages: state.contentLanguages, 134 }, 135 })}> 136 {children} 137 </AnalyticsContext> 138 </apiContext.Provider> 139 </stateContext.Provider> 140 ) 141} 142 143export function useLanguagePrefs() { 144 return React.useContext(stateContext) 145} 146 147export function useLanguagePrefsApi() { 148 return React.useContext(apiContext) 149} 150 151export function getContentLanguages() { 152 return persisted.get('languagePrefs').contentLanguages 153} 154 155/** 156 * Be careful with this. It's used for the PWI home screen so that users can 157 * select a UI language and have it apply to the fetched Discover feed. 158 * 159 * We only support BCP-47 two-letter codes here, hence the split. 160 */ 161export function getAppLanguageAsContentLanguage() { 162 return persisted.get('languagePrefs').appLanguage.split('-')[0] 163} 164 165export function toPostLanguages(postLanguage: string): string[] { 166 // filter out empty strings if exist 167 return postLanguage.split(',').filter(Boolean) 168} 169 170export function fromPostLanguages(languages: string[]): string { 171 return languages.filter(Boolean).join(',') 172} 173 174export function hasPostLanguage(postLanguage: string, code2: string): boolean { 175 return toPostLanguages(postLanguage).includes(code2) 176}