Merge remote-tracking branch 'origin/develop' into settings-changed

* origin/develop: (48 commits)
  fix/leftover-emoji-checkboxes-in-settings
  Apply 1 suggestion(s) to 1 file(s)
  Translated using Weblate (Spanish)
  Translated using Weblate (Persian)
  Translated using Weblate (Persian)
  Translated using Weblate (Polish)
  update changelog
  Stop click propagation when unhiding nsfw
  Fix Follow Requests title style
  Translated using Weblate (Persian)
  Translated using Weblate (Persian)
  Translated using Weblate (French)
  Added translation using Weblate (Persian)
  Translated using Weblate (Chinese (Traditional))
  Translated using Weblate (Chinese (Simplified))
  Translated using Weblate (Italian)
  Translated using Weblate (English)
  Translated using Weblate (English)
  Translated using Weblate (Basque)
  Translated using Weblate (Spanish)
  ...
This commit is contained in:
Henry Jameson 2020-10-17 19:24:07 +03:00
commit 29ff0be92c
45 changed files with 1774 additions and 139 deletions

View file

@ -4,18 +4,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- New option to optimize timeline rendering to make the site more responsive (enabled by default)
## [Unreleased patch]
### Fixed
- Fixed chats list not updating its order when new messages come in
- Fixed chat messages sometimes getting lost when you receive a message at the same time
- Fixed chat messages sometimes getting lost when you receive a message at the same time
- Fixed clicking NSFW hider through status popover
### Added
- Import/export a muted users
## [2.1.1] - 2020-09-08
### Changed
- Polls will be hidden with status content if "Collapse posts with subjects" is enabled and the post is collapsed.
### Fixed
- Network fetches don't pile up anymore but wait for previous ones to finish to reduce throttling.
- Autocomplete won't stop at the second @, so it'll still work with "@lain@l" and not start over.
- Fixed weird autocomplete behavior when you write ":custom_emoji: ?"

View file

