diff --git a/package.json b/package.json
index c6c7bfa9..67747791 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 604fca2b..a247c72d 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'
@@ -118,6 +119,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
component={SettingsScreen}
options={{title: title('Settings')}}
/>
+
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 35266d85..7049d60f 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 5e07685c..5ae39167 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 fc068469..b98ec805 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 d7559e3c..d5191cf4 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 23d8546b..71be3969 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 07848aa8..1e6f2741 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 00000000..8b952a56
--- /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
+
+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[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 (
+
+
+
+
+
+
+ Primary Language
+
+
+ Select your preferred language for translations in your feed.
+
+
+
+ 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,
+ },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ Content Languages
+
+
+ Select which languages you want your subscribed feeds to include. If
+ none are selected, all languages will be shown.
+
+
+
+
+
+
+ )
+})
+
+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 1ff5f58f..4783f335 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(
+ accessibilityHint="Language settings"
+ accessibilityLabel="Opens configurable language settings">
- Content languages
+ Languages