Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Suggest post language correction (#2486)

* feat: suggested language

* fix: wording correction

* Factor out SuggestedLanguage into a separate component

* Tighten the language-suggestion confidence to avoid false positives

* Tweak the copy and UI

* Add function fallbacks for safari

---------

Co-authored-by: Mary <pineapplecreamcheese@skiff.com>

authored by

Paul Frazee
Mary
and committed by
GitHub
95d771b3 998ee299

+111
+8
src/locale/helpers.ts
··· 22 22 return lang 23 23 } 24 24 25 + export function code3ToCode2Strict(lang: string): string | undefined { 26 + if (lang.length === 3) { 27 + return LANGUAGES_MAP_CODE3[lang]?.code2 28 + } 29 + 30 + return undefined 31 + } 32 + 25 33 export function codeToLanguageName(lang: string): string { 26 34 const lang2 = code3ToCode2(lang) 27 35 return LANGUAGES_MAP_CODE2[lang2]?.name || lang
+2
src/view/com/composer/Composer.tsx
··· 45 45 import {MAX_GRAPHEME_LENGTH} from 'lib/constants' 46 46 import {LabelsBtn} from './labels/LabelsBtn' 47 47 import {SelectLangBtn} from './select-language/SelectLangBtn' 48 + import {SuggestedLanguage} from './select-language/SuggestedLanguage' 48 49 import {insertMentionAt} from 'lib/strings/mention-manip' 49 50 import {Trans, msg} from '@lingui/macro' 50 51 import {useLingui} from '@lingui/react' ··· 454 455 ))} 455 456 </View> 456 457 ) : null} 458 + <SuggestedLanguage text={richtext.text} /> 457 459 <View style={[pal.border, styles.bottomBar]}> 458 460 {canSelectImages ? ( 459 461 <>
+101
src/view/com/composer/select-language/SuggestedLanguage.tsx
··· 1 + import React, {useEffect, useState} from 'react' 2 + import {StyleSheet, View} from 'react-native' 3 + import lande from 'lande' 4 + import {Trans, msg} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + import {Text} from '../../util/text/Text' 7 + import {Button} from '../../util/forms/Button' 8 + import {code3ToCode2Strict, codeToLanguageName} from '#/locale/helpers' 9 + import { 10 + toPostLanguages, 11 + useLanguagePrefs, 12 + useLanguagePrefsApi, 13 + } from '#/state/preferences/languages' 14 + import {usePalette} from '#/lib/hooks/usePalette' 15 + import {s} from '#/lib/styles' 16 + import { 17 + FontAwesomeIcon, 18 + FontAwesomeIconStyle, 19 + } from '@fortawesome/react-native-fontawesome' 20 + 21 + // fallbacks for safari 22 + const onIdle = globalThis.requestIdleCallback || (cb => setTimeout(cb, 1)) 23 + const cancelIdle = globalThis.cancelIdleCallback || clearTimeout 24 + 25 + export function SuggestedLanguage({text}: {text: string}) { 26 + const [suggestedLanguage, setSuggestedLanguage] = useState<string>() 27 + const langPrefs = useLanguagePrefs() 28 + const setLangPrefs = useLanguagePrefsApi() 29 + const pal = usePalette('default') 30 + const {_} = useLingui() 31 + 32 + useEffect(() => { 33 + const textTrimmed = text.trim() 34 + 35 + // Don't run the language model on small posts, the results are likely 36 + // to be inaccurate anyway. 37 + if (textTrimmed.length < 40) { 38 + setSuggestedLanguage(undefined) 39 + return 40 + } 41 + 42 + const idle = onIdle(() => { 43 + // Only select languages that have a high confidence and convert to code2 44 + const result = lande(textTrimmed).filter( 45 + ([lang, value]) => value >= 0.97 && code3ToCode2Strict(lang), 46 + ) 47 + 48 + setSuggestedLanguage( 49 + result.length > 0 ? code3ToCode2Strict(result[0][0]) : undefined, 50 + ) 51 + }) 52 + 53 + return () => cancelIdle(idle) 54 + }, [text]) 55 + 56 + return suggestedLanguage && 57 + !toPostLanguages(langPrefs.postLanguage).includes(suggestedLanguage) ? ( 58 + <View style={[pal.border, styles.infoBar]}> 59 + <FontAwesomeIcon 60 + icon="language" 61 + style={pal.text as FontAwesomeIconStyle} 62 + size={24} 63 + /> 64 + <Text style={[pal.text, s.flex1]}> 65 + <Trans> 66 + Are you writing in{' '} 67 + <Text type="sm-bold" style={pal.text}> 68 + {codeToLanguageName(suggestedLanguage)} 69 + </Text> 70 + ? 71 + </Trans> 72 + </Text> 73 + 74 + <Button 75 + type="default" 76 + onPress={() => setLangPrefs.setPostLanguage(suggestedLanguage)} 77 + accessibilityLabel={_( 78 + msg`Change post language to ${codeToLanguageName(suggestedLanguage)}`, 79 + )} 80 + accessibilityHint=""> 81 + <Text type="button" style={[pal.link, s.fw600]}> 82 + <Trans>Yes</Trans> 83 + </Text> 84 + </Button> 85 + </View> 86 + ) : null 87 + } 88 + 89 + const styles = StyleSheet.create({ 90 + infoBar: { 91 + flexDirection: 'row', 92 + alignItems: 'center', 93 + gap: 10, 94 + borderWidth: 1, 95 + borderRadius: 6, 96 + paddingHorizontal: 16, 97 + paddingVertical: 12, 98 + marginHorizontal: 10, 99 + marginBottom: 10, 100 + }, 101 + })