@ -279,7 +279,7 @@ input, textarea, .select, .input {
+ label::before {
flex-shrink: 0;
display: inline-block;
content: '';
content: '';
transition: color 200ms;
width: 1.1em;
height: 1.1em;

View file

@ -28,7 +28,7 @@
:href="attachment.url"
:alt="attachment.description"
:title="attachment.description"
@click.prevent="toggleHidden"
@click.prevent.stop="toggleHidden"
>
<img
:key="nsfwImage"
@ -80,6 +80,8 @@
class="video"
:attachment="attachment"
:controls="allowPlay"
@play="$emit('play')"
@pause="$emit('pause')"
/>
<i
v-if="!allowPlay"
@ -93,6 +95,8 @@
:alt="attachment.description"
:title="attachment.description"
controls
@play="$emit('play')"
@pause="$emit('pause')"
/>
<div

View file

@ -5,6 +5,7 @@ import ChatMessage from '../chat_message/chat_message.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
import ChatTitle from '../chat_title/chat_title.vue'
import chatService from '../../services/chat_service/chat_service.js'
import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
import { getScrollPosition, getNewTopPosition, isBottomedOut, scrollableContainerHeight } from './chat_layout_utils.js'
const BOTTOMED_OUT_OFFSET = 10
@ -246,7 +247,7 @@ const Chat = {
const fetchOlderMessages = !!maxId
const sinceId = fetchLatest && chatMessageService.maxId
this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId })
return this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId })
.then((messages) => {
// Clear the current chat in case we're recovering from a ws connection loss.
if (isFirstFetch) {
@ -287,7 +288,7 @@ const Chat = {
},
doStartFetching () {
this.$store.dispatch('startFetchingCurrentChat', {
fetcher: () => setInterval(() => this.fetchChat({ fetchLatest: true }), 5000)
fetcher: () => promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000)
})
this.fetchChat({ isFirstFetch: true })
},

View file

@ -44,7 +44,8 @@ const conversation = {
'isPage',
'pinnedStatusIdsObject',
'inProfile',
'profileUserId'
'profileUserId',
'virtualHidden'
],
created () {
if (this.isPage) {
@ -52,6 +53,13 @@ const conversation = {
}
},
computed: {
hideStatus () {
if (this.$refs.statusComponent && this.$refs.statusComponent[0]) {
return this.virtualHidden && this.$refs.statusComponent[0].suspendable
} else {
return this.virtualHidden
}
},
status () {
return this.$store.state.statuses.allStatusesObject[this.statusId]
},
@ -102,6 +110,10 @@ const conversation = {
},
isExpanded () {
return this.expanded || this.isPage
},
hiddenStyle () {
const height = (this.status && this.status.virtualHeight) || '120px'
return this.virtualHidden ? { height } : {}
}
},
components: {
@ -121,6 +133,12 @@ const conversation = {
if (value) {
this.fetchConversation()
}
},
virtualHidden (value) {
this.$store.dispatch(
'setVirtualHeight',
{ statusId: this.statusId, height: `${this.$el.clientHeight}px` }
)
}
},
methods: {

View file

@ -1,5 +1,7 @@
<template>
<div
v-if="!hideStatus"
:style="hiddenStyle"
class="Conversation"
:class="{ '-expanded' : isExpanded, 'panel' : isExpanded }"
>
@ -18,6 +20,7 @@
<status
v-for="status in conversation"
:key="status.id"
ref="statusComponent"
:inline-expanded="collapsable && isExpanded"
:statusoid="status"
:expandable="!isExpanded"
@ -33,6 +36,10 @@
@toggleExpanded="toggleExpanded"
/>
</div>
<div
v-else
:style="hiddenStyle"
/>
</template>
<script src="./conversation.js"></script>
@ -53,8 +60,8 @@
.conversation-status {
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-left: 4px solid $fallback--cRed;
border-left: 4px solid var(--cRed, $fallback--cRed);
border-left-color: $fallback--cRed;
border-left-color: var(--cRed, $fallback--cRed);
}
.conversation-status:last-child {

View file

@ -1,7 +1,9 @@
<template>
<div class="settings panel panel-default">
<div class="panel-heading">
{{ $t('nav.friend_requests') }}
<div class="title">
{{ $t('nav.friend_requests') }}
</div>
</div>
<div class="panel-body">
<FollowRequestCard

View file

@ -178,7 +178,7 @@
box-shadow: var(--inputShadow);
&.menu-checkbox-checked::after {
content: '';
content: '';
}
}

View file

@ -7,12 +7,12 @@
:to="{ name: timelinesRoute }"
:class="onTimelineRoute && 'router-link-active'"
>
<i class="button-icon icon-home-2" /> {{ $t("nav.timelines") }}
<i class="button-icon icon-home-2" />{{ $t("nav.timelines") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
<i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
<i class="button-icon icon-bell-alt" />{{ $t("nav.interactions") }}
</router-link>
</li>
<li v-if="currentUser && pleromaChatMessagesAvailable">
@ -23,12 +23,12 @@
>
{{ unreadChatCount }}
</div>
<i class="button-icon icon-chat" /> {{ $t("nav.chats") }}
<i class="button-icon icon-chat" />{{ $t("nav.chats") }}
</router-link>
</li>
<li v-if="currentUser && currentUser.locked">
<router-link :to="{ name: 'friend-requests' }">
<i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
<i class="button-icon icon-user-plus" />{{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
class="badge follow-request-count"
@ -39,7 +39,7 @@
</li>
<li>
<router-link :to="{ name: 'about' }">
<i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
<i class="button-icon icon-info-circled" />{{ $t("nav.about") }}
</router-link>
</li>
</ul>
@ -125,6 +125,10 @@
}
}
.nav-panel .button-icon {
margin-right: 0.5em;
}
.nav-panel .button-icon:before {
width: 1.1em;
}

View file

@ -1,5 +1,4 @@
import Popover from '../popover/popover.vue'
import { mapGetters } from 'vuex'
const ReactButton = {
props: ['status'],
@ -35,7 +34,9 @@ const ReactButton = {
}
return this.$store.state.instance.emoji || []
},
...mapGetters(['mergedConfig'])
mergedConfig () {
return this.$store.getters.mergedConfig
}
}
}

View file

@ -1,4 +1,3 @@
import { mapGetters } from 'vuex'
const RetweetButton = {
props: ['status', 'loggedIn', 'visibility'],
@ -28,7 +27,9 @@ const RetweetButton = {
'animate-spin': this.animated
}
},
...mapGetters(['mergedConfig'])
mergedConfig () {
return this.$store.getters.mergedConfig
}
}
}

View file

@ -1,6 +1,7 @@
import Importer from 'src/components/importer/importer.vue'
import Exporter from 'src/components/exporter/exporter.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import { mapState } from 'vuex'
const DataImportExportTab = {
data () {
@ -18,21 +19,26 @@ const DataImportExportTab = {
Checkbox
},
computed: {
user () {
return this.$store.state.users.currentUser
}
...mapState({
backendInteractor: (state) => state.api.backendInteractor,
user: (state) => state.users.currentUser
})
},
methods: {
getFollowsContent () {
return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
return this.backendInteractor.exportFriends({ id: this.user.id })
.then(this.generateExportableUsersContent)
},
getBlocksContent () {
return this.$store.state.api.backendInteractor.fetchBlocks()
return this.backendInteractor.fetchBlocks()
.then(this.generateExportableUsersContent)
},
getMutesContent () {
return this.backendInteractor.fetchMutes()
.then(this.generateExportableUsersContent)
},
importFollows (file) {
return this.$store.state.api.backendInteractor.importFollows({ file })
return this.backendInteractor.importFollows({ file })
.then((status) => {
if (!status) {
throw new Error('failed')
@ -40,7 +46,15 @@ const DataImportExportTab = {
})
},
importBlocks (file) {
return this.$store.state.api.backendInteractor.importBlocks({ file })
return this.backendInteractor.importBlocks({ file })
.then((status) => {
if (!status) {
throw new Error('failed')
}
})
},
importMutes (file) {
return this.backendInteractor.importMutes({ file })
.then((status) => {
if (!status) {
throw new Error('failed')

View file

@ -36,6 +36,23 @@
:export-button-label="$t('settings.block_export_button')"
/>
</div>
<div class="setting-item">
<h2>{{ $t('settings.mute_import') }}</h2>
<p>{{ $t('settings.import_mutes_from_a_csv_file') }}</p>
<Importer
:submit-handler="importMutes"
:success-message="$t('settings.mutes_imported')"
:error-message="$t('settings.mute_import_error')"
/>
</div>
<div class="setting-item">
<h2>{{ $t('settings.mute_export') }}</h2>
<Exporter
:get-content="getMutesContent"
filename="mutes.csv"
:export-button-label="$t('settings.mute_export_button')"
/>
</div>
</div>
</template>

View file

@ -58,6 +58,11 @@
{{ $t('settings.emoji_reactions_on_timeline') }}
</BooleanSetting>
</li>
<li>
<Checkbox v-model="virtualScrolling">
{{ $t('settings.virtual_scrolling') }}
</Checkbox>
</li>
</ul>
</div>

View file

@ -15,7 +15,6 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { muteWordHits } from '../../services/status_parser/status_parser.js'
import { unescape, uniqBy } from 'lodash'
import { mapGetters, mapState } from 'vuex'
const Status = {
name: 'Status',
@ -54,6 +53,8 @@ const Status = {
replying: false,
unmuted: false,
userExpanded: false,
mediaPlaying: [],
suspendable: true,
error: null
}
},
@ -157,7 +158,7 @@ const Status = {
return this.mergedConfig.hideFilteredStatuses
},
hideStatus () {
return this.deleted || (this.muted && this.hideFilteredStatuses)
return this.deleted || (this.muted && this.hideFilteredStatuses) || this.virtualHidden
},
isFocused () {
// retweet or root of an expanded conversation
@ -207,11 +208,18 @@ const Status = {
hidePostStats () {
return this.mergedConfig.hidePostStats
},
...mapGetters(['mergedConfig']),
...mapState({
betterShadow: state => state.interface.browserSupport.cssFilter,
currentUser: state => state.users.currentUser
})
currentUser () {
return this.$store.state.users.currentUser
},
betterShadow () {
return this.$store.state.interface.browserSupport.cssFilter
},
mergedConfig () {
return this.$store.getters.mergedConfig
},
isSuspendable () {
return !this.replying && this.mediaPlaying.length === 0
}
},
methods: {
visibilityIcon (visibility) {
@ -251,6 +259,12 @@ const Status = {
},
generateUserProfileLink (id, name) {
return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames)
},
addMediaPlaying (id) {
this.mediaPlaying.push(id)
},
removeMediaPlaying (id) {
this.mediaPlaying = this.mediaPlaying.filter(mediaId => mediaId !== id)
}
},
watch: {
@ -280,6 +294,9 @@ const Status = {
if (this.isFocused && this.statusFromGlobalRepository.favoritedBy && this.statusFromGlobalRepository.favoritedBy.length !== num) {
this.$store.dispatch('fetchFavs', this.status.id)
}
},
'isSuspendable': function (val) {
this.suspendable = val
}
},
filters: {

View file

@ -25,6 +25,11 @@ $status-margin: 0.75em;
--icon: var(--selectedPostIcon, $fallback--icon);
}
&.-conversation {
border-left-width: 4px;
border-left-style: solid;
}
.status-container {
display: flex;
padding: $status-margin;

View file

@ -16,7 +16,7 @@
/>
</div>
<template v-if="muted && !isPreview">
<div class="status-csontainer muted">
<div class="status-container muted">
<small class="status-username">
<i
v-if="muted && retweet"
@ -227,6 +227,7 @@
</span>
</a>
</StatusPopover>
<span
v-else
class="reply-to-no-popover"
@ -272,6 +273,8 @@
:no-heading="noHeading"
:highlight="highlight"
:focused="isFocused"
@mediaplay="addMediaPlaying($event)"
@mediapause="removeMediaPlaying($event)"
/>
<transition name="fade">
@ -354,6 +357,7 @@
@onSuccess="clearError"
/>
</div>
</div>
</div>
<div
@ -376,4 +380,5 @@
</template>
<script src="./status.js" ></script>
<style src="./status.scss" lang="scss"></style>

View file

@ -107,6 +107,8 @@
:attachment="attachment"
:allow-play="true"
:set-media="setMedia()"
@play="$emit('mediaplay', attachment.id)"
@pause="$emit('mediapause', attachment.id)"
/>
<gallery
v-if="galleryAttachments.length > 0"

View file

@ -19,14 +19,16 @@ const StillImage = {
},
methods: {
onLoad () {
this.imageLoadHandler && this.imageLoadHandler(this.$refs.src)
const image = this.$refs.src
if (!image) return
this.imageLoadHandler && this.imageLoadHandler(image)
const canvas = this.$refs.canvas
if (!canvas) return
const width = this.$refs.src.naturalWidth
const height = this.$refs.src.naturalHeight
const width = image.naturalWidth
const height = image.naturalHeight
canvas.width = width
canvas.height = height
canvas.getContext('2d').drawImage(this.$refs.src, 0, 0, width, height)
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
},
onError () {
this.imageLoadError && this.imageLoadError()

View file

@ -33,7 +33,8 @@ const Timeline = {
return {
paused: false,
unfocused: false,
bottomedOut: false
bottomedOut: false,
virtualScrollIndex: 0
}
},
components: {
@ -78,6 +79,16 @@ const Timeline = {
},
pinnedStatusIdsObject () {
return keyBy(this.pinnedStatusIds)
},
statusesToDisplay () {
const amount = this.timeline.visibleStatuses.length
const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80))
const min = Math.max(0, this.virtualScrollIndex - statusesPerSide)
const max = Math.min(amount, this.virtualScrollIndex + statusesPerSide)
return this.timeline.visibleStatuses.slice(min, max).map(_ => _.id)
},
virtualScrollingEnabled () {
return this.$store.getters.mergedConfig.virtualScrolling
}
},
created () {
@ -85,7 +96,7 @@ const Timeline = {
const credentials = store.state.users.currentUser.credentials
const showImmediately = this.timeline.visibleStatuses.length === 0
window.addEventListener('scroll', this.scrollLoad)
window.addEventListener('scroll', this.handleScroll)
if (store.state.api.fetchers[this.timelineName]) { return false }
@ -104,9 +115,10 @@ const Timeline = {
this.unfocused = document.hidden
}
window.addEventListener('keydown', this.handleShortKey)
setTimeout(this.determineVisibleStatuses, 250)
},
destroyed () {
window.removeEventListener('scroll', this.scrollLoad)
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('keydown', this.handleShortKey)
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
@ -146,6 +158,48 @@ const Timeline = {
}
})
}, 1000, this),
determineVisibleStatuses () {
if (!this.$refs.timeline) return
if (!this.virtualScrollingEnabled) return
const statuses = this.$refs.timeline.children
const cappedScrollIndex = Math.max(0, Math.min(this.virtualScrollIndex, statuses.length - 1))
if (statuses.length === 0) return
const height = Math.max(document.body.offsetHeight, window.pageYOffset)
const centerOfScreen = window.pageYOffset + (window.innerHeight * 0.5)
// Start from approximating the index of some visible status by using the
// the center of the screen on the timeline.
let approxIndex = Math.floor(statuses.length * (centerOfScreen / height))
let err = statuses[approxIndex].getBoundingClientRect().y
// if we have a previous scroll index that can be used, test if it's
// closer than the previous approximation, use it if so
const virtualScrollIndexY = statuses[cappedScrollIndex].getBoundingClientRect().y
if (Math.abs(err) > virtualScrollIndexY) {
approxIndex = cappedScrollIndex
err = virtualScrollIndexY
}
// if the status is too far from viewport, check the next/previous ones if
// they happen to be better
while (err < -20 && approxIndex < statuses.length - 1) {
err += statuses[approxIndex].offsetHeight
approxIndex++
}
while (err > window.innerHeight + 100 && approxIndex > 0) {
approxIndex--
err -= statuses[approxIndex].offsetHeight
}
// this status is now the center point for virtual scrolling and visible
// statuses will be nearby statuses before and after it
this.virtualScrollIndex = approxIndex
},
scrollLoad (e) {
const bodyBRect = document.body.getBoundingClientRect()
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
@ -155,6 +209,10 @@ const Timeline = {
this.fetchOlderStatuses()
}
},
handleScroll: throttle(function (e) {
this.determineVisibleStatuses()
this.scrollLoad(e)
}, 200),
handleVisibilityChange () {
this.unfocused = document.hidden
}

View file

@ -32,7 +32,10 @@
</div>
</div>
<div :class="classes.body">
<div class="timeline">
<div
ref="timeline"
class="timeline"
>
<template v-for="statusId in pinnedStatusIds">
<conversation
v-if="timeline.statusesObject[statusId]"
@ -54,6 +57,7 @@
:collapsable="true"
:in-profile="inProfile"
:profile-user-id="userId"
:virtual-hidden="virtualScrollingEnabled && !statusesToDisplay.includes(status.id)"
/>
</template>
</div>

View file

@ -138,15 +138,11 @@
&:last-child {
border: none;
}
i {
margin: 0 0.5em;
}
}
a {
display: block;
padding: 0.6em 0;
padding: 0.6em 0.65em;
&:hover {
background-color: $fallback--lightBg;
@ -174,6 +170,10 @@
text-decoration: underline;
}
}
i {
margin-right: 0.5em;
}
}
}

View file

@ -156,8 +156,7 @@
.user-profile-field {
display: flex;
margin: 0.25em auto;
max-width: 32em;
margin: 0.25em;
border: 1px solid var(--border, $fallback--border);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);

View file

@ -3,27 +3,48 @@ const VideoAttachment = {
props: ['attachment', 'controls'],
data () {
return {
loopVideo: this.$store.getters.mergedConfig.loopVideo
blocksSuspend: false,
// Start from true because removing "loop" property seems buggy in Vue
hasAudio: true
}
},
computed: {
loopVideo () {
if (this.$store.getters.mergedConfig.loopVideoSilentOnly) {
return !this.hasAudio
}
return this.$store.getters.mergedConfig.loopVideo
}
},
methods: {
onVideoDataLoad (e) {
onPlaying (e) {
this.setHasAudio(e)
if (this.loopVideo) {
this.$emit('play', { looping: true })
return
}
this.$emit('play')
},
onPaused (e) {
this.$emit('pause')
},
setHasAudio (e) {
const target = e.srcElement || e.target
// If hasAudio is false, we've already marked this video to not have audio,
// a video can't gain audio out of nowhere so don't bother checking again.
if (!this.hasAudio) return
if (typeof target.webkitAudioDecodedByteCount !== 'undefined') {
// non-zero if video has audio track
if (target.webkitAudioDecodedByteCount > 0) {
this.loopVideo = this.loopVideo && !this.$store.getters.mergedConfig.loopVideoSilentOnly
}
} else if (typeof target.mozHasAudio !== 'undefined') {
// true if video has audio track
if (target.mozHasAudio) {
this.loopVideo = this.loopVideo && !this.$store.getters.mergedConfig.loopVideoSilentOnly
}
} else if (typeof target.audioTracks !== 'undefined') {
if (target.audioTracks.length > 0) {
this.loopVideo = this.loopVideo && !this.$store.getters.mergedConfig.loopVideoSilentOnly
}
if (target.webkitAudioDecodedByteCount > 0) return
}
if (typeof target.mozHasAudio !== 'undefined') {
// true if video has audio track
if (target.mozHasAudio) return
}
if (typeof target.audioTracks !== 'undefined') {
if (target.audioTracks.length > 0) return
}
this.hasAudio = false
}
}
}

View file

@ -7,7 +7,8 @@
:alt="attachment.description"
:title="attachment.description"
playsinline
@loadeddata="onVideoDataLoad"
@playing="onPlaying"
@pause="onPaused"
/>
</template>

View file

@ -9,7 +9,8 @@
"scope_options": "Opcions d'abast i visibilitat",
"text_limit": "Límit de text",
"title": "Funcionalitats",
"who_to_follow": "A qui seguir"
"who_to_follow": "A qui seguir",
"pleroma_chat_messages": "Xat de Pleroma"
},
"finder": {
"error_fetching_user": "No s'ha pogut carregar l'usuari/a",
@ -17,7 +18,21 @@
},
"general": {
"apply": "Aplica",
"submit": "Desa"
"submit": "Desa",
"close": "Tanca",
"verify": "Verifica",
"confirm": "Confirma",
"enable": "Habilita",
"disable": "Deshabilitar",
"cancel": "Cancel·la",
"show_less": "Mostra menys",
"show_more": "Mostra més",
"optional": "opcional",
"retry": "Prova de nou",
"error_retry": "Si us plau, prova de nou",
"generic_error": "Hi ha hagut un error",
"loading": "Carregant…",
"more": "Més"
},
"login": {
"login": "Inicia sessió",
@ -25,7 +40,12 @@
"password": "Contrasenya",
"placeholder": "p.ex.: Maria",
"register": "Registra't",
"username": "Nom d'usuari/a"
"username": "Nom d'usuari/a",
"recovery_code": "Codi de recuperació",
"enter_recovery_code": "Posa un codi de recuperació",
"authentication_code": "Codi d'autenticació",
"hint": "Entra per participar a la conversa",
"description": "Entra amb OAuth"
},
"nav": {
"chat": "Xat local públic",
@ -33,7 +53,16 @@
"mentions": "Mencions",
"public_tl": "Flux públic del node",
"timeline": "Flux personal",
"twkn": "Flux de la xarxa coneguda"
"twkn": "Flux de la xarxa coneguda",
"chats": "Xats",
"timelines": "Línies de temps",
"preferences": "Preferències",
"who_to_follow": "A qui seguir",
"search": "Cerca",
"dms": "Missatges directes",
"interactions": "Interaccions",
"back": "Enrere",
"administration": "Administració"
},
"notifications": {
"broken_favorite": "No es coneix aquest estat. S'està cercant.",
@ -42,14 +71,19 @@
"load_older": "Carrega més notificacions",
"notifications": "Notificacions",
"read": "Read!",
"repeated_you": "ha repetit el teu estat"
"repeated_you": "ha repetit el teu estat",
"migrated_to": "migrat a",
"no_more_notifications": "No més notificacions",
"follow_request": "et vol seguir"
},
"post_status": {
"account_not_locked_warning": "El teu compte no està {0}. Qualsevol persona pot seguir-te per llegir les teves entrades reservades només a seguidores.",
"account_not_locked_warning_link": "bloquejat",
"attachments_sensitive": "Marca l'adjunt com a delicat",
"content_type": {
"text/plain": "Text pla"
"text/plain": "Text pla",
"text/markdown": "Markdown",
"text/html": "HTML"
},
"content_warning": "Assumpte (opcional)",
"default": "Em sento…",
@ -60,7 +94,13 @@
"private": "Només seguidors/es - Publica només per comptes que et segueixin",
"public": "Pública - Publica als fluxos públics",
"unlisted": "Silenciosa - No la mostris en fluxos públics"
}
},
"scope_notice": {
"private": "Aquesta entrada serà visible només per a qui et segueixi",
"public": "Aquesta entrada serà visible per a tothom"
},
"preview_empty": "Buida",
"preview": "Vista prèvia"
},
"registration": {
"bio": "Presentació",
@ -68,7 +108,17 @@
"fullname": "Nom per mostrar",
"password_confirm": "Confirma la contrasenya",
"registration": "Registra't",
"token": "Codi d'invitació"
"token": "Codi d'invitació",
"validations": {
"password_confirmation_match": "hauria de ser la mateixa que la contrasenya",
"password_confirmation_required": "no es pot deixar en blanc",
"password_required": "no es pot deixar en blanc",
"email_required": "no es pot deixar en blanc",
"fullname_required": "no es pot deixar en blanc",
"username_required": "no es pot deixar en blanc"
},
"fullname_placeholder": "p. ex. Lain Iwakura",
"username_placeholder": "p. ex. lain"
},
"settings": {
"attachmentRadius": "Adjunts",
@ -94,7 +144,7 @@
"data_import_export_tab": "Importa o exporta dades",
"default_vis": "Abast per defecte de les entrades",
"delete_account": "Esborra el compte",
"delete_account_description": "Esborra permanentment el teu compte i tots els missatges",
"delete_account_description": "Esborra permanentment les teves dades i desactiva el teu compte.",
"delete_account_error": "No s'ha pogut esborrar el compte. Si continua el problema, contacta amb l'administració del node.",
"delete_account_instructions": "Confirma que vols esborrar el compte escrivint la teva contrasenya aquí sota.",
"export_theme": "Desa el tema",
@ -164,7 +214,57 @@
"values": {
"false": "no",
"true": "sí"
}
},
"show_moderator_badge": "Mostra una insígnia de Moderació en el meu perfil",
"show_admin_badge": "Mostra una insígnia d'Administració en el meu perfil",
"hide_followers_description": "No mostris qui m'està seguint",
"hide_follows_description": "No mostris a qui segueixo",
"notification_visibility_emoji_reactions": "Reaccions",
"new_email": "Nou correu electrònic",
"profile_fields": {
"value": "Contingut",
"name": "Etiqueta",
"add_field": "Afegeix un camp",
"label": "Metadades del perfil"
},
"mutes_tab": "Silenciaments",
"interface": "Interfície",
"instance_default_simple": "(per defecte)",
"checkboxRadius": "Caselles",
"import_blocks_from_a_csv_file": "Importa bloquejos des d'un arxiu csv",
"hide_post_stats": "Amaga les estadístiques de les entrades (p. ex. el nombre de favorits)",
"use_one_click_nsfw": "Obre els adjunts NSFW amb només un clic",
"hide_muted_posts": "Amaga les entrades de comptes silenciats",
"avatar_size_instruction": "La mida mínima recomanada per la imatge de l'avatar és de 150x150 píxels.",
"domain_mutes": "Dominis",
"discoverable": "Permet la descoberta d'aquest compte en resultats de cerques i altres serveis",
"mutes_and_blocks": "Silenciaments i bloquejos",
"composing": "Composant",
"chatMessageRadius": "Missatge de xat",
"changed_email": "Correu electrònic canviat amb èxit!",
"change_email_error": "Hi ha hagut un problema al canviar el teu correu electrònic.",
"change_email": "Canvia el correu electrònic",
"bot": "Aquest és un compte automatitzat",
"blocks_tab": "Bloquejos",
"blocks_imported": "Bloquejos importats! Processar-los pot trigar una mica.",
"block_import_error": "Error al importar bloquejos",
"block_import": "Importa bloquejos",
"block_export_button": "Exporta els teus bloquejos a un arxiu csv",
"block_export": "Exporta bloquejos",
"allow_following_move": "Permet el seguiment automàtic quan un compte a qui seguim es mou",
"mfa": {
"scan": {
"secret_code": "Clau"
},
"authentication_methods": "Mètodes d'autenticació",
"waiting_a_recovery_codes": "Rebent còpies de seguretat dels codis…",
"recovery_codes": "Codis de recuperació.",
"warning_of_generate_new_codes": "Quan generes nous codis de recuperació, els antics ja no funcionaran més.",
"generate_new_recovery_codes": "Genera nous codis de recuperació"
},
"enter_current_password_to_confirm": "Posar la contrasenya actual per confirmar la teva identitat",
"security": "Seguretat",
"app_name": "Nom de l'aplicació"
},
"time": {
"day": "{0} dia",
@ -232,5 +332,75 @@
"who_to_follow": {
"more": "More",
"who_to_follow": "A qui seguir"
},
"selectable_list": {
"select_all": "Selecciona-ho tot"
},
"remote_user_resolver": {
"error": "No trobat.",
"searching_for": "Cercant per"
},
"interactions": {
"load_older": "Carrega antigues interaccions",
"favs_repeats": "Repeticions i favorits"
},
"emoji": {
"stickers": "Adhesius"
},
"polls": {
"expired": "L'enquesta va acabar fa {0}",
"expires_in": "L'enquesta acaba en {0}",
"multiple_choices": "Múltiples opcions",
"single_choice": "Una sola opció",
"type": "Tipus d'enquesta",
"vote": "Vota",
"votes": "vots",
"option": "Opció",
"add_option": "Afegeix opció",
"add_poll": "Afegeix enquesta"
},
"media_modal": {
"next": "Següent",
"previous": "Anterior"
},
"importer": {
"error": "Ha succeït un error mentre s'importava aquest arxiu.",
"success": "Importat amb èxit."
},
"image_cropper": {
"cancel": "Cancel·la",
"save_without_cropping": "Desa sense retallar",
"save": "Desa",
"crop_picture": "Retalla la imatge"
},
"exporter": {
"processing": "Processant, aviat se't preguntarà per descarregar el teu arxiu",
"export": "Exporta"
},
"domain_mute_card": {
"mute_progress": "Silenciant…",
"mute": "Silencia"
},
"about": {
"staff": "Equip responsable",
"mrf": {
"simple": {
"quarantine_desc": "Aquesta instància només enviarà entrades públiques a les següents instàncies:",
"quarantine": "Quarantena",
"reject_desc": "Aquesta instància no acceptarà missatges de les següents instàncies:",
"reject": "Rebutja",
"accept_desc": "Aquesta instància només accepta missatges de les següents instàncies:",
"accept": "Accepta",
"simple_policies": "Polítiques específiques de la instància"
},
"mrf_policies_desc": "Les polítiques MRF controlen el comportament federat de la instància. Les següents polítiques estan habilitades:",
"mrf_policies": "Polítiques MRF habilitades",
"keyword": {
"replace": "Reemplaça",
"reject": "Rebutja",
"keyword_policies": "Polítiques de paraules clau"
},
"federation": "Federació"
}
}
}

View file

@ -275,6 +275,12 @@
"block_import": "Block import",
"block_import_error": "Error importing blocks",
"blocks_imported": "Blocks imported! Processing them will take a while.",
"mute_export": "Mute export",
"mute_export_button": "Export your mutes to a csv file",
"mute_import": "Mute import",
"mute_import_error": "Error importing mutes",
"mutes_imported": "Mutes imported! Processing them will take a while.",
"import_mutes_from_a_csv_file": "Import mutes from a csv file",
"blocks_tab": "Blocks",
"bot": "This is a bot account",
"btnRadius": "Buttons",
@ -429,6 +435,7 @@
"false": "no",
"true": "yes"
},
"virtual_scrolling": "Optimize timeline rendering",
"fun": "Fun",
"greentext": "Meme arrows",
"notifications": "Notifications",

View file

@ -551,7 +551,14 @@
"change_email_error": "Ha ocurrido un error al intentar modificar tu correo electrónico.",
"change_email": "Modificar el correo electrónico",
"bot": "Esta cuenta es un bot",
"allow_following_move": "Permitir el seguimiento automático, cuando la cuenta que sigues se traslada a otra instancia"
"allow_following_move": "Permitir el seguimiento automático, cuando la cuenta que sigues se traslada a otra instancia",
"virtual_scrolling": "Optimizar la representación de la linea temporal",
"import_mutes_from_a_csv_file": "Importar silenciados desde un archivo csv",
"mutes_imported": "¡Silenciados importados! Procesarlos llevará un tiempo.",
"mute_import_error": "Error al importar los silenciados",
"mute_import": "Importar silenciados",
"mute_export_button": "Exportar los silenciados a un archivo csv",
"mute_export": "Exportar silenciados"
},
"time": {
"day": "{0} día",
@ -762,7 +769,7 @@
"ftl_removal_desc": "Esta instancia elimina las siguientes instancias de la línea de tiempo \"Toda la red conocida\":",
"ftl_removal": "Eliminar de la línea de tiempo \"Toda La Red Conocida\"",
"quarantine_desc": "Esta instancia enviará solo publicaciones públicas a las siguientes instancias:",
"simple_policies": "Políticas sobre instancias específicas",
"simple_policies": "Políticas específicas de la instancia",
"reject_desc": "Esta instancia no aceptará mensajes de las siguientes instancias:",
"reject": "Rechazar",
"accept": "Aceptar"

View file

@ -13,7 +13,8 @@
"scope_options": "Ikusgaitasun aukerak",
"text_limit": "Testu limitea",
"title": "Ezaugarriak",
"who_to_follow": "Nori jarraitu"
"who_to_follow": "Nori jarraitu",
"pleroma_chat_messages": "Pleroma Txata"
},
"finder": {
"error_fetching_user": "Errorea erabiltzailea eskuratzen",
@ -31,7 +32,13 @@
"disable": "Ezgaitu",
"enable": "Gaitu",
"confirm": "Baieztatu",
"verify": "Egiaztatu"
"verify": "Egiaztatu",
"peek": "Begiratu",
"close": "Itxi",
"dismiss": "Baztertu",
"retry": "Saiatu berriro",
"error_retry": "Saiatu berriro mesedez",
"loading": "Kargatzen…"
},
"image_cropper": {
"crop_picture": "Moztu argazkia",
@ -81,7 +88,10 @@
"user_search": "Erabiltzailea Bilatu",
"search": "Bilatu",
"who_to_follow": "Nori jarraitu",
"preferences": "Hobespenak"
"preferences": "Hobespenak",
"chats": "Txatak",
"timelines": "Denbora-lerroak",
"bookmarks": "Laster-markak"
},
"notifications": {
"broken_favorite": "Egoera ezezaguna, bilatzen…",
@ -91,7 +101,10 @@
"notifications": "Jakinarazpenak",
"read": "Irakurrita!",
"repeated_you": "zure mezua errepikatu du",
"no_more_notifications": "Ez dago jakinarazpen gehiago"
"no_more_notifications": "Ez dago jakinarazpen gehiago",
"reacted_with": "{0}kin erreakzionatu zuen",
"migrated_to": "hona migratua:",
"follow_request": "jarraitu nahi zaitu"
},
"polls": {
"add_poll": "Inkesta gehitu",
@ -114,7 +127,8 @@
"search_emoji": "Bilatu emoji bat",
"add_emoji": "Emoji bat gehitu",
"custom": "Ohiko emojiak",
"unicode": "Unicode emojiak"
"unicode": "Unicode emojiak",
"load_all": "{emojiAmount} emoji guztiak kargatzen"
},
"stickers": {
"add_sticker": "Pegatina gehitu"
@ -226,7 +240,7 @@
"composing": "Idazten",
"confirm_new_password": "Baieztatu pasahitz berria",
"current_avatar": "Zure uneko avatarra",
"current_password": "Indarrean den pasahitza",
"current_password": "Indarrean dagoen pasahitza",
"current_profile_banner": "Zure profilaren banner-a",
"data_import_export_tab": "Datuak Inportatu / Esportatu",
"default_vis": "Lehenetsitako ikusgaitasunak",
@ -634,9 +648,40 @@
"about": {
"mrf": {
"keyword": {
"keyword_policies": "Gako-hitz politika"
"keyword_policies": "Gako-hitz politika",
"ftl_removal": "\"Ezagutzen den Sarea\" denbora-lerrotik ezabatu",
"is_replaced_by": "→",
"replace": "Ordezkatuak",
"reject": "Ukatuak"
},
"federation": "Federazioa"
}
"federation": "Federazioa",
"simple": {
"media_nsfw_desc": "Instantzia honek hurrengo instantzien multimediak sentikorrak izatera behartzen ditu:",
"media_nsfw": "Behartu Multimedia Sentikor",
"media_removal_desc": "Instantzia honek atxikitutako multimedia hurrengo instantzietatik ezabatzen ditu:",
"media_removal": "Multimedia Ezabatu",
"ftl_removal_desc": "Instantzia honek hurrengo instantziak ezabatzen ditu \"Ezagutzen den Sarea\" denbora-lerrotik:",
"ftl_removal": "\"Ezagutzen den Sarea\" denbora-lerrotik ezabatu",
"quarantine_desc": "Instantzia honek soilik mezu publikoak bidaliko ditu instantzia hauetara:",
"quarantine": "Koarentena",
"reject_desc": "Instantzia honek ez ditu hurrengo instantzien mezuak onartuko:",
"reject": "Ukatuak",
"accept_desc": "Instantzia honek hurrengo instantzietako mezuak soilik onartzen ditu:",
"accept": "Onartu",
"simple_policies": "Gure instantziaren politika zehatzak"
},
"mrf_policies_desc": "MRF politikek instantzia honen federazioa manipulatzen dute gainerako instantziekin. Honako politika hauek daude gaituta:",
"mrf_policies": "Gaitutako MRF politikak"
},
"staff": "Arduradunak"
},
"domain_mute_card": {
"unmute_progress": "Isiltasuna kentzen…",
"unmute": "Isiltasuna kendu",
"mute_progress": "Isiltzen…",
"mute": "Isilarazi"
},
"shoutbox": {
"title": "Oihu-kutxa"
}
}

155
src/i18n/fa.json Normal file
View file

@ -0,0 +1,155 @@
{
"about": {
"mrf": {
"simple": {
"media_removal_desc": "این نمونه رسانه‌ی پیغام‌های نمونه‌های ذکر شده را حذف می‌کند:",
"ftl_removal_desc": "این نمونه،‌ نمونه‌های ذکر شده را از تایم‌لاین «تمام شبکه‌ شناخته شده» حذف می‌کند:",
"media_removal": "حذف رسانه",
"ftl_removal": "حذف از تایم‌لاین «تمام شبکه شناخته شده»",
"quarantine_desc": "این نمونه تنها پیغام‌های عمومی را به نمونه‌های ذکر شده پیغام ارسال می‌کند:",
"quarantine": "قرنطینه شده",
"reject_desc": "این نمونه از نمونه‌های ذکر شده پیغامی دریافت نمی‌کند:",
"reject": "رد کننده",
"accept_desc": "این نمونه تنها از نمونه‌های ذکر شده پیغام دریافت می‌کند:",
"simple_policies": "سیاست‌های مخصوص نمونه",
"accept": "دریافت کننده",
"media_nsfw_desc": "این نمونه،‌ رسانه نمونه‌های ذکر شده را به اجبار حساس می‌کند:",
"media_nsfw": "به اجبار حساس کردن رسانه"
},
"federation": "فدراسیون",
"mrf_policies_desc": "سیاست‌های MRF رفتار فدراسیون این نمونه را تغییر می‌دهد. سیاست‌هایی که در ادامه آمده اعمال شده است:",
"keyword": {
"reject": "رد کننده",
"replace": "جایگزین کننده",
"keyword_policies": "سیاست‌های واژگان کلیدی",
"is_replaced_by": "→",
"ftl_removal": "حذف از تایم‌لاین «تمام شبکه شناخته شده»"
},
"mrf_policies": "سیاست‌های MRF(وسیله بازنویسی پیغام) فعال شده"
},
"staff": "کارکنان"
},
"image_cropper": {
"crop_picture": "برش تصویر",
"cancel": "لغو",
"save_without_cropping": "ذخیره بدون برش",
"save": "ذخیره"
},
"notifications": {
"followed_you": "پیگیر شما شد",
"favorited_you": "پیغام شما را پسندید",
"broken_favorite": "پیغام ناشناخته، در حال جستجو…"
},
"nav": {
"chats": "گپ‌ها",
"timelines": "تایم‌لاین‌ها",
"preferences": "ترجیحات",
"who_to_follow": "چه کسانی را پیگیری کنیم",
"search": "جستجو",
"user_search": "جستجوی کاربر",
"bookmarks": "نشانک‌ها",
"twkn": "شبکه شناخته شده",
"timeline": "تایم‌لاین",
"public_tl": "تایم‌لاین عمومی",
"dms": "پیغام‌های مستقیم",
"interactions": "تعاملات",
"mentions": "نام بردن‌ها",
"friend_requests": "درخواست پیگیری",
"back": "قبلی",
"administration": "مدیریت",
"about": "درباره"
},
"features_panel": {
"who_to_follow": "چه کسانی را پیگیری کنیم",
"title": "ویژگی‌ها",
"text_limit": "محدودیت متن",
"scope_options": "تنظیمات حوزه",
"media_proxy": "پروکسی رسانه",
"gopher": "گوفر",
"pleroma_chat_messages": "گپ پلروما",
"chat": "گپ"
},
"media_modal": {
"next": "بعدی",
"previous": "قبلی"
},
"login": {
"heading": {
"recovery": "بازیابی دو مرحله‌ای",
"totp": "احراز هویت دو مرحله‌ای"
},
"enter_two_factor_code": "کد احراز هویت دو مرحله‌ای را وارد کنید",
"recovery_code": "کد بازیابی",
"enter_recovery_code": "کد بازیابی را وارد کنید",
"authentication_code": "کد احراز هویت",
"hint": "برای شرکت در گفتگو، وارد سامانه شوید",
"username": "نام کاربری",
"register": "ثبت نام",
"description": "ورود به سامانه از طریق OAuth",
"placeholder": "به عنوان مثال: lain",
"password": "رمز عبور",
"logout": "خروج از سامانه",
"login": "ورود به سامانه"
},
"importer": {
"error": "در حین بارگذاری فایل خطایی رخ داد.",
"success": "با موفقیت بارگذاری شد.",
"submit": "ارسال"
},
"general": {
"peek": "نگاه سریع",
"close": "بستن",
"verify": "تأیید",
"confirm": "تأیید",
"enable": "فعال",
"disable": "غیر فعال",
"cancel": "لغو",
"show_less": "کمتر نشان بده",
"show_more": "بیشتر نشان بده",
"optional": "اختیاری",
"retry": "دوباره امتحان کنید",
"error_retry": "لطفاً دوباره امتحان کنید",
"generic_error": "خطایی رخ داد",
"loading": "در حال بارگذاری…",
"more": "بیشتر",
"submit": "ارسال",
"apply": "اعمال"
},
"finder": {
"find_user": "جستجوی کاربر",
"error_fetching_user": "دریافت کاربر با خطا مواجه شد"
},
"exporter": {
"processing": "در حال پردازش، شما به زودی قادر به دانلود فایل خواهید بود",
"export": "صادر کردن"
},
"domain_mute_card": {
"unmute": "صدا دار",
"unmute_progress": "در حال صدا دار کردن …",
"mute_progress": "در حال بی صدا کردن…",
"mute": "بی صدا"
},
"shoutbox": {
"title": "چت باکس"
},
"display_date": {
"today": "امروز"
},
"file_type": {
"file": "فایل",
"image": "تصویر",
"video": "ویدئو",
"audio": "صدا"
},
"chats": {
"empty_chat_list_placeholder": "شما هنوز هیچ گپی ندارید، گپ جدیدی را آغاز کنید!",
"delete": "حذف",
"error_sending_message": "در حین ارسال پیغام خطایی رخ داد.",
"error_loading_chat": "در هنگام بارگذاری گپ خطایی رخ داد.",
"delete_confirm": "آیا از حذف این پیغام اطمینان دارید؟",
"more": "بیشتر",
"empty_message_error": "نمی‌توان پیغام خالی فرستاد",
"new": "گپ جدید",
"chats": "گپ‌ها"
}
}

View file

@ -13,7 +13,8 @@
"scope_options": "Options de visibilité",
"text_limit": "Limite de texte",
"title": "Caractéristiques",
"who_to_follow": "Personnes à suivre"
"who_to_follow": "Personnes à suivre",
"pleroma_chat_messages": "Chat Pleroma"
},
"finder": {
"error_fetching_user": "Erreur lors de la recherche de l'utilisateur·ice",
@ -32,7 +33,12 @@
"enable": "Activer",
"confirm": "Confirmer",
"verify": "Vérifier",
"dismiss": "Rejeter"
"dismiss": "Rejeter",
"peek": "Jeter un coup d'œil",
"close": "Fermer",
"retry": "Réessayez",
"error_retry": "Veuillez réessayer",
"loading": "Chargement…"
},
"image_cropper": {
"crop_picture": "Rogner l'image",
@ -77,15 +83,17 @@
"dms": "Messages directs",
"public_tl": "Fil d'actualité public",
"timeline": "Fil d'actualité",
"twkn": "Ensemble du réseau connu",
"twkn": "Réseau connu",
"user_search": "Recherche d'utilisateur·ice",
"who_to_follow": "Qui suivre",
"preferences": "Préférences",
"search": "Recherche",
"administration": "Administration"
"administration": "Administration",
"chats": "Chats",
"bookmarks": "Marques-Pages"
},
"notifications": {
"broken_favorite": "Chargement d'un message inconnu…",
"broken_favorite": "Message inconnu, chargement…",
"favorited_you": "a aimé votre statut",
"followed_you": "a commencé à vous suivre",
"load_older": "Charger les notifications précédentes",
@ -115,7 +123,7 @@
"text/bbcode": "BBCode"
},
"content_warning": "Sujet (optionnel)",
"default": "Écrivez ici votre prochain statut.",
"default": "Je viens d'atterrir en Tchéquie.",
"direct_warning_to_all": "Ce message sera visible pour toutes les personnes mentionnées.",
"direct_warning_to_first_only": "Ce message sera visible uniquement pour personnes mentionnées au début du message.",
"posting": "Envoi en cours",
@ -129,7 +137,12 @@
"private": "Abonné·e·s uniquement - Seul·e·s vos abonné·e·s verront vos billets",
"public": "Publique - Afficher dans les fils publics",
"unlisted": "Non-Listé - Ne pas afficher dans les fils publics"
}
},
"media_description_error": "Échec de téléversement du media, essayez encore",
"empty_status_error": "Impossible de poster un statut vide sans attachements",
"preview_empty": "Vide",
"preview": "Prévisualisation",
"media_description": "Description de l'attachement"
},
"registration": {
"bio": "Biographie",
@ -488,7 +501,15 @@
"notification_setting_privacy_option": "Masquer l'expéditeur et le contenu des notifications push",
"notification_setting_privacy": "Intimité",
"hide_followers_count_description": "Masquer le nombre d'abonnés",
"accent": "Accent"
"accent": "Accent",
"chatMessageRadius": "Message de chat",
"bot": "Ce compte est un robot",
"import_mutes_from_a_csv_file": "Importer les masquages depuis un fichier CSV",
"mutes_imported": "Masquages importés! Leur application peut prendre du temps.",
"mute_import_error": "Erreur à l'import des masquages",
"mute_import": "Import des masquages",
"mute_export_button": "Exporter vos masquages dans un fichier CSV",
"mute_export": "Export des masquages"
},
"timeline": {
"collapse": "Fermer",
@ -732,5 +753,11 @@
"return_home": "Retourner à la page d'accueil",
"too_many_requests": "Vos avez atteint la limite d'essais, essayez plus tard.",
"password_reset_required": "Vous devez changer votre mot de passe pour vous authentifier."
},
"errors": {
"storage_unavailable": "Pleroma n'a pas pu accéder au stockage du navigateur. Votre identifiant ou vos mots de passes ne seront sauvegardés et des problèmes pourront être rencontrés. Essayez d'activer les cookies."
},
"shoutbox": {
"title": "Shoutbox"
}
}

