Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Language settings updates, new primary language setting #1471

Merged
merged 9 commits into from
Sep 21, 2023
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions src/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -118,6 +119,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
component={SettingsScreen}
options={{title: title('Settings')}}
/>
<Stack.Screen
name="LanguageSettings"
component={LanguageSettingsScreen}
options={{title: title('Language Settings')}}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
Expand Down
1 change: 1 addition & 0 deletions src/lib/routes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
4 changes: 2 additions & 2 deletions src/locale/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)}`
}
1 change: 1 addition & 0 deletions src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
15 changes: 15 additions & 0 deletions src/state/models/ui/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -78,6 +79,7 @@ export class PreferencesModel {

serialize() {
return {
primaryLanguage: this.primaryLanguage,
contentLanguages: this.contentLanguages,
postLanguage: this.postLanguage,
postLanguageHistory: this.postLanguageHistory,
Expand Down Expand Up @@ -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') &&
Expand Down Expand Up @@ -542,6 +553,10 @@ export class PreferencesModel {
this.requireAltTextEnabled = !this.requireAltTextEnabled
}

setPrimaryLanguage(lang: string) {
this.primaryLanguage = lang
}

getFeedTuners(
feedType: 'home' | 'following' | 'author' | 'custom' | 'likes',
) {
Expand Down
5 changes: 4 additions & 1 deletion src/view/com/post-thread/PostThreadItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
5 changes: 4 additions & 1 deletion src/view/com/post/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
5 changes: 4 additions & 1 deletion src/view/com/posts/FeedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 2 additions & 0 deletions src/view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -197,5 +198,6 @@ export function setup() {
faTrashCan,
faX,
faXmark,
faChevronDown,
)
}
205 changes: 205 additions & 0 deletions src/view/screens/LanguageSettings.tsx
Original file line number Diff line number Diff line change
@@ -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,
},
})
Loading