about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Navigation.tsx6
-rw-r--r--src/lib/routes/types.ts1
-rw-r--r--src/locale/helpers.ts4
-rw-r--r--src/routes.ts1
-rw-r--r--src/state/models/ui/preferences.ts15
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx5
-rw-r--r--src/view/com/post/Post.tsx5
-rw-r--r--src/view/com/posts/FeedItem.tsx5
-rw-r--r--src/view/index.ts2
-rw-r--r--src/view/screens/LanguageSettings.tsx205
-rw-r--r--src/view/screens/Settings.tsx17
11 files changed, 252 insertions, 14 deletions
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