View file

@ -407,7 +407,14 @@
"reset_background_confirm": "Vuoi veramente azzerare lo sfondo?",
"chatMessageRadius": "Messaggi istantanei",
"notification_setting_hide_notification_contents": "Nascondi mittente e contenuti delle notifiche push",
"notification_setting_block_from_strangers": "Blocca notifiche da utenti che non segui"
"notification_setting_block_from_strangers": "Blocca notifiche da utenti che non segui",
"virtual_scrolling": "Velocizza l'elaborazione delle sequenze",
"import_mutes_from_a_csv_file": "Importa silenziati da un file CSV",
"mutes_imported": "Silenziati importati! Saranno elaborati a breve.",
"mute_import_error": "Errore nell'importazione",
"mute_import": "Importa silenziati",
"mute_export_button": "Esporta la tua lista di silenziati in un file CSV",
"mute_export": "Esporta silenziati"
},
"timeline": {
"error_fetching": "Errore nell'aggiornamento",

View file

@ -49,7 +49,8 @@
"scope_options": "Ustawienia zakresu",
"text_limit": "Limit tekstu",
"title": "Funkcje",
"who_to_follow": "Propozycje obserwacji"
"who_to_follow": "Propozycje obserwacji",
"pleroma_chat_messages": "Czat Pleromy"
},
"finder": {
"error_fetching_user": "Błąd przy pobieraniu profilu",
@ -71,7 +72,9 @@
"verify": "Zweryfikuj",
"close": "Zamknij",
"loading": "Ładowanie…",
"retry": "Spróbuj ponownie"
"retry": "Spróbuj ponownie",
"peek": "Spójrz",
"error_retry": "Spróbuj ponownie"
},
"image_cropper": {
"crop_picture": "Przytnij obrazek",
@ -117,12 +120,14 @@
"dms": "Wiadomości prywatne",
"public_tl": "Publiczna oś czasu",
"timeline": "Oś czasu",
"twkn": "Cała znana sieć",
"twkn": "Znana sieć",
"user_search": "Wyszukiwanie użytkowników",
"search": "Wyszukiwanie",
"who_to_follow": "Sugestie obserwacji",
"preferences": "Preferencje",
"bookmarks": "Zakładki"
"bookmarks": "Zakładki",
"chats": "Czaty",
"timelines": "Osie czasu"
},
"notifications": {
"broken_favorite": "Nieznany status, szukam go…",
@ -197,7 +202,9 @@
},
"preview_empty": "Pusty",
"preview": "Podgląd",
"empty_status_error": "Nie można wysłać pustego wpisu bez plików"
"empty_status_error": "Nie można wysłać pustego wpisu bez plików",
"media_description_error": "Nie udało się zaktualizować mediów, spróbuj ponownie",
"media_description": "Opis mediów"
},
"registration": {
"bio": "Bio",
@ -400,7 +407,7 @@
"theme_help_v2_1": "Możesz też zastąpić kolory i widoczność poszczególnych komponentów przełączając pola wyboru, użyj „Wyczyść wszystko” aby usunąć wszystkie zastąpienia.",
"theme_help_v2_2": "Ikony pod niektórych wpisami są wskaźnikami kontrastu pomiędzy tłem a tekstem, po najechaniu na nie otrzymasz szczegółowe informacje. Zapamiętaj, że jeżeli używasz przezroczystości, wskaźniki pokazują najgorszy możliwy przypadek.",
"tooltipRadius": "Etykiety/alerty",
"type_domains_to_mute": "Wpisz domeny, które chcesz wyciszyć",
"type_domains_to_mute": "Wyszukaj domeny, które chcesz wyciszyć",
"upload_a_photo": "Wyślij zdjęcie",
"user_settings": "Ustawienia użytkownika",
"values": {
@ -492,7 +499,8 @@
"tabs": "Karty",
"chat": {
"outgoing": "Wiadomości wychodzące",
"incoming": "Wiadomości przychodzące"
"incoming": "Wiadomości przychodzące",
"border": "Granica"
}
},
"radii": {
@ -573,7 +581,22 @@
"add_field": "Dodaj pole"
},
"bot": "To konto jest prowadzone przez bota",
"notification_setting_hide_notification_contents": "Ukryj nadawcę i zawartość powiadomień push"
"notification_setting_hide_notification_contents": "Ukryj nadawcę i zawartość powiadomień push",
"notification_setting_block_from_strangers": "Zablokuj powiadomienia od użytkowników których nie obserwujesz",
"virtual_scrolling": "Optymalizuj renderowanie osi czasu",
"reset_background_confirm": "Czy naprawdę chcesz zresetować tło?",
"reset_banner_confirm": "Czy naprawdę chcesz zresetować banner?",
"reset_avatar_confirm": "Czy naprawdę chcesz zresetować awatar?",
"reset_profile_banner": "Zresetuj banner profilowy",
"reset_profile_background": "Zresetuj tło profilowe",
"mutes_and_blocks": "Wyciszenia i blokady",
"chatMessageRadius": "Wiadomość czatu",
"import_mutes_from_a_csv_file": "Zaimportuj wyciszenia z pliku .csv",
"mutes_imported": "Zaimportowano wyciszenia! Przetwarzanie zajmie chwilę.",
"mute_import_error": "Wystąpił błąd podczas importowania wyciszeń",
"mute_import": "Import wyciszeń",
"mute_export_button": "Wyeksportuj swoje wyciszenia do pliku .csv",
"mute_export": "Eksport wyciszeń"
},
"time": {
"day": "{0} dzień",
@ -639,7 +662,11 @@
"unbookmark": "Usuń z zakładek",
"bookmark": "Dodaj do zakładek",
"hide_content": "Ukryj zawartość",
"show_content": "Pokaż zawartość"
"show_content": "Pokaż zawartość",
"hide_full_subject": "Ukryj cały temat",
"show_full_subject": "Pokaż cały temat",
"thread_muted_and_words": ", ma słowa:",
"thread_muted": "Wątek wyciszony"
},
"user_card": {
"approve": "Przyjmij",
@ -723,7 +750,8 @@
"add_reaction": "Dodaj reakcję",
"user_settings": "Ustawienia użytkownika",
"accept_follow_request": "Akceptuj prośbę o możliwość obserwacji",
"reject_follow_request": "Odrzuć prośbę o możliwość obserwacji"
"reject_follow_request": "Odrzuć prośbę o możliwość obserwacji",
"bookmark": "Zakładka"
},
"upload": {
"error": {
@ -773,9 +801,17 @@
"error_sending_message": "Coś poszło nie tak podczas wysyłania wiadomości.",
"error_loading_chat": "Coś poszło nie tak podczas ładowania czatu.",
"empty_message_error": "Nie można wysłać pustej wiadomości",
"new": "Nowy czat"
"new": "Nowy czat",
"empty_chat_list_placeholder": "Nie masz jeszcze żadnych czatów. Zacznij nowy czat!",
"chats": "Czaty"
},
"display_date": {
"today": "Dzisiaj"
},
"shoutbox": {
"title": "Shoutbox"
},
"errors": {
"storage_unavailable": "Pleroma nie mogła uzyskać dostępu do pamięci masowej przeglądarki. Twój login lub lokalne ustawienia nie zostaną zapisane i możesz napotkać problemy. Spróbuj włączyć ciasteczka."
}
}

