diff options
author | Eric Bailey <git@esb.lol> | 2023-09-21 13:33:19 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-21 11:33:19 -0700 |
commit | 8a5f9cd43df6afde99c0e0111c7de0faa7635162 (patch) | |
tree | 64fa6efcb4d66be8df51780657599e91565fb0c9 | |
parent | 335061f76349a4cecfdefb0348fc26bb67f6ebfa (diff) | |
download | voidsky-8a5f9cd43df6afde99c0e0111c7de0faa7635162.tar.zst |
Language settings updates, new primary language setting (#1471)
* move content languages to screen * add dropdown library, style primary lang select * update settings button * show selected langauges in button * use primary language in translator link * update copy * lint
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/Navigation.tsx | 6 | ||||
-rw-r--r-- | src/lib/routes/types.ts | 1 | ||||
-rw-r--r-- | src/locale/helpers.ts | 4 | ||||
-rw-r--r-- | src/routes.ts | 1 | ||||
-rw-r--r-- | src/state/models/ui/preferences.ts | 15 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 5 | ||||
-rw-r--r-- | src/view/com/post/Post.tsx | 5 | ||||
-rw-r--r-- | src/view/com/posts/FeedItem.tsx | 5 | ||||
-rw-r--r-- | src/view/index.ts | 2 | ||||
-rw-r--r-- | src/view/screens/LanguageSettings.tsx | 205 | ||||
-rw-r--r-- | src/view/screens/Settings.tsx | 17 | ||||
-rw-r--r-- | yarn.lock | 18 |
13 files changed, 272 insertions, 14 deletions
diff --git a/package.json b/package.json index c6c7bfa99..67747791b 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@react-native-community/blur": "^4.3.0", "@react-native-community/datetimepicker": "7.2.0", "@react-native-menu/menu": "^0.8.0", + "@react-native-picker/picker": "2.4.10", "@react-navigation/bottom-tabs": "^6.5.7", "@react-navigation/drawer": "^6.6.2", "@react-navigation/native": "^6.1.6", @@ -130,6 +131,7 @@ "react-native-ios-context-menu": "^1.15.3", "react-native-linear-gradient": "^2.6.2", "react-native-pager-view": "6.1.4", + "react-native-picker-select": "^8.1.0", "react-native-progress": "bluesky-social/react-native-progress", "react-native-reanimated": "^3.4.2", "react-native-root-siblings": "^4.1.1", diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 604fca2b9..a247c72dd 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -46,6 +46,7 @@ import {ModerationScreen} from './view/screens/Moderation' import {ModerationMuteListsScreen} from './view/screens/ModerationMuteLists' import {NotFoundScreen} from './view/screens/NotFound' import {SettingsScreen} from './view/screens/Settings' +import {LanguageSettingsScreen} from './view/screens/LanguageSettings' import {ProfileScreen} from './view/screens/Profile' import {ProfileFollowersScreen} from './view/screens/ProfileFollowers' import {ProfileFollowsScreen} from './view/screens/ProfileFollows' @@ -119,6 +120,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { options={{title: title('Settings')}} /> <Stack.Screen + name="LanguageSettings" + component={LanguageSettingsScreen} + options={{title: title('Language Settings')}} + /> + <Stack.Screen name="Profile" component={ProfileScreen} options={({route}) => ({ diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index e2867a707..35a379d48 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -10,6 +10,7 @@ export type CommonNavigatorParams = { ModerationMutedAccounts: undefined ModerationBlockedAccounts: undefined Settings: undefined + LanguageSettings: undefined Profile: {name: string; hideBackButton?: boolean} ProfileFollowers: {name: string} ProfileFollows: {name: string} diff --git a/src/locale/helpers.ts b/src/locale/helpers.ts index 3ada8ef0e..65db32f2f 100644 --- a/src/locale/helpers.ts +++ b/src/locale/helpers.ts @@ -79,8 +79,8 @@ export function isPostInLanguage( return bcp47Match.basicFilter(lang, targetLangs).length > 0 } -export function getTranslatorLink(text: string): string { - return `https://translate.google.com/?sl=auto&text=${encodeURIComponent( +export function getTranslatorLink(text: string, lang: string): string { + return `https://translate.google.com/?sl=auto&tl=${lang}&text=${encodeURIComponent( text, )}` } diff --git a/src/routes.ts b/src/routes.ts index 35266d85b..7049d60ff 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -6,6 +6,7 @@ export const router = new Router({ Feeds: '/feeds', Notifications: '/notifications', Settings: '/settings', + LanguageSettings: '/settings/language', Moderation: '/moderation', ModerationMuteLists: '/moderation/mute-lists', ModerationMutedAccounts: '/moderation/muted-accounts', diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts index 5e07685ca..5ae391670 100644 --- a/src/state/models/ui/preferences.ts +++ b/src/state/models/ui/preferences.ts @@ -44,6 +44,7 @@ export class LabelPreferencesModel { export class PreferencesModel { adultContentEnabled = false + primaryLanguage: string = deviceLocales[0] || 'en' contentLanguages: string[] = deviceLocales || [] postLanguage: string = deviceLocales[0] || 'en' postLanguageHistory: string[] = DEFAULT_LANG_CODES @@ -78,6 +79,7 @@ export class PreferencesModel { serialize() { return { + primaryLanguage: this.primaryLanguage, contentLanguages: this.contentLanguages, postLanguage: this.postLanguage, postLanguageHistory: this.postLanguageHistory, @@ -105,6 +107,15 @@ export class PreferencesModel { */ hydrate(v: unknown) { if (isObj(v)) { + if ( + hasProp(v, 'primaryLanguage') && + typeof v.primaryLanguage === 'string' + ) { + this.primaryLanguage = v.primaryLanguage + } else { + // default to the device languages + this.primaryLanguage = deviceLocales[0] || 'en' + } // check if content languages in preferences exist, otherwise default to device languages if ( hasProp(v, 'contentLanguages') && @@ -542,6 +553,10 @@ export class PreferencesModel { this.requireAltTextEnabled = !this.requireAltTextEnabled } + setPrimaryLanguage(lang: string) { + this.primaryLanguage = lang + } + getFeedTuners( feedType: 'home' | 'following' | 'author' | 'custom' | 'likes', ) { diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index fc0684698..b98ec805e 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -75,7 +75,10 @@ export const PostThreadItem = observer(function PostThreadItem({ }, [item.post.uri, item.post.author]) const repostsTitle = 'Reposts of this post' - const translatorUrl = getTranslatorLink(record?.text || '') + const translatorUrl = getTranslatorLink( + record?.text || '', + store.preferences.primaryLanguage, + ) const needsTranslation = useMemo( () => store.preferences.contentLanguages.length > 0 && diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index d7559e3c4..d5191cf4e 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -115,7 +115,10 @@ const PostLoaded = observer(function PostLoadedImpl({ replyAuthorDid = urip.hostname } - const translatorUrl = getTranslatorLink(record?.text || '') + const translatorUrl = getTranslatorLink( + record?.text || '', + store.preferences.primaryLanguage, + ) const onPressReply = React.useCallback(() => { store.shell.openComposer({ diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 23d8546bc..71be3969a 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -64,7 +64,10 @@ export const FeedItem = observer(function FeedItemImpl({ const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri) return urip.hostname }, [record?.reply]) - const translatorUrl = getTranslatorLink(record?.text || '') + const translatorUrl = getTranslatorLink( + record?.text || '', + store.preferences.primaryLanguage, + ) const onPressReply = React.useCallback(() => { track('FeedItem:PostReply') diff --git a/src/view/index.ts b/src/view/index.ts index 07848aa8f..1e6f27419 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -97,6 +97,7 @@ import {faUserXmark} from '@fortawesome/free-solid-svg-icons/faUserXmark' import {faUsersSlash} from '@fortawesome/free-solid-svg-icons/faUsersSlash' import {faX} from '@fortawesome/free-solid-svg-icons/faX' import {faXmark} from '@fortawesome/free-solid-svg-icons/faXmark' +import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown' export function setup() { library.add( @@ -197,5 +198,6 @@ export function setup() { faTrashCan, faX, faXmark, + faChevronDown, ) } diff --git a/src/view/screens/LanguageSettings.tsx b/src/view/screens/LanguageSettings.tsx new file mode 100644 index 000000000..8b952a564 --- /dev/null +++ b/src/view/screens/LanguageSettings.tsx @@ -0,0 +1,205 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import {observer} from 'mobx-react-lite' +import {Text} from '../com/util/text/Text' +import {useStores} from 'state/index' +import {s} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {ViewHeader} from 'view/com/util/ViewHeader' +import {CenteredView} from 'view/com/util/Views' +import {Button} from 'view/com/util/forms/Button' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' +import {useAnalytics} from 'lib/analytics/analytics' +import {useFocusEffect} from '@react-navigation/native' +import {LANGUAGES} from 'lib/../locale/languages' +import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' + +type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'> + +export const LanguageSettingsScreen = observer(function LanguageSettingsImpl( + _: Props, +) { + const pal = usePalette('default') + const store = useStores() + const {isTabletOrDesktop} = useWebMediaQueries() + const {screen, track} = useAnalytics() + + useFocusEffect( + React.useCallback(() => { + screen('Settings') + store.shell.setMinimalShellMode(false) + }, [screen, store]), + ) + + const onPressContentLanguages = React.useCallback(() => { + track('Settings:ContentlanguagesButtonClicked') + store.shell.openModal({name: 'content-languages-settings'}) + }, [track, store]) + + const onChangePrimaryLanguage = React.useCallback( + (value: Parameters<PickerSelectProps['onValueChange']>[0]) => { + store.preferences.setPrimaryLanguage(value) + }, + [store.preferences], + ) + + const myLanguages = React.useMemo(() => { + return ( + store.preferences.contentLanguages + .map(lang => LANGUAGES.find(l => l.code2 === lang)) + .filter(Boolean) + // @ts-ignore + .map(l => l.name) + .join(', ') + ) + }, [store.preferences.contentLanguages]) + + return ( + <CenteredView + style={[ + pal.view, + pal.border, + styles.container, + isTabletOrDesktop && styles.desktopContainer, + ]}> + <ViewHeader title="Language Settings" showOnDesktop /> + + <View style={{paddingTop: 20, paddingHorizontal: 20}}> + <View style={{paddingBottom: 20}}> + <Text type="title-sm" style={[pal.text, s.pb5]}> + Primary Language + </Text> + <Text style={[pal.text, s.pb10]}> + Select your preferred language for translations in your feed. + </Text> + + <View style={{position: 'relative'}}> + <RNPickerSelect + value={store.preferences.primaryLanguage} + onValueChange={onChangePrimaryLanguage} + items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ + label: l.name, + value: l.code2, + key: l.code2 + l.code3, + }))} + style={{ + inputAndroid: { + backgroundColor: pal.viewLight.backgroundColor, + color: pal.text.color, + fontSize: 14, + letterSpacing: 0.5, + fontWeight: '500', + paddingHorizontal: 14, + paddingVertical: 8, + borderRadius: 24, + }, + inputIOS: { + backgroundColor: pal.viewLight.backgroundColor, + color: pal.text.color, + fontSize: 14, + letterSpacing: 0.5, + fontWeight: '500', + paddingHorizontal: 14, + paddingVertical: 8, + borderRadius: 24, + }, + inputWeb: { + // @ts-ignore web only + cursor: 'pointer', + '-moz-appearance': 'none', + '-webkit-appearance': 'none', + appearance: 'none', + outline: 0, + borderWidth: 0, + backgroundColor: pal.viewLight.backgroundColor, + color: pal.text.color, + fontSize: 14, + letterSpacing: 0.5, + fontWeight: '500', + paddingHorizontal: 14, + paddingVertical: 8, + borderRadius: 24, + }, + }} + /> + + <View + style={{ + position: 'absolute', + top: 1, + right: 1, + bottom: 1, + width: 40, + backgroundColor: pal.viewLight.backgroundColor, + borderRadius: 24, + pointerEvents: 'none', + alignItems: 'center', + justifyContent: 'center', + }}> + <FontAwesomeIcon + icon="chevron-down" + style={pal.text as FontAwesomeIconStyle} + /> + </View> + </View> + </View> + + <View + style={{ + height: 1, + backgroundColor: pal.border.borderColor, + marginBottom: 20, + }} + /> + + <View style={{paddingBottom: 20}}> + <Text type="title-sm" style={[pal.text, s.pb5]}> + Content Languages + </Text> + <Text style={[pal.text, s.pb10]}> + Select which languages you want your subscribed feeds to include. If + none are selected, all languages will be shown. + </Text> + + <Button + type="default" + onPress={onPressContentLanguages} + style={styles.button}> + <FontAwesomeIcon + icon={myLanguages.length ? 'check' : 'plus'} + style={pal.text as FontAwesomeIconStyle} + /> + <Text + type="button" + style={[pal.text, {flexShrink: 1, overflow: 'hidden'}]} + numberOfLines={1}> + {myLanguages.length ? myLanguages : 'Select languages'} + </Text> + </Button> + </View> + </View> + </CenteredView> + ) +}) + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingBottom: 90, + }, + desktopContainer: { + borderLeftWidth: 1, + borderRightWidth: 1, + paddingBottom: 40, + }, + button: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + }, +}) diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 1ff5f58ff..4783f3353 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -145,10 +145,9 @@ export const SettingsScreen = withAuthRequired( store.shell.openModal({name: 'invite-codes'}) }, [track, store]) - const onPressContentLanguages = React.useCallback(() => { - track('Settings:ContentlanguagesButtonClicked') - store.shell.openModal({name: 'content-languages-settings'}) - }, [track, store]) + const onPressLanguageSettings = React.useCallback(() => { + navigation.navigate('LanguageSettings') + }, [navigation]) const onPressSignout = React.useCallback(() => { track('Settings:SignOutButtonClicked') @@ -456,12 +455,12 @@ export const SettingsScreen = withAuthRequired( </Text> </TouchableOpacity> <TouchableOpacity - testID="contentLanguagesBtn" + testID="languageSettingsBtn" style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]} - onPress={isSwitching ? undefined : onPressContentLanguages} + onPress={isSwitching ? undefined : onPressLanguageSettings} accessibilityRole="button" - accessibilityHint="Content languages" - accessibilityLabel="Opens configurable content language settings"> + accessibilityHint="Language settings" + accessibilityLabel="Opens configurable language settings"> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon="language" @@ -469,7 +468,7 @@ export const SettingsScreen = withAuthRequired( /> </View> <Text type="lg" style={pal.text}> - Content languages + Languages </Text> </TouchableOpacity> <TouchableOpacity diff --git a/yarn.lock b/yarn.lock index 0aa1c4415..449d92db2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3374,6 +3374,16 @@ resolved "https://registry.yarnpkg.com/@react-native-menu/menu/-/menu-0.8.0.tgz#dbf227c2081e5ffd3d2073ee68ecc84cf8639727" integrity sha512-kxiT6ySZsDbBvNWovrKVAfs4AQvAytKIf0f8KQLkVO6eNYMUmONBQPzi6onTTbVujXtZHambo7qr/PcedaR8Tg== +"@react-native-picker/picker@2.4.10": + version "2.4.10" + resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-2.4.10.tgz#339c7bfc6e1d9a5e934122eaaa7767dc1c5fb725" + integrity sha512-EvAlHmPEPOwvbP6Pjg/gtDV3XJzIjIxr10fXFNlX5r9HeHw582G1Zt2o8FLyB718nOttgj8HYUTGxvhu4N65sQ== + +"@react-native-picker/picker@^1.8.3": + version "1.16.8" + resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-1.16.8.tgz#2126ca54d4a5a3e9ea5e3f39ad1e6643f8e4b3d4" + integrity sha512-pacdQDX6V6EmjF+HoiIh6u++qx4mTK0WnhgUHRc01B+Qt5eoeUwseBqmqfTSXTx/aHDEd6PiIw7UGvKgFoqgFQ== + "@react-native/assets-registry@^0.72.0": version "0.72.0" resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.72.0.tgz#c82a76a1d86ec0c3907be76f7faf97a32bbed05d" @@ -15769,6 +15779,14 @@ react-native-pager-view@6.1.4: resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.1.4.tgz#3a63ebd1b72f81991157ea552bb9c887e529bc8c" integrity sha512-fmTwgGwPxGCBusKAq7gHzm+s1Yp0qh5rKPoQszaCuxrb+76KgK4Qe82jJNPUp2xTZOKSw+FbJU2QahF8ncTl+w== +react-native-picker-select@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/react-native-picker-select/-/react-native-picker-select-8.1.0.tgz#667a5442f783f4bcfd3f65880c6926155fd2c39c" + integrity sha512-iLsLv2OEWpXnQMDYJS6du5Cl1HTHy887n60Yp5OOiMny0TDB9w5CfxTUYWtpsvJJrUa/Yrv+1NMQiJy7IA4ETw== + dependencies: + "@react-native-picker/picker" "^1.8.3" + lodash.isequal "^4.5.0" + react-native-progress@bluesky-social/react-native-progress: version "5.0.0" resolved "https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4" |