mirror of
https://github.com/tooot-app/app
synced 2024-12-22 07:34:06 +01:00
Fixed #529
This commit is contained in:
parent
c89612d875
commit
705c1d0ad0
@ -1,2 +1,3 @@
|
|||||||
Enjoy toooting! This version includes following improvements and fixes:
|
Enjoy toooting! This version includes following improvements and fixes:
|
||||||
|
- Automatic setting detected language when tooting
|
||||||
- Fix whole word filter matching
|
- Fix whole word filter matching
|
@ -1,2 +1,3 @@
|
|||||||
toooting愉快!此版本包括以下改进和修复:
|
toooting愉快!此版本包括以下改进和修复:
|
||||||
|
- 自动识别发嘟语言
|
||||||
- 修复过滤整词功能
|
- 修复过滤整词功能
|
@ -301,7 +301,7 @@ PODS:
|
|||||||
- React-Core
|
- React-Core
|
||||||
- react-native-ios-context-menu (1.15.1):
|
- react-native-ios-context-menu (1.15.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
- react-native-language-detection (0.1.0):
|
- react-native-language-detection (0.2.2):
|
||||||
- React
|
- React
|
||||||
- react-native-live-text-image-view (0.4.0):
|
- react-native-live-text-image-view (0.4.0):
|
||||||
- React-Core
|
- React-Core
|
||||||
@ -739,7 +739,7 @@ SPEC CHECKSUMS:
|
|||||||
react-native-cameraroll: a40b082318eb1ecd0336a2f29d9f74b7f2c8cae8
|
react-native-cameraroll: a40b082318eb1ecd0336a2f29d9f74b7f2c8cae8
|
||||||
react-native-image-picker: bf34f3f516d139ed3e24c5f5a381a91819e349ea
|
react-native-image-picker: bf34f3f516d139ed3e24c5f5a381a91819e349ea
|
||||||
react-native-ios-context-menu: b170594b4448c0cd10c79e13432216bac99de1ac
|
react-native-ios-context-menu: b170594b4448c0cd10c79e13432216bac99de1ac
|
||||||
react-native-language-detection: 0e43195ad014974f1b7a31b64820eff34a243f2d
|
react-native-language-detection: f414937fa715108ab50a6269a3de0bcb95e4ceb0
|
||||||
react-native-live-text-image-view: 483bacfdba464162b8cf176bba555364f18b584c
|
react-native-live-text-image-view: 483bacfdba464162b8cf176bba555364f18b584c
|
||||||
react-native-menu: 8e172cfcf0e42e92f028e7781eddf84d430cae24
|
react-native-menu: 8e172cfcf0e42e92f028e7781eddf84d430cae24
|
||||||
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
"react-native-htmlview": "^0.16.0",
|
"react-native-htmlview": "^0.16.0",
|
||||||
"react-native-image-picker": "^4.10.2",
|
"react-native-image-picker": "^4.10.2",
|
||||||
"react-native-ios-context-menu": "^1.15.1",
|
"react-native-ios-context-menu": "^1.15.1",
|
||||||
"react-native-language-detection": "^0.1.0",
|
"react-native-language-detection": "^0.2.2",
|
||||||
"react-native-live-text-image-view": "^0.4.0",
|
"react-native-live-text-image-view": "^0.4.0",
|
||||||
"react-native-pager-view": "^6.1.2",
|
"react-native-pager-view": "^6.1.2",
|
||||||
"react-native-reanimated": "^2.13.0",
|
"react-native-reanimated": "^2.13.0",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ParseHTML } from '@components/Parse'
|
import { ParseHTML } from '@components/Parse'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
|
import detectLanguage from '@helpers/detectLanguage'
|
||||||
import getLanguage from '@helpers/getLanguage'
|
import getLanguage from '@helpers/getLanguage'
|
||||||
import { useTranslateQuery } from '@utils/queryHooks/translate'
|
import { useTranslateQuery } from '@utils/queryHooks/translate'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
@ -7,38 +8,44 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import * as Localization from 'expo-localization'
|
import * as Localization from 'expo-localization'
|
||||||
import React, { useContext, useEffect, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable } from 'react-native'
|
import { Platform, Pressable } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
import { Circle } from 'react-native-animated-spinkit'
|
||||||
import detectLanguage from 'react-native-language-detection'
|
|
||||||
import StatusContext from './Context'
|
import StatusContext from './Context'
|
||||||
|
|
||||||
const TimelineTranslate = () => {
|
const TimelineTranslate = () => {
|
||||||
const { status, highlighted } = useContext(StatusContext)
|
const { status, highlighted, copiableContent } = useContext(StatusContext)
|
||||||
if (!status || !highlighted) return null
|
if (!status || !highlighted) return null
|
||||||
|
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const text = status.spoiler_text ? [status.spoiler_text, status.content] : [status.content]
|
const backupTextProcessing = (): string[] => {
|
||||||
|
const text = status.spoiler_text ? [status.spoiler_text, status.content] : [status.content]
|
||||||
|
|
||||||
for (const i in text) {
|
for (const i in text) {
|
||||||
for (const emoji of status.emojis) {
|
for (const emoji of status.emojis) {
|
||||||
text[i] = text[i].replaceAll(`:${emoji.shortcode}:`, ' ')
|
text[i] = text[i].replaceAll(`:${emoji.shortcode}:`, ' ')
|
||||||
|
}
|
||||||
|
text[i] = text[i]
|
||||||
|
.replace(/(<([^>]+)>)/gi, ' ')
|
||||||
|
.replace(/@.*? /gi, ' ')
|
||||||
|
.replace(/#.*? /gi, ' ')
|
||||||
|
.replace(/http(s):\/\/.*? /gi, ' ')
|
||||||
}
|
}
|
||||||
text[i] = text[i]
|
return text
|
||||||
.replace(/(<([^>]+)>)/gi, ' ')
|
|
||||||
.replace(/@.*? /gi, ' ')
|
|
||||||
.replace(/#.*? /gi, ' ')
|
|
||||||
.replace(/http(s):\/\/.*? /gi, ' ')
|
|
||||||
}
|
}
|
||||||
|
const text = copiableContent?.current.content
|
||||||
|
? [copiableContent?.current.content]
|
||||||
|
: backupTextProcessing()
|
||||||
|
|
||||||
const [detectedLanguage, setDetectedLanguage] = useState<string>('')
|
const [detectedLanguage, setDetectedLanguage] = useState<{
|
||||||
|
language: string
|
||||||
|
confidence: number
|
||||||
|
}>({ language: status.language || '', confidence: 0 })
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const detect = async () => {
|
const detect = async () => {
|
||||||
const result = await detectLanguage(text.join(`\n\n`)).catch(() => {
|
const result = await detectLanguage(text.join('\n\n'))
|
||||||
// No need to log language detection failure
|
result && setDetectedLanguage(result)
|
||||||
})
|
|
||||||
result?.detected && setDetectedLanguage(result.detected.slice(0, 2))
|
|
||||||
}
|
}
|
||||||
detect()
|
detect()
|
||||||
}, [])
|
}, [])
|
||||||
@ -50,20 +57,36 @@ const TimelineTranslate = () => {
|
|||||||
|
|
||||||
const [enabled, setEnabled] = useState(false)
|
const [enabled, setEnabled] = useState(false)
|
||||||
const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({
|
const { refetch, data, isLoading, isSuccess, isError } = useTranslateQuery({
|
||||||
source: detectedLanguage,
|
source: detectedLanguage.language,
|
||||||
target: targetLanguage,
|
target: targetLanguage,
|
||||||
text,
|
text,
|
||||||
options: { enabled }
|
options: { enabled }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const devView = () => {
|
||||||
|
return __DEV__ ? (
|
||||||
|
<CustomText fontStyle='S' style={{ color: colors.secondary }}>{` Source: ${
|
||||||
|
detectedLanguage?.language
|
||||||
|
}; Confidence: ${
|
||||||
|
detectedLanguage?.confidence.toString().slice(0, 5) || 'null'
|
||||||
|
}; Target: ${targetLanguage}`}</CustomText>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
if (!detectedLanguage) {
|
if (!detectedLanguage) {
|
||||||
return null
|
return devView()
|
||||||
}
|
}
|
||||||
if (Localization.locale.slice(0, 2).includes(detectedLanguage)) {
|
if (
|
||||||
return null
|
Platform.OS === 'ios' &&
|
||||||
|
Localization.locale.slice(0, 2).includes(detectedLanguage.language.slice(0, 2))
|
||||||
|
) {
|
||||||
|
return devView()
|
||||||
}
|
}
|
||||||
if (settingsLanguage?.slice(0, 2).includes(detectedLanguage)) {
|
if (
|
||||||
return null
|
Platform.OS === 'android' &&
|
||||||
|
settingsLanguage?.slice(0, 2).includes(detectedLanguage.language.slice(0, 2))
|
||||||
|
) {
|
||||||
|
return devView()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -102,9 +125,6 @@ const TimelineTranslate = () => {
|
|||||||
})
|
})
|
||||||
: t('shared.translate.default')}
|
: t('shared.translate.default')}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
<CustomText>
|
|
||||||
{__DEV__ ? ` Source: ${detectedLanguage}; Target: ${targetLanguage}` : undefined}
|
|
||||||
</CustomText>
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Circle
|
<Circle
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
@ -113,6 +133,7 @@ const TimelineTranslate = () => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
{devView()}
|
||||||
{data && data.error === undefined
|
{data && data.error === undefined
|
||||||
? data.text.map((d, i) => <ParseHTML key={i} content={d} size={'M'} numberOfLines={999} />)
|
? data.text.map((d, i) => <ParseHTML key={i} content={d} size={'M'} numberOfLines={999} />)
|
||||||
: null}
|
: null}
|
||||||
|
10
src/helpers/detectLanguage.ts
Normal file
10
src/helpers/detectLanguage.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import detect from 'react-native-language-detection'
|
||||||
|
|
||||||
|
const detectLanguage = async (
|
||||||
|
text: string
|
||||||
|
): Promise<{ language: string; confidence: number } | null> => {
|
||||||
|
const possibleLanguages = await detect(text).catch(() => {})
|
||||||
|
return possibleLanguages ? possibleLanguages.filter(lang => lang.confidence > 0.5)?.[0] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default detectLanguage
|
@ -6,7 +6,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
|||||||
import haptics from '@root/components/haptics'
|
import haptics from '@root/components/haptics'
|
||||||
import { useAppDispatch } from '@root/store'
|
import { useAppDispatch } from '@root/store'
|
||||||
import ComposeRoot from '@screens/Compose/Root'
|
import ComposeRoot from '@screens/Compose/Root'
|
||||||
import formatText from '@screens/Compose/utils/formatText'
|
import { formatText } from '@screens/Compose/utils/processText'
|
||||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import { useTimelineMutation } from '@utils/queryHooks/timeline'
|
import { useTimelineMutation } from '@utils/queryHooks/timeline'
|
||||||
import { updateStoreReview } from '@utils/slices/contextsSlice'
|
import { updateStoreReview } from '@utils/slices/contextsSlice'
|
||||||
|
@ -15,7 +15,7 @@ import { PanGestureHandler } from 'react-native-gesture-handler'
|
|||||||
import { SwipeListView } from 'react-native-swipe-list-view'
|
import { SwipeListView } from 'react-native-swipe-list-view'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import ComposeContext from '../utils/createContext'
|
import ComposeContext from '../utils/createContext'
|
||||||
import formatText from '../utils/formatText'
|
import { formatText } from '../utils/processText'
|
||||||
import { ComposeStateDraft, ExtendedAttachment } from '../utils/types'
|
import { ComposeStateDraft, ExtendedAttachment } from '../utils/types'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { TextInput } from 'react-native'
|
import { TextInput } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import ComposeContext from '../../utils/createContext'
|
import ComposeContext from '../../utils/createContext'
|
||||||
import formatText from '../../utils/formatText'
|
import { formatText } from '../../utils/processText'
|
||||||
|
|
||||||
const ComposeSpoilerInput: React.FC = () => {
|
const ComposeSpoilerInput: React.FC = () => {
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
|
@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { Alert } from 'react-native'
|
import { Alert } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import ComposeContext from '../../utils/createContext'
|
import ComposeContext from '../../utils/createContext'
|
||||||
import formatText from '../../utils/formatText'
|
import { formatText } from '../../utils/processText'
|
||||||
import { uploadAttachment } from '../Footer/addAttachment'
|
import { uploadAttachment } from '../Footer/addAttachment'
|
||||||
|
|
||||||
const ComposeTextInput: React.FC = () => {
|
const ComposeTextInput: React.FC = () => {
|
||||||
|
@ -3,7 +3,7 @@ import haptics from '@components/haptics'
|
|||||||
import ComponentHashtag from '@components/Hashtag'
|
import ComponentHashtag from '@components/Hashtag'
|
||||||
import React, { useContext, useEffect } from 'react'
|
import React, { useContext, useEffect } from 'react'
|
||||||
import ComposeContext from '../utils/createContext'
|
import ComposeContext from '../utils/createContext'
|
||||||
import formatText from '../utils/formatText'
|
import { formatText } from '../utils/processText'
|
||||||
|
|
||||||
type Props = { item: Mastodon.Account & Mastodon.Tag }
|
type Props = { item: Mastodon.Account & Mastodon.Tag }
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import apiInstance, { InstanceResponse } from '@api/instance'
|
import apiInstance, { InstanceResponse } from '@api/instance'
|
||||||
|
import detectLanguage from '@helpers/detectLanguage'
|
||||||
import { ComposeState } from '@screens/Compose/utils/types'
|
import { ComposeState } from '@screens/Compose/utils/types'
|
||||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||||
import * as Crypto from 'expo-crypto'
|
import * as Crypto from 'expo-crypto'
|
||||||
|
import { getPureContent } from './processText'
|
||||||
|
|
||||||
const composePost = async (
|
const composePost = async (
|
||||||
params: RootStackParamList['Screen-Compose'],
|
params: RootStackParamList['Screen-Compose'],
|
||||||
@ -9,6 +11,13 @@ const composePost = async (
|
|||||||
): Promise<InstanceResponse<Mastodon.Status>> => {
|
): Promise<InstanceResponse<Mastodon.Status>> => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
|
||||||
|
const detectedLanguage = await detectLanguage(
|
||||||
|
getPureContent([composeState.spoiler.raw, composeState.text.raw].join('\n\n'))
|
||||||
|
)
|
||||||
|
if (detectedLanguage) {
|
||||||
|
formData.append('language', detectedLanguage.language)
|
||||||
|
}
|
||||||
|
|
||||||
if (composeState.replyToStatus) {
|
if (composeState.replyToStatus) {
|
||||||
try {
|
try {
|
||||||
await apiInstance<Mastodon.Status>({
|
await apiInstance<Mastodon.Status>({
|
||||||
|
@ -150,7 +150,7 @@ const formatText = ({ textInput, composeDispatch, content, disableDebounce = fal
|
|||||||
})
|
})
|
||||||
children.push(_content)
|
children.push(_content)
|
||||||
contentLength = contentLength + _content.length
|
contentLength = contentLength + _content.length
|
||||||
|
getPureContent(content)
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: textInput,
|
type: textInput,
|
||||||
payload: {
|
payload: {
|
||||||
@ -161,4 +161,18 @@ const formatText = ({ textInput, composeDispatch, content, disableDebounce = fal
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default formatText
|
const getPureContent = (content: string): string => {
|
||||||
|
const tags = linkify.match(content)
|
||||||
|
if (!tags) {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
let _content = content
|
||||||
|
for (const tag of tags) {
|
||||||
|
_content = _content.replace(tag.raw, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return _content.replace(/\s\s+/g, ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
export { formatText, getPureContent }
|
@ -10853,10 +10853,10 @@ react-native-iphone-x-helper@^1.3.1:
|
|||||||
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
||||||
integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==
|
integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==
|
||||||
|
|
||||||
react-native-language-detection@^0.1.0:
|
react-native-language-detection@^0.2.2:
|
||||||
version "0.1.0"
|
version "0.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-language-detection/-/react-native-language-detection-0.1.0.tgz#06b5d20bffb60dbbd599c8e62b6acf500952afa8"
|
resolved "https://registry.yarnpkg.com/react-native-language-detection/-/react-native-language-detection-0.2.2.tgz#4cc94177aa1c4575c4656f6d42456fa6c72ed5db"
|
||||||
integrity sha512-26CLndVMmMbVp40Y9Herza73nfR08JFTcYkJ3MX5MIQbGRoqgNAG89z8pA1y7dPHHK1Nfa6AWKAYpNv7tMRCaw==
|
integrity sha512-6u1JBgr+UG/GX/xMmT4K8CaBlSep4XfM91jwUzRA/Y3bMCHDx7bNVxGQvqvzkmOchby9h66XD8F5Eo+kV01CAA==
|
||||||
|
|
||||||
react-native-live-text-image-view@^0.4.0:
|
react-native-live-text-image-view@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user