View file

@ -11,9 +11,10 @@
"gopher": "Gopher",
"media_proxy": "媒体代理",
"scope_options": "可见范围设置",
"text_limit": "文本长度限制",
"text_limit": "文字數量限制",
"title": "功能",
"who_to_follow": "推荐关注"
"who_to_follow": "推荐关注",
"pleroma_chat_messages": "Pleroma 聊天"
},
"finder": {
"error_fetching_user": "获取用户时发生错误",
@ -32,7 +33,12 @@
"enable": "启用",
"confirm": "确认",
"verify": "验证",
"dismiss": "忽略"
"dismiss": "忽略",
"peek": "窥探",
"close": "关闭",
"retry": "重试",
"error_retry": "请重试",
"loading": "载入中…"
},
"image_cropper": {
"crop_picture": "裁剪图片",
@ -77,12 +83,15 @@
"dms": "私信",
"public_tl": "公共时间线",
"timeline": "时间线",
"twkn": "所有已知网络",
"twkn": "已知网络",
"user_search": "用户搜索",
"search": "搜索",
"who_to_follow": "推荐关注",
"preferences": "偏好设置",
"administration": "管理员"
"administration": "管理员",
"chats": "聊天",
"timelines": "时间线",
"bookmarks": "书签"
},
"notifications": {
"broken_favorite": "未知的状态,正在搜索中…",
@ -132,7 +141,7 @@
"text/bbcode": "BBCode"
},
"content_warning": "主题(可选)",
"default": "刚刚抵达上海",
"default": "刚刚抵达洛杉矶",
"direct_warning_to_all": "本条内容只有被提及的用户能够看到。",
"direct_warning_to_first_only": "本条内容只有被在消息开始处提及的用户能够看到。",
"posting": "发送",
@ -146,7 +155,12 @@
"private": "仅关注者 - 只有关注了你的人能看到",
"public": "公共 - 发送到公共时间轴",
"unlisted": "不公开 - 不会发送到公共时间轴"
}
},
"preview_empty": "空的",
"preview": "预览",
"media_description": "媒体描述",
"media_description_error": "更新媒体失败,请重试",
"empty_status_error": "不能发布没有内容、没有附件的发文"
},
"registration": {
"bio": "简介",
@ -350,7 +364,17 @@
"clear_opacity": "清除透明度",
"load_theme": "加载主题",
"help": {
"upgraded_from_v2": "PleromaFE 已升级,主题会和你记忆中的不太一样。"
"upgraded_from_v2": "PleromaFE 已升级,主题会和你记忆中的不太一样。",
"older_version_imported": "您导入的文件来自旧版本的 FE。",
"future_version_imported": "您导入的文件来自更高版本的 FE。",
"v2_imported": "您导入的文件是旧版 FE 的。我们尽可能保持兼容性,但还是可能出现不一致的情况。",
"snapshot_source_mismatch": "版本冲突:很有可能是 FE 版本回滚后再次升级了,如果您使用旧版本的 FE 更改了主题那么您可能需要使用旧版本,否则请使用新版本。",
"migration_napshot_gone": "不知出于何种原因,主题快照缺失了,一些地方可能与您印象中的不符。",
"migration_snapshot_ok": "为保万无一失,加载了主题快照。您可以试着加载主题数据。",
"fe_downgraded": "PleromaFE 的版本回滚了。",
"fe_upgraded": "PleromaFE 的主题引擎随着版本更新升级了。",
"snapshot_missing": "在文件中没有主题快照,所以网站外观可能会与原来预想的不同。",
"snapshot_present": "主题快照已加载,因此所有的值均被覆盖。您可以改为加载主题的实际数据。"
},
"use_source": "新版本",
"use_snapshot": "老版本",
@ -389,7 +413,25 @@
"borders": "边框",
"buttons": "按钮",
"inputs": "输入框",
"faint_text": "灰度文字"
"faint_text": "灰度文字",
"chat": {
"border": "边框",
"outgoing": "发出的",
"incoming": "收到的"
},
"disabled": "禁用的",
"pressed": "按下的",
"highlight": "强调元素",
"selectedMenu": "选中的菜单项",
"selectedPost": "选中的发布内容",
"icons": "图标",
"poll": "投票统计图",
"popover": "提示框,菜单,弹出框",
"post": "发布内容/用户简介",
"alert_neutral": "中性",
"alert_warning": "警告",
"tabs": "标签页",
"underlay": "底衬"
},
"radii": {
"_tab_label": "圆角"
@ -470,7 +512,34 @@
"notification_visibility_emoji_reactions": "互动",
"notification_visibility_moves": "用户迁移",
"new_email": "新邮箱",
"emoji_reactions_on_timeline": "在时间线上显示表情符号互动"
"emoji_reactions_on_timeline": "在时间线上显示表情符号互动",
"notification_setting_hide_notification_contents": "隐藏推送通知中的发送者与内容信息",
"notification_setting_block_from_strangers": "屏蔽来自你没有关注的用户的通知",
"type_domains_to_mute": "搜索需要隐藏的域名",
"useStreamingApi": "实时接收发布以及通知",
"user_mutes": "用户",
"reset_background_confirm": "您确定要重置个人资料背景图吗?",
"reset_banner_confirm": "您确定要重置横幅图片吗?",
"reset_avatar_confirm": "您确定要重置头像吗?",
"reset_profile_banner": "重置横幅图片",
"reset_profile_background": "重置个人资料背景图",
"reset_avatar": "重置头像",
"hide_followers_count_description": "不显示关注者数量",
"profile_fields": {
"value": "内容",
"name": "标签",
"add_field": "添加字段",
"label": "个人资料元数据"
},
"accent": "强调色",
"pad_emoji": "从表情符号选择器插入表情符号时,在表情两侧插入空格",
"discoverable": "允许通过搜索检索等服务找到此账号",
"mutes_and_blocks": "隐藏与屏蔽",
"bot": "这是一个机器人账号",
"fun": "趣味",
"useStreamingApiWarning": "(不推荐使用,试验性,已知会跳过一些消息)",
"chatMessageRadius": "聊天消息",
"greentext": "Meme 箭头"
},
"time": {
"day": "{0} 天",
@ -516,7 +585,8 @@
"show_new": "显示新内容",
"up_to_date": "已是最新",
"no_more_statuses": "没有更多的状态",
"no_statuses": "没有状态更新"
"no_statuses": "没有状态更新",
"reload": "重新载入"
},
"status": {
"favorites": "收藏",
@ -529,7 +599,17 @@
"reply_to": "回复",
"replies_list": "回复:",
"mute_conversation": "隐藏对话",
"unmute_conversation": "对话取消隐藏"
"unmute_conversation": "对话取消隐藏",
"hide_content": "隐藏内容",
"show_content": "显示内容",
"hide_full_subject": "隐藏此部分标题",
"show_full_subject": "显示全部标题",
"thread_muted": "此系列消息已被隐藏",
"copy_link": "复制状态链接",
"status_unavailable": "状态不可取得",
"unbookmark": "取消书签",
"bookmark": "书签",
"thread_muted_and_words": ",含有过滤词:"
},
"user_card": {
"approve": "允许",
@ -583,7 +663,9 @@
},
"hidden": "已隐藏",
"show_repeats": "显示转发",
"hide_repeats": "隐藏转发"
"hide_repeats": "隐藏转发",
"message": "消息",
"mention": "提及"
},
"user_profile": {
"timeline_title": "用户时间线",
@ -610,7 +692,9 @@
"favorite": "收藏",
"user_settings": "用户设置",
"reject_follow_request": "拒绝关注请求",
"add_reaction": "添加互动"
"add_reaction": "添加互动",
"bookmark": "书签",
"accept_follow_request": "接受关注请求"
},
"upload": {
"error": {
@ -628,7 +712,7 @@
},
"search": {
"people": "人",
"hashtags": "Hashtags",
"hashtags": "话题标签",
"person_talking": "{count} 人正在讨论",
"people_talking": "{count} 人正在讨论",
"no_results": "没有搜索结果"
@ -641,7 +725,9 @@
"check_email": "检查你的邮箱,会有一个链接用于重置密码。",
"return_home": "回到首页",
"too_many_requests": "你触发了尝试的限制,请稍后再试。",
"password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。"
"password_reset_disabled": "密码重置已经被禁用。请联系你的实例管理员。",
"password_reset_required_but_mailer_is_disabled": "您必须重置密码,但是密码重置被禁用了。请联系您所在实例的管理员。",
"password_reset_required": "您必须重置密码才能登陆。"
},
"remote_user_resolver": {
"error": "未找到。",
@ -655,7 +741,9 @@
"custom": "自定义表情符号",
"add_emoji": "插入表情符号",
"search_emoji": "搜索表情符号",
"emoji": "表情符号"
"emoji": "表情符号",
"load_all": "加载所有表情符号(共 {emojiAmount} 个)",
"load_all_hint": "最先加载的 {saneAmount} 表情符号,加载全部表情符号可能会带来性能问题。"
},
"about": {
"mrf": {
@ -667,7 +755,12 @@
"accept_desc": "本实例只接收来自下列实例的消息:",
"simple_policies": "站规",
"accept": "接受",
"media_removal": "移除媒体"
"media_removal": "移除媒体",
"media_nsfw_desc": "本实例将来自以下实例的媒体强制设置为敏感内容:",
"media_nsfw": "强制设置媒体为敏感内容",
"media_removal_desc": "本实例移除了来自以下实例的媒体内容:",
"ftl_removal_desc": "该实例在从“全部已知网络”时间线上移除了:",
"ftl_removal": "从“全部已知网络”时间线上移除"
},
"mrf_policies_desc": "MRF 策略会影响本实例的互通行为。以下策略已启用:",
"mrf_policies": "已启动 MRF 策略",
@ -679,12 +772,41 @@
"reject": "拒绝"
},
"federation": "联邦"
}
},
"staff": "管理人员"
},
"domain_mute_card": {
"unmute_progress": "正在取消隐藏…",
"unmute": "取消隐藏",
"mute_progress": "隐藏中…",
"mute": "隐藏"
},
"errors": {
"storage_unavailable": "Pleroma 无法访问浏览器储存。您的登陆名以及本地设置将不会被保存,您可能遇到意外问题。请尝试启用 cookies。"
},
"shoutbox": {
"title": "留言板"
},
"display_date": {
"today": "今天"
},
"file_type": {
"file": "文件",
"image": "图片",
"video": "视频",
"audio": "音频"
},
"chats": {
"empty_chat_list_placeholder": "您还没有任何聊天记录。开始聊天吧!",
"error_sending_message": "发送消息时出了点问题。",
"error_loading_chat": "加载聊天时出了点问题。",
"delete_confirm": "您确实要删除此消息吗?",
"more": "更多",
"empty_message_error": "无法发布空消息",
"new": "新聊天",
"chats": "聊天",
"delete": "删除",
"message_user": "发消息给 {nickname}",
"you": "你:"
}
}

