mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import {useEffect, useState} from 'react'
2import {View} from 'react-native'
3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5import lande from 'lande'
6
7import {code3ToCode2Strict, codeToLanguageName} from '#/locale/helpers'
8import {
9 toPostLanguages,
10 useLanguagePrefs,
11 useLanguagePrefsApi,
12} from '#/state/preferences/languages'
13import {atoms as a, useTheme} from '#/alf'
14import {Button, ButtonText} from '#/components/Button'
15import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe'
16import {Text} from '#/components/Typography'
17
18// fallbacks for safari
19const onIdle = globalThis.requestIdleCallback || (cb => setTimeout(cb, 1))
20const cancelIdle = globalThis.cancelIdleCallback || clearTimeout
21
22export function SuggestedLanguage({
23 text,
24 replyToLanguage,
25}: {
26 text: string
27 replyToLanguage?: string
28}) {
29 const [suggestedLanguage, setSuggestedLanguage] = useState<
30 string | undefined
31 >(text.length === 0 ? replyToLanguage : undefined)
32 const langPrefs = useLanguagePrefs()
33 const setLangPrefs = useLanguagePrefsApi()
34 const t = useTheme()
35 const {_} = useLingui()
36
37 useEffect(() => {
38 // For replies, suggest the language of the post being replied to if no text
39 // has been typed yet
40 if (replyToLanguage && text.length === 0) {
41 setSuggestedLanguage(replyToLanguage)
42 return
43 }
44
45 const textTrimmed = text.trim()
46
47 // Don't run the language model on small posts, the results are likely
48 // to be inaccurate anyway.
49 if (textTrimmed.length < 40) {
50 setSuggestedLanguage(undefined)
51 return
52 }
53
54 const idle = onIdle(() => {
55 setSuggestedLanguage(guessLanguage(textTrimmed))
56 })
57
58 return () => cancelIdle(idle)
59 }, [text, replyToLanguage])
60
61 if (
62 suggestedLanguage &&
63 !toPostLanguages(langPrefs.postLanguage).includes(suggestedLanguage)
64 ) {
65 const suggestedLanguageName = codeToLanguageName(
66 suggestedLanguage,
67 langPrefs.appLanguage,
68 )
69
70 return (
71 <View
72 style={[
73 t.atoms.border_contrast_low,
74 a.gap_sm,
75 a.border,
76 a.flex_row,
77 a.align_center,
78 a.rounded_sm,
79 a.px_lg,
80 a.py_md,
81 a.mx_md,
82 a.my_sm,
83 t.atoms.bg,
84 ]}>
85 <EarthIcon />
86 <Text style={[a.flex_1]}>
87 <Trans>
88 Are you writing in{' '}
89 <Text style={[a.font_bold]}>{suggestedLanguageName}</Text>?
90 </Trans>
91 </Text>
92
93 <Button
94 color="secondary"
95 size="small"
96 variant="solid"
97 onPress={() => setLangPrefs.setPostLanguage(suggestedLanguage)}
98 label={_(msg`Change post language to ${suggestedLanguageName}`)}>
99 <ButtonText>
100 <Trans>Yes</Trans>
101 </ButtonText>
102 </Button>
103 </View>
104 )
105 } else {
106 return null
107 }
108}
109
110/**
111 * This function is using the lande language model to attempt to detect the language
112 * We want to only make suggestions when we feel a high degree of certainty
113 * The magic numbers are based on debugging sessions against some test strings
114 */
115function guessLanguage(text: string): string | undefined {
116 const scores = lande(text).filter(([_lang, value]) => value >= 0.0002)
117 // if the model has multiple items with a score higher than 0.0002, it isn't certain enough
118 if (scores.length !== 1) {
119 return undefined
120 }
121 const [lang, value] = scores[0]
122 // if the model doesn't give a score of 0.97 or above, it isn't certain enough
123 if (value < 0.97) {
124 return undefined
125 }
126 return code3ToCode2Strict(lang)
127}