announcements (#42)

Reviewed-on: AkkomaGang/pleroma-fe#42
This commit is contained in:
floatingghost 2022-07-18 13:08:50 +00:00
parent 2977edc04d
commit fab72940c4
58 changed files with 1514 additions and 175 deletions

View file

@ -21,10 +21,10 @@
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/vue-fontawesome": "3.0.0-5",
"@fortawesome/vue-fontawesome": "3.0.1",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@vuelidate/core": "2.0.0-alpha.41",
"@vuelidate/validators": "2.0.0-alpha.27",
"@vuelidate/core": "2.0.0-alpha.42",
"@vuelidate/validators": "2.0.0-alpha.30",
"body-scroll-lock": "2.7.1",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
@ -86,7 +86,7 @@
"html-webpack-plugin": "3.2.0",
"http-proxy-middleware": "0.21.0",
"inject-loader": "2.0.1",
"iso-639-1": "2.1.13",
"iso-639-1": "2.1.15",
"isparta-loader": "2.0.0",
"json-loader": "0.5.7",
"karma": "6.3.17",
@ -107,8 +107,8 @@
"ora": "0.4.1",
"postcss-loader": "3.0.0",
"raw-loader": "0.5.1",
"sass": "1.20.1",
"sass-loader": "7.2.0",
"sass": "1.53.0",
"sass-loader": "7.3.1",
"selenium-server": "2.53.1",
"semver": "5.7.1",
"serviceworker-webpack-plugin": "1.0.1",
@ -123,7 +123,7 @@
"vue-style-loader": "4.1.2",
"webpack": "4.46.0",
"webpack-dev-middleware": "3.7.3",
"webpack-hot-middleware": "2.24.3",
"webpack-hot-middleware": "2.25.1",
"webpack-merge": "0.20.0"
},
"engines": {

View file

@ -310,7 +310,6 @@ nav {
border-top-right-radius: 0;
}
.underlay,
#sidebar,
#notifs-column {
display: none;

View file

@ -370,6 +370,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
store.dispatch('startFetchingAnnouncements')
getTOS({ store })
getStickers({ store })

View file

@ -23,6 +23,7 @@ import RemoteUserResolver from 'components/remote_user_resolver/remote_user_reso
import Lists from 'components/lists/lists.vue'
import ListTimeline from 'components/list_timeline/list_timeline.vue'
import ListEdit from 'components/list_edit/list_edit.vue'
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@ -72,10 +73,11 @@ export default (store) => {
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About },
{ name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile },
{ name: 'lists', path: '/lists', component: Lists },
{ name: 'list-timeline', path: '/lists/:id', component: ListTimeline },
{ name: 'list-edit', path: '/lists/:id/edit', component: ListEdit }
{ name: 'list-edit', path: '/lists/:id/edit', component: ListEdit },
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage },
{ name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile }
]
if (store.state.instance.pleromaChatMessagesAvailable) {

View file

@ -0,0 +1,105 @@
import { mapState } from 'vuex'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
import RichContent from '../rich_content/rich_content.jsx'
import localeService from '../../services/locale/locale.service.js'
const Announcement = {
components: {
AnnouncementEditor,
RichContent
},
data () {
return {
editing: false,
editedAnnouncement: {
content: '',
startsAt: undefined,
endsAt: undefined,
allDay: undefined
},
editError: ''
}
},
props: {
announcement: Object
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
}),
content () {
return this.announcement.content
},
isRead () {
return this.announcement.read
},
publishedAt () {
const time = this.announcement['published_at']
if (!time) {
return
}
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
},
startsAt () {
const time = this.announcement['starts_at']
if (!time) {
return
}
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
},
endsAt () {
const time = this.announcement['ends_at']
if (!time) {
return
}
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
},
inactive () {
return this.announcement.inactive
}
},
methods: {
markAsRead () {
if (!this.isRead) {
return this.$store.dispatch('markAnnouncementAsRead', this.announcement.id)
}
},
deleteAnnouncement () {
return this.$store.dispatch('deleteAnnouncement', this.announcement.id)
},
formatTimeOrDate (time, locale) {
const d = new Date(time)
return this.announcement['all_day'] ? d.toLocaleDateString(locale) : d.toLocaleString(locale)
},
enterEditMode () {
this.editedAnnouncement.content = this.announcement.pleroma['raw_content']
this.editedAnnouncement.startsAt = this.announcement['starts_at']
this.editedAnnouncement.endsAt = this.announcement['ends_at']
this.editedAnnouncement.allDay = this.announcement['all_day']
this.editing = true
},
submitEdit () {
this.$store.dispatch('editAnnouncement', {
id: this.announcement.id,
...this.editedAnnouncement
})
.then(() => {
this.editing = false
})
.catch(error => {
this.editError = error.error
})
},
cancelEdit () {
this.editing = false
},
clearError () {
this.editError = undefined
}
}
}
export default Announcement