810
src/i18n/zh_Hant.json Normal file
View file

@ -0,0 +1,810 @@
{
"emoji": {
"unicode": "統一碼繪文字",
"custom": "自定義繪文字",
"add_emoji": "插入繪文字",
"search_emoji": "搜索繪文字",
"keep_open": "選擇器保持打開",
"emoji": "繪文字",
"stickers": "貼紙",
"load_all": "加載所有繪文字(共 {emojiAmount} 個)",
"load_all_hint": "最先加載的 {saneAmount} ,加載全部繪文字可能會帶來性能問題。"
},
"polls": {
"not_enough_options": "投票的選項太少",
"expired": "投票 {0} 前已結束",
"expires_in": "投票於 {0} 內結束",
"expiry": "投票期限",
"multiple_choices": "多選",
"single_choice": "單選",
"type": "問卷類型",
"vote": "投票",
"votes": "票",
"option": "選項",
"add_option": "增加選項",
"add_poll": "增加投票"
},
"notifications": {
"reacted_with": "和 {0} 互動過",
"migrated_to": "遷移到",
"no_more_notifications": "沒有更多的通知",
"repeated_you": "轉發了你的發文",
"read": "已閱!",
"notifications": "通知",
"load_older": "載入更早的通知",
"follow_request": "想要關注你",
"followed_you": "關注了你",
"favorited_you": "喜歡了你的發文",
"broken_favorite": "未知的狀態,正在搜索中…"
},
"nav": {
"chats": "聊天",
"timelines": "時間線",
"preferences": "偏好設置",
"who_to_follow": "推薦關注",
"search": "搜索",
"user_search": "用戶搜索",
"bookmarks": "書籤",
"twkn": "已知網絡",
"timeline": "時間線",
"public_tl": "公共時間線",
"dms": "私信",
"interactions": "互動",
"mentions": "提及",
"friend_requests": "關注請求",
"back": "後退",
"administration": "管理",
"about": "關於"
},
"media_modal": {
"next": "往後",
"previous": "往前"
},
"login": {
"heading": {
"recovery": "雙重因素恢復",
"totp": "雙重因素驗證"
},
"recovery_code": "恢復碼",
"enter_two_factor_code": "輸入一個雙重因素驗證碼",
"enter_recovery_code": "輸入一個恢復碼",
"authentication_code": "驗證碼",
"hint": "登錄後加入討論",
"username": "用戶名",
"register": "註冊",
"placeholder": "例:鈴音",
"password": "密碼",
"logout": "登出",
"description": "用 OAuth 登入",
"login": "登入"
},
"importer": {
"error": "導入此文件時出現一個錯誤。",
"success": "導入成功。",
"submit": "提交"
},
"image_cropper": {
"cancel": "取消",
"save_without_cropping": "保存不裁剪",
"save": "保存",
"crop_picture": "裁剪圖片"
},
"general": {
"peek": "窺視",
"close": "關閉",
"verify": "驗證",
"confirm": "確認",
"enable": "啟用",
"disable": "禁用",
"cancel": "取消",
"dismiss": "忽略",
"show_less": "收起",
"show_more": "展開",
"optional": "可選",
"retry": "再試",
"error_retry": "請再試",
"generic_error": "發生一個錯誤",
"loading": "載入中…",
"more": "更多",
"submit": "提交",
"apply": "應用"
},
"finder": {
"find_user": "尋找用戶",
"error_fetching_user": "獲取用戶時發生錯誤"
},
"features_panel": {
"who_to_follow": "推薦關注",
"title": "特色",
"text_limit": "文字數量限制",
"scope_options": "可見範圍設置",
"media_proxy": "媒體代理",
"pleroma_chat_messages": "Pleroma 聊天",
"chat": "聊天",
"gopher": "Gopher"
},
"exporter": {
"processing": "正在處理,稍後會提示您下載文件",
"export": "導出"
},
"domain_mute_card": {
"unmute_progress": "取消靜音中…",
"unmute": "取消靜音",
"mute_progress": "靜音中…",
"mute": "靜音"
},
"shoutbox": {
"title": "留言板"
},
"about": {
"staff": "職員",
"mrf": {
"simple": {
"media_nsfw_desc": "這個實例強迫以下實例的帖子媒體設定為敏感:",
"media_nsfw": "媒體強制設定為敏感",
"media_removal_desc": "這個實例移除以下實例的帖子媒體:",
"media_removal": "移除媒體",
"ftl_removal_desc": "這個實例在所有已知網絡中移除下列實例:",
"ftl_removal": "從所有已知網路中移除",
"quarantine_desc": "本實例只會把公開發文發送到下列實例:",
"quarantine": "隔離",
"reject_desc": "本實例不會接收來自下列實例的消息:",
"reject": "拒絕",
"accept_desc": "本實例只接收來自下列實例的消息:",
"simple_policies": "站規",
"accept": "接受"
},
"mrf_policies_desc": "MRF 策略會影響本實例的互通行為。以下策略已啟用:",
"keyword": {
"ftl_removal": "從“全部已知網絡”時間線上移除",
"replace": "取代",
"reject": "拒絕",
"is_replaced_by": "→",
"keyword_policies": "關鍵字政策"
},
"mrf_policies": "已啟用的MRF政策",
"federation": "聯邦"
}
},
"settings": {
"style": {
"common": {
"color": "顏色",
"contrast": {
"context": {
"18pt": "大字文本 (18pt+)",
"text": "文本"
},
"level": {
"aaa": "符合 AAA 等級準則(推薦)",
"aa": "符合 AA 等級準則(最低)",
"bad": "不符合任何輔助功能指南"
},
"hint": "對比度是 {ratio} 它 {level} {context}"
},
"opacity": "透明度"
},
"advanced_colors": {
"faint_text": "灰度文字",
"alert_error": "錯誤",
"badge_notification": "通知",
"alert": "提醒或警告背景色",
"_tab_label": "高级",
"alert_warning": "警告",
"alert_neutral": "中性",
"post": "帖子/用戶簡介",
"badge": "徽章背景",
"popover": "提示框,菜單,彈出框",
"panel_header": "面板標題",
"top_bar": "頂欄",
"borders": "邊框",
"buttons": "按鈕",
"inputs": "輸入框",
"underlay": "底襯",
"poll": "投票統計圖",
"icons": "圖標",
"highlight": "強調元素",
"pressed": "按下",
"selectedPost": "選中的帖子",
"selectedMenu": "選中的菜單項",
"disabled": "關閉",
"toggled": "切換",
"tabs": "標籤",
"chat": {
"incoming": "收到",
"outgoing": "發出",
"border": "邊框"
}
},
"preview": {
"header_faint": "這很正常",
"header": "預覽",
"content": "內容",
"error": "例子錯誤",
"button": "按鈕",
"text": "有堆 {0} 和 {1}",
"mono": "內容",
"input": "剛剛抵達洛杉磯.",
"faint_link": "有用的手冊",
"fine_print": "閱讀我們的 {0} ,然而什麼有用的也學不到!",
"checkbox": "我已經瀏覽了條款及細則",
"link": "一個很好的小鏈接"
},
"shadows": {
"override": "覆寫",
"_tab_label": "陰影和燈光",
"component": "組件",
"shadow_id": "陰影 #{value}",
"blur": "模糊",
"spread": "擴散",
"inset": "插圖",
"hintV3": "對於陰影,您還可以使用{0}表示法來使用其他顏色插槽。",
"filter_hint": {
"always_drop_shadow": "警告,此陰影設置會總是使用 {0} ,如果瀏覽器支持的話。",
"drop_shadow_syntax": "{0} 不支持參數 {1} 和關鍵詞 {2} 。",
"avatar_inset": "請注意組合兩個內部和非內部的陰影到頭像上,在透明頭像上可能會有意料之外的效果。",
"spread_zero": "陰影的擴散 > 0 會同設置成零一樣",
"inset_classic": "插入內部的陰影會使用 {0}"
},
"components": {
"panel": "面板",
"panelHeader": "面板標題",
"topBar": "頂欄",
"avatar": "用戶頭像(在個人資料欄)",
"avatarStatus": "用戶頭像(在帖子顯示欄)",
"popup": "彈窗和工具提示",
"button": "按鈕",
"buttonHover": "按鈕(懸停)",
"buttonPressed": "按鈕(按下)",
"buttonPressedHover": "按鈕(按下和懸停)",
"input": "輸入框"
}
},
"switcher": {
"use_snapshot": "舊版",
"load_theme": "載入主題",
"keep_color": "保留顏色",
"keep_shadows": "保留陰影",
"keep_opacity": "保留透明度",
"keep_roundness": "保留圓角",
"help": {
"migration_napshot_gone": "不知出於何種原因,主題快照缺失了,一些地方可能與您印象中的不符。",
"snapshot_source_mismatch": "版本衝突:很有可能是 FE 版本回滾後再次升級了,如果您使用舊版本的 FE 更改了主題那麼您可能需要使用舊版本,否則請使用新版本。",
"future_version_imported": "您導入的文件來自更高版本的 FE。",
"older_version_imported": "您導入的文件來自舊版本的 FE。",
"snapshot_missing": "在文件中沒有主題快照,所以網站外觀可能會與原來預想的不同。",
"fe_upgraded": "PleromaFE 的主題引擎隨著版本更新升級了。",
"fe_downgraded": "PleromaFE 的版本回滾了。",
"upgraded_from_v2": "PleromaFE 已升級,主題會和你記憶中的不太一樣。",
"v2_imported": "您導入的文件是舊版 FE 的。我們儘可能保持兼容性,但還是可能出現不一致的情況。",
"snapshot_present": "載入快照已加載,因此所有值均被覆蓋。 您可以改為載入主題實際數據。",
"migration_snapshot_ok": "為保萬無一失,載入了主題快照。您可以試著載入主題數據。"
},
"use_source": "新版本",
"keep_as_is": "保持原狀",
"clear_opacity": "清除透明度",
"clear_all": "清除全部",
"reset": "重置",
"keep_fonts": "保留字體",
"save_load_hint": "\"保留\" 選項在選擇或載入主題時保留當前設置的選項,在導出主題時還會存儲上述選項。當所有複選框未設置時,導出主題將保存所有內容。"
},
"fonts": {
"components": {
"interface": "界面",
"input": "輸入框",
"post": "發帖文字",
"postCode": "帖子中使用等間距文字(富文本)"
},
"_tab_label": "字體",
"help": "給用戶界面的元素選擇字體。選擇 “自選”的你必須輸入確切的字體名稱。",
"family": "字體名稱",
"size": "大小 (像素)",
"weight": "字重 (粗體))",
"custom": "自選"
},
"common_colors": {
"foreground_hint": "點擊”高級“ 標籤進行細緻的控制",
"main": "常用顏色",
"_tab_label": "共同",
"rgbo": "圖標,強調,徽章"
},
"radii": {
"_tab_label": "圓角"
}
},
"notification_setting_block_from_strangers": "屏蔽來自你沒有關注的用戶的通知",
"user_mutes": "用户",
"hide_followers_count_description": "不顯示關注者數量",
"no_rich_text_description": "不顯示富文本格式",
"notification_visibility_moves": "用戶遷移",
"notification_visibility_repeats": "轉發",
"notification_visibility_mentions": "提及",
"notification_visibility_likes": "點贊",
"interfaceLanguage": "界面語言",
"instance_default": "(默認:{value})",
"inputRadius": "輸入框",
"import_theme": "導入預置主題",
"import_followers_from_a_csv_file": "從 csv 文件中導入關注",
"import_blocks_from_a_csv_file": "從 csv 文件中導入封鎖黑名單名單",
"hide_filtered_statuses": "隱藏過濾的發文",
"lock_account_description": "你需要手動審核關注請求",
"loop_video": "循環視頻",
"loop_video_silent_only": "只循環沒有聲音的視頻例如Mastodon 裡的“GIF”",
"mutes_tab": "靜音",
"play_videos_in_modal": "在彈出框內播放視頻",
"profile_fields": {
"add_field": "添加字段",
"name": "標籤",
"value": "內容",
"label": "個人資料元數據"
},
"use_contain_fit": "生成縮略圖時不要裁剪附件",
"notification_visibility": "要顯示的通知類型",
"notification_visibility_follows": "關注",
"new_email": "新電郵",
"subject_line_mastodon": "比如mastodon: copy as is",
"reset_background_confirm": "您確定要重置個人資料背景圖嗎?",
"reset_banner_confirm": "您確定要重置橫幅圖片嗎?",
"reset_avatar_confirm": "您確定要重置頭像嗎?",
"reset_profile_banner": "重置橫幅圖片",
"reset_profile_background": "重置個人資料背景圖",
"reset_avatar": "重置頭像",
"discoverable": "允許通過搜索檢索等服務找到此賬號",
"delete_account_error": "刪除賬戶時發生錯誤,如果一直刪除不了,請聯繫實例管理員。",
"composing": "正在書寫",
"chatMessageRadius": "聊天訊息",
"mfa": {
"confirm_and_enable": "確認並啟用OTP",
"setup_otp": "設置OTP",
"otp": "OTP",
"wait_pre_setup_otp": "預設OTP",
"verify": {
"desc": "要啟用雙因素驗證,請把你的雙因素驗證 app 裡的數字輸入:"
},
"scan": {
"secret_code": "密鑰",
"desc": "使用你的雙因素驗證 app掃瞄這個二維碼或者輸入這些文字密鑰",
"title": "掃瞄"
},
"authentication_methods": "身份驗證方法",
"recovery_codes_warning": "抄寫這些號碼,或者保存在安全的地方。這些號碼不會再次顯示。如果你無法訪問你的 2FA app也丟失了你的恢復碼你的賬號就再也無法登錄了。",
"waiting_a_recovery_codes": "正在接收備份碼…",
"recovery_codes": "恢復碼。",
"warning_of_generate_new_codes": "當你生成新的恢復碼時,你的舊恢復碼就失效了。",
"generate_new_recovery_codes": "生成新的恢復碼",
"title": "雙因素驗證"
},
"new_password": "新密碼",
"name_bio": "名字及簡介",
"name": "名字",
"domain_mutes": "域名",
"delete_account_instructions": "在下面輸入密碼,以確認刪除帳戶。",
"delete_account_description": "永久刪除你的帳號和所有數據。",
"delete_account": "刪除帳戶",
"default_vis": "默認可見性範圍",
"data_import_export_tab": "數據導入/導出",
"mutes_and_blocks": "靜音與封鎖",
"current_password": "當前密碼",
"confirm_new_password": "確認新密碼",
"collapse_subject": "摺疊帶標題的內容",
"checkboxRadius": "複選框",
"instance_default_simple": "(默認)",
"interface": "界面",
"invalid_theme_imported": "您所選擇的主題文件不被 Pleroma 支持,因此主題未被修改。",
"limited_availability": "在您的瀏覽器中無法使用",
"links": "鏈接",
"changed_password": "成功修改了密碼!",
"change_password_error": "修改密碼的時候出了點問題。",
"change_password": "修改密碼",
"changed_email": "郵箱修改成功!",
"bot": "這是一個機器人賬號",
"change_email": "修改電子郵箱",
"cRed": "紅色(取消)",
"cOrange": "橙色(收藏)",
"cGreen": "綠色(轉發)",
"cBlue": "藍色(回覆,關注)",
"btnRadius": "按鈕",
"notification_visibility_emoji_reactions": "互動",
"no_blocks": "沒有封鎖",
"no_mutes": "沒有靜音",
"hide_follows_description": "不要顯示我所關注的人",
"hide_followers_description": "不要顯示關注我的人",
"hide_follows_count_description": "不顯示關注數",
"nsfw_clickthrough": "將敏感附件隱藏,點擊才能打開",
"valid_until": "有效期至",
"panelRadius": "面板",
"pause_on_unfocused": "在離開頁面時暫停時間線推送",
"notifications": "通知",
"notification_setting_filters": "過濾器",
"notification_setting_privacy": "隱私",
"notification_mutes": "要停止收到某個指定的用戶的通知,請使用靜音功能。",
"notification_blocks": "封鎖一個用戶會停掉所有他的通知,等同於取消關注。",
"enable_web_push_notifications": "啟用 web 推送通知",
"presets": "預置",
"profile_background": "個人背景圖",
"profile_banner": "橫幅圖片",
"profile_tab": "個人資料",
"radii_help": "設置界面邊緣的圓角 (單位:像素)",
"reply_visibility_all": "顯示所有回覆",
"autohide_floating_post_button": "自動隱藏新帖子的按鈕(移動設備)",
"saving_err": "保存設置時發生錯誤",
"saving_ok": "設置已保存",
"search_user_to_block": "搜索你想屏蔽的用戶",
"search_user_to_mute": "搜索你想要隱藏的用戶",
"security_tab": "安全",
"set_new_avatar": "設置新頭像",
"set_new_profile_background": "設置新的個人背景",
"set_new_profile_banner": "設置新的個人橫幅",
"settings": "設置",
"subject_input_always_show": "總是顯示主題框",
"subject_line_behavior": "回覆時複製主題",
"subject_line_email": "比如電郵: \"re: 主題\"",
"subject_line_noop": "不要複製",
"post_status_content_type": "發文內容類型",
"stop_gifs": "鼠標懸停時播放GIF",
"streaming": "開啟滾動到頂部時的自動推送",
"text": "文本",
"theme": "主題",
"theme_help": "使用十六進制代碼(#rrggbb來設置主題顏色。",
"theme_help_v2_1": "你也可以通過切換複選框來覆蓋某些組件的顏色和透明。使用“清除所有”來清楚所有覆蓋設置。",
"theme_help_v2_2": "某些條目下的圖標是背景或文本對比指示器,鼠標懸停可以獲取詳細信息。請記住,使用透明度來顯示最差的情況。",
"tooltipRadius": "提醒",
"upload_a_photo": "上傳照片",
"user_settings": "用戶設置",
"values": {
"false": "否",
"true": "是"
},
"avatar_size_instruction": "推薦的頭像圖片最小的尺寸是 150x150 像素。",
"emoji_reactions_on_timeline": "在時間線上顯示繪文字互動",
"export_theme": "導出預置主題",
"filtering": "過濾",
"filtering_explanation": "所有包含以下詞彙的內容都會被隱藏,一行一個",
"follow_export": "導出關注",
"follow_export_button": "將關注導出成 csv 文件",
"follow_import": "導入關注",
"follow_import_error": "導入關注時錯誤",
"follows_imported": "關注已導入!尚需要一些時間來處理。",
"hide_attachments_in_convo": "在對話中隱藏附件",
"hide_attachments_in_tl": "在時間線上隱藏附件",
"hide_muted_posts": "不顯示被靜音的用戶的帖子",
"max_thumbnails": "最多每個帖子所能顯示的縮略圖數量",
"hide_isp": "隱藏指定實例的面板",
"preload_images": "預載圖片",
"use_one_click_nsfw": "點擊一次以打開工作場所不適宜的附件",
"hide_post_stats": "隱藏帖子的統計數據(例如:收藏的次數)",
"hide_user_stats": "隱藏用戶的統計數據(例如:關注者的數量)",
"general": "通用",
"foreground": "前景",
"blocks_tab": "封鎖",
"blocks_imported": "封鎖黑名單導入成功!需要一點時間來處理。",
"block_import_error": "導入封鎖黑名單出錯",
"block_import": "封鎖黑名單導入",
"block_export_button": "導出你的封鎖黑名單到一個 csv 文件",
"block_export": "封鎖黑名單導出",
"bio": "簡介",
"background": "背景",
"avatarRadius": "頭像",
"avatarAltRadius": "頭像(通知)",
"avatar": "頭像",
"attachments": "附件",
"attachmentRadius": "附件",
"allow_following_move": "正在關注的賬號遷移時自動重新關注",
"enter_current_password_to_confirm": "輸入你當前密碼來確認你的身份",
"security": "安全",
"app_name": "App 名稱",
"change_email_error": "修改你的電子郵箱時發生錯誤。",
"type_domains_to_mute": "搜索需要隱藏的域名",
"pad_emoji": "從繪文字選擇器插入繪文字時,在繪文字兩側插入空格",
"useStreamingApi": "實時接收發佈以及通知",
"minimal_scopes_mode": "最小發文範圍",
"scope_copy": "回覆時的複製範圍(私信是總是複製的)",
"reply_visibility_self": "只顯示發送給我的回覆",
"reply_visibility_following": "只顯示發送給我的回覆/發送給我關注的用戶的回覆",
"replies_in_timeline": "時間線中的回覆",
"revoke_token": "撤消",
"show_admin_badge": "顯示管理徽章",
"accent": "強調色",
"greentext": "前文箭頭",
"show_moderator_badge": "顯示主持人徽章",
"oauth_tokens": "OAuth代幣",
"token": "代幣",
"refresh_token": "刷新代幣",
"useStreamingApiWarning": "(不推薦使用,實驗性的,已知跳過文章)",
"fun": "有趣",
"notification_setting_hide_notification_contents": "隱藏推送通知中的發送者與內容信息",
"version": {
"title": "版本",
"backend_version": "後端版本",
"frontend_version": "前端版本"
},
"virtual_scrolling": "優化時間線渲染",
"import_mutes_from_a_csv_file": "從CSV文件導入靜音",
"mutes_imported": "靜音導入了!處理它們將需要一段時間。",
"mute_import": "靜音導入",
"mute_import_error": "導入靜音時出錯",
"mute_export_button": "將靜音導出到csv文件",
"mute_export": "靜音導出"
},
"chats": {
"more": "更多",
"delete_confirm": "您確實要刪除此消息嗎?",
"error_loading_chat": "加載聊天時出了點問題。",
"error_sending_message": "發送消息時出了點問題。",
"empty_chat_list_placeholder": "您還沒有任何聊天記錄。 開始新的聊天!",
"new": "新聊天",
"empty_message_error": "無法發布空消息",
"you": "你:",
"message_user": "發消息給 {nickname}",
"delete": "刪除",
"chats": "聊天"
},
"file_type": {
"audio": "音頻",
"video": "視頻",
"image": "图片",
"file": "檔案"
},
"display_date": {
"today": "今天"
},
"status": {
"mute_conversation": "靜音對話",
"replies_list": "回覆:",
"reply_to": "回覆",
"pin": "在個人資料置頂",
"unpin": "取消在個人資料置頂",
"favorites": "喜歡",
"repeats": "轉發",
"delete": "刪除發文",
"pinned": "置頂",
"bookmark": "書籤",
"unbookmark": "取消書籤",
"delete_confirm": "你真的想要刪除這條發文嗎?",
"unmute_conversation": "對話取消靜音",
"status_unavailable": "發文不可取得",
"copy_link": "複製發文鏈接",
"thread_muted": "静音線程",
"show_full_subject": "顯示完整標題",
"thread_muted_and_words": ",有这些字:",
"hide_full_subject": "隱藏完整標題",
"show_content": "顯示內容",
"hide_content": "隱藏內容"
},
"time": {
"hours": "{0} 小時",
"days_short": "{0}天",
"day_short": "{0}天",
"days": "{0} 天",
"hour": "{0} 小时",
"hour_short": "{0}h",
"hours_short": "{0}h",
"years_short": "{0} y",
"now": "剛剛",
"day": "{0} 天",
"in_future": "還有 {0}",
"in_past": "{0} 之前",
"minute": "{0} 分鐘",
"minute_short": "{0} 分",
"minutes_short": "{0} 分",
"minutes": "{0} 分鐘",
"month": "{0} 月",
"months": "{0} 月",
"month_short": "{0} 月",
"months_short": "{0} 月",
"now_short": "剛剛",
"second": "{0} 秒",
"seconds": "{0} 秒",
"second_short": "{0} 秒",
"seconds_short": "{0} 秒",
"week": "{0}周",
"weeks": "{0}周",
"week_short": "{0}周",
"weeks_short": "{0}周",
"year": "{0} 年",
"years": "{0} 年",
"year_short": "{0}年"
},
"post_status": {
"media_description_error": "無法更新媒體,請重試",
"media_description": "媒體描述",
"scope": {
"unlisted": "不公開 - 不會發送到公共時間軸",
"public": "公共 - 發送到公共時間軸",
"private": "僅關注者 - 只有關注了你的人能看到",
"direct": "私信 - 只發送給被提及的用戶"
},
"scope_notice": {
"unlisted": "本條內容既不在公共時間線,也不會在所有已知網絡上可見",
"private": "關注你的人才能看到本條內容",
"public": "本條帖子可以被所有人看到"
},
"preview_empty": "空的",
"preview": "預覽",
"posting": "正在發送",
"direct_warning_to_first_only": "本條內容只有被在消息開始處提及的用戶能夠看到。",
"direct_warning_to_all": "本條內容只有被提及的用戶能夠看到。",
"account_not_locked_warning": "你的帳號沒有 {0}。任何人都可以關注你並瀏覽你的上鎖內容。",
"new_status": "發佈新發文",
"content_warning": "主題(可選)",
"content_type": {
"text/bbcode": "BBCode",
"text/markdown": "Markdown",
"text/html": "HTML",
"text/plain": "純文本"
},
"attachments_sensitive": "標記附件為敏感內容",
"account_not_locked_warning_link": "上鎖",
"default": "剛剛抵達洛杉磯。",
"empty_status_error": "無法發佈沒有附件的空發文"
},
"errors": {
"storage_unavailable": "Pleroma無法訪問瀏覽器存儲。您的登錄名或本地設置將不會保存您可能會遇到意外問題。嘗試啟用Cookie。"
},
"timeline": {
"error_fetching": "獲取更新時發生錯誤",
"conversation": "對話",
"no_retweet_hint": "這條內容僅關注者可見,或者是私信,因此不能轉發",
"collapse": "摺疊",
"load_older": "載入更早的發文",
"repeated": "已轉發",
"show_new": "顯示新內容",
"reload": "重新載入",
"up_to_date": "已是最新",
"no_more_statuses": "没有更多發文",
"no_statuses": "没有發文"
},
"interactions": {
"load_older": "載入更早的互動",
"moves": "用戶遷移",
"follows": "新的關注者",
"favs_repeats": "轉發和收藏"
},
"selectable_list": {
"select_all": "選擇全部"
},
"remote_user_resolver": {
"error": "未找到。",
"searching_for": "搜索",
"remote_user_resolver": "遠程用戶解析器"
},
"registration": {
"validations": {
"password_confirmation_match": "不能和密碼一樣",
"password_confirmation_required": "不能留空",
"password_required": "不能留空",
"email_required": "不能留空",
"fullname_required": "不能留空",
"username_required": "不能留空"
},
"fullname": "顯示名稱",
"bio_placeholder": "例如:\n你好我是玲音。\n我是一個住在日本郊區的動畫少女。你可能在 Wired 見過我。",
"fullname_placeholder": "例如:岩倉玲音",
"username_placeholder": "例如:玲音",
"new_captcha": "點擊圖片獲取新的驗證碼",
"captcha": "CAPTCHA",
"token": "邀請碼",
"registration": "註冊",
"password_confirm": "確認密碼",
"email": "電子郵箱",
"bio": "簡介"
},
"user_card": {
"its_you": "就是你!!",
"media": "媒體",
"per_day": "每天",
"remote_follow": "跨站關注",
"subscribe": "訂閱",
"mute_progress": "靜音中…",
"admin_menu": {
"delete_account": "刪除賬號",
"delete_user": "刪除用戶",
"delete_user_confirmation": "你確認嗎?此操作無法撤銷。",
"moderation": "調停",
"grant_admin": "賦予管理權限",
"revoke_admin": "撤銷管理權限",
"grant_moderator": "賦予主持人權限",
"revoke_moderator": "撤銷主持人權限",
"activate_account": "啟用賬號",
"deactivate_account": "關閉賬號",
"force_nsfw": "標記所有的帖子都是工作場合不適",
"strip_media": "從帖子裡刪除媒體文件",
"force_unlisted": "強制帖子為不公開",
"sandbox": "強制帖子為只有關注者可看",
"disable_remote_subscription": "禁止從遠程實例關注用戶",
"disable_any_subscription": "完全禁止關注用戶",
"quarantine": "從聯合實例中禁止用戶帖子"
},
"approve": "批准",
"block": "封鎖",
"blocked": "已封鎖!",
"deny": "拒絕",
"favorites": "喜歡",
"follow": "關注",
"follow_sent": "請求已發送!",
"follow_progress": "請求中…",
"follow_again": "再次發送請求?",
"follow_unfollow": "取消關注",
"followees": "正在關注",
"followers": "關注者",
"following": "正在關注!",
"follows_you": "關注了你!",
"hidden": "已隱藏",
"mention": "提及",
"message": "消息",
"mute": "靜音",
"muted": "已靜音",
"report": "報告",
"statuses": "發文",
"unsubscribe": "退訂",
"unblock": "取消封鎖",
"unblock_progress": "取消封鎖中…",
"block_progress": "封鎖中…",
"unmute": "取消靜音",
"unmute_progress": "取消靜音中…",
"hide_repeats": "隱藏轉發",
"show_repeats": "顯示轉發"
},
"user_profile": {
"timeline_title": "用戶時間線",
"profile_does_not_exist": "抱歉,此個人資料不存在。",
"profile_loading_error": "抱歉,載入個人資料時出錯。"
},
"user_reporting": {
"title": "報告 {0}",
"add_comment_description": "此報告會發送給你的實例管理員。你可以在下面提供更多詳細信息解釋報告的緣由:",
"forward_to": "轉發 {0}",
"submit": "提交",
"generic_error": "當處理你的請求時,發生了一個錯誤。",
"additional_comments": "其它評論",
"forward_description": "這個賬號是從另外一個服務器。同時發送一個報告到那裡?"
},
"who_to_follow": {
"more": "更多",
"who_to_follow": "推薦關注"
},
"tool_tip": {
"media_upload": "上傳多媒體",
"repeat": "轉發",
"favorite": "喜歡",
"add_reaction": "添加互動",
"reply": "回覆",
"user_settings": "用戶設置",
"accept_follow_request": "接受關注請求",
"reject_follow_request": "拒絕關注請求",
"bookmark": "書籤"
},
"upload": {
"file_size_units": {
"B": "B",
"KiB": "KiB",
"TiB": "TiB",
"MiB": "MiB",
"GiB": "GiB"
},
"error": {
"base": "上傳失敗。",
"file_too_big": "文件太大[{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
"default": "稍後再試"
}
},
"search": {
"people": "人",
"hashtags": "標籤",
"person_talking": "{count} 人正在討論",
"people_talking": "{count} 人正在討論",
"no_results": "沒有搜索結果"
},
"password_reset": {
"forgot_password": "忘記密碼了?",
"password_reset": "重置密碼",
"instruction": "輸入你的電郵地址或者用戶名,我們將發送一個鏈接到你的郵箱,用於重置密碼。",
"placeholder": "你的電郵地址或者用戶名",
"check_email": "檢查你的郵箱,會有一個鏈接用於重置密碼。",
"return_home": "回到首頁",
"too_many_requests": "你觸發了嘗試的限制,請稍後再試。",
"password_reset_disabled": "密碼重置已經被禁用。請聯繫你的實例管理員。",
"password_reset_required": "您必須重置密碼才能登陸。",
"password_reset_required_but_mailer_is_disabled": "您必須重置密碼,但是密碼重置被禁用了。請聯繫您所在實例的管理員。"
}
}

