Update translations

This commit is contained in:
Zhiyuan Zheng 2021-01-19 01:13:45 +01:00
parent 5c4f7ce8c7
commit 5a248716bf
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
79 changed files with 948 additions and 541 deletions

View File

@ -85,8 +85,7 @@ const client = async <T = unknown>({
ctx.bold(' API '),
ctx.bold('response'),
error.response.status,
error.response.data.error,
error.request
error.response.data.error
)
return Promise.reject(error.response)
} else if (error.request) {

View File

@ -1,4 +1,4 @@
import React, { useRef } from 'react'
import React from 'react'
import { Dimensions, Modal, StyleSheet, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTheme } from '@utils/styles/ThemeManager'

View File

@ -12,6 +12,7 @@ import {
} from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import * as Linking from 'expo-linking'
import { debounce } from 'lodash'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -36,7 +37,7 @@ const ComponentInstance: React.FC<Props> = ({
const navigation = useNavigation()
const dispatch = useDispatch()
const queryClient = useQueryClient()
const { t } = useTranslation('meRoot')
const { t } = useTranslation('componentInstance')
const { theme } = useTheme()
const [instanceDomain, setInstanceDomain] = useState<string | undefined>()
const [appData, setApplicationData] = useState<InstanceLocal['appData']>()
@ -92,13 +93,14 @@ const ComponentInstance: React.FC<Props> = ({
.length
) {
Alert.alert(
'域名已存在',
'可以登录同个域名的另外一个账户,现有账户🈚️用',
t('update.local.alert.title'),
t('update.local.alert.message'),
[
{ text: '取消', style: 'cancel' },
{ text: t('update.local.alert.buttons.cancel'), style: 'cancel' },
{
text: '继续',
text: t('update.local.alert.buttons.continue'),
onPress: () => {
setApplicationData(undefined)
applicationQuery.refetch()
}
}
@ -116,8 +118,8 @@ const ComponentInstance: React.FC<Props> = ({
]
dispatch(remoteUpdate(instanceDomain))
queryClient.resetQueries(queryKey)
toast({ type: 'success', message: '重置成功' })
navigation.navigate('Screen-Public', { screen: 'Screen-Public-Root' })
toast({ type: 'success', message: t('update.remote.succeed') })
navigation.goBack()
break
}
}
@ -143,9 +145,9 @@ const ComponentInstance: React.FC<Props> = ({
const buttonContent = useMemo(() => {
switch (type) {
case 'local':
return t('content.login.button')
return t('server.button.local')
case 'remote':
return '登记'
return t('server.button.remote')
}
}, [])
@ -176,7 +178,7 @@ const ComponentInstance: React.FC<Props> = ({
keyboardType='url'
textContentType='URL'
onSubmitEditing={onSubmitEditing}
placeholder={t('content.login.server.placeholder')}
placeholder={t('server.textInput.placeholder')}
placeholderTextColor={theme.secondary}
returnKeyType='go'
/>
@ -191,21 +193,21 @@ const ComponentInstance: React.FC<Props> = ({
<View>
<InstanceInfo
visible={instanceQuery.data?.title !== undefined}
header='实例名称'
header={t('server.information.name')}
content={instanceQuery.data?.title || undefined}
potentialWidth={10}
/>
<InstanceInfo
visible={instanceQuery.data?.short_description !== undefined}
header='实例介绍'
header={t('server.information.description.heading')}
content={instanceQuery.data?.short_description || undefined}
potentialLines={5}
/>
<View style={styles.instanceStats}>
<InstanceInfo
style={{ alignItems: 'flex-start' }}
visible={instanceQuery.data?.stats?.user_count !== null}
header='用户总数'
visible={instanceQuery.data?.stats?.user_count === null}
header={t('server.information.accounts')}
content={
instanceQuery.data?.stats?.user_count?.toString() || undefined
}
@ -213,8 +215,8 @@ const ComponentInstance: React.FC<Props> = ({
/>
<InstanceInfo
style={{ alignItems: 'center' }}
visible={instanceQuery.data?.stats?.status_count !== null}
header='嘟嘟总数'
visible={instanceQuery.data?.stats?.status_count === null}
header={t('server.information.statuses')}
content={
instanceQuery.data?.stats?.status_count?.toString() || undefined
}
@ -222,22 +224,31 @@ const ComponentInstance: React.FC<Props> = ({
/>
<InstanceInfo
style={{ alignItems: 'flex-end' }}
visible={instanceQuery.data?.stats?.domain_count !== null}
header='嘟嘟总数'
visible={instanceQuery.data?.stats?.domain_count === null}
header={t('server.information.domains')}
content={
instanceQuery.data?.stats?.domain_count?.toString() || undefined
}
potentialWidth={4}
/>
</View>
<Text style={[styles.disclaimer, { color: theme.secondary }]}>
<View style={styles.disclaimer}>
<Icon
name='Lock'
size={StyleConstants.Font.Size.M}
size={StyleConstants.Font.Size.S}
color={theme.secondary}
/>{' '}
</Text>
style={styles.disclaimerIcon}
/>
<Text
style={[styles.disclaimerText, { color: theme.secondary }]}
onPress={() => Linking.openURL('https://tooot.app/privacy')}
>
{t('server.disclaimer')}
<Text style={{ color: theme.blue }}>
https://tooot.app/privacy
</Text>
</Text>
</View>
</View>
</View>
@ -275,9 +286,18 @@ const styles = StyleSheet.create({
flexDirection: 'row'
},
disclaimer: {
...StyleConstants.FontStyle.S,
flexDirection: 'row',
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
marginVertical: StyleConstants.Spacing.M
},
disclaimerIcon: {
marginTop:
(StyleConstants.Font.LineHeight.S - StyleConstants.Font.Size.S) / 2,
marginRight: StyleConstants.Spacing.XS
},
disclaimerText: {
flex: 1,
...StyleConstants.FontStyle.S
}
})

View File

@ -3,6 +3,7 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { LinearGradient } from 'expo-linear-gradient'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, StyleSheet, Text, View, ViewStyle } from 'react-native'
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
@ -24,6 +25,7 @@ const InstanceInfo = React.memo(
potentialWidth,
potentialLines = 1
}: Props) => {
const { t } = useTranslation('componentInstance')
const { theme } = useTheme()
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
@ -40,14 +42,18 @@ const InstanceInfo = React.memo(
StyleConstants.Spacing.Global.PagePadding * 4
}
height={StyleConstants.Font.LineHeight.M * potentialLines}
shimmerColors={[theme.shimmerDefault, theme.shimmerHighlight, theme.shimmerDefault]}
shimmerColors={[
theme.shimmerDefault,
theme.shimmerHighlight,
theme.shimmerDefault
]}
>
{content ? (
<ParseHTML
content={content}
size={'M'}
numberOfLines={5}
expandHint='介绍'
expandHint={t('server.information.description.expandHint')}
/>
) : null}
</ShimmerPlaceholder>

View File

@ -19,7 +19,7 @@ export interface Props {
switchDisabled?: boolean
switchOnValueChange?: () => void
iconBack?: 'ChevronRight' | 'Check'
iconBack?: 'ChevronRight' | 'ExternalLink'
iconBackColor?: ColorDefinitions
loading?: boolean

View File

@ -1,8 +1,8 @@
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import { StyleSheet, Text } from 'react-native'
import { Image } from 'react-native-expo-image-cache'
import { useTheme } from '@utils/styles/ThemeManager'
import { StyleConstants } from '@utils/styles/constants'
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)

View File

@ -7,6 +7,7 @@ import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager'
import { LinearGradient } from 'expo-linear-gradient'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Pressable, Text, View } from 'react-native'
import HTMLView from 'react-native-htmlview'
@ -191,6 +192,7 @@ const ParseHTML: React.FC<Props> = ({
}, [])
const rootComponent = useCallback(
({ children }) => {
const { t } = useTranslation('componentParse')
const lineHeight = StyleConstants.Font.LineHeight[size]
const [expandAllow, setExpandAllow] = useState(false)
@ -249,7 +251,9 @@ const ParseHTML: React.FC<Props> = ({
color: theme.primary
}}
>
{`${expanded ? '折叠' : '展开'}${expandHint}`}
{expanded
? t('HTML.expanded.true', { hint: expandHint })
: t('HTML.expanded.false', { hint: expandHint })}
</Text>
</LinearGradient>
</Pressable>

View File

@ -16,7 +16,7 @@ export interface Props {
const RelationshipOutgoing = React.memo(
({ id }: Props) => {
const { t } = useTranslation()
const { t } = useTranslation('componentRelationship')
const query = useRelationshipQuery({ id })
@ -30,12 +30,12 @@ const RelationshipOutgoing = React.memo(
[res]
)
},
onError: (err: any, { type }) => {
onError: (err: any, { payload: { action } }) => {
haptics('Error')
toast({
type: 'error',
message: t('common:toastMessage.error.message', {
function: t(`relationship:${type}.function`)
function: t(`button.${action}.function`)
}),
...(err.status &&
typeof err.status === 'number' &&
@ -52,15 +52,15 @@ const RelationshipOutgoing = React.memo(
let onPress: () => void
if (query.isError) {
content = t('relationship:button.error')
content = t('button.error')
onPress = () => {}
} else {
if (query.data?.blocked_by) {
content = t('relationship:button.blocked_by')
content = t('button.blocked_by')
onPress = () => null
} else {
if (query.data?.blocking) {
content = t('relationship:button.blocking')
content = t('button.blocking')
onPress = () =>
mutation.mutate({
id,
@ -72,7 +72,7 @@ const RelationshipOutgoing = React.memo(
})
} else {
if (query.data?.following) {
content = t('relationship:button.following')
content = t('button.following')
onPress = () =>
mutation.mutate({
id,
@ -84,7 +84,7 @@ const RelationshipOutgoing = React.memo(
})
} else {
if (query.data?.requested) {
content = t('relationship:button.requested')
content = t('button.requested')
onPress = () =>
mutation.mutate({
id,
@ -95,7 +95,7 @@ const RelationshipOutgoing = React.memo(
}
})
} else {
content = t('relationship:button.default')
content = t('button.default')
onPress = () =>
mutation.mutate({
id,

View File

@ -10,7 +10,7 @@ export interface Props {
}
const RelativeTime: React.FC<Props> = ({ date }) => {
const { t } = useTranslation('relativeTime')
const { t } = useTranslation('componentRelativeTime')
return (
<TimeAgo

View File

@ -15,7 +15,7 @@ export interface Props {
const TimelineEmpty: React.FC<Props> = ({ status, refetch }) => {
const { mode, theme } = useTheme()
const { t, i18n } = useTranslation('timeline')
const { t, i18n } = useTranslation('componentTimeline')
const children = useMemo(() => {
switch (status) {

View File

@ -20,7 +20,7 @@ const TimelineEnd: React.FC<Props> = ({ hasNextPage }) => {
) : (
<Text style={[styles.text, { color: theme.secondary }]}>
<Trans
i18nKey='timeline:shared.end.message'
i18nKey='componentTimeline:end.message'
components={[
<Icon
name='Coffee'

View File

@ -4,11 +4,13 @@ import { StyleConstants } from '@root/utils/styles/constants'
import { useTheme } from '@root/utils/styles/ThemeManager'
import { updatePublicRemoteNotice } from '@utils/slices/contextsSlice'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { useDispatch } from 'react-redux'
const TimelineHeader = React.memo(
() => {
const { t } = useTranslation('componentTimeline')
const dispatch = useDispatch()
const navigation = useNavigation()
const { theme } = useTheme()
@ -16,7 +18,7 @@ const TimelineHeader = React.memo(
return (
<View style={[styles.base, { borderColor: theme.border }]}>
<Text style={[styles.text, { color: theme.primary }]}>
{' '}
{t('header.explanation')}
<Text
style={{ color: theme.blue }}
onPress={() => {
@ -27,7 +29,7 @@ const TimelineHeader = React.memo(
})
}}
>
{' '}
{t('header.button')}
<Icon
name='ArrowRight'
size={StyleConstants.Font.Size.S}

View File

@ -18,7 +18,7 @@ const TimelineActioned: React.FC<Props> = ({
action,
notification = false
}) => {
const { t } = useTranslation('timeline')
const { t } = useTranslation('componentTimeline')
const { theme } = useTheme()
const navigation = useNavigation()
const name = account.display_name || account.username

View File

@ -11,14 +11,7 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import {
Platform,
Pressable,
Share,
StyleSheet,
Text,
View
} from 'react-native'
import { Pressable, StyleSheet, Text, View } from 'react-native'
import { useQueryClient } from 'react-query'
export interface Props {
@ -29,7 +22,7 @@ export interface Props {
const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
const navigation = useNavigation()
const { t } = useTranslation()
const { t } = useTranslation('componentTimeline')
const { theme } = useTheme()
const iconColor = theme.secondary
const iconColorAction = (state: boolean) =>
@ -84,7 +77,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
type: 'error',
message: t('common:toastMessage.error.message', {
function: t(
`timeline:shared.actions.${correctParam.payload.property}.function`
`shared.actions.${correctParam.payload.property}.function`
)
}),
...(err.status &&
@ -95,7 +88,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
description: err.data.error
})
})
queryClient.setQueryData(queryKey, oldData)
queryClient.invalidateQueries(queryKey)
}
})
@ -150,18 +143,6 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
}),
[status.bookmarked]
)
const onPressShare = useCallback(() => {
switch (Platform.OS) {
case 'ios':
return Share.share({
url: status.uri
})
case 'android':
return Share.share({
message: status.uri
})
}
}, [])
const childrenReply = useMemo(
() => (
@ -220,12 +201,6 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
),
[status.bookmarked]
)
const childrenShare = useMemo(
() => (
<Icon name='Share2' color={iconColor} size={StyleConstants.Font.Size.L} />
),
[]
)
return (
<>
@ -256,12 +231,6 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
onPress={onPressBookmark}
children={childrenBookmark}
/>
<Pressable
style={styles.action}
onPress={onPressShare}
children={childrenShare}
/>
</View>
</>
)
@ -269,16 +238,13 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
const styles = StyleSheet.create({
actions: {
width: '100%',
flex: 1,
flexDirection: 'row',
marginTop: StyleConstants.Spacing.S
},
action: {
width: '20%',
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
paddingVertical: StyleConstants.Spacing.S
}
})

View File

@ -17,7 +17,7 @@ export interface Props {
}
const TimelineAttachment: React.FC<Props> = ({ status }) => {
const { t } = useTranslation('timeline')
const { t } = useTranslation('componentTimeline')
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
const onPressBlurView = useCallback(() => {

View File

@ -22,7 +22,7 @@ const AttachmentUnsupported: React.FC<Props> = ({
sensitiveShown,
attachment
}) => {
const { t } = useTranslation('timeline')
const { t } = useTranslation('componentTimeline')
const { theme } = useTheme()
return (

View File

@ -17,7 +17,7 @@ const TimelineContent: React.FC<Props> = ({
highlighted = false,
disableDetails = false
}) => {
const { t } = useTranslation('timeline')
const { t } = useTranslation('componentTimeline')
return (
<>

View File

@ -20,30 +20,29 @@ const HeaderActionsAccount: React.FC<Props> = ({
account,
setBottomSheetVisible
}) => {
const { t } = useTranslation()
const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient()
const mutateion = useTimelineMutation({
queryClient,
onSuccess: (_, { type }) => {
onSuccess: (_, { payload: { property } }) => {
haptics('Success')
toast({
type: 'success',
message: t('common:toastMessage.success.message', {
function: t(
`timeline:shared.header.default.actions.account.${type}.function`,
{ acct: account.acct }
)
function: t(`shared.header.actions.account.${property}.function`, {
acct: account.acct
})
})
})
},
onError: (err: any, { type }) => {
onError: (err: any, { payload: { property } }) => {
haptics('Error')
toast({
type: 'error',
message: t('common:toastMessage.error.message', {
function: t(
`timeline:shared.header.default.actions.account.${type}.function`,
`shared.header.actions.account.${property}.function`,
{ acct: account.acct }
)
}),
@ -64,7 +63,7 @@ const HeaderActionsAccount: React.FC<Props> = ({
return (
<MenuContainer>
<MenuHeader
heading={t('timeline:shared.header.default.actions.account.heading')}
heading={t('shared.header.actions.account.heading')}
/>
<MenuRow
onPress={() => {
@ -77,7 +76,7 @@ const HeaderActionsAccount: React.FC<Props> = ({
})
}}
iconFront='EyeOff'
title={t('timeline:shared.header.default.actions.account.mute.button', {
title={t('shared.header.actions.account.mute.button', {
acct: account.acct
})}
/>
@ -93,7 +92,7 @@ const HeaderActionsAccount: React.FC<Props> = ({
}}
iconFront='XCircle'
title={t(
'timeline:shared.header.default.actions.account.block.button',
'shared.header.actions.account.block.button',
{
acct: account.acct
}
@ -111,7 +110,7 @@ const HeaderActionsAccount: React.FC<Props> = ({
}}
iconFront='Flag'
title={t(
'timeline:shared.header.default.actions.account.report.button',
'shared.header.actions.account.reports.button',
{
acct: account.acct
}

View File

@ -22,7 +22,7 @@ const HeaderActionsDomain: React.FC<Props> = ({
domain,
setBottomSheetVisible
}) => {
const { t } = useTranslation()
const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient()
const mutation = useTimelineMutation({
queryClient,
@ -30,9 +30,7 @@ const HeaderActionsDomain: React.FC<Props> = ({
toast({
type: 'success',
message: t('common:toastMessage.success.message', {
function: t(
`timeline:shared.header.default.actions.domain.block.function`
)
function: t(`shared.header.actions.domain.block.function`)
})
})
queryClient.invalidateQueries(queryKey)
@ -41,20 +39,19 @@ const HeaderActionsDomain: React.FC<Props> = ({
return (
<MenuContainer>
<MenuHeader
heading={t(`timeline:shared.header.default.actions.domain.heading`)}
/>
<MenuHeader heading={t(`shared.header.actions.domain.heading`)} />
<MenuRow
onPress={() => {
Alert.alert(
t('timeline:shared.header.default.actions.domain.alert.title'),
t('timeline:shared.header.default.actions.domain.alert.message'),
t('shared.header.actions.domain.alert.title', { domain }),
t('shared.header.actions.domain.alert.message'),
[
{ text: t('common:buttons.cancel'), style: 'cancel' },
{
text: t(
'timeline:shared.header.default.actions.domain.alert.confirm'
),
text: t('shared.header.actions.domain.alert.buttons.cancel'),
style: 'cancel'
},
{
text: t('shared.header.actions.domain.alert.buttons.confirm'),
style: 'destructive',
onPress: () => {
setBottomSheetVisible(false)
@ -69,7 +66,7 @@ const HeaderActionsDomain: React.FC<Props> = ({
)
}}
iconFront='CloudOff'
title={t(`timeline:shared.header.default.actions.domain.block.button`, {
title={t(`shared.header.actions.domain.block.button`, {
domain
})}
/>

View File

@ -7,17 +7,20 @@ import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useMemo, useState } from 'react'
import { Pressable, StyleSheet } from 'react-native'
import { useSelector } from 'react-redux'
import HeaderActionsAccount from './ActionsAccount'
import HeaderActionsDomain from './ActionsDomain'
import HeaderActionsStatus from './ActionsStatus'
import HeaderActionsAccount from './Account'
import HeaderActionsDomain from './Domain'
import HeaderActionsShare from './Share'
import HeaderActionsStatus from './Status'
export interface Props {
queryKey: QueryKeyTimeline
status: Mastodon.Status
url?: string
type?: 'status' | 'account'
}
const HeaderActions = React.memo(
({ queryKey, status }: Props) => {
({ queryKey, status, url, type }: Props) => {
const { theme } = useTheme()
const localAccount = useSelector(getLocalAccount)
@ -73,6 +76,14 @@ const HeaderActions = React.memo(
setBottomSheetVisible={setBottomSheetVisible}
/>
)}
{url && type ? (
<HeaderActionsShare
url={url}
type={type}
setBottomSheetVisible={setBottomSheetVisible}
/>
) : null}
</BottomSheet>
)}
</>

View File

@ -0,0 +1,52 @@
import MenuContainer from '@components/Menu/Container'
import MenuHeader from '@components/Menu/Header'
import MenuRow from '@components/Menu/Row'
import { toast } from '@components/toast'
import {
QueryKeyTimeline,
useTimelineMutation
} from '@utils/queryHooks/timeline'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Platform, Share } from 'react-native'
export interface Props {
type: 'status' | 'account'
url: string
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
}
const HeaderActionsShare: React.FC<Props> = ({
type,
url,
setBottomSheetVisible
}) => {
const { t } = useTranslation('componentTimeline')
return (
<MenuContainer>
<MenuHeader heading={t(`shared.header.actions.share.${type}.heading`)} />
<MenuRow
iconFront='Share2'
title={t(`shared.header.actions.share.${type}.button`)}
onPress={async () => {
switch (Platform.OS) {
case 'ios':
await Share.share({
url
})
break
case 'android':
await Share.share({
message: url
})
break
}
setBottomSheetVisible(false)
}}
/>
</MenuContainer>
)
}
export default HeaderActionsShare

View File

@ -23,7 +23,7 @@ const HeaderActionsStatus: React.FC<Props> = ({
setBottomSheetVisible
}) => {
const navigation = useNavigation()
const { t } = useTranslation()
const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient()
const mutation = useTimelineMutation({
@ -38,7 +38,7 @@ const HeaderActionsStatus: React.FC<Props> = ({
type: 'error',
message: t('common:toastMessage.error.message', {
function: t(
`timeline:shared.header.default.actions.status.${theFunction}.function`
`shared.header.actions.status.${theFunction}.function`
)
}),
...(err.status &&
@ -56,7 +56,7 @@ const HeaderActionsStatus: React.FC<Props> = ({
return (
<MenuContainer>
<MenuHeader
heading={t('timeline:shared.header.default.actions.status.heading')}
heading={t('shared.header.actions.status.heading')}
/>
<MenuRow
onPress={() => {
@ -69,20 +69,20 @@ const HeaderActionsStatus: React.FC<Props> = ({
})
}}
iconFront='Trash'
title={t('timeline:shared.header.default.actions.status.delete.button')}
title={t('shared.header.actions.status.delete.button')}
/>
<MenuRow
onPress={() => {
Alert.alert(
t('timeline:shared.header.default.actions.status.edit.alert.title'),
t('shared.header.actions.status.edit.alert.title'),
t(
'timeline:shared.header.default.actions.status.edit.alert.message'
'shared.header.actions.status.edit.alert.message'
),
[
{ text: t('common:buttons.cancel'), style: 'cancel' },
{ text: t('shared.header.actions.status.edit.alert.buttons.cancel'), style: 'cancel' },
{
text: t(
'timeline:shared.header.default.actions.status.edit.alert.confirm'
'shared.header.actions.status.edit.alert.buttons.confirm'
),
style: 'destructive',
onPress: async () => {
@ -105,8 +105,8 @@ const HeaderActionsStatus: React.FC<Props> = ({
]
)
}}
iconFront='Trash'
title={t('timeline:shared.header.default.actions.status.edit.button')}
iconFront='Edit'
title={t('shared.header.actions.status.edit.button')}
/>
<MenuRow
onPress={() => {
@ -122,10 +122,10 @@ const HeaderActionsStatus: React.FC<Props> = ({
title={
status.muted
? t(
'timeline:shared.header.default.actions.status.mute.button.negative'
'shared.header.actions.status.mute.button.negative'
)
: t(
'timeline:shared.header.default.actions.status.mute.button.positive'
'shared.header.actions.status.mute.button.positive'
)
}
/>
@ -145,10 +145,10 @@ const HeaderActionsStatus: React.FC<Props> = ({
title={
status.pinned
? t(
'timeline:shared.header.default.actions.status.pin.button.negative'
'shared.header.actions.status.pin.button.negative'
)
: t(
'timeline:shared.header.default.actions.status.pin.button.positive'
'shared.header.actions.status.pin.button.positive'
)
}
/>

View File

@ -21,7 +21,7 @@ export interface Props {
}
const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
const { t } = useTranslation()
const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient()
const mutation = useTimelineMutation({
@ -32,7 +32,7 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
toast({
type: 'error',
message: t('common:toastMessage.error.message', {
function: t(`timeline:shared.header.conversation.delete.function`)
function: t(`shared.header.conversation.delete.function`)
}),
...(err.status &&
typeof err.status === 'number' &&

View File

@ -27,7 +27,14 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => {
</View>
</View>
{queryKey ? <HeaderActions queryKey={queryKey} status={status} /> : null}
{queryKey ? (
<HeaderActions
queryKey={queryKey}
status={status}
url={status.url || status.uri}
type='status'
/>
) : null}
</View>
)
}

View File

@ -11,7 +11,7 @@ export interface Props {
const HeaderSharedApplication: React.FC<Props> = ({ application }) => {
const { theme } = useTheme()
const { t } = useTranslation('timeline')
const { t } = useTranslation('componentTimeline')
return application && application.name !== 'Web' ? (
<Text

View File

@ -2,7 +2,6 @@ import RelativeTime from '@components/RelativeTime'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text } from 'react-native'
export interface Props {
@ -11,7 +10,6 @@ export interface Props {
const HeaderSharedCreated: React.FC<Props> = ({ created_at }) => {
const { theme } = useTheme()
const { i18n } = useTranslation()
return (
<Text style={[styles.created_at, { color: theme.secondary }]}>

View File

@ -5,6 +5,7 @@ import { ParseEmojis } from '@components/Parse'
import RelativeTime from '@components/RelativeTime'
import { toast } from '@components/toast'
import {
MutationVarsTimelineUpdateStatusProperty,
QueryKeyTimeline,
useTimelineMutation
} from '@utils/queryHooks/timeline'
@ -32,7 +33,7 @@ const TimelinePoll: React.FC<Props> = ({
sameAccount
}) => {
const { mode, theme } = useTheme()
const { t } = useTranslation('timeline')
const { t } = useTranslation('componentTimeline')
const [allOptions, setAllOptions] = useState(
new Array(poll.options.length).fill(false)
@ -42,19 +43,21 @@ const TimelinePoll: React.FC<Props> = ({
const mutation = useTimelineMutation({
queryClient,
onSuccess: true,
onError: (err: any) => {
onError: (err: any, params) => {
const theParams = params as MutationVarsTimelineUpdateStatusProperty
haptics('Error')
toast({
type: 'error',
message: '投票错误',
message: t('common:toastMessage.error.message', {
function: t(`shared.poll.meta.button.${theParams.payload.type}`)
}),
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
}),
autoHide: false
})
})
queryClient.invalidateQueries(queryKey)
}

View File

@ -1,5 +1,4 @@
import * as Analytics from 'expo-firebase-analytics'
import * as Sentry from 'sentry-expo'
const analytics = (event: string, params?: { [key: string]: string }) => {
Analytics.logEvent(event, params).catch(

View File

@ -1,5 +1,5 @@
export default {
common: require('./common').default,
relativeTime: require('./components/relativeTime').default
componentRelativeTime: require('./components/relativeTime').default,
}

View File

@ -15,12 +15,18 @@ export default {
meListsList: require('./screens/meListsList').default,
meSettings: require('./screens/meSettings').default,
meSettingsUpdateRemote: require('./screens/meSettingsUpdateRemote').default,
meSwitch: require('./screens/meSwitch').default,
sharedAccount: require('./screens/sharedAccount').default,
sharedToot: require('./screens/sharedToot').default,
sharedAnnouncements: require('./screens/sharedAnnouncements').default,
sharedCompose: require('./screens/sharedCompose').default,
sharedRelationships: require('./screens/sharedRelationships').default,
sharedSearch: require('./screens/sharedSearch').default,
sharedToot: require('./screens/sharedToot').default,
relationship: require('./components/relationship').default,
relativeTime: require('./components/relativeTime').default,
timeline: require('./components/timeline').default
componentInstance: require('./components/instance').default,
componentParse: require('./components/parse').default,
componentRelationship: require('./components/relationship').default,
componentRelativeTime: require('./components/relativeTime').default,
componentTimeline: require('./components/timeline').default
}

View File

@ -0,0 +1,33 @@
export default {
server: {
textInput: { placeholder: '输入社区服务器地址' },
button: {
local: '登录',
remote: '围观'
},
information: {
name: '社区名称',
description: { heading: '社区简介', expandHint: '简介' },
accounts: '用户总数',
statuses: '嘟文总数',
domains: '连结总数'
},
disclaimer:
'登录过程将使用系统浏览器您的账户登录信息tooot应用无法读取。详见'
},
update: {
local: {
alert: {
title: '此社区已登录',
message: '您可以登录同个社区的另一个账户,不影响现有账户',
buttons: {
cancel: '$t(common:buttons.cancel)',
continue: '继续'
}
}
},
remote: {
succeed: '围观登记成功'
}
}
}

View File

@ -0,0 +1,8 @@
export default {
HTML: {
expanded: {
true: '折叠{{hint}}',
false: '展开{{hint}}'
}
}
}

View File

@ -5,9 +5,17 @@ export default {
button: '重试'
},
success: {
message: '🈳🈚1一物'
message: '空无一物'
}
},
end: {
message: '居然刷到底了,喝杯 <0 /> 吧'
},
header: {
explanation:
'围观的社区可能不属于已经登录的社区的已知连结,因此只可围观嘟文,不能进行操作。设置里可以切换想要围观的社区。',
button: '前往设置'
},
shared: {
actioned: {
pinned: '置顶',
@ -22,17 +30,14 @@ export default {
}
},
actions: {
favourite: {
favourited: {
function: '喜欢嘟文'
// button: '隐藏 {{acct}} 的嘟文'
},
reblog: {
reblogged: {
function: '转嘟'
// button: '屏蔽 {{acct}}'
},
bookmark: {
bookmarked: {
function: '收藏嘟文'
// button: '举报 {{acct}}'
}
},
attachment: {
@ -40,16 +45,13 @@ export default {
button: '显示敏感内容'
},
unsupported: {
text: '件读取错误',
text: '件读取错误',
button: '尝试远程链接'
}
},
content: {
expandHint: '隐藏内容'
},
end: {
message: '居然刷到底了,喝杯 <0 /> 吧'
},
header: {
shared: {
application: '发自于 {{application}}'
@ -59,59 +61,73 @@ export default {
function: '删除私信'
}
},
default: {
actions: {
account: {
heading: '关于用户',
mute: {
function: '隐藏 @{{acct}} 的嘟文',
button: '隐藏 @{{acct}} 的嘟文'
},
block: {
function: '屏蔽 @{{acct}}',
button: '屏蔽 @{{acct}}'
},
report: {
function: '举报 @{{acct}}',
button: '举报 @{{acct}}'
actions: {
account: {
heading: '关于用户',
mute: {
function: '隐藏 @{{acct}} 的嘟文',
button: '隐藏 @{{acct}} 的嘟文'
},
block: {
function: '屏蔽 @{{acct}}',
button: '屏蔽 @{{acct}}'
},
reports: {
function: '举报 @{{acct}}',
button: '举报 @{{acct}}'
}
},
domain: {
heading: '关于社区',
block: {
function: '屏蔽社区',
button: '屏蔽社区 {{domain}}'
},
alert: {
title: '确定要屏蔽 {{domain}} 吗?',
message:
'多数情况下,隐藏或屏蔽特定用户即可。\n\n屏蔽之后来自此社区的所有内容将不再出现在你的时间轴里。同时来自该社区的关注者将被移除。请谨慎使用。',
buttons: {
confirm: '确定屏蔽整个社区',
cancel: '$t(common:buttons.cancel)'
}
}
},
share: {
status: { heading: '分享嘟文', button: '分享此条嘟文的链接' },
account: { heading: '分享用户', button: '分享此用户的链接' }
},
status: {
heading: '关于嘟文',
delete: {
function: '删除',
button: '删除此条嘟文'
},
edit: {
function: '删除',
button: '删除并重新编辑此条嘟文',
alert: {
title: '确认删除嘟文?',
message:
'确定要删除这条嘟文并重新编辑它吗?所有相关的转嘟和喜欢都会被清除,回复将会失去关联。',
buttons: {
confirm: '删除并重新编辑',
cancel: '$t(common:buttons.cancel)'
}
}
},
domain: {
heading: '关于域名',
block: {
function: '屏蔽域名',
button: '屏蔽域名 {{domain}}'
mute: {
function: '静音',
button: {
positive: '静音此条嘟文及对话',
negative: '取消静音此条嘟文及对话'
}
},
status: {
heading: '关于嘟嘟',
delete: {
function: '删除',
button: '删除次条嘟文'
},
edit: {
function: '删除',
button: '删除并重新编辑次条嘟文',
alert: {
title: '确认删除嘟嘟?',
message:
'你确定要删除这条嘟文并重新编辑它吗?所有相关的转嘟和喜欢都会被清除,回复将会失去关联。',
confirm: '删除并重新编辑'
}
},
mute: {
function: '静音',
button: {
positive: '静音此条嘟文及对话',
negative: '取消静音此条嘟文及对话'
}
},
pin: {
function: '置顶',
button: {
positive: '置顶此条嘟文',
negative: '取消置顶此条嘟文'
}
pin: {
function: '置顶',
button: {
positive: '置顶此条嘟文',
negative: '取消置顶此条嘟文'
}
}
}

View File

@ -4,6 +4,5 @@ export default {
left: '我的关注',
right: '本站嘟嘟'
}
},
content: {}
}
}

View File

@ -1,18 +1,18 @@
export default {
heading: '我的长毛象',
content: {
login: {
server: {
placeholder: '请输入服务器'
},
button: '登录'
},
collections: {
conversations: '$t(meConversations:heading)',
bookmarks: '$t(meBookmarks:heading)',
favourites: '$t(meFavourites:heading)',
lists: '$t(meLists:heading)',
announcements: '$t(sharedAnnouncements:heading)'
announcements: {
heading: '$t(sharedAnnouncements:heading)',
content: {
unread: '{{amount}} 条未读公告',
read: '无未读公告'
}
}
},
settings: '$t(meSettings:heading)',
logout: {

View File

@ -19,24 +19,30 @@ export default {
}
},
browser: {
heading: '打开链接',
heading: '外部链接',
options: {
internal: '应用内',
external: '系统浏览器',
internal: '应用内打开',
external: '浏览器打开',
cancel: '$t(common:buttons.cancel)'
}
},
remote: {
heading: '$t(meSettingsUpdateRemote:heading)',
description: '外站只能不能玩'
description: '外站只能浏览不能玩'
},
cache: {
heading: '清空缓存',
empty: '暂无缓存'
},
support: {
heading: '赞助 tooot 开发'
},
review: {
heading: '给 tooot 打分'
},
analytics: {
heading: '帮助我们改进',
description: '允许我们收集不与用户相关联的使用信息'
description: '收集不与用户相关联的使用信息'
},
version: '版本 v{{version}}'
}

View File

@ -1,45 +1,3 @@
export default {
heading: '外站链接',
content: {
language: {
heading: '切换语言',
options: {
zh: '简体中文',
en: 'English',
cancel: '$t(common:buttons.cancel)'
}
},
theme: {
heading: '应用外观',
options: {
auto: '跟随系统',
light: '浅色模式',
dark: '深色模式',
cancel: '$t(common:buttons.cancel)'
}
},
browser: {
heading: '打开链接',
options: {
internal: '应用内',
external: '系统浏览器',
cancel: '$t(common:buttons.cancel)'
}
},
remote: {
heading: '外站链接',
description: '外站只能看不能玩'
},
cache: {
heading: '清空缓存'
},
analytics: {
heading: '帮助我们改进',
description: '允许我们收集不与用户相关联的使用信息'
},
copyrights: {
heading: '版权信息'
},
version: '版本 v{{version}}'
}
heading: '外站社区'
}

View File

@ -0,0 +1,7 @@
export default {
heading: '切换账号',
content: {
existing: '选择已有账号',
new: '登录新社区'
}
}

View File

@ -1,4 +1,3 @@
export default {
heading: '通知',
content: {}
heading: '通知'
}

View File

@ -4,6 +4,5 @@ export default {
left: '跨站关注',
right: '外站嘟嘟'
}
},
content: {}
}
}

View File

@ -1,19 +1,10 @@
export default {
heading: {
loading: '加载中…',
error: '加载错误'
},
content: {
created_at: '加入时间:{{date}}',
summary: {
statuses_count: '{{count}} 条嘟文',
following_count: '关注 {{count}} 人',
followers_count: '被 {{count}} 人关注'
},
segments: {
left: '所有嘟嘟',
middle: '嘟嘟和回复',
right: '所有媒体'
}
}
}

View File

@ -1,4 +1,10 @@
export default {
heading: '公告',
content: {}
content: {
published: '发布于 <0 />',
button: {
read: '已读',
unread: '标记已读'
}
}
}

View File

@ -0,0 +1,144 @@
export default {
heading: {
left: {
button: '退出编辑',
alert: {
title: '确认退出编辑?',
buttons: {
exit: '退出编辑',
continue: '继续编辑'
}
}
},
right: {
button: {
default: '发嘟嘟',
conversation: '发私信',
reply: '发布回复',
edit: '发嘟嘟'
},
alert: {
title: '发布失败',
button: '返回重试'
}
}
},
content: {
root: {
header: {
postingAs: '用 @{{acct}}@{{domain}} 发布',
spoilerInput: {
placeholder: '折叠部分的警告信息'
},
textInput: {
placeholder: '想说点什么'
}
},
footer: {
attachments: {
sensitive: '标记媒体为敏感内容'
},
poll: {
option: {
placeholder: {
single: '单选项',
multiple: '多选项'
}
},
multiple: {
heading: '可选项',
options: {
single: '单选',
multiple: '多选',
cancel: '$t(common:buttons.cancel)'
}
},
expiration: {
heading: '有效期',
options: {
'300': '5分钟',
'1800': '30分钟',
'3600': '1小时',
'21600': '6小时',
'86400': '1天',
'259200': '3天',
'604800': '7天',
cancel: '$t(common:buttons.cancel)'
}
}
}
},
actions: {
attachment: {
actions: {
options: {
library: '从相册上传',
photo: '拍摄上传',
cancel: '$t(common:buttons.cancel)'
},
library: {
alert: {
title: '无读取权限',
message: '需要相片权限才能上传附件',
buttons: {
settings: '去系统设置',
cancel: '取消上传'
}
}
},
photo: {
alert: {
title: '无拍照权限',
message: '需要相机权限才能上传附件',
buttons: {
settings: '去系统设置',
cancel: '取消上传'
}
}
}
},
failed: {
alert: {
title: '上传失败',
button: '返回重试'
}
}
},
visibility: {
title: '嘟文可见范围',
options: {
public: '公开',
unlisted: '不公开',
private: '仅关注者',
direct: '私信',
cancel: '$t(common:buttons.cancel)'
}
}
}
},
editAttachment: {
header: {
left: '取消修改',
right: {
button: '应用修改',
succeed: {
title: '修改成功',
button: '好的'
},
failed: {
title: '修改失败',
button: '返回重试'
}
}
},
content: {
altText: {
heading: '为附件添加文字说明',
placeholder:
'你可以为附件添加文字说明,以便更多人可以查看他们(包括视力障碍或视力受损人士)。\n\n优质的描述应该简洁明了但要准确地描述照片中的内容以便用户理解其含义。'
},
imageFocus: '在预览图上拖动圆圈,以选择缩略图的焦点'
}
}
}
}

View File

@ -0,0 +1,8 @@
export default {
heading: {
segments: {
left: '关注中',
right: '关注者'
}
}
}

View File

@ -0,0 +1,32 @@
export default {
heading: '对话',
content: {
header: {
prefix: '搜索',
placeholder: '些什么'
},
empty: {
general:
'输入关键词搜索<bold>$t(sharedSearch:content.sections.accounts)</bold>、<bold>$t(sharedSearch:content.sections.hashtags)</bold>或者<bold>$t(sharedSearch:content.sections.statuses)</bold>',
advanced: {
header: '高级搜索格式',
example: {
account:
'$t(sharedSearch:content.header.prefix)$t(sharedSearch:content.sections.accounts)',
hashtag:
'$t(sharedSearch:content.header.prefix)$t(sharedSearch:content.sections.hashtags)',
statusLink:
'$t(sharedSearch:content.header.prefix)指定$t(sharedSearch:content.sections.statuses)',
accountLink:
'$t(sharedSearch:content.header.prefix)$t(sharedSearch:content.sections.accounts)'
}
}
},
sections: {
accounts: '用户',
hashtags: '话题标签',
statuses: '嘟文'
},
notFound: '找不到 <bold>{{searchTerm}}</bold> 相关的 {{type}}'
}
}

View File

@ -1,4 +1,3 @@
export default {
heading: '对话',
content: {}
heading: '对话'
}

View File

@ -7,7 +7,7 @@ import ScreenMeRoot from '@screens/Me/Root'
import ScreenMeListsList from '@screens/Me/Root/Lists/List'
import ScreenMeSettings from '@screens/Me/Settings'
import ScreenMeSwitch from '@screens/Me/Switch'
import UpdateRemote from '@screens/Me/UpdateRemote'
import ScreenMeUpdateRemote from '@screens/Me/UpdateRemote'
import sharedScreens from '@screens/Shared/sharedScreens'
import React from 'react'
import { useTranslation } from 'react-i18next'
@ -89,7 +89,9 @@ const ScreenMe: React.FC = () => {
headerTitle: t('meListsList:heading', { list: route.params.title }),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('meListsList:heading')} />
<HeaderCenter
content={t('meListsList:heading', { list: route.params.title })}
/>
)
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
@ -110,7 +112,7 @@ const ScreenMe: React.FC = () => {
/>
<Stack.Screen
name='Screen-Me-Settings-UpdateRemote'
component={UpdateRemote}
component={ScreenMeUpdateRemote}
options={({ navigation }: any) => ({
headerTitle: t('meSettingsUpdateRemote:heading'),
...(Platform.OS === 'android' && {
@ -127,17 +129,11 @@ const ScreenMe: React.FC = () => {
options={({ navigation }: any) => ({
stackPresentation: 'fullScreenModal',
headerShown: false,
headerTitle: t('meSettings:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => (
<HeaderCenter content={t('meSettings:heading')} />
)
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})}
/>
{sharedScreens(Stack)}
{sharedScreens(Stack as any)}
</Stack.Navigator>
)
}

View File

@ -1,10 +1,9 @@
import { MenuContainer, MenuRow } from '@components/Menu'
import { useNavigation } from '@react-navigation/native'
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { MenuContainer, MenuRow } from '@components/Menu'
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
const Collections: React.FC = () => {
const { t } = useTranslation('meRoot')
const navigation = useNavigation()
@ -15,9 +14,9 @@ const Collections: React.FC = () => {
if (data) {
const amount = data.filter(announcement => !announcement.read).length
if (amount) {
return `${amount} 条未读公告`
return t('content.collections.announcements.content.unread', { amount })
} else {
return '无未读公告'
return t('content.collections.announcements.content.read')
}
}
}, [data])
@ -51,7 +50,7 @@ const Collections: React.FC = () => {
<MenuRow
iconFront='Clipboard'
iconBack='ChevronRight'
title={t('content.collections.announcements')}
title={t('content.collections.announcements.heading')}
content={announcementContent}
loading={isFetching}
onPress={() =>

View File

@ -116,9 +116,11 @@ const ScreenMeSettings: React.FC = () => {
cancelButtonIndex: i18n.languages.length
},
buttonIndex => {
haptics('Success')
dispatch(changeLanguage(availableLanguages[buttonIndex]))
i18n.changeLanguage(availableLanguages[buttonIndex])
if (buttonIndex < i18n.languages.length) {
haptics('Success')
dispatch(changeLanguage(availableLanguages[buttonIndex]))
i18n.changeLanguage(availableLanguages[buttonIndex])
}
}
)
}}
@ -226,7 +228,7 @@ const ScreenMeSettings: React.FC = () => {
onPress={() => Linking.openURL('https://www.patreon.com/xmflsct')}
/>
<MenuRow
title={t('content.copyrights.heading')}
title={t('content.review.heading')}
content={
<Icon
name='Star'

View File

@ -1,5 +1,6 @@
import { HeaderCenter, HeaderLeft } from '@components/Header'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Platform, StyleSheet } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import ScreenMeSwitchRoot from './Switch/Root'
@ -7,6 +8,7 @@ import ScreenMeSwitchRoot from './Switch/Root'
const Stack = createNativeStackNavigator()
const ScreenMeSwitch: React.FC = ({ navigation }) => {
const { t } = useTranslation()
return (
<Stack.Navigator
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
@ -15,9 +17,9 @@ const ScreenMeSwitch: React.FC = ({ navigation }) => {
name='Screen-Me-Switch-Root'
component={ScreenMeSwitchRoot}
options={{
headerTitle: '切换账号',
headerTitle: t('meSwitch:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => <HeaderCenter content='切换账号' />
headerCenter: () => <HeaderCenter content={t('meSwitch:heading')} />
}),
headerLeft: () => (
<HeaderLeft content='X' onPress={() => navigation.goBack()} />

View File

@ -12,6 +12,7 @@ import {
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import { useTranslation } from 'react-i18next'
import {
KeyboardAvoidingView,
Platform,
@ -59,7 +60,8 @@ const AccountButton: React.FC<Props> = ({
)
}
const ScreenMeSwitchRoot = () => {
const ScreenMeSwitchRoot: React.FC = () => {
const { t } = useTranslation('meSwitch')
const { theme } = useTheme()
const localInstances = useSelector(getLocalInstances)
const localActiveIndex = useSelector(getLocalActiveIndex)
@ -70,15 +72,11 @@ const ScreenMeSwitchRoot = () => {
style={{ flex: 1 }}
>
<ScrollView keyboardShouldPersistTaps='handled'>
<View style={styles.firstSection}>
<View
style={[styles.firstSection, { borderBottomColor: theme.border }]}
>
<Text style={[styles.header, { color: theme.primary }]}>
</Text>
<ComponentInstance type='local' disableHeaderImage goBack />
</View>
<View style={[styles.secondSection, { borderTopColor: theme.border }]}>
<Text style={[styles.header, { color: theme.primary }]}>
{t('content.existing')}
</Text>
<View style={styles.accountButtons}>
{localInstances.length
@ -93,6 +91,13 @@ const ScreenMeSwitchRoot = () => {
: null}
</View>
</View>
<View style={styles.secondSection}>
<Text style={[styles.header, { color: theme.primary }]}>
{t('content.new')}
</Text>
<ComponentInstance type='local' disableHeaderImage goBack />
</View>
</ScrollView>
</KeyboardAvoidingView>
)
@ -105,12 +110,13 @@ const styles = StyleSheet.create({
paddingVertical: StyleConstants.Spacing.S
},
firstSection: {
marginTop: StyleConstants.Spacing.S
marginTop: StyleConstants.Spacing.S,
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
paddingBottom: StyleConstants.Spacing.S,
borderBottomWidth: StyleSheet.hairlineWidth
},
secondSection: {
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
paddingTop: StyleConstants.Spacing.M,
borderTopWidth: StyleSheet.hairlineWidth
paddingTop: StyleConstants.Spacing.M
},
accountButtons: {
flex: 1,

View File

@ -3,7 +3,7 @@ import React from 'react'
import { KeyboardAvoidingView, Platform } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
const UpdateRemote: React.FC = () => {
const ScreenMeUpdateRemote: React.FC = () => {
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
@ -16,4 +16,4 @@ const UpdateRemote: React.FC = () => {
)
}
export default UpdateRemote
export default ScreenMeUpdateRemote

View File

@ -1,7 +1,8 @@
import BottomSheet from '@components/BottomSheet'
import { HeaderRight } from '@components/Header'
import Timeline from '@components/Timelines/Timeline'
import HeaderActionsAccount from '@components/Timelines/Timeline/Shared/HeaderActions/ActionsAccount'
import HeaderActionsAccount from '@components/Timelines/Timeline/Shared/HeaderActions/Account'
import HeaderActionsShare from '@components/Timelines/Timeline/Shared/HeaderActions/Share'
import { useAccountQuery } from '@utils/queryHooks/account'
import { getLocalAccount } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
@ -97,6 +98,12 @@ const ScreenSharedAccount: React.FC<SharedAccountProp> = ({
setBottomSheetVisible={setBottomSheetVisible}
/>
)}
<HeaderActionsShare
url={account.url}
type='account'
setBottomSheetVisible={setBottomSheetVisible}
/>
</BottomSheet>
</AccountContext.Provider>
)

View File

@ -1,14 +1,16 @@
import Button from '@components/Button'
import { useNavigation } from '@react-navigation/native'
import React from 'react'
import { useTranslation } from 'react-i18next'
const AccountInformationSwitch: React.FC = () => {
const navigation = useNavigation()
const { t } = useTranslation()
return (
<Button
type='text'
content='切换账号'
content={t('meSwitch:heading')}
onPress={() => navigation.navigate('Screen-Me-Switch')}
/>
)

View File

@ -92,5 +92,4 @@ const styles = StyleSheet.create({
}
})
// export default React.memo(AccountNav, (_, next) => next.account === undefined)
export default AccountNav
export default React.memo(AccountNav, (_, next) => next.account === undefined)

View File

@ -10,7 +10,7 @@ import {
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Trans, useTranslation } from 'react-i18next'
import {
Dimensions,
Image,
@ -33,7 +33,7 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
const { theme } = useTheme()
const bottomTabBarHeight = useBottomTabBarHeight()
const [index, setIndex] = useState(0)
const { t, i18n } = useTranslation()
const { t } = useTranslation('sharedAnnouncements')
const query = useAnnouncementQuery({
showAll,
@ -80,7 +80,10 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
]}
>
<Text style={[styles.published, { color: theme.secondary }]}>
<RelativeTime date={item.published_at} />
<Trans
i18nKey='sharedAnnouncements:content.published'
components={[<RelativeTime date={item.published_at} />]}
/>
</Text>
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator>
<ParseHTML
@ -145,7 +148,9 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
) : null}
<Button
type='text'
content={item.read ? '已读' : '标记阅读'}
content={
item.read ? t('content.button.read') : t('content.button.unread')
}
loading={mutation.isLoading}
disabled={item.read}
onPress={() =>

View File

@ -9,6 +9,7 @@ import { getLocalAccount } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useReducer, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
Alert,
Keyboard,
@ -35,6 +36,7 @@ const Compose: React.FC<SharedComposeProp> = ({
route: { params },
navigation
}) => {
const { t } = useTranslation('sharedCompose')
const { theme } = useTheme()
const queryClient = useQueryClient()
@ -114,17 +116,11 @@ const Compose: React.FC<SharedComposeProp> = ({
(composeState.spoiler.active ? composeState.spoiler.count : 0) +
composeState.text.count
const postButtonText = {
conversation: '回复私信',
reply: '发布回复',
edit: '发嘟嘟'
}
const headerLeft = useCallback(
() => (
<HeaderLeft
type='text'
content='退出编辑'
content={t('heading.left.button')}
onPress={() => {
if (
totalTextCount === 0 &&
@ -134,13 +130,16 @@ const Compose: React.FC<SharedComposeProp> = ({
navigation.goBack()
return
} else {
Alert.alert('确认取消编辑?', '', [
Alert.alert(t('heading.left.alert.title'), undefined, [
{
text: '退出编辑',
text: t('heading.left.alert.buttons.exit'),
style: 'destructive',
onPress: () => navigation.goBack()
},
{ text: '继续编辑', style: 'cancel' }
{
text: t('heading.left.alert.buttons.continue'),
style: 'cancel'
}
])
}
}}
@ -168,7 +167,11 @@ const Compose: React.FC<SharedComposeProp> = ({
() => (
<HeaderRight
type='text'
content={params?.type ? postButtonText[params.type] : '发嘟嘟'}
content={
params?.type
? t(`heading.right.button.${params.type}`)
: t('heading.right.button.default')
}
onPress={() => {
composeDispatch({ type: 'posting', payload: true })
@ -190,9 +193,9 @@ const Compose: React.FC<SharedComposeProp> = ({
.catch(() => {
haptics('Error')
composeDispatch({ type: 'posting', payload: false })
Alert.alert('发布失败', '', [
Alert.alert(t('heading.right.alert.title'), undefined, [
{
text: '返回重试'
text: t('heading.right.alert.button')
}
])
})

View File

@ -8,6 +8,7 @@ import React, {
useRef,
useState
} from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, KeyboardAvoidingView, Platform } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
@ -32,6 +33,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
navigation
}) => {
const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('sharedCompose')
const theAttachment = composeState.attachments.uploads[index].remote!
const [isSubmitting, setIsSubmitting] = useState(false)
@ -71,7 +73,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
() => (
<HeaderLeft
type='text'
content='取消'
content={t('content.editAttachment.header.left')}
onPress={() => navigation.goBack()}
/>
),
@ -81,7 +83,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
() => (
<HeaderRight
type='text'
content='应用'
content={t('content.editAttachment.header.right.button')}
loading={isSubmitting}
onPress={() => {
if (!altText && focus.current.x === 0 && focus.current.y === 0) {
@ -105,24 +107,36 @@ const ComposeEditAttachment: React.FC<Props> = ({
})
.then(() => {
haptics('Success')
Alert.alert('修改成功', '', [
{
text: '好的',
onPress: () => {
navigation.goBack()
Alert.alert(
t('content.editAttachment.header.right.succeed.title'),
undefined,
[
{
text: t(
'content.editAttachment.header.right.succeed.button'
),
onPress: () => {
navigation.goBack()
}
}
}
])
]
)
})
.catch(() => {
setIsSubmitting(false)
haptics('Error')
Alert.alert('修改失败', '', [
{
text: '返回重试',
style: 'cancel'
}
])
Alert.alert(
t('content.editAttachment.header.right.failed.title'),
undefined,
[
{
text: t(
'content.editAttachment.header.right.failed.button'
),
style: 'cancel'
}
]
)
})
}}
/>

View File

@ -1,6 +1,7 @@
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { MutableRefObject, useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, Image, StyleSheet, Text, View } from 'react-native'
import { PanGestureHandler } from 'react-native-gesture-handler'
import Animated, {
@ -23,6 +24,7 @@ export interface Props {
}
const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
const { t } = useTranslation('sharedCompose')
const { theme } = useTheme()
const { composeState } = useContext(ComposeContext)
@ -149,7 +151,7 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
</PanGestureHandler>
</View>
<Text style={[styles.imageFocusText, { color: theme.primary }]}>
{t('content.editAttachment.content.imageFocus')}
</Text>
</>
)

View File

@ -8,6 +8,7 @@ import React, {
useContext,
useMemo
} from 'react'
import { useTranslation } from 'react-i18next'
import { ScrollView, StyleSheet, Text, TextInput, View } from 'react-native'
import ComposeContext from '../utils/createContext'
import ComposeEditAttachmentImage from './Image'
@ -28,6 +29,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
altText,
setAltText
}) => {
const { t } = useTranslation('sharedCompose')
const { theme } = useTheme()
const { composeState } = useContext(ComposeContext)
const theAttachment = composeState.attachments.uploads[index].remote!
@ -41,6 +43,8 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
const video = composeState.attachments.uploads[index]
return (
<AttachmentVideo
total={1}
index={0}
sensitiveShown={false}
video={
video.local
@ -62,7 +66,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
{mediaDisplay}
<View style={styles.altTextContainer}>
<Text style={[styles.altTextInputHeading, { color: theme.primary }]}>
{t('content.editAttachment.content.altText.heading')}
</Text>
<TextInput
style={[
@ -74,9 +78,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
maxLength={1500}
multiline
onChangeText={e => setAltText(e)}
placeholder={
'你可以为附件添加文字说明,以便更多人可以查看他们(包括视力障碍或视力受损人士)。\n\n优质的描述应该简洁明了但要准确地描述照片中的内容以便用户理解其含义。'
}
placeholder={t('content.editAttachment.content.altText.placeholder')}
placeholderTextColor={theme.secondary}
scrollEnabled
value={altText}

View File

@ -7,7 +7,7 @@ import { forEach, groupBy, sortBy } from 'lodash'
import React, { useCallback, useContext, useEffect, useMemo } from 'react'
import { View, FlatList, StyleSheet } from 'react-native'
import { Chase } from 'react-native-animated-spinkit'
import ComposeActions from './Actions'
import ComposeActions from './Root/Actions'
import ComposePosting from './Posting'
import ComposeRootFooter from './Root/Footer'
import ComposeRootHeader from './Root/Header'

View File

@ -4,13 +4,15 @@ import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useContext, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Pressable, StyleSheet, View } from 'react-native'
import addAttachment from './/addAttachment'
import ComposeContext from './utils/createContext'
import ComposeContext from '../utils/createContext'
import addAttachment from './Footer/addAttachment'
const ComposeActions: React.FC = () => {
const { showActionSheetWithOptions } = useActionSheet()
const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('sharedCompose')
const { theme } = useTheme()
const attachmentColor = useMemo(() => {
@ -71,7 +73,14 @@ const ComposeActions: React.FC = () => {
if (!composeState.visibilityLock) {
showActionSheetWithOptions(
{
options: ['公开', '不公开', '仅关注着', '私信', '取消'],
title: t('content.root.actions.visibility.title'),
options: [
t('content.root.actions.visibility.options.public'),
t('content.root.actions.visibility.options.unlisted'),
t('content.root.actions.visibility.options.private'),
t('content.root.actions.visibility.options.direct'),
t('content.root.actions.visibility.options.cancel')
],
cancelButtonIndex: 4
},
buttonIndex => {

View File

@ -1,8 +1,8 @@
import React, { useContext } from 'react'
import ComposeAttachments from '@screens/Shared/Compose/Attachments'
import ComposeEmojis from '@screens/Shared/Compose/Emojis'
import ComposePoll from '@screens/Shared/Compose/Poll'
import ComposeReply from '@screens/Shared/Compose/Reply'
import ComposeAttachments from '@screens/Shared/Compose/Root/Footer/Attachments'
import ComposeEmojis from '@screens/Shared/Compose/Root/Footer/Emojis'
import ComposePoll from '@screens/Shared/Compose/Root/Footer/Poll'
import ComposeReply from '@screens/Shared/Compose/Root/Footer/Reply'
import ComposeContext from '@screens/Shared/Compose//utils/createContext'
const ComposeRootFooter: React.FC = () => {

View File

@ -13,6 +13,7 @@ import React, {
useMemo,
useRef
} from 'react'
import { useTranslation } from 'react-i18next'
import {
FlatList,
Image,
@ -22,15 +23,16 @@ import {
View
} from 'react-native'
import { Chase } from 'react-native-animated-spinkit'
import ComposeContext from '../../utils/createContext'
import { ExtendedAttachment } from '../../utils/types'
import addAttachment from './addAttachment'
import ComposeContext from './utils/createContext'
import { ExtendedAttachment } from './utils/types'
const DEFAULT_HEIGHT = 200
const ComposeAttachments: React.FC = () => {
const { showActionSheetWithOptions } = useActionSheet()
const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('sharedCompose')
const { theme } = useTheme()
const navigation = useNavigation()
@ -234,7 +236,7 @@ const ComposeAttachments: React.FC = () => {
color={theme.primary}
/>
<Text style={[styles.sensitiveText, { color: theme.primary }]}>
{t('content.root.footer.attachments.sensitive')}
</Text>
</Pressable>
<FlatList

View File

@ -10,8 +10,8 @@ import {
Text,
View
} from 'react-native'
import ComposeContext from './utils/createContext'
import updateText from './updateText'
import ComposeContext from '../../utils/createContext'
import updateText from '../../updateText'
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
const { composeState, composeDispatch } = useContext(ComposeContext)

View File

@ -5,8 +5,9 @@ import { useActionSheet } from '@expo/react-native-action-sheet'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, TextInput, View } from 'react-native'
import ComposeContext from './utils/createContext'
import ComposeContext from '../../utils/createContext'
const ComposePoll: React.FC = () => {
const { showActionSheetWithOptions } = useActionSheet()
@ -16,18 +17,9 @@ const ComposePoll: React.FC = () => {
},
composeDispatch
} = useContext(ComposeContext)
const { t } = useTranslation('sharedCompose')
const { theme } = useTheme()
const expireMapping: { [key: string]: string } = {
'300': '5分钟',
'1800': '30分钟',
'3600': '1小时',
'21600': '6小时',
'86400': '1天',
'259200': '3天',
'604800': '7天'
}
const [firstRender, setFirstRender] = useState(true)
useEffect(() => {
setFirstRender(false)
@ -63,7 +55,11 @@ const ComposePoll: React.FC = () => {
color: hasConflict ? theme.red : theme.primary
}
]}
placeholder={`选项`}
placeholder={
multiple
? t('content.root.footer.poll.option.placeholder.multiple')
: t('content.root.footer.poll.option.placeholder.single')
}
placeholderTextColor={theme.secondary}
maxLength={50}
// @ts-ignore
@ -110,12 +106,20 @@ const ComposePoll: React.FC = () => {
/>
</View>
<MenuRow
title='可选项'
content={multiple ? '多选' : '单选'}
title={t('content.root.footer.poll.multiple.heading')}
content={
multiple
? t('content.root.footer.poll.multiple.options.multiple')
: t('content.root.footer.poll.multiple.options.single')
}
onPress={() =>
showActionSheetWithOptions(
{
options: ['单选', '多选', '取消'],
options: [
t('content.root.footer.poll.multiple.options.single'),
t('content.root.footer.poll.multiple.options.multiple'),
t('content.root.footer.poll.multiple.options.cancel')
],
cancelButtonIndex: 2
},
index =>
@ -129,22 +133,36 @@ const ComposePoll: React.FC = () => {
iconBack='ChevronRight'
/>
<MenuRow
title='有效期'
content={expireMapping[expire]}
onPress={() =>
title={t('content.root.footer.poll.expiration.heading')}
content={t(`content.root.footer.poll.expiration.options.${expire}`)}
onPress={() => {
const expirations: [
'300',
'1800',
'3600',
'21600',
'86400',
'259200',
'604800'
] = ['300', '1800', '3600', '21600', '86400', '259200', '604800']
showActionSheetWithOptions(
{
options: [...Object.values(expireMapping), '取消'],
options: [
...expirations.map(e =>
t(`content.root.footer.poll.expiration.options.${e}`)
),
t('content.root.footer.poll.expiration.options.cancel')
],
cancelButtonIndex: 7
},
index =>
index < 7 &&
composeDispatch({
type: 'poll',
payload: { expire: Object.keys(expireMapping)[index] }
payload: { expire: expirations[index] }
})
)
}
}}
iconBack='ChevronRight'
/>
</View>

View File

@ -1,5 +1,5 @@
import { useTheme } from '@utils/styles/ThemeManager'
import ComposeContext from './utils/createContext'
import ComposeContext from '../../utils/createContext'
import React, { useContext } from 'react'
import { StyleSheet, View } from 'react-native'
import TimelineDefault from '@root/components/Timelines/Timeline/Default'
@ -12,12 +12,7 @@ const ComposeReply: React.FC = () => {
return (
<View style={[styles.base, { borderTopColor: theme.border }]}>
<TimelineDefault
item={replyToStatus!}
index={0}
disableDetails
disableOnPress
/>
<TimelineDefault item={replyToStatus!} disableDetails disableOnPress />
</View>
)
}

View File

@ -5,8 +5,9 @@ import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
import * as VideoThumbnails from 'expo-video-thumbnails'
import { Dispatch } from 'react'
import { Alert, Linking } from 'react-native'
import { ComposeAction } from './utils/types'
import { ComposeAction } from '../../utils/types'
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
import i18next from 'i18next'
export interface Props {
composeDispatch: Dispatch<ComposeAction>
@ -75,6 +76,27 @@ const addAttachment = async ({
break
}
const uploadFailed = () => {
composeDispatch({
type: 'attachment/upload/fail',
payload: hash
})
Alert.alert(
i18next.t(
'sharedCompose:content.root.actions.attachment.failed.alert.title'
),
undefined,
[
{
text: i18next.t(
'sharedCompose:content.root.actions.attachment.failed.alert.button'
),
onPress: () => {}
}
]
)
}
const formData = new FormData()
formData.append('file', {
// @ts-ignore
@ -96,35 +118,27 @@ const addAttachment = async ({
payload: { remote: res, local: result }
})
} else {
composeDispatch({
type: 'attachment/upload/fail',
payload: hash
})
Alert.alert('上传失败', '', [
{
text: '返回重试',
onPress: () => {}
}
])
uploadFailed()
}
})
.catch(() => {
composeDispatch({
type: 'attachment/upload/fail',
payload: hash
})
Alert.alert('上传失败', '', [
{
text: '返回重试',
onPress: () => {}
}
])
uploadFailed()
})
}
showActionSheetWithOptions(
{
options: ['从相册选取', '现照', '取消'],
options: [
i18next.t(
'sharedCompose:content.root.actions.attachment.actions.options.library'
),
i18next.t(
'sharedCompose:content.root.actions.attachment.actions.options.photo'
),
i18next.t(
'sharedCompose:content.root.actions.attachment.actions.options.cancel'
)
],
cancelButtonIndex: 2
},
async buttonIndex => {
@ -133,18 +147,30 @@ const addAttachment = async ({
status
} = await ImagePicker.requestMediaLibraryPermissionsAsync()
if (status !== 'granted') {
Alert.alert('🈚️读取权限', '需要相片权限才能上传照片', [
{
text: '取消',
style: 'cancel',
onPress: () => {}
},
{
text: '去系统设置',
style: 'default',
onPress: () => Linking.openURL('app-settings:')
}
])
Alert.alert(
i18next.t(
'sharedCompose:content.root.actions.attachment.actions.library.alert.title'
),
i18next.t(
'sharedCompose:content.root.actions.attachment.actions.library.alert.message'
),
[
{
text: i18next.t(
'sharedCompose:content.root.actions.attachment.actions.library.alert.buttons.cancel'
),
style: 'cancel',
onPress: () => {}
},
{
text: i18next.t(
'sharedCompose:content.root.actions.attachment.actions.library.alert.buttons.settings'
),
style: 'default',
onPress: () => Linking.openURL('app-settings:')
}
]
)
} else {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
@ -158,18 +184,30 @@ const addAttachment = async ({
} else if (buttonIndex === 1) {
const { status } = await ImagePicker.requestCameraPermissionsAsync()
if (status !== 'granted') {
Alert.alert('🈚️读取权限', '需要相机权限才能上传照片', [
{
text: '取消',
style: 'cancel',
onPress: () => {}
},
{
text: '去系统设置',
style: 'default',
onPress: () => Linking.openURL('app-settings:')
}
])
Alert.alert(
i18next.t(
'sharedCompose:content.root.actions.attachment.actions.photo.alert.title'
),
i18next.t(
'sharedCompose:content.root.actions.attachment.actions.photo.alert.message'
),
[
{
text: i18next.t(
'sharedCompose:content.root.actions.attachment.actions.photo.alert.buttons.cancel'
),
style: 'cancel',
onPress: () => {}
},
{
text: i18next.t(
'sharedCompose:content.root.actions.attachment.actions.photo.alert.buttons.settings'
),
style: 'default',
onPress: () => Linking.openURL('app-settings:')
}
]
)
} else {
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,

View File

@ -1,45 +1,15 @@
import { useAccountQuery } from '@utils/queryHooks/account'
import {
getLocalActiveIndex,
getLocalInstances,
InstanceLocal
getLocalInstances
} from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext } from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Chase } from 'react-native-animated-spinkit'
import { StyleSheet, View } from 'react-native'
import { useSelector } from 'react-redux'
import ComposeSpoilerInput from '../SpoilerInput'
import ComposeTextInput from '../TextInput'
import ComposeContext from '../utils/createContext'
const PostingAs: React.FC<{
id: Mastodon.Account['id']
domain: InstanceLocal['url']
}> = ({ id, domain }) => {
const { theme } = useTheme()
const { data, status } = useAccountQuery({ id })
switch (status) {
case 'loading':
return (
<Chase
size={StyleConstants.Font.LineHeight.M - 2}
color={theme.secondary}
/>
)
case 'success':
return (
<Text style={[styles.postingAsText, { color: theme.secondary }]}>
@{data?.acct}@{domain}
</Text>
)
default:
return null
}
}
import PostingAs from './Header/PostingAs'
import ComposeSpoilerInput from './Header/SpoilerInput'
import ComposeTextInput from './Header/TextInput'
const ComposeRootHeader: React.FC = () => {
const { composeState } = useContext(ComposeContext)
@ -68,9 +38,6 @@ const styles = StyleSheet.create({
postingAs: {
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
marginTop: StyleConstants.Spacing.S
},
postingAsText: {
...StyleConstants.FontStyle.S
}
})

View File

@ -0,0 +1,44 @@
import { useAccountQuery } from '@utils/queryHooks/account'
import { InstanceLocal } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text } from 'react-native'
import { Chase } from 'react-native-animated-spinkit'
const ComposePostingAs: React.FC<{
id: Mastodon.Account['id']
domain: InstanceLocal['url']
}> = ({ id, domain }) => {
const { t } = useTranslation('sharedCompose')
const { theme } = useTheme()
const { data, status } = useAccountQuery({ id })
switch (status) {
case 'loading':
return (
<Chase
size={StyleConstants.Font.LineHeight.M - 2}
color={theme.secondary}
/>
)
case 'success':
return (
<Text style={[styles.text, { color: theme.secondary }]}>
{t('content.root.header.postingAs', { acct: data?.acct, domain })}
</Text>
)
default:
return null
}
}
const styles = StyleSheet.create({
text: {
...StyleConstants.FontStyle.S
}
})
export default ComposePostingAs

View File

@ -1,12 +1,14 @@
import React, { useContext } from 'react'
import { StyleSheet, Text, TextInput } from 'react-native'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import formatText from './formatText'
import ComposeContext from './utils/createContext'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, TextInput } from 'react-native'
import formatText from '../../formatText'
import ComposeContext from '../../utils/createContext'
const ComposeSpoilerInput: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('sharedCompose')
const { theme } = useTheme()
return (
@ -23,7 +25,7 @@ const ComposeSpoilerInput: React.FC = () => {
autoFocus
enablesReturnKeyAutomatically
multiline
placeholder='折叠部分的警告信息'
placeholder={t('content.root.header.spoilerInput.placeholder')}
placeholderTextColor={theme.secondary}
onChangeText={content =>
formatText({

View File

@ -1,12 +1,14 @@
import React, { useContext } from 'react'
import { StyleSheet, Text, TextInput } from 'react-native'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import formatText from './formatText'
import ComposeContext from './utils/createContext'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, TextInput } from 'react-native'
import formatText from '../../formatText'
import ComposeContext from '../../utils/createContext'
const ComposeTextInput: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('sharedCompose')
const { theme } = useTheme()
return (
@ -23,7 +25,7 @@ const ComposeTextInput: React.FC = () => {
autoFocus
enablesReturnKeyAutomatically
multiline
placeholder='想说点什么'
placeholder={t('content.root.header.textInput.placeholder')}
placeholderTextColor={theme.secondary}
onChangeText={content =>
formatText({

View File

@ -1,6 +1,6 @@
import ComponentAccount from '@components/Account'
import haptics from '@components/haptics'
import ComponentHashtag from '@components/Timelines/Hashtag'
import ComponentHashtag from '@components/Hashtag'
import React, { Dispatch, useCallback } from 'react'
import updateText from '../updateText'
import { ComposeAction, ComposeState } from '../utils/types'

View File

@ -39,15 +39,7 @@ export type ComposeState = {
'3': string | undefined
}
multiple: boolean
expire:
| '300'
| '1800'
| '3600'
| '21600'
| '86400'
| '259200'
| '604800'
| string
expire: '300' | '1800' | '3600' | '21600' | '86400' | '259200' | '604800'
}
attachments: {
sensitive: boolean

View File

@ -2,6 +2,7 @@ import SegmentedControl from '@react-native-community/segmented-control'
import { useNavigation } from '@react-navigation/native'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, StyleSheet, View } from 'react-native'
import { TabView } from 'react-native-tab-view'
import RelationshipsList from './Relationships/List'
@ -12,6 +13,7 @@ const ScreenSharedRelationships: React.FC<SharedRelationshipsProp> = ({
params: { account, initialType }
}
}) => {
const { t } = useTranslation('sharedRelationships')
const { mode } = useTheme()
const navigation = useNavigation()
@ -23,7 +25,7 @@ const ScreenSharedRelationships: React.FC<SharedRelationshipsProp> = ({
<View style={styles.segmentsContainer}>
<SegmentedControl
appearance={mode}
values={['关注中', '关注者']}
values={[t('heading.segments.left'), t('heading.segments.right')]}
selectedIndex={segment}
onChange={({ nativeEvent }) =>
setSegment(nativeEvent.selectedSegmentIndex)

View File

@ -2,7 +2,7 @@ import ComponentAccount from '@components/Account'
import ComponentSeparator from '@components/Separator'
import TimelineEmpty from '@components/Timelines/Timeline/Empty'
import TimelineEnd from '@components/Timelines/Timeline/End'
import { useScrollToTop } from '@react-navigation/native'
import { useNavigation, useScrollToTop } from '@react-navigation/native'
import { useRelationshipsQuery } from '@utils/queryHooks/relationships'
import React, { useCallback, useMemo, useRef } from 'react'
import { RefreshControl, StyleSheet } from 'react-native'
@ -14,6 +14,7 @@ export interface Props {
}
const RelationshipsList: React.FC<Props> = ({ id, type }) => {
const navigation = useNavigation()
const {
status,
data,
@ -42,7 +43,14 @@ const RelationshipsList: React.FC<Props> = ({ id, type }) => {
const keyExtractor = useCallback(({ id }) => id, [])
const renderItem = useCallback(
({ item }) => <ComponentAccount account={item} />,
({ item }) => (
<ComponentAccount
account={item}
onPress={() =>
navigation.push('Screen-Shared-Account', { account: item })
}
/>
),
[]
)
const flItemEmptyComponent = useMemo(

View File

@ -1,12 +1,13 @@
import ComponentHashtag from '@components/Timelines/Hashtag'
import ComponentAccount from '@components/Account'
import ComponentHashtag from '@components/Hashtag'
import ComponentSeparator from '@components/Separator'
import TimelineDefault from '@components/Timelines/Timeline/Default'
import { useNavigation } from '@react-navigation/native'
import ComponentAccount from '@root/components/Account'
import ComponentSeparator from '@root/components/Separator'
import TimelineDefault from '@root/components/Timelines/Timeline/Default'
import { useSearchQuery } from '@utils/queryHooks/search'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import {
KeyboardAvoidingView,
Platform,
@ -22,6 +23,7 @@ export interface Props {
}
const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
const { t } = useTranslation('sharedSearch')
const navigation = useNavigation()
const { theme } = useTheme()
const { status, data, refetch } = useSearchQuery({
@ -32,6 +34,11 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
const [setctionData, setSectionData] = useState<
{ title: string; data: any }[]
>([])
const mapKeyToTranslations = {
accounts: t('content.sections.accounts'),
hashtags: t('content.sections.hashtags'),
statuses: t('content.sections.statuses')
}
useEffect(
() =>
data &&
@ -40,6 +47,8 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
.map(key => ({
title: key,
// @ts-ignore
translation: mapKeyToTranslations[key],
// @ts-ignore
data: data[key]
}))
.sort((a, b) => {
@ -63,8 +72,8 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
}
}, [searchTerm])
const listEmpty = useMemo(
() => (
const listEmpty = useMemo(() => {
return (
<View style={styles.emptyBase}>
<View>
{status === 'loading' ? (
@ -83,67 +92,70 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
{ color: theme.primary }
]}
>
<Text style={styles.emptyFontBold}></Text>
<Text style={styles.emptyFontBold}></Text>
<Text style={styles.emptyFontBold}></Text>
<Trans
i18nKey='sharedSearch:content.empty.general'
components={{ bold: <Text style={styles.emptyFontBold} /> }}
/>
</Text>
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
{t('content.empty.advanced.header')}
</Text>
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
<Text style={{ color: theme.secondary }}>@username@domain</Text>
{' '}
{t('content.empty.advanced.example.account')}
</Text>
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
<Text style={{ color: theme.secondary }}>#example</Text>
{' '}
{' '}
{t('content.empty.advanced.example.hashtag')}
</Text>
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
<Text style={{ color: theme.secondary }}>URL</Text>
{' '}
{' '}
{t('content.empty.advanced.example.statusLink')}
</Text>
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
<Text style={{ color: theme.secondary }}>URL</Text>
{' '}
{' '}
{t('content.empty.advanced.example.accountLink')}
</Text>
</>
)}
</View>
</View>
),
[status]
)
)
}, [status])
const sectionHeader = useCallback(
({ section: { title } }) => (
({ section: { translation } }) => (
<View
style={[styles.sectionHeader, { backgroundColor: theme.background }]}
>
<Text style={[styles.sectionHeaderText, { color: theme.primary }]}>
{title}
{translation}
</Text>
</View>
),
[]
)
const sectionFooter = useCallback(
({ section: { data, title } }) =>
({ section: { data, translation } }) =>
!data.length ? (
<View
style={[styles.sectionFooter, { backgroundColor: theme.background }]}
>
<Text style={[styles.sectionFooterText, { color: theme.secondary }]}>
{' '}
<Text style={{ fontWeight: StyleConstants.Font.Weight.Bold }}>
{searchTerm}
</Text>{' '}
{title}
<Trans
i18nKey='sharedSearch:content.notFound'
values={{ searchTerm, type: translation }}
components={{ bold: <Text style={styles.emptyFontBold} /> }}
/>
</Text>
</View>
) : null,
[searchTerm]
)
const listItem = useCallback(({ item, section, index }) => {
const listItem = useCallback(({ item, section }) => {
switch (section.title) {
case 'accounts':
return (

View File

@ -1,4 +1,4 @@
import { HeaderLeft } from '@components/Header'
import { HeaderCenter, HeaderLeft } from '@components/Header'
import { ParseEmojis } from '@components/Parse'
import { StackNavigationState, TypedNavigator } from '@react-navigation/native'
import { StackScreenProps } from '@react-navigation/stack'
@ -15,7 +15,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
import { debounce } from 'lodash'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { Platform, StyleSheet, Text, View } from 'react-native'
import { TextInput } from 'react-native-gesture-handler'
import { NativeStackNavigationOptions } from 'react-native-screens/lib/typescript'
import {
@ -211,7 +211,7 @@ const sharedScreens = (
color: theme.primary
}
]}
children='搜索'
children={t('sharedSearch:content.header.prefix')}
/>
}
/>
@ -233,7 +233,7 @@ const sharedScreens = (
onSubmitEditing={({ nativeEvent: { text } }) =>
setSearchTerm(text)
}
placeholder={'些什么'}
placeholder={t('sharedSearch:content.header.placeholder')}
placeholderTextColor={theme.secondary}
returnKeyType='go'
/>
@ -248,7 +248,10 @@ const sharedScreens = (
name='Screen-Shared-Toot'
component={ScreenSharedToot}
options={({ navigation }: SharedTootProp) => ({
title: t('sharedToot:heading'),
headerTitle: t('sharedToot:heading'),
...(Platform.OS === 'android' && {
headerCenter: () => <HeaderCenter content={t('sharedToot:heading')} />
}),
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
})}
/>