View file

@ -0,0 +1,136 @@
<template>
<div class="announcement">
<div class="heading">
<h4>{{ $t('announcements.title') }}</h4>
</div>
<div class="body">
<rich-content
v-if="!editing"
:html="content"
:emoji="announcement.emojis"
:handle-links="true"
/>
<announcement-editor
v-else
:announcement="editedAnnouncement"
/>
</div>
<div class="footer">
<div
v-if="!editing"
class="times"
>
<span v-if="publishedAt">
{{ $t('announcements.published_time_display', { time: publishedAt }) }}
</span>
<span v-if="startsAt">
{{ $t('announcements.start_time_display', { time: startsAt }) }}
</span>
<span v-if="endsAt">
{{ $t('announcements.end_time_display', { time: endsAt }) }}
</span>
</div>
<div
v-if="!editing"
class="actions"
>
<button
v-if="currentUser"
class="btn button-default"
:class="{ toggled: isRead }"
:disabled="inactive"
:title="inactive ? $t('announcements.inactive_message') : ''"
@click="markAsRead"
>
{{ $t('announcements.mark_as_read_action') }}
</button>
<button
v-if="currentUser && currentUser.role === 'admin'"
class="btn button-default"
@click="enterEditMode"
>
{{ $t('announcements.edit_action') }}
</button>
<button
v-if="currentUser && currentUser.role === 'admin'"
class="btn button-default"
@click="deleteAnnouncement"
>
{{ $t('announcements.delete_action') }}
</button>
</div>
<div
v-else
class="actions"
>
<button
class="btn button-default"
@click="submitEdit"
>
{{ $t('announcements.submit_edit_action') }}
</button>
<button
class="btn button-default"
@click="cancelEdit"
>
{{ $t('announcements.cancel_edit_action') }}
</button>
<div
v-if="editing && editError"
class="alert error"
>
{{ $t('announcements.edit_error', { error }) }}
<button
class="button-unstyled"
@click="clearError"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
:title="$t('announcements.close_error')"
/>
</button>
</div>
</div>
</div>
</div>
</template>
<script src="./announcement.js"></script>
<style lang="scss">
@import "../../variables";
.announcement {
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
border-radius: 0;
padding: var(--status-margin, $status-margin);
.heading, .body {
margin-bottom: var(--status-margin, $status-margin);
}
.footer {
display: flex;
flex-direction: column;
.times {
display: flex;
flex-direction: column;
}
}
.footer .actions {
display: flex;
flex-direction: row;
justify-content: space-evenly;
.btn {
flex: 1;
margin: 1em;
max-width: 10em;
}
}
}
</style>

View file

@ -0,0 +1,13 @@
import Checkbox from '../checkbox/checkbox.vue'
const AnnouncementEditor = {
components: {
Checkbox
},
props: {
announcement: Object,
disabled: Boolean
}
}
export default AnnouncementEditor

View file