View file

@ -20,7 +20,7 @@ const api = {
state.fetchers[fetcherName] = fetcher
},
removeFetcher (state, { fetcherName, fetcher }) {
window.clearInterval(fetcher)
state.fetchers[fetcherName].stop()
delete state.fetchers[fetcherName]
},
setWsToken (state, token) {

View file

@ -3,6 +3,7 @@ import { find, omitBy, orderBy, sumBy } from 'lodash'
import chatService from '../services/chat_service/chat_service.js'
import { parseChat, parseChatMessage } from '../services/entity_normalizer/entity_normalizer.service.js'
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
import { promiseInterval } from '../services/promise_interval/promise_interval.js'
const emptyChatList = () => ({
data: [],
@ -42,12 +43,10 @@ const chats = {
actions: {
// Chat list
startFetchingChats ({ dispatch, commit }) {
const fetcher = () => {
dispatch('fetchChats', { latest: true })
}
const fetcher = () => dispatch('fetchChats', { latest: true })
fetcher()
commit('setChatListFetcher', {
fetcher: () => setInterval(() => { fetcher() }, 5000)
fetcher: () => promiseInterval(fetcher, 5000)
})
},
stopFetchingChats ({ commit }) {
@ -113,14 +112,14 @@ const chats = {
setChatListFetcher (state, { commit, fetcher }) {
const prevFetcher = state.chatListFetcher
if (prevFetcher) {
clearInterval(prevFetcher)
prevFetcher.stop()
}
state.chatListFetcher = fetcher && fetcher()
},
setCurrentChatFetcher (state, { fetcher }) {
const prevFetcher = state.fetcher
if (prevFetcher) {
clearInterval(prevFetcher)
prevFetcher.stop()
}
state.fetcher = fetcher && fetcher()
},

View file

@ -65,7 +65,8 @@ export const defaultState = {
useContainFit: false,
greentext: undefined, // instance default
hidePostStats: undefined, // instance default
hideUserStats: undefined // instance default
hideUserStats: undefined, // instance default
virtualScrolling: undefined // instance default
}
// caching the instance default properties

View file

@ -41,6 +41,7 @@ const defaultState = {
sidebarRight: false,
subjectLineBehavior: 'email',
theme: 'pleroma-dark',
virtualScrolling: true,
// Nasty stuff
customEmoji: [],

View file

@ -568,6 +568,9 @@ export const mutations = {
updateStatusWithPoll (state, { id, poll }) {
const status = state.allStatusesObject[id]
status.poll = poll
},
setVirtualHeight (state, { statusId, height }) {
state.allStatusesObject[statusId].virtualHeight = height
}
}
@ -753,6 +756,9 @@ const statuses = {
store.commit('addNewStatuses', { statuses: data.statuses })
return data
})
},
setVirtualHeight ({ commit }, { statusId, height }) {
commit('setVirtualHeight', { statusId, height })
}
},
mutations

View file

@ -3,6 +3,7 @@ import { parseStatus, parseUser, parseNotification, parseAttachment, parseChat,
import { RegistrationError, StatusCodeError } from '../errors/errors'
/* eslint-env browser */
const MUTES_IMPORT_URL = '/api/pleroma/mutes_import'
const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
@ -539,8 +540,10 @@ const fetchTimeline = ({
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}`
let status = ''
let statusText = ''
let pagination = {}
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => {
@ -710,6 +713,17 @@ const setMediaDescription = ({ id, description, credentials }) => {
}).then((data) => parseAttachment(data))
}
const importMutes = ({ file, credentials }) => {
const formData = new FormData()
formData.append('list', file)
return fetch(MUTES_IMPORT_URL, {
body: formData,
method: 'POST',
headers: authHeaders(credentials)
})
.then((response) => response.ok)
}
const importBlocks = ({ file, credentials }) => {
const formData = new FormData()
formData.append('list', file)
@ -1280,6 +1294,7 @@ const apiService = {
getCaptcha,
updateProfileImages,
updateProfile,
importMutes,
importBlocks,
importFollows,
deleteAccount,

View file

@ -1,4 +1,5 @@
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
const fetchAndUpdate = ({ store, credentials }) => {
return apiService.fetchFollowRequests({ credentials })
@ -10,9 +11,9 @@ const fetchAndUpdate = ({ store, credentials }) => {
}
const startFetching = ({ credentials, store }) => {
fetchAndUpdate({ credentials, store })
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
return setInterval(boundFetchAndUpdate, 10000)
boundFetchAndUpdate()
return promiseInterval(boundFetchAndUpdate, 10000)
}
const followRequestFetcher = {

View file

@ -1,4 +1,5 @@
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
const update = ({ store, notifications, older }) => {
store.dispatch('setNotificationsError', { value: false })
@ -39,6 +40,7 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
args['since'] = Math.max(...readNotifsIds)
fetchNotifications({ store, args, older })
}
return result
}
}
@ -53,13 +55,13 @@ const fetchNotifications = ({ store, args, older }) => {
}
const startFetching = ({ credentials, store }) => {
fetchAndUpdate({ credentials, store })
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
// Initially there's set flag to silence all desktop notifications so
// that there won't spam of them when user just opened up the FE we
// reset that flag after a while to show new notifications once again.
setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
return setInterval(boundFetchAndUpdate, 10000)
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
boundFetchAndUpdate()
return promiseInterval(boundFetchAndUpdate, 10000)
}
const notificationsFetcher = {

View file

@ -0,0 +1,27 @@
// promiseInterval - replacement for setInterval for promises, starts counting
// the interval only after a promise is done instead of immediately.
// - promiseCall is a function that returns a promise, it's called the first
// time after the first interval.
// - interval is the interval delay in ms.
export const promiseInterval = (promiseCall, interval) => {
let stopped = false
let timeout = null
const func = () => {
promiseCall().finally(() => {
if (stopped) return
timeout = window.setTimeout(func, interval)
})
}
const stopFetcher = () => {
stopped = true
window.clearTimeout(timeout)
}
timeout = window.setTimeout(func, interval)
return { stop: stopFetcher }
}

View file

@ -1,6 +1,7 @@
import { camelCase } from 'lodash'
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => {
const ccTimeline = camelCase(timeline)
@ -71,8 +72,9 @@ const startFetching = ({ timeline = 'friends', credentials, store, userId = fals
const showImmediately = timelineData.visibleStatuses.length === 0
timelineData.userId = userId
fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, tag })
const boundFetchAndUpdate = () => fetchAndUpdate({ timeline, credentials, store, userId, tag })
return setInterval(boundFetchAndUpdate, 10000)
const boundFetchAndUpdate = () =>
fetchAndUpdate({ timeline, credentials, store, userId, tag })
return promiseInterval(boundFetchAndUpdate, 10000)
}
const timelineFetcher = {
fetchAndUpdate,