aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.js6
-rw-r--r--src/App.scss23
-rw-r--r--src/App.vue2
-rw-r--r--src/boot/after_store.js7
-rw-r--r--src/boot/routes.js2
-rw-r--r--src/components/attachment/attachment.vue3
-rw-r--r--src/components/basic_user_card/basic_user_card.vue1
-rw-r--r--src/components/conversation/conversation.js7
-rw-r--r--src/components/conversation/conversation.vue2
-rw-r--r--src/components/extra_buttons/extra_buttons.js14
-rw-r--r--src/components/extra_buttons/extra_buttons.vue16
-rw-r--r--src/components/features_panel/features_panel.js4
-rw-r--r--src/components/gallery/gallery.vue8
-rw-r--r--src/components/instance_specific_panel/instance_specific_panel.js3
-rw-r--r--src/components/instance_specific_panel/instance_specific_panel.vue8
-rw-r--r--src/components/interactions/interactions.js4
-rw-r--r--src/components/interactions/interactions.vue9
-rw-r--r--src/components/link-preview/link-preview.js14
-rw-r--r--src/components/link-preview/link-preview.vue2
-rw-r--r--src/components/login_form/login_form.vue5
-rw-r--r--src/components/media_modal/media_modal.vue1
-rw-r--r--src/components/mobile_nav/mobile_nav.js4
-rw-r--r--src/components/mobile_nav/mobile_nav.vue1
-rw-r--r--src/components/mobile_post_status_modal/mobile_post_status_modal.vue11
-rw-r--r--src/components/password_reset/password_reset.js62
-rw-r--r--src/components/password_reset/password_reset.vue116
-rw-r--r--src/components/registration/registration.vue1
-rw-r--r--src/components/search/search.js4
-rw-r--r--src/components/search/search.vue11
-rw-r--r--src/components/search_bar/search_bar.js5
-rw-r--r--src/components/status/status.js2
-rw-r--r--src/components/status/status.vue2
-rw-r--r--src/components/still-image/still-image.vue2
-rw-r--r--src/components/tab_switcher/tab_switcher.js32
-rw-r--r--src/components/timeline/timeline.js27
-rw-r--r--src/components/timeline/timeline.vue26
-rw-r--r--src/components/user_avatar/user_avatar.js2
-rw-r--r--src/components/user_avatar/user_avatar.vue2
-rw-r--r--src/components/user_card/user_card.js10
-rw-r--r--src/components/user_card/user_card.vue48
-rw-r--r--src/components/user_profile/user_profile.js74
-rw-r--r--src/components/user_profile/user_profile.vue41
-rw-r--r--src/components/who_to_follow/who_to_follow.js5
-rw-r--r--src/components/who_to_follow_panel/who_to_follow_panel.js2
-rw-r--r--src/i18n/en.json17
-rw-r--r--src/i18n/es.json113
-rw-r--r--src/i18n/eu.json610
-rw-r--r--src/i18n/fi.json9
-rw-r--r--src/i18n/ja.json17
-rw-r--r--src/i18n/ja_pedantic.json17
-rw-r--r--src/i18n/messages.js2
-rw-r--r--src/i18n/nb.json440
-rw-r--r--src/i18n/ru.json11
-rw-r--r--src/i18n/te.json352
-rw-r--r--src/modules/api.js19
-rw-r--r--src/modules/chat.js10
-rw-r--r--src/modules/instance.js5
-rw-r--r--src/modules/statuses.js20
-rw-r--r--src/modules/users.js25
-rw-r--r--src/services/api/api.service.js46
-rw-r--r--src/services/backend_interactor_service/backend_interactor_service.js7
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js4
-rw-r--r--src/services/errors/errors.js35
-rw-r--r--src/services/follow_manipulate/follow_manipulate.js30
-rw-r--r--src/services/new_api/password_reset.js18
65 files changed, 2108 insertions, 330 deletions
diff --git a/src/App.js b/src/App.js
index 3624171e..e9cd5917 100644
--- a/src/App.js
+++ b/src/App.js
@@ -89,7 +89,11 @@ export default {
sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
- showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel },
+ showInstanceSpecificPanel () {
+ return this.$store.state.instance.showInstanceSpecificPanel &&
+ !this.$store.state.config.hideISP &&
+ this.$store.state.instance.instanceSpecificPanelContent
+ },
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
isMobileLayout () { return this.$store.state.interface.mobileLayout }
},
diff --git a/src/App.scss b/src/App.scss
index 1299e05d..fac800bc 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -549,23 +549,6 @@ nav {
color: var(--faint, $fallback--faint);
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
box-shadow: var(--topBarShadow);
-
- .back-button {
- display: block;
- max-width: 99px;
- transition-property: opacity, max-width;
- transition-duration: 300ms;
- transition-timing-function: ease-out;
-
- i {
- margin: 0 1em;
- }
-
- &.hidden {
- opacity: 0;
- max-width: 5px;
- }
- }
}
.fade-enter-active, .fade-leave-active {
@@ -601,12 +584,6 @@ nav {
overflow-y: scroll;
}
- nav {
- .back-button {
- display: none;
- }
- }
-
.sidebar-bounds {
overflow: hidden;
max-height: 100vh;
diff --git a/src/App.vue b/src/App.vue
index be4d1f75..719e00a4 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -41,6 +41,7 @@
<search-bar
class="nav-icon mobile-hidden"
@toggled="onSearchBarToggled"
+ @click.stop.native
/>
<router-link
class="mobile-hidden"
@@ -106,6 +107,7 @@
:floating="true"
class="floating-chat mobile-hidden"
/>
+ <MobilePostStatusModal />
<UserReportingModal />
<portal-target name="modal" />
</div>
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 0e59358b..490ac4d0 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -109,12 +109,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('noAttachmentLinks')
copyInstanceOption('showFeaturesPanel')
- if ((config.chatDisabled)) {
- store.dispatch('disableChat')
- } else {
- store.dispatch('initializeSocket')
- }
-
return store.dispatch('setTheme', config['theme'])
}
@@ -254,6 +248,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
+ store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
diff --git a/src/boot/routes.js b/src/boot/routes.js
index 7dc4b2a5..cd02711c 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -9,6 +9,7 @@ import UserProfile from 'components/user_profile/user_profile.vue'
import Search from 'components/search/search.vue'
import Settings from 'components/settings/settings.vue'
import Registration from 'components/registration/registration.vue'
+import PasswordReset from 'components/password_reset/password_reset.vue'
import UserSettings from 'components/user_settings/user_settings.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
@@ -46,6 +47,7 @@ export default (store) => {
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'settings', path: '/settings', component: Settings },
{ name: 'registration', path: '/registration', component: Registration },
+ { name: 'password-reset', path: '/password-reset', component: PasswordReset },
{ name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
{ name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index ec326c45..af16e302 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -190,6 +190,7 @@
.video {
width: 100%;
+ height: 100%;
}
.play-icon {
@@ -286,7 +287,7 @@
}
img {
- image-orientation: from-image;
+ image-orientation: from-image; // NOTE: only FF supports this
}
}
}
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index 568e9359..8a02174e 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -87,6 +87,7 @@
&-expanded-content {
flex: 1;
margin-left: 0.7em;
+ min-width: 0;
}
}
</style>
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index a2b3aeab..49fa8612 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -42,7 +42,7 @@ const conversation = {
'statusoid',
'collapsable',
'isPage',
- 'showPinned'
+ 'pinnedStatusIdsObject'
],
created () {
if (this.isPage) {
@@ -110,7 +110,7 @@ const conversation = {
Status
},
watch: {
- '$route': 'fetchConversation',
+ status: 'fetchConversation',
expanded (value) {
if (value) {
this.fetchConversation()
@@ -149,9 +149,6 @@ const conversation = {
},
toggleExpanded () {
this.expanded = !this.expanded
- if (!this.expanded) {
- this.setHighlight(null)
- }
}
}
}
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index 5a900607..f184c071 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -21,7 +21,7 @@
:inline-expanded="collapsable && isExpanded"
:statusoid="status"
:expandable="!isExpanded"
- :show-pinned="showPinned"
+ :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
:focused="focused(status.id)"
:in-conversation="isExpanded"
:highlight="getHighlight()"
diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
index 2ec72729..5ac73e97 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -16,6 +16,16 @@ const ExtraButtons = {
this.$store.dispatch('unpinStatus', this.status.id)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
+ },
+ muteConversation () {
+ this.$store.dispatch('muteConversation', this.status.id)
+ .then(() => this.$emit('onSuccess'))
+ .catch(err => this.$emit('onError', err.error.error))
+ },
+ unmuteConversation () {
+ this.$store.dispatch('unmuteConversation', this.status.id)
+ .then(() => this.$emit('onSuccess'))
+ .catch(err => this.$emit('onError', err.error.error))
}
},
computed: {
@@ -31,8 +41,8 @@ const ExtraButtons = {
canPin () {
return this.ownStatus && (this.status.visibility === 'public' || this.status.visibility === 'unlisted')
},
- enabled () {
- return this.canPin || this.canDelete
+ canMute () {
+ return !!this.currentUser
}
}
}
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index cdad1666..ed0f3aa4 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -1,6 +1,6 @@
<template>
<v-popover
- v-if="enabled"
+ v-if="canDelete || canMute || canPin"
trigger="click"
placement="top"
class="extra-button-popover"
@@ -10,6 +10,20 @@
<div slot="popover">
<div class="dropdown-menu">
<button
+ v-if="canMute && !status.muted"
+ class="dropdown-item dropdown-item-icon"
+ @click.prevent="muteConversation"
+ >
+ <i class="icon-eye-off" /><span>{{ $t("status.mute_conversation") }}</span>
+ </button>
+ <button
+ v-if="canMute && status.muted"
+ class="dropdown-item dropdown-item-icon"
+ @click.prevent="unmuteConversation"
+ >
+ <i class="icon-eye-off" /><span>{{ $t("status.unmute_conversation") }}</span>
+ </button>
+ <button
v-if="!status.pinned && canPin"
v-close-popover
class="dropdown-item dropdown-item-icon"
diff --git a/src/components/features_panel/features_panel.js b/src/components/features_panel/features_panel.js
index 5f0b7b25..5f80a079 100644
--- a/src/components/features_panel/features_panel.js
+++ b/src/components/features_panel/features_panel.js
@@ -1,8 +1,6 @@
const FeaturesPanel = {
computed: {
- chat: function () {
- return this.$store.state.instance.chatAvailable && (!this.$store.state.chatDisabled)
- },
+ chat: function () { return this.$store.state.instance.chatAvailable },
gopher: function () { return this.$store.state.instance.gopherAvailable },
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
index 6adfb76c..6169d294 100644
--- a/src/components/gallery/gallery.vue
+++ b/src/components/gallery/gallery.vue
@@ -61,13 +61,17 @@
}
&.contain-fit {
- img, video {
+ img,
+ video,
+ canvas {
object-fit: contain;
}
}
&.cover-fit {
- img, video {
+ img,
+ video,
+ canvas {
object-fit: cover;
}
}
diff --git a/src/components/instance_specific_panel/instance_specific_panel.js b/src/components/instance_specific_panel/instance_specific_panel.js
index 9bb5e945..09e3d055 100644
--- a/src/components/instance_specific_panel/instance_specific_panel.js
+++ b/src/components/instance_specific_panel/instance_specific_panel.js
@@ -2,9 +2,6 @@ const InstanceSpecificPanel = {
computed: {
instanceSpecificPanelContent () {
return this.$store.state.instance.instanceSpecificPanelContent
- },
- show () {
- return !this.$store.state.config.hideISP
}
}
}
diff --git a/src/components/instance_specific_panel/instance_specific_panel.vue b/src/components/instance_specific_panel/instance_specific_panel.vue
index a7cf6b48..7448ca06 100644
--- a/src/components/instance_specific_panel/instance_specific_panel.vue
+++ b/src/components/instance_specific_panel/instance_specific_panel.vue
@@ -1,8 +1,5 @@
<template>
- <div
- v-if="show"
- class="instance-specific-panel"
- >
+ <div class="instance-specific-panel">
<div class="panel panel-default">
<div class="panel-body">
<!-- eslint-disable vue/no-v-html -->
@@ -14,6 +11,3 @@
</template>
<script src="./instance_specific_panel.js" ></script>
-
-<style lang="scss">
-</style>
diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js
index d4e3cc17..1f8a9de9 100644
--- a/src/components/interactions/interactions.js
+++ b/src/components/interactions/interactions.js
@@ -13,8 +13,8 @@ const Interactions = {
}
},
methods: {
- onModeSwitch (index, dataset) {
- this.filterMode = tabModeDict[dataset.filter]
+ onModeSwitch (key) {
+ this.filterMode = tabModeDict[key]
}
},
components: {
diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue
index d71c99d5..08cee343 100644
--- a/src/components/interactions/interactions.vue
+++ b/src/components/interactions/interactions.vue
@@ -10,18 +10,15 @@
:on-switch="onModeSwitch"
>
<span
- data-tab-dummy
- data-filter="mentions"
+ key="mentions"
:label="$t('nav.mentions')"
/>
<span
- data-tab-dummy
- data-filter="likes+repeats"
+ key="likes+repeats"
:label="$t('interactions.favs_repeats')"
/>
<span
- data-tab-dummy
- data-filter="follows"
+ key="follows"
:label="$t('interactions.follows')"
/>
</tab-switcher>
diff --git a/src/components/link-preview/link-preview.js b/src/components/link-preview/link-preview.js
index 2f6da55e..444aafbe 100644
--- a/src/components/link-preview/link-preview.js
+++ b/src/components/link-preview/link-preview.js
@@ -5,6 +5,11 @@ const LinkPreview = {
'size',
'nsfw'
],
+ data () {
+ return {
+ imageLoaded: false
+ }
+ },
computed: {
useImage () {
// Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid
@@ -15,6 +20,15 @@ const LinkPreview = {
useDescription () {
return this.card.description && /\S/.test(this.card.description)
}
+ },
+ created () {
+ if (this.useImage) {
+ const newImg = new Image()
+ newImg.onload = () => {
+ this.imageLoaded = true
+ }
+ newImg.src = this.card.image
+ }
}
}
diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue
index 493774c2..69171977 100644
--- a/src/components/link-preview/link-preview.vue
+++ b/src/components/link-preview/link-preview.vue
@@ -7,7 +7,7 @@
rel="noopener"
>
<div
- v-if="useImage"
+ v-if="useImage && imageLoaded"
class="card-image"
:class="{ 'small-image': size === 'small' }"
>
diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue
index 3ec7fe0c..b4fdcefb 100644
--- a/src/components/login_form/login_form.vue
+++ b/src/components/login_form/login_form.vue
@@ -33,6 +33,11 @@
type="password"
>
</div>
+ <div class="form-group">
+ <router-link :to="{name: 'password-reset'}">
+ {{ $t('password_reset.forgot_password') }}
+ </router-link>
+ </div>
</template>
<div
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 0543e677..ab5a36a5 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -63,6 +63,7 @@
max-width: 90%;
max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
+ image-orientation: from-image; // NOTE: only FF supports this
}
.modal-view-button-arrow {
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index 9b341a3b..c2bb76ee 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -1,14 +1,12 @@
import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue'
-import MobilePostStatusModal from '../mobile_post_status_modal/mobile_post_status_modal.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
const MobileNav = {
components: {
SideDrawer,
- Notifications,
- MobilePostStatusModal
+ Notifications
},
data: () => ({
notificationsCloseGesture: undefined,
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index f67b7ff8..d1c24e56 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -70,7 +70,6 @@
ref="sideDrawer"
:logout="logout"
/>
- <MobilePostStatusModal />
</div>
</template>
diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue
index 5db7584b..b6d7d3ba 100644
--- a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue
+++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue
@@ -34,14 +34,19 @@
@import '../../_variables.scss';
.post-form-modal-view {
- max-height: 100%;
- display: block;
+ align-items: flex-start;
}
.post-form-modal-panel {
flex-shrink: 0;
- margin: 25% 0 4em 0;
+ margin-top: 25%;
+ margin-bottom: 2em;
width: 100%;
+ max-width: 700px;
+
+ @media (orientation: landscape) {
+ margin-top: 8%;
+ }
}
.new-status-button {
diff --git a/src/components/password_reset/password_reset.js b/src/components/password_reset/password_reset.js
new file mode 100644
index 00000000..fa71e07a
--- /dev/null
+++ b/src/components/password_reset/password_reset.js
@@ -0,0 +1,62 @@
+import { mapState } from 'vuex'
+import passwordResetApi from '../../services/new_api/password_reset.js'
+
+const passwordReset = {
+ data: () => ({
+ user: {
+ email: ''
+ },
+ isPending: false,
+ success: false,
+ throttled: false,
+ error: null
+ }),
+ computed: {
+ ...mapState({
+ signedIn: (state) => !!state.users.currentUser,
+ instance: state => state.instance
+ }),
+ mailerEnabled () {
+ return this.instance.mailerEnabled
+ }
+ },
+ created () {
+ if (this.signedIn) {
+ this.$router.push({ name: 'root' })
+ }
+ },
+ methods: {
+ dismissError () {
+ this.error = null
+ },
+ submit () {
+ this.isPending = true
+ const email = this.user.email
+ const instance = this.instance.server
+
+ passwordResetApi({ instance, email }).then(({ status }) => {
+ this.isPending = false
+ this.user.email = ''
+
+ if (status === 204) {
+ this.success = true
+ this.error = null
+ } else if (status === 404 || status === 400) {
+ this.error = this.$t('password_reset.not_found')
+ this.$nextTick(() => {
+ this.$refs.email.focus()
+ })
+ } else if (status === 429) {
+ this.throttled = true
+ this.error = this.$t('password_reset.too_many_requests')
+ }
+ }).catch(() => {
+ this.isPending = false
+ this.user.email = ''
+ this.error = this.$t('general.generic_error')
+ })
+ }
+ }
+}
+
+export default passwordReset
diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue
new file mode 100644
index 00000000..00474e95
--- /dev/null
+++ b/src/components/password_reset/password_reset.vue
@@ -0,0 +1,116 @@
+<template>
+ <div class="settings panel panel-default">
+ <div class="panel-heading">
+ {{ $t('password_reset.password_reset') }}
+ </div>
+ <div class="panel-body">
+ <form
+ class="password-reset-form"
+ @submit.prevent="submit"
+ >
+ <div class="container">
+ <div v-if="!mailerEnabled">
+ <p>
+ {{ $t('password_reset.password_reset_disabled') }}
+ </p>
+ </div>
+ <div v-else-if="success || throttled">
+ <p v-if="success">
+ {{ $t('password_reset.check_email') }}
+ </p>
+ <div class="form-group text-center">
+ <router-link :to="{name: 'root'}">
+ {{ $t('password_reset.return_home') }}
+ </router-link>
+ </div>
+ </div>
+ <div v-else>
+ <p>
+ {{ $t('password_reset.instruction') }}
+ </p>
+ <div class="form-group">
+ <input
+ ref="email"
+ v-model="user.email"
+ :disabled="isPending"
+ :placeholder="$t('password_reset.placeholder')"
+ class="form-control"
+ type="input"
+ >
+ </div>
+ <div class="form-group">
+ <button
+ :disabled="isPending"
+ type="submit"
+ class="btn btn-default btn-block"
+ >
+ {{ $t('general.submit') }}
+ </button>
+ </div>
+ </div>
+ <p
+ v-if="error"
+ class="alert error notice-dismissible"
+ >
+ <span>{{ error }}</span>
+ <a
+ class="button-icon dismiss"
+ @click.prevent="dismissError()"
+ >
+ <i class="icon-cancel" />
+ </a>
+ </p>
+ </div>
+ </form>
+ </div>
+ </div>
+</template>
+
+<script src="./password_reset.js"></script>
+<style lang="scss">
+@import '../../_variables.scss';
+
+.password-reset-form {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 0.6em;
+
+ .container {
+ display: flex;
+ flex: 1 0;
+ flex-direction: column;
+ margin-top: 0.6em;
+ max-width: 18rem;
+ }
+
+ .form-group {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 1em;
+ padding: 0.3em 0.0em 0.3em;
+ line-height: 24px;
+ }
+
+ .error {
+ text-align: center;
+ animation-name: shakeError;
+ animation-duration: 0.4s;
+ animation-timing-function: ease-in-out;
+ }
+
+ .alert {
+ padding: 0.5em;
+ margin: 0.3em 0.0em 1em;
+ }
+
+ .notice-dismissible {
+ padding-right: 2rem;
+ }
+
+ .icon-cancel {
+ cursor: pointer;
+ }
+}
+
+</style>
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index e0fa214a..5bb06a4f 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -268,6 +268,7 @@ $validations-cRed: #f04124;
textarea {
min-height: 100px;
+ resize: vertical;
}
.form-group {
diff --git a/src/components/search/search.js b/src/components/search/search.js
index b434e127..8e903052 100644
--- a/src/components/search/search.js
+++ b/src/components/search/search.js
@@ -75,8 +75,8 @@ const Search = {
const length = this[tabName].length
return length === 0 ? '' : ` (${length})`
},
- onResultTabSwitch (_index, dataset) {
- this.currenResultTab = dataset.filter
+ onResultTabSwitch (key) {
+ this.currenResultTab = key
},
getActiveTab () {
if (this.visibleStatuses.length > 0) {
diff --git a/src/components/search/search.vue b/src/components/search/search.vue
index 4350e672..746bbaa2 100644
--- a/src/components/search/search.vue
+++ b/src/components/search/search.vue
@@ -31,21 +31,18 @@
<tab-switcher
ref="tabSwitcher"
:on-switch="onResultTabSwitch"
- :custom-active="currenResultTab"
+ :active-tab="currenResultTab"
>
<span
- data-tab-dummy
- data-filter="statuses"
+ key="statuses"
:label="$t('user_card.statuses') + resultCount('visibleStatuses')"
/>
<span
- data-tab-dummy
- data-filter="people"
+ key="people"
:label="$t('search.people') + resultCount('users')"
/>
<span
- data-tab-dummy
- data-filter="hashtags"
+ key="hashtags"
:label="$t('search.hashtags') + resultCount('hashtags')"
/>
</tab-switcher>
diff --git a/src/components/search_bar/search_bar.js b/src/components/search_bar/search_bar.js
index b8a792ee..d7d85676 100644
--- a/src/components/search_bar/search_bar.js
+++ b/src/components/search_bar/search_bar.js
@@ -20,6 +20,11 @@ const SearchBar = {
toggleHidden () {
this.hidden = !this.hidden
this.$emit('toggled', this.hidden)
+ this.$nextTick(() => {
+ if (!this.hidden) {
+ this.$refs.searchInput.focus()
+ }
+ })
}
}
}
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 3c172e5b..502d9583 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -335,7 +335,7 @@ const Status = {
return
}
}
- if (target.className.match(/hashtag/)) {
+ if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) {
// Extract tag name from link url
const tag = extractTagFromUrl(target.href)
if (tag) {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index ab506632..64218f6e 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -32,7 +32,7 @@
</template>
<template v-else>
<div
- v-if="showPinned && statusoid.pinned"
+ v-if="showPinned"
class="status-pin"
>
<i class="fa icon-pin faint" />
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index 3fff63f9..4137bd59 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -7,8 +7,10 @@
v-if="animated"
ref="canvas"
/>
+ <!-- NOTE: key is required to force to re-render img tag when src is changed -->
<img
ref="src"
+ :key="src"
:src="src"
:referrerpolicy="referrerpolicy"
@load="onLoad"
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index 99428044..3ca316b9 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -14,7 +14,7 @@ export default Vue.component('tab-switcher', {
required: false,
type: Function
},
- customActive: {
+ activeTab: {
required: false,
type: String
},
@@ -29,6 +29,16 @@ export default Vue.component('tab-switcher', {
active: this.$slots.default.findIndex(_ => _.tag)
}
},
+ computed: {
+ activeIndex () {
+ // In case of controlled component
+ if (this.activeTab) {
+ return this.$slots.default.findIndex(slot => this.activeTab === slot.key)
+ } else {
+ return this.active
+ }
+ }
+ },
beforeUpdate () {
const currentSlot = this.$slots.default[this.active]
if (!currentSlot.tag) {
@@ -36,22 +46,14 @@ export default Vue.component('tab-switcher', {
}
},
methods: {
- activateTab (index, dataset) {
+ activateTab (index) {
return (e) => {
e.preventDefault()
if (typeof this.onSwitch === 'function') {
- this.onSwitch.call(null, index, this.$slots.default[index].elm.dataset)
+ this.onSwitch.call(null, this.$slots.default[index].key)
}
this.active = index
}
- },
- isActiveTab (index) {
- const customActiveIndex = this.$slots.default.findIndex(slot => {
- const dataFilter = slot.data && slot.data.attrs && slot.data.attrs['data-filter']
- return this.customActive && this.customActive === dataFilter
- })
-
- return customActiveIndex > -1 ? customActiveIndex === index : index === this.active
}
},
render (h) {
@@ -61,13 +63,13 @@ export default Vue.component('tab-switcher', {
const classesTab = ['tab']
const classesWrapper = ['tab-wrapper']
- if (this.isActiveTab(index)) {
+ if (this.activeIndex === index) {
classesTab.push('active')
classesWrapper.push('active')
}
if (slot.data.attrs.image) {
return (
- <div class={ classesWrapper.join(' ')}>
+ <div class={classesWrapper.join(' ')}>
<button
disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)}
@@ -79,7 +81,7 @@ export default Vue.component('tab-switcher', {
)
}
return (
- <div class={ classesWrapper.join(' ')}>
+ <div class={classesWrapper.join(' ')}>
<button
disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)}
@@ -91,7 +93,7 @@ export default Vue.component('tab-switcher', {
const contents = this.$slots.default.map((slot, index) => {
if (!slot.tag) return
- const active = index === this.active
+ const active = this.activeIndex === index
if (this.renderOnlyFocused) {
return active
? <div class="active">{slot}</div>
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 5e24bd15..8df48f7f 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -1,7 +1,20 @@
import Status from '../status/status.vue'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import Conversation from '../conversation/conversation.vue'
-import { throttle } from 'lodash'
+import { throttle, keyBy } from 'lodash'
+
+export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => {
+ const ids = []
+ if (pinnedStatusIds && pinnedStatusIds.length > 0) {
+ for (let status of statuses) {
+ if (!pinnedStatusIds.includes(status.id)) {
+ break
+ }
+ ids.push(status.id)
+ }
+ }
+ return ids
+}
const Timeline = {
props: [
@@ -11,7 +24,8 @@ const Timeline = {
'userId',
'tag',
'embedded',
- 'count'
+ 'count',
+ 'pinnedStatusIds'
],
data () {
return {
@@ -39,6 +53,15 @@ const Timeline = {
body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []),
footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : [])
}
+ },
+ // id map of statuses which need to be hidden in the main list due to pinning logic
+ excludedStatusIdsObject () {
+ const ids = getExcludedStatusIdsByPinning(this.timeline.visibleStatuses, this.pinnedStatusIds)
+ // Convert id array to object
+ return keyBy(ids)
+ },
+ pinnedStatusIdsObject () {
+ return keyBy(this.pinnedStatusIds)
}
},
components: {
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 1fc52083..4ad51714 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -28,13 +28,25 @@
</div>
<div :class="classes.body">
<div class="timeline">
- <conversation
- v-for="status in timeline.visibleStatuses"
- :key="status.id"
- class="status-fadein"
- :statusoid="status"
- :collapsable="true"
- />
+ <template v-for="statusId in pinnedStatusIds">
+ <conversation
+ v-if="timeline.statusesObject[statusId]"
+ :key="statusId + '-pinned'"
+ class="status-fadein"
+ :statusoid="timeline.statusesObject[statusId]"
+ :collapsable="true"
+ :pinned-status-ids-object="pinnedStatusIdsObject"
+ />
+ </template>
+ <template v-for="status in timeline.visibleStatuses">
+ <conversation
+ v-if="!excludedStatusIdsObject[status.id]"
+ :key="status.id"
+ class="status-fadein"
+ :statusoid="status"
+ :collapsable="true"
+ />
+ </template>
</div>
</div>
<div :class="classes.footer">
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
index a42b9c71..4adf8211 100644
--- a/src/components/user_avatar/user_avatar.js
+++ b/src/components/user_avatar/user_avatar.js
@@ -16,7 +16,7 @@ const UserAvatar = {
},
computed: {
imgSrc () {
- return this.showPlaceholder ? '/images/avi.png' : this.src
+ return this.showPlaceholder ? '/images/avi.png' : this.user.profile_image_url_original
}
},
methods: {
diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue
index 811efd3c..9ffb28d8 100644
--- a/src/components/user_avatar/user_avatar.vue
+++ b/src/components/user_avatar/user_avatar.vue
@@ -3,7 +3,7 @@
class="avatar"
:alt="user.screen_name"
:title="user.screen_name"
- :src="user.profile_image_url_original"
+ :src="imgSrc"
:class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
:image-load-error="imageLoadError"
/>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index e019ebbd..82d3b835 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -7,7 +7,7 @@ import { requestFollow, requestUnfollow } from '../../services/follow_manipulate
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
export default {
- props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered' ],
+ props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' ],
data () {
return {
followRequestInProgress: false,
@@ -162,6 +162,14 @@ export default {
},
reportUser () {
this.$store.dispatch('openUserReportingModal', this.user.id)
+ },
+ zoomAvatar () {
+ const attachment = {
+ url: this.user.profile_image_url_original,
+ mimetype: 'image'
+ }
+ this.$store.dispatch('setMedia', [attachment])
+ this.$store.dispatch('setCurrent', attachment)
}
}
}
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 9e142480..fc18e240 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -7,7 +7,23 @@
<div class="panel-heading">
<div class="user-info">
<div class="container">
- <router-link :to="userProfileLink(user)">
+ <a
+ v-if="allowZoomingAvatar"
+ class="user-info-avatar-link"
+ @click="zoomAvatar"
+ >
+ <UserAvatar
+ :better-shadow="betterShadow"
+ :user="user"
+ />
+ <div class="user-info-avatar-link-overlay">
+ <i class="button-icon icon-zoom-in" />
+ </div>
+ </a>
+ <router-link
+ v-else
+ :to="userProfileLink(user)"
+ >
<UserAvatar
:better-shadow="betterShadow"
:user="user"
@@ -351,6 +367,7 @@
.container {
padding: 16px 0 6px;
display: flex;
+ align-items: flex-start;
max-height: 56px;
.avatar {
@@ -372,6 +389,35 @@
}
}
+ &-avatar-link {
+ position: relative;
+ cursor: pointer;
+
+ &-overlay {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.3);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: $fallback--avatarRadius;
+ border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ opacity: 0;
+ transition: opacity .2s ease;
+
+ i {
+ color: #FFF;
+ }
+ }
+
+ &:hover &-overlay {
+ opacity: 1;
+ }
+ }
+
.usersettings {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 39b99dac..00055707 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -22,21 +22,23 @@ const FriendList = withLoadMore({
additionalPropNames: ['userId']
})(List)
+const defaultTabKey = 'statuses'
+
const UserProfile = {
data () {
return {
error: false,
- userId: null
+ userId: null,
+ tab: defaultTabKey
}
},
created () {
- // Make sure that timelines used in this page are empty
- this.cleanUp()
const routeParams = this.$route.params
this.load(routeParams.name || routeParams.id)
+ this.tab = get(this.$route, 'query.tab', defaultTabKey)
},
destroyed () {
- this.cleanUp()
+ this.stopFetching()
},
computed: {
timeline () {
@@ -67,17 +69,36 @@ const UserProfile = {
},
methods: {
load (userNameOrId) {
+ const startFetchingTimeline = (timeline, userId) => {
+ // Clear timeline only if load another user's profile
+ if (userId !== this.$store.state.statuses.timelines[timeline].userId) {
+ this.$store.commit('clearTimeline', { timeline })
+ }
+ this.$store.dispatch('startFetchingTimeline', { timeline, userId })
+ }
+
+ const loadById = (userId) => {
+ this.userId = userId
+ startFetchingTimeline('user', userId)
+ startFetchingTimeline('media', userId)
+ if (this.isUs) {
+ startFetchingTimeline('favorites', userId)
+ }
+ // Fetch all pinned statuses immediately
+ this.$store.dispatch('fetchPinnedStatuses', userId)
+ }
+
+ // Reset view
+ this.userId = null
+ this.error = false
+
// Check if user data is already loaded in store
const user = this.$store.getters.findUser(userNameOrId)
if (user) {
- this.userId = user.id
- this.fetchTimelines()
+ loadById(user.id)
} else {
this.$store.dispatch('fetchUser', userNameOrId)
- .then(({ id }) => {
- this.userId = id
- this.fetchTimelines()
- })
+ .then(({ id }) => loadById(id))
.catch((reason) => {
const errorMessage = get(reason, 'error.error')
if (errorMessage === 'No user with such user_id') { // Known error
@@ -90,40 +111,33 @@ const UserProfile = {
})
}
},
- fetchTimelines () {
- const userId = this.userId
- this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId })
- this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId })
- if (this.isUs) {
- this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId })
- }
- // Fetch all pinned statuses immediately
- this.$store.dispatch('fetchPinnedStatuses', userId)
- },
- cleanUp () {
+ stopFetching () {
this.$store.dispatch('stopFetching', 'user')
this.$store.dispatch('stopFetching', 'favorites')
this.$store.dispatch('stopFetching', 'media')
- this.$store.commit('clearTimeline', { timeline: 'user' })
- this.$store.commit('clearTimeline', { timeline: 'favorites' })
- this.$store.commit('clearTimeline', { timeline: 'media' })
+ },
+ switchUser (userNameOrId) {
+ this.stopFetching()
+ this.load(userNameOrId)
+ },
+ onTabSwitch (tab) {
+ this.tab = tab
+ this.$router.replace({ query: { tab } })
}
},
watch: {
'$route.params.id': function (newVal) {
if (newVal) {
- this.cleanUp()
- this.load(newVal)
+ this.switchUser(newVal)
}
},
'$route.params.name': function (newVal) {
if (newVal) {
- this.cleanUp()
- this.load(newVal)
+ this.switchUser(newVal)
}
},
- $route () {
- this.$refs.tabSwitcher.activateTab(0)()
+ '$route.query': function (newVal) {
+ this.tab = newVal.tab || defaultTabKey
}
},
components: {
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 4ea0a869..42516916 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -8,36 +8,28 @@
:user="user"
:switcher="true"
:selected="timeline.viewing"
+ :allow-zooming-avatar="true"
rounded="top"
/>
<tab-switcher
- ref="tabSwitcher"
+ :active-tab="tab"
:render-only-focused="true"
+ :on-switch="onTabSwitch"
>
- <div :label="$t('user_card.statuses')">
- <div class="timeline">
- <template v-for="statusId in user.pinnedStatuseIds">
- <Conversation
- v-if="timeline.statusesObject[statusId]"
- :key="statusId"
- class="status-fadein"
- :statusoid="timeline.statusesObject[statusId]"
- :collapsable="true"
- :show-pinned="true"
- />
- </template>
- </div>
- <Timeline
- :count="user.statuses_count"
- :embedded="true"
- :title="$t('user_profile.timeline_title')"
- :timeline="timeline"
- :timeline-name="'user'"
- :user-id="userId"
- />
- </div>
+ <Timeline
+ key="statuses"
+ :label="$t('user_card.statuses')"
+ :count="user.statuses_count"
+ :embedded="true"
+ :title="$t('user_profile.timeline_title')"
+ :timeline="timeline"
+ timeline-name="user"
+ :user-id="userId"
+ :pinned-status-ids="user.pinnedStatusIds"
+ />
<div
v-if="followsTabVisible"
+ key="followees"
:label="$t('user_card.followees')"
:disabled="!user.friends_count"
>
@@ -52,6 +44,7 @@
</div>
<div
v-if="followersTabVisible"
+ key="followers"
:label="$t('user_card.followers')"
:disabled="!user.followers_count"
>
@@ -68,6 +61,7 @@
</FollowerList>
</div>
<Timeline
+ key="media"
:label="$t('user_card.media')"
:disabled="!media.visibleStatuses.length"
:embedded="true"
@@ -78,6 +72,7 @@
/>
<Timeline
v-if="isUs"
+ key="favorites"
:label="$t('user_card.favorites')"
:disabled="!favorites.visibleStatuses.length"
:embedded="true"
diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js
index f8100257..1aa3a4cd 100644
--- a/src/components/who_to_follow/who_to_follow.js
+++ b/src/components/who_to_follow/who_to_follow.js
@@ -21,11 +21,12 @@ const WhoToFollow = {
name: i.display_name,
screen_name: i.acct,
profile_image_url: i.avatar || '/images/avi.png',
- profile_image_url_original: i.avatar || '/images/avi.png'
+ profile_image_url_original: i.avatar || '/images/avi.png',
+ statusnet_profile_url: i.url
}
this.users.push(user)
- this.$store.state.api.backendInteractor.externalProfile(user.screen_name)
+ this.$store.state.api.backendInteractor.fetchUser({ id: user.screen_name })
.then((externalUser) => {
if (!externalUser.error) {
this.$store.commit('addNewUsers', [externalUser])
diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js
index 7d01678b..dcb56106 100644
--- a/src/components/who_to_follow_panel/who_to_follow_panel.js
+++ b/src/components/who_to_follow_panel/who_to_follow_panel.js
@@ -13,7 +13,7 @@ function showWhoToFollow (panel, reply) {
toFollow.img = img
toFollow.name = name
- panel.$store.state.api.backendInteractor.externalProfile(name)
+ panel.$store.state.api.backendInteractor.fetchUser({ id: name })
.then((externalUser) => {
if (!externalUser.error) {
panel.$store.commit('addNewUsers', [externalUser])
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 13f7168f..12920b92 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -267,7 +267,7 @@
"loop_video": "Loop videos",
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"mutes_tab": "Mutes",
- "play_videos_in_modal": "Play videos directly in the media viewer",
+ "play_videos_in_modal": "Play videos in a popup frame",
"use_contain_fit": "Don't crop the attachment in thumbnails",
"name": "Name",
"name_bio": "Name & Bio",
@@ -513,7 +513,9 @@
"pinned": "Pinned",
"delete_confirm": "Do you really want to delete this status?",
"reply_to": "Reply to",
- "replies_list": "Replies:"
+ "replies_list": "Replies:",
+ "mute_conversation": "Mute conversation",
+ "unmute_conversation": "Unmute conversation"
},
"user_card": {
"approve": "Approve",
@@ -611,5 +613,16 @@
"person_talking": "{count} person talking",
"people_talking": "{count} people talking",
"no_results": "No results"
+ },
+ "password_reset": {
+ "forgot_password": "Forgot password?",
+ "password_reset": "Password reset",
+ "instruction": "Enter your email address or username. We will send you a link to reset your password.",
+ "placeholder": "Your email or username",
+ "check_email": "Check your email for a link to reset your password.",
+ "return_home": "Return to the home page",
+ "not_found": "We couldn't find that email or username.",
+ "too_many_requests": "You have reached the limit of attempts, try again later.",
+ "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator."
}
}
diff --git a/src/i18n/es.json b/src/i18n/es.json
index bf87937a..009599f5 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -9,9 +9,9 @@
"features_panel": {
"chat": "Chat",
"gopher": "Gopher",
- "media_proxy": "Media proxy",
+ "media_proxy": "Proxy de medios",
"scope_options": "Opciones del alcance de la visibilidad",
- "text_limit": "Límite de carácteres",
+ "text_limit": "Límite de caracteres",
"title": "Características",
"who_to_follow": "A quién seguir"
},
@@ -47,19 +47,19 @@
"login": {
"login": "Identificación",
"description": "Identificación con OAuth",
- "logout": "Salir",
+ "logout": "Cerrar sesión",
"password": "Contraseña",
"placeholder": "p.ej. lain",
- "register": "Registrar",
+ "register": "Registrarse",
"username": "Usuario",
"hint": "Inicia sesión para unirte a la discusión",
- "authentication_code": "Código de autentificación",
+ "authentication_code": "Código de autenticación",
"enter_recovery_code": "Inserta el código de recuperación",
- "enter_two_factor_code": "Inserta el código de doble factor",
+ "enter_two_factor_code": "Inserta el código de dos factores",
"recovery_code": "Código de recuperación",
"heading" : {
- "totp" : "Autentificación de doble factor",
- "recovery" : "Recuperación de doble factor"
+ "totp" : "Autenticación de dos factores",
+ "recovery" : "Recuperación de dos factores"
}
},
"media_modal": {
@@ -67,13 +67,13 @@
"next": "Siguiente"
},
"nav": {
- "about": "Sobre",
+ "about": "Acerca de",
"back": "Volver",
"chat": "Chat Local",
- "friend_requests": "Solicitudes de amistad",
+ "friend_requests": "Solicitudes de seguimiento",
"mentions": "Menciones",
"interactions": "Interacciones",
- "dms": "Mensajes Directo",
+ "dms": "Mensajes Directos",
"public_tl": "Línea Temporal Pública",
"timeline": "Línea Temporal",
"twkn": "Toda La Red Conocida",
@@ -89,7 +89,7 @@
"load_older": "Cargar notificaciones antiguas",
"notifications": "Notificaciones",
"read": "¡Leído!",
- "repeated_you": "repite tu estado",
+ "repeated_you": "repitió tu estado",
"no_more_notifications": "No hay más notificaciones"
},
"polls": {
@@ -100,16 +100,19 @@
"vote": "Votar",
"type": "Tipo de encuesta",
"single_choice": "Elección única",
- "multiple_choices": "Múltiples elecciones",
+ "multiple_choices": "Elección múltiple",
"expiry": "Tiempo de vida de la encuesta",
"expires_in": "La encuensta termina en {0}",
"expired": "La encuesta terminó hace {0}",
"not_enough_options": "Muy pocas opciones únicas en la encuesta"
},
+ "stickers": {
+ "add_sticker": "Añadir Pegatina"
+ },
"interactions": {
"favs_repeats": "Favoritos y Repetidos",
"follows": "Nuevos seguidores",
- "load_older": "Cargar interacciones antiguas"
+ "load_older": "Cargar interacciones más antiguas"
},
"post_status": {
"new_status": "Publicar un nuevo estado",
@@ -134,20 +137,20 @@
},
"scope": {
"direct": "Directo - Solo para los usuarios mencionados.",
- "private": "Solo-Seguidores - Solo tus seguidores leeran la publicación",
+ "private": "Solo-seguidores - Solo tus seguidores leerán la publicación",
"public": "Público - Entradas visibles en las Líneas Temporales Públicas",
- "unlisted": "Sin Listar - Entradas no visibles en las Líneas Temporales Públicas"
+ "unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas"
}
},
"registration": {
"bio": "Biografía",
"email": "Correo electrónico",
"fullname": "Nombre a mostrar",
- "password_confirm": "Confirmación de contraseña",
+ "password_confirm": "Confirmar contraseña",
"registration": "Registro",
"token": "Token de invitación",
"captcha": "CAPTCHA",
- "new_captcha": "Click en la imagen para obtener un nuevo captca",
+ "new_captcha": "Haz click en la imagen para obtener un nuevo captcha",
"username_placeholder": "p.ej. lain",
"fullname_placeholder": "p.ej. Lain Iwakura",
"bio_placeholder": "e.g.\nHola, soy un ejemplo.\nAquí puedes poner algo representativo tuyo... o no.",
@@ -161,7 +164,7 @@
}
},
"selectable_list": {
- "select_all": "Seleccionarlo todo"
+ "select_all": "Seleccionar todo"
},
"settings": {
"app_name": "Nombre de la aplicación",
@@ -172,8 +175,8 @@
"setup_otp" : "Configurar OTP",
"wait_pre_setup_otp" : "preconfiguración OTP",
"confirm_and_enable" : "Confirmar y habilitar OTP",
- "title": "Autentificación de Doble Factor",
- "generate_new_recovery_codes" : "Generar nuevos códigos de recuperación",
+ "title": "Autentificación de dos factores",
+ "generate_new_recovery_codes" : "Generar códigos de recuperación nuevos",
"warning_of_generate_new_codes" : "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
"recovery_codes" : "Códigos de recuperación.",
"waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
@@ -181,16 +184,16 @@
"authentication_methods" : "Métodos de autentificación",
"scan": {
"title": "Escanear",
- "desc": "Usando su aplicación de doble factor, escanee este código QR o ingrese la clave de texto:",
+ "desc": "Usando su aplicación de dos factores, escanee este código QR o ingrese la clave de texto:",
"secret_code": "Clave"
},
"verify": {
- "desc": "Para habilitar la autenticación de doble factor, ingrese el código de su aplicación 2FA:"
+ "desc": "Para habilitar la autenticación de dos factores, ingrese el código de su aplicación 2FA:"
}
},
"attachmentRadius": "Adjuntos",
"attachments": "Adjuntos",
- "autoload": "Activar carga automática al llegar al final de la página",
+ "autoload": "Habilitar carga automática al llegar al final de la página",
"avatar": "Avatar",
"avatarAltRadius": "Avatares (Notificaciones)",
"avatarRadius": "Avatares",
@@ -224,12 +227,12 @@
"delete_account_instructions": "Escribe tu contraseña para confirmar la eliminación de tu cuenta.",
"avatar_size_instruction": "El tamaño mínimo recomendado para el avatar es de 150X150 píxeles.",
"export_theme": "Exportar tema",
- "filtering": "Filtros",
+ "filtering": "Filtrado",
"filtering_explanation": "Todos los estados que contengan estas palabras serán silenciados, una por línea",
"follow_export": "Exportar personas que tú sigues",
- "follow_export_button": "Exporta tus seguidores a un archivo csv",
+ "follow_export_button": "Exporta tus seguidores a un fichero csv",
"follow_import": "Importar personas que tú sigues",
- "follow_import_error": "Error al importal el archivo",
+ "follow_import_error": "Error al importar el fichero",
"follows_imported": "¡Importado! Procesarlos llevará tiempo.",
"foreground": "Primer plano",
"general": "General",
@@ -259,7 +262,7 @@
"loop_video": "Vídeos en bucle",
"loop_video_silent_only": "Bucle solo en vídeos sin sonido (p.ej. \"gifs\" de Mastodon)",
"mutes_tab": "Silenciados",
- "play_videos_in_modal": "Reproducir los vídeos directamente en el visor de medios",
+ "play_videos_in_modal": "Reproducir los vídeos en un marco emergente",
"use_contain_fit": "No recortar los adjuntos en miniaturas",
"name": "Nombre",
"name_bio": "Nombre y Biografía",
@@ -274,8 +277,8 @@
"no_mutes": "No hay usuarios sinlenciados",
"hide_follows_description": "No mostrar a quién sigo",
"hide_followers_description": "No mostrar quién me sigue",
- "show_admin_badge": "Mostrar la placa de administrador en mi perfil",
- "show_moderator_badge": "Mostrar la placa de moderador en mi perfil",
+ "show_admin_badge": "Mostrar la insignia de Administrador en mi perfil",
+ "show_moderator_badge": "Mostrar la insignia de Moderador en mi perfil",
"nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW",
"oauth_tokens": "Tokens de OAuth",
"token": "Token",
@@ -288,13 +291,13 @@
"profile_background": "Fondo del Perfil",
"profile_banner": "Cabecera del Perfil",
"profile_tab": "Perfil",
- "radii_help": "Estable el redondeo de las esquinas del interfaz (en píxeles)",
+ "radii_help": "Estable el redondeo de las esquinas de la interfaz (en píxeles)",
"replies_in_timeline": "Réplicas en la línea temporal",
- "reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encim",
+ "reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encima",
"reply_visibility_all": "Mostrar todas las réplicas",
"reply_visibility_following": "Solo mostrar réplicas para mí o usuarios a los que sigo",
"reply_visibility_self": "Solo mostrar réplicas para mí",
- "autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (móvil)",
+ "autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (para móviles)",
"saving_err": "Error al guardar los ajustes",
"saving_ok": "Ajustes guardados",
"search_user_to_block": "Buscar usuarios a bloquear",
@@ -303,25 +306,25 @@
"scope_copy": "Copiar la visibilidad de la publicación cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
"minimal_scopes_mode": "Minimizar las opciones de publicación",
"set_new_avatar": "Cambiar avatar",
- "set_new_profile_background": "Cambiar fondo del perfil",
- "set_new_profile_banner": "Cambiar cabecera del perfil",
+ "set_new_profile_background": "Cambiar el fondo del perfil",
+ "set_new_profile_banner": "Cambiar la cabecera del perfil",
"settings": "Ajustes",
"subject_input_always_show": "Mostrar siempre el campo del tema",
- "subject_line_behavior": "Copiar el tema en las contestaciones",
- "subject_line_email": "Tipo email: \"re: tema\"",
- "subject_line_mastodon": "Tipo mastodon: copiar como es",
+ "subject_line_behavior": "Copiar el tema en las respuestas",
+ "subject_line_email": "Como email: \"re: tema\"",
+ "subject_line_mastodon": "Como mastodon: copiar como es",
"subject_line_noop": "No copiar",
"post_status_content_type": "Formato de publicación",
"stop_gifs": "Iniciar GIFs al pasar el ratón",
- "streaming": "Habilite la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
+ "streaming": "Habilitar la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
"text": "Texto",
"theme": "Tema",
"theme_help": "Use códigos de color hexadecimales (#rrggbb) para personalizar su tema de colores.",
- "theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación, use el botón \"Borrar todo\" para deshacer los cambios.",
- "theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón para obtener información detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
+ "theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.",
+ "theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
"tooltipRadius": "Información/alertas",
"upload_a_photo": "Subir una foto",
- "user_settings": "Ajustes de Usuario",
+ "user_settings": "Ajustes del Usuario",
"values": {
"false": "no",
"true": "sí"
@@ -392,14 +395,14 @@
"shadow_id": "Sombra #{value}",
"blur": "Difuminar",
"spread": "Cantidad",
- "inset": "Insertada",
+ "inset": "Sombra interior",
"hint": "Para las sombras, también puede usar --variable como un valor de color para usar las variables CSS3. Tenga en cuenta que establecer la opacidad no funcionará en este caso.",
"filter_hint": {
"always_drop_shadow": "Advertencia, esta sombra siempre usa {0} cuando el navegador lo soporta.",
"drop_shadow_syntax": "{0} no soporta el parámetro {1} y la palabra clave {2}.",
- "avatar_inset": "Tenga en cuenta que la combinación de sombras insertadas como no-insertadas en los avatares, puede dar resultados inesperados con los avatares transparentes.",
+ "avatar_inset": "Tenga en cuenta que la combinación de sombras interiores como no-interiores en los avatares, puede dar resultados inesperados con los avatares transparentes.",
"spread_zero": "Sombras con una cantidad > 0 aparecerá como si estuviera puesto a cero",
- "inset_classic": "Las sombras insertadas estarán usando {0}"
+ "inset_classic": "Las sombras interiores estarán usando {0}"
},
"components": {
"panel": "Panel",
@@ -417,7 +420,7 @@
},
"fonts": {
"_tab_label": "Fuentes",
- "help": "Seleccione la fuente para utilizar para los elementos de la interfaz de usuario. Para \"personalizado\", debe ingresar el nombre exacto de la fuente tal como aparece en el sistema.",
+ "help": "Seleccione la fuente a utilizar para los elementos de la interfaz de usuario. Para \"personalizar\", debe ingresar el nombre exacto de la fuente tal como aparece en el sistema.",
"components": {
"interface": "Interfaz",
"input": "Campos de entrada",
@@ -476,7 +479,7 @@
"second_short": "{0}s",
"seconds_short": "{0}s",
"week": "{0} semana",
- "weeks": "{0} semana",
+ "weeks": "{0} semanas",
"week_short": "{0}sem",
"weeks_short": "{0}sem",
"year": "{0} año",
@@ -504,11 +507,11 @@
"unpin": "Desclavar de tu perfil",
"pinned": "Fijado",
"delete_confirm": "¿Realmente quieres borrar la publicación?",
- "reply_to": "Responder a",
+ "reply_to": "Respondiendo a",
"replies_list": "Respuestas:"
},
"user_card": {
- "approve": "Aprovar",
+ "approve": "Aprobar",
"block": "Bloquear",
"blocked": "¡Bloqueado!",
"deny": "Denegar",
@@ -535,8 +538,8 @@
"unblock": "Desbloquear",
"unblock_progress": "Desbloqueando...",
"block_progress": "Bloqueando...",
- "unmute": "Desenmudecer",
- "unmute_progress": "Sesenmudeciendo...",
+ "unmute": "Quitar silencio",
+ "unmute_progress": "Quitando silencio...",
"mute_progress": "Silenciando...",
"admin_menu": {
"moderation": "Moderación",
@@ -546,7 +549,7 @@
"revoke_moderator": "Revocar permisos de Moderador",
"activate_account": "Activar cuenta",
"deactivate_account": "Desactivar cuenta",
- "delete_account": "Borrar cuenta",
+ "delete_account": "Eliminar cuenta",
"force_nsfw": "Marcar todas las publicaciones como NSFW (no es seguro/apropiado para el trabajo)",
"strip_media": "Eliminar archivos multimedia de las publicaciones",
"force_unlisted": "Forzar que se publique en el modo -Sin Listar-",
@@ -554,12 +557,12 @@
"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": "Borrar usuario",
+ "delete_user": "Eliminar usuario",
"delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
}
},
"user_profile": {
- "timeline_title": "Linea temporal del usuario",
+ "timeline_title": "Linea Temporal del Usuario",
"profile_does_not_exist": "Lo sentimos, este perfil no existe.",
"profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil."
},
@@ -599,7 +602,7 @@
},
"search": {
"people": "Personas",
- "hashtags": "Hashtags",
+ "hashtags": "Etiquetas",
"person_talking": "{count} personas hablando",
"people_talking": "{count} gente hablando",
"no_results": "Sin resultados"
diff --git a/src/i18n/eu.json b/src/i18n/eu.json
new file mode 100644
index 00000000..1efaa310
--- /dev/null
+++ b/src/i18n/eu.json
@@ -0,0 +1,610 @@
+{
+ "chat": {
+ "title": "Txata"
+ },
+ "exporter": {
+ "export": "Esportatu",
+ "processing": "Prozesatzen, zure fitxategia deskargatzeko eskatuko zaizu laster"
+ },
+ "features_panel": {
+ "chat": "Txata",
+ "gopher": "Ghoper",
+ "media_proxy": "Media proxy",
+ "scope_options": "Ikusgaitasun aukerak",
+ "text_limit": "Testu limitea",
+ "title": "Ezaugarriak",
+ "who_to_follow": "Nori jarraitu"
+ },
+ "finder": {
+ "error_fetching_user": "Errorea erabiltzailea eskuratzen",
+ "find_user": "Bilatu erabiltzailea"
+ },
+ "general": {
+ "apply": "Aplikatu",
+ "submit": "Bidali",
+ "more": "Gehiago",
+ "generic_error": "Errore bat gertatu da",
+ "optional": "Hautazkoa",
+ "show_more": "Gehiago erakutsi",
+ "show_less": "Gutxiago erakutsi",
+ "cancel": "Ezeztatu",
+ "disable": "Ezgaitu",
+ "enable": "Gaitu",
+ "confirm": "Baieztatu",
+ "verify": "Egiaztatu"
+ },
+ "image_cropper": {
+ "crop_picture": "Moztu argazkia",
+ "save": "Gorde",
+ "save_without_cropping": "Gorde moztu gabe",
+ "cancel": "Ezeztatu"
+ },
+ "importer": {
+ "submit": "Bidali",
+ "success": "Ondo inportatu da.",
+ "error": "Errore bat gertatu da fitxategi hau inportatzerakoan."
+ },
+ "login": {
+ "login": "Saioa hasi",
+ "description": "OAuth-ekin saioa hasi",
+ "logout": "Saioa itxi",
+ "password": "Pasahitza",
+ "placeholder": "adibidez Lain",
+ "register": "Erregistratu",
+ "username": "Erabiltzaile-izena",
+ "hint": "Hasi saioa eztabaidan parte-hartzeko",
+ "authentication_code": "Autentifikazio kodea",
+ "enter_recovery_code": "Sartu berreskuratze kodea",
+ "enter_two_factor_code": "Sartu bi-faktore kodea",
+ "recovery_code": "Berreskuratze kodea",
+ "heading": {
+ "totp": "Bi-faktore autentifikazioa",
+ "recovery": "Bi-faktore berreskuratzea"
+ }
+ },
+ "media_modal": {
+ "previous": "Aurrekoa",
+ "next": "Hurrengoa"
+ },
+ "nav": {
+ "about": "Honi buruz",
+ "back": "Atzera",
+ "chat": "Txat lokala",
+ "friend_requests": "Jarraitzeko eskaerak",
+ "mentions": "Aipamenak",
+ "interactions": "Interakzioak",
+ "dms": "Zuzeneko Mezuak",
+ "public_tl": "Denbora-lerro Publikoa",
+ "timeline": "Denbora-lerroa",
+ "twkn": "Ezagutzen den Sarea",
+ "user_search": "Erabiltzailea Bilatu",
+ "search": "Bilatu",
+ "who_to_follow": "Nori jarraitu",
+ "preferences": "Hobespenak"
+ },
+ "notifications": {
+ "broken_favorite": "Egoera ezezaguna, bilatzen...",
+ "favorited_you": "zure mezua gogoko du",
+ "followed_you": "Zu jarraitzen zaitu",
+ "load_older": "Kargatu jakinarazpen zaharragoak",
+ "notifications": "Jakinarazpenak",
+ "read": "Irakurri!",
+ "repeated_you": "zure mezua errepikatu du",
+ "no_more_notifications": "Ez dago jakinarazpen gehiago"
+ },
+ "polls": {
+ "add_poll": "Inkesta gehitu",
+ "add_option": "Gehitu aukera",
+ "option": "Aukera",
+ "votes": "Bozkak",
+ "vote": "Bozka",
+ "type": "Inkesta mota",
+ "single_choice": "Aukera bakarra",
+ "multiple_choices": "Aukera anizkoitza",
+ "expiry": "Inkestaren iraupena",
+ "expires_in": "Inkesta {0} bukatzen da",
+ "expired": "Inkesta {0} bukatu zen",
+ "not_enough_options": "Aukera gutxiegi inkestan"
+ },
+ "stickers": {
+ "add_sticker": "Pegatina gehitu"
+ },
+ "interactions": {
+ "favs_repeats": "Errepikapen eta gogokoak",
+ "follows": "Jarraitzaile berriak",
+ "load_older": "Kargatu elkarrekintza zaharragoak"
+ },
+ "post_status": {
+ "new_status": "Mezu berri bat idatzi",
+ "account_not_locked_warning": "Zure kontua ez dago {0}. Edozeinek jarraitzen hastearekin, zure mezuak irakur dezake.",
+ "account_not_locked_warning_link": "Blokeatuta",
+ "attachments_sensitive": "Nabarmendu eranskinak hunkigarri gisa ",
+ "content_type": {
+ "text/plain": "Testu arrunta",
+ "text/html": "HTML",
+ "text/markdown": "Markdown",
+ "text/bbcode": "BBCode"
+ },
+ "content_warning": "Gaia (hautazkoa)",
+ "default": "Iadanik Los Angeles-en",
+ "direct_warning_to_all": "Mezu hau aipatutako erabiltzaile guztientzat ikusgai egongo da.",
+ "direct_warning_to_first_only": "Mezu hau ikusgai egongo da bakarrik hasieran aipatzen diren erabiltzaileei.",
+ "posting": "Argitaratzen",
+ "scope_notice": {
+ "public": "Mezu hau guztiontzat ikusgai izango da",
+ "private": "Mezu hau zure jarraitzaileek soilik ikusiko dute",
+ "unlisted": "Mezu hau ez da argitaratuko Denbora-lerro Publikoan ezta Ezagutzen den Sarean"
+ },
+ "scope": {
+ "direct": "Zuzena - Bidali aipatutako erabiltzaileei besterik ez",
+ "private": "Jarraitzaileentzako bakarrik- Bidali jarraitzaileentzat bakarrik",
+ "public": "Publickoa - Bistaratu denbora-lerro publikoetan",
+ "unlisted": "Zerrendatu gabea - ez bidali denbora-lerro publikoetan"
+ }
+ },
+ "registration": {
+ "bio": "Biografia",
+ "email": "E-posta",
+ "fullname": "Erakutsi izena",
+ "password_confirm": "Pasahitza berretsi",
+ "registration": "Izena ematea",
+ "token": "Gonbidapen txartela",
+ "captcha": "CAPTCHA",
+ "new_captcha": "Klikatu irudia captcha berri bat lortzeko",
+ "username_placeholder": "Adibidez lain",
+ "fullname_placeholder": "Adibidez Lain Iwakura",
+ "bio_placeholder": "Adidibez.\nKaixo, Lain naiz.\nFedibertsoa gustokoa dut eta euskeraz hitzegiten dut.",
+ "validations": {
+ "username_required": "Ezin da hutsik utzi",
+ "fullname_required": "Ezin da hutsik utzi",
+ "email_required": "Ezin da hutsik utzi",
+ "password_required": "Ezin da hutsik utzi",
+ "password_confirmation_required": "Ezin da hutsik utzi",
+ "password_confirmation_match": "Pasahitzaren berdina izan behar du"
+ }
+ },
+ "selectable_list": {
+ "select_all": "Hautatu denak"
+ },
+ "settings": {
+ "app_name": "App izena",
+ "security": "Segurtasuna",
+ "enter_current_password_to_confirm": "Sar ezazu zure egungo pasahitza zure identitatea baieztatzeko",
+ "mfa": {
+ "otp": "OTP",
+ "setup_otp": "OTP konfiguratu",
+ "wait_pre_setup_otp": "OTP aurredoitzen",
+ "confirm_and_enable": "Baieztatu eta gaitu OTP",
+ "title": "Bi-faktore autentifikazioa",
+ "generate_new_recovery_codes": "Sortu berreskuratze kode berriak",
+ "warning_of_generate_new_codes": "Berreskuratze kode berriak sortzean, zure berreskuratze kode zaharrak ez dute balioko",
+ "recovery_codes": "Berreskuratze kodea",
+ "waiting_a_recovery_codes": "Babes-kopia kodeak jasotzen...",
+ "recovery_codes_warning": "Idatzi edo gorde kodeak leku seguruan - bestela ez dituzu berriro ikusiko. Zure 2FA aplikaziorako sarbidea eta berreskuratze kodeak galduz gero, zure kontutik blokeatuta egongo zara.",
+ "authentication_methods": "Autentifikazio metodoa",
+ "scan": {
+ "title": "Eskaneatu",
+ "desc": "Zure bi-faktore aplikazioa erabiliz, eskaneatu QR kode hau edo idatzi testu-gakoa:",
+ "secret_code": "Giltza"
+ },
+ "verify": {
+ "desc": "Bi-faktore autentifikazioa gaitzeko, sar ezazu bi-faktore kodea zure app-tik"
+ }
+ },
+ "attachmentRadius": "Eranskinak",
+ "attachments": "Eranskinak",
+ "autoload": "Gaitu karga automatikoa beheraino mugitzean",
+ "avatar": "Avatarra",
+ "avatarAltRadius": "Avatarra (Aipamenak)",
+ "avatarRadius": "Avatarrak",
+ "background": "Atzeko planoa",
+ "bio": "Biografia",
+ "block_export": "Bloke esportatzea",
+ "block_export_button": "Esportatu zure blokeak csv fitxategi batera",
+ "block_import": "Bloke inportazioa",
+ "block_import_error": "Errorea blokeak inportatzen",
+ "blocks_imported": "Blokeak inportaturik! Hauek prozesatzeak denbora hartuko du.",
+ "blocks_tab": "Blokeak",
+ "btnRadius": "Botoiak",
+ "cBlue": "Urdina (erantzun, jarraitu)",
+ "cGreen": "Berdea (Bertxiotu)",
+ "cOrange": "Laranja (Gogokoa)",
+ "cRed": "Gorria (ezeztatu)",
+ "change_password": "Pasahitza aldatu",
+ "change_password_error": "Arazao bat egon da zure pasahitza aldatzean",
+ "changed_password": "Pasahitza ondo aldatu da!",
+ "collapse_subject": "Bildu gaia daukaten mezuak",
+ "composing": "Idazten",
+ "confirm_new_password": "Baieztatu pasahitz berria",
+ "current_avatar": "Zure uneko avatarra",
+ "current_password": "Indarrean den pasahitza",
+ "current_profile_banner": "Zure profilaren banner-a",
+ "data_import_export_tab": "Datuak Inportatu / Esportatu",
+ "default_vis": "Lehenetsitako ikusgaitasunak",
+ "delete_account": "Ezabatu kontua",
+ "delete_account_description": "Betirako ezabatu zure kontua eta zure mezu guztiak",
+ "delete_account_error": "Arazo bat gertatu da zure kontua ezabatzerakoan. Arazoa jarraitu eskero, administratzailearekin harremanetan jarri.",
+ "delete_account_instructions": "Idatzi zure pasahitza kontua ezabatzeko.",
+ "avatar_size_instruction": "Avatar irudien gomendatutako gutxieneko tamaina 150x150 pixel dira.",
+ "export_theme": "Gorde aurre-ezarpena",
+ "filtering": "Iragazten",
+ "filtering_explanation": "Hitz hauek dituzten muzu guztiak isilduak izango dira. Lerro bakoitzeko bat",
+ "follow_export": "Jarraitzen dituzunak esportatu",
+ "follow_export_button": "Esportatu zure jarraitzaileak csv fitxategi batean",
+ "follow_import": "Jarraitzen dituzunak inportatu",
+ "follow_import_error": "Errorea jarraitzaileak inportatzerakoan",
+ "follows_imported": "Jarraitzaileak inportatuta! Prozesatzeak denbora pixka bat iraungo du.",
+ "foreground": "Aurreko planoa",
+ "general": "Orokorra",
+ "hide_attachments_in_convo": "Ezkutatu eranskinak elkarrizketatan ",
+ "hide_attachments_in_tl": "Ezkutatu eranskinak donbora-lerroan",
+ "hide_muted_posts": "Ezkutatu mutututako erabiltzaileen mezuak",
+ "max_thumbnails": "Mezu bakoitzeko argazki-miniatura kopuru maximoa",
+ "hide_isp": "Instantziari buruzko panela ezkutatu",
+ "preload_images": "Argazkiak aurrekargatu",
+ "use_one_click_nsfw": "Ireki eduki hunkigarria duten eranskinak klik batekin",
+ "hide_post_stats": "Ezkutatu mezuaren estatistikak (adibidez faborito kopurua)",
+ "hide_user_stats": "Ezkutatu erabiltzaile estatistikak (adibidez jarraitzaile kopurua)",
+ "hide_filtered_statuses": "Ezkutatu iragazitako mezuak",
+ "import_blocks_from_a_csv_file": "Blokeatutakoak inportatu CSV fitxategi batetik",
+ "import_followers_from_a_csv_file": "Inportatu jarraitzaileak csv fitxategi batetik",
+ "import_theme": "Kargatu aurre-ezarpena",
+ "inputRadius": "Sarrera eremuak",
+ "checkboxRadius": "Kuadrotxoak",
+ "instance_default": "(lehenetsia: {value})",
+ "instance_default_simple": "(lehenetsia)",
+ "interface": "Interfazea",
+ "interfaceLanguage": "Interfaze hizkuntza",
+ "invalid_theme_imported": "Hautatutako fitxategia ez da onartutako Pleroma gaia. Ez da zure gaian aldaketarik burutu.",
+ "limited_availability": "Ez dago erabilgarri zure nabigatzailean",
+ "links": "Estekak",
+ "lock_account_description": "Mugatu zure kontua soilik onartutako jarraitzaileei",
+ "loop_video": "Begizta bideoak",
+ "loop_video_silent_only": "Soinu gabeko bideoak begiztatu bakarrik (adibidez Mastodon-eko gif-ak)",
+ "mutes_tab": "Mututuak",
+ "play_videos_in_modal": "Erreproduzitu bideoak zuzenean multimedia erreproduzigailuan",
+ "use_contain_fit": "Eranskinak ez moztu miniaturetan",
+ "name": "Izena",
+ "name_bio": "Izena eta biografia",
+ "new_password": "Pasahitz berria",
+ "notification_visibility": "Erakusteko jakinarazpen motak",
+ "notification_visibility_follows": "Jarraitzaileak",
+ "notification_visibility_likes": "Gogokoak",
+ "notification_visibility_mentions": "Aipamenak",
+ "notification_visibility_repeats": "Errepikapenak",
+ "no_rich_text_description": "Kendu testu-formatu aberastuak mezu guztietatik",
+ "no_blocks": "Ez daude erabiltzaile blokeatutak",
+ "no_mutes": "Ez daude erabiltzaile mututuak",
+ "hide_follows_description": "Ez erakutsi nor jarraitzen ari naizen",
+ "hide_followers_description": "Ez erakutsi nor ari de ni jarraitzen",
+ "show_admin_badge": "Erakutsi Administratzaile etiketa nire profilan",
+ "show_moderator_badge": "Erakutsi Moderatzaile etiketa nire profilan",
+ "nsfw_clickthrough": "Gaitu klika hunkigarri eranskinak ezkutatzeko",
+ "oauth_tokens": "OAuth tokenak",
+ "token": "Tokena",
+ "refresh_token": "Berrgin Tokena",
+ "valid_until": "Baliozkoa Arte",
+ "revoke_token": "Ezeztatu",
+ "panelRadius": "Panelak",
+ "pause_on_unfocused": "Eguneraketa automatikoa gelditu fitxatik kanpo",
+ "presets": "Aurrezarpenak",
+ "profile_background": "Profilaren atzeko planoa",
+ "profile_banner": "Profilaren Banner-a",
+ "profile_tab": "Profila",
+ "radii_help": "Konfiguratu interfazearen ertzen biribiltzea (pixeletan)",
+ "replies_in_timeline": "Denbora-lerroko erantzunak",
+ "reply_link_preview": "Gaitu erantzun-estekaren aurrebista arratoiarekin",
+ "reply_visibility_all": "Erakutsi erantzun guztiak",
+ "reply_visibility_following": "Erakutsi bakarrik niri zuzendutako edo nik jarraitutako erabiltzaileen erantzunak",
+ "reply_visibility_self": "Erakutsi bakarrik niri zuzendutako erantzunak",
+ "autohide_floating_post_button": "Automatikoki ezkutatu Mezu Berriaren botoia (sakelako)",
+ "saving_err": "Errorea ezarpenak gordetzean",
+ "saving_ok": "Ezarpenak gordeta",
+ "search_user_to_block": "Bilatu zein blokeatu nahi duzun",
+ "search_user_to_mute": "Bilatu zein isilarazi nahi duzun",
+ "security_tab": "Segurtasuna",
+ "scope_copy": "Ikusgaitasun aukerak kopiatu mezua erantzuterakoan (Zuzeneko Mezuak beti kopiatzen dute)",
+ "minimal_scopes_mode": "Bildu ikusgaitasun aukerak",
+ "set_new_avatar": "Ezarri avatar berria",
+ "set_new_profile_background": "Ezarri atzeko plano berria",
+ "set_new_profile_banner": "Ezarri profil banner berria",
+ "settings": "Ezarpenak",
+ "subject_input_always_show": "Erakutsi beti gaiaren eremua",
+ "subject_line_behavior": "Gaia kopiatu erantzuterakoan",
+ "subject_line_email": "E-maila bezala: \"re: gaia\"",
+ "subject_line_mastodon": "Mastodon bezala: kopiatu den bezala",
+ "subject_line_noop": "Ez kopiatu",
+ "post_status_content_type": "Argitarapen formatua",
+ "stop_gifs": "GIF-a iniziatu arratoia gainean jarrita",
+ "streaming": "Gaitu mezu berrien karga goraino mugitzean",
+ "text": "Testua",
+ "theme": "Gaia",
+ "theme_help": "Erabili hex-kolore kodeak (#rrggbb) gaiaren koloreak pertsonalizatzeko.",
+ "theme_help_v2_1": "Zenbait osagaien koloreak eta opakutasuna ezeztatu ditzakezu kontrol-laukia aktibatuz, \"Garbitu dena\" botoia erabili aldaketak deusezteko.",
+ "theme_help_v2_2": "Sarreren batzuen azpian dauden ikonoak atzeko planoaren eta testuaren arteko kontrastearen adierazleak dira, kokatu arratoia gainean informazio zehatza eskuratzeko. Kontuan izan gardentasun kontrasteen adierazleek erabiltzen direnean, kasurik okerrena erakusten dutela.",
+ "tooltipRadius": "Argibideak/alertak",
+ "upload_a_photo": "Argazkia kargatu",
+ "user_settings": "Erabiltzaile Ezarpenak",
+ "values": {
+ "false": "ez",
+ "true": "bai"
+ },
+ "notifications": "Jakinarazpenak",
+ "notification_setting": "Jaso pertsona honen jakinarazpenak:",
+ "notification_setting_follows": "Jarraitutako erabiltzaileak",
+ "notification_setting_non_follows": "Jarraitzen ez dituzun erabiltzaileak",
+ "notification_setting_followers": "Zu jarraitzen zaituzten erabiltzaileak",
+ "notification_setting_non_followers": "Zu jarraitzen ez zaituzten erabiltzaileak",
+ "notification_mutes": "Erabiltzaile jakin baten jakinarazpenak jasotzeari uzteko, isilarazi ezazu.",
+ "notification_blocks": "Erabiltzaile bat blokeatzeak jakinarazpen guztiak gelditzen ditu eta harpidetza ezeztatu.",
+ "enable_web_push_notifications": "Gaitu web jakinarazpenak",
+ "style": {
+ "switcher": {
+ "keep_color": "Mantendu koloreak",
+ "keep_shadows": "Mantendu itzalak",
+ "keep_opacity": "Mantendu opakotasuna",
+ "keep_roundness": "Mantendu biribiltasuna",
+ "keep_fonts": "Mantendu iturriak",
+ "save_load_hint": "\"Mantendu\" aukerak uneko konfiguratutako aukerak gordetzen ditu gaiak hautatzerakoan edo kargatzean, gai hauek esportatze garaian ere gordetzen ditu. Kontrol-lauki guztiak garbitzen direnean, esportazio-gaiak dena gordeko du.",
+ "reset": "Berrezarri",
+ "clear_all": "Garbitu dena",
+ "clear_opacity": "Garbitu opakotasuna"
+ },
+ "common": {
+ "color": "Kolorea",
+ "opacity": "Opakotasuna",
+ "contrast": {
+ "hint": "Kontrastearen erlazioa {ratio} da, {level} {context}",
+ "level": {
+ "aa": "AA Mailako gidaliburua betetzen du (gutxienezkoa)",
+ "aaa": "AAA Mailako gidaliburua betetzen du (gomendatua)",
+ "bad": "ez ditu irisgarritasun arauak betetzen"
+ },
+ "context": {
+ "18pt": "testu handientzat (+18pt)",
+ "text": "testuentzat"
+ }
+ }
+ },
+ "common_colors": {
+ "_tab_label": "Ohikoa",
+ "main": "Ohiko koloreak",
+ "foreground_hint": "Ikusi \"Aurreratua\" fitxa kontrol zehatzagoa lortzeko",
+ "rgbo": "Ikono, azentu eta etiketak"
+ },
+ "advanced_colors": {
+ "_tab_label": "Aurreratua",
+ "alert": "Alerten atzeko planoa",
+ "alert_error": "Errorea",
+ "badge": "Etiketen atzeko planoa",
+ "badge_notification": "Jakinarazpenak",
+ "panel_header": "Panelaren goiburua",
+ "top_bar": "Goiko barra",
+ "borders": "Ertzak",
+ "buttons": "Botoiak",
+ "inputs": "Sarrera eremuak",
+ "faint_text": "Testu itzalita"
+ },
+ "radii": {
+ "_tab_label": "Biribiltasuna"
+ },
+ "shadows": {
+ "_tab_label": "Itzal eta argiak",
+ "component": "Atala",
+ "override": "Berridatzi",
+ "shadow_id": "Itzala #{value}",
+ "blur": "Lausotu",
+ "spread": "Hedapena",
+ "inset": "Barrutik",
+ "hint": "Itzaletarako ere erabil dezakezu --aldagarri kolore balio gisa CSS3 aldagaiak erabiltzeko. Kontuan izan opakutasuna ezartzeak ez duela kasu honetan funtzionatuko.",
+ "filter_hint": {
+ "always_drop_shadow": "Kontuz, itzal honek beti erabiltzen du {0} nabigatzaileak onartzen duenean.",
+ "drop_shadow_syntax": "{0} ez du onartzen {1} parametroa eta {2} gako-hitza.",
+ "avatar_inset": "Kontuan izan behar da barruko eta kanpoko itzal konbinazioak, ez esparotako emaitzak ager daitezkeela atzeko plano gardena duten Avatarretan.",
+ "spread_zero": "Hedapena > 0 duten itzalak zero izango balitz bezala agertuko dira",
+ "inset_classic": "Barruko itzalak {0} erabiliko dute"
+ },
+ "components": {
+ "panel": "Panela",
+ "panelHeader": "Panel goiburua",
+ "topBar": "Goiko barra",
+ "avatar": "Erabiltzailearen avatarra (profilan)",
+ "avatarStatus": "Erabiltzailearen avatarra (mezuetan)",
+ "popup": "Popup-ak eta argibideak",
+ "button": "Botoia",
+ "buttonHover": "Botoia (gainean)",
+ "buttonPressed": "Botoai (sakatuta)",
+ "buttonPressedHover": "Botoia (sakatuta+gainean)",
+ "input": "Sarrera eremuak"
+ }
+ },
+ "fonts": {
+ "_tab_label": "Letra-tipoak",
+ "help": "Aukeratu letra-tipoak erabiltzailearen interfazean erabiltzeko. \"Pertsonalizatua\" letra-tipoan, sisteman agertzen den izen berdinarekin idatzi behar duzu.",
+ "components": {
+ "interface": "Interfazea",
+ "input": "Sarrera eremuak",
+ "post": "Mezuen testua",
+ "postCode": "Tarte-bakarreko testua mezuetan (testu-formatu aberastuak)"
+ },
+ "family": "Letra-tipoaren izena",
+ "size": "Tamaina (px)",
+ "weight": "Pisua (lodiera)",
+ "custom": "Pertsonalizatua"
+ },
+ "preview": {
+ "header": "Aurrebista",
+ "content": "Edukia",
+ "error": "Adibide errorea",
+ "button": "Botoia",
+ "text": "Hamaika {0} eta {1}",
+ "mono": "edukia",
+ "input": "Jadanik Los Angeles-en",
+ "faint_link": "laguntza",
+ "fine_print": "Irakurri gure {0} ezer erabilgarria ikasteko!",
+ "header_faint": "Ondo dago",
+ "checkbox": "Baldintzak berrikusi ditut",
+ "link": "esteka polita"
+ }
+ },
+ "version": {
+ "title": "Bertsioa",
+ "backend_version": "Backend Bertsio",
+ "frontend_version": "Frontend Bertsioa"
+ }
+ },
+ "time": {
+ "day": "{0} egun",
+ "days": "{0} egun",
+ "day_short": "{0}d",
+ "days_short": "{0}d",
+ "hour": "{0} ordu",
+ "hours": "{0} ordu",
+ "hour_short": "{0}o",
+ "hours_short": "{0}o",
+ "in_future": "{0} barru",
+ "in_past": "duela {0}",
+ "minute": "{0} minutu",
+ "minutes": "{0} minutu",
+ "minute_short": "{0}min",
+ "minutes_short": "{0}min",
+ "month": "{0} hilabete",
+ "months": "{0} hilabete",
+ "month_short": "{0}h",
+ "months_short": "{0}h",
+ "now": "oraintxe bertan",
+ "now_short": "orain",
+ "second": "{0} segundu",
+ "seconds": "{0} segundu",
+ "second_short": "{0}s",
+ "seconds_short": "{0}s",
+ "week": "{0} aste",
+ "weeks": "{0} aste",
+ "week_short": "{0}a",
+ "weeks_short": "{0}a",
+ "year": "{0} urte",
+ "years": "{0} urte",
+ "year_short": "{0}u",
+ "years_short": "{0}u"
+ },
+ "timeline": {
+ "collapse": "Bildu",
+ "conversation": "Elkarrizketa",
+ "error_fetching": "Errorea eguneraketak eskuratzen",
+ "load_older": "Kargatu mezu zaharragoak",
+ "no_retweet_hint": "Mezu hau jarraitzailentzko bakarrik markatuta dago eta ezin da errepikatu",
+ "repeated": "Errepikatuta",
+ "show_new": "Berriena erakutsi",
+ "up_to_date": "Eguneratuta",
+ "no_more_statuses": "Ez daude mezu gehiago",
+ "no_statuses": "Mezurik gabe"
+ },
+ "status": {
+ "favorites": "Gogokoak",
+ "repeats": "Errepikapenak",
+ "delete": "Mezua ezabatu",
+ "pin": "Profilan ainguratu",
+ "unpin": "Aingura ezeztatu profilatik",
+ "pinned": "Ainguratuta",
+ "delete_confirm": "Mezu hau benetan ezabatu nahi duzu?",
+ "reply_to": "Erantzun",
+ "replies_list": "Erantzunak:"
+ },
+ "user_card": {
+ "approve": "Onartu",
+ "block": "Blokeatu",
+ "blocked": "Blokeatuta!",
+ "deny": "Ukatu",
+ "favorites": "Gogokoak",
+ "follow": "Jarraitu",
+ "follow_sent": "Eskaera bidalita!",
+ "follow_progress": "Eskatzen...",
+ "follow_again": "Eskaera berriro bidali?",
+ "follow_unfollow": "Jarraitzeari utzi",
+ "followees": "Jarraitzen",
+ "followers": "Jarraitzaileak",
+ "following": "Jarraitzen!",
+ "follows_you": "Jarraitzen dizu!",
+ "its_you": "Zu zara!",
+ "media": "Multimedia",
+ "mute": "Isilarazi",
+ "muted": "Isilduta",
+ "per_day": "eguneko",
+ "remote_follow": "Jarraitu",
+ "report": "Berri eman",
+ "statuses": "Mezuak",
+ "subscribe": "Harpidetu",
+ "unsubscribe": "Harpidetza ezeztatu",
+ "unblock": "Blokeoa kendu",
+ "unblock_progress": "Blokeoa ezeztatzen...",
+ "block_progress": "Blokeatzen...",
+ "unmute": "Isiltasuna kendu",
+ "unmute_progress": "Isiltasuna kentzen...",
+ "mute_progress": "Isiltzen...",
+ "admin_menu": {
+ "moderation": "Moderazioa",
+ "grant_admin": "Administratzaile baimena",
+ "revoke_admin": "Ezeztatu administratzaile baimena",
+ "grant_moderator": "Moderatzaile baimena",
+ "revoke_moderator": "Ezeztatu moderatzaile baimena",
+ "activate_account": "Aktibatu kontua",
+ "deactivate_account": "Desaktibatu kontua",
+ "delete_account": "Ezabatu kontua",
+ "force_nsfw": "Markatu mezu guztiak hunkigarri gisa",
+ "strip_media": "Kendu multimedia mezuetatik",
+ "force_unlisted": "Behartu mezuak listatu gabekoak izatea",
+ "sandbox": "Behartu zure jarraitzaileentzako bakarrik argitaratzera",
+ "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."
+ }
+ },
+ "user_profile": {
+ "timeline_title": "Erabiltzailearen denbora-lerroa",
+ "profile_does_not_exist": "Barkatu, profil hau ez da existitzen.",
+ "profile_loading_error": "Barkatu, errore bat gertatu da profila kargatzean."
+ },
+ "user_reporting": {
+ "title": "{0}-ri buruz berri ematen",
+ "add_comment_description": "Zure kexa moderatzaileei bidaliko da. Nahi baduzu zure kexaren zergatia idatz dezakezu:",
+ "additional_comments": "Iruzkin gehiago",
+ "forward_description": "Kontu hau beste instantzia batekoa da. Nahi duzu txostenaren kopia bat bidali ere?",
+ "forward_to": "{0}-ri birbidali",
+ "submit": "Bidali",
+ "generic_error": "Errore bat gertatu da zure eskaera prozesatzerakoan."
+ },
+ "who_to_follow": {
+ "more": "Gehiago",
+ "who_to_follow": "Nori jarraitu"
+ },
+ "tool_tip": {
+ "media_upload": "Multimedia igo",
+ "repeat": "Erreplikatu",
+ "reply": "Erantzun",
+ "favorite": "Gogokoa",
+ "user_settings": "Erabiltzaile ezarpenak"
+ },
+ "upload": {
+ "error": {
+ "base": "Igoerak huts egin du.",
+ "file_too_big": "Artxiboa haundiegia [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+ "default": "Saiatu berriro geroago"
+ },
+ "file_size_units": {
+ "B": "B",
+ "KiB": "KiB",
+ "MiB": "MiB",
+ "GiB": "GiB",
+ "TiB": "TiB"
+ }
+ },
+ "search": {
+ "people": "Gendea",
+ "hashtags": "Traolak",
+ "person_talking": "{count} pertsona hitzegiten",
+ "people_talking": "{count} gende hitzegiten",
+ "no_results": "Emaitzarik ez"
+ }
+} \ No newline at end of file
diff --git a/src/i18n/fi.json b/src/i18n/fi.json
index f4179495..e7ed5408 100644
--- a/src/i18n/fi.json
+++ b/src/i18n/fi.json
@@ -278,8 +278,15 @@
"status": {
"favorites": "Tykkäykset",
"repeats": "Toistot",
+ "delete": "Poista",
+ "pin": "Kiinnitä profiiliisi",
+ "unpin": "Poista kiinnitys",
+ "pinned": "Kiinnitetty",
+ "delete_confirm": "Haluatko varmasti postaa viestin?",
"reply_to": "Vastaus",
- "replies_list": "Vastaukset:"
+ "replies_list": "Vastaukset:",
+ "mute_conversation": "Hiljennä keskustelu",
+ "unmute_conversation": "Poista hiljennys"
},
"user_card": {
"approve": "Hyväksy",
diff --git a/src/i18n/ja.json b/src/i18n/ja.json
index c77f28a6..b4c6015d 100644
--- a/src/i18n/ja.json
+++ b/src/i18n/ja.json
@@ -78,6 +78,7 @@
"timeline": "タイムライン",
"twkn": "つながっているすべてのネットワーク",
"user_search": "ユーザーをさがす",
+ "search": "さがす",
"who_to_follow": "おすすめユーザー",
"preferences": "せってい"
},
@@ -105,6 +106,9 @@
"expired": "いれふだは {0} まえに、おわりました",
"not_enough_options": "ユニークなオプションが、たりません"
},
+ "stickers": {
+ "add_sticker": "ステッカーをふやす"
+ },
"interactions": {
"favs_repeats": "リピートとおきにいり",
"follows": "あたらしいフォロー",
@@ -506,7 +510,9 @@
"pinned": "ピンどめ",
"delete_confirm": "ほんとうに、このステータスを、けしてもいいですか?",
"reply_to": "へんしん:",
- "replies_list": "へんしん:"
+ "replies_list": "へんしん:",
+ "mute_conversation": "スレッドをミュートする",
+ "unmute_conversation": "スレッドをミュートするのをやめる"
},
"user_card": {
"approve": "うけいれ",
@@ -531,6 +537,8 @@
"remote_follow": "リモートフォロー",
"report": "つうほう",
"statuses": "ステータス",
+ "subscribe": "サブスクライブ",
+ "unsubscribe": "サブスクライブをやめる",
"unblock": "ブロックをやめる",
"unblock_progress": "ブロックをとりけしています...",
"block_progress": "ブロックしています...",
@@ -595,5 +603,12 @@
"GiB": "GiB",
"TiB": "TiB"
}
+ },
+ "search": {
+ "people": "ひとびと",
+ "hashtags": "ハッシュタグ",
+ "person_talking": "{count} にんが、はなしています",
+ "people_talking": "{count} にんが、はなしています",
+ "no_results": "みつかりませんでした"
}
}
diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json
index 992a2fa6..42bb53d4 100644
--- a/src/i18n/ja_pedantic.json
+++ b/src/i18n/ja_pedantic.json
@@ -78,6 +78,7 @@
"timeline": "タイムライン",
"twkn": "接続しているすべてのネットワーク",
"user_search": "ユーザーを探す",
+ "search": "検索",
"who_to_follow": "おすすめユーザー",
"preferences": "設定"
},
@@ -105,6 +106,9 @@
"expired": "投票は {0} 前に終了しました",
"not_enough_options": "相異なる選択肢が不足しています"
},
+ "stickers": {
+ "add_sticker": "ステッカーを追加"
+ },
"interactions": {
"favs_repeats": "リピートとお気に入り",
"follows": "新しいフォロワー",
@@ -506,7 +510,9 @@
"pinned": "ピン留め",
"delete_confirm": "本当にこのステータスを削除してもよろしいですか?",
"reply_to": "返信",
- "replies_list": "返信:"
+ "replies_list": "返信:",
+ "mute_conversation": "スレッドをミュート",
+ "unmute_conversation": "スレッドのミュートを解除"
},
"user_card": {
"approve": "受け入れ",
@@ -531,6 +537,8 @@
"remote_follow": "リモートフォロー",
"report": "通報",
"statuses": "ステータス",
+ "subscribe": "購読",
+ "unsubscribe": "購読を解除",
"unblock": "ブロック解除",
"unblock_progress": "ブロックを解除しています...",
"block_progress": "ブロックしています...",
@@ -595,5 +603,12 @@
"GiB": "GiB",
"TiB": "TiB"
}
+ },
+ "search": {
+ "people": "人々",
+ "hashtags": "ハッシュタグ",
+ "person_talking": "{count} 人が話しています",
+ "people_talking": "{count} 人が話しています",
+ "no_results": "見つかりませんでした"
}
}
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index 404a4079..89c8a8c8 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -16,6 +16,7 @@ const messages = {
eo: require('./eo.json'),
es: require('./es.json'),
et: require('./et.json'),
+ eu: require('./eu.json'),
fi: require('./fi.json'),
fr: require('./fr.json'),
ga: require('./ga.json'),
@@ -32,6 +33,7 @@ const messages = {
pt: require('./pt.json'),
ro: require('./ro.json'),
ru: require('./ru.json'),
+ te: require('./te.json'),
zh: require('./zh.json')
}
diff --git a/src/i18n/nb.json b/src/i18n/nb.json
index 298dc0b9..248b05bc 100644
--- a/src/i18n/nb.json
+++ b/src/i18n/nb.json
@@ -2,14 +2,18 @@
"chat": {
"title": "Nettprat"
},
+ "exporter": {
+ "export": "Eksporter",
+ "processing": "Arbeider, du vil snart bli spurt om å laste ned filen din"
+ },
"features_panel": {
"chat": "Nettprat",
"gopher": "Gopher",
"media_proxy": "Media proxy",
"scope_options": "Velg mottakere",
- "text_limit": "Tekst-grense",
+ "text_limit": "Tekstgrense",
"title": "Egenskaper",
- "who_to_follow": "Hvem å følge"
+ "who_to_follow": "Kontoer å følge"
},
"finder": {
"error_fetching_user": "Feil ved henting av bruker",
@@ -17,23 +21,66 @@
},
"general": {
"apply": "Bruk",
- "submit": "Send"
+ "submit": "Send",
+ "more": "Mer",
+ "generic_error": "Det oppsto en feil",
+ "optional": "valgfritt",
+ "show_more": "Vis mer",
+ "show_less": "Vis mindre",
+ "cancel": "Avbryt",
+ "disable": "Slå av",
+ "enable": "Slå på",
+ "confirm": "Godta",
+ "verify": "Godkjenn"
+ },
+ "image_cropper": {
+ "crop_picture": "Minsk bilde",
+ "save": "Lagre",
+ "save_without_cropping": "Lagre uten å minske bildet",
+ "cancel": "Avbryt"
+ },
+ "importer": {
+ "submit": "Send",
+ "success": "Importering fullført",
+ "error": "Det oppsto en feil under importering av denne filen"
},
"login": {
"login": "Logg inn",
+ "description": "Log inn med OAuth",
"logout": "Logg ut",
"password": "Passord",
"placeholder": "f. eks lain",
"register": "Registrer",
- "username": "Brukernavn"
+ "username": "Brukernavn",
+ "hint": "Logg inn for å delta i diskusjonen",
+ "authentication_code": "Verifikasjonskode",
+ "enter_recovery_code": "Skriv inn en gjenopprettingskode",
+ "enter_two_factor_code": "Skriv inn en to-faktors kode",
+ "recovery_code": "Gjenopprettingskode",
+ "heading" : {
+ "totp" : "To-faktors autentisering",
+ "recovery" : "To-faktors gjenoppretting"
+ }
+ },
+ "media_modal": {
+ "previous": "Forrige",
+ "next": "Neste"
},
"nav": {
+ "about": "Om",
+ "back": "Tilbake",
"chat": "Lokal nettprat",
"friend_requests": "Følgeforespørsler",
"mentions": "Nevnt",
+ "interactions": "Interaksjooner",
+ "dms": "Direktemeldinger",
"public_tl": "Offentlig Tidslinje",
"timeline": "Tidslinje",
- "twkn": "Det hele kjente nettverket"
+ "twkn": "Det hele kjente nettverket",
+ "user_search": "Søk etter brukere",
+ "search": "Søk",
+ "who_to_follow": "Kontoer å følge",
+ "preferences": "Innstillinger"
},
"notifications": {
"broken_favorite": "Ukjent status, leter etter den...",
@@ -42,19 +89,52 @@
"load_older": "Last eldre varsler",
"notifications": "Varslinger",
"read": "Les!",
- "repeated_you": "Gjentok din status"
+ "repeated_you": "Gjentok din status",
+ "no_more_notifications": "Ingen gjenstående varsler"
+ },
+ "polls": {
+ "add_poll": "Legg til undersøkelse",
+ "add_option": "Legg til svaralternativ",
+ "option": "Svaralternativ",
+ "votes": "stemmer",
+ "vote": "Stem",
+ "type": "Undersøkelsestype",
+ "single_choice": "Enkeltvalg",
+ "multiple_choices": "Flervalg",
+ "expiry": "Undersøkelsestid",
+ "expires_in": "Undersøkelsen er over om {0}",
+ "expired": "Undersøkelsen ble ferdig {0} siden",
+ "not_enough_options": "For få unike svaralternativer i undersøkelsen"
+ },
+ "stickers": {
+ "add_sticker": "Legg til klistremerke"
+ },
+ "interactions": {
+ "favs_repeats": "Gjentakelser og favoritter",
+ "follows": "Nye følgere",
+ "load_older": "Last eldre interaksjoner"
},
"post_status": {
+ "new_status": "Legg ut ny status",
"account_not_locked_warning": "Kontoen din er ikke {0}. Hvem som helst kan følge deg for å se dine statuser til følgere",
"account_not_locked_warning_link": "låst",
"attachments_sensitive": "Merk vedlegg som sensitive",
"content_type": {
- "text/plain": "Klar tekst"
+ "text/plain": "Klar tekst",
+ "text/html": "HTML",
+ "text/markdown": "Markdown",
+ "text/bbcode": "BBCode"
},
"content_warning": "Tema (valgfritt)",
"default": "Landet akkurat i L.A.",
- "direct_warning": "Denne statusen vil kun bli sett av nevnte brukere",
+ "direct_warning_to_all": "Denne statusen vil være synlig av nevnte brukere",
+ "direct_warning_to_first_only": "Denne statusen vil være synlig for de brukerene som blir nevnt først i statusen.",
"posting": "Publiserer",
+ "scope_notice": {
+ "public": "Denne statusen vil være synlig for alle",
+ "private": "Denne statusen vil være synlig for dine følgere",
+ "unlisted": "Denne statusen vil ikke være synlig i Offentlig Tidslinje eller Det Hele Kjente Nettverket"
+ },
"scope": {
"direct": "Direkte, publiser bare til nevnte brukere",
"private": "Bare følgere, publiser bare til brukere som følger deg",
@@ -68,9 +148,49 @@
"fullname": "Visningsnavn",
"password_confirm": "Bekreft passord",
"registration": "Registrering",
- "token": "Invitasjons-bevis"
+ "token": "Invitasjons-bevis",
+ "captcha": "CAPTCHA",
+ "new_captcha": "Trykk på bildet for å få en ny captcha",
+ "username_placeholder": "f.eks. Lain Iwakura",
+ "fullname_placeholder": "f.eks. Lain Iwakura",
+ "bio_placeholder": "e.g.\nHei, jeg er Lain.\nJeg er en animert jente som bor i forstaden i Japan. Du kjenner meg kanskje fra the Wired.",
+ "validations": {
+ "username_required": "kan ikke stå tomt",
+ "fullname_required": "kan ikke stå tomt",
+ "email_required": "kan ikke stå tomt",
+ "password_required": "kan ikke stå tomt",
+ "password_confirmation_required": "kan ikke stå tomt",
+ "password_confirmation_match": "skal være det samme som passord"
+ }
+ },
+ "selectable_list": {
+ "select_all": "Velg alle"
},
"settings": {
+ "app_name": "Applikasjonsnavn",
+ "security": "Sikkerhet",
+ "enter_current_password_to_confirm": "Skriv inn ditt nåverende passord for å bekrefte din identitet",
+ "mfa": {
+ "otp" : "OTP",
+ "setup_otp" : "Set opp OTP",
+ "wait_pre_setup_otp" : "forhåndsstiller OTP",
+ "confirm_and_enable" : "Bekreft og slå på OTP",
+ "title": "To-faktors autentisering",
+ "generate_new_recovery_codes" : "Generer nye gjenopprettingskoder",
+ "warning_of_generate_new_codes" : "Når du genererer nye gjenopprettingskoder, vil de gamle slutte å fungere.",
+ "recovery_codes" : "Gjenopprettingskoder.",
+ "waiting_a_recovery_codes": "Mottar gjenopprettingskoder...",
+ "recovery_codes_warning" : "Skriv disse kodene ned eller plasser dem ett sikkert sted - ellers så vil du ikke se dem igjen. Dersom du mister tilgang til din to-faktors app og dine gjenopprettingskoder, vil du bli stengt ute av kontoen din.",
+ "authentication_methods" : "Autentiseringsmetoder",
+ "scan": {
+ "title": "Skann",
+ "desc": "Ved hjelp av din to-faktors applikasjon, skann denne QR-koden eller skriv inn tekstnøkkelen",
+ "secret_code": "Nøkkel"
+ },
+ "verify": {
+ "desc": "For å skru på to-faktors autentisering, skriv inn koden i fra din to-faktors app:"
+ }
+ },
"attachmentRadius": "Vedlegg",
"attachments": "Vedlegg",
"autoload": "Automatisk lasting når du blar ned til bunnen",
@@ -79,6 +199,12 @@
"avatarRadius": "Profilbilde",
"background": "Bakgrunn",
"bio": "Biografi",
+ "block_export": "Eksporter blokkeringer",
+ "block_export_button": "Eksporter blokkeringer til en csv fil",
+ "block_import": "Import blokkeringer",
+ "block_import_error": "Det oppsto en feil under importering av blokkeringer",
+ "blocks_imported": "Blokkeringer importert, det vil ta litt å prossesere dem",
+ "blocks_tab": "Blokkeringer",
"btnRadius": "Knapper",
"cBlue": "Blå (Svar, følg)",
"cGreen": "Grønn (Gjenta)",
@@ -88,6 +214,7 @@
"change_password_error": "Feil ved endring av passord",
"changed_password": "Passord endret",
"collapse_subject": "Sammenfold statuser med tema",
+ "composing": "komponering",
"confirm_new_password": "Bekreft nytt passord",
"current_avatar": "Ditt nåværende profilbilde",
"current_password": "Nåværende passord",
@@ -95,15 +222,15 @@
"data_import_export_tab": "Data import / eksport",
"default_vis": "Standard visnings-omfang",
"delete_account": "Slett konto",
- "delete_account_description": "Slett din konto og alle dine statuser",
+ "delete_account_description": "Fjern din konto og alle dine meldinger for alltid.",
"delete_account_error": "Det oppsto et problem ved sletting av kontoen din, hvis dette problemet forblir kontakt din administrator",
"delete_account_instructions": "Skriv inn ditt passord i feltet nedenfor for å bekrefte sletting av konto",
+ "avatar_size_instruction": "Den anbefalte minste-størrelsen for profilbilder er 150x150 piksler",
"export_theme": "Lagre tema",
"filtering": "Filtrering",
"filtering_explanation": "Alle statuser som inneholder disse ordene vil bli dempet, en kombinasjon av tegn per linje",
"follow_export": "Eksporter følginger",
"follow_export_button": "Eksporter følgingene dine til en .csv fil",
- "follow_export_processing": "Jobber, du vil snart bli spurt om å laste ned filen din.",
"follow_import": "Importer følginger",
"follow_import_error": "Feil ved importering av følginger.",
"follows_imported": "Følginger importert! Behandling vil ta litt tid.",
@@ -111,10 +238,22 @@
"general": "Generell",
"hide_attachments_in_convo": "Gjem vedlegg i samtaler",
"hide_attachments_in_tl": "Gjem vedlegg på tidslinje",
+ "hide_muted_posts": "Gjem statuser i fra gjemte brukere",
+ "max_thumbnails": "Maks antall forhåndsbilder per status",
+ "hide_isp": "Gjem instans-spesifikt panel",
+ "preload_images": "Forhåndslast bilder",
+ "use_one_click_nsfw": "Åpne sensitive vedlegg med ett klikk",
+ "hide_post_stats": "Gjem status statistikk (f.eks. antall likes",
+ "hide_user_stats": "Gjem bruker statistikk (f.eks. antall følgere)",
+ "hide_filtered_statuses": "Gjem filtrerte statuser",
+ "import_blocks_from_a_csv_file": "Importer blokkeringer fra en csv fil",
"import_followers_from_a_csv_file": "Importer følginger fra en csv fil",
"import_theme": "Last tema",
- "inputRadius": "Input felt",
+ "inputRadius": "Tekst felt",
+ "checkboxRadius": "Sjekkbokser",
"instance_default": "(standard: {value})",
+ "instance_default_simple": "(standard)",
+ "interface": "Grensesnitt",
"interfaceLanguage": "Grensesnitt-språk",
"invalid_theme_imported": "Den valgte filen er ikke ett støttet Pleroma-tema, ingen endringer til ditt tema ble gjort",
"limited_availability": "Ikke tilgjengelig i din nettleser",
@@ -122,6 +261,9 @@
"lock_account_description": "Begrens din konto til bare godkjente følgere",
"loop_video": "Gjenta videoer",
"loop_video_silent_only": "Gjenta bare videoer uten lyd, (for eksempel Mastodon sine \"gifs\")",
+ "mutes_tab": "Dempinger",
+ "play_videos_in_modal": "Spill videoer direkte i media-avspilleren",
+ "use_contain_fit": "Ikke minsk vedlegget i forhåndsvisninger",
"name": "Navn",
"name_bio": "Navn & Biografi",
"new_password": "Nytt passord",
@@ -131,10 +273,16 @@
"notification_visibility_mentions": "Nevnt",
"notification_visibility_repeats": "Gjentakelser",
"no_rich_text_description": "Fjern all formatering fra statuser",
+ "no_blocks": "Ingen blokkeringer",
+ "no_mutes": "Ingen dempinger",
+ "hide_follows_description": "Ikke hvis hvem jeg følger",
+ "hide_followers_description": "Ikke hvis hvem som følger meg",
+ "show_admin_badge": "Hvis ett administratormerke på min profil",
+ "show_moderator_badge": "Hvis ett moderatormerke på min profil",
"nsfw_clickthrough": "Krev trykk for å vise statuser som kan være upassende",
"oauth_tokens": "OAuth Tokens",
"token": "Pollett",
- "refresh_token": "Refresh Token",
+ "refresh_token": "Fornyingspolett",
"valid_until": "Gyldig til",
"revoke_token": "Tilbakekall",
"panelRadius": "Panel",
@@ -149,25 +297,196 @@
"reply_visibility_all": "Vis alle svar",
"reply_visibility_following": "Vis bare svar som er til meg eller folk jeg følger",
"reply_visibility_self": "Vis bare svar som er til meg",
+ "autohide_floating_post_button": "Skjul Ny Status knapp automatisk (mobil)",
"saving_err": "Feil ved lagring av innstillinger",
"saving_ok": "Innstillinger lagret",
+ "search_user_to_block": "Søk etter hvem du vil blokkere",
+ "search_user_to_mute": "Søk etter hvem du vil dempe",
"security_tab": "Sikkerhet",
+ "scope_copy": "Kopier mottakere når du svarer noen (Direktemeldinger blir alltid kopiert",
+ "minimal_scopes_mode": "Minimaliser mottakervalg",
"set_new_avatar": "Rediger profilbilde",
"set_new_profile_background": "Rediger profil-bakgrunn",
"set_new_profile_banner": "Sett ny profil-banner",
"settings": "Innstillinger",
+ "subject_input_always_show": "Alltid hvis tema-felt",
+ "subject_line_behavior": "Kopier tema når du svarer",
+ "subject_line_email": "Som email: \"re: tema\"",
+ "subject_line_mastodon": "Som mastodon: kopier som den er",
+ "subject_line_noop": "Ikke koper",
+ "post_status_content_type": "Status innholdstype",
"stop_gifs": "Spill av GIFs når du holder over dem",
"streaming": "Automatisk strømming av nye statuser når du har bladd til toppen",
"text": "Tekst",
"theme": "Tema",
"theme_help": "Bruk heksadesimale fargekoder (#rrggbb) til å endre farge-temaet ditt.",
+ "theme_help_v2_1": "Du kan også overskrive noen komponenter sine farger og opasitet ved å sjekke av sjekkboksen, bruk \"Nullstill alt\" knappen for å fjerne alle overskrivelser.",
+ "theme_help_v2_2": "Ikoner under noen av innstillingene er bakgrunn/tekst kontrast indikatorer, hold over dem for detaljert informasjon. Vennligst husk at disse indikatorene viser det verste utfallet.",
"tooltipRadius": "Verktøytips/advarsler",
+ "upload_a_photo": "Last opp ett bilde",
"user_settings": "Brukerinstillinger",
"values": {
"false": "nei",
"true": "ja"
+ },
+ "notifications": "Varsler",
+ "notification_setting": "Motta varsler i fra:",
+ "notification_setting_follows": "Brukere du følger",
+ "notification_setting_non_follows": "Brukere du ikke følger",
+ "notification_setting_followers": "Brukere som følger deg",
+ "notification_setting_non_followers": "Brukere som ikke følger deg",
+ "notification_mutes": "For å stoppe å motta varsler i fra en spesifikk bruker, kan du dempe dem.",
+ "notification_blocks": "Hvis du blokkerer en bruker vil det stoppe alle varsler og i tilleg få dem til å slutte å følge deg",
+ "enable_web_push_notifications": "Skru på pushnotifikasjoner i nettlesere",
+ "style": {
+ "switcher": {
+ "keep_color": "Behold farger",
+ "keep_shadows": "Behold skygger",
+ "keep_opacity": "Behold opasitet",
+ "keep_roundness": "Behold rundhet",
+ "keep_fonts": "Behold fonter",
+ "save_load_hint": "\"Behold\" alternativer beholder de instillingene som er satt når du velger eller laster inn temaer, det lagrer også disse alternativene når du eksporterer ett tema, Når alle sjekkboksene er tomme, vil alt bli lagret når du eksporterer ett tema.",
+ "reset": "Still in på nytt",
+ "clear_all": "Nullstill alt",
+ "clear_opacity": "Nullstill opasitet"
+ },
+ "common": {
+ "color": "Farge",
+ "opacity": "Opasitet",
+ "contrast": {
+ "hint": "Kontrast forholdet er {ratio}, it {level} {context}",
+ "level": {
+ "aa": "møter Nivå AA retningslinje (minimal)",
+ "aaa": "møter Nivå AAA retningslinje (recommended)",
+ "bad": "møter ingen tilgjengeligshetsretningslinjer"
+ },
+ "context": {
+ "18pt": "for stor (18pt+) tekst",
+ "text": "for tekst"
+ }
+ }
+ },
+ "common_colors": {
+ "_tab_label": "Vanlig",
+ "main": "Vanlige farger",
+ "foreground_hint": "Se \"Avansert\" fanen for mer detaljert kontroll",
+ "rgbo": "Ikoner, aksenter, merker"
+ },
+ "advanced_colors": {
+ "_tab_label": "Avansert",
+ "alert": "Varslingsbakgrunn",
+ "alert_error": "Feil",
+ "badge": "Merkebakgrunn",
+ "badge_notification": "Varsling",
+ "panel_header": "Panelhode",
+ "top_bar": "Topplinje",
+ "borders": "Kanter",
+ "buttons": "Knapper",
+ "inputs": "Tekstfelt",
+ "faint_text": "Svak tekst"
+ },
+ "radii": {
+ "_tab_label": "Rundhet"
+ },
+ "shadows": {
+ "_tab_label": "Skygger og belysning",
+ "component": "Komponent",
+ "override": "Overskriv",
+ "shadow_id": "Skygge #{value}",
+ "blur": "Uklarhet",
+ "spread": "Spredning",
+ "inset": "Insett",
+ "hint": "For skygger kan du sette --variable som en fargeveerdi for å bruke CSS3 variabler. Vær oppmerksom på at å sette opasitet da ikke vil fungere her.",
+ "filter_hint": {
+ "always_drop_shadow": "Advarsel, denne skyggen bruker alltid {0} når nettleseren støtter det.",
+ "drop_shadow_syntax": "{0} støtter ikke {1} parameter og {2} nøkkelord.",
+ "avatar_inset": "Vær oppmerksom på at å kombinere både insatte og uinsatte skygger på profilbilder kan gi uforventede resultater med gjennomsiktige profilbilder.",
+ "spread_zero": "Skygger med spredning > 0 vil fremstå som de var satt til 0",
+ "inset_classic": "Insette skygger vil bruke {0}"
+ },
+ "components": {
+ "panel": "Panel",
+ "panelHeader": "Panelhode",
+ "topBar": "Topplinje",
+ "avatar": "Profilbilde (i profilvisning)",
+ "avatarStatus": "Profilbilde (i statusvisning)",
+ "popup": "Popups og tooltips",
+ "button": "Knapp",
+ "buttonHover": "Knapp (holdt)",
+ "buttonPressed": "Knapp (nedtrykt)",
+ "buttonPressedHover": "Knapp (nedtrykt+holdt)",
+ "input": "Tekstfelt"
+ }
+ },
+ "fonts": {
+ "_tab_label": "Fonter",
+ "help": "Velg font til elementene i brukergrensesnittet. For \"egendefinert\" må du skrive inn det nøyaktige font-navnet som det fremstår på systemet",
+ "components": {
+ "interface": "Grensesnitt",
+ "input": "Tekstfelt",
+ "post": "Statustekst",
+ "postCode": "Monospaced tekst i en status (rik tekst)"
+ },
+ "family": "Font naavn",
+ "size": "Størrelse (i piksler)",
+ "weight": "Vekt (dristighet)",
+ "custom": "Egendefinert"
+ },
+ "preview": {
+ "header": "Forhåndsvisning",
+ "content": "Innhold",
+ "error": "Eksempel feil",
+ "button": "Knapp",
+ "text": "Mye mer {0} og {1}",
+ "mono": "innhold",
+ "input": "Landet akkurat i L.A.",
+ "faint_link": "hjelpfull brukerveiledning",
+ "fine_print": "Les vår {0} for å lære ingenting nyttig!",
+ "header_faint": "Dette er OK",
+ "checkbox": "Jeg har skumlest vilkår og betingelser",
+ "link": "en flott liten link"
+ }
+ },
+ "version": {
+ "title": "Versjon",
+ "backend_version": "Backend Versjon",
+ "frontend_version": "Frontend Versjon"
}
},
+ "time": {
+ "day": "{0} dag",
+ "days": "{0} dager",
+ "day_short": "{0}d",
+ "days_short": "{0}d",
+ "hour": "{0} time",
+ "hours": "{0} timer",
+ "hour_short": "{0}t",
+ "hours_short": "{0}t",
+ "in_future": "om {0}",
+ "in_past": "{0} siden",
+ "minute": "{0} minutt",
+ "minutes": "{0} minutter",
+ "minute_short": "{0}min",
+ "minutes_short": "{0}min",
+ "month": "{0} måned",
+ "months": "{0} måneder",
+ "month_short": "{0}md.",
+ "months_short": "{0}md.",
+ "now": "akkurat nå",
+ "now_short": "nå",
+ "second": "{0} sekund",
+ "seconds": "{0} sekunder",
+ "second_short": "{0}s",
+ "seconds_short": "{0}s",
+ "week": "{0} uke",
+ "weeks": "{0} uker",
+ "week_short": "{0}u",
+ "weeks_short": "{0}u",
+ "year": "{0} år",
+ "years": "{0} år",
+ "year_short": "{0}år",
+ "years_short": "{0}år"
+ },
"timeline": {
"collapse": "Sammenfold",
"conversation": "Samtale",
@@ -176,29 +495,116 @@
"no_retweet_hint": "Status er markert som bare til følgere eller direkte og kan ikke gjentas",
"repeated": "gjentok",
"show_new": "Vis nye",
- "up_to_date": "Oppdatert"
+ "up_to_date": "Oppdatert",
+ "no_more_statuses": "Ingen flere statuser",
+ "no_statuses": "Ingen statuser"
+ },
+ "status": {
+ "favorites": "Favoritter",
+ "repeats": "Gjentakelser",
+ "delete": "Slett status",
+ "pin": "Fremhev på profil",
+ "unpin": "Fjern fremhevelse",
+ "pinned": "Fremhevet",
+ "delete_confirm": "Har du virkelig lyst til å slette denne statusen?",
+ "reply_to": "Svar til",
+ "replies_list": "Svar:"
},
"user_card": {
"approve": "Godkjenn",
"block": "Blokker",
"blocked": "Blokkert!",
"deny": "Avslå",
+ "favorites": "Favoritter",
"follow": "Følg",
+ "follow_sent": "Forespørsel sendt!",
+ "follow_progress": "Forespør…",
+ "follow_again": "Gjenta forespørsel?",
+ "follow_unfollow": "Avfølg",
"followees": "Følger",
"followers": "Følgere",
"following": "Følger!",
"follows_you": "Følger deg!",
+ "its_you": "Det er deg!",
+ "media": "Media",
"mute": "Demp",
"muted": "Dempet",
"per_day": "per dag",
"remote_follow": "Følg eksternt",
- "statuses": "Statuser"
+ "report": "Rapport",
+ "statuses": "Statuser",
+ "subscribe": "Abonner",
+ "unsubscribe": "Avabonner",
+ "unblock": "Fjern blokkering",
+ "unblock_progress": "Fjerner blokkering...",
+ "block_progress": "Blokkerer...",
+ "unmute": "Fjern demping",
+ "unmute_progress": "Fjerner demping...",
+ "mute_progress": "Demper...",
+ "admin_menu": {
+ "moderation": "Moderering",
+ "grant_admin": "Gi Administrator",
+ "revoke_admin": "Fjern Administrator",
+ "grant_moderator": "Gi Moderator",
+ "revoke_moderator": "Fjern Moderator",
+ "activate_account": "Aktiver konto",
+ "deactivate_account": "Deaktiver kontro",
+ "delete_account": "Slett konto",
+ "force_nsfw": "Merk alle statuser som sensitive",
+ "strip_media": "Fjern media i fra statuser",
+ "force_unlisted": "Tving statuser til å være uopplistet",
+ "sandbox": "Tving statuser til å bare vises til følgere",
+ "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."
+ }
},
"user_profile": {
- "timeline_title": "Bruker-tidslinje"
+ "timeline_title": "Bruker-tidslinje",
+ "profile_does_not_exist": "Beklager, denne profilen eksisterer ikke.",
+ "profile_loading_error": "Beklager, det oppsto en feil under lasting av denne profilen."
+ },
+ "user_reporting": {
+ "title": "Rapporterer {0}",
+ "add_comment_description": "Rapporten blir sent til moderatorene av din instans. Du kan gi en forklaring på hvorfor du rapporterer denne kontoen under:",
+ "additional_comments": "Videre kommentarer",
+ "forward_description": "Denne kontoen er fra en annen server, vil du sende en kopi av rapporten til dem også?",
+ "forward_to": "Videresend til {0}",
+ "submit": "Send",
+ "generic_error": "Det oppsto en feil under behandling av din forespørsel."
},
"who_to_follow": {
"more": "Mer",
- "who_to_follow": "Hvem å følge"
+ "who_to_follow": "Kontoer å følge"
+ },
+ "tool_tip": {
+ "media_upload": "Last opp media",
+ "repeat": "Gjenta",
+ "reply": "Svar",
+ "favorite": "Lik",
+ "user_settings": "Brukerinnstillinger"
+ },
+ "upload":{
+ "error": {
+ "base": "Det oppsto en feil under opplastning.",
+ "file_too_big": "Fil for stor [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+ "default": "Prøv igjen senere"
+ },
+ "file_size_units": {
+ "B": "B",
+ "KiB": "KiB",
+ "MiB": "MiB",
+ "GiB": "GiB",
+ "TiB": "TiB"
+ }
+ },
+ "search": {
+ "people": "Folk",
+ "hashtags": "Emneknagger",
+ "person_talking": "{count} person snakker om dette",
+ "people_talking": "{count} personer snakker om dette",
+ "no_results": "Ingen resultater"
}
}
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 90ed6664..3af65f40 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -389,5 +389,16 @@
"person_talking": "Популярно у {count} человека",
"people_talking": "Популярно у {count} человек",
"no_results": "Ничего не найдено"
+ },
+ "password_reset": {
+ "forgot_password": "Забыли пароль?",
+ "password_reset": "Сброс пароля",
+ "instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.",
+ "placeholder": "Ваш email или имя пользователя",
+ "check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.",
+ "return_home": "Вернуться на главную страницу",
+ "not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.",
+ "too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.",
+ "password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера."
}
}
diff --git a/src/i18n/te.json b/src/i18n/te.json
new file mode 100644
index 00000000..f0953d97
--- /dev/null
+++ b/src/i18n/te.json
@@ -0,0 +1,352 @@
+{
+ "chat.title": "చాట్",
+ "features_panel.chat": "చాట్",
+ "features_panel.gopher": "గోఫర్",
+ "features_panel.media_proxy": "మీడియా ప్రాక్సీ",
+ "features_panel.scope_options": "స్కోప్ ఎంపికలు",
+ "features_panel.text_limit": "వచన పరిమితి",
+ "features_panel.title": "లక్షణాలు",
+ "features_panel.who_to_follow": "ఎవరిని అనుసరించాలి",
+ "finder.error_fetching_user": "వినియోగదారుని పొందడంలో లోపం",
+ "finder.find_user": "వినియోగదారుని కనుగొనండి",
+ "general.apply": "వర్తించు",
+ "general.submit": "సమర్పించు",
+ "general.more": "మరిన్ని",
+ "general.generic_error": "ఒక తప్పిదం సంభవించినది",
+ "general.optional": "ఐచ్చికం",
+ "image_cropper.crop_picture": "చిత్రాన్ని కత్తిరించండి",
+ "image_cropper.save": "దాచు",
+ "image_cropper.save_without_cropping": "కత్తిరించకుండా సేవ్ చేయి",
+ "image_cropper.cancel": "రద్దుచేయి",
+ "login.login": "లాగిన్",
+ "login.description": "OAuth తో లాగిన్ అవ్వండి",
+ "login.logout": "లాగౌట్",
+ "login.password": "సంకేతపదము",
+ "login.placeholder": "ఉదా. lain",
+ "login.register": "నమోదు చేసుకోండి",
+ "login.username": "వాడుకరి పేరు",
+ "login.hint": "చర్చలో చేరడానికి లాగిన్ అవ్వండి",
+ "media_modal.previous": "ముందరి పుట",
+ "media_modal.next": "తరువాత",
+ "nav.about": "గురించి",
+ "nav.back": "వెనక్కి",
+ "nav.chat": "స్థానిక చాట్",
+ "nav.friend_requests": "అనుసరించడానికి అభ్యర్థనలు",
+ "nav.mentions": "ప్రస్తావనలు",
+ "nav.dms": "నేరుగా పంపిన సందేశాలు",
+ "nav.public_tl": "ప్రజా కాలక్రమం",
+ "nav.timeline": "కాలక్రమం",
+ "nav.twkn": "మొత్తం తెలిసిన నెట్వర్క్",
+ "nav.user_search": "వాడుకరి శోధన",
+ "nav.who_to_follow": "ఎవరిని అనుసరించాలి",
+ "nav.preferences": "ప్రాధాన్యతలు",
+ "notifications.broken_favorite": "తెలియని స్థితి, దాని కోసం శోధిస్తోంది...",
+ "notifications.favorited_you": "మీ స్థితిని ఇష్టపడ్డారు",
+ "notifications.followed_you": "మిమ్మల్ని అనుసరించారు",
+ "notifications.load_older": "పాత నోటిఫికేషన్లను లోడ్ చేయండి",
+ "notifications.notifications": "ప్రకటనలు",
+ "notifications.read": "చదివాను!",
+ "notifications.repeated_you": "మీ స్థితిని పునరావృతం చేసారు",
+ "notifications.no_more_notifications": "ఇక నోటిఫికేషన్లు లేవు",
+ "post_status.new_status": "క్రొత్త స్థితిని పోస్ట్ చేయండి",
+ "post_status.account_not_locked_warning": "మీ ఖాతా {౦} కాదు. ఎవరైనా మిమ్మల్ని అనుసరించి అనుచరులకు మాత్రమే ఉద్దేశించిన పోస్టులను చూడవచ్చు.",
+ "post_status.account_not_locked_warning_link": "తాళం వేయబడినది",
+ "post_status.attachments_sensitive": "జోడింపులను సున్నితమైనవిగా గుర్తించండి",
+ "post_status.content_type.text/plain": "సాధారణ అక్షరాలు",
+ "post_status.content_type.text/html": "హెచ్‌టిఎమ్ఎల్",
+ "post_status.content_type.text/markdown": "మార్క్డౌన్",
+ "post_status.content_warning": "విషయం (ఐచ్ఛికం)",
+ "post_status.default": "ఇప్పుడే విజయవాడలో దిగాను.",
+ "post_status.direct_warning": "ఈ పోస్ట్ మాత్రమే పేర్కొన్న వినియోగదారులకు మాత్రమే కనిపిస్తుంది.",
+ "post_status.posting": "పోస్ట్ చేస్తున్నా",
+ "post_status.scope.direct": "ప్రత్యక్ష - పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
+ "post_status.scope.private": "అనుచరులకు మాత్రమే - అనుచరులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
+ "post_status.scope.public": "పబ్లిక్ - ప్రజా కాలక్రమాలకు పోస్ట్ చేయబడుతుంది",
+ "post_status.scope.unlisted": "జాబితా చేయబడనిది - ప్రజా కాలక్రమాలకు పోస్ట్ చేయవద్దు",
+ "registration.bio": "బయో",
+ "registration.email": "ఈ మెయిల్",
+ "registration.fullname": "ప్రదర్శన పేరు",
+ "registration.password_confirm": "పాస్వర్డ్ నిర్ధారణ",
+ "registration.registration": "నమోదు",
+ "registration.token": "ఆహ్వాన టోకెన్",
+ "registration.captcha": "కాప్చా",
+ "registration.new_captcha": "కొత్త కాప్చా పొందుటకు చిత్రం మీద క్లిక్ చేయండి",
+ "registration.username_placeholder": "ఉదా. lain",
+ "registration.fullname_placeholder": "ఉదా. Lain Iwakura",
+ "registration.bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
+ "registration.validations.username_required": "ఖాళీగా విడిచిపెట్టరాదు",
+ "registration.validations.fullname_required": "ఖాళీగా విడిచిపెట్టరాదు",
+ "registration.validations.email_required": "ఖాళీగా విడిచిపెట్టరాదు",
+ "registration.validations.password_required": "ఖాళీగా విడిచిపెట్టరాదు",
+ "registration.validations.password_confirmation_required": "ఖాళీగా విడిచిపెట్టరాదు",
+ "registration.validations.password_confirmation_match": "సంకేతపదం వలె ఉండాలి",
+ "settings.app_name": "అనువర్తన పేరు",
+ "settings.attachmentRadius": "జోడింపులు",
+ "settings.attachments": "జోడింపులు",
+ "settings.autoload": "క్రిందికి స్క్రోల్ చేయబడినప్పుడు స్వయంచాలక లోడింగ్ని ప్రారంభించు",
+ "settings.avatar": "అవతారం",
+ "settings.avatarAltRadius": "అవతారాలు (ప్రకటనలు)",
+ "settings.avatarRadius": "అవతారాలు",
+ "settings.background": "బ్యాక్‌గ్రౌండు",
+ "settings.bio": "బయో",
+ "settings.blocks_tab": "బ్లాక్‌లు",
+ "settings.btnRadius": "బటన్లు",
+ "settings.cBlue": "నీలం (ప్రత్యుత్తరం, అనుసరించండి)",
+ "settings.cGreen": "Green (Retweet)",
+ "settings.cOrange": "ఆరెంజ్ (ఇష్టపడు)",
+ "settings.cRed": "Red (Cancel)",
+ "settings.change_password": "పాస్‌వర్డ్ మార్చండి",
+ "settings.change_password_error": "మీ పాస్వర్డ్ను మార్చడంలో సమస్య ఉంది.",
+ "settings.changed_password": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది!",
+ "settings.collapse_subject": "Collapse posts with subjects",
+ "settings.composing": "Composing",
+ "settings.confirm_new_password": "కొత్త పాస్వర్డ్ను నిర్ధారించండి",
+ "settings.current_avatar": "మీ ప్రస్తుత అవతారం",
+ "settings.current_password": "ప్రస్తుత పాస్వర్డ్",
+ "settings.current_profile_banner": "మీ ప్రస్తుత ప్రొఫైల్ బ్యానర్",
+ "settings.data_import_export_tab": "Data Import / Export",
+ "settings.default_vis": "Default visibility scope",
+ "settings.delete_account": "Delete Account",
+ "settings.delete_account_description": "మీ ఖాతా మరియు మీ అన్ని సందేశాలను శాశ్వతంగా తొలగించండి.",
+ "settings.delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
+ "settings.delete_account_instructions": "ఖాతా తొలగింపును నిర్ధారించడానికి దిగువ ఇన్పుట్లో మీ పాస్వర్డ్ను టైప్ చేయండి.",
+ "settings.avatar_size_instruction": "అవతార్ చిత్రాలకు సిఫార్సు చేసిన కనీస పరిమాణం 150x150 పిక్సెల్స్.",
+ "settings.export_theme": "Save preset",
+ "settings.filtering": "వడపోత",
+ "settings.filtering_explanation": "All statuses containing these words will be muted, one per line",
+ "settings.follow_export": "Follow export",
+ "settings.follow_export_button": "Export your follows to a csv file",
+ "settings.follow_export_processing": "Processing, you'll soon be asked to download your file",
+ "settings.follow_import": "Follow import",
+ "settings.follow_import_error": "అనుచరులను దిగుమతి చేయడంలో లోపం",
+ "settings.follows_imported": "Follows imported! Processing them will take a while.",
+ "settings.foreground": "Foreground",
+ "settings.general": "General",
+ "settings.hide_attachments_in_convo": "సంభాషణలలో జోడింపులను దాచు",
+ "settings.hide_attachments_in_tl": "కాలక్రమంలో జోడింపులను దాచు",
+ "settings.hide_muted_posts": "మ్యూట్ చేసిన వినియోగదారుల యొక్క పోస్ట్లను దాచిపెట్టు",
+ "settings.max_thumbnails": "Maximum amount of thumbnails per post",
+ "settings.hide_isp": "Hide instance-specific panel",
+ "settings.preload_images": "Preload images",
+ "settings.use_one_click_nsfw": "కేవలం ఒక క్లిక్ తో NSFW జోడింపులను తెరవండి",
+ "settings.hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
+ "settings.hide_user_stats": "Hide user statistics (e.g. the number of followers)",
+ "settings.hide_filtered_statuses": "Hide filtered statuses",
+ "settings.import_followers_from_a_csv_file": "Import follows from a csv file",
+ "settings.import_theme": "Load preset",
+ "settings.inputRadius": "Input fields",
+ "settings.checkboxRadius": "Checkboxes",
+ "settings.instance_default": "(default: {value})",
+ "settings.instance_default_simple": "(default)",
+ "settings.interface": "Interface",
+ "settings.interfaceLanguage": "Interface language",
+ "settings.invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
+ "settings.limited_availability": "మీ బ్రౌజర్లో అందుబాటులో లేదు",
+ "settings.links": "Links",
+ "settings.lock_account_description": "మీ ఖాతాను ఆమోదించిన అనుచరులకు మాత్రమే పరిమితం చేయండి",
+ "settings.loop_video": "Loop videos",
+ "settings.loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
+ "settings.mutes_tab": "మ్యూట్ చేయబడినవి",
+ "settings.play_videos_in_modal": "మీడియా వీక్షికలో నేరుగా వీడియోలను ప్లే చేయి",
+ "settings.use_contain_fit": "అటాచ్మెంట్ సూక్ష్మచిత్రాలను కత్తిరించవద్దు",
+ "settings.name": "Name",
+ "settings.name_bio": "పేరు & బయో",
+ "settings.new_password": "కొత్త సంకేతపదం",
+ "settings.notification_visibility": "చూపించవలసిన నోటిఫికేషన్ రకాలు",
+ "settings.notification_visibility_follows": "Follows",
+ "settings.notification_visibility_likes": "ఇష్టాలు",
+ "settings.notification_visibility_mentions": "ప్రస్తావనలు",
+ "settings.notification_visibility_repeats": "పునఃప్రసారాలు",
+ "settings.no_rich_text_description": "అన్ని పోస్ట్ల నుండి రిచ్ టెక్స్ట్ ఫార్మాటింగ్ను స్ట్రిప్ చేయండి",
+ "settings.no_blocks": "బ్లాక్స్ లేవు",
+ "settings.no_mutes": "మ్యూట్లు లేవు",
+ "settings.hide_follows_description": "నేను ఎవరిని అనుసరిస్తున్నానో చూపించవద్దు",
+ "settings.hide_followers_description": "నన్ను ఎవరు అనుసరిస్తున్నారో చూపవద్దు",
+ "settings.show_admin_badge": "నా ప్రొఫైల్ లో అడ్మిన్ బ్యాడ్జ్ చూపించు",
+ "settings.show_moderator_badge": "నా ప్రొఫైల్లో మోడరేటర్ బ్యాడ్జ్ని చూపించు",
+ "settings.nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
+ "settings.oauth_tokens": "OAuth tokens",
+ "settings.token": "Token",
+ "settings.refresh_token": "Refresh Token",
+ "settings.valid_until": "Valid Until",
+ "settings.revoke_token": "Revoke",
+ "settings.panelRadius": "Panels",
+ "settings.pause_on_unfocused": "Pause streaming when tab is not focused",
+ "settings.presets": "Presets",
+ "settings.profile_background": "Profile Background",
+ "settings.profile_banner": "Profile Banner",
+ "settings.profile_tab": "Profile",
+ "settings.radii_help": "Set up interface edge rounding (in pixels)",
+ "settings.replies_in_timeline": "Replies in timeline",
+ "settings.reply_link_preview": "Enable reply-link preview on mouse hover",
+ "settings.reply_visibility_all": "Show all replies",
+ "settings.reply_visibility_following": "Only show replies directed at me or users I'm following",
+ "settings.reply_visibility_self": "Only show replies directed at me",
+ "settings.saving_err": "Error saving settings",
+ "settings.saving_ok": "Settings saved",
+ "settings.security_tab": "Security",
+ "settings.scope_copy": "Copy scope when replying (DMs are always copied)",
+ "settings.set_new_avatar": "Set new avatar",
+ "settings.set_new_profile_background": "Set new profile background",
+ "settings.set_new_profile_banner": "Set new profile banner",
+ "settings.settings": "Settings",
+ "settings.subject_input_always_show": "Always show subject field",
+ "settings.subject_line_behavior": "Copy subject when replying",
+ "settings.subject_line_email": "Like email: \"re: subject\"",
+ "settings.subject_line_mastodon": "Like mastodon: copy as is",
+ "settings.subject_line_noop": "Do not copy",
+ "settings.post_status_content_type": "Post status content type",
+ "settings.stop_gifs": "Play-on-hover GIFs",
+ "settings.streaming": "Enable automatic streaming of new posts when scrolled to the top",
+ "settings.text": "Text",
+ "settings.theme": "Theme",
+ "settings.theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
+ "settings.theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
+ "settings.theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
+ "settings.tooltipRadius": "Tooltips/alerts",
+ "settings.upload_a_photo": "Upload a photo",
+ "settings.user_settings": "User Settings",
+ "settings.values.false": "no",
+ "settings.values.true": "yes",
+ "settings.notifications": "Notifications",
+ "settings.enable_web_push_notifications": "Enable web push notifications",
+ "settings.style.switcher.keep_color": "Keep colors",
+ "settings.style.switcher.keep_shadows": "Keep shadows",
+ "settings.style.switcher.keep_opacity": "Keep opacity",
+ "settings.style.switcher.keep_roundness": "Keep roundness",
+ "settings.style.switcher.keep_fonts": "Keep fonts",
+ "settings.style.switcher.save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
+ "settings.style.switcher.reset": "Reset",
+ "settings.style.switcher.clear_all": "Clear all",
+ "settings.style.switcher.clear_opacity": "Clear opacity",
+ "settings.style.common.color": "Color",
+ "settings.style.common.opacity": "Opacity",
+ "settings.style.common.contrast.hint": "Contrast ratio is {ratio}, it {level} {context}",
+ "settings.style.common.contrast.level.aa": "meets Level AA guideline (minimal)",
+ "settings.style.common.contrast.level.aaa": "meets Level AAA guideline (recommended)",
+ "settings.style.common.contrast.level.bad": "doesn't meet any accessibility guidelines",
+ "settings.style.common.contrast.context.18pt": "for large (18pt+) text",
+ "settings.style.common.contrast.context.text": "for text",
+ "settings.style.common_colors._tab_label": "Common",
+ "settings.style.common_colors.main": "Common colors",
+ "settings.style.common_colors.foreground_hint": "See \"Advanced\" tab for more detailed control",
+ "settings.style.common_colors.rgbo": "Icons, accents, badges",
+ "settings.style.advanced_colors._tab_label": "Advanced",
+ "settings.style.advanced_colors.alert": "Alert background",
+ "settings.style.advanced_colors.alert_error": "Error",
+ "settings.style.advanced_colors.badge": "Badge background",
+ "settings.style.advanced_colors.badge_notification": "Notification",
+ "settings.style.advanced_colors.panel_header": "Panel header",
+ "settings.style.advanced_colors.top_bar": "Top bar",
+ "settings.style.advanced_colors.borders": "Borders",
+ "settings.style.advanced_colors.buttons": "Buttons",
+ "settings.style.advanced_colors.inputs": "Input fields",
+ "settings.style.advanced_colors.faint_text": "Faded text",
+ "settings.style.radii._tab_label": "Roundness",
+ "settings.style.shadows._tab_label": "Shadow and lighting",
+ "settings.style.shadows.component": "Component",
+ "settings.style.shadows.override": "Override",
+ "settings.style.shadows.shadow_id": "Shadow #{value}",
+ "settings.style.shadows.blur": "Blur",
+ "settings.style.shadows.spread": "Spread",
+ "settings.style.shadows.inset": "Inset",
+ "settings.style.shadows.hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.",
+ "settings.style.shadows.filter_hint.always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
+ "settings.style.shadows.filter_hint.drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
+ "settings.style.shadows.filter_hint.avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
+ "settings.style.shadows.filter_hint.spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
+ "settings.style.shadows.filter_hint.inset_classic": "Inset shadows will be using {0}",
+ "settings.style.shadows.components.panel": "Panel",
+ "settings.style.shadows.components.panelHeader": "Panel header",
+ "settings.style.shadows.components.topBar": "Top bar",
+ "settings.style.shadows.components.avatar": "User avatar (in profile view)",
+ "settings.style.shadows.components.avatarStatus": "User avatar (in post display)",
+ "settings.style.shadows.components.popup": "Popups and tooltips",
+ "settings.style.shadows.components.button": "Button",
+ "settings.style.shadows.components.buttonHover": "Button (hover)",
+ "settings.style.shadows.components.buttonPressed": "Button (pressed)",
+ "settings.style.shadows.components.buttonPressedHover": "Button (pressed+hover)",
+ "settings.style.shadows.components.input": "Input field",
+ "settings.style.fonts._tab_label": "Fonts",
+ "settings.style.fonts.help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
+ "settings.style.fonts.components.interface": "Interface",
+ "settings.style.fonts.components.input": "Input fields",
+ "settings.style.fonts.components.post": "Post text",
+ "settings.style.fonts.components.postCode": "Monospaced text in a post (rich text)",
+ "settings.style.fonts.family": "Font name",
+ "settings.style.fonts.size": "Size (in px)",
+ "settings.style.fonts.weight": "Weight (boldness)",
+ "settings.style.fonts.custom": "Custom",
+ "settings.style.preview.header": "Preview",
+ "settings.style.preview.content": "Content",
+ "settings.style.preview.error": "Example error",
+ "settings.style.preview.button": "Button",
+ "settings.style.preview.text": "A bunch of more {0} and {1}",
+ "settings.style.preview.mono": "content",
+ "settings.style.preview.input": "Just landed in L.A.",
+ "settings.style.preview.faint_link": "helpful manual",
+ "settings.style.preview.fine_print": "Read our {0} to learn nothing useful!",
+ "settings.style.preview.header_faint": "This is fine",
+ "settings.style.preview.checkbox": "I have skimmed over terms and conditions",
+ "settings.style.preview.link": "a nice lil' link",
+ "settings.version.title": "Version",
+ "settings.version.backend_version": "Backend Version",
+ "settings.version.frontend_version": "Frontend Version",
+ "timeline.collapse": "Collapse",
+ "timeline.conversation": "Conversation",
+ "timeline.error_fetching": "Error fetching updates",
+ "timeline.load_older": "Load older statuses",
+ "timeline.no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
+ "timeline.repeated": "repeated",
+ "timeline.show_new": "Show new",
+ "timeline.up_to_date": "Up-to-date",
+ "timeline.no_more_statuses": "No more statuses",
+ "timeline.no_statuses": "No statuses",
+ "status.reply_to": "Reply to",
+ "status.replies_list": "Replies:",
+ "user_card.approve": "Approve",
+ "user_card.block": "Block",
+ "user_card.blocked": "Blocked!",
+ "user_card.deny": "Deny",
+ "user_card.favorites": "Favorites",
+ "user_card.follow": "Follow",
+ "user_card.follow_sent": "Request sent!",
+ "user_card.follow_progress": "Requesting…",
+ "user_card.follow_again": "Send request again?",
+ "user_card.follow_unfollow": "Unfollow",
+ "user_card.followees": "Following",
+ "user_card.followers": "Followers",
+ "user_card.following": "Following!",
+ "user_card.follows_you": "Follows you!",
+ "user_card.its_you": "It's you!",
+ "user_card.media": "Media",
+ "user_card.mute": "Mute",
+ "user_card.muted": "Muted",
+ "user_card.per_day": "per day",
+ "user_card.remote_follow": "Remote follow",
+ "user_card.statuses": "Statuses",
+ "user_card.unblock": "Unblock",
+ "user_card.unblock_progress": "Unblocking...",
+ "user_card.block_progress": "Blocking...",
+ "user_card.unmute": "Unmute",
+ "user_card.unmute_progress": "Unmuting...",
+ "user_card.mute_progress": "Muting...",
+ "user_profile.timeline_title": "User Timeline",
+ "user_profile.profile_does_not_exist": "Sorry, this profile does not exist.",
+ "user_profile.profile_loading_error": "Sorry, there was an error loading this profile.",
+ "who_to_follow.more": "More",
+ "who_to_follow.who_to_follow": "Who to follow",
+ "tool_tip.media_upload": "Upload Media",
+ "tool_tip.repeat": "Repeat",
+ "tool_tip.reply": "Reply",
+ "tool_tip.favorite": "Favorite",
+ "tool_tip.user_settings": "User Settings",
+ "upload.error.base": "Upload failed.",
+ "upload.error.file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+ "upload.error.default": "Try again later",
+ "upload.file_size_units.B": "B",
+ "upload.file_size_units.KiB": "KiB",
+ "upload.file_size_units.MiB": "MiB",
+ "upload.file_size_units.GiB": "GiB",
+ "upload.file_size_units.TiB": "TiB"
+}
diff --git a/src/modules/api.js b/src/modules/api.js
index d51b31f3..eb6a7980 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -6,7 +6,6 @@ const api = {
backendInteractor: backendInteractorService(),
fetchers: {},
socket: null,
- chatDisabled: false,
followRequests: []
},
mutations: {
@@ -25,9 +24,6 @@ const api = {
setSocket (state, socket) {
state.socket = socket
},
- setChatDisabled (state, value) {
- state.chatDisabled = value
- },
setFollowRequests (state, value) {
state.followRequests = value
}
@@ -55,17 +51,20 @@ const api = {
setWsToken (store, token) {
store.commit('setWsToken', token)
},
- initializeSocket (store) {
+ initializeSocket ({ dispatch, commit, state, rootState }) {
// Set up websocket connection
- if (!store.state.chatDisabled) {
- const token = store.state.wsToken
+ const token = state.wsToken
+ if (rootState.instance.chatAvailable && typeof token !== 'undefined' && state.socket === null) {
const socket = new Socket('/socket', { params: { token } })
socket.connect()
- store.dispatch('initializeChat', socket)
+
+ commit('setSocket', socket)
+ dispatch('initializeChat', socket)
}
},
- disableChat (store) {
- store.commit('setChatDisabled', true)
+ disconnectFromSocket ({ commit, state }) {
+ state.socket && state.socket.disconnect()
+ commit('setSocket', null)
},
removeFollowRequest (store, request) {
let requests = store.state.followRequests.filter((it) => it !== request)
diff --git a/src/modules/chat.js b/src/modules/chat.js
index e1b03bca..c798549d 100644
--- a/src/modules/chat.js
+++ b/src/modules/chat.js
@@ -1,16 +1,12 @@
const chat = {
state: {
messages: [],
- channel: { state: '' },
- socket: null
+ channel: { state: '' }
},
mutations: {
setChannel (state, channel) {
state.channel = channel
},
- setSocket (state, socket) {
- state.socket = socket
- },
addMessage (state, message) {
state.messages.push(message)
state.messages = state.messages.slice(-19, 20)
@@ -20,12 +16,8 @@ const chat = {
}
},
actions: {
- disconnectFromChat (store) {
- store.state.socket && store.state.socket.disconnect()
- },
initializeChat (store, socket) {
const channel = socket.channel('chat:public')
- store.commit('setSocket', socket)
channel.on('new_msg', (msg) => {
store.commit('addMessage', msg)
})
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 93b56577..7d602aa1 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -79,6 +79,11 @@ const instance = {
case 'name':
dispatch('setPageTitle')
break
+ case 'chatAvailable':
+ if (value) {
+ dispatch('initializeSocket')
+ }
+ break
}
},
setTheme ({ commit }, themeName) {
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 7d5d5a67..4356d0a7 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -426,9 +426,9 @@ export const mutations = {
newStatus.favoritedBy.push(user)
}
},
- setPinned (state, status) {
+ setMuted (state, status) {
const newStatus = state.allStatusesObject[status.id]
- newStatus.pinned = status.pinned
+ newStatus.muted = status.muted
},
setRetweeted (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
@@ -556,13 +556,21 @@ const statuses = {
rootState.api.backendInteractor.fetchPinnedStatuses(userId)
.then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true }))
},
- pinStatus ({ rootState, commit }, statusId) {
+ pinStatus ({ rootState, dispatch }, statusId) {
return rootState.api.backendInteractor.pinOwnStatus(statusId)
- .then((status) => commit('setPinned', status))
+ .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
},
- unpinStatus ({ rootState, commit }, statusId) {
+ unpinStatus ({ rootState, dispatch }, statusId) {
rootState.api.backendInteractor.unpinOwnStatus(statusId)
- .then((status) => commit('setPinned', status))
+ .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
+ },
+ muteConversation ({ rootState, commit }, statusId) {
+ return rootState.api.backendInteractor.muteConversation(statusId)
+ .then((status) => commit('setMuted', status))
+ },
+ unmuteConversation ({ rootState, commit }, statusId) {
+ return rootState.api.backendInteractor.unmuteConversation(statusId)
+ .then((status) => commit('setMuted', status))
},
retweet ({ rootState, commit }, status) {
// Optimistic retweeting...
diff --git a/src/modules/users.js b/src/modules/users.js
index 57d3a3e3..4d02f8d7 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -3,7 +3,6 @@ import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, merge, last, concat, uniq } from 'lodash'
import { set } from 'vue'
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
-import { humanizeErrors } from './errors'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
@@ -165,13 +164,13 @@ export const mutations = {
state.currentUser.muteIds.push(muteId)
}
},
- setPinned (state, status) {
+ setPinnedToUser (state, status) {
const user = state.usersObject[status.user.id]
- const index = user.pinnedStatuseIds.indexOf(status.id)
+ const index = user.pinnedStatusIds.indexOf(status.id)
if (status.pinned && index === -1) {
- user.pinnedStatuseIds.push(status.id)
+ user.pinnedStatusIds.push(status.id)
} else if (!status.pinned && index !== -1) {
- user.pinnedStatuseIds.splice(index, 1)
+ user.pinnedStatusIds.splice(index, 1)
}
},
setUserForStatus (state, status) {
@@ -339,13 +338,13 @@ const users = {
// Reconnect users to statuses
store.commit('setUserForStatus', status)
// Set pinned statuses to user
- store.commit('setPinned', status)
+ store.commit('setPinnedToUser', status)
})
each(compact(map(statuses, 'retweeted_status')), (status) => {
// Reconnect users to retweets
store.commit('setUserForStatus', status)
// Set pinned retweets to user
- store.commit('setPinned', status)
+ store.commit('setPinnedToUser', status)
})
},
addNewNotifications (store, { notifications }) {
@@ -382,16 +381,8 @@ const users = {
store.dispatch('loginUser', data.access_token)
} catch (e) {
let errors = e.message
- // replace ap_id with username
- if (typeof errors === 'object') {
- if (errors.ap_id) {
- errors.username = errors.ap_id
- delete errors.ap_id
- }
- errors = humanizeErrors(errors)
- }
store.commit('signUpFailure', errors)
- throw Error(errors)
+ throw e
}
},
async getCaptcha (store) {
@@ -419,7 +410,7 @@ const users = {
})
.then(() => {
store.commit('clearCurrentUser')
- store.dispatch('disconnectFromChat')
+ store.dispatch('disconnectFromSocket')
store.commit('clearToken')
store.dispatch('stopFetching', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index d4ad1c4e..887d7d7a 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,18 +1,14 @@
import { each, map, concat, last } from 'lodash'
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
import 'whatwg-fetch'
-import { StatusCodeError } from '../errors/errors'
+import { RegistrationError, StatusCodeError } from '../errors/errors'
/* eslint-env browser */
-const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
-const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests'
-const APPROVE_USER_URL = '/api/pleroma/friendships/approve'
-const DENY_USER_URL = '/api/pleroma/friendships/deny'
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status`
@@ -40,6 +36,9 @@ const MASTODON_FOLLOW_URL = id => `/api/v1/accounts/${id}/follow`
const MASTODON_UNFOLLOW_URL = id => `/api/v1/accounts/${id}/unfollow`
const MASTODON_FOLLOWING_URL = id => `/api/v1/accounts/${id}/following`
const MASTODON_FOLLOWERS_URL = id => `/api/v1/accounts/${id}/followers`
+const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
+const MASTODON_APPROVE_USER_URL = id => `/api/v1/follow_requests/${id}/authorize`
+const MASTODON_DENY_USER_URL = id => `/api/v1/follow_requests/${id}/reject`
const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct'
const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public'
const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home'
@@ -67,6 +66,8 @@ const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
const MASTODON_REPORT_USER_URL = '/api/v1/reports'
const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin`
const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin`
+const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
+const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = `/api/v2/search`
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
@@ -199,12 +200,11 @@ const register = ({ params, credentials }) => {
...rest
})
})
- .then((response) => [response.ok, response])
- .then(([ok, response]) => {
- if (ok) {
+ .then((response) => {
+ if (response.ok) {
return response.json()
} else {
- return response.json().then((error) => { throw new Error(error) })
+ return response.json().then((error) => { throw new RegistrationError(error) })
}
})
}
@@ -219,14 +219,6 @@ const authHeaders = (accessToken) => {
}
}
-const externalProfile = ({ profileUrl, credentials }) => {
- let url = `${EXTERNAL_PROFILE_URL}?profileurl=${profileUrl}`
- return fetch(url, {
- headers: authHeaders(credentials),
- method: 'GET'
- }).then((data) => data.json())
-}
-
const followUser = ({ id, credentials }) => {
let url = MASTODON_FOLLOW_URL(id)
return fetch(url, {
@@ -253,6 +245,16 @@ const unpinOwnStatus = ({ id, credentials }) => {
.then((data) => parseStatus(data))
}
+const muteConversation = ({ id, credentials }) => {
+ return promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST' })
+ .then((data) => parseStatus(data))
+}
+
+const unmuteConversation = ({ id, credentials }) => {
+ return promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST' })
+ .then((data) => parseStatus(data))
+}
+
const blockUser = ({ id, credentials }) => {
return fetch(MASTODON_BLOCK_USER_URL(id), {
headers: authHeaders(credentials),
@@ -268,7 +270,7 @@ const unblockUser = ({ id, credentials }) => {
}
const approveUser = ({ id, credentials }) => {
- let url = `${APPROVE_USER_URL}?user_id=${id}`
+ let url = MASTODON_APPROVE_USER_URL(id)
return fetch(url, {
headers: authHeaders(credentials),
method: 'POST'
@@ -276,7 +278,7 @@ const approveUser = ({ id, credentials }) => {
}
const denyUser = ({ id, credentials }) => {
- let url = `${DENY_USER_URL}?user_id=${id}`
+ let url = MASTODON_DENY_USER_URL(id)
return fetch(url, {
headers: authHeaders(credentials),
method: 'POST'
@@ -352,9 +354,10 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => {
}
const fetchFollowRequests = ({ credentials }) => {
- const url = FOLLOW_REQUESTS_URL
+ const url = MASTODON_FOLLOW_REQUESTS_URL
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
+ .then((data) => data.map(parseUser))
}
const fetchConversation = ({ id, credentials }) => {
@@ -921,6 +924,8 @@ const apiService = {
unfollowUser,
pinOwnStatus,
unpinOwnStatus,
+ muteConversation,
+ unmuteConversation,
blockUser,
unblockUser,
fetchUser,
@@ -952,7 +957,6 @@ const apiService = {
updateBg,
updateProfile,
updateBanner,
- externalProfile,
importBlocks,
importFollows,
deleteAccount,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index bdfe0465..3c44a10c 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -117,6 +117,8 @@ const backendInteractorService = credentials => {
const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id })
const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id })
+ const muteConversation = (id) => apiService.muteConversation({ credentials, id })
+ const unmuteConversation = (id) => apiService.unmuteConversation({ credentials, id })
const getCaptcha = () => apiService.getCaptcha()
const register = (params) => apiService.register({ credentials, params })
@@ -125,8 +127,6 @@ const backendInteractorService = credentials => {
const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner })
const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params })
- const externalProfile = (profileUrl) => apiService.externalProfile({ profileUrl, credentials })
-
const importBlocks = (file) => apiService.importBlocks({ file, credentials })
const importFollows = (file) => apiService.importFollows({ file, credentials })
@@ -178,6 +178,8 @@ const backendInteractorService = credentials => {
fetchPinnedStatuses,
pinOwnStatus,
unpinOwnStatus,
+ muteConversation,
+ unmuteConversation,
tagUser,
untagUser,
addRight,
@@ -190,7 +192,6 @@ const backendInteractorService = credentials => {
updateBg,
updateBanner,
updateProfile,
- externalProfile,
importBlocks,
importFollows,
deleteAccount,
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index de6664d1..6cc1851d 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -65,6 +65,7 @@ export const parseUser = (data) => {
if (relationship) {
output.follows_you = relationship.followed_by
+ output.requested = relationship.requested
output.following = relationship.following
output.statusnet_blocking = relationship.blocking
output.muted = relationship.muting
@@ -152,7 +153,7 @@ export const parseUser = (data) => {
output.statuses_count = data.statuses_count
output.friendIds = []
output.followerIds = []
- output.pinnedStatuseIds = []
+ output.pinnedStatusIds = []
if (data.pleroma) {
output.follow_request_count = data.pleroma.follow_request_count
@@ -240,6 +241,7 @@ export const parseStatus = (data) => {
output.external_url = data.url
output.poll = data.poll
output.pinned = data.pinned
+ output.muted = data.muted
} else {
output.favorited = data.favorited
output.fave_num = data.fave_num
diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js
index 548f3c68..590552da 100644
--- a/src/services/errors/errors.js
+++ b/src/services/errors/errors.js
@@ -1,3 +1,5 @@
+import { humanizeErrors } from '../../modules/errors'
+
export function StatusCodeError (statusCode, body, options, response) {
this.name = 'StatusCodeError'
this.statusCode = statusCode
@@ -12,3 +14,36 @@ export function StatusCodeError (statusCode, body, options, response) {
}
StatusCodeError.prototype = Object.create(Error.prototype)
StatusCodeError.prototype.constructor = StatusCodeError
+
+export class RegistrationError extends Error {
+ constructor (error) {
+ super()
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this)
+ }
+
+ try {
+ // the error is probably a JSON object with a single key, "errors", whose value is another JSON object containing the real errors
+ if (typeof error === 'string') {
+ error = JSON.parse(error)
+ if (error.hasOwnProperty('error')) {
+ error = JSON.parse(error.error)
+ }
+ }
+
+ if (typeof error === 'object') {
+ // replace ap_id with username
+ if (error.ap_id) {
+ error.username = error.ap_id
+ delete error.ap_id
+ }
+ this.message = humanizeErrors(error)
+ } else {
+ this.message = error
+ }
+ } catch (e) {
+ // can't parse it, so just treat it like a string
+ this.message = error
+ }
+ }
+}
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index b2486e7c..529fdb9b 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -2,17 +2,17 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => {
setTimeout(() => {
store.state.api.backendInteractor.fetchUser({ id: user.id })
.then((user) => store.commit('addNewUsers', [user]))
- .then(() => resolve([user.following, attempt]))
+ .then(() => resolve([user.following, user.requested, user.locked, attempt]))
.catch((e) => reject(e))
}, 500)
-}).then(([following, attempt]) => {
- if (!following && attempt <= 3) {
+}).then(([following, sent, locked, attempt]) => {
+ if (!following && !(locked && sent) && attempt <= 3) {
// If we BE reports that we still not following that user - retry,
// increment attempts by one
return fetchUser(++attempt, user, store)
} else {
// If we run out of attempts, just return whatever status is.
- return following
+ return sent
}
})
@@ -21,14 +21,10 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
.then((updated) => {
store.commit('updateUserRelationship', [updated])
- // For locked users we just mark it that we sent the follow request
- if (updated.locked) {
- resolve({ sent: true })
- }
-
- if (updated.following) {
- // If we get result immediately, just stop.
- resolve({ sent: false })
+ if (updated.following || (user.locked && user.requested)) {
+ // If we get result immediately or the account is locked, just stop.
+ resolve({ sent: updated.requested })
+ return
}
// But usually we don't get result immediately, so we ask server
@@ -39,14 +35,8 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
// Recursive Promise, it will call itself up to 3 times.
return fetchUser(1, user, store)
- .then((following) => {
- if (following) {
- // We confirmed and everything's good.
- resolve({ sent: false })
- } else {
- // If after all the tries, just treat it as if user is locked
- resolve({ sent: false })
- }
+ .then((sent) => {
+ resolve({ sent })
})
})
})
diff --git a/src/services/new_api/password_reset.js b/src/services/new_api/password_reset.js
new file mode 100644
index 00000000..43199625
--- /dev/null
+++ b/src/services/new_api/password_reset.js
@@ -0,0 +1,18 @@
+import { reduce } from 'lodash'
+
+const MASTODON_PASSWORD_RESET_URL = `/auth/password`
+
+const resetPassword = ({ instance, email }) => {
+ const params = { email }
+ const query = reduce(params, (acc, v, k) => {
+ const encoded = `${k}=${encodeURIComponent(v)}`
+ return `${acc}&${encoded}`
+ }, '')
+ const url = `${instance}${MASTODON_PASSWORD_RESET_URL}?${query}`
+
+ return window.fetch(url, {
+ method: 'POST'
+ })
+}
+
+export default resetPassword