@ -0,0 +1,60 @@
<template>
<div class="announcement-editor">
<textarea
ref="textarea"
v-model="announcement.content"
class="post-textarea"
rows="1"
cols="1"
:placeholder="$t('announcements.post_placeholder')"
:disabled="disabled"
/>
<span class="announcement-metadata">
<label for="announcement-start-time">{{ $t('announcements.start_time_prompt') }}</label>
<input
id="announcement-start-time"
v-model="announcement.startsAt"
:type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled"
>
</span>
<span class="announcement-metadata">
<label for="announcement-end-time">{{ $t('announcements.end_time_prompt') }}</label>
<input
id="announcement-end-time"
v-model="announcement.endsAt"
:type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled"
>
</span>
<span class="announcement-metadata">
<Checkbox
id="announcement-all-day"
v-model="announcement.allDay"
:disabled="disabled"
/>
<label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label>
</span>
</div>
</template>
<script src="./announcement_editor.js"></script>
<style lang="scss">
.announcement-editor {
display: flex;
align-items: stretch;
flex-direction: column;
.announcement-metadata {
margin-top: 0.5em;
}
.post-textarea {
resize: vertical;
height: 10em;
overflow: none;
box-sizing: content-box;
}
}
</style>

View file

@ -0,0 +1,55 @@
import { mapState } from 'vuex'
import Announcement from '../announcement/announcement.vue'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
const AnnouncementsPage = {
components: {
Announcement,
AnnouncementEditor
},
data () {
return {
newAnnouncement: {
content: '',
startsAt: undefined,
endsAt: undefined,
allDay: false
},
posting: false,
error: undefined
}
},
mounted () {
this.$store.dispatch('fetchAnnouncements')
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
}),
announcements () {
return this.$store.state.announcements.announcements
}
},
methods: {
postAnnouncement () {
this.posting = true
this.$store.dispatch('postAnnouncement', this.newAnnouncement)
.then(() => {
this.newAnnouncement.content = ''
this.startsAt = undefined
this.endsAt = undefined
})
.catch(error => {
this.error = error.error
})
.finally(() => {
this.posting = false
})
},
clearError () {
this.error = undefined
}
}
}
export default AnnouncementsPage

View file

@ -0,0 +1,79 @@
<template>
<div class="panel panel-default announcements-page">
<div class="panel-heading">
<span>
{{ $t('announcements.page_header') }}
</span>
</div>
<div class="panel-body">
<section
v-if="currentUser && currentUser.role === 'admin'"
>
<div class="post-form">
<div class="heading">
<h4>{{ $t('announcements.post_form_header') }}</h4>
</div>
<div class="body">
<announcement-editor
:announcement="newAnnouncement"
:disabled="posting"
/>
</div>
<div class="footer">
<button
class="btn button-default post-button"
:disabled="posting"
@click.prevent="postAnnouncement"
>
{{ $t('announcements.post_action') }}
</button>
<div
v-if="error"
class="alert error"
>
{{ $t('announcements.post_error', { error }) }}
<button
class="button-unstyled"
@click="clearError"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
:title="$t('announcements.close_error')"
/>
</button>
</div>
</div>
</div>
</section>
<section
v-for="announcement in announcements"
:key="announcement.id"
>
<announcement
:announcement="announcement"
/>
</section>
</div>
</div>
</template>
<script src="./announcements_page.js"></script>
<style lang="scss">
@import "../../variables";
.announcements-page {
.post-form {
padding: var(--status-margin, $status-margin);
.heading, .body {
margin-bottom: var(--status-margin, $status-margin);
}
.post-button {
min-width: 10em;
}
}
}
</style>

View file

