1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Merge branch 'main' into release

This commit is contained in:
xmflsct
2022-12-20 00:46:57 +01:00
190 changed files with 2418 additions and 1274 deletions

View File

@ -0,0 +1 @@
../../it/description.txt

View File

@ -0,0 +1 @@
../../it/subtitle.txt

View File

@ -1,5 +1,10 @@
tooot is an open source, simple yet elegant Mastodon mobile client. tooot is an open source, simple yet elegant Mastodon mobile client. A Mastodon (https://joinmastodon.org/) account is required to use this app.
A Mastodon (https://joinmastodon.org/) account is required to use this app. tooot supports:
- Cross platform, including iPadOS and MacOS
- Multiple accounts
- Dark mode or adapt to system
- Adjustable toot font size
- Push notification
If you have suggestions, please reach out to @tooot@xmflsct.com or support@tooot.ap. If you have suggestions, please reach out to @tooot@xmflsct.com or support@tooot.app.

View File

@ -1,10 +1,5 @@
Enjoy toooting! This version includes following improvements and fixes: Enjoy toooting! This version includes following improvements and fixes:
- Added Ukrainian (Slava Ukraini) - Align filter experience with v4.0 and above
- Automatic setting detected language when tooting - Supports enlarging user's avatar and banner
- Remember public timeline type selection - Fix iPad weird sizing (not optimisation)
- Show diffing of edit history - Experiment (!) support of Pleroma
- Allow hiding boosts and replies in home timeline
- Support toot in RTL languages
- Added notification for admins
- Fix whole word filter matching
- Fix tablet cannot delete toot drafts

View File

@ -0,0 +1,10 @@
tooot è un client Mastodon semplice e open source. Per utilizzare questo client, devi disporre di un account Mastodon. (https://joinmastodon.org/).
Tooot supporta:
- Multipiattaforma, inclusi iPadOS e MacOS
- Accesso a più account
- Modalità scura o adattiva
- Dimensione del carattere del testo regolabile
- Notifiche push e altre funzioni
Per suggerimenti o commenti sull'utilizzo, contattare @tooot@xmflsct.com o support@tooot.app.

View File

@ -0,0 +1 @@
Client open source per Mastodon

View File

@ -1,11 +1,10 @@
tooot是一个专门为中文用户社区所打造的开源、简洁长毛象客户端。使用此客户端需要已经拥有一个长毛象https://joinmastodon.org/)账号。 tooot起始于专注中文社区的简洁、开源长毛象手机客户端。使用此客户端需要已经拥有一个长毛象https://joinmastodon.org/)账号。
tooot支持 tooot支持
- iPad - 跨平台及iPadOS、MacOS
- 多账号登录 - 多账号登录
- 黑暗或自适应模式 - 黑暗或自适应模式
- 可调正文字体大小 - 可调正文字体尺寸
- 消息推送 - 消息推送
等功能。
如有使用建议或意见,请联系@tooot@xmflsct.com或者support@tooot.app。 如有使用建议或意见,请联系@tooot@xmflsct.com或者support@tooot.app。

View File

@ -1,10 +1,5 @@
toooting愉快此版本包括以下改进和修复 toooting愉快此版本包括以下改进和修复
- 增加乌克兰语Slava Ukraini - 改进过滤体验与v4.0以上版本一致
- 自动识别发嘟语言 - 支持查看用户的头像和横幅图片
- 记住上次公共时间轴选项 - 修复iPad部分尺寸问题非优化
- 显示编辑历史的差异 - 试验性支持Pleroma
- 关注列表可隐藏转嘟和回复
- 新增管理员推送通知
- 支持嘟文右到左文字
- 修复过滤整词功能
- 修复平板不能删除草稿

View File

@ -1,6 +1,6 @@
{ {
"name": "tooot", "name": "tooot",
"version": "4.7.0", "version": "4.7.1",
"description": "tooot for Mastodon", "description": "tooot for Mastodon",
"author": "xmflsct <me@xmflsct.com>", "author": "xmflsct <me@xmflsct.com>",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",

View File

@ -263,7 +263,8 @@ declare namespace Mastodon {
verified_at: string | null verified_at: string | null
} }
type Filter = { type Filter<T extends 'v1' | 'v2'> = T extends 'v2' ? Filter_V2 : Filter_V1
type Filter_V1 = {
id: string id: string
phrase: string phrase: string
context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[] context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[]
@ -271,6 +272,25 @@ declare namespace Mastodon {
irreversible: boolean irreversible: boolean
whole_word: boolean whole_word: boolean
} }
type Filter_V2 = {
id: string
title: string
context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[]
expires_at?: string
filter_action: 'warn' | 'hide'
keywords: FilterKeyword[]
statuses: FilterStatus[]
}
type FilterKeyword = { id: string; keyword: string; whole_word: boolean }
type FilterStatus = { id: string; status_id: string }
type FilterResult = {
filter: Filter<'v2'>
keyword_matches?: FilterKeyword['keyword'][]
status_matches?: FilterStatus['id'][]
}
type List = { type List = {
id: string id: string
@ -406,6 +426,8 @@ declare namespace Mastodon {
id: string id: string
following: boolean following: boolean
showing_reblogs: boolean showing_reblogs: boolean
notifying?: boolean
languages?: string[]
followed_by: boolean followed_by: boolean
blocking: boolean blocking: boolean
blocked_by: boolean blocked_by: boolean
@ -459,7 +481,7 @@ declare namespace Mastodon {
sensitive: boolean sensitive: boolean
spoiler_text?: string spoiler_text?: string
media_attachments: Attachment[] media_attachments: Attachment[]
application: Application application?: Application
// Attributes // Attributes
mentions: Mention[] mentions: Mention[]
@ -470,7 +492,7 @@ declare namespace Mastodon {
reblogs_count: number reblogs_count: number
favourites_count: number favourites_count: number
replies_count: number replies_count: number
edited_at?: string // FEATURE edit_post edited_at?: string
favourited: boolean favourited: boolean
reblogged: boolean reblogged: boolean
muted: boolean muted: boolean
@ -486,6 +508,7 @@ declare namespace Mastodon {
card?: Card card?: Card
language?: string language?: string
text?: string text?: string
filtered?: FilterResult[]
} }
type StatusHistory = { type StatusHistory = {

View File

@ -56,7 +56,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
useEffect(() => { useEffect(() => {
const screenshotListener = addScreenshotListener(() => const screenshotListener = addScreenshotListener(() =>
Alert.alert(t('screenshot.title'), t('screenshot.message'), [ Alert.alert(t('screenshot.title'), t('screenshot.message'), [
{ text: t('screenshot.button'), style: 'destructive' } { text: t('common:buttons.confirm'), style: 'destructive' }
]) ])
) )
Platform.select({ ios: screenshotListener }) Platform.select({ ios: screenshotListener })

View File

@ -53,7 +53,7 @@ const ComponentHashtag: React.FC<PropsWithChildren & Props> = ({
#{hashtag.name} #{hashtag.name}
</CustomText> </CustomText>
<View <View
style={{ flexDirection: 'row', alignItems: 'center' }} style={{ flexDirection: 'row', alignItems: 'center', alignSelf: 'stretch' }}
onLayout={({ onLayout={({
nativeEvent: { nativeEvent: {
layout: { height } layout: { height }
@ -61,7 +61,7 @@ const ComponentHashtag: React.FC<PropsWithChildren & Props> = ({
}) => setHeight(height)} }) => setHeight(height)}
> >
<Sparkline <Sparkline
data={hashtag.history.map(h => parseInt(h.uses)).reverse()} data={hashtag.history?.map(h => parseInt(h.uses)).reverse()}
width={width} width={width}
height={height} height={height}
margin={children ? StyleConstants.Spacing.S : undefined} margin={children ? StyleConstants.Spacing.S : undefined}

View File

@ -15,6 +15,7 @@ import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'r
import { ScrollView } from 'react-native-gesture-handler' import { ScrollView } from 'react-native-gesture-handler'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { Placeholder } from 'rn-placeholder' import { Placeholder } from 'rn-placeholder'
import validUrl from 'valid-url'
import InstanceInfo from './Info' import InstanceInfo from './Info'
import CustomText from '../Text' import CustomText from '../Text'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
@ -39,12 +40,26 @@ const ComponentInstance: React.FC<Props> = ({
const navigation = useNavigation<TabMeStackNavigationProp<'Tab-Me-Root' | 'Tab-Me-Switch'>>() const navigation = useNavigation<TabMeStackNavigationProp<'Tab-Me-Root' | 'Tab-Me-Switch'>>()
const [domain, setDomain] = useState<string>('') const [domain, setDomain] = useState<string>('')
const [errorCode, setErrorCode] = useState<number | null>(null)
const whitelisted: boolean =
!!domain.length &&
!!errorCode &&
!!validUrl.isHttpsUri(`https://${domain}`) &&
errorCode === 401
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const instances = useSelector(getInstances, () => true) const instances = useSelector(getInstances, () => true)
const instanceQuery = useInstanceQuery({ const instanceQuery = useInstanceQuery({
domain, domain,
options: { enabled: !!domain, retry: false } options: {
enabled: !!domain,
retry: false,
onError: err => {
if (err.status) {
setErrorCode(err.status)
}
}
}
}) })
const deprecateAuthFollow = useSelector(checkInstanceFeature('deprecate_auth_follow')) const deprecateAuthFollow = useSelector(checkInstanceFeature('deprecate_auth_follow'))
@ -146,7 +161,11 @@ const ComponentInstance: React.FC<Props> = ({
borderBottomWidth: 1, borderBottomWidth: 1,
...StyleConstants.FontStyle.M, ...StyleConstants.FontStyle.M,
color: colors.primaryDefault, color: colors.primaryDefault,
borderBottomColor: instanceQuery.isError ? colors.red : colors.border, borderBottomColor: instanceQuery.isError
? whitelisted
? colors.yellow
: colors.red
: colors.border,
...(Platform.OS === 'android' && { paddingRight: 0 }) ...(Platform.OS === 'android' && { paddingRight: 0 })
}} }}
editable={false} editable={false}
@ -159,12 +178,23 @@ const ComponentInstance: React.FC<Props> = ({
...StyleConstants.FontStyle.M, ...StyleConstants.FontStyle.M,
marginRight: StyleConstants.Spacing.M, marginRight: StyleConstants.Spacing.M,
color: colors.primaryDefault, color: colors.primaryDefault,
borderBottomColor: instanceQuery.isError ? colors.red : colors.border, borderBottomColor: instanceQuery.isError
? whitelisted
? colors.yellow
: colors.red
: colors.border,
...(Platform.OS === 'android' && { paddingLeft: 0 }) ...(Platform.OS === 'android' && { paddingLeft: 0 })
}} }}
onChangeText={debounce(text => setDomain(text.replace(/^http(s)?\:\/\//i, '')), 1000, { onChangeText={debounce(
text => {
setDomain(text.replace(/^http(s)?\:\/\//i, ''))
setErrorCode(null)
},
1000,
{
trailing: true trailing: true
})} }
)}
autoCapitalize='none' autoCapitalize='none'
clearButtonMode='never' clearButtonMode='never'
keyboardType='url' keyboardType='url'
@ -194,12 +224,24 @@ const ComponentInstance: React.FC<Props> = ({
type='text' type='text'
content={t('server.button')} content={t('server.button')}
onPress={processUpdate} onPress={processUpdate}
disabled={!instanceQuery.data?.uri} disabled={!instanceQuery.data?.uri && !whitelisted}
loading={instanceQuery.isFetching || appsMutation.isLoading} loading={instanceQuery.isFetching || appsMutation.isLoading}
/> />
</View> </View>
<View> <View>
{whitelisted ? (
<CustomText
fontStyle='S'
style={{
color: colors.yellow,
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
paddingTop: StyleConstants.Spacing.XS
}}
>
{t('server.whitelisted')}
</CustomText>
) : (
<Placeholder> <Placeholder>
<InstanceInfo <InstanceInfo
header={t('server.information.name')} header={t('server.information.name')}
@ -227,6 +269,7 @@ const ComponentInstance: React.FC<Props> = ({
/> />
</View> </View>
</Placeholder> </Placeholder>
)}
<View <View
style={{ style={{
flexDirection: 'row', flexDirection: 'row',

View File

@ -4,8 +4,8 @@ import { getSettingsFontsize } from '@utils/slices/settingsSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { adaptiveScale } from '@utils/styles/scaling' import { adaptiveScale } from '@utils/styles/scaling'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useMemo } from 'react' import React from 'react'
import { Platform, StyleSheet, TextStyle } from 'react-native' import { Platform, TextStyle } from 'react-native'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import validUrl from 'valid-url' import validUrl from 'valid-url'
@ -36,23 +36,19 @@ const ParseEmojis = React.memo(
) )
const { colors, theme } = useTheme() const { colors, theme } = useTheme()
const styles = useMemo(() => {
return StyleSheet.create({ return (
text: { <CustomText
style={[
{
color: colors.primaryDefault, color: colors.primaryDefault,
fontSize: adaptedFontsize, fontSize: adaptedFontsize,
lineHeight: adaptedLineheight lineHeight: adaptedLineheight
}, },
image: { style
width: adaptedFontsize, ]}
height: adaptedFontsize, fontWeight={fontBold ? 'Bold' : undefined}
...(Platform.OS === 'android' && { transform: [{ translateY: 2 }] }) >
}
})
}, [theme, adaptiveFontsize])
return (
<CustomText style={[styles.text, style]} fontWeight={fontBold ? 'Bold' : undefined}>
{emojis ? ( {emojis ? (
content content
.split(regexEmoji) .split(regexEmoji)
@ -73,7 +69,14 @@ const ParseEmojis = React.memo(
return ( return (
<CustomText key={emojiShortcode + i}> <CustomText key={emojiShortcode + i}>
{i === 0 ? ' ' : undefined} {i === 0 ? ' ' : undefined}
<FastImage source={{ uri }} style={styles.image} /> <FastImage
source={{ uri }}
style={{
width: adaptedFontsize,
height: adaptedFontsize,
transform: [{ translateY: Platform.OS === 'ios' ? -1 : 2 }]
}}
/>
</CustomText> </CustomText>
) )
} else { } else {

View File

@ -102,7 +102,7 @@ const renderNode = ({
) )
} }
} else { } else {
const domain = href?.split(new RegExp(/:\/\/(.[^\/]+)/)) const domain = href?.split(new RegExp(/:\/\/(.[^\/]+\/.{3})/))
// Need example here // Need example here
const content = node.children && node.children[0] && node.children[0].data const content = node.children && node.children[0] && node.children[0].data
const shouldBeTag = tags && tags.filter(tag => `#${tag.name}` === content).length > 0 const shouldBeTag = tags && tags.filter(tag => `#${tag.name}` === content).length > 0
@ -128,17 +128,7 @@ const renderNode = ({
}} }}
> >
{content && content !== href ? content : showFullLink ? href : domain?.[1]} {content && content !== href ? content : showFullLink ? href : domain?.[1]}
{!shouldBeTag ? ( {!shouldBeTag ? '...' : null}
<Icon
color={colors.blue}
name='ExternalLink'
size={adaptedFontsize}
style={{
marginLeft: StyleConstants.Spacing.XS,
...(Platform.OS === 'android' && { transform: [{ translateY: 2 }] })
}}
/>
) : null}
</CustomText> </CustomText>
) )
} }

View File

@ -6,21 +6,24 @@ import {
useRelationshipMutation, useRelationshipMutation,
useRelationshipQuery useRelationshipQuery
} from '@utils/queryHooks/relationship' } from '@utils/queryHooks/relationship'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { useSelector } from 'react-redux'
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
export interface Props { export interface Props {
id: Mastodon.Account['id'] id: Mastodon.Account['id']
} }
const RelationshipOutgoing = React.memo( const RelationshipOutgoing: React.FC<Props> = ({ id }: Props) => {
({ id }: Props) => {
const { theme } = useTheme() const { theme } = useTheme()
const { t } = useTranslation('componentRelationship') const { t } = useTranslation('componentRelationship')
const canFollowNotify = useSelector(checkInstanceFeature('account_follow_notify'))
const query = useRelationshipQuery({ id }) const query = useRelationshipQuery({ id })
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }] const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
@ -120,6 +123,27 @@ const RelationshipOutgoing = React.memo(
} }
return ( return (
<>
{canFollowNotify && query.data?.following ? (
<Button
type='icon'
content={query.data.notifying ? 'BellOff' : 'Bell'}
round
onPress={() =>
mutation.mutate({
id,
type: 'outgoing',
payload: {
action: 'follow',
state: false,
notify: !query.data.notifying
}
})
}
loading={query.isLoading || mutation.isLoading}
style={{ marginRight: StyleConstants.Spacing.S }}
/>
) : null}
<Button <Button
type='text' type='text'
content={content} content={content}
@ -127,9 +151,8 @@ const RelationshipOutgoing = React.memo(
loading={query.isLoading || mutation.isLoading} loading={query.isLoading || mutation.isLoading}
disabled={query.isError || query.data?.blocked_by} disabled={query.isError || query.data?.blocked_by}
/> />
</>
) )
}, }
() => true
)
export default RelationshipOutgoing export default RelationshipOutgoing

View File

@ -98,7 +98,6 @@ const TimelineConversation: React.FC<Props> = ({ conversation, queryKey, highlig
</View> </View>
{conversation.last_status ? ( {conversation.last_status ? (
<>
<View <View
style={{ style={{
paddingTop: highlighted ? StyleConstants.Spacing.S : 0, paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
@ -107,10 +106,9 @@ const TimelineConversation: React.FC<Props> = ({ conversation, queryKey, highlig
> >
<TimelineContent /> <TimelineContent />
<TimelinePoll /> <TimelinePoll />
</View>
<TimelineActions /> <TimelineActions />
</> </View>
) : null} ) : null}
</Pressable> </Pressable>
</StatusContext.Provider> </StatusContext.Provider>

View File

@ -9,11 +9,12 @@ import TimelineCard from '@components/Timeline/Shared/Card'
import TimelineContent from '@components/Timeline/Shared/Content' import TimelineContent from '@components/Timeline/Shared/Content'
import TimelineHeaderDefault from '@components/Timeline/Shared/HeaderDefault' import TimelineHeaderDefault from '@components/Timeline/Shared/HeaderDefault'
import TimelinePoll from '@components/Timeline/Shared/Poll' import TimelinePoll from '@components/Timeline/Shared/Poll'
import removeHTML from '@helpers/removeHTML'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack' import { StackNavigationProp } from '@react-navigation/stack'
import { TabLocalStackParamList } from '@utils/navigation/navigators' import { TabLocalStackParamList } from '@utils/navigation/navigators'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { getInstanceAccount } from '@utils/slices/instancesSlice' import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useRef, useState } from 'react' import React, { useRef, useState } from 'react'
@ -22,7 +23,7 @@ import { useSelector } from 'react-redux'
import * as ContextMenu from 'zeego/context-menu' import * as ContextMenu from 'zeego/context-menu'
import StatusContext from './Shared/Context' import StatusContext from './Shared/Context'
import TimelineFeedback from './Shared/Feedback' import TimelineFeedback from './Shared/Feedback'
import TimelineFiltered, { shouldFilter } from './Shared/Filtered' import TimelineFiltered, { FilteredProps, shouldFilter } from './Shared/Filtered'
import TimelineFullConversation from './Shared/FullConversation' import TimelineFullConversation from './Shared/FullConversation'
import TimelineHeaderAndroid from './Shared/HeaderAndroid' import TimelineHeaderAndroid from './Shared/HeaderAndroid'
import TimelineTranslate from './Shared/Translate' import TimelineTranslate from './Shared/Translate'
@ -47,12 +48,20 @@ const TimelineDefault: React.FC<Props> = ({
disableOnPress = false, disableOnPress = false,
isConversation = false isConversation = false
}) => { }) => {
const status = item.reblog ? item.reblog : item
const rawContent = useRef<string[]>([])
if (highlighted) {
rawContent.current = [
removeHTML(status.content),
status.spoiler_text ? removeHTML(status.spoiler_text) : ''
].filter(c => c.length)
}
const { colors } = useTheme() const { colors } = useTheme()
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>() const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
const instanceAccount = useSelector(getInstanceAccount, () => true) const instanceAccount = useSelector(getInstanceAccount, () => true)
const status = item.reblog ? item.reblog : item
const ownAccount = status.account?.id === instanceAccount?.id const ownAccount = status.account?.id === instanceAccount?.id
const [spoilerExpanded, setSpoilerExpanded] = useState( const [spoilerExpanded, setSpoilerExpanded] = useState(
instanceAccount?.preferences?.['reading:expand:spoilers'] || false instanceAccount?.preferences?.['reading:expand:spoilers'] || false
@ -60,15 +69,7 @@ const TimelineDefault: React.FC<Props> = ({
const spoilerHidden = status.spoiler_text?.length const spoilerHidden = status.spoiler_text?.length
? !instanceAccount?.preferences?.['reading:expand:spoilers'] && !spoilerExpanded ? !instanceAccount?.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
: false : false
const copiableContent = useRef<{ content: string; complete: boolean }>({ const detectedLanguage = useRef<string>(status.language || '')
content: '',
complete: false
})
const filtered = queryKey && shouldFilter({ copiableContent, status, queryKey })
if (queryKey && filtered && !highlighted) {
return <TimelineFiltered phrase={filtered} />
}
const mainStyle: StyleProp<ViewStyle> = { const mainStyle: StyleProp<ViewStyle> = {
flex: 1, flex: 1,
@ -102,8 +103,9 @@ const TimelineDefault: React.FC<Props> = ({
paddingTop: highlighted ? StyleConstants.Spacing.S : 0, paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
paddingLeft: highlighted paddingLeft: highlighted
? 0 ? 0
: (disableDetails ? StyleConstants.Avatar.XS : StyleConstants.Avatar.M) + : (disableDetails || isConversation
StyleConstants.Spacing.S, ? StyleConstants.Avatar.XS
: StyleConstants.Avatar.M) + StyleConstants.Spacing.S,
...(disableDetails && { marginTop: -StyleConstants.Spacing.S }) ...(disableDetails && { marginTop: -StyleConstants.Spacing.S })
}} }}
> >
@ -114,9 +116,9 @@ const TimelineDefault: React.FC<Props> = ({
<TimelineFullConversation /> <TimelineFullConversation />
<TimelineTranslate /> <TimelineTranslate />
<TimelineFeedback /> <TimelineFeedback />
</View>
<TimelineActions /> <TimelineActions />
</View>
</> </>
) )
@ -124,11 +126,36 @@ const TimelineDefault: React.FC<Props> = ({
visibility: status.visibility, visibility: status.visibility,
type: 'status', type: 'status',
url: status.url || status.uri, url: status.url || status.uri,
copiableContent rawContent
}) })
const mStatus = menuStatus({ status, queryKey, rootQueryKey }) const mStatus = menuStatus({ status, queryKey, rootQueryKey })
const mInstance = menuInstance({ status, queryKey, rootQueryKey }) const mInstance = menuInstance({ status, queryKey, rootQueryKey })
if (!ownAccount) {
let filterResults: FilteredProps['filterResults'] = []
const [filterRevealed, setFilterRevealed] = useState(false)
const hasFilterServerSide = useSelector(checkInstanceFeature('filter_server_side'))
if (hasFilterServerSide) {
if (status.filtered?.length) {
filterResults = status.filtered?.map(filter => filter.filter)
}
} else {
if (queryKey) {
const checkFilter = shouldFilter({ queryKey, status })
if (checkFilter?.length) {
filterResults = checkFilter
}
}
}
if (queryKey && !highlighted && filterResults?.length && !filterRevealed) {
return !filterResults.filter(result => result.filter_action === 'hide').length ? (
<Pressable onPress={() => setFilterRevealed(!filterRevealed)}>
<TimelineFiltered filterResults={filterResults} />
</Pressable>
) : null
}
}
return ( return (
<StatusContext.Provider <StatusContext.Provider
value={{ value={{
@ -138,7 +165,8 @@ const TimelineDefault: React.FC<Props> = ({
reblogStatus: item.reblog ? item : undefined, reblogStatus: item.reblog ? item : undefined,
ownAccount, ownAccount,
spoilerHidden, spoilerHidden,
copiableContent, rawContent,
detectedLanguage,
highlighted, highlighted,
inThread: queryKey?.[1].page === 'Toot', inThread: queryKey?.[1].page === 'Toot',
disableDetails, disableDetails,

View File

@ -13,7 +13,7 @@ import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack' import { StackNavigationProp } from '@react-navigation/stack'
import { TabLocalStackParamList } from '@utils/navigation/navigators' import { TabLocalStackParamList } from '@utils/navigation/navigators'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { getInstanceAccount } from '@utils/slices/instancesSlice' import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useRef, useState } from 'react' import React, { useCallback, useRef, useState } from 'react'
@ -21,7 +21,7 @@ import { Pressable, View } from 'react-native'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import * as ContextMenu from 'zeego/context-menu' import * as ContextMenu from 'zeego/context-menu'
import StatusContext from './Shared/Context' import StatusContext from './Shared/Context'
import TimelineFiltered, { shouldFilter } from './Shared/Filtered' import TimelineFiltered, { FilteredProps, shouldFilter } from './Shared/Filtered'
import TimelineFullConversation from './Shared/FullConversation' import TimelineFullConversation from './Shared/FullConversation'
import TimelineHeaderAndroid from './Shared/HeaderAndroid' import TimelineHeaderAndroid from './Shared/HeaderAndroid'
@ -47,21 +47,6 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
const spoilerHidden = notification.status?.spoiler_text?.length const spoilerHidden = notification.status?.spoiler_text?.length
? !instanceAccount.preferences?.['reading:expand:spoilers'] && !spoilerExpanded ? !instanceAccount.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
: false : false
const copiableContent = useRef<{ content: string; complete: boolean }>({
content: '',
complete: false
})
const filtered =
notification.status &&
shouldFilter({
copiableContent,
status: notification.status,
queryKey
})
if (notification.status && filtered) {
return <TimelineFiltered phrase={filtered} />
}
const { colors } = useTheme() const { colors } = useTheme()
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>() const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
@ -112,11 +97,11 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
<TimelineAttachment /> <TimelineAttachment />
<TimelineCard /> <TimelineCard />
<TimelineFullConversation /> <TimelineFullConversation />
<TimelineActions />
</View> </View>
) : null} ) : null}
</View> </View>
<TimelineActions />
</> </>
) )
} }
@ -124,20 +109,44 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
const mShare = menuShare({ const mShare = menuShare({
visibility: notification.status?.visibility, visibility: notification.status?.visibility,
type: 'status', type: 'status',
url: notification.status?.url || notification.status?.uri, url: notification.status?.url || notification.status?.uri
copiableContent
}) })
const mStatus = menuStatus({ status: notification.status, queryKey }) const mStatus = menuStatus({ status: notification.status, queryKey })
const mInstance = menuInstance({ status: notification.status, queryKey }) const mInstance = menuInstance({ status: notification.status, queryKey })
if (!ownAccount) {
let filterResults: FilteredProps['filterResults'] = []
const [filterRevealed, setFilterRevealed] = useState(false)
const hasFilterServerSide = useSelector(checkInstanceFeature('filter_server_side'))
if (notification.status) {
if (hasFilterServerSide) {
if (notification.status.filtered?.length) {
filterResults = notification.status.filtered.map(filter => filter.filter)
}
} else {
const checkFilter = shouldFilter({ queryKey, status: notification.status })
if (checkFilter?.length) {
filterResults = checkFilter
}
}
if (filterResults?.length && !filterRevealed) {
return !filterResults.filter(result => result.filter_action === 'hide').length ? (
<Pressable onPress={() => setFilterRevealed(!filterRevealed)}>
<TimelineFiltered filterResults={filterResults} />
</Pressable>
) : null
}
}
}
return ( return (
<StatusContext.Provider <StatusContext.Provider
value={{ value={{
queryKey, queryKey,
status, status,
ownAccount, ownAccount,
spoilerHidden, spoilerHidden
copiableContent
}} }}
> >
<ContextMenu.Root> <ContextMenu.Root>

View File

@ -55,7 +55,7 @@ const TimelineRefresh: React.FC<Props> = ({
firstPage?.links?.prev && { firstPage?.links?.prev && {
...(firstPage.links.prev.isOffset ...(firstPage.links.prev.isOffset
? { offset: firstPage.links.prev.id } ? { offset: firstPage.links.prev.id }
: { max_id: firstPage.links.prev.id }), : { min_id: firstPage.links.prev.id }),
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372 // https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
limit: '3' limit: '3'
}, },

View File

@ -2,6 +2,7 @@ import Icon from '@components/Icon'
import { displayMessage } from '@components/Message' import { displayMessage } from '@components/Message'
import CustomText from '@components/Text' import CustomText from '@components/Text'
import { useActionSheet } from '@expo/react-native-action-sheet' import { useActionSheet } from '@expo/react-native-action-sheet'
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack' import { StackNavigationProp } from '@react-navigation/stack'
import { RootStackParamList } from '@utils/navigation/navigators' import { RootStackParamList } from '@utils/navigation/navigators'
@ -99,7 +100,8 @@ const TimelineActions: React.FC = () => {
t('shared.actions.reblogged.options.unlisted'), t('shared.actions.reblogged.options.unlisted'),
t('common:buttons.cancel') t('common:buttons.cancel')
], ],
cancelButtonIndex: 2 cancelButtonIndex: 2,
...androidActionSheetStyles(colors)
}, },
(selectedIndex: number) => { (selectedIndex: number) => {
switch (selectedIndex) { switch (selectedIndex) {
@ -263,11 +265,6 @@ const TimelineActions: React.FC = () => {
}, [status.bookmarked]) }, [status.bookmarked])
return ( return (
<View
style={{
paddingLeft: highlighted ? 0 : StyleConstants.Avatar.M + StyleConstants.Spacing.S
}}
>
<View style={{ flexDirection: 'row' }}> <View style={{ flexDirection: 'row' }}>
<Pressable <Pressable
{...(highlighted {...(highlighted
@ -320,7 +317,6 @@ const TimelineActions: React.FC = () => {
children={childrenBookmark} children={childrenBookmark}
/> />
</View> </View>
</View>
) )
} }

View File

@ -70,7 +70,6 @@ const TimelineAttachment = () => {
preview_url: attachment.preview_url, preview_url: attachment.preview_url,
url: attachment.url, url: attachment.url,
remote_url: attachment.remote_url, remote_url: attachment.remote_url,
blurhash: attachment.blurhash,
width: attachment.meta?.original?.width, width: attachment.meta?.original?.width,
height: attachment.meta?.original?.height height: attachment.meta?.original?.height
} }
@ -90,7 +89,6 @@ const TimelineAttachment = () => {
preview_url: attachment.preview_url, preview_url: attachment.preview_url,
url: attachment.url, url: attachment.url,
remote_url: attachment.remote_url, remote_url: attachment.remote_url,
blurhash: attachment.blurhash,
width: attachment.meta?.original?.width, width: attachment.meta?.original?.width,
height: attachment.meta?.original?.height height: attachment.meta?.original?.height
} }

View File

@ -48,8 +48,7 @@ const TimelineAvatar: React.FC<Props> = ({ account }) => {
style={{ style={{
borderRadius: StyleConstants.Avatar.M, borderRadius: StyleConstants.Avatar.M,
overflow: 'hidden', overflow: 'hidden',
marginRight: StyleConstants.Spacing.S, marginRight: StyleConstants.Spacing.S
marginLeft: isConversation ? StyleConstants.Avatar.M - StyleConstants.Avatar.XS : undefined
}} }}
/> />
) )

View File

@ -145,6 +145,7 @@ const TimelineCard: React.FC = () => {
/> />
) : null} ) : null}
<View style={{ flex: 1, padding: StyleConstants.Spacing.S }}> <View style={{ flex: 1, padding: StyleConstants.Spacing.S }}>
{status.card?.title.length ? (
<CustomText <CustomText
fontStyle='S' fontStyle='S'
numberOfLines={2} numberOfLines={2}
@ -155,9 +156,10 @@ const TimelineCard: React.FC = () => {
fontWeight='Bold' fontWeight='Bold'
testID='title' testID='title'
> >
{status.card?.title} {status.card.title}
</CustomText> </CustomText>
{status.card?.description ? ( ) : null}
{status.card?.description.length ? (
<CustomText <CustomText
fontStyle='S' fontStyle='S'
numberOfLines={1} numberOfLines={1}
@ -170,9 +172,11 @@ const TimelineCard: React.FC = () => {
{status.card.description} {status.card.description}
</CustomText> </CustomText>
) : null} ) : null}
{status.card?.url.length ? (
<CustomText fontStyle='S' numberOfLines={1} style={{ color: colors.secondary }}> <CustomText fontStyle='S' numberOfLines={1} style={{ color: colors.secondary }}>
{status.card?.url} {status.card.url}
</CustomText> </CustomText>
) : null}
</View> </View>
</> </>
) )
@ -187,10 +191,6 @@ const TimelineCard: React.FC = () => {
style={{ style={{
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',
minHeight:
(isStatus && foundStatus) || (isAccount && foundAccount)
? undefined
: StyleConstants.Font.LineHeight.M * 5,
marginTop: StyleConstants.Spacing.M, marginTop: StyleConstants.Spacing.M,
borderWidth: StyleSheet.hairlineWidth, borderWidth: StyleSheet.hairlineWidth,
borderRadius: StyleConstants.Spacing.S, borderRadius: StyleConstants.Spacing.S,

View File

@ -1,8 +1,12 @@
import { ParseHTML } from '@components/Parse' import { ParseHTML } from '@components/Parse'
import CustomText from '@components/Text'
import { getInstanceAccount } from '@utils/slices/instancesSlice' import { getInstanceAccount } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native' import { Platform, StyleSheet, View } from 'react-native'
import { Path, Svg } from 'react-native-svg'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { isRtlLang } from 'rtl-detect' import { isRtlLang } from 'rtl-detect'
import StatusContext from './Context' import StatusContext from './Context'
@ -16,6 +20,7 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
const { status, highlighted, inThread, disableDetails } = useContext(StatusContext) const { status, highlighted, inThread, disableDetails } = useContext(StatusContext)
if (!status || typeof status.content !== 'string' || !status.content.length) return null if (!status || typeof status.content !== 'string' || !status.content.length) return null
const { colors } = useTheme()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const instanceAccount = useSelector(getInstanceAccount, () => true) const instanceAccount = useSelector(getInstanceAccount, () => true)
@ -39,6 +44,11 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
: undefined : undefined
} }
/> />
{inThread ? (
<CustomText fontStyle='S' style={{ textAlign: 'center', color: colors.secondary, paddingVertical: StyleConstants.Spacing.XS }}>
{t('shared.content.expandHint')}
</CustomText>
) : null}
<ParseHTML <ParseHTML
content={status.content} content={status.content}
size={highlighted ? 'L' : 'M'} size={highlighted ? 'L' : 'M'}

View File

@ -1,7 +1,9 @@
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { createContext } from 'react' import { createContext } from 'react'
type ContextType = { export type HighlightedStatusContextType = {}
type StatusContextType = {
queryKey?: QueryKeyTimeline queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline rootQueryKey?: QueryKeyTimeline
@ -10,10 +12,8 @@ type ContextType = {
reblogStatus?: Mastodon.Status // When it is a reblog, pass the root status reblogStatus?: Mastodon.Status // When it is a reblog, pass the root status
ownAccount?: boolean ownAccount?: boolean
spoilerHidden?: boolean spoilerHidden?: boolean
copiableContent?: React.MutableRefObject<{ rawContent?: React.MutableRefObject<string[]> // When highlighted, for translate, edit history
content: string detectedLanguage?: React.MutableRefObject<string>
complete: boolean
}>
highlighted?: boolean highlighted?: boolean
inThread?: boolean inThread?: boolean
@ -21,6 +21,6 @@ type ContextType = {
disableOnPress?: boolean disableOnPress?: boolean
isConversation?: boolean isConversation?: boolean
} }
const StatusContext = createContext<ContextType>({} as ContextType) const StatusContext = createContext<StatusContextType>({} as StatusContextType)
export default StatusContext export default StatusContext

View File

@ -11,7 +11,7 @@ import { StyleSheet, View } from 'react-native'
import StatusContext from './Context' import StatusContext from './Context'
const TimelineFeedback = () => { const TimelineFeedback = () => {
const { status, highlighted } = useContext(StatusContext) const { status, highlighted, detectedLanguage } = useContext(StatusContext)
if (!status || !highlighted) return null if (!status || !highlighted) return null
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
@ -80,7 +80,12 @@ const TimelineFeedback = () => {
accessibilityHint={t('shared.actionsUsers.history.accessibilityHint')} accessibilityHint={t('shared.actionsUsers.history.accessibilityHint')}
accessibilityRole='button' accessibilityRole='button'
style={[styles.text, { marginRight: 0, color: colors.blue }]} style={[styles.text, { marginRight: 0, color: colors.blue }]}
onPress={() => navigation.push('Tab-Shared-History', { id: status.id })} onPress={() =>
navigation.push('Tab-Shared-History', {
id: status.id,
detectedLanguage: detectedLanguage?.current || status.language || ''
})
}
> >
{t('shared.actionsUsers.history.text', { {t('shared.actionsUsers.history.text', {
count: data.length - 1 count: data.length - 1

View File

@ -1,19 +1,46 @@
import CustomText from '@components/Text' import CustomText from '@components/Text'
import removeHTML from '@helpers/removeHTML'
import { store } from '@root/store' import { store } from '@root/store'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { getInstance, getInstanceAccount } from '@utils/slices/instancesSlice' import { getInstance } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import htmlparser2 from 'htmlparser2-without-node-native'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { View } from 'react-native' import { View } from 'react-native'
const TimelineFiltered = React.memo( export interface FilteredProps {
({ phrase }: { phrase: string }) => { filterResults: { title: string; filter_action: Mastodon.Filter<'v2'>['filter_action'] }[]
}
const TimelineFiltered: React.FC<FilteredProps> = ({ filterResults }) => {
const { colors } = useTheme() const { colors } = useTheme()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const main = () => {
if (!filterResults?.length) {
return <></>
}
switch (typeof filterResults[0]) {
case 'string': // v1 filter
return <>{t('shared.filtered.match', { context: 'v1', phrase: filterResults[0] })}</>
default:
return (
<>
{t('shared.filtered.match', {
context: 'v2',
count: filterResults.length,
filters: filterResults.map(result => result.title).join(t('common:separator'))
})}
<CustomText
style={{ color: colors.blue }}
children={`\n${t('shared.filtered.reveal')}`}
/>
</>
)
}
}
return ( return (
<View style={{ backgroundColor: colors.backgroundDefault }}> <View style={{ backgroundColor: colors.backgroundDefault }}>
<CustomText <CustomText
@ -25,67 +52,47 @@ const TimelineFiltered = React.memo(
paddingLeft: StyleConstants.Avatar.M + StyleConstants.Spacing.S paddingLeft: StyleConstants.Avatar.M + StyleConstants.Spacing.S
}} }}
> >
{t('shared.filtered', { phrase })} {main()}
</CustomText> </CustomText>
</View> </View>
) )
}, }
() => true
)
export const shouldFilter = ({ export const shouldFilter = ({
copiableContent, queryKey,
status, status
queryKey
}: { }: {
copiableContent: React.MutableRefObject<{
content: string
complete: boolean
}>
status: Mastodon.Status
queryKey: QueryKeyTimeline queryKey: QueryKeyTimeline
}): string | null => { status: Pick<Mastodon.Status, 'content' | 'spoiler_text'>
}): FilteredProps['filterResults'] | undefined => {
const page = queryKey[1] const page = queryKey[1]
const instance = getInstance(store.getState()) const instance = getInstance(store.getState())
const ownAccount = getInstanceAccount(store.getState())?.id === status.account?.id
let shouldFilter: string | null = null let returnFilter: FilteredProps['filterResults'] | undefined
if (!ownAccount) { const rawContentCombined = [
let rawContent = '' removeHTML(status.content),
const parser = new htmlparser2.Parser({ status.spoiler_text ? removeHTML(status.spoiler_text) : ''
ontext: (text: string) => { ]
if (!copiableContent.current.complete) { .filter(c => c.length)
copiableContent.current.content = copiableContent.current.content + text .join(`\n`)
} const checkFilter = (filter: Mastodon.Filter<'v1'>) => {
rawContent = rawContent + text
}
})
if (status.spoiler_text) {
parser.write(status.spoiler_text)
rawContent = rawContent + `\n\n`
}
parser.write(status.content)
parser.end()
const checkFilter = (filter: Mastodon.Filter) => {
const escapedPhrase = filter.phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string const escapedPhrase = filter.phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
switch (filter.whole_word) { switch (filter.whole_word) {
case true: case true:
if (new RegExp(`\\b${escapedPhrase}\\b`, 'i').test(rawContent)) { if (new RegExp(`\\b${escapedPhrase}\\b`, 'i').test(rawContentCombined)) {
shouldFilter = filter.phrase returnFilter = [{ title: filter.phrase, filter_action: 'warn' }]
} }
break break
case false: case false:
if (new RegExp(escapedPhrase, 'i').test(rawContent)) { if (new RegExp(escapedPhrase, 'i').test(rawContentCombined)) {
shouldFilter = filter.phrase returnFilter = [{ title: filter.phrase, filter_action: 'warn' }]
} }
break break
} }
} }
instance?.filters?.forEach(filter => { instance?.filters?.forEach(filter => {
if (shouldFilter) { if (returnFilter) {
return return
} }
if (filter.expires_at) { if (filter.expires_at) {
@ -100,30 +107,27 @@ export const shouldFilter = ({
case 'List': case 'List':
case 'Account': case 'Account':
if (filter.context.includes('home')) { if (filter.context.includes('home')) {
checkFilter(filter) checkFilter(filter as Mastodon.Filter<'v1'>)
} }
break break
case 'Notifications': case 'Notifications':
if (filter.context.includes('notifications')) { if (filter.context.includes('notifications')) {
checkFilter(filter) checkFilter(filter as Mastodon.Filter<'v1'>)
} }
break break
case 'LocalPublic': case 'LocalPublic':
if (filter.context.includes('public')) { if (filter.context.includes('public')) {
checkFilter(filter) checkFilter(filter as Mastodon.Filter<'v1'>)
} }
break break
case 'Toot': case 'Toot':
if (filter.context.includes('thread')) { if (filter.context.includes('thread')) {
checkFilter(filter) checkFilter(filter as Mastodon.Filter<'v1'>)
} }
} }
}) })
copiableContent.current.complete = true return returnFilter
}
return shouldFilter
} }
export default TimelineFiltered export default TimelineFiltered

View File

@ -10,7 +10,7 @@ import * as DropdownMenu from 'zeego/dropdown-menu'
import StatusContext from './Context' import StatusContext from './Context'
const TimelineHeaderAndroid: React.FC = () => { const TimelineHeaderAndroid: React.FC = () => {
const { queryKey, rootQueryKey, status, disableDetails, disableOnPress } = const { queryKey, rootQueryKey, status, disableDetails, disableOnPress, rawContent } =
useContext(StatusContext) useContext(StatusContext)
if (Platform.OS !== 'android' || !status || disableDetails || disableOnPress) return null if (Platform.OS !== 'android' || !status || disableDetails || disableOnPress) return null
@ -21,7 +21,8 @@ const TimelineHeaderAndroid: React.FC = () => {
const mShare = menuShare({ const mShare = menuShare({
visibility: status.visibility, visibility: status.visibility,
type: 'status', type: 'status',
url: status.url || status.uri url: status.url || status.uri,
rawContent
}) })
const mAccount = menuAccount({ const mAccount = menuAccount({
type: 'status', type: 'status',

View File

@ -16,7 +16,7 @@ import HeaderSharedMuted from './HeaderShared/Muted'
import HeaderSharedVisibility from './HeaderShared/Visibility' import HeaderSharedVisibility from './HeaderShared/Visibility'
const TimelineHeaderDefault: React.FC = () => { const TimelineHeaderDefault: React.FC = () => {
const { queryKey, rootQueryKey, status, copiableContent, highlighted, disableDetails } = const { queryKey, rootQueryKey, status, highlighted, disableDetails, rawContent } =
useContext(StatusContext) useContext(StatusContext)
if (!status) return null if (!status) return null
@ -28,7 +28,7 @@ const TimelineHeaderDefault: React.FC = () => {
visibility: status.visibility, visibility: status.visibility,
type: 'status', type: 'status',
url: status.url || status.uri, url: status.url || status.uri,
copiableContent rawContent
}) })
const mAccount = menuAccount({ const mAccount = menuAccount({
type: 'status', type: 'status',

View File

@ -13,39 +13,25 @@ import { Circle } from 'react-native-animated-spinkit'
import StatusContext from './Context' import StatusContext from './Context'
const TimelineTranslate = () => { const TimelineTranslate = () => {
const { status, highlighted, copiableContent } = useContext(StatusContext) const { status, highlighted, rawContent, detectedLanguage } = useContext(StatusContext)
if (!status || !highlighted) return null if (!status || !highlighted || !rawContent?.current.length) return null
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { colors } = useTheme() const { colors } = useTheme()
const backupTextProcessing = (): string[] => { const [detected, setDetected] = useState<{
const text = status.spoiler_text ? [status.spoiler_text, status.content] : [status.content]
for (const i in text) {
for (const emoji of status.emojis) {
text[i] = text[i].replaceAll(`:${emoji.shortcode}:`, ' ')
}
text[i] = text[i]
.replace(/(<([^>]+)>)/gi, ' ')
.replace(/@.*? /gi, ' ')
.replace(/#.*? /gi, ' ')
.replace(/http(s):\/\/.*? /gi, ' ')
}
return text
}
const text = copiableContent?.current.content
? [copiableContent?.current.content]
: backupTextProcessing()
const [detectedLanguage, setDetectedLanguage] = useState<{
language: string language: string
confidence: number confidence: number
}>({ language: status.language || '', confidence: 0 }) }>({ language: status.language || '', confidence: 0 })
useEffect(() => { useEffect(() => {
const detect = async () => { const detect = async () => {
const result = await detectLanguage(text.join('\n\n')) const result = await detectLanguage(rawContent.current.join('\n\n'))
result && setDetectedLanguage(result) if (result) {
setDetected(result)
if (detectedLanguage) {
detectedLanguage.current = result.language
}
}
} }
detect() detect()
}, []) }, [])
@ -57,18 +43,18 @@ const TimelineTranslate = () => {
const [enabled, setEnabled] = useState(false) const [enabled, setEnabled] = useState(false)
const { refetch, data, isFetching, isSuccess, isError } = useTranslateQuery({ const { refetch, data, isFetching, isSuccess, isError } = useTranslateQuery({
source: detectedLanguage.language, source: detected.language,
target: targetLanguage, target: targetLanguage,
text, text: rawContent.current,
options: { enabled } options: { enabled }
}) })
const devView = () => { const devView = () => {
return __DEV__ ? ( return __DEV__ ? (
<CustomText fontStyle='S' style={{ color: colors.secondary }}>{` Source: ${ <CustomText fontStyle='S' style={{ color: colors.secondary }}>{` Source: ${
detectedLanguage?.language detected?.language
}; Confidence: ${ }; Confidence: ${
detectedLanguage?.confidence.toString().slice(0, 5) || 'null' detected?.confidence.toString().slice(0, 5) || 'null'
}; Target: ${targetLanguage}`}</CustomText> }; Target: ${targetLanguage}`}</CustomText>
) : null ) : null
} }
@ -78,13 +64,13 @@ const TimelineTranslate = () => {
} }
if ( if (
Platform.OS === 'ios' && Platform.OS === 'ios' &&
Localization.locale.slice(0, 2).includes(detectedLanguage.language.slice(0, 2)) Localization.locale.slice(0, 2).includes(detected.language.slice(0, 2))
) { ) {
return devView() return devView()
} }
if ( if (
Platform.OS === 'android' && Platform.OS === 'android' &&
settingsLanguage?.slice(0, 2).includes(detectedLanguage.language.slice(0, 2)) settingsLanguage?.slice(0, 2).includes(detected.language.slice(0, 2))
) { ) {
return devView() return devView()
} }

View File

@ -16,7 +16,7 @@ import {
import { getInstanceAccount } from '@utils/slices/instancesSlice' import { getInstanceAccount } from '@utils/slices/instancesSlice'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Platform } from 'react-native' import { Alert, Platform } from 'react-native'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
@ -186,12 +186,22 @@ const menuAccount = ({
key: 'account-block', key: 'account-block',
item: { item: {
onSelect: () => onSelect: () =>
Alert.alert(t('account.block.alert.title', { username: account.username }), undefined, [
{
text: t('common:buttons.confirm'),
style: 'destructive',
onPress: () =>
timelineMutation.mutate({ timelineMutation.mutate({
type: 'updateAccountProperty', type: 'updateAccountProperty',
queryKey, queryKey,
id: account.id, id: account.id,
payload: { property: 'block', currentValue: data?.blocking } payload: { property: 'block', currentValue: data?.blocking }
}), })
},
{
text: t('common:buttons.cancel')
}
]),
disabled: Platform.OS !== 'android' ? !data || !isFetched : false, disabled: Platform.OS !== 'android' ? !data || !isFetched : false,
destructive: !data?.blocking, destructive: !data?.blocking,
hidden: false hidden: false
@ -204,7 +214,15 @@ const menuAccount = ({
{ {
key: 'account-reports', key: 'account-reports',
item: { item: {
onSelect: () => { onSelect: () =>
Alert.alert(
t('account.reports.alert.title', { username: account.username }),
undefined,
[
{
text: t('common:buttons.confirm'),
style: 'destructive',
onPress: () => {
timelineMutation.mutate({ timelineMutation.mutate({
type: 'updateAccountProperty', type: 'updateAccountProperty',
queryKey, queryKey,
@ -217,7 +235,13 @@ const menuAccount = ({
id: account.id, id: account.id,
payload: { property: 'block', currentValue: false } payload: { property: 'block', currentValue: false }
}) })
}
}, },
{
text: t('common:buttons.cancel')
}
]
),
disabled: false, disabled: false,
destructive: true, destructive: true,
hidden: false hidden: false

View File

@ -49,7 +49,7 @@ const menuInstance = ({
t('instance.block.alert.message'), t('instance.block.alert.message'),
[ [
{ {
text: t('instance.block.alert.buttons.confirm'), text: t('common:buttons.confirm'),
style: 'destructive', style: 'destructive',
onPress: () => { onPress: () => {
mutation.mutate({ mutation.mutate({

View File

@ -7,10 +7,7 @@ const menuShare = (
params: params:
| { | {
visibility?: Mastodon.Status['visibility'] visibility?: Mastodon.Status['visibility']
copiableContent?: React.MutableRefObject<{ rawContent?: React.MutableRefObject<string[]>
content?: string | undefined
complete: boolean
}>
type: 'status' type: 'status'
url?: string url?: string
} }
@ -48,17 +45,17 @@ const menuShare = (
icon: 'square.and.arrow.up' icon: 'square.and.arrow.up'
}) })
} }
if (params.type === 'status' && Platform.OS === 'ios') if (params.type === 'status')
menus[0].push({ menus[0].push({
key: 'copy', key: 'copy',
item: { item: {
onSelect: () => { onSelect: () => {
Clipboard.setString(params.copiableContent?.current.content || '') Clipboard.setString(params.rawContent?.current.join(`\n\n`) || '')
displayMessage({ type: 'success', message: t(`copy.succeed`) }) displayMessage({ type: 'success', message: t(`copy.succeed`) })
}, },
disabled: false, disabled: false,
destructive: false, destructive: false,
hidden: !params.copiableContent?.current.content?.length hidden: !params.rawContent?.current.length
}, },
title: t('copy.action'), title: t('copy.action'),
icon: 'doc.on.doc' icon: 'doc.on.doc'

View File

@ -109,7 +109,7 @@ const menuStatus = ({
onSelect: () => onSelect: () =>
Alert.alert(t('status.deleteEdit.alert.title'), t('status.deleteEdit.alert.message'), [ Alert.alert(t('status.deleteEdit.alert.title'), t('status.deleteEdit.alert.message'), [
{ {
text: t('status.deleteEdit.alert.buttons.confirm'), text: t('common:buttons.confirm'),
style: 'destructive', style: 'destructive',
onPress: async () => { onPress: async () => {
let replyToStatus: Mastodon.Status | undefined = undefined let replyToStatus: Mastodon.Status | undefined = undefined
@ -153,7 +153,7 @@ const menuStatus = ({
onSelect: () => onSelect: () =>
Alert.alert(t('status.delete.alert.title'), t('status.delete.alert.message'), [ Alert.alert(t('status.delete.alert.title'), t('status.delete.alert.message'), [
{ {
text: t('status.delete.alert.buttons.confirm'), text: t('common:buttons.confirm'),
style: 'destructive', style: 'destructive',
onPress: async () => { onPress: async () => {
mutation.mutate({ mutation.mutate({

View File

@ -0,0 +1,5 @@
export const androidActionSheetStyles = (colors: any) => ({
containerStyle: { backgroundColor: colors.backgroundDefault },
textStyle: { color: colors.primaryDefault },
titleTextStyle: { color: colors.secondary }
})

View File

@ -1,4 +1,8 @@
[ [
{
"feature": "account_follow_notify",
"version": 3.3
},
{ {
"feature": "notification_type_status", "feature": "notification_type_status",
"version": 3.3 "version": 3.3
@ -38,5 +42,9 @@
{ {
"feature": "notification_type_admin_report", "feature": "notification_type_admin_report",
"version": 4.0 "version": 4.0
},
{
"feature": "filter_server_side",
"version": 4.0
} }
] ]

View File

@ -1,5 +1,21 @@
import { QueryClient } from '@tanstack/react-query' import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5 } } }) const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5,
retry: (failureCount, error: any) => {
if (error?.status === 404) {
return false
}
if (failureCount <= 3) {
return true
} else {
return false
}
}
}
}
})
export default queryClient export default queryClient

View File

@ -6,6 +6,9 @@ const removeHTML = (text: string): string => {
const parser = new htmlparser2.Parser({ const parser = new htmlparser2.Parser({
ontext: (text: string) => { ontext: (text: string) => {
raw = raw + text raw = raw + text
},
onclosetag: (tag: string) => {
if (['p', 'br'].includes(tag)) raw = raw + `\n`
} }
}) })

View File

@ -7,7 +7,8 @@
"continue": "Continua", "continue": "Continua",
"create": "Crea", "create": "Crea",
"delete": "Esborra", "delete": "Esborra",
"done": "Fet" "done": "Fet",
"confirm": "Confirma"
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "Emoji personalitzat {{emoji}}" "accessibilityLabel": "Emoji personalitzat {{emoji}}"

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "Bloqueja l'usuari", "action_false": "Bloqueja l'usuari",
"action_true": "Deixa de bloquejar l'usuari" "action_true": "Deixa de bloquejar l'usuari",
"alert": {
"title": ""
}
}, },
"reports": { "reports": {
"action": "Denuncia i bloqueja l'usuari" "action": "Denuncia i bloqueja l'usuari",
"alert": {
"title": ""
}
} }
}, },
"at": { "at": {
@ -32,11 +38,8 @@
"block": { "block": {
"action": "Bloquejar la instància {{instance}}", "action": "Bloquejar la instància {{instance}}",
"alert": { "alert": {
"title": "Confirma el bloqueig de la instància {{instance}}?", "title": "Vols bloquejar la instància {{instance}}?",
"message": "Pots silenciar o bloquejar a un usuari.\n\nDesprés de bloquejar una instància, tot el seu contingut, amb els seus seguidors, seran esborrats!", "message": "Pots silenciar o bloquejar a un usuari.\n\nDesprés de bloquejar una instància, tot el seu contingut, amb els seus seguidors, seran esborrats!"
"buttons": {
"confirm": "Confirma"
}
} }
} }
}, },
@ -56,21 +59,15 @@
"delete": { "delete": {
"action": "Elimina la publicació", "action": "Elimina la publicació",
"alert": { "alert": {
"title": "Confirma l'eliminació?", "title": "Vols eliminar-ho?",
"message": "Tots els impulsos i favorits s'esborraran, incloses totes les respostes.", "message": "Tots els impulsos i favorits s'esborraran, incloses totes les respostes."
"buttons": {
"confirm": "Confirma"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "Elimina la publicació i torna a publicar", "action": "Elimina i torna-ho a publicar",
"alert": { "alert": {
"title": "Confirma l'eliminació i tornar a publicar?", "title": "Vols eliminar i tornar-ho a publicar?",
"message": "Tots els impulsos i favorits s'esborraran, incloses totes les respostes.", "message": "Tots els impulsos i favorits s'esborraran, incloses totes les respostes."
"buttons": {
"confirm": "Confirma"
}
} }
}, },
"mute": { "mute": {

View File

@ -1,8 +1,9 @@
{ {
"server": { "server": {
"textInput": { "textInput": {
"placeholder": "" "placeholder": "Domini de la instància"
}, },
"whitelisted": "Pot ser una instància que estigui a la llista blanca de la qual tooot no pot obtenir les dades abans d'iniciar la sessió.",
"button": "Inicia la sessió", "button": "Inicia la sessió",
"information": { "information": {
"name": "Nom", "name": "Nom",

View File

@ -1,6 +1,6 @@
{ {
"title": "Selecciona origen multimèdia", "title": "Selecciona origen multimèdia",
"message": "Les dades multimèdia EXIF no s'han penjat", "message": "Les dades multimèdia EXIF no es penjen",
"options": { "options": {
"image": "Penja fotos", "image": "Penja fotos",
"image_max": "Penja fotos (màx. {{max}})", "image_max": "Penja fotos (màx. {{max}})",

View File

@ -31,7 +31,7 @@
"notification": "{{name}} ha impulsat la teva publicació" "notification": "{{name}} ha impulsat la teva publicació"
}, },
"update": "L'impuls ha sigut editat", "update": "L'impuls ha sigut editat",
"admin.sign_up": "", "admin.sign_up": "{{name}} s'ha unit a la instància",
"admin.report": "" "admin.report": ""
}, },
"actions": { "actions": {
@ -55,7 +55,7 @@
"accessibilityLabel": "Afegeix aquesta publicació a marcadors", "accessibilityLabel": "Afegeix aquesta publicació a marcadors",
"function": "Afegeix la publicació a marcadors" "function": "Afegeix la publicació a marcadors"
}, },
"openReport": "" "openReport": "Obre la denúncia"
}, },
"actionsUsers": { "actionsUsers": {
"reblogged_by": { "reblogged_by": {
@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "Contingut ocult" "expandHint": "Contingut ocult"
}, },
"filtered": "Filtrat: {{phrase}}.", "filtered": {
"reveal": "Mostra-ho de totes maneres",
"match_v1": "Filtrat: {{phrase}}.",
"match_v2_one": "Filtrat per {{filters}}.",
"match_v2_other": "Filtrat per {{count}} filtres, {{filters}}."
},
"fullConversation": "Llegeix conversacions", "fullConversation": "Llegeix conversacions",
"translate": { "translate": {
"default": "Tradueix", "default": "Tradueix",

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "Protecció de la privacitat", "title": "Protecció de la privacitat",
"message": "Si us plau, no revelis la identitat d'altres usuaris, així com el nom d'usuari, avatar, etc. Gràcies!", "message": "Si us plau, no revelis la identitat d'altres usuaris, així com el nom d'usuari, avatar, etc. Gràcies!"
"button": "Confirma"
}, },
"localCorrupt": { "localCorrupt": {
"message": "La sessió ha sigut expirada. Si us plau, torna a iniciar la sessió" "message": "La sessió ha sigut expirada. Si us plau, torna a iniciar la sessió"

View File

@ -11,11 +11,11 @@
}, },
"right": { "right": {
"button": { "button": {
"default": "Publicació", "default": "Publica",
"conversation": "Envia un missatge directe", "conversation": "Envia un missatge directe",
"reply": "Resposta de la publicació", "reply": "Publica la resposta",
"deleteEdit": "Publicació", "deleteEdit": "Publicació",
"edit": "Publicació", "edit": "Publica l'edició",
"share": "Publicació" "share": "Publicació"
}, },
"alert": { "alert": {

View File

@ -3,15 +3,15 @@
"local": { "local": {
"name": "Seguint", "name": "Seguint",
"options": { "options": {
"showBoosts": "", "showBoosts": "Mostra les publicacions",
"showReplies": "" "showReplies": "Mostra les respostes"
} }
}, },
"public": { "public": {
"segments": { "segments": {
"federated": "Federat", "federated": "Federat",
"local": "Local", "local": "Local",
"trending": "En tendència" "trending": "Tendència"
} }
}, },
"notifications": { "notifications": {
@ -31,13 +31,13 @@
"title": "Mostra les notificacions", "title": "Mostra les notificacions",
"options": { "options": {
"follow": "$t(screenTabs:me.push.follow.heading)", "follow": "$t(screenTabs:me.push.follow.heading)",
"follow_request": "Sol·licitud de seguiment", "follow_request": "Sol·licituds de seguiment",
"favourite": "$t(screenTabs:me.push.favourite.heading)", "favourite": "$t(screenTabs:me.push.favourite.heading)",
"reblog": "$t(screenTabs:me.push.reblog.heading)", "reblog": "$t(screenTabs:me.push.reblog.heading)",
"mention": "$t(screenTabs:me.push.mention.heading)", "mention": "$t(screenTabs:me.push.mention.heading)",
"poll": "$t(screenTabs:me.push.poll.heading)", "poll": "$t(screenTabs:me.push.poll.heading)",
"status": "Publicació d'usuaris subscrits", "status": "Publicacions d'usuaris subscrits",
"update": "L'impuls ha sigut editat", "update": "Edicions d'impulsos",
"admin.sign_up": "$t(screenTabs:me.push.admin.sign_up.heading)", "admin.sign_up": "$t(screenTabs:me.push.admin.sign_up.heading)",
"admin.report": "$t(screenTabs:me.push.admin.report.heading)" "admin.report": "$t(screenTabs:me.push.admin.report.heading)"
} }
@ -163,7 +163,7 @@
"total_other": "{{count}} camps" "total_other": "{{count}} camps"
}, },
"visibility": { "visibility": {
"title": "Visibilitat de la publicació", "title": "Visibilitat",
"options": { "options": {
"public": "Públic", "public": "Públic",
"unlisted": "Sense llistar", "unlisted": "Sense llistar",
@ -171,7 +171,7 @@
} }
}, },
"sensitive": { "sensitive": {
"title": "Publica contingut multimèdia sensible" "title": "Continguts multimèdia sensibles"
}, },
"lock": { "lock": {
"title": "Fes el compte privat", "title": "Fes el compte privat",
@ -211,28 +211,28 @@
"heading": "Per defecte" "heading": "Per defecte"
}, },
"follow": { "follow": {
"heading": "Nou seguidor" "heading": "Seguidors nous"
}, },
"follow_request": { "follow_request": {
"heading": "Sol·licitud de seguiment" "heading": "Sol·licituds de seguiment"
}, },
"favourite": { "favourite": {
"heading": "Favorits" "heading": "Favorits"
}, },
"reblog": { "reblog": {
"heading": "Impulsat" "heading": "Impulsos"
}, },
"mention": { "mention": {
"heading": "T'ha mencionat" "heading": "Mencions"
}, },
"poll": { "poll": {
"heading": "Actualització d'una votació" "heading": "Actualitzacions d'una votació"
}, },
"status": { "status": {
"heading": "Publicació d'usuaris subscrits" "heading": "Publicacions d'usuaris subscrits"
}, },
"update": { "update": {
"heading": "L'impuls ha sigut editat" "heading": "Edicions d'impulsos"
}, },
"admin.sign_up": { "admin.sign_up": {
"heading": "Administració: Registra" "heading": "Administració: Registra"
@ -399,7 +399,7 @@
"reblogged_by": "{{count}} impulsats", "reblogged_by": "{{count}} impulsats",
"favourited_by": "{{count}} favorits" "favourited_by": "{{count}} favorits"
}, },
"resultIncomplete": "" "resultIncomplete": "Els resultats d'una instància remota són incomplets"
} }
} }
} }

View File

@ -7,7 +7,8 @@
"continue": "", "continue": "",
"create": "", "create": "",
"delete": "", "delete": "",
"done": "" "done": "",
"confirm": ""
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "" "accessibilityLabel": ""

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "", "action_false": "",
"action_true": "" "action_true": "",
"alert": {
"title": ""
}
}, },
"reports": { "reports": {
"action": "" "action": "",
"alert": {
"title": ""
}
} }
}, },
"at": { "at": {
@ -33,10 +39,7 @@
"action": "", "action": "",
"alert": { "alert": {
"title": "", "title": "",
"message": "", "message": ""
"buttons": {
"confirm": ""
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "", "action": "",
"alert": { "alert": {
"title": "", "title": "",
"message": "", "message": ""
"buttons": {
"confirm": ""
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "", "action": "",
"alert": { "alert": {
"title": "", "title": "",
"message": "", "message": ""
"buttons": {
"confirm": ""
}
} }
}, },
"mute": { "mute": {

View File

@ -3,6 +3,7 @@
"textInput": { "textInput": {
"placeholder": "" "placeholder": ""
}, },
"whitelisted": "",
"button": "", "button": "",
"information": { "information": {
"name": "", "name": "",

View File

@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "" "expandHint": ""
}, },
"filtered": "", "filtered": {
"reveal": "",
"match_v1": "",
"match_v2_one": "",
"match_v2_other": ""
},
"fullConversation": "", "fullConversation": "",
"translate": { "translate": {
"default": "", "default": "",

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "", "title": "",
"message": "", "message": ""
"button": ""
}, },
"localCorrupt": { "localCorrupt": {
"message": "" "message": ""

View File

@ -7,7 +7,8 @@
"continue": "Weiter", "continue": "Weiter",
"create": "Erstellen", "create": "Erstellen",
"delete": "Löschen", "delete": "Löschen",
"done": "Fertig" "done": "Fertig",
"confirm": "Bestätigen"
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "Eigenes Emoji {{emoji}}" "accessibilityLabel": "Eigenes Emoji {{emoji}}"

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "Nutzer blockieren", "action_false": "Nutzer blockieren",
"action_true": "User entblocken" "action_true": "User entblocken",
"alert": {
"title": ""
}
}, },
"reports": { "reports": {
"action": "Nutzer melden und blockieren" "action": "Nutzer melden und blockieren",
"alert": {
"title": ""
}
} }
}, },
"at": { "at": {
@ -33,10 +39,7 @@
"action": "Instanz {{instance}} blockieren", "action": "Instanz {{instance}} blockieren",
"alert": { "alert": {
"title": "{{instance}} wirklich blockieren?", "title": "{{instance}} wirklich blockieren?",
"message": "Üblicherweise kannst du einen User stummschalten oder blockieren.\nBlockierst du hingegegen eine Instanz, wird deren gesamter Inhalt samt Usern, die dir von dieser Instanz folgen, entfernt!", "message": "Üblicherweise kannst du einen User stummschalten oder blockieren.\nBlockierst du hingegegen eine Instanz, wird deren gesamter Inhalt samt Usern, die dir von dieser Instanz folgen, entfernt!"
"buttons": {
"confirm": "Bestätigen"
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "Tröt löschen", "action": "Tröt löschen",
"alert": { "alert": {
"title": "Löschen bestätigen?", "title": "Löschen bestätigen?",
"message": "Alle Boosts, Sterne und Antworten werden entfernt.", "message": "Alle Boosts, Sterne und Antworten werden entfernt."
"buttons": {
"confirm": "Bestätigen"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "Tröt neu entwerfen", "action": "Tröt neu entwerfen",
"alert": { "alert": {
"title": "Beitrag wirklich entfernen?", "title": "Beitrag wirklich entfernen?",
"message": "Alle Boosts und Likes inklusive der Antworten werden gelöscht.", "message": "Alle Boosts und Likes inklusive der Antworten werden gelöscht."
"buttons": {
"confirm": "Bestätigen"
}
} }
}, },
"mute": { "mute": {

View File

@ -3,6 +3,7 @@
"textInput": { "textInput": {
"placeholder": "Domain der Instanz" "placeholder": "Domain der Instanz"
}, },
"whitelisted": "",
"button": "Login", "button": "Login",
"information": { "information": {
"name": "Name", "name": "Name",

View File

@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "Ausgeblendeter Inhalt" "expandHint": "Ausgeblendeter Inhalt"
}, },
"filtered": "Gefiltert: {{phrase}}.", "filtered": {
"reveal": "Trotzdem anzeigen",
"match_v1": "Gefiltert: {{phrase}}.",
"match_v2_one": "Gefiltert durch {{filters}}.",
"match_v2_other": "Gefiltert durch {{count}} Filter, {{filters}}."
},
"fullConversation": "Unterhaltung anzeigen", "fullConversation": "Unterhaltung anzeigen",
"translate": { "translate": {
"default": "Übersetzen", "default": "Übersetzen",

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "Datenschutz", "title": "Datenschutz",
"message": "Bitte geben Sie nicht die Identität anderer Nutzer preis, wie z. B. Benutzername, Avatar, etc. Vielen Dank!", "message": "Bitte geben Sie nicht die Identität anderer Nutzer preis, wie z. B. Benutzername, Avatar, etc. Vielen Dank!"
"button": "Bestätigen"
}, },
"localCorrupt": { "localCorrupt": {
"message": "Login abgelaufen, bitte erneut anmelden" "message": "Login abgelaufen, bitte erneut anmelden"

View File

@ -7,7 +7,8 @@
"continue": "Continue", "continue": "Continue",
"create": "Create", "create": "Create",
"delete": "Delete", "delete": "Delete",
"done": "Done" "done": "Done",
"confirm": "Confirm"
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "Custom emoji {{emoji}}" "accessibilityLabel": "Custom emoji {{emoji}}"

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "Block user", "action_false": "Block user",
"action_true": "Unblock user" "action_true": "Unblock user",
"alert": {
"title": "Confirm blocking user @{{username}} ?"
}
}, },
"reports": { "reports": {
"action": "Report and block user" "action": "Report and block user",
"alert": {
"title": "Confirm report and blocking user @{{username}} ?"
}
} }
}, },
"at": { "at": {
@ -33,10 +39,7 @@
"action": "Block instance {{instance}}", "action": "Block instance {{instance}}",
"alert": { "alert": {
"title": "Confirm blocking instance {{instance}} ?", "title": "Confirm blocking instance {{instance}} ?",
"message": "Mostly you can mute or block certain user.\n\nAfter blocking instance, all its content including followers from this instance will be removed!", "message": "Mostly you can mute or block certain user.\n\nAfter blocking instance, all its content including followers from this instance will be removed!"
"buttons": {
"confirm": "Confirm"
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "Delete toot", "action": "Delete toot",
"alert": { "alert": {
"title": "Confirm deleting?", "title": "Confirm deleting?",
"message": "All boosts and favourites will be cleared, including all replies.", "message": "All boosts and favourites will be cleared, including all replies."
"buttons": {
"confirm": "Confirm"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "Delete toot and repost", "action": "Delete toot and repost",
"alert": { "alert": {
"title": "Confirm deleting and repost?", "title": "Confirm deleting and repost?",
"message": "All boosts and favourites will be cleared, including all replies.", "message": "All boosts and favourites will be cleared, including all replies."
"buttons": {
"confirm": "Confirm"
}
} }
}, },
"mute": { "mute": {

View File

@ -3,6 +3,7 @@
"textInput": { "textInput": {
"placeholder": "Instance's domain" "placeholder": "Instance's domain"
}, },
"whitelisted": "This may be a whitelisted instance that tooot cannot retrieve data from before logging in.",
"button": "Login", "button": "Login",
"information": { "information": {
"name": "Name", "name": "Name",

View File

@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "Hidden content" "expandHint": "Hidden content"
}, },
"filtered": "Filtered: {{phrase}}.", "filtered": {
"reveal": "Show anyway",
"match_v1": "Filtered: {{phrase}}.",
"match_v2_one": "Filtered by {{filters}}.",
"match_v2_other": "Filtered by {{count}} filters, {{filters}}."
},
"fullConversation": "Read conversations", "fullConversation": "Read conversations",
"translate": { "translate": {
"default": "Translate", "default": "Translate",

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "Privacy Protection", "title": "Privacy Protection",
"message": "Please do not disclose other user's identity, such as username, avatar, etc. Thank you!", "message": "Please do not disclose other user's identity, such as username, avatar, etc. Thank you!"
"button": "Confirm"
}, },
"localCorrupt": { "localCorrupt": {
"message": "Login expired, please login again" "message": "Login expired, please login again"

View File

@ -7,7 +7,8 @@
"continue": "Continuar", "continue": "Continuar",
"create": "Crear", "create": "Crear",
"delete": "Borrar", "delete": "Borrar",
"done": "Hecho" "done": "Hecho",
"confirm": "Confirmar"
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "Emoji personalizado {{emoji}}" "accessibilityLabel": "Emoji personalizado {{emoji}}"

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "Bloquear usuario", "action_false": "Bloquear usuario",
"action_true": "Desbloquear usuario" "action_true": "Desbloquear usuario",
"alert": {
"title": ""
}
}, },
"reports": { "reports": {
"action": "Reportar y bloquear usuario" "action": "Reportar y bloquear usuario",
"alert": {
"title": ""
}
} }
}, },
"at": { "at": {
@ -33,10 +39,7 @@
"action": "Bloquear instancia {{instance}}", "action": "Bloquear instancia {{instance}}",
"alert": { "alert": {
"title": "¿Confirmar bloqueo de la instancia {{instance}}?", "title": "¿Confirmar bloqueo de la instancia {{instance}}?",
"message": "Puedes silenciar o bloquear a un usuario.\n\nTras bloquear una instancia, todo su contenido junto con sus seguidores se eliminarán.", "message": "Puedes silenciar o bloquear a un usuario.\n\nTras bloquear una instancia, todo su contenido junto con sus seguidores se eliminarán."
"buttons": {
"confirm": "Confirmar"
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "Eliminar toot", "action": "Eliminar toot",
"alert": { "alert": {
"title": "¿Confirmar eliminación?", "title": "¿Confirmar eliminación?",
"message": "Todos los boosts y favoritos se eliminarán, incluidas todas las respuestas.", "message": "Todos los boosts y favoritos se eliminarán, incluidas todas las respuestas."
"buttons": {
"confirm": "Confirmar"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "Eliminar toot y volver a publicar", "action": "Eliminar toot y volver a publicar",
"alert": { "alert": {
"title": "¿Confirmar eliminación y volver a publicar?", "title": "¿Confirmar eliminación y volver a publicar?",
"message": "Todos los boosts y favoritos se eliminarán, incluidas todas las respuestas.", "message": "Todos los boosts y favoritos se eliminarán, incluidas todas las respuestas."
"buttons": {
"confirm": "Confirmar"
}
} }
}, },
"mute": { "mute": {

View File

@ -1,8 +1,9 @@
{ {
"server": { "server": {
"textInput": { "textInput": {
"placeholder": "" "placeholder": "Dominio de la instancia"
}, },
"whitelisted": "Puede ser que la instancia esté en una lista blanca por la que tooot no pueda obtener los datos antes de iniciar la sesión.",
"button": "Iniciar sesión", "button": "Iniciar sesión",
"information": { "information": {
"name": "Nombre", "name": "Nombre",

View File

@ -31,7 +31,7 @@
"notification": "{{name}} ha impulsado tu toot" "notification": "{{name}} ha impulsado tu toot"
}, },
"update": "El impulso ha sido editado", "update": "El impulso ha sido editado",
"admin.sign_up": "", "admin.sign_up": "{{name}} se unió a la instancia",
"admin.report": "" "admin.report": ""
}, },
"actions": { "actions": {
@ -55,7 +55,7 @@
"accessibilityLabel": "Añadir este toot en marcadores", "accessibilityLabel": "Añadir este toot en marcadores",
"function": "Añadir toot a marcadores" "function": "Añadir toot a marcadores"
}, },
"openReport": "" "openReport": "Abrir denuncia"
}, },
"actionsUsers": { "actionsUsers": {
"reblogged_by": { "reblogged_by": {
@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "Contenido oculto" "expandHint": "Contenido oculto"
}, },
"filtered": "Filtrado: {{phrase}}.", "filtered": {
"reveal": "Mostrar de todos modos",
"match_v1": "Filtrado: {{phrase}}.",
"match_v2_one": "Filtrado por {{filters}}.",
"match_v2_other": "Filtrado por {{count}} filtros, {{filters}}."
},
"fullConversation": "Leer conversaciones", "fullConversation": "Leer conversaciones",
"translate": { "translate": {
"default": "Traducir", "default": "Traducir",

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "Protección de la privacidad", "title": "Protección de la privacidad",
"message": "Por favor, no revele la identidad de otros usuarios, como el nombre de usuario, avatar, etc. ¡Gracias!", "message": "Por favor, no revele la identidad de otros usuarios, como el nombre de usuario, avatar, etc. ¡Gracias!"
"button": "Confirmar"
}, },
"localCorrupt": { "localCorrupt": {
"message": "La sesión se ha expirado. Por favor, vuelve a iniciar sesión" "message": "La sesión se ha expirado. Por favor, vuelve a iniciar sesión"

View File

@ -15,7 +15,7 @@
"conversation": "Mensaje privado", "conversation": "Mensaje privado",
"reply": "Respuesta al toot", "reply": "Respuesta al toot",
"deleteEdit": "Toot", "deleteEdit": "Toot",
"edit": "Toot", "edit": "Edita el toot",
"share": "Toot" "share": "Toot"
}, },
"alert": { "alert": {

View File

@ -399,7 +399,7 @@
"reblogged_by": "{{count}} impulsados", "reblogged_by": "{{count}} impulsados",
"favourited_by": "{{count}} favoritos" "favourited_by": "{{count}} favoritos"
}, },
"resultIncomplete": "" "resultIncomplete": "Los resultados de una instancia remota están incompletos"
} }
} }
} }

View File

@ -3,11 +3,12 @@
"OK": "Ok", "OK": "Ok",
"apply": "Confirmer", "apply": "Confirmer",
"cancel": "Annuler", "cancel": "Annuler",
"discard": "Ne pas tenir compte", "discard": "Abandonner",
"continue": "Continuer", "continue": "Continuer",
"create": "", "create": "Créer",
"delete": "", "delete": "Supprimer",
"done": "" "done": "Fait",
"confirm": "Confirmer"
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "Émoji personnalisé {{emoji}}" "accessibilityLabel": "Émoji personnalisé {{emoji}}"
@ -20,12 +21,12 @@
"message": "" "message": ""
}, },
"error": { "error": {
"message": "Échec de la connexion, veuillez réessayer" "message": "{{function}} a échoué, veuillez réessayer"
} }
}, },
"separator": ", ", "separator": ", ",
"discard": { "discard": {
"title": "Modifications non sauvegardées", "title": "Modifications non sauvegardées",
"message": "Votre modification n'a pas été enregistrée. Voulez-vous annuler l'enregistrement des modifications ?" "message": "Votre modification n'a pas été enregistrée. Voulez-vous renoncer à enregistrer les modifications ?"
} }
} }

View File

@ -4,24 +4,30 @@
"title": "Actions de l'utilisateur", "title": "Actions de l'utilisateur",
"following": { "following": {
"action_false": "Suivre l'utilisateur", "action_false": "Suivre l'utilisateur",
"action_true": "" "action_true": "Ne plus suivre l'utilisateur"
}, },
"inLists": "", "inLists": "Gérer l'utilisateur des listes",
"mute": { "mute": {
"action_false": "Rendre muet l'utilisateur", "action_false": "Rendre muet l'utilisateur",
"action_true": "Rendre la parole" "action_true": "Rendre la parole"
}, },
"block": { "block": {
"action_false": "Bloquer l'utilisateur", "action_false": "Bloquer l'utilisateur",
"action_true": "Débloquer l'utilisateur" "action_true": "Débloquer l'utilisateur",
"alert": {
"title": ""
}
}, },
"reports": { "reports": {
"action": "" "action": "Signaler et bloquer un utilisateur",
"alert": {
"title": ""
}
} }
}, },
"at": { "at": {
"direct": "Message direct", "direct": "Message direct",
"public": "" "public": "Message public"
}, },
"copy": { "copy": {
"action": "Copier le Pouet", "action": "Copier le Pouet",
@ -33,10 +39,7 @@
"action": "Bloquer l'instance {{instance}}", "action": "Bloquer l'instance {{instance}}",
"alert": { "alert": {
"title": "Confirmer le blocage de l'instance {{instance}}?", "title": "Confirmer le blocage de l'instance {{instance}}?",
"message": "Vous pouvez masquer ou bloquer certains utilisateurs.\n\nAprès avoir bloqué l'instance, tout son contenu, y compris les followers de cette instance, sera supprimé !", "message": "Vous pouvez masquer ou bloquer certains utilisateurs.\n\nAprès avoir bloqué l'instance, tout son contenu, y compris les followers de cette instance, sera supprimé !"
"buttons": {
"confirm": "Confirmer"
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "Supprimer le pouet", "action": "Supprimer le pouet",
"alert": { "alert": {
"title": "Confirmer la suppression ?", "title": "Confirmer la suppression ?",
"message": "Tous les boosts et favoris seront effacés, y compris toutes les réponses.", "message": "Tous les boosts et favoris seront effacés, y compris toutes les réponses."
"buttons": {
"confirm": "Confirmer"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "Supprimer le pouet et le republié", "action": "Supprimer le pouet et le republié",
"alert": { "alert": {
"title": "Confirmer la suppression et le repost ?", "title": "Confirmer la suppression et le repost ?",
"message": "Tous les boosts et favoris seront effacés, y compris toutes les réponses.", "message": "Tous les boosts et favoris seront effacés, y compris toutes les réponses."
"buttons": {
"confirm": "Confirmer"
}
} }
}, },
"mute": { "mute": {

View File

@ -1,8 +1,9 @@
{ {
"server": { "server": {
"textInput": { "textInput": {
"placeholder": "" "placeholder": "URL de linstance"
}, },
"whitelisted": "Il peut sagir dune instance sur liste blanche à partir de laquelle tooot ne peut pas récupérer de données avant de se connecter.",
"button": "Connexion", "button": "Connexion",
"information": { "information": {
"name": "Nom", "name": "Nom",
@ -20,7 +21,7 @@
"update": { "update": {
"alert": { "alert": {
"title": "Connecté à cette instance", "title": "Connecté à cette instance",
"message": "Vous pouvez vous connecter à un autre compte, en maintenant un compte connecté existant" "message": "Vous pouvez vous connecter à un autre compte, en conservant le compte connecté existant"
} }
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"HTML": { "HTML": {
"accessibilityHint": "Appuyez pour agrandir ou réduire le contenu", "accessibilityHint": "Touchez pour développer ou réduire le contenu",
"expanded": "{{hint}}{{moreLines}}", "expanded": "{{hint}}{{moreLines}}",
"moreLines": " ({{count}} lignes en plus)", "moreLines": " ({{count}} lignes en plus)",
"defaultHint": "Pouet long" "defaultHint": "Pouet long"

View File

@ -5,7 +5,7 @@
"button": "Réessayer" "button": "Réessayer"
}, },
"success": { "success": {
"message": "La chronologie est vide" "message": "Le fil est vide"
} }
}, },
"end": { "end": {
@ -31,8 +31,8 @@
"notification": "{{name}} a partagé votre message" "notification": "{{name}} a partagé votre message"
}, },
"update": "Le reblog a été modifié", "update": "Le reblog a été modifié",
"admin.sign_up": "", "admin.sign_up": "{{name}} a rejoint l'instance",
"admin.report": "" "admin.report": "{{name}} a signalé:"
}, },
"actions": { "actions": {
"reply": { "reply": {
@ -40,7 +40,7 @@
}, },
"reblogged": { "reblogged": {
"accessibilityLabel": "Partager ce pouet", "accessibilityLabel": "Partager ce pouet",
"function": "Pouet de Boost", "function": "Pouet de boost",
"options": { "options": {
"title": "Choisir la visibilité du boost", "title": "Choisir la visibilité du boost",
"public": "Boost public", "public": "Boost public",
@ -49,13 +49,13 @@
}, },
"favourited": { "favourited": {
"accessibilityLabel": "Ajouter ce pouet aux favoris", "accessibilityLabel": "Ajouter ce pouet aux favoris",
"function": "Mettre le pouet en favori" "function": "Pouet en favoris"
}, },
"bookmarked": { "bookmarked": {
"accessibilityLabel": "Ajouter ce pouet aux signets", "accessibilityLabel": "Ajouter ce pouet aux marque-pages",
"function": "Pouet de signet" "function": "Pouet en marque-pages"
}, },
"openReport": "" "openReport": "Ouvrir un rapport"
}, },
"actionsUsers": { "actionsUsers": {
"reblogged_by": { "reblogged_by": {
@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "Contenu masqué" "expandHint": "Contenu masqué"
}, },
"filtered": "Filtré: {{phrase}}.", "filtered": {
"reveal": "Montrer quand même",
"match_v1": "Filtré : {{phrase}}.",
"match_v2_one": "Filtré par {{filters}}.",
"match_v2_other": "Filtré par {{count}} filtres, {{filters}}."
},
"fullConversation": "Conversations lues", "fullConversation": "Conversations lues",
"translate": { "translate": {
"default": "Traduire", "default": "Traduire",
@ -104,7 +109,7 @@
"shared": { "shared": {
"account": { "account": {
"name": { "name": {
"accessibilityHint": "Nom de l'utilisateur" "accessibilityHint": "Nom d'affichage de l'utilisateur"
}, },
"account": { "account": {
"accessibilityHint": "Compte de l'utilisateur" "accessibilityHint": "Compte de l'utilisateur"
@ -119,7 +124,7 @@
}, },
"visibility": { "visibility": {
"direct": { "direct": {
"accessibilityLabel": "Envoyer un message direct" "accessibilityLabel": "Envoyer un message privé"
}, },
"private": { "private": {
"accessibilityLabel": "Visible uniquement pour les abonné·e·s" "accessibilityLabel": "Visible uniquement pour les abonné·e·s"

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "Protection de la confidentialité", "title": "Protection de la confidentialité",
"message": "Veuillez ne pas divulguer l'identité d'un autre utilisateur, tel que le nom d'utilisateur, l'avatar, etc. Merci!", "message": "Veuillez ne pas divulguer l'identité d'un autre utilisateur, tel que le nom d'utilisateur, l'avatar, etc. Merci!"
"button": "Confirmer"
}, },
"localCorrupt": { "localCorrupt": {
"message": "Session expirée, veuillez ré-essayer" "message": "Session expirée, veuillez ré-essayer"

View File

@ -12,7 +12,7 @@
"right": { "right": {
"button": { "button": {
"default": "Pouet", "default": "Pouet",
"conversation": "Pouet DM", "conversation": "Pouet MP",
"reply": "Réponse de pouet", "reply": "Réponse de pouet",
"deleteEdit": "Pouet", "deleteEdit": "Pouet",
"edit": "Pouet", "edit": "Pouet",
@ -24,8 +24,8 @@
"button": "Réessayer" "button": "Réessayer"
}, },
"removeReply": { "removeReply": {
"title": "Le pouet répondu est introuvable", "title": "Le pouet réponse est introuvable",
"description": "Le pouet répondu a peut-être été supprimé. Voulez-vous le supprimer de votre référence ?", "description": "Le pouet réponse a peut-être été supprimé. Voulez-vous le supprimer de votre référence ?",
"confirm": "Supprimer la référence" "confirm": "Supprimer la référence"
} }
} }
@ -62,7 +62,7 @@
} }
}, },
"emojis": { "emojis": {
"accessibilityHint": "Tapotez pour ajouter des émojis au pouet" "accessibilityHint": "Appuyez pour ajouter des émojis au pouet"
}, },
"poll": { "poll": {
"option": { "option": {
@ -90,7 +90,7 @@
} }
}, },
"expiration": { "expiration": {
"heading": "Validité", "heading": "Durée",
"options": { "options": {
"300": "5 minutes", "300": "5 minutes",
"1800": "30 minutes", "1800": "30 minutes",
@ -106,7 +106,7 @@
"actions": { "actions": {
"attachment": { "attachment": {
"accessibilityLabel": "Téléchargez une pièce-jointe", "accessibilityLabel": "Téléchargez une pièce-jointe",
"accessibilityHint": "La fonction de sondage sera désactivée lorsqu'il y a une pièce jointe", "accessibilityHint": "La fonction de sondage sera désactivée s'il y a une pièce jointe",
"failed": { "failed": {
"alert": { "alert": {
"title": "Le téléchargement a échoué", "title": "Le téléchargement a échoué",

View File

@ -3,15 +3,15 @@
"local": { "local": {
"name": "Suit", "name": "Suit",
"options": { "options": {
"showBoosts": "", "showBoosts": "Afficher les boosts",
"showReplies": "" "showReplies": "Afficher les réponses"
} }
}, },
"public": { "public": {
"segments": { "segments": {
"federated": "Fédéré", "federated": "Fédéré",
"local": "Local", "local": "Local",
"trending": "" "trending": "Tendance"
} }
}, },
"notifications": { "notifications": {
@ -28,7 +28,7 @@
"filters": { "filters": {
"accessibilityLabel": "Filtrer", "accessibilityLabel": "Filtrer",
"accessibilityHint": "Filtrer les types de notifications affichés", "accessibilityHint": "Filtrer les types de notifications affichés",
"title": "", "title": "Afficher les notifications",
"options": { "options": {
"follow": "$t(screenTabs:me.push.follow.heading)", "follow": "$t(screenTabs:me.push.follow.heading)",
"follow_request": "Demande d'abonnement", "follow_request": "Demande d'abonnement",
@ -55,7 +55,7 @@
"name": "Favoris" "name": "Favoris"
}, },
"followedTags": { "followedTags": {
"name": "" "name": "Hashtags suivis"
}, },
"fontSize": { "fontSize": {
"name": "Taille de la police de Pouet" "name": "Taille de la police de Pouet"
@ -67,25 +67,25 @@
"name": "Liste : {{list}}" "name": "Liste : {{list}}"
}, },
"listAccounts": { "listAccounts": {
"name": "" "name": "Utilisateurs dans la liste : {{list}}"
}, },
"listAdd": { "listAdd": {
"name": "" "name": "Créer une liste"
}, },
"listEdit": { "listEdit": {
"name": "" "name": "Modifier les détails de la liste"
}, },
"lists": { "lists": {
"name": "Listes" "name": "Listes"
}, },
"push": { "push": {
"name": "Push de Notification" "name": "Push de notification"
}, },
"profile": { "profile": {
"name": "Modifier le profil" "name": "Modifier le profil"
}, },
"profileName": { "profileName": {
"name": "Editer le nom d'affichage" "name": "Éditer le nom d'affichage"
}, },
"profileNote": { "profileNote": {
"name": "Éditer la description" "name": "Éditer la description"
@ -114,33 +114,33 @@
} }
}, },
"listAccounts": { "listAccounts": {
"heading": "", "heading": "Gérer les utilisateurs",
"error": "", "error": "Supprimer l'utilisateur de la liste",
"empty": "" "empty": "Aucun utilisateur ajouté dans cette liste"
}, },
"listEdit": { "listEdit": {
"heading": "", "heading": "Modifier les détails de la liste",
"title": "", "title": "Titre",
"repliesPolicy": { "repliesPolicy": {
"heading": "", "heading": "Montrer les réponses à :",
"options": { "options": {
"none": "", "none": "Personne",
"list": "", "list": "Un membre de la liste",
"followed": "" "followed": "N'importe quel utilisateur suivi"
} }
} }
}, },
"listDelete": { "listDelete": {
"heading": "", "heading": "Supprimer la liste",
"confirm": { "confirm": {
"title": "", "title": "Supprimer la liste \"{{list}}\" ?",
"message": "" "message": "Cette action ne peut être annulé."
} }
}, },
"profile": { "profile": {
"feedback": { "feedback": {
"succeed": "{{type}} mis à jour", "succeed": "{{type}} mis à jour",
"failed": "{{type}} Échec de la mise à jour, veuillez ré-essayer" "failed": "La mise à jour de {{type}} a échoué, veuillez réessayer"
}, },
"root": { "root": {
"name": { "name": {
@ -178,13 +178,13 @@
"description": "Nécessite que vous approuviez manuellement chaque abonné·e" "description": "Nécessite que vous approuviez manuellement chaque abonné·e"
}, },
"bot": { "bot": {
"title": "Compte Bot", "title": "Compte bot",
"description": "Ce compte effectue principalement des actions automatisées et peut ne pas être surveillé" "description": "Ce compte effectue principalement des actions automatisées et peut ne pas être surveillé"
} }
}, },
"fields": { "fields": {
"group": "Groupe {{index}}", "group": "Groupe {{index}}",
"label": "Étiquette", "label": "Libellé",
"content": "Contenu" "content": "Contenu"
}, },
"mediaSelectionFailed": "Le traitement de l'image a échoué. Veuillez réessayer." "mediaSelectionFailed": "Le traitement de l'image a échoué. Veuillez réessayer."
@ -196,8 +196,8 @@
"settings": "Activer dans les paramètres" "settings": "Activer dans les paramètres"
}, },
"missingServerKey": { "missingServerKey": {
"message": "", "message": "Le serveur n'est pas configuré pour le push",
"description": "" "description": "Veuillez contacter l'administrateur de votre serveur pour configurer le support push"
}, },
"global": { "global": {
"heading": "Activer pour {{acct}}", "heading": "Activer pour {{acct}}",
@ -205,13 +205,13 @@
}, },
"decode": { "decode": {
"heading": "Détails du message", "heading": "Détails du message",
"description": "Les messages acheminés par le serveur de tooot sont chiffrés, mais vous pouvez choisir de décoder le message sur le serveur. Le code source de notre serveur est open source et aucune politique de log." "description": "Les messages acheminés par le serveur de tooot sont chiffrés, mais vous pouvez choisir de décoder le message sur le serveur. Le code source de notre serveur est open source et il n'y a aucune politique de log."
}, },
"default": { "default": {
"heading": "Par défaut" "heading": "Par défaut"
}, },
"follow": { "follow": {
"heading": "Nouvel abonné" "heading": "Nouveau⋅elle abonné⋅e"
}, },
"follow_request": { "follow_request": {
"heading": "Demande d'abonnement" "heading": "Demande d'abonnement"
@ -229,16 +229,16 @@
"heading": "Mise à jour du sondage" "heading": "Mise à jour du sondage"
}, },
"status": { "status": {
"heading": "Pouet des utilisateurs inscrits" "heading": "Pouet des utilisateurs auxquels vous êtes abonnés"
}, },
"update": { "update": {
"heading": "" "heading": "Le reblog a été édité"
}, },
"admin.sign_up": { "admin.sign_up": {
"heading": "" "heading": "Admin : s'inscrire"
}, },
"admin.report": { "admin.report": {
"heading": "" "heading": "Admin : signalement"
}, },
"howitworks": "Apprenez comment cela fonctionne" "howitworks": "Apprenez comment cela fonctionne"
}, },
@ -261,7 +261,7 @@
"button": "Se déconnecter", "button": "Se déconnecter",
"alert": { "alert": {
"title": "Déconnexion?", "title": "Déconnexion?",
"message": "Après vous être déconnecté, vous devez vous reconnecter", "message": "Après vous être déconnecté, vous devrez vous reconnecter",
"buttons": { "buttons": {
"logout": "Déconnexion" "logout": "Déconnexion"
} }
@ -317,7 +317,7 @@
"contact": { "contact": {
"heading": "Contacter tooot" "heading": "Contacter tooot"
}, },
"version": "Version {{version}}", "version": "Version v{{version}}",
"instanceVersion": "Version de Mastodon v{{version}}" "instanceVersion": "Version de Mastodon v{{version}}"
}, },
"switch": { "switch": {
@ -346,12 +346,12 @@
"suspended": "Compte suspendu par les modérateurs de votre serveur" "suspended": "Compte suspendu par les modérateurs de votre serveur"
}, },
"accountInLists": { "accountInLists": {
"name": "", "name": "Listes de @{{username}}",
"inLists": "", "inLists": "Dans les listes",
"notInLists": "" "notInLists": "Autres listes"
}, },
"attachments": { "attachments": {
"name": "" "name": "Média de <0 /><1></1>"
}, },
"hashtag": { "hashtag": {
"follow": "Suivre", "follow": "Suivre",
@ -377,7 +377,7 @@
} }
}, },
"trending": { "trending": {
"tags": "" "tags": "Tags tendance"
} }
}, },
"sections": { "sections": {
@ -399,7 +399,7 @@
"reblogged_by": "{{count}} boosté", "reblogged_by": "{{count}} boosté",
"favourited_by": "{{count}} mis en favori" "favourited_by": "{{count}} mis en favori"
}, },
"resultIncomplete": "" "resultIncomplete": "Les résultats d'une instance distante sont incomplets"
} }
} }
} }

View File

@ -7,7 +7,8 @@
"continue": "Continua", "continue": "Continua",
"create": "", "create": "",
"delete": "", "delete": "",
"done": "" "done": "",
"confirm": "Ho capito"
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "Emoji personalizzata {{emoji}}" "accessibilityLabel": "Emoji personalizzata {{emoji}}"

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "Blocca utente", "action_false": "Blocca utente",
"action_true": "Sblocca utente" "action_true": "Sblocca utente",
"alert": {
"title": ""
}
}, },
"reports": { "reports": {
"action": "" "action": "",
"alert": {
"title": ""
}
} }
}, },
"at": { "at": {
@ -33,10 +39,7 @@
"action": "Blocca istanza {{instance}}", "action": "Blocca istanza {{instance}}",
"alert": { "alert": {
"title": "Confermi di voler bloccare l'istanza {{instance}}?", "title": "Confermi di voler bloccare l'istanza {{instance}}?",
"message": "Sarebbe meglio mutare o bloccare singoli utenti.\n\nSe blocchi un'istanza, tutti i suoi contenuti a te relativi, inclusi tutti i tuoi seguaci da questa, saranno rimossi.", "message": "Sarebbe meglio mutare o bloccare singoli utenti.\n\nSe blocchi un'istanza, tutti i suoi contenuti a te relativi, inclusi tutti i tuoi seguaci da questa, saranno rimossi."
"buttons": {
"confirm": "Ho capito"
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "Cancella toot", "action": "Cancella toot",
"alert": { "alert": {
"title": "Conferma?", "title": "Conferma?",
"message": "Tutti i retoot, gli apprezzamenti, e le risposte, saranno cancellati.", "message": "Tutti i retoot, gli apprezzamenti, e le risposte, saranno cancellati."
"buttons": {
"confirm": "Ho capito"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "Cancella e ripubblica toot", "action": "Cancella e ripubblica toot",
"alert": { "alert": {
"title": "Confermi cancellazione e ripubblicazione?", "title": "Confermi cancellazione e ripubblicazione?",
"message": "Tutti i retoot, gli apprezzamenti, e le risposte, saranno cancellati.", "message": "Tutti i retoot, gli apprezzamenti, e le risposte, saranno cancellati."
"buttons": {
"confirm": "Ho capito"
}
} }
}, },
"mute": { "mute": {

View File

@ -3,6 +3,7 @@
"textInput": { "textInput": {
"placeholder": "" "placeholder": ""
}, },
"whitelisted": "",
"button": "Accedi", "button": "Accedi",
"information": { "information": {
"name": "Nome", "name": "Nome",

View File

@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "Contenuto nascosto" "expandHint": "Contenuto nascosto"
}, },
"filtered": "Filtrato: {{phrase}}.", "filtered": {
"reveal": "",
"match_v1": "",
"match_v2_one": "",
"match_v2_other": ""
},
"fullConversation": "Leggi la conversazione", "fullConversation": "Leggi la conversazione",
"translate": { "translate": {
"default": "Traduci", "default": "Traduci",

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "Tutela della privacy", "title": "Tutela della privacy",
"message": "Per favore, non rivelare l'identità degli altri utenti (nome, foto profilo, ecc..). Grazie!", "message": "Per favore, non rivelare l'identità degli altri utenti (nome, foto profilo, ecc..). Grazie!"
"button": "Ho capito"
}, },
"localCorrupt": { "localCorrupt": {
"message": "La sessione è scaduta, devi riaccedere" "message": "La sessione è scaduta, devi riaccedere"

View File

@ -5,9 +5,10 @@
"cancel": "キャンセル", "cancel": "キャンセル",
"discard": "変更を破棄", "discard": "変更を破棄",
"continue": "続ける", "continue": "続ける",
"create": "", "create": "作成",
"delete": "削除", "delete": "削除",
"done": "完了" "done": "完了",
"confirm": "確認"
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "カスタム絵文字 {{emoji}}" "accessibilityLabel": "カスタム絵文字 {{emoji}}"

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "ユーザーをブロック", "action_false": "ユーザーをブロック",
"action_true": "ユーザーのブロックを解除" "action_true": "ユーザーのブロックを解除",
"alert": {
"title": ""
}
}, },
"reports": { "reports": {
"action": "ユーザーの報告とブロック" "action": "ユーザーの報告とブロック",
"alert": {
"title": ""
}
} }
}, },
"at": { "at": {
@ -33,10 +39,7 @@
"action": "インスタンスをブロック {{instance}}", "action": "インスタンスをブロック {{instance}}",
"alert": { "alert": {
"title": "インスタンス {{instance}} をブロックしますか?", "title": "インスタンス {{instance}} をブロックしますか?",
"message": "ほとんどの場合、特定のユーザーをミュートまたはブロックすることができます。\n\nインスタンスをブロックすると、このインスタンスからフォロワーを含むすべてのコンテンツが削除されます", "message": "ほとんどの場合、特定のユーザーをミュートまたはブロックすることができます。\n\nインスタンスをブロックすると、このインスタンスからフォロワーを含むすべてのコンテンツが削除されます"
"buttons": {
"confirm": "確定"
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "トゥートを削除", "action": "トゥートを削除",
"alert": { "alert": {
"title": "削除しますか?", "title": "削除しますか?",
"message": "この投稿へのすべてのお気に入り登録やブーストは消去され、すべての返信は孤立することになります。", "message": "この投稿へのすべてのお気に入り登録やブーストは消去され、すべての返信は孤立することになります。"
"buttons": {
"confirm": "確定"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "トゥートを削除し、再投稿する", "action": "トゥートを削除し、再投稿する",
"alert": { "alert": {
"title": "削除して再投稿しますか?", "title": "削除して再投稿しますか?",
"message": "この投稿へのすべてのお気に入り登録やブーストは消去され、すべての返信は孤立することになります。", "message": "この投稿へのすべてのお気に入り登録やブーストは消去され、すべての返信は孤立することになります。"
"buttons": {
"confirm": "確定"
}
} }
}, },
"mute": { "mute": {

View File

@ -3,6 +3,7 @@
"textInput": { "textInput": {
"placeholder": "インスタンスのドメイン" "placeholder": "インスタンスのドメイン"
}, },
"whitelisted": "",
"button": "ログイン", "button": "ログイン",
"information": { "information": {
"name": "名前", "name": "名前",

View File

@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "内容を非表示にする" "expandHint": "内容を非表示にする"
}, },
"filtered": "フィルター: {{phrase}}.", "filtered": {
"reveal": "",
"match_v1": "",
"match_v2_one": "",
"match_v2_other": ""
},
"fullConversation": "スレッドを読む", "fullConversation": "スレッドを読む",
"translate": { "translate": {
"default": "翻訳", "default": "翻訳",

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "プライバシー保護", "title": "プライバシー保護",
"message": "ユーザー名やアバターなど、他のユーザーを特定する情報は公開しないでください。", "message": "ユーザー名やアバターなど、他のユーザーを特定する情報は公開しないでください。"
"button": "確認"
}, },
"localCorrupt": { "localCorrupt": {
"message": "ログインの有効期限が切れました。もう一度ログインしてください。" "message": "ログインの有効期限が切れました。もう一度ログインしてください。"

View File

@ -5,9 +5,10 @@
"cancel": "취소", "cancel": "취소",
"discard": "취소", "discard": "취소",
"continue": "계속", "continue": "계속",
"create": "", "create": "생성",
"delete": "삭제", "delete": "삭제",
"done": "완료" "done": "완료",
"confirm": "확인"
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "커스텀 에모지 {{emoji}}" "accessibilityLabel": "커스텀 에모지 {{emoji}}"

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "사용자 차단", "action_false": "사용자 차단",
"action_true": "사용자 차단 해제" "action_true": "사용자 차단 해제",
"alert": {
"title": ""
}
}, },
"reports": { "reports": {
"action": "사용자 신고 및 차단" "action": "사용자 신고 및 차단",
"alert": {
"title": ""
}
} }
}, },
"at": { "at": {
@ -33,10 +39,7 @@
"action": "{{instance}} 인스턴스 차단", "action": "{{instance}} 인스턴스 차단",
"alert": { "alert": {
"title": "정말 {{instance}} 인스턴스를 차단할까요?", "title": "정말 {{instance}} 인스턴스를 차단할까요?",
"message": "보통은 사용자 뮤트나 차단으로 충분해요.\n\n인스턴스를 차단하면, 팔로워를 포함한 인스턴스의 모든 콘텐츠가 삭제됩니다!", "message": "보통은 사용자 뮤트나 차단으로 충분해요.\n\n인스턴스를 차단하면, 팔로워를 포함한 인스턴스의 모든 콘텐츠가 삭제됩니다!"
"buttons": {
"confirm": "확인"
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "툿 삭제", "action": "툿 삭제",
"alert": { "alert": {
"title": "정말 삭제할까요?", "title": "정말 삭제할까요?",
"message": "답장을 포함한 모든 부스트와 즐겨찾기가 지워져요.", "message": "답장을 포함한 모든 부스트와 즐겨찾기가 지워져요."
"buttons": {
"confirm": "확인"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "툿 삭제 후 다시 게시", "action": "툿 삭제 후 다시 게시",
"alert": { "alert": {
"title": "툿을 정말 삭제하고 다시 게시할까요?", "title": "툿을 정말 삭제하고 다시 게시할까요?",
"message": "답장을 포함한 모든 부스트와 즐겨찾기가 지워져요.", "message": "답장을 포함한 모든 부스트와 즐겨찾기가 지워져요."
"buttons": {
"confirm": "확인"
}
} }
}, },
"mute": { "mute": {

View File

@ -1,8 +1,9 @@
{ {
"server": { "server": {
"textInput": { "textInput": {
"placeholder": "" "placeholder": "인스턴스 도메인"
}, },
"whitelisted": "화이트리스트 등록이 필요한 인스턴스에서 tooot이 정보를 읽어올 수 없는 문제일 수 있습니다.",
"button": "로그인", "button": "로그인",
"information": { "information": {
"name": "이름", "name": "이름",

View File

@ -31,8 +31,8 @@
"notification": "{{name}} 님이 내 툿을 부스트했어요" "notification": "{{name}} 님이 내 툿을 부스트했어요"
}, },
"update": "부스트한 툿이 수정됨", "update": "부스트한 툿이 수정됨",
"admin.sign_up": "", "admin.sign_up": "{{name}} 님이 인스턴스에 가입함",
"admin.report": "" "admin.report": "{{name}} 님의 신고:"
}, },
"actions": { "actions": {
"reply": { "reply": {
@ -55,7 +55,7 @@
"accessibilityLabel": "툿 북마크에 추가", "accessibilityLabel": "툿 북마크에 추가",
"function": "툿 북마크" "function": "툿 북마크"
}, },
"openReport": "" "openReport": "신고 열기"
}, },
"actionsUsers": { "actionsUsers": {
"reblogged_by": { "reblogged_by": {
@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "숨겨진 콘텐츠" "expandHint": "숨겨진 콘텐츠"
}, },
"filtered": "필터: {{phrase}}.", "filtered": {
"reveal": "무시하고 보기",
"match_v1": "필터됨: {{phrase}}.",
"match_v2_one": "{{filters}}에 의해 필터됨.",
"match_v2_other": "{{count}}개의 필터 {{filters}}에 의해 필터됨."
},
"fullConversation": "대화 보기", "fullConversation": "대화 보기",
"translate": { "translate": {
"default": "번역", "default": "번역",

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "개인정보 보호", "title": "개인정보 보호",
"message": "다른 사용자의 이름이나, 프로필 사진 등의 정보를 유출하지 말아주세요. 고마워요!", "message": "다른 사용자의 이름이나, 프로필 사진 등의 정보를 유출하지 말아주세요. 고마워요!"
"button": "확인"
}, },
"localCorrupt": { "localCorrupt": {
"message": "로그인이 만료되었어요. 다시 로그인해주세요" "message": "로그인이 만료되었어요. 다시 로그인해주세요"

View File

@ -3,15 +3,15 @@
"local": { "local": {
"name": "팔로우 중", "name": "팔로우 중",
"options": { "options": {
"showBoosts": "", "showBoosts": "부스트 표시",
"showReplies": "" "showReplies": "답글 표시"
} }
}, },
"public": { "public": {
"segments": { "segments": {
"federated": "연합", "federated": "연합",
"local": "로컬", "local": "로컬",
"trending": "" "trending": "유행"
} }
}, },
"notifications": { "notifications": {
@ -28,7 +28,7 @@
"filters": { "filters": {
"accessibilityLabel": "필터", "accessibilityLabel": "필터",
"accessibilityHint": "받는 알림 종류 선택", "accessibilityHint": "받는 알림 종류 선택",
"title": "", "title": "알림 표시",
"options": { "options": {
"follow": "$t(screenTabs:me.push.follow.heading)", "follow": "$t(screenTabs:me.push.follow.heading)",
"follow_request": "팔로우 요청", "follow_request": "팔로우 요청",
@ -55,7 +55,7 @@
"name": "즐겨찾기" "name": "즐겨찾기"
}, },
"followedTags": { "followedTags": {
"name": "" "name": "팔로우 중인 해시태그"
}, },
"fontSize": { "fontSize": {
"name": "툿 폰트 크기" "name": "툿 폰트 크기"
@ -235,10 +235,10 @@
"heading": "부스트한 툿이 수정됨" "heading": "부스트한 툿이 수정됨"
}, },
"admin.sign_up": { "admin.sign_up": {
"heading": "" "heading": "관리자: 신규 가입"
}, },
"admin.report": { "admin.report": {
"heading": "" "heading": "관리자: 신고 요청"
}, },
"howitworks": "메시지 라우팅 방식 더 알아보기" "howitworks": "메시지 라우팅 방식 더 알아보기"
}, },
@ -399,7 +399,7 @@
"reblogged_by": "{{count}} 부스트", "reblogged_by": "{{count}} 부스트",
"favourited_by": "{{count}} 즐겨찾기" "favourited_by": "{{count}} 즐겨찾기"
}, },
"resultIncomplete": "" "resultIncomplete": "원격 인스턴스의 응답 형태가 올바르지 않아요"
} }
} }
} }

View File

@ -7,7 +7,8 @@
"continue": "Ga verder", "continue": "Ga verder",
"create": "Maak", "create": "Maak",
"delete": "Verwijder", "delete": "Verwijder",
"done": "Gereed" "done": "Gereed",
"confirm": "Bevestig"
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "Aangepaste emoji {{emoji}}" "accessibilityLabel": "Aangepaste emoji {{emoji}}"

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "Gebruiker blokkeren", "action_false": "Gebruiker blokkeren",
"action_true": "Gebruiker deblokkeren" "action_true": "Gebruiker deblokkeren",
"alert": {
"title": "Bevestig blokkeren van @{{username}} ?"
}
}, },
"reports": { "reports": {
"action": "Rapporteren en blokkeren" "action": "Rapporteren en blokkeren",
"alert": {
"title": "Bevestig rapporteren en blokkeren van @{{username}} ?"
}
} }
}, },
"at": { "at": {
@ -33,10 +39,7 @@
"action": "Blokkeer instantie {{instance}}", "action": "Blokkeer instantie {{instance}}",
"alert": { "alert": {
"title": "Bevestig blokkeren van {{instance}} ?", "title": "Bevestig blokkeren van {{instance}} ?",
"message": "Meestal kunt u bepaalde gebruiker dempen of blokkeren.\n\nNa het blokkeren van de instantie, wordt alle inhoud inclusief volgers van deze instantie verwijderd!", "message": "Meestal kunt u bepaalde gebruiker dempen of blokkeren.\n\nNa het blokkeren van de instantie, wordt alle inhoud inclusief volgers van deze instantie verwijderd!"
"buttons": {
"confirm": "Bevestig"
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "Toot verwijderen", "action": "Toot verwijderen",
"alert": { "alert": {
"title": "Verwijderen bevestigen?", "title": "Verwijderen bevestigen?",
"message": "Alle boosts en favorieten zullen worden gewist, inclusief alle antwoorden.", "message": "Alle boosts en favorieten zullen worden gewist, inclusief alle antwoorden."
"buttons": {
"confirm": "Bevestig"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "Toot verwijderen en opnieuw plaatsen", "action": "Toot verwijderen en opnieuw plaatsen",
"alert": { "alert": {
"title": "Bevestig verwijderen en opnieuw plaatsen?", "title": "Bevestig verwijderen en opnieuw plaatsen?",
"message": "Alle boosts en favorieten zullen worden gewist, inclusief alle antwoorden.", "message": "Alle boosts en favorieten zullen worden gewist, inclusief alle antwoorden."
"buttons": {
"confirm": "Bevestig"
}
} }
}, },
"mute": { "mute": {

View File

@ -3,6 +3,7 @@
"textInput": { "textInput": {
"placeholder": "Domeinnaam van instantie" "placeholder": "Domeinnaam van instantie"
}, },
"whitelisted": "Dit kan een gewhiteliste instantie zijn waarvan tooot geen gegevens kan ophalen voordat er ingelogd is.",
"button": "Inloggen", "button": "Inloggen",
"information": { "information": {
"name": "Naam", "name": "Naam",

View File

@ -91,7 +91,12 @@
"content": { "content": {
"expandHint": "Verborgen inhoud" "expandHint": "Verborgen inhoud"
}, },
"filtered": "Gefilterd: {{phrase}}.", "filtered": {
"reveal": "Toch weergeven",
"match_v1": "Gefilterd: {{phrase}}.",
"match_v2_one": "Gefilterd door {{filters}}.",
"match_v2_other": "Gefilterd door {{count}} filters, {{filters}}."
},
"fullConversation": "Gesprekken lezen", "fullConversation": "Gesprekken lezen",
"translate": { "translate": {
"default": "Vertaal", "default": "Vertaal",

View File

@ -1,8 +1,7 @@
{ {
"screenshot": { "screenshot": {
"title": "Privacy Bescherming", "title": "Privacy Bescherming",
"message": "Gelieve de identiteit van een andere gebruiker niet openbaar te maken, zoals gebruikersnaam of avatar en meer. Bedankt!", "message": "Gelieve de identiteit van een andere gebruiker niet openbaar te maken, zoals gebruikersnaam of avatar en meer. Bedankt!"
"button": "Bevestig"
}, },
"localCorrupt": { "localCorrupt": {
"message": "Sessie verlopen. Log opnieuw in" "message": "Sessie verlopen. Log opnieuw in"

View File

@ -7,7 +7,8 @@
"continue": "Dalej", "continue": "Dalej",
"create": "", "create": "",
"delete": "Usuń", "delete": "Usuń",
"done": "" "done": "",
"confirm": ""
}, },
"customEmoji": { "customEmoji": {
"accessibilityLabel": "Własne emoji {{emoji}}" "accessibilityLabel": "Własne emoji {{emoji}}"

View File

@ -13,10 +13,16 @@
}, },
"block": { "block": {
"action_false": "Zablokuj użytkownika", "action_false": "Zablokuj użytkownika",
"action_true": "Odblokuj użytkownika" "action_true": "Odblokuj użytkownika",
"alert": {
"title": ""
}
}, },
"reports": { "reports": {
"action": "Zgłoś i zablokuj" "action": "Zgłoś i zablokuj",
"alert": {
"title": ""
}
} }
}, },
"at": { "at": {
@ -33,10 +39,7 @@
"action": "Zablokuj instancję {{instance}}", "action": "Zablokuj instancję {{instance}}",
"alert": { "alert": {
"title": "Na pewno zablokować {{instance}}?", "title": "Na pewno zablokować {{instance}}?",
"message": "Zazwyczaj wycisza się (albo blokuje) konkretnych użytkowników. \n\nGdy zablokujesz instancję, cała jej zawartość (włączając np. obserwujące Cię osoby, które do niej należą) zostanie usunięta!", "message": "Zazwyczaj wycisza się (albo blokuje) konkretnych użytkowników. \n\nGdy zablokujesz instancję, cała jej zawartość (włączając np. obserwujące Cię osoby, które do niej należą) zostanie usunięta!"
"buttons": {
"confirm": "Na pewno?"
}
} }
} }
}, },
@ -57,20 +60,14 @@
"action": "Usuń wpis", "action": "Usuń wpis",
"alert": { "alert": {
"title": "Na pewno usunąć?", "title": "Na pewno usunąć?",
"message": "Wszystkie podbite i polubione wpisy zostaną wyczyszczone - wraz z odpowiedziami.", "message": "Wszystkie podbite i polubione wpisy zostaną wyczyszczone - wraz z odpowiedziami."
"buttons": {
"confirm": "Na pewno?"
}
} }
}, },
"deleteEdit": { "deleteEdit": {
"action": "", "action": "",
"alert": { "alert": {
"title": "", "title": "",
"message": "", "message": ""
"buttons": {
"confirm": ""
}
} }
}, },
"mute": { "mute": {

Some files were not shown because too many files have changed in this diff Show More