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