@ -3,6 +3,10 @@
.DesktopNav {
width: 100%;
input {
color: var(--inputTopbarText, var(--inputText));
}
a {
color: var(--topBarLink, $fallback--link);
}

View file

@ -6,6 +6,7 @@ import {
faStickyNote,
faSmileBeam
} from '@fortawesome/free-solid-svg-icons'
import { trim } from 'lodash'
library.add(
faBoxOpen,
@ -176,7 +177,7 @@ const EmojiPicker = {
filteredEmoji () {
return filterByKeyword(
this.$store.state.instance.customEmoji || [],
this.keyword
trim(this.keyword)
)
},
customEmojiBuffer () {
@ -197,7 +198,7 @@ const EmojiPicker = {
id: 'standard',
text: this.$t('emoji.unicode'),
icon: 'box-open',
emojis: filterByKeyword(standardEmojis, this.keyword)
emojis: filterByKeyword(standardEmojis, trim(this.keyword))
}
]
},

View file

@ -47,6 +47,7 @@
type="text"
class="form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
>
</div>
<div

View file

@ -47,7 +47,7 @@ const MobileNav = {
isChat () {
return this.$route.name === 'chat'
},
...mapGetters(['unreadChatCount'])
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
},
methods: {
toggleMobileSidebar () {

View file

@ -17,7 +17,7 @@
icon="bars"
/>
<div
v-if="unreadChatCount"
v-if="unreadChatCount || unreadAnnouncementCount"
class="alert-dot"
/>
</button>

View file

@ -13,7 +13,8 @@ import {
faBell,
faInfoCircle,
faStream,
faList
faList,
faBullhorn
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -27,7 +28,8 @@ library.add(
faBell,
faInfoCircle,
faStream,
faList
faList,
faBullhorn
)
const NavPanel = {
@ -57,7 +59,7 @@ const NavPanel = {
federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
}),
...mapGetters(['unreadChatCount'])
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
}
}

View file

@ -97,6 +97,24 @@
/>{{ $t("nav.about") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link
class="menu-item"
:to="{ name: 'announcements' }"
>
<FAIcon
fixed-width
class="fa-scale-110"
icon="bullhorn"
/>{{ $t('nav.announcements') }}
<span
v-if="unreadAnnouncementCount > 0"
class="badge badge-notification"
>
{{ unreadAnnouncementCount }}
</span>
</router-link>
</li>
</ul>
</div>
</div>

View file

@ -60,7 +60,7 @@ const Notifications = {
return this.unseenNotifications.length
},
unseenCountTitle () {
return this.unseenCount + (this.unreadChatCount)
return this.unseenCount + (this.unreadChatCount) + this.unreadAnnouncementCount
},
loading () {
return this.$store.state.statuses.notifications.loading
@ -80,7 +80,7 @@ const Notifications = {
notificationsToDisplay () {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
},
...mapGetters(['unreadChatCount'])
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
},
watch: {
unseenCountTitle (count) {

View file

@ -7,11 +7,16 @@ const DataImportExportTab = {
data () {
return {
activeTab: 'profile',
newDomainToMute: ''
newDomainToMute: '',
listBackupsError: false,
addBackupError: false,
addedBackup: false,
backups: []
}
},
created () {
this.$store.dispatch('fetchTokens')
this.fetchBackups()
},
components: {
Importer,
@ -72,6 +77,28 @@ const DataImportExportTab = {
}
return user.screen_name
}).join('\n')
},
addBackup () {
this.$store.state.api.backendInteractor.addBackup()
.then((res) => {
this.addedBackup = true
this.addBackupError = false
})
.catch((error) => {
this.addedBackup = false
this.addBackupError = error
})
.then(() => this.fetchBackups())
},
fetchBackups () {
this.$store.state.api.backendInteractor.listBackups()
.then((res) => {
this.backups = res
this.listBackupsError = false
})
.catch((error) => {
this.listBackupsError = error.error
})
}
}
}

View file

@ -53,6 +53,67 @@
:export-button-label="$t('settings.mute_export_button')"
/>
</div>
<div class="setting-item">
<h2>{{ $t('settings.account_backup') }}</h2>
<p>{{ $t('settings.account_backup_description') }}</p>
<table>
<thead>
<tr>
<th>{{ $t('settings.account_backup_table_head') }}</th>
<th />
</tr>
</thead>
<tbody>
<tr
v-for="backup in backups"
:key="backup.id"
>
<td>{{ backup.inserted_at }}</td>
<td class="actions">
<a
v-if="backup.processed"
target="_blank"
:href="backup.url"
>
{{ $t('settings.download_backup') }}
</a>
<span
v-else
>
{{ $t('settings.backup_not_ready') }}
</span>
</td>
</tr>
</tbody>
</table>
<div
v-if="listBackupsError"
class="alert error"
>
{{ $t('settings.list_backups_error', { error }) }}
<button
:title="$t('settings.hide_list_backups_error_action')"
@click="listBackupsError = false"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
/>
</button>
</div>
<button
class="btn button-default"
@click="addBackup"
>
{{ $t('settings.add_backup') }}
</button>
<p v-if="addedBackup">
{{ $t('settings.added_backup') }}
</p>
<template v-if="addBackupError !== false">
<p>{{ $t('settings.add_backup_error', { error: addBackupError }) }}</p>
</template>
</div>
</div>
</template>

View file

@ -88,7 +88,7 @@ const SideDrawer = {
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
}),
...mapGetters(['unreadChatCount'])
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
},
methods: {
toggleDrawer () {

View file

@ -191,6 +191,26 @@
/> {{ $t("nav.administration") }}
</a>
</li>
<li
v-if="currentUser"
@click="toggleDrawer"
>
<router-link
:to="{ name: 'announcements' }"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="bullhorn"
/> {{ $t("nav.announcements") }}
<span
v-if="unreadAnnouncementCount"
class="badge badge-notification"
>
{{ unreadAnnouncementCount }}
</span>
</router-link>
</li>
<li
v-if="currentUser"
@click="toggleDrawer"

View file

@ -3,7 +3,7 @@
:datetime="time"
:title="localeDateString"
>
{{ $t(relativeTime.key, [relativeTime.num]) }}
{{ $tc(relativeTime.key, relativeTime.num, [relativeTime.num]) }}
</time>
</template>

View file

@ -621,7 +621,6 @@
"disable_any_subscription": "Deshabilita completament seguir algú",
"quarantine": "Deshabilita la federació a les entrades de les usuàries",
"moderation": "Moderació",
"delete_user_confirmation": "Estàs completament segur/a? Aquesta acció no es pot desfer.",
"revoke_admin": "Revoca l'Admin",
"activate_account": "Activa el compte",
"deactivate_account": "Desactiva el compte",

View file

@ -582,7 +582,6 @@
"statuses": "Beiträge",
"admin_menu": {
"sandbox": "Erzwinge Beiträge nur für Follower sichtbar zu sein",
"delete_user_confirmation": "Achtung! Diese Entscheidung kann nicht rückgängig gemacht werden! Trotzdem durchführen?",
"grant_admin": "Administratorprivilegien gewähren",
"delete_user": "Nutzer löschen",
"strip_media": "Medien von Beiträgen entfernen",

View file

@ -32,6 +32,27 @@
},
"staff": "Staff"
},
"announcements": {
"page_header": "Announcements",
"title": "Announcement",
"mark_as_read_action": "Mark as read",
"post_form_header": "Post announcement",
"post_placeholder": "Type your announcement content here...",
"post_action": "Post",
"post_error": "Error: {error}",
"close_error": "Close",
"delete_action": "Delete",
"start_time_prompt": "Start time: ",
"end_time_prompt": "End time: ",
"all_day_prompt": "This is an all-day event",
"published_time_display": "Published at {time}",
"start_time_display": "Starts at {time}",
"end_time_display": "Ends at {time}",
"edit_action": "Edit",
"submit_edit_action": "Submit",
"cancel_edit_action": "Cancel",
"inactive_message": "This announcement is inactive"
},
"shoutbox": {
"title": "Shoutbox"
},
@ -147,7 +168,8 @@
"preferences": "Preferences",
"timelines": "Timelines",
"chats": "Chats",
"lists": "Lists"
"lists": "Lists",
"announcements": "Announcements"
},
"notifications": {
"broken_favorite": "Unknown status, searching for it…",
@ -322,6 +344,16 @@
"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",
"account_backup": "Account backup",
"account_backup_description": "This allows you to download an archive of your account information and your posts, but they cannot yet be imported into a Pleroma account.",
"account_backup_table_head": "Backup",
"download_backup": "Download",
"backup_not_ready": "This backup is not ready yet.",
"remove_backup": "Remove",
"list_backups_error": "Error fetching backup list: {error}",
"add_backup": "Create a new backup",
"added_backup": "Added a new backup.",
"add_backup_error": "Error adding a new backup: {error}",
"blocks_tab": "Blocks",
"bot": "This is a bot account",
"btnRadius": "Buttons",
@ -718,38 +750,26 @@
}
},
"time": {
"day": "{0} day",
"days": "{0} days",
"day_short": "{0}d",
"days_short": "{0}d",
"hour": "{0} hour",
"hours": "{0} hours",
"hour_short": "{0}h",
"hours_short": "{0}h",
"unit": {
"days": "{0} day | {0} days",
"days_short": "{0}d",
"hours": "{0} hour | {0} hours",
"hours_short": "{0}h",
"minutes": "{0} minute | {0} minutes",
"minutes_short": "{0}min",
"months": "{0} month | {0} months",
"months_short": "{0}mo",
"seconds": "{0} second | {0} seconds",
"seconds_short": "{0}s",
"weeks": "{0} week | {0} weeks",
"weeks_short": "{0}w",
"years": "{0} year | {0} years",
"years_short": "{0}y"
},
"in_future": "in {0}",
"in_past": "{0} ago",
"minute": "{0} minute",
"minutes": "{0} minutes",
"minute_short": "{0}min",
"minutes_short": "{0}min",
"month": "{0} month",
"months": "{0} months",
"month_short": "{0}mo",
"months_short": "{0}mo",
"now": "just now",
"now_short": "now",
"second": "{0} second",
"seconds": "{0} seconds",
"second_short": "{0}s",
"seconds_short": "{0}s",
"week": "{0} week",
"weeks": "{0} weeks",
"week_short": "{0}w",
"weeks_short": "{0}w",
"year": "{0} year",
"years": "{0} years",
"year_short": "{0}y",
"years_short": "{0}y"
"now_short": "now"
},
"timeline": {
"collapse": "Collapse",
@ -878,7 +898,7 @@
"disable_any_subscription": "Disallow following user at all",
"quarantine": "Disallow user posts from federating",
"delete_user": "Delete user",
"delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
"delete_user_data_and_deactivate_confirmation": "This will permanently delete the data from this account and deactivate it. Are you absolutely sure?"
},
"highlight": {
"disabled": "No highlight",

View file

@ -606,7 +606,6 @@
"mention": "Mencio",
"hidden": "Kaŝita",
"admin_menu": {
"delete_user_confirmation": "Ĉu vi tute certas? Ĉi tiu ago ne estas malfarebla.",
"delete_user": "Forigi uzanton",
"quarantine": "Malpermesi federadon de afiŝoj de uzanto",
"disable_any_subscription": "Malpermesi ĉian abonadon al uzanto",

View file

@ -731,8 +731,7 @@
"disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga",
"disable_any_subscription": "No permitir que ningún usuario te siga",
"quarantine": "No permitir publicaciones de usuarios de instancias remotas",
"delete_user": "Eliminar usuario",
"delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
"delete_user": "Eliminar usuario"
},
"show_repeats": "Mostrar repetidos",
"hide_repeats": "Ocultar repetidos",

View file

@ -609,8 +609,7 @@
"disable_remote_subscription": "Ez utzi istantzia kanpoko erabiltzaileak zuri jarraitzea",
"disable_any_subscription": "Ez utzi beste erabiltzaileak zuri jarraitzea",
"quarantine": "Ez onartu mezuak beste instantzietatik",
"delete_user": "Erabiltzailea ezabatu",
"delete_user_confirmation": "Erabat ziur zaude? Ekintza hau ezin da desegin."
"delete_user": "Erabiltzailea ezabatu"
}
},
"user_profile": {

View file

@ -620,8 +620,7 @@
"sandbox": "Pakota viestit vain seuraajille",
"disable_remote_subscription": "Estä seuraaminen ulkopuolisilta sivuilta",
"quarantine": "Estä käyttäjän viestin federoituminen",
"delete_user": "Poista käyttäjä",
"delete_user_confirmation": "Oletko aivan varma? Tätä ei voi kumota."
"delete_user": "Poista käyttäjä"
},
"favorites": "Tykkäykset",
"mention": "Mainitse",

View file

@ -659,8 +659,7 @@
"disable_remote_subscription": "Interdir de s'abonner a l'utilisateur depuis l'instance distante",
"disable_any_subscription": "Interdir de s'abonner à l'utilisateur tout court",
"quarantine": "Interdir les statuts de l'utilisateur à fédérer",
"delete_user": "Supprimer l'utilisateur",
"delete_user_confirmation": "Êtes-vous absolument-sûr⋅e ? Cette action ne peut être annulée."
"delete_user": "Supprimer l'utilisateur"
},
"mention": "Mention",
"hidden": "Caché",

View file

@ -347,8 +347,7 @@
"disable_remote_subscription": "אל תאפשר עקיבה של המשתמש מאינסטנס אחר",
"disable_any_subscription": "אל תאפשר עקיבה של המשתמש בכלל",
"quarantine": "אל תאפשר פדרציה של ההודעות של המשתמש",
"delete_user": "מחק משתמש",
"delete_user_confirmation": "בטוח? פעולה זו הינה בלתי הפיכה."
"delete_user": "מחק משתמש"
}
},
"user_profile": {

View file

@ -327,8 +327,7 @@
"delete_account": "Hapus akun",
"force_nsfw": "Tandai semua postingan sebagai NSFW",
"strip_media": "Hapus media dari postingan-postingan",
"delete_user": "Hapus pengguna",
"delete_user_confirmation": "Apakah Anda benar-benar yakin? Tindakan ini tidak dapat dibatalkan."
"delete_user": "Hapus pengguna"
},
"follow_unfollow": "Berhenti mengikuti",
"followees": "Mengikuti",

View file

@ -485,7 +485,6 @@
"deny": "Nega",
"remote_follow": "Segui da remoto",
"admin_menu": {
"delete_user_confirmation": "Ne sei completamente sicuro? Non potrai tornare indietro.",
"delete_user": "Elimina utente",
"quarantine": "I messaggi non arriveranno alle altre stanze",
"disable_any_subscription": "Rendi utente non seguibile",

View file

@ -608,8 +608,7 @@
"disable_remote_subscription": "ほかのインスタンスからフォローされないようにする",
"disable_any_subscription": "フォローされないようにする",
"quarantine": "ほかのインスタンスのユーザーのとうこうをとめる",
"delete_user": "ユーザーをけす",
"delete_user_confirmation": "あなたは、ほんとうに、きはたしかですか? これは、とりけすことが、できません。"
"delete_user": "ユーザーをけす"
}
},
"user_profile": {

View file

@ -731,8 +731,7 @@
"disable_remote_subscription": "他のインスタンスからフォローされないようにする",
"disable_any_subscription": "フォローされないようにする",
"quarantine": "他のインスタンスからの投稿を止める",
"delete_user": "ユーザーを削除",
"delete_user_confirmation": "あなたの精神状態に何か問題はございませんか? この操作を取り消すことはできません。"
"delete_user": "ユーザーを削除"
},
"roles": {
"moderator": "モデレーター",

View file

@ -32,6 +32,7 @@ const loaders = {
pt: () => import('./pt.json'),
ro: () => import('./ro.json'),
ru: () => import('./ru.json'),
sk: () => import('./sk.json'),
te: () => import('./te.json'),
uk: () => import('./uk.json'),
zh: () => import('./zh.json'),

View file

@ -553,8 +553,7 @@
"disable_remote_subscription": "Fjern mulighet til å følge brukeren fra andre instanser",
"disable_any_subscription": "Fjern mulighet til å følge brukeren",
"quarantine": "Gjør at statuser fra brukeren ikke kan sendes til andre instanser",
"delete_user": "Slett bruker",
"delete_user_confirmation": "Er du helt sikker? Denne handlingen kan ikke omgjøres."
"delete_user": "Slett bruker"
}
},
"user_profile": {

View file

@ -580,7 +580,6 @@
"remote_follow": "Volg vanop afstand",
"statuses": "Statussen",
"admin_menu": {
"delete_user_confirmation": "Weet je het heel zeker? Deze uitvoering kan niet ongedaan worden gemaakt.",
"delete_user": "Gebruiker verwijderen",
"quarantine": "Federeren van gebruikers berichten verbieden",
"disable_any_subscription": "Volgen van gebruiker in zijn geheel verbieden",

View file

@ -501,8 +501,7 @@
"disable_remote_subscription": "Desactivar lo seguiment dutilizaire dinstàncias alonhadas",
"disable_any_subscription": "Desactivar tot seguiment",
"quarantine": "Defendre la federacion de las publicacions de lutilizaire",
"delete_user": "Suprimir lutilizaire",
"delete_user_confirmation": "Volètz vertadièrament far aquò? Aquesta accion se pòt pas anullar."
"delete_user": "Suprimir lutilizaire"
}
},
"user_profile": {

View file

@ -762,8 +762,7 @@
"disable_remote_subscription": "Zakaż obserwowania użytkownika ze zdalnych instancji",
"disable_any_subscription": "Zakaż całkowicie obserwowania użytkownika",
"quarantine": "Zakaż federowania postów od tego użytkownika",
"delete_user": "Usuń użytkownika",
"delete_user_confirmation": "Czy jesteś absolutnie pewny(-a)? Ta operacja nie może być cofnięta."
"delete_user": "Usuń użytkownika"
},
"message": "Napisz",
"edit_profile": "Edytuj profil",

View file

@ -594,7 +594,6 @@
"unmute_progress": "A retirar silêncio…",
"mute_progress": "A silenciar…",
"admin_menu": {
"delete_user_confirmation": "Tens a certeza? Esta ação não pode ser revertida.",
"delete_user": "Eliminar utilizador",
"quarantine": "Não permitir publicações de utilizadores de instâncias remotas",
"disable_any_subscription": "Não permitir que nenhum utilizador te siga",

View file

@ -576,8 +576,7 @@
"disable_remote_subscription": "Запретить читать с других узлов",
"disable_any_subscription": "Запретить читать пользователя",
"quarantine": "Не федерировать статусы пользователя",
"delete_user": "Удалить пользователя",
"delete_user_confirmation": "Вы уверены? Это действие нельзя отменить."
"delete_user": "Удалить пользователя"
},
"media": "С вложениями",
"mention": "Упомянуть",

View file

@ -27,6 +27,7 @@ const messages = {
pt: require('../lib/notification-i18n-loader.js!./pt.json'),
ro: require('../lib/notification-i18n-loader.js!./ro.json'),
ru: require('../lib/notification-i18n-loader.js!./ru.json'),
sk: require('../lib/notification-i18n-loader.js!./sk.json'),
te: require('../lib/notification-i18n-loader.js!./te.json'),
zh: require('../lib/notification-i18n-loader.js!./zh.json'),
en: require('../lib/notification-i18n-loader.js!./en.json')

512
src/i18n/sk.json Normal file
View file

@ -0,0 +1,512 @@
{
"about": {
"mrf": {
"federation": "Federácia",
"keyword": {
"keyword_policies": "Pravidlá pre kľúčové slová",
"ftl_removal": "Odstránenie z časovej osy \"Celej známej siete\"",
"reject": "Odmietni"