diff options
| author | Henry Jameson <me@hjkos.com> | 2021-03-08 22:01:28 +0200 |
|---|---|---|
| committer | Henry Jameson <me@hjkos.com> | 2021-03-08 22:01:28 +0200 |
| commit | 2e7bd99444546b3a71e1ff0753e12e6706c8228e (patch) | |
| tree | b01067c88bc969041d23392e7e34c976fae7b801 | |
| parent | 9a8bc245a6f76f1a41da9d05408dadc36625ffe9 (diff) | |
| parent | 6281241b92bc17a9535b15a52e656b9f218e3322 (diff) | |
Merge remote-tracking branch 'origin/develop' into websocket-fixes
* origin/develop: (119 commits)
Apply 1 suggestion(s) to 1 file(s)
Make it possible to localize user highlight options
remove shoutbox test hacks
fix shoutbox header, use custom scroll-to-bottom system, remove vue-chat-scroll, temporarily add chat test hack
update changelog with 2.3.0
change icons around
Translated using Weblate (Japanese)
Update timeline_quick_settings.js
add screen_name_ui to tests
separate screen_name and screen_name_ui with decoded punycode
Update CHANGELOG.md
add basic validation for statusless status notifications
changelog mention
fix chat unread badge
update shelljs to get rid of warnings on build
save a few characters
focus input in emoji picker and react picker
fix vue warnings
add only to wording
basic loggedin check for reply filtering
...
89 files changed, 2190 insertions, 625 deletions
diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..0b198a47 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +rinpatch <rin@patch.cx> <rinpatch@sdf.org>
\ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d0ca7d0..a17ff604 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,33 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - ## [Unreleased] ### Added +- Added a quick settings to timeline header for easier access +- Added option to mark posts as sensitive by default + +## [2.3.0] - 2021-03-01 +### Fixed +- Button to remove uploaded media in post status form is now properly placed and sized. +- Fixed shoutbox not working in mobile layout +- Fixed missing highlighted border in expanded conversations again +- Fixed some UI jumpiness when opening images particularly in chat view +- Fixed chat unread badge looking weird +- Fixed punycode names not working properly +- Fixed notifications crashing on an invalid notification + +### Changed +- Display 'people voted' instead of 'votes' for multi-choice polls +- Optimized chat to not get horrible performance after keeping the same chat open for a long time +- When opening emoji picker or react picker, it automatically focuses the search field +- Language picker now uses native language names + +### Added +- Added reason field for registration when approval is required +- Group staff members by role in the About page + +## [2.2.3] - 2021-01-18 +### Added - Added Report button to status ellipsis menu for easier reporting ### Fixed @@ -16,6 +40,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix not being able to re-enable websocket until page refresh - Fix annoying issue where timeline might have few posts when streaming is enabled +### Changed +- Don't filter own posts when they hit your wordfilter + + ## [2.2.2] - 2020-12-22 ### Added - Mouseover titles for emojis in reaction picker diff --git a/package.json b/package.json index e11396bf..8dbf2503 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "punycode.js": "^2.1.0", "v-click-outside": "^2.1.1", "vue": "^2.6.11", - "vue-chat-scroll": "^1.2.1", "vue-i18n": "^7.3.2", "vue-router": "^3.0.1", "vue-template-compiler": "^2.6.11", @@ -103,7 +102,7 @@ "selenium-server": "2.53.1", "semver": "^5.3.0", "serviceworker-webpack-plugin": "^1.0.0", - "shelljs": "^0.7.4", + "shelljs": "^0.8.4", "sinon": "^2.1.0", "sinon-chai": "^2.8.0", "stylelint": "^13.6.1", diff --git a/src/App.scss b/src/App.scss index a3b1c87a..f860c16d 100644 --- a/src/App.scss +++ b/src/App.scss @@ -178,6 +178,13 @@ a { &.-fullwidth { width: 100%; } + + &.-hover-highlight { + &:hover svg { + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); + } + } } input, textarea, .select, .input { @@ -579,6 +586,7 @@ nav { color: var(--faint, $fallback--faint); box-shadow: 0px 0px 4px rgba(0,0,0,.6); box-shadow: var(--topBarShadow); + box-sizing: border-box; } .fade-enter-active, .fade-leave-active { @@ -880,6 +888,11 @@ nav { overflow: hidden; height: 100%; + // Get rid of scrollbar on body as scrolling happens on different element + body { + overflow: hidden; + } + // Ensures the fixed position of the mobile browser bars on scroll up / down events. // Prevents the mobile browser bars from overlapping or hiding the message posting form. @media all and (max-width: 800px) { diff --git a/src/boot/after_store.js b/src/boot/after_store.js index b472fcf6..45090e5d 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -51,6 +51,7 @@ const getInstanceConfig = async ({ store }) => { const vapidPublicKey = data.pleroma.vapid_public_key store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit }) + store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required }) if (vapidPublicKey) { store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 9e410610..c53f6a9c 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -42,7 +42,7 @@ class="basic-user-card-screen-name" :to="userProfileLink(user)" > - @{{ user.screen_name }} + @{{ user.screen_name_ui }} </router-link> </div> <slot /> diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index e57fcb91..b54f5fb2 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -73,7 +73,7 @@ const Chat = { }, formPlaceholder () { if (this.recipient) { - return this.$t('chats.message_user', { nickname: this.recipient.screen_name }) + return this.$t('chats.message_user', { nickname: this.recipient.screen_name_ui }) } else { return '' } @@ -234,6 +234,13 @@ const Chat = { const scrollable = this.$refs.scrollable return scrollable && scrollable.scrollTop <= 0 }, + cullOlderCheck () { + window.setTimeout(() => { + if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) { + this.$store.dispatch('cullOlderMessages', this.currentChatMessageService.chatId) + } + }, 5000) + }, handleScroll: _.throttle(function () { if (!this.currentChat) { return } @@ -241,6 +248,7 @@ const Chat = { this.fetchChat({ maxId: this.currentChatMessageService.minId }) } else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) { this.jumpToBottomButtonVisible = false + this.cullOlderCheck() if (this.newMessageCount > 0) { // Use a delay before marking as read to prevent situation where new messages // arrive just as you're leaving the view and messages that you didn't actually diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss index aef58495..3a26686c 100644 --- a/src/components/chat/chat.scss +++ b/src/components/chat/chat.scss @@ -98,10 +98,10 @@ .unread-message-count { font-size: 0.8em; left: 50%; - transform: translate(-50%, 0); - border-radius: 100%; margin-top: -1rem; - padding: 0; + padding: 0.1em; + border-radius: 50px; + position: absolute; } .chat-loading-error { diff --git a/src/components/chat_message_date/chat_message_date.vue b/src/components/chat_message_date/chat_message_date.vue index 79c346b6..98349b75 100644 --- a/src/components/chat_message_date/chat_message_date.vue +++ b/src/components/chat_message_date/chat_message_date.vue @@ -5,6 +5,8 @@ </template> <script> +import localeService from 'src/services/locale/locale.service.js' + export default { name: 'Timeago', props: ['date'], @@ -16,7 +18,7 @@ export default { if (this.date.getTime() === today.getTime()) { return this.$t('display_date.today') } else { - return this.date.toLocaleDateString('en', { day: 'numeric', month: 'long' }) + return this.date.toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale), { day: 'numeric', month: 'long' }) } } } diff --git a/src/components/chat_panel/chat_panel.js b/src/components/chat_panel/chat_panel.js index c3887098..556694ae 100644 --- a/src/components/chat_panel/chat_panel.js +++ b/src/components/chat_panel/chat_panel.js @@ -35,6 +35,18 @@ const chatPanel = { userProfileLink (user) { return generateProfileLink(user.id, user.username, this.$store.state.instance.restrictedNicknames) } + }, + watch: { + messages (newVal) { + const scrollEl = this.$el.querySelector('.chat-window') + if (!scrollEl) return + if (scrollEl.scrollTop + scrollEl.offsetHeight + 20 > scrollEl.scrollHeight) { + this.$nextTick(() => { + if (!scrollEl) return + scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.offsetHeight + }) + } + } } } diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue index 7993c94d..8a829115 100644 --- a/src/components/chat_panel/chat_panel.vue +++ b/src/components/chat_panel/chat_panel.vue @@ -10,17 +10,15 @@ @click.stop.prevent="togglePanel" > <div class="title"> - <span>{{ $t('shoutbox.title') }}</span> + {{ $t('shoutbox.title') }} <FAIcon v-if="floating" icon="times" + class="close-icon" /> </div> </div> - <div - v-chat-scroll - class="chat-window" - > + <div class="chat-window"> <div v-for="message in messages" :key="message.id" @@ -94,6 +92,13 @@ .icon { color: $fallback--text; color: var(--text, $fallback--text); + margin-right: 0.5em; + } + + .title { + display: flex; + justify-content: space-between; + align-items: center; } } diff --git a/src/components/chat_title/chat_title.js b/src/components/chat_title/chat_title.js index e424bb1f..edfbe7a4 100644 --- a/src/components/chat_title/chat_title.js +++ b/src/components/chat_title/chat_title.js @@ -12,7 +12,7 @@ export default Vue.component('chat-title', { ], computed: { title () { - return this.user ? this.user.screen_name : '' + return this.user ? this.user.screen_name_ui : '' }, htmlTitle () { return this.user ? this.user.name_html : '' diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 353859b8..3fb26d92 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -50,7 +50,6 @@ .Conversation { .conversation-status { - border-left: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: var(--border, $fallback--border); diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 2068a598..dc03bc9f 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -194,11 +194,18 @@ const EmojiInput = { } }, methods: { + focusPickerInput () { + const pickerEl = this.$refs.picker.$el + if (!pickerEl) return + const pickerInput = pickerEl.querySelector('input') + if (pickerInput) pickerInput.focus() + }, triggerShowPicker () { this.showPicker = true this.$refs.picker.startEmojiLoad() this.$nextTick(() => { this.scrollIntoView() + this.focusPickerInput() }) // This temporarily disables "click outside" handler // since external trigger also means click originates @@ -214,6 +221,7 @@ const EmojiInput = { if (this.showPicker) { this.scrollIntoView() this.$refs.picker.startEmojiLoad() + this.$nextTick(this.focusPickerInput) } }, replace (replacement) { diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index 4becdc41..ad62484d 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -9,6 +9,7 @@ <button v-if="!hideEmojiButton" class="button-unstyled emoji-picker-icon" + type="button" @click.prevent="togglePicker" > <FAIcon :icon="['far', 'smile-beam']" /> diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index 14a2b41e..e8efbd1e 100644 --- a/src/components/emoji_input/suggestor.js +++ b/src/components/emoji_input/suggestor.js @@ -116,8 +116,8 @@ export const suggestUsers = ({ dispatch, state }) => { return diff + nameAlphabetically + screenNameAlphabetically /* eslint-disable camelcase */ - }).map(({ screen_name, name, profile_image_url_original }) => ({ - displayText: screen_name, + }).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({ + displayText: screen_name_ui, detailText: name, imageUrl: profile_image_url_original, replacement: '@' + screen_name + ' ' diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue index e845d8fc..c6cb9fbe 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -139,6 +139,11 @@ @import '../../_variables.scss'; .ExtraButtons { + /* override of popover internal stuff */ + .popover-trigger-button { + width: auto; + } + .popover-trigger { position: static; padding: 10px; diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue index 524ec5e9..dc3bd408 100644 --- a/src/components/interface_language_switcher/interface_language_switcher.vue +++ b/src/components/interface_language_switcher/interface_language_switcher.vue @@ -12,11 +12,11 @@ v-model="language" > <option - v-for="(langCode, i) in languageCodes" - :key="langCode" - :value="langCode" + v-for="lang in languages" + :key="lang.code" + :value="lang.code" > - {{ languageNames[i] }} + {{ lang.name }} </option> </select> <FAIcon @@ -29,6 +29,7 @@ <script> import languagesObject from '../../i18n/messages' +import localeService from '../../services/locale/locale.service.js' import ISO6391 from 'iso-639-1' import _ from 'lodash' import { library } from '@fortawesome/fontawesome-svg-core' @@ -42,12 +43,8 @@ library.add( export default { computed: { - languageCodes () { - return languagesObject.languages - }, - - languageNames () { - return _.map(this.languageCodes, this.getLanguageName) + languages () { + return _.map(languagesObject.languages, (code) => ({ code: code, name: this.getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name)) }, language: { @@ -61,12 +58,13 @@ export default { methods: { getLanguageName (code) { const specialLanguageNames = { - 'ja': 'Japanese (日本語)', - 'ja_easy': 'Japanese (やさしいにほんご)', - 'zh': 'Simplified Chinese (简体中文)', - 'zh_Hant': 'Traditional Chinese (繁體中文)' + 'ja_easy': 'やさしいにほんご', + 'zh': '简体中文', + 'zh_Hant': '繁體中文' } - return specialLanguageNames[code] || ISO6391.getName(code) + const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code) + const browserLocale = localeService.internalToBrowserLocale(code) + return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1) } } } diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index ea7f7a7f..54bc5335 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -73,11 +73,21 @@ } } +@keyframes media-fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + .modal-image { 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 + animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein; } .modal-view-button-arrow { diff --git a/src/components/mfa_form/recovery_form.vue b/src/components/mfa_form/recovery_form.vue index 0bf68e27..7c594228 100644 --- a/src/components/mfa_form/recovery_form.vue +++ b/src/components/mfa_form/recovery_form.vue @@ -25,6 +25,7 @@ <div> <button class="button-unstyled -link" + type="button" @click.prevent="requireTOTP" > {{ $t('login.enter_two_factor_code') }} @@ -32,6 +33,7 @@ <br> <button class="button-unstyled -link" + type="button" @click.prevent="abortMFA" > {{ $t('general.cancel') }} diff --git a/src/components/mfa_form/totp_form.vue b/src/components/mfa_form/totp_form.vue index 79230148..4ee13992 100644 --- a/src/components/mfa_form/totp_form.vue +++ b/src/components/mfa_form/totp_form.vue @@ -27,6 +27,7 @@ <div> <button class="button-unstyled -link" + type="button" @click.prevent="requireRecovery" > {{ $t('login.enter_recovery_code') }} @@ -34,6 +35,7 @@ <br> <button class="button-unstyled -link" + type="button" @click.prevent="abortMFA" > {{ $t('general.cancel') }} diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue index 5c7b82ec..bf25adc5 100644 --- a/src/components/moderation_tools/moderation_tools.vue +++ b/src/components/moderation_tools/moderation_tools.vue @@ -50,74 +50,74 @@ class="button-default dropdown-item" @click="toggleTag(tags.FORCE_NSFW)" > - {{ $t('user_card.admin_menu.force_nsfw') }} <span class="menu-checkbox" :class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }" /> + {{ $t('user_card.admin_menu.force_nsfw') }} </button> <button class="button-default dropdown-item" @click="toggleTag(tags.STRIP_MEDIA)" > - {{ $t('user_card.admin_menu.strip_media') }} <span class="menu-checkbox" :class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }" /> + {{ $t('user_card.admin_menu.strip_media') }} </button> <button class="button-default dropdown-item" @click="toggleTag(tags.FORCE_UNLISTED)" > - {{ $t('user_card.admin_menu.force_unlisted') }} <span class="menu-checkbox" :class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }" /> + {{ $t('user_card.admin_menu.force_unlisted') }} </button> <button class="button-default dropdown-item" @click="toggleTag(tags.SANDBOX)" > - {{ $t('user_card.admin_menu.sandbox') }} <span class="menu-checkbox" :class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }" /> + {{ $t('user_card.admin_menu.sandbox') }} </button> <button v-if="user.is_local" class="button-default dropdown-item" @click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)" > - {{ $t('user_card.admin_menu.disable_remote_subscription') }} <span class="menu-checkbox" :class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }" /> + {{ $t('user_card.admin_menu.disable_remote_subscription') }} </button> <button v-if="user.is_local" class="button-default dropdown-item" @click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)" > - {{ $t('user_card.admin_menu.disable_any_subscription') }} <span class="menu-checkbox" :class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }" /> + {{ $t('user_card.admin_menu.disable_any_subscription') }} </button> <button v-if="user.is_local" class="button-default dropdown-item" @click="toggleTag(tags.QUARANTINE)" > - {{ $t('user_card.admin_menu.quarantine') }} <span class="menu-checkbox" :class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }" /> + {{ $t('user_card.admin_menu.quarantine') }} </button> </span> </div> @@ -163,25 +163,6 @@ <style lang="scss"> @import '../../_variables.scss'; -.menu-checkbox { - float: right; - min-width: 22px; - max-width: 22px; - min-height: 22px; - max-height: 22px; - line-height: 22px; - text-align: center; - border-radius: 0px; - background-color: $fallback--fg; - background-color: var(--input, $fallback--fg); - box-shadow: 0px 0px 2px black inset; - box-shadow: var(--inputShadow); - - &.menu-checkbox-checked::after { - content: '✓'; - } -} - .moderation-tools-popover { height: 100%; .trigger { diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index f56aa977..0081dee4 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -11,7 +11,7 @@ > <small> <router-link :to="userProfileLink"> - {{ notification.from_profile.screen_name }} + {{ notification.from_profile.screen_name_ui }} </router-link> </small> <button @@ -54,14 +54,14 @@ <bdi v-if="!!notification.from_profile.name_html" class="username" - :title="'@'+notification.from_profile.screen_name" + :title="'@'+notification.from_profile.screen_name_ui" v-html="notification.from_profile.name_html" /> <!-- eslint-enable vue/no-v-html --> <span v-else class="username" - :title="'@'+notification.from_profile.screen_name" + :title="'@'+notification.from_profile.screen_name_ui" >{{ notification.from_profile.name }}</span> <span v-if="notification.type === 'like'"> <FAIcon @@ -152,7 +152,7 @@ :to="userProfileLink" class="follow-name" > - @{{ notification.from_profile.screen_name }} + @{{ notification.from_profile.screen_name_ui }} </router-link> <div v-if="notification.type === 'follow_request'" @@ -177,7 +177,7 @@ class="move-text" > <router-link :to="targetUserProfileLink"> - @{{ notification.target.screen_name }} + @{{ notification.target.screen_name_ui }} </router-link> </div> <template v-else> diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue index 42819c19..187d1829 100644 --- a/src/components/poll/poll.vue +++ b/src/components/poll/poll.vue @@ -58,7 +58,12 @@ {{ $t('polls.vote') }} </button> <div class="total"> - {{ totalVotesCount }} {{ $t("polls.votes") }} · + <template v-if="typeof poll.voters_count === 'number'"> + {{ $tc("polls.people_voted_count", poll.voters_count, { count: poll.voters_count }) }} · + </template> + <template v-else> + {{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }} · + </template> </div> <i18n :path="expired ? 'polls.expired' : 'polls.expires_in'"> <Timeago diff --git a/src/components/poll/poll_form.vue b/src/components/poll/poll_form.vue index 31f204a0..c4403210 100644 --- a/src/components/poll/poll_form.vue +++ b/src/components/poll/poll_form.vue @@ -21,20 +21,17 @@ @keydown.enter.stop.prevent="nextOption(index)" > </div> - <div + <button v-if="options.length > 2" - class="icon-container" + class="delete-option button-unstyled -hover-highlight" + @click="deleteOption(index)" > - <FAIcon - icon="times" - class="delete" - @click="deleteOption(index)" - /> - </div> + <FAIcon icon="times" /> + </button> </div> - <a + <button v-if="options.length < maxOptions" - class="add-option faint" + class="add-option faint button-unstyled -hover-highlight" @click="addOption" > <FAIcon @@ -43,7 +40,7 @@ /> {{ $t("polls.add_option") }} - </a> + </button> <div class="poll-type-expiry"> <div class="poll-type" @@ -116,7 +113,6 @@ align-self: flex-start; padding-top: 0.25em; padding-left: 0.1em; - cursor: pointer; } .poll-option { @@ -135,19 +131,11 @@ } } - .icon-container { + .delete-option { // Hack: Move the icon over the input box width: 1.5em; margin-left: -1.5em; z-index: 1; - - .delete { - cursor: pointer; - - &:hover { - color: inherit; - } - } } .poll-type-expiry { @@ -163,6 +151,7 @@ border: none; box-shadow: none; background-color: transparent; + padding-right: 0.75em; } } diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js index 5e417fa0..391d6422 100644 --- a/src/components/popover/popover.js +++ b/src/components/popover/popover.js @@ -3,25 +3,32 @@ const Popover = { props: { // Action to trigger popover: either 'hover' or 'click' trigger: String, + // Either 'top' or 'bottom' placement: String, + // Takes object with properties 'x' and 'y', values of these can be // 'container' for using offsetParent as boundaries for either axis // or 'viewport' boundTo: Object, + // Takes a selector to use as a replacement for the parent container // for getting boundaries for x an y axis boundToSelector: String, + // Takes a top/bottom/left/right object, how much space to leave // between boundary and popover element margin: Object, + // Takes a x/y object and tells how many pixels to offset from // anchor point on either axis offset: Object, + // Replaces the classes you may want for the popover container. // Use 'popover-default' in addition to get the default popover // styles with your custom class. popoverClass: String, + // If true, subtract padding when calculating position for the popover, // use it when popover offset looks to be different on top vs bottom. removePadding: Boolean @@ -121,9 +128,12 @@ const Popover = { } }, showPopover () { - if (this.hidden) this.$emit('show') + const wasHidden = this.hidden this.hidden = false - this.$nextTick(this.updateStyles) + this.$nextTick(() => { + if (wasHidden) this.$emit('show') + this.updateStyles() + }) }, hidePopover () { if (!this.hidden) this.$emit('close') diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue index 2252c68f..2e78a09e 100644 --- a/src/components/popover/popover.vue +++ b/src/components/popover/popover.vue @@ -6,6 +6,7 @@ <button ref="trigger" class="button-unstyled -fullwidth popover-trigger-button" + type="button" @click="onClick" > <slot name="trigger" /> @@ -81,10 +82,9 @@ .dropdown-item { line-height: 21px; - margin-right: 5px; overflow: auto; display: block; - padding: .25rem 1.0rem .25rem 1.5rem; + padding: .5em 0.75em; clear: both; font-weight: 400; text-align: inherit; @@ -100,10 +100,9 @@ --btnText: var(--popoverText, $fallback--text); &-icon { - padding-left: 0.5rem; - svg { - margin-right: 0.25rem; + width: 22px; + margin-right: 0.75rem; color: var(--menuPopoverIcon, $fallback--icon) } } @@ -122,6 +121,33 @@ } } + .menu-checkbox { + display: inline-block; + vertical-align: middle; + min-width: 22px; + max-width: 22px; + min-height: 22px; + max-height: 22px; + line-height: 22px; + text-align: center; + border-radius: 0px; + background-color: $fallback--fg; + background-color: var(--input, $fallback--fg); + box-shadow: 0px 0px 2px black inset; + box-shadow: var(--inputShadow); + margin-right: 0.75em; + + &.menu-checkbox-checked::after { + font-size: 1.25em; + content: '✓'; + } + + &.menu-checkbox-radio::after { + font-size: 2em; + content: '•'; + } + } + } } </style> diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 4148381c..e540654b 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -115,7 +115,7 @@ const PostStatusForm = { ? this.copyMessageScope : this.$store.state.users.currentUser.default_scope - const { postContentType: contentType } = this.$store.getters.mergedConfig + const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig return { dropFiles: [], @@ -126,7 +126,7 @@ const PostStatusForm = { newStatus: { spoilerText: this.subject || '', status: statusText, - nsfw: false, + nsfw: !!sensitiveByDefault, files: [], poll: {}, mediaDescriptions: {}, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index ed830f57..73f6a4f1 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -302,11 +302,12 @@ :key="file.url" class="media-upload-wrapper" > - <FAIcon - class="fa-scale-110 fa-old-padding" - icon="times" + <button + class="button-unstyled hider" @click="removeMediaFile(file)" - /> + > + <FAIcon icon="times" /> + </button> <attachment :attachment="file" :set-media="() => $store.dispatch('setMedia', newStatus.files)" @@ -516,26 +517,11 @@ } .attachments .media-upload-wrapper { - padding: 0 0.5em; + position: relative; .attachment { margin: 0; padding: 0; - position: relative; - } - - .fa-scale-110 fa-old-padding { - position: absolute; - margin: 10px; - margin: .75em; - padding: .5em; - background: rgba(230,230,230,0.6); - z-index: 2; - color: black; - border-radius: $fallback--attachmentRadius; - border-radius: var(--attachmentRadius, $fallback--attachmentRadius); - font-weight: bold; - cursor: pointer; } } diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js index 5e7b7580..ce82c90d 100644 --- a/src/components/react_button/react_button.js +++ b/src/components/react_button/react_button.js @@ -23,6 +23,12 @@ const ReactButton = { this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) } close() + }, + focusInput () { + this.$nextTick(() => { + const input = this.$el.querySelector('input') + if (input) input.focus() + }) } }, computed: { diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue index ac940b98..4839024c 100644 --- a/src/components/react_button/react_button.vue +++ b/src/components/react_button/react_button.vue @@ -1,10 +1,12 @@ <template> <Popover trigger="click" + class="ReactButton" placement="top" :offset="{ y: 5 }" :bound-to="{ x: 'container' }" remove-padding + @show="focusInput" > <div slot="content" @@ -42,7 +44,7 @@ </div> <span slot="trigger" - class="ReactButton" + class="popover-trigger" :title="$t('tool_tip.add_reaction')" > <FAIcon @@ -58,62 +60,71 @@ <style lang="scss"> @import '../../_variables.scss'; -.reaction-picker-filter { - padding: 0.5em; - display: flex; - input { - flex: 1; +.ReactButton { + .reaction-picker-filter { + padding: 0.5em; + display: flex; + + input { + flex: 1; + } } -} -.reaction-picker-divider { - height: 1px; - width: 100%; - margin: 0.5em; - background-color: var(--border, $fallback--border); -} + .reaction-picker-divider { + height: 1px; + width: 100%; + margin: 0.5em; + background-color: var(--border, $fallback--border); + } -.reaction-picker { - width: 10em; - height: 9em; - font-size: 1.5em; - overflow-y: scroll; - display: flex; - flex-wrap: wrap; - padding: 0.5em; - text-align: center; - align-content: flex-start; - user-select: none; + .reaction-picker { + width: 10em; + height: 9em; + font-size: 1.5em; + overflow-y: scroll; + display: flex; + flex-wrap: wrap; + padding: 0.5em; + text-align: center; + align-content: flex-start; + user-select: none; - mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat, - linear-gradient(to bottom, white 0, transparent 100%) top no-repeat, - linear-gradient(to top, white, white); - transition: mask-size 150ms; - mask-size: 100% 20px, 100% 20px, auto; - // Autoprefixed seem to ignore this one, and also syntax is different - -webkit-mask-composite: xor; - mask-composite: exclude; + mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat, + linear-gradient(to bottom, white 0, transparent 100%) top no-repeat, + linear-gradient(to top, white, white); + transition: mask-size 150ms; + mask-size: 100% 20px, 100% 20px, auto; - .emoji-button { - cursor: pointer; + /* Autoprefixed seem to ignore this one, and also syntax is different */ + -webkit-mask-composite: xor; + mask-composite: exclude; - flex-basis: 20%; - line-height: 1.5em; - align-content: center; + .emoji-button { + cursor: pointer; - &:hover { - transform: scale(1.25); + flex-basis: 20%; + line-height: 1.5em; + align-content: center; + + &:hover { + transform: scale(1.25); + } } } -} -.ReactButton { - padding: 10px; - margin: -10px; + /* override of popover internal stuff */ + .popover-trigger-button { + width: auto; + } + + .popover-trigger { + padding: 10px; + margin: -10px; - &:hover .svg-inline--fa { - color: $fallback--text; - color: var(--text, $fallback--text); + &:hover .svg-inline--fa { + color: $fallback--text; + color: var(--text, $fallback--text); + } } } diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index dab06e1e..1ac8e8be 100644 --- a/src/components/registration/registration.js +++ b/src/components/registration/registration.js @@ -10,7 +10,8 @@ const registration = { fullname: '', username: '', password: '', - confirm: '' + confirm: '', + reason: '' }, captcha: {} }), @@ -24,7 +25,8 @@ const registration = { confirm: { required, sameAsPassword: sameAs('password') - } + }, + reason: { required: requiredIf(() => this.accountApprovalRequired) } } } }, @@ -38,7 +40,10 @@ const registration = { computed: { token () { return this.$route.params.token }, bioPlaceholder () { - return this.$t('registration.bio_placeholder').replace(/\s*\n\s*/g, ' \n') + return this.replaceNewlines(this.$t('registration.bio_placeholder')) + }, + reasonPlaceholder () { + return this.replaceNewlines(this.$t('registration.reason_placeholder')) }, ...mapState({ registrationOpen: (state) => state.instance.registrationOpen, @@ -46,7 +51,8 @@ const registration = { isPending: (state) => state.users.signUpPending, serverValidationErrors: (state) => state.users.signUpErrors, termsOfService: (state) => state.instance.tos, - accountActivationRequired: (state) => state.instance.accountActivationRequired + accountActivationRequired: (state) => state.instance.accountActivationRequired, + accountApprovalRequired: (state) => state.instance.accountApprovalRequired }) }, methods: { @@ -73,6 +79,9 @@ const registration = { }, setCaptcha () { this.getCaptcha().then(cpt => { this.captcha = cpt }) + }, + replaceNewlines (str) { + return str.replace(/\s*\n\s*/g, ' \n') } } } diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index 100df0d6..062d4121 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -163,6 +163,23 @@ </div> <div + v-if="accountApprovalRequired" + class="form-group" + > + <label + class="form--label" + for="reason" + >{{ $t('registration.reason') }}</label> + <textarea + id="reason" + v-model="user.reason" + :disabled="isPending" + class="form-control" + :placeholder="reasonPlaceholder" + /> + </div> + + <div v-if="captcha.type != 'none'" id="captcha-group" class="form-group" diff --git a/src/components/scope_selector/scope_selector.vue b/src/components/scope_selector/scope_selector.vue index 66ac612e..a01242fc 100644 --- a/src/components/scope_selector/scope_selector.vue +++ b/src/components/scope_selector/scope_selector.vue @@ -8,6 +8,7 @@ class="button-unstyled scope" :class="css.direct" :title="$t('post_status.scope.direct')" + type="button" @click="changeVis('direct')" > <FAIcon @@ -20,6 +21,7 @@ class="button-unstyled scope" :class="css.private" :title="$t('post_status.scope.private')" + type="button" @click="changeVis('private')" > <FAIcon @@ -32,6 +34,7 @@ class="button-unstyled scope" :class="css.unlisted" :title="$t('post_status.scope.unlisted')" + type="button" @click="changeVis('unlisted')" > <FAIcon @@ -44,6 +47,7 @@ class="button-unstyled scope" :class="css.public" :title="$t('post_status.scope.public')" + type="button" @click="changeVis('public')" > <FAIcon diff --git a/src/components/search/search.vue b/src/components/search/search.vue index a6503c9f..b7bfc1f3 100644 --- a/src/components/search/search.vue +++ b/src/components/search/search.vue @@ -15,6 +15,7 @@ > <button class="btn button-default search-button" + type="submit" @click="newQuery(searchTerm)" > <FAIcon icon="search" /> diff --git a/src/components/search_bar/search_bar.vue b/src/components/search_bar/search_bar.vue index 6cf9179e..222f57ba 100644 --- a/src/components/search_bar/search_bar.vue +++ b/src/components/search_bar/search_bar.vue @@ -7,6 +7,7 @@ v-if="hidden" class="button-unstyled nav-icon" :title="$t('nav.search')" + type="button" @click.prevent.stop="toggleHidden" > <FAIcon @@ -27,6 +28,7 @@ > <button class="button-default search-button" + type="submit" @click="find(searchTerm)" > <FAIcon @@ -36,6 +38,7 @@ </button> <button class="button-unstyled cancel-search" + type="button" @click.prevent.stop="toggleHidden" > <FAIcon diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue new file mode 100644 index 00000000..146ad6c1 --- /dev/null +++ b/src/components/settings_modal/helpers/boolean_setting.vue @@ -0,0 +1,57 @@ +<template> + <label + class="BooleanSetting" + > + <Checkbox + :checked="state" + :disabled="disabled" + @change="update" + > + <span + v-if="!!$slots.default" + class="label" + > + <slot /> + </span> + <ModifiedIndicator :changed="isChanged" /> + </Checkbox> + </label> +</template> + +<script> +import { get, set } from 'lodash' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import ModifiedIndicator from './modified_indicator.vue' +export default { + components: { + Checkbox, + ModifiedIndicator + }, + props: [ + 'path', + 'disabled' + ], + computed: { + pathDefault () { + const [firstSegment, ...rest] = this.path.split('.') + return [firstSegment + 'DefaultValue', ...rest].join('.') + }, + state () { + return get(this.$parent, this.path) + }, + isChanged () { + return get(this.$parent, this.path) !== get(this.$parent, this.pathDefault) + } + }, + methods: { + update (e) { + set(this.$parent, this.path, e) + } + } +} +</script> + +<style lang="scss"> +.BooleanSetting { +} +</style> diff --git a/src/components/settings_modal/helpers/modified_indicator.vue b/src/components/settings_modal/helpers/modified_indicator.vue new file mode 100644 index 00000000..9f4e81fe --- /dev/null +++ b/src/components/settings_modal/helpers/modified_indicator.vue @@ -0,0 +1,51 @@ +<template> + <span + v-if="changed" + class="ModifiedIndicator" + > + <Popover + trigger="hover" + > + <span slot="trigger"> + + <FAIcon + icon="wrench" + /> + </span> + <div + slot="content" + class="modified-tooltip" + > + {{ $t('settings.setting_changed') }} + </div> + </Popover> + </span> +</template> + +<script> +import Popover from 'src/components/popover/popover.vue' +import { library } from '@fortawesome/fontawesome-svg-core' +import { faWrench } from '@fortawesome/free-solid-svg-icons' + +library.add( + faWrench +) + +export default { + components: { Popover }, + props: ['changed'] +} +</script> + +<style lang="scss"> +.ModifiedIndicator { + display: inline-block; + position: relative; + + .modified-tooltip { + margin: 0.5em 1em; + min-width: 10em; + text-align: center; + } +} +</style> diff --git a/src/components/settings_modal/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js index 86703697..2c833c0c 100644 --- a/src/components/settings_modal/helpers/shared_computed_object.js +++ b/src/components/settings_modal/helpers/shared_computed_object.js @@ -1,29 +1,15 @@ -import { - instanceDefaultProperties, - multiChoiceProperties, - defaultState as configDefaultState -} from 'src/modules/config.js' +import { defaultState as configDefaultState } from 'src/modules/config.js' const SharedComputedObject = () => ({ user () { return this.$store.state.users.currentUser }, - // Getting localized values for instance-default properties - ...instanceDefaultProperties - .filter(key => multiChoiceProperties.includes(key)) + // Getting values for default properties + ...Object.keys(configDefaultState) .map(key => [ key + 'DefaultValue', function () { - return this.$store.getters.instanceDefaultConfig[key] - } - ]) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), - ...instanceDefaultProperties - .filter(key => !multiChoiceProperties.includes(key)) - .map(key => [ - key + 'LocalizedValue', - function () { - return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key]) + return this.$store.getters.defaultConfig[key] } ]) .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index 5f38a5ae..6e95f7af 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -1,5 +1,5 @@ import { filter, trim } from 'lodash' -import Checkbox from 'src/components/checkbox/checkbox.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import { library } from '@fortawesome/fontawesome-svg-core' @@ -18,7 +18,7 @@ const FilteringTab = { } }, components: { - Checkbox + BooleanSetting }, computed: { ...SharedComputedObject(), diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index 8f850c8b..402c2a4a 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -5,34 +5,34 @@ <span class="label">{{ $t('settings.notification_visibility') }}</span> <ul class="option-list"> <li> - <Checkbox v-model="notificationVisibility.likes"> + <BooleanSetting path="notificationVisibility.likes"> {{ $t('settings.notification_visibility_likes') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="notificationVisibility.repeats"> + <BooleanSetting path="notificationVisibility.repeats"> {{ $t('settings.notification_visibility_repeats') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="notificationVisibility.follows"> + <BooleanSetting path="notificationVisibility.follows"> {{ $t('settings.notification_visibility_follows') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="notificationVisibility.mentions"> + <BooleanSetting path="notificationVisibility.mentions"> {{ $t('settings.notification_visibility_mentions') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="notificationVisibility.moves"> + <BooleanSetting path="notificationVisibility.moves"> {{ $t('settings.notification_visibility_moves') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="notificationVisibility.emojiReactions"> + <BooleanSetting path="notificationVisibility.emojiReactions"> {{ $t('settings.notification_visibility_emoji_reactions') }} - </Checkbox> + </BooleanSetting> </li> </ul> </div> @@ -60,14 +60,14 @@ </label> </div> <div> - <Checkbox v-model="hidePostStats"> - {{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }} - </Checkbox> + <BooleanSetting path="hidePostStats"> + {{ $t('settings.hide_post_stats') }} + </BooleanSetting> </div> <div> - <Checkbox v-model="hideUserStats"> - {{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }} - </Checkbox> + <BooleanSetting path="hideUserStats"> + {{ $t('settings.hide_user_stats') }} + </BooleanSetting> </div> </div> <div class="setting-item"> @@ -75,14 +75,14 @@ <p>{{ $t('settings.filtering_explanation') }}</p> <textarea id="muteWords" - class="resize-height" v-model="muteWordsString" + class="resize-height" /> </div> <div> - <Checkbox v-model="hideFilteredStatuses"> - {{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }} - </Checkbox> + <BooleanSetting path="hideFilteredStatuses"> + {{ $t('settings.hide_filtered_statuses') }} + </BooleanSetting> </div> </div> </div> diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index 029ee7a1..2db523be 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -1,4 +1,4 @@ -import Checkbox from 'src/components/checkbox/checkbox.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' @@ -26,7 +26,7 @@ const GeneralTab = { } }, components: { - Checkbox, + BooleanSetting, InterfaceLanguageSwitcher }, computed: { diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index a9081793..9228c78e 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -7,14 +7,14 @@ <interface-language-switcher /> </li> <li v-if="instanceSpecificPanelPresent"> - <Checkbox v-model="hideISP"> + <BooleanSetting path="hideISP"> {{ $t('settings.hide_isp') }} - </Checkbox> + </BooleanSetting> </li> <li v-if="instanceWallpaperUsed"> - <Checkbox v-model="hideInstanceWallpaper"> + <BooleanSetting path="hideInstanceWallpaper"> {{ $t('settings.hide_wallpaper') }} - </Checkbox> + </BooleanSetting> </li> </ul> </div> @@ -22,51 +22,51 @@ <h2>{{ $t('nav.timeline') }}</h2> <ul class="setting-list"> <li> - <Checkbox v-model="hideMutedPosts"> - {{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }} - </Checkbox> + <BooleanSetting path="hideMutedPosts"> + {{ $t('settings.hide_muted_posts') }} + </BooleanSetting> </li> <li> - <Checkbox v-model="collapseMessageWithSubject"> - {{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }} - </Checkbox> + <BooleanSetting path="collapseMessageWithSubject"> + {{ $t('settings.collapse_subject') }} + </BooleanSetting> </li> <li> - <Checkbox v-model="streaming"> + <BooleanSetting path="streaming"> {{ $t('settings.streaming') }} - </Checkbox> + </BooleanSetting> <ul class="setting-list suboptions" :class="[{disabled: !streaming}]" > <li> - <Checkbox - v-model="pauseOnUnfocused" + <BooleanSetting + path="pauseOnUnfocused" :disabled="!streaming" > {{ $t('settings.pause_on_unfocused') }} - </Checkbox> + </BooleanSetting> </li> </ul> </li> <li> - <Checkbox v-model="useStreamingApi"> + <BooleanSetting path="useStreamingApi"> {{ $t('settings.useStreamingApi') }} <br> <small> {{ $t('settings.useStreamingApiWarning') }} </small> - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="emojiReactionsOnTimeline"> + <BooleanSetting path="emojiReactionsOnTimeline"> {{ $t('settings.emoji_reactions_on_timeline') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="virtualScrolling"> + <BooleanSetting path="virtualScrolling"> {{ $t('settings.virtual_scrolling') }} - </Checkbox> + </BooleanSetting> </li> </ul> </div> @@ -75,14 +75,14 @@ <h2>{{ $t('settings.composing') }}</h2> <ul class="setting-list"> <li> - <Checkbox v-model="scopeCopy"> - {{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }} - </Checkbox> + <BooleanSetting path="scopeCopy"> + {{ $t('settings.scope_copy') }} + </BooleanSetting> </li> <li> - <Checkbox v-model="alwaysShowSubjectInput"> - {{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }} - </Checkbox> + <BooleanSetting path="alwaysShowSubjectInput"> + {{ $t('settings.subject_input_always_show') }} + </BooleanSetting> </li> <li> <div> @@ -143,19 +143,24 @@ </div> </li> <li> - <Checkbox v-model="minimalScopesMode"> - {{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }} - </Checkbox> + <BooleanSetting path="minimalScopesMode"> + {{ $t('settings.minimal_scopes_mode') }} + </BooleanSetting> </li> <li> - <Checkbox v-model="autohideFloatingPostButton"> + <BooleanSetting path="sensitiveByDefault"> + {{ $t('settings.sensitive_by_default') }} + </BooleanSetting> + </li> + <li> + <BooleanSetting path="autohideFloatingPostButton"> {{ $t('settings.autohide_floating_post_button') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="padEmoji"> + <BooleanSetting path="padEmoji"> {{ $t('settings.pad_emoji') }} - </Checkbox> + </BooleanSetting> </li> </ul> </div> @@ -164,14 +169,14 @@ <h2>{{ $t('settings.attachments') }}</h2> <ul class="setting-list"> <li> - <Checkbox v-model="hideAttachments"> + <BooleanSetting path="hideAttachments"> {{ $t('settings.hide_attachments_in_tl') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="hideAttachmentsInConv"> + <BooleanSetting path="hideAttachmentsInConv"> {{ $t('settings.hide_attachments_in_convo') }} - </Checkbox> + </BooleanSetting> </li> <li> <label for="maxThumbnails"> @@ -179,7 +184,7 @@ </label> <input id="maxThumbnails" - v-model.number="maxThumbnails" + path.number="maxThumbnails" class="number-input" type="number" min="0" @@ -187,48 +192,48 @@ > </li> <li> - <Checkbox v-model="hideNsfw"> + <BooleanSetting path="hideNsfw"> {{ $t('settings.nsfw_clickthrough') }} - </Checkbox> + </BooleanSetting> </li> <ul class="setting-list suboptions"> <li> - <Checkbox - v-model="preloadImage" + <BooleanSetting + path="preloadImage" :disabled="!hideNsfw" > {{ $t('settings.preload_images') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox - v-model="useOneClickNsfw" + <BooleanSetting + path="useOneClickNsfw" :disabled="!hideNsfw" > {{ $t('settings.use_one_click_nsfw') }} - </Checkbox> + </BooleanSetting> </li> </ul> <li> - <Checkbox v-model="stopGifs"> + <BooleanSetting path="stopGifs"> {{ $t('settings.stop_gifs') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="loopVideo"> + <BooleanSetting path="loopVideo"> {{ $t('settings.loop_video') }} - </Checkbox> + </BooleanSetting> <ul class="setting-list suboptions" :class="[{disabled: !streaming}]" > <li> - <Checkbox - v-model="loopVideoSilentOnly" + <BooleanSetting + path="loopVideoSilentOnly" :disabled="!loopVideo || !loopSilentAvailable" > {{ $t('settings.loop_video_silent_only') }} - </Checkbox> + </BooleanSetting> <div v-if="!loopSilentAvailable" class="unavailable" @@ -239,14 +244,14 @@ </ul> </li> <li> - <Checkbox v-model="playVideosInModal"> + <BooleanSetting path="playVideosInModal"> {{ $t('settings.play_videos_in_modal') }} - </Checkbox> + </BooleanSetting> </li> <li> - <Checkbox v-model="useContainFit"> + <BooleanSetting path="useContainFit"> {{ $t('settings.use_contain_fit') }} - </Checkbox> + </BooleanSetting> </li> </ul> </div> @@ -255,9 +260,9 @@ <h2>{{ $t('settings.notifications') }}</h2> <ul class="setting-list"> <li> - <Checkbox v-model="webPushNotifications"> + <BooleanSetting path="webPushNotifications"> {{ $t('settings.enable_web_push_notifications') }} - </Checkbox> + </BooleanSetting> </li> </ul> </div> @@ -266,9 +271,9 @@ <h2>{{ $t('settings.fun') }}</h2> <ul class="setting-list"> <li> - <Checkbox v-model="greentext"> - {{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }} - </Checkbox> + <BooleanSetting path="greentext"> + {{ $t('settings.greentext') }} + </BooleanSetting> </li> </ul> </div> diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss index e821f952..111eaed3 100644 --- a/src/components/settings_modal/tabs/profile_tab.scss +++ b/src/components/settings_modal/tabs/profile_tab.scss @@ -111,16 +111,17 @@ .profile-fields { display: flex; - &>.emoji-input { + & > .emoji-input { flex: 1 1 auto; - margin: 0 .2em .5em; + margin: 0 0.2em 0.5em; min-width: 0; } - &>.icon-container { + .delete-field { width: 20px; align-self: center; - margin: 0 .2em .5em; + margin: 0 0.2em 0.5em; + padding: 0 0.5em; } } } diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index b7ef21d7..175a0219 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -124,24 +124,24 @@ :placeholder="$t('settings.profile_fields.value')" > </EmojiInput> - <div - class="icon-container" + <button + class="delete-field button-unstyled -hover-highlight" + @click="deleteField(i)" > <FAIcon v-show="newFields.length > 1" icon="times" - @click="deleteField(i)" /> - </div> + </button> </div> - <a + <button v-if="newFields.length < maxFields" - class="add-field faint" + class="add-field faint button-unstyled -hover-highlight" @click="addField" > <FAIcon icon="plus" /> {{ $t("settings.profile_fields.add_field") }} - </a> + </button> </div> <p> <Checkbox v-model="bot"> diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js index 811161a5..65d20fc0 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -1,6 +1,7 @@ import ProgressButton from 'src/components/progress_button/progress_button.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' import Mfa from './mfa.vue' +import localeService from 'src/services/locale/locale.service.js' const SecurityTab = { data () { @@ -37,7 +38,7 @@ const SecurityTab = { return { id: oauthToken.id, appName: oauthToken.app_name, - validUntil: new Date(oauthToken.valid_until).toLocaleDateString() + validUntil: new Date(oauthToken.valid_until).toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale)) } }) } diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index 695ae03b..223b1632 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -109,7 +109,7 @@ v-if="chat" @click="toggleDrawer" > - <router-link :to="{ name: 'chat' }"> + <router-link :to="{ name: 'chat-panel' }"> <FAIcon fixed-width class="fa-scale-110 fa-old-padding" diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js index 8665648a..b9561bf1 100644 --- a/src/components/staff_panel/staff_panel.js +++ b/src/components/staff_panel/staff_panel.js @@ -1,4 +1,6 @@ import map from 'lodash/map' +import groupBy from 'lodash/groupBy' +import { mapGetters, mapState } from 'vuex' import BasicUserCard from '../basic_user_card/basic_user_card.vue' const StaffPanel = { @@ -10,9 +12,21 @@ const StaffPanel = { BasicUserCard }, computed: { - staffAccounts () { - return map(this.$store.state.instance.staffAccounts, nickname => this.$store.getters.findUser(nickname)).filter(_ => _) - } + groupedStaffAccounts () { + const staffAccounts = map(this.staffAccounts, this.findUser).filter(_ => _) + const groupedStaffAccounts = groupBy(staffAccounts, 'role') + + return [ + { role: 'admin', users: groupedStaffAccounts['admin'] }, + { role: 'moderator', users: groupedStaffAccounts['moderator'] } + ].filter(group => group.users) + }, + ...mapGetters([ + 'findUser' + ]), + ...mapState({ + staffAccounts: state => state.instance.staffAccounts + }) } } diff --git a/src/components/staff_panel/staff_panel.vue b/src/components/staff_panel/staff_panel.vue index 1d13003d..c52ade42 100644 --- a/src/components/staff_panel/staff_panel.vue +++ b/src/components/staff_panel/staff_panel.vue @@ -7,11 +7,18 @@ </div> </div> <div class="panel-body"> - <basic-user-card - v-for="user in staffAccounts" - :key="user.screen_name" - :user="user" - /> + <div + v-for="group in groupedStaffAccounts" + :key="group.role" + class="staff-group" + > + <h4>{{ $t('general.role.' + group.role) }}</h4> + <basic-user-card + v-for="user in group.users" + :key="user.screen_name" + :user="user" + /> + </div> </div> </div> </div> @@ -20,4 +27,14 @@ <script src="./staff_panel.js" ></script> <style lang="scss"> + +.staff-group { + padding-left: 1em; + padding-top: 1em; + + .basic-user-card { + padding-left: 0; + } +} + </style> diff --git a/src/components/status/status.js b/src/components/status/status.js index f9c710ab..470c01f1 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -136,7 +136,7 @@ const Status = { } }, retweet () { return !!this.statusoid.retweeted_status }, - retweeter () { return this.statusoid.user.name || this.statusoid.user.screen_name }, + retweeter () { return this.statusoid.user.name || this.statusoid.user.screen_name_ui }, retweeterHtml () { return this.statusoid.user.name_html }, retweeterProfileLink () { return this.generateUserProfileLink(this.statusoid.user.id, this.statusoid.user.screen_name) }, status () { @@ -157,6 +157,7 @@ const Status = { return muteWordHits(this.status, this.muteWords) }, muted () { + if (this.statusoid.user.id === this.currentUser.id) return false const { status } = this const { reblog } = status const relationship = this.$store.getters.relationship(status.user.id) @@ -215,7 +216,7 @@ const Status = { return this.status.in_reply_to_screen_name } else { const user = this.$store.getters.findUser(this.status.in_reply_to_user_id) - return user && user.screen_name + return user && user.screen_name_ui } }, replySubject () { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 6ee8117f..00e962f3 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -26,7 +26,7 @@ icon="retweet" /> <router-link :to="userProfileLink"> - {{ status.user.screen_name }} + {{ status.user.screen_name_ui }} </router-link> </small> <small @@ -156,10 +156,10 @@ </h4> <router-link class="account-name" - :title="status.user.screen_name" + :title="status.user.screen_name_ui" :to="userProfileLink" > - {{ status.user.screen_name }} + {{ status.user.screen_name_ui }} </router-link> <img v-if="!!(status.user && status.user.favicon)" diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js index 76e7ef03..12aac8e6 100644 --- a/src/components/tab_switcher/tab_switcher.js +++ b/src/components/tab_switcher/tab_switcher.js @@ -93,7 +93,9 @@ export default Vue.component('tab-switcher', { <button disabled={slot.data.attrs.disabled} onClick={this.clickTab(index)} - class={classesTab.join(' ')}> + class={classesTab.join(' ')} + type="button" + > <img src={slot.data.attrs.image} title={slot.data.attrs['image-tooltip']}/> {slot.data.attrs.label ? '' : slot.data.attrs.label} </button> diff --git a/src/components/timeago/timeago.vue b/src/components/timeago/timeago.vue index 6df0524d..55a2dd94 100644 --- a/src/components/timeago/timeago.vue +++ b/src/components/timeago/timeago.vue @@ -9,6 +9,7 @@ <script> import * as DateUtils from 'src/services/date_utils/date_utils.js' +import localeService from 'src/services/locale/locale.service.js' export default { name: 'Timeago', @@ -21,9 +22,10 @@ export default { }, computed: { localeDateString () { + const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale) return typeof this.time === 'string' - ? new Date(Date.parse(this.time)).toLocaleString() - : this.time.toLocaleString() + ? new Date(Date.parse(this.time)).toLocaleString(browserLocale) + : this.time.toLocaleString(browserLocale) } }, created () { diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 665d195e..44f749c3 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -2,12 +2,14 @@ import Status from '../status/status.vue' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' import Conversation from '../conversation/conversation.vue' import TimelineMenu from '../timeline_menu/timeline_menu.vue' +import TimelineQuickSettings from './timeline_quick_settings.vue' import { debounce, throttle, keyBy } from 'lodash' import { library } from '@fortawesome/fontawesome-svg-core' -import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { faCircleNotch, faCog } from '@fortawesome/free-solid-svg-icons' library.add( - faCircleNotch + faCircleNotch, + faCog ) export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => { @@ -47,7 +49,8 @@ const Timeline = { components: { Status, Conversation, - TimelineMenu + TimelineMenu, + TimelineQuickSettings }, computed: { newStatusCount () { diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 4c43fe5c..286477c2 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -16,6 +16,7 @@ > {{ $t('timeline.up_to_date') }} </div> + <TimelineQuickSettings v-if="!embedded" /> </div> <div :class="classes.body"> <div @@ -103,9 +104,12 @@ max-width: 100%; flex-wrap: nowrap; align-items: center; + position: relative; + .loadmore-button { flex-shrink: 0; } + .loadmore-text { flex-shrink: 0; line-height: 1em; diff --git a/src/components/timeline/timeline_quick_settings.js b/src/components/timeline/timeline_quick_settings.js new file mode 100644 index 00000000..9ec1a007 --- /dev/null +++ b/src/components/timeline/timeline_quick_settings.js @@ -0,0 +1,63 @@ +import Popover from '../popover/popover.vue' +import BooleanSetting from '../settings_modal/helpers/boolean_setting.vue' +import { mapGetters } from 'vuex' +import { library } from '@fortawesome/fontawesome-svg-core' +import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons' + +library.add( + faFilter, + faFont, + faWrench +) + +const TimelineQuickSettings = { + components: { + Popover, + BooleanSetting + }, + methods: { + setReplyVisibility (visibility) { + this.$store.dispatch('setOption', { name: 'replyVisibility', value: visibility }) + this.$store.dispatch('queueFlushAll') + }, + openTab (tab) { + this.$store.dispatch('openSettingsModalTab', tab) + } + }, + computed: { + ...mapGetters(['mergedConfig']), + loggedIn () { + return !!this.$store.state.users.currentUser + }, + replyVisibilitySelf: { + get () { return this.mergedConfig.replyVisibility === 'self' }, + set () { this.setReplyVisibility('self') } + }, + replyVisibilityFollowing: { + get () { return this.mergedConfig.replyVisibility === 'following' }, + set () { this.setReplyVisibility('following') } + }, + replyVisibilityAll: { + get () { return this.mergedConfig.replyVisibility === 'all' }, + set () { this.setReplyVisibility('all') } + }, + hideMedia: { + get () { return this.mergedConfig.hideAttachments || this.mergedConfig.hideAttachmentsInConv }, + set () { + const value = !this.hideMedia + this.$store.dispatch('setOption', { name: 'hideAttachments', value }) + this.$store.dispatch('setOption', { name: 'hideAttachmentsInConv', value }) + } + }, + hideMutedPosts: { + get () { return this.mergedConfig.hideMutedPosts || this.mergedConfig.hideFilteredStatuses }, + set () { + const value = !this.hideMutedPosts + this.$store.dispatch('setOption', { name: 'hideMutedPosts', value }) + this.$store.dispatch('setOption', { name: 'hideFilteredStatuses', value }) + } + } + } +} + +export default TimelineQuickSettings diff --git a/src/components/timeline/timeline_quick_settings.vue b/src/components/timeline/timeline_quick_settings.vue new file mode 100644 index 00000000..4ad2842b --- /dev/null +++ b/src/components/timeline/timeline_quick_settings.vue @@ -0,0 +1,107 @@ +<template> + <Popover + trigger="click" + class="TimelineQuickSettings" + :bound-to="{ x: 'container' }" + > + <div + slot="content" + class="timeline-settings-menu dropdown-menu" + > + <div v-if="loggedIn"> + <button + class="button-default dropdown-item" + @click="replyVisibilityAll = true" + > + <span + class="menu-checkbox" + :class="{ 'menu-checkbox-radio': replyVisibilityAll }" + />{{ $t('settings.reply_visibility_all') }} + </button> + <button + class="button-default dropdown-item" + @click="replyVisibilityFollowing = true" + > + <span + class="menu-checkbox" + :class="{ 'menu-checkbox-radio': replyVisibilityFollowing }" + />{{ $t('settings.reply_visibility_following_short') }} + </button> + <button + class="button-default dropdown-item" + @click="replyVisibilitySelf = true" + > + <span + class="menu-checkbox" + :class="{ 'menu-checkbox-radio': replyVisibilitySelf }" + />{{ $t('settings.reply_visibility_self_short') }} + </button> + <div + role="separator" + class="dropdown-divider" + /> + </div> + <button + class="button-default dropdown-item" + @click="hideMedia = !hideMedia" + > + <span + class="menu-checkbox" + :class="{ 'menu-checkbox-checked': hideMedia }" + />{{ $t('settings.hide_media_previews') }} + </button> + <button + class="button-default dropdown-item" + @click="hideMutedPosts = !hideMutedPosts" + > + <span + class="menu-checkbox" + :class="{ 'menu-checkbox-checked': hideMutedPosts }" + />{{ $t('settings.hide_all_muted_posts') }} + </button> + <button + class="button-default dropdown-item dropdown-item-icon" + @click="openTab('filtering')" + > + <FAIcon icon="font" />{{ $t('settings.word_filter') }} + </button> + <button + class="button-default dropdown-item dropdown-item-icon" + @click="openTab('general')" + > + <FAIcon icon="wrench" />{{ $t('settings.more_settings') }} + </button> + </div> + <div slot="trigger"> + <FAIcon icon="filter" /> + </div> + </Popover> +</template> + +<script src="./timeline_quick_settings.js"></script> + +<style lang="scss"> + +.TimelineQuickSettings { + align-self: stretch; + + > button { + font-size: 1.2em; + padding-left: 0.7em; + padding-right: 0.2em; + line-height: 100%; + height: 100%; + } + + .dropdown-item { + margin: 0; + } + + .timeline-settings-menu { + display: flex; + min-width: 12em; + flex-direction: column; + } +} + +</style> diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue index 0f7c584b..4040e263 100644 --- a/src/components/user_avatar/user_avatar.vue +++ b/src/components/user_avatar/user_avatar.vue @@ -2,8 +2,8 @@ <StillImage v-if="user" class="Avatar" - :alt="user.screen_name" - :title="user.screen_name" + :alt="user.screen_name_ui" + :title="user.screen_name_ui" :src="imgSrc(user.profile_image_url_original)" :class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }" :image-load-error="imageLoadError" diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 16dd5249..4b7ee7d5 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -73,23 +73,23 @@ <div class="bottom-line"> <router-link class="user-screen-name" - :title="user.screen_name" + :title="user.screen_name_ui" :to="userProfileLink(user)" > - @{{ user.screen_name }} + @{{ user.screen_name_ui }} </router-link> <template v-if="!hideBio"> <span v-if="!!visibleRole" class="alert user-role" > - {{ visibleRole }} + {{ $t(`general.role.${visibleRole}`) }} </span> <span v-if="user.bot" class="alert user-role" > - bot + {{ $t('user_card.bot') }} </span> </template> <span v-if="user.locked"> @@ -141,10 +141,10 @@ v-model="userHighlightType" class="userHighlightSel" > - <option value="disabled">No highlight</option> - <option value="solid">Solid bg</option> - <option value="striped">Striped bg</option> - <option value="side">Side stripe</option> + <option value="disabled">{{ $t('user_card.highlight.disabled') }}</option> + <option value="solid">{{ $t('user_card.highlight.solid') }}</option> + <option value="striped">{{ $t('user_card.highlight.striped') }}</option> + <option value="side">{{ $t('user_card.highlight.side') }}</option> </select> <FAIcon class="select-down-icon" @@ -507,7 +507,6 @@ .user-role { flex: none; - text-transform: capitalize; color: $fallback--text; color: var(--alertNeutralText, $fallback--text); background-color: $fallback--fg; diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue index 95673733..d0aa315e 100644 --- a/src/components/user_list_popover/user_list_popover.vue +++ b/src/components/user_list_popover/user_list_popover.vue @@ -26,7 +26,7 @@ <!-- eslint-disable vue/no-v-html --> <span v-html="user.name_html" /> <!-- eslint-enable vue/no-v-html --> - <span class="user-list-screen-name">{{ user.screen_name }}</span> + <span class="user-list-screen-name">{{ user.screen_name_ui }}</span> </div> </div> </div> diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue index fb43094f..a71c02c1 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.vue +++ b/src/components/user_reporting_modal/user_reporting_modal.vue @@ -6,7 +6,7 @@ <div class="user-reporting-panel panel"> <div class="panel-heading"> <div class="title"> - {{ $t('user_reporting.title', [user.screen_name]) }} + {{ $t('user_reporting.title', [user.screen_name_ui]) }} </div> </div> <div class="panel-body"> diff --git a/src/i18n/en.json b/src/i18n/en.json index 5d613ea9..217ac7c8 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -75,7 +75,11 @@ "confirm": "Confirm", "verify": "Verify", "close": "Close", - "peek": "Peek" + "peek": "Peek", + "role": { + "admin": "Admin", + "moderator": "Moderator" + } }, "image_cropper": { "crop_picture": "Crop picture", @@ -148,6 +152,8 @@ "add_option": "Add Option", "option": "Option", "votes": "votes", + "people_voted_count": "{count} person voted | {count} people voted", + "votes_count": "{count} vote | {count} votes", "vote": "Vote", "type": "Poll type", "single_choice": "Single choice", @@ -222,6 +228,8 @@ "username_placeholder": "e.g. lain", "fullname_placeholder": "e.g. Lain Iwakura", "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.", + "reason": "Reason to register", + "reason_placeholder": "This instance approves registrations manually.\nLet the administration know why you want to register.", "validations": { "username_required": "cannot be left blank", "fullname_required": "cannot be left blank", @@ -242,6 +250,7 @@ "settings": { "app_name": "App name", "security": "Security", + "setting_changed": "Setting is different from default", "enter_current_password_to_confirm": "Enter your current password to confirm your identity", "mfa": { "otp": "OTP", @@ -316,6 +325,7 @@ "export_theme": "Save preset", "filtering": "Filtering", "filtering_explanation": "All statuses containing these words will be muted, one per line", + "word_filter": "Word filter", "follow_export": "Follow export", "follow_export_button": "Export your follows to a csv file", "follow_import": "Follow import", @@ -326,7 +336,9 @@ "general": "General", "hide_attachments_in_convo": "Hide attachments in conversations", "hide_attachments_in_tl": "Hide attachments in timeline", + "hide_media_previews": "Hide media previews", "hide_muted_posts": "Hide posts of muted users", + "hide_all_muted_posts": "Hide muted posts", "max_thumbnails": "Maximum amount of thumbnails per post", "hide_isp": "Hide instance-specific panel", "hide_wallpaper": "Hide instance wallpaper", @@ -396,6 +408,8 @@ "reply_visibility_all": "Show all replies", "reply_visibility_following": "Only show replies directed at me or users I'm following", "reply_visibility_self": "Only show replies directed at me", + "reply_visibility_following_short": "Show replies to my follows", + "reply_visibility_self_short": "Show replies to self only", "autohide_floating_post_button": "Automatically hide New Post button (mobile)", "saving_err": "Error saving settings", "saving_ok": "Settings saved", @@ -420,6 +434,7 @@ "subject_line_mastodon": "Like mastodon: copy as is", "subject_line_noop": "Do not copy", "post_status_content_type": "Post status content type", + "sensitive_by_default": "Mark posts as sensitive by default", "stop_gifs": "Play-on-hover GIFs", "streaming": "Enable automatic streaming of new posts when scrolled to the top", "user_mutes": "Users", @@ -449,6 +464,7 @@ "notification_mutes": "To stop receiving notifications from a specific user, use a mute.", "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.", "enable_web_push_notifications": "Enable web push notifications", + "more_settings": "More settings", "style": { "switcher": { "keep_color": "Keep colors", @@ -714,6 +730,7 @@ "mute_progress": "Muting…", "hide_repeats": "Hide repeats", "show_repeats": "Show repeats", + "bot": "Bot", "admin_menu": { "moderation": "Moderation", "grant_admin": "Grant Admin", @@ -732,6 +749,12 @@ "quarantine": "Disallow user posts from federating", "delete_user": "Delete user", "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone." + }, + "highlight": { + "disabled": "No highlight", + "solid": "Solid bg", + "striped": "Striped bg", + "side": "Side stripe" } }, "user_profile": { diff --git a/src/i18n/eo.json b/src/i18n/eo.json index 58f8e125..9f0491fb 100644 --- a/src/i18n/eo.json +++ b/src/i18n/eo.json @@ -35,7 +35,11 @@ "retry": "Reprovi", "error_retry": "Bonvolu reprovi", "loading": "Enlegante…", - "peek": "Antaŭmontri" + "peek": "Antaŭmontri", + "role": { + "moderator": "Reguligisto", + "admin": "Administranto" + } }, "image_cropper": { "crop_picture": "Tondi bildon", @@ -365,7 +369,8 @@ "post": "Afiŝoj/Priskriboj de uzantoj", "alert_neutral": "Neŭtrala", "alert_warning": "Averto", - "toggled": "Ŝaltita" + "toggled": "Ŝaltita", + "wallpaper": "Fonbildo" }, "radii": { "_tab_label": "Rondeco" @@ -516,7 +521,9 @@ "mute_import_error": "Eraris enporto de silentigoj", "mute_import": "Enporto de silentigoj", "mute_export_button": "Elportu viajn silentigojn al CSV-dosiero", - "mute_export": "Elporto de silentigoj" + "mute_export": "Elporto de silentigoj", + "hide_wallpaper": "Kaŝi fonbildon de nodo", + "setting_changed": "Agordo malsamas de la implicita" }, "timeline": { "collapse": "Maletendi", @@ -586,7 +593,8 @@ "show_repeats": "Montri ripetojn", "hide_repeats": "Kaŝi ripetojn", "unsubscribe": "Ne ricevi sciigojn", - "subscribe": "Ricevi sciigojn" + "subscribe": "Ricevi sciigojn", + "bot": "Roboto" }, "user_profile": { "timeline_title": "Historio de uzanto", @@ -612,7 +620,8 @@ "error": { "base": "Alŝuto malsukcesis.", "file_too_big": "Dosiero estas tro granda [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", - "default": "Reprovu pli poste" + "default": "Reprovu pli poste", + "message": "Malsukcesis alŝuto: {0}" }, "file_size_units": { "B": "B", @@ -645,7 +654,9 @@ "votes": "voĉoj", "option": "Elekteblo", "add_option": "Aldoni elekteblon", - "add_poll": "Aldoni enketon" + "add_poll": "Aldoni enketon", + "votes_count": "{count} voĉdono | {count} voĉdonoj", + "people_voted_count": "{count} persono voĉdonis | {count} personoj voĉdonis" }, "importer": { "error": "Eraris enporto de ĉi tiu dosiero.", @@ -732,7 +743,9 @@ "repeats": "Ripetoj", "favorites": "Ŝatoj", "status_deleted": "Ĉi tiu afiŝo foriĝis", - "nsfw": "Konsterna" + "nsfw": "Konsterna", + "expand": "Etendi", + "external_source": "Ekstera fonto" }, "time": { "years_short": "{0}j", diff --git a/src/i18n/es.json b/src/i18n/es.json index f4d87eb3..0ee67ed4 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -562,7 +562,8 @@ "mute_import": "Importar silenciados", "mute_export_button": "Exportar los silenciados a un archivo csv", "mute_export": "Exportar silenciados", - "hide_wallpaper": "Ocultar el fondo de pantalla de la instancia" + "hide_wallpaper": "Ocultar el fondo de pantalla de la instancia", + "setting_changed": "La configuración es diferente a la predeterminada" }, "time": { "day": "{0} día", @@ -693,7 +694,11 @@ "show_repeats": "Mostrar repetidos", "hide_repeats": "Ocultar repetidos", "message": "Mensaje", - "hidden": "Oculto" + "hidden": "Oculto", + "roles": { + "moderator": "Moderador", + "admin": "Administrador" + } }, "user_profile": { "timeline_title": "Linea Temporal del Usuario", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 63ad46d2..c2eff808 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -280,7 +280,7 @@ "hide_followers_description": "Ne pas afficher qui est abonné à moi", "show_admin_badge": "Afficher le badge d'Administrateur⋅ice sur mon profil", "show_moderator_badge": "Afficher le badge de Modérateur⋅ice sur mon profil", - "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible", + "nsfw_clickthrough": "Activer le clic pour dévoiler les pièces jointes et cacher l'aperçu des liens pour les statuts marqués comme sensibles", "oauth_tokens": "Jetons OAuth", "token": "Jeton", "refresh_token": "Rafraichir le jeton", @@ -409,7 +409,13 @@ "tabs": "Onglets", "toggled": "(Dés)activé", "highlight": "Éléments mis en valeur", - "popover": "Infobulles, menus" + "popover": "Infobulles, menus", + "chat": { + "border": "Bordure", + "outgoing": "Sortant(s)", + "incoming": "Entrant(s)" + }, + "wallpaper": "Fond d'écran" }, "radii": { "_tab_label": "Rondeur" @@ -485,7 +491,7 @@ "notification_visibility_emoji_reactions": "Réactions", "hide_follows_count_description": "Masquer le nombre de suivis", "useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)", - "type_domains_to_mute": "Écrire les domaines à masquer", + "type_domains_to_mute": "Chercher les domaines à masquer", "fun": "Rigolo", "greentext": "greentexting", "allow_following_move": "Suivre automatiquement quand ce compte migre", @@ -509,7 +515,21 @@ "mute_import_error": "Erreur à l'import des masquages", "mute_import": "Import des masquages", "mute_export_button": "Exporter vos masquages dans un fichier CSV", - "mute_export": "Export des masquages" + "mute_export": "Export des masquages", + "notification_setting_hide_notification_contents": "Cacher l'expéditeur et le contenu des notifications push", + "notification_setting_block_from_strangers": "Bloquer les notifications des utilisateur⋅ice⋅s que vous ne suivez pas", + "virtual_scrolling": "Optimiser le rendu du fil d'actualité", + "reset_background_confirm": "Voulez-vraiment réinitialiser l'arrière-plan ?", + "reset_banner_confirm": "Voulez-vraiment réinitialiser la bannière ?", + "reset_avatar_confirm": "Voulez-vraiment réinitialiser l'avatar ?", + "reset_profile_banner": "Réinitialiser la bannière du profil", + "reset_profile_background": "Réinitialiser l'arrière-plan du profil", + "reset_avatar": "Réinitialiser l'avatar", + "profile_fields": { + "value": "Contenu", + "name": "Étiquette", + "add_field": "Ajouter un champ" + } }, "timeline": { "collapse": "Fermer", @@ -521,7 +541,9 @@ "show_new": "Afficher plus", "up_to_date": "À jour", "no_more_statuses": "Pas plus de statuts", - "no_statuses": "Aucun statuts" + "no_statuses": "Aucun statuts", + "reload": "Recharger", + "error": "Erreur lors de l'affichage du fil d'actualité : {0}" }, "status": { "favorites": "Favoris", @@ -536,7 +558,19 @@ "mute_conversation": "Masquer la conversation", "unmute_conversation": "Démasquer la conversation", "status_unavailable": "Status indisponible", - "copy_link": "Copier le lien au status" + "copy_link": "Copier le lien au status", + "expand": "Développer", + "nsfw": "Contenu sensible", + "status_deleted": "Ce post a été effacé", + "hide_content": "Cacher le contenu", + "show_content": "Montrer le contenu", + "hide_full_subject": "Cacher le sujet", + "show_full_subject": "Montrer le sujet en entier", + "thread_muted_and_words": ", contient les mots :", + "thread_muted": "Fil de discussion masqué", + "external_source": "Source externe", + "unbookmark": "Supprimer des favoris", + "bookmark": "Ajouter aux favoris" }, "user_card": { "approve": "Accepter", @@ -591,7 +625,12 @@ "subscribe": "Abonner", "unsubscribe": "Désabonner", "hide_repeats": "Cacher les partages", - "show_repeats": "Montrer les partages" + "show_repeats": "Montrer les partages", + "roles": { + "moderator": "Modérateur⋅ice", + "admin": "Administrateur⋅ice" + }, + "message": "Message" }, "user_profile": { "timeline_title": "Journal de l'utilisateur⋅ice", @@ -619,13 +658,15 @@ "user_settings": "Paramètres utilisateur", "add_reaction": "Ajouter une réaction", "accept_follow_request": "Accepter la demande de suivit", - "reject_follow_request": "Rejeter la demande de suivit" + "reject_follow_request": "Rejeter la demande de suivit", + "bookmark": "Favori" }, "upload": { "error": { "base": "L'envoi a échoué.", "file_too_big": "Fichier trop gros [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", - "default": "Réessayez plus tard" + "default": "Réessayez plus tard", + "message": "Envoi échoué : {0}" }, "file_size_units": { "B": "O", @@ -759,5 +800,27 @@ }, "shoutbox": { "title": "Shoutbox" + }, + "display_date": { + "today": "Aujourd'hui" + }, + "file_type": { + "file": "Fichier", + "image": "Image", + "video": "Vidéo", + "audio": "Audio" + }, + "chats": { + "empty_chat_list_placeholder": "Vous n'avez pas encore de discussions. Démarrez-en une nouvelle !", + "error_sending_message": "Quelque chose s'est mal passé pendant l'envoi du message.", + "error_loading_chat": "Quelque chose s'est mal passé au chargement de la discussion.", + "delete_confirm": "Voulez-vous vraiment effacer ce message ?", + "more": "Plus", + "empty_message_error": "Impossible d'envoyer un message vide", + "new": "Nouvelle discussion", + "chats": "Discussions", + "delete": "Effacer", + "message_user": "Message à {nickname}", + "you": "Vous :" } } diff --git a/src/i18n/it.json b/src/i18n/it.json index ea8f1760..32028262 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -17,7 +17,11 @@ "close": "Chiudi", "retry": "Riprova", "error_retry": "Per favore, riprova", - "loading": "Carico…" + "loading": "Carico…", + "role": { + "moderator": "Moderatore", + "admin": "Amministratore" + } }, "nav": { "mentions": "Menzioni", @@ -30,7 +34,7 @@ "administration": "Amministrazione", "back": "Indietro", "interactions": "Interazioni", - "dms": "Messaggi diretti", + "dms": "Messaggi privati", "user_search": "Ricerca utenti", "search": "Ricerca", "who_to_follow": "Chi seguire", @@ -44,7 +48,7 @@ "notifications": "Notifiche", "read": "Letto!", "broken_favorite": "Stato sconosciuto, lo sto cercando…", - "favorited_you": "ha gradito il tuo messaggio", + "favorited_you": "gradisce il tuo messaggio", "load_older": "Carica notifiche precedenti", "repeated_you": "ha condiviso il tuo messaggio", "follow_request": "vuole seguirti", @@ -417,7 +421,8 @@ "mute_import": "Importa silenziati", "mute_export_button": "Esporta la tua lista di silenziati in un file CSV", "mute_export": "Esporta silenziati", - "hide_wallpaper": "Nascondi sfondo della stanza" + "hide_wallpaper": "Nascondi sfondo della stanza", + "setting_changed": "Valore personalizzato" }, "timeline": { "error_fetching": "Errore nell'aggiornamento", @@ -487,7 +492,8 @@ "follow_progress": "Richiedo…", "follow_sent": "Richiesta inviata!", "favorites": "Preferiti", - "message": "Contatta" + "message": "Contatta", + "bot": "Bot" }, "chat": { "title": "Chat" @@ -515,18 +521,18 @@ "register": "Registrati", "username": "Nome utente", "description": "Accedi con OAuth", - "hint": "Accedi per partecipare alla discussione", + "hint": "Accedi per conversare", "authentication_code": "Codice di autenticazione", "enter_recovery_code": "Inserisci un codice di recupero", - "enter_two_factor_code": "Inserisci un codice two-factor", + "enter_two_factor_code": "Inserisci un codice 2FA", "recovery_code": "Codice di recupero", "heading": { - "totp": "Autenticazione two-factor", - "recovery": "Recupero two-factor" + "totp": "Autenticazione 2FA", + "recovery": "Recupero 2FA" } }, "post_status": { - "account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi riservati ai tuoi seguaci.", + "account_not_locked_warning": "Il tuo profilo non è {0}. Chiunque può seguirti e vedere i tuoi messaggi per seguaci.", "account_not_locked_warning_link": "protetto", "attachments_sensitive": "Nascondi gli allegati", "content_type": { @@ -536,7 +542,7 @@ "text/html": "HTML" }, "content_warning": "Oggetto (facoltativo)", - "default": "Sono appena atterrato a Fiumicino.", + "default": "Sono appena atterrato a Città Laggiù.", "direct_warning": "Questo post sarà visibile solo dagli utenti menzionati.", "posting": "Sto pubblicando", "scope": { @@ -578,7 +584,9 @@ "fullname_placeholder": "es. Lupo Lucio", "username_placeholder": "es. mister_wolf", "new_captcha": "Clicca l'immagine per avere un altro captcha", - "captcha": "CAPTCHA" + "captcha": "CAPTCHA", + "reason_placeholder": "L'amministratore esamina ciascuna richiesta.\nFornisci il motivo della tua iscrizione.", + "reason": "Motivo dell'iscrizione" }, "user_profile": { "timeline_title": "Sequenza dell'Utente", @@ -646,20 +654,22 @@ }, "polls": { "add_poll": "Sondaggio", - "add_option": "Alternativa", + "add_option": "Aggiungi opzione", "option": "Opzione", "votes": "voti", "vote": "Vota", "type": "Tipo di sondaggio", "single_choice": "Scelta singola", "multiple_choices": "Scelta multipla", - "expiry": "Scadenza", - "expires_in": "Scade fra {0}", - "expired": "Scaduto {0} fa", - "not_enough_options": "Aggiungi altre risposte" + "expiry": "Età", + "expires_in": "Chiude fra {0}", + "expired": "Chiuso {0} fa", + "not_enough_options": "Aggiungi altre risposte", + "votes_count": "{count} voto | {count} voti", + "people_voted_count": "{count} votante | {count} votanti" }, "interactions": { - "favs_repeats": "Condivisi e preferiti", + "favs_repeats": "Condivisi e Graditi", "load_older": "Carica vecchie interazioni", "moves": "Utenti migrati", "follows": "Nuovi seguìti" @@ -668,8 +678,8 @@ "load_all": "Carico tutti i {emojiAmount} emoji", "load_all_hint": "Primi {saneAmount} emoji caricati, caricarli tutti potrebbe causare rallentamenti.", "unicode": "Emoji Unicode", - "custom": "Emoji personale", - "add_emoji": "Inserisci Emoji", + "custom": "Emoji della stanza", + "add_emoji": "Inserisci emoji", "search_emoji": "Cerca un emoji", "keep_open": "Tieni aperto il menù", "emoji": "Emoji", @@ -684,7 +694,7 @@ "remote_user_resolver": "Cerca utenti remoti" }, "errors": { - "storage_unavailable": "Pleroma non ha potuto accedere ai dati del tuo browser. Le tue credenziali o le tue impostazioni locali non potranno essere salvate e potresti incontrare strani errori. Prova ad abilitare i cookie." + "storage_unavailable": "Pleroma non può accedere ai dati del tuo browser. Il tuo accesso o le tue impostazioni non saranno salvate e potresti incontrare strani errori. Prova ad abilitare i cookie." }, "status": { "pinned": "Intestato", diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json index e2de1066..846c793a 100644 --- a/src/i18n/ja_pedantic.json +++ b/src/i18n/ja_pedantic.json @@ -4,7 +4,7 @@ }, "exporter": { "export": "エクスポート", - "processing": "処理中です。処理が完了すると、ファイルをダウンロードするよう指示があります。" + "processing": "処理中です。処理が完了すると、ファイルをダウンロードするよう指示があります" }, "features_panel": { "chat": "チャット", @@ -13,10 +13,12 @@ "scope_options": "公開範囲選択", "text_limit": "文字の数", "title": "有効な機能", - "who_to_follow": "おすすめユーザー" + "who_to_follow": "おすすめユーザー", + "upload_limit": "ファイルサイズの上限", + "pleroma_chat_messages": "Pleroma チャット" }, "finder": { - "error_fetching_user": "ユーザー検索がエラーになりました。", + "error_fetching_user": "ユーザー検索がエラーになりました", "find_user": "ユーザーを探す" }, "general": { @@ -31,7 +33,17 @@ "disable": "無効", "enable": "有効", "confirm": "確認", - "verify": "検査" + "verify": "検査", + "peek": "隠す", + "close": "閉じる", + "dismiss": "無視", + "retry": "もう一度お試し下さい", + "error_retry": "もう一度お試し下さい", + "loading": "読み込み中…", + "role": { + "moderator": "モデレーター", + "admin": "管理者" + } }, "image_cropper": { "crop_picture": "画像を切り抜く", @@ -57,9 +69,9 @@ "enter_recovery_code": "リカバリーコードを入力してください", "enter_two_factor_code": "2段階認証コードを入力してください", "recovery_code": "リカバリーコード", - "heading" : { - "totp" : "2段階認証", - "recovery" : "2段階リカバリー" + "heading": { + "totp": "2段階認証", + "recovery": "2段階リカバリー" } }, "media_modal": { @@ -76,21 +88,29 @@ "dms": "ダイレクトメッセージ", "public_tl": "パブリックタイムライン", "timeline": "タイムライン", - "twkn": "接続しているすべてのネットワーク", + "twkn": "すべてのネットワーク", "user_search": "ユーザーを探す", "search": "検索", "who_to_follow": "おすすめユーザー", - "preferences": "設定" + "preferences": "設定", + "administration": "管理", + "bookmarks": "ブックマーク", + "timelines": "タイムライン", + "chats": "チャット" }, "notifications": { - "broken_favorite": "ステータスが見つかりません。探しています...", + "broken_favorite": "ステータスが見つかりません。探しています…", "favorited_you": "あなたのステータスがお気に入りされました", "followed_you": "フォローされました", "load_older": "古い通知をみる", "notifications": "通知", "read": "読んだ!", "repeated_you": "あなたのステータスがリピートされました", - "no_more_notifications": "通知はありません" + "no_more_notifications": "通知はありません", + "reacted_with": "{0} でリアクションしました", + "migrated_to": "インスタンスを引っ越しました", + "follow_request": "あなたをフォローしたいです", + "error": "通知の取得に失敗しました: {0}" }, "polls": { "add_poll": "投票を追加", @@ -104,7 +124,9 @@ "expiry": "投票期間", "expires_in": "投票は {0} で終了します", "expired": "投票は {0} 前に終了しました", - "not_enough_options": "相異なる選択肢が不足しています" + "not_enough_options": "相異なる選択肢が不足しています", + "votes_count": "{count} 票 | {count} 票", + "people_voted_count": "{count} 人投票 | {count} 人投票" }, "emoji": { "stickers": "ステッカー", @@ -113,7 +135,9 @@ "search_emoji": "絵文字を検索", "add_emoji": "絵文字を挿入", "custom": "カスタム絵文字", - "unicode": "Unicode絵文字" + "unicode": "Unicode絵文字", + "load_all": "全 {emojiAmount} 絵文字を読み込む", + "load_all_hint": "最初の {saneAmount} 絵文字を読み込みました、全て読み込むと重くなる可能性があります。" }, "stickers": { "add_sticker": "ステッカーを追加" @@ -121,7 +145,8 @@ "interactions": { "favs_repeats": "リピートとお気に入り", "follows": "新しいフォロワー", - "load_older": "古いインタラクションを見る" + "load_older": "古いインタラクションを見る", + "moves": "ユーザーの引っ越し" }, "post_status": { "new_status": "投稿する", @@ -142,15 +167,20 @@ "posting": "投稿", "scope_notice": { "public": "この投稿は、誰でも見ることができます", - "private": "この投稿は、あなたのフォロワーだけが、見ることができます。", - "unlisted": "この投稿は、パブリックタイムラインと、接続しているすべてのネットワークには、表示されません。" + "private": "この投稿は、あなたのフォロワーだけが、見ることができます", + "unlisted": "この投稿は、パブリックタイムラインと、接続しているすべてのネットワークには、表示されません" }, "scope": { - "direct": "ダイレクト: メンションされたユーザーのみに届きます。", - "private": "フォロワーげんてい: フォロワーのみに届きます。", - "public": "パブリック: パブリックタイムラインに届きます。", - "unlisted": "アンリステッド: パブリックタイムラインに届きません。" - } + "direct": "ダイレクト: メンションされたユーザーのみに届きます", + "private": "フォロワー限定: フォロワーのみに届きます", + "public": "パブリック: パブリックタイムラインに届きます", + "unlisted": "アンリステッド: パブリックタイムラインに届きません" + }, + "media_description_error": "メディアのアップロードに失敗しました。もう一度お試しください", + "empty_status_error": "投稿内容を入力してください", + "preview_empty": "何もありません", + "preview": "プレビュー", + "media_description": "メディアの説明" }, "registration": { "bio": "プロフィール", @@ -171,7 +201,9 @@ "password_required": "必須", "password_confirmation_required": "必須", "password_confirmation_match": "パスワードが違います" - } + }, + "reason_placeholder": "このインスタンスは、新規登録を手動で受け付けています。\n登録したい理由を、インスタンスの管理者に教えてください。", + "reason": "登録するための目的" }, "selectable_list": { "select_all": "すべて選択" @@ -181,17 +213,17 @@ "security": "セキュリティ", "enter_current_password_to_confirm": "あなたのアイデンティティを証明するため、現在のパスワードを入力してください", "mfa": { - "otp" : "OTP", - "setup_otp" : "OTPのセットアップ", - "wait_pre_setup_otp" : "OTPのプリセット", - "confirm_and_enable" : "OTPの確認と有効化", + "otp": "OTP", + "setup_otp": "OTPのセットアップ", + "wait_pre_setup_otp": "OTPのプリセット", + "confirm_and_enable": "OTPの確認と有効化", "title": "2段階認証", - "generate_new_recovery_codes" : "新しいリカバリーコードを生成", - "warning_of_generate_new_codes" : "新しいリカバリーコードを生成すると、古いコードは使用できなくなります。", - "recovery_codes" : "リカバリーコード。", - "waiting_a_recovery_codes": "バックアップコードを受信しています...", - "recovery_codes_warning" : "コードを紙に書くか、安全な場所に保存してください。そうでなければ、あなたはコードを再び見ることはできません。もし2段階認証アプリのアクセスを喪失し、なおかつ、リカバリーコードもないならば、あなたは自分のアカウントから閉め出されます。", - "authentication_methods" : "認証方法", + "generate_new_recovery_codes": "新しいリカバリーコードを生成", + "warning_of_generate_new_codes": "新しいリカバリーコードを生成すると、古いコードは使用できなくなります。", + "recovery_codes": "リカバリーコード。", + "waiting_a_recovery_codes": "バックアップコードを受信しています…", + "recovery_codes_warning": "コードを紙に書くか、安全な場所に保存してください。そうでなければ、あなたはコードを再び見ることはできません。もし2段階認証アプリのアクセスを喪失し、なおかつ、リカバリーコードもないならば、あなたは自分のアカウントから閉め出されます。", + "authentication_methods": "認証方法", "scan": { "title": "スキャン", "desc": "あなたの2段階認証アプリを使って、このQRコードをスキャンするか、テキストキーを入力してください:", @@ -231,7 +263,7 @@ "data_import_export_tab": "インポートとエクスポート", "default_vis": "デフォルトの公開範囲", "delete_account": "アカウントを消す", - "delete_account_description": "あなたのアカウントとメッセージが、消えます。", + "delete_account_description": "あなたのデータが消えて、アカウントが使えなくなります。", "delete_account_error": "アカウントを消すことが、できなかったかもしれません。インスタンスの管理者に、連絡してください。", "delete_account_instructions": "本当にアカウントを消してもいいなら、パスワードを入力してください。", "discoverable": "検索などのサービスでこのアカウントを見つけることを許可する", @@ -239,12 +271,12 @@ "pad_emoji": "ピッカーから絵文字を挿入するとき、絵文字の両側にスペースを入れる", "export_theme": "保存", "filtering": "フィルタリング", - "filtering_explanation": "これらの言葉を含むすべてのものがミュートされます。1行に1つの言葉を書いてください。", + "filtering_explanation": "これらの言葉を含むすべてのものがミュートされます。1行に1つの言葉を書いてください", "follow_export": "フォローのエクスポート", "follow_export_button": "エクスポート", "follow_export_processing": "お待ちください。まもなくファイルをダウンロードできます。", "follow_import": "フォローのインポート", - "follow_import_error": "フォローのインポートがエラーになりました。", + "follow_import_error": "フォローのインポートがエラーになりました", "follows_imported": "フォローがインポートされました! 少し時間がかかるかもしれません。", "foreground": "フォアグラウンド", "general": "全般", @@ -305,7 +337,7 @@ "profile_background": "プロフィールのバックグラウンド", "profile_banner": "プロフィールバナー", "profile_tab": "プロフィール", - "radii_help": "インターフェースの丸さを設定する。", + "radii_help": "インターフェースの丸さを設定する", "replies_in_timeline": "タイムラインのリプライ", "reply_visibility_all": "すべてのリプライを見る", "reply_visibility_following": "私に宛てられたリプライと、フォローしている人からのリプライを見る", @@ -332,7 +364,7 @@ "streaming": "上までスクロールしたとき、自動的にストリーミングする", "text": "文字", "theme": "テーマ", - "theme_help": "カラーテーマをカスタマイズできます", + "theme_help": "カラーテーマをカスタマイズできます。", "theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、色と透明度をオーバーライドできます。「すべてクリア」ボタンを押すと、すべてのオーバーライドをやめます。", "theme_help_v2_2": "バックグラウンドとテキストのコントラストを表すアイコンがあります。マウスをホバーすると、詳しい説明が出ます。透明な色を使っているときは、最悪の場合のコントラストが示されます。", "tooltipRadius": "ツールチップとアラート", @@ -356,7 +388,24 @@ "save_load_hint": "「残す」オプションをONにすると、テーマを選んだときとロードしたとき、現在の設定を残します。また、テーマをエクスポートするとき、これらのオプションを維持します。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべての設定を保存します。", "reset": "リセット", "clear_all": "すべてクリア", - "clear_opacity": "透明度をクリア" + "clear_opacity": "透明度をクリア", + "help": { + "snapshot_missing": "テーマのスナップショットがありません。思っていた見た目と違うかもしれません。", + "migration_snapshot_ok": "念のために、テーマのスナップショットが読み込まれました。テーマのデータを読み込むことができます。", + "fe_downgraded": "フロントエンドが前のバージョンに戻りました。", + "fe_upgraded": "フロントエンドと一緒に、テーマエンジンが新しくなりました。", + "older_version_imported": "古いフロントエンドで作られたファイルをインポートしました。", + "future_version_imported": "新しいフロントエンドで作られたファイルをインポートしました。", + "v2_imported": "古いフロントエンドのためのファイルをインポートしました。設定した通りにならないかもしれません。", + "upgraded_from_v2": "フロントエンドが新しくなったので、今までの見た目と少し違うかもしれません。", + "snapshot_source_mismatch": "フロントエンドがロールバックと更新を繰り返したため、バージョンが競合しています。", + "migration_napshot_gone": "スナップショットがありません、覚えているものと見た目が違うかもしれません。", + "snapshot_present": "テーマのスナップショットが読み込まれました。設定は上書きされました。代わりとして実データを読み込むことができます。" + }, + "use_source": "新しいバージョン", + "use_snapshot": "古いバージョン", + "load_theme": "テーマの読み込み", + "keep_as_is": "変更しない" }, "common": { "color": "色", @@ -364,9 +413,9 @@ "contrast": { "hint": "コントラストは {ratio} です。{level}。({context})", "level": { - "aa": "AAレベルガイドライン (ミニマル) を満たします", - "aaa": "AAAレベルガイドライン (レコメンデッド) を満たします。", - "bad": "ガイドラインを満たしません。" + "aa": "AAレベルガイドライン (最低限) を満たします", + "aaa": "AAAレベルガイドライン (推奨) を満たします", + "bad": "ガイドラインを満たしません" }, "context": { "18pt": "大きい (18ポイント以上) テキスト", @@ -391,7 +440,27 @@ "borders": "境界", "buttons": "ボタン", "inputs": "インプットフィールド", - "faint_text": "薄いテキスト" + "faint_text": "薄いテキスト", + "alert_neutral": "それ以外", + "chat": { + "border": "境界線", + "outgoing": "送信", + "incoming": "受信" + }, + "tabs": "タブ", + "toggled": "切り替えたとき", + "disabled": "無効なとき", + "selectedMenu": "選択されたメニューアイテム", + "selectedPost": "選択された投稿", + "pressed": "押したとき", + "highlight": "強調された要素", + "icons": "アイコン", + "poll": "投票グラフ", + "wallpaper": "壁紙", + "underlay": "アンダーレイ", + "popover": "ツールチップ、メニュー、ポップオーバー", + "post": "投稿/プロフィール", + "alert_warning": "警告" }, "radii": { "_tab_label": "丸さ" @@ -409,8 +478,8 @@ "always_drop_shadow": "ブラウザーがサポートしていれば、常に {0} が使われます。", "drop_shadow_syntax": "{0} は、{1} パラメーターと {2} キーワードをサポートしていません。", "avatar_inset": "内側の影と外側の影を同時に使うと、透明なアバターの表示が乱れます。", - "spread_zero": "広がりが 0 よりも大きな影は、0 と同じです。", - "inset_classic": "内側の影は {0} を使います。" + "spread_zero": "広がりが 0 よりも大きな影は、0 と同じです", + "inset_classic": "内側の影は {0} を使います" }, "components": { "panel": "パネル", @@ -424,7 +493,8 @@ "buttonPressed": "ボタン (押されているとき)", "buttonPressedHover": "ボタン (ホバー、かつ、押されているとき)", "input": "インプットフィールド" - } + }, + "hintV3": "影の場合は、 {0} 表記を使って他の色スロットを使うこともできます。" }, "fonts": { "_tab_label": "フォント", @@ -445,7 +515,7 @@ "content": "本文", "error": "エラーの例", "button": "ボタン", - "text": "これは{0}と{1}の例です。", + "text": "これは{0}と{1}の例です", "mono": "monospace", "input": "羽田空港に着きました。", "faint_link": "とても助けになるマニュアル", @@ -459,7 +529,52 @@ "title": "バージョン", "backend_version": "バックエンドのバージョン", "frontend_version": "フロントエンドのバージョン" - } + }, + "notification_setting_hide_notification_contents": "送った人と内容を、プッシュ通知に表示しない", + "notification_setting_privacy": "プライバシー", + "notification_setting_block_from_strangers": "フォローしていないユーザーからの通知を拒否する", + "notification_setting_filters": "フィルター", + "fun": "お楽しみ", + "virtual_scrolling": "タイムラインの描画を最適化する", + "type_domains_to_mute": "ミュートしたいドメインを検索", + "useStreamingApiWarning": "(実験中で、投稿を取りこぼすかもしれないので、おすすめしません)", + "useStreamingApi": "投稿と通知を、すぐに受け取る", + "user_mutes": "ユーザー", + "reset_background_confirm": "本当にバックグラウンドを初期化しますか?", + "reset_banner_confirm": "本当にバナーを初期化しますか?", + "reset_avatar_confirm": "本当にアバターを初期化しますか?", + "hide_wallpaper": "インスタンスのバックグラウンドを隠す", + "reset_profile_background": "プロフィールのバックグラウンドを初期化", + "reset_profile_banner": "プロフィールのバナーを初期化", + "reset_avatar": "アバターを初期化", + "notification_visibility_emoji_reactions": "リアクション", + "notification_visibility_moves": "ユーザーの引っ越し", + "new_email": "新しいメールアドレス", + "profile_fields": { + "value": "内容", + "name": "ラベル", + "add_field": "枠を追加", + "label": "プロフィール補足情報" + }, + "accent": "アクセント", + "mutes_imported": "ミュートをインポートしました!少し時間がかかるかもしれません。", + "emoji_reactions_on_timeline": "絵文字リアクションをタイムラインに表示", + "domain_mutes": "ドメイン", + "mutes_and_blocks": "ミュートとブロック", + "chatMessageRadius": "チャットメッセージ", + "change_email_error": "メールアドレスを変えることが、できなかったかもしれません。", + "changed_email": "メールアドレスが、変わりました!", + "change_email": "メールアドレスを変える", + "bot": "これは bot アカウントです", + "mute_export_button": "ミュートをCSVファイルにエクスポートする", + "import_mutes_from_a_csv_file": "CSVファイルからミュートをインポートする", + "mute_import_error": "ミュートのインポートに失敗しました", + "mute_import": "ミュートのインポート", + "mute_export": "ミュートのエクスポート", + "allow_following_move": "フォロー中のアカウントが引っ越したとき、自動フォローを許可する", + "setting_changed": "規定の設定と異なっています", + "greentext": "引用を緑色で表示", + "sensitive_by_default": "はじめから投稿をセンシティブとして設定" }, "time": { "day": "{0}日", @@ -505,7 +620,9 @@ "show_new": "読み込み", "up_to_date": "最新", "no_more_statuses": "これで終わりです", - "no_statuses": "ステータスはありません" + "no_statuses": "ステータスはありません", + "reload": "再読み込み", + "error": "タイムラインの読み込みに失敗しました: {0}" }, "status": { "favorites": "お気に入り", @@ -518,7 +635,21 @@ "reply_to": "返信", "replies_list": "返信:", "mute_conversation": "スレッドをミュート", - "unmute_conversation": "スレッドのミュートを解除" + "unmute_conversation": "スレッドのミュートを解除", + "nsfw": "閲覧注意", + "expand": "広げる", + "status_deleted": "この投稿は削除されました", + "hide_content": "隠す", + "show_content": "見る", + "hide_full_subject": "隠す", + "show_full_subject": "全部見る", + "thread_muted_and_words": "以下の単語を含むため:", + "thread_muted": "ミュートされたスレッド", + "external_source": "外部ソース", + "copy_link": "リンクをコピー", + "status_unavailable": "利用できません", + "unbookmark": "ブックマーク解除", + "bookmark": "ブックマーク" }, "user_card": { "approve": "受け入れ", @@ -539,7 +670,7 @@ "media": "メディア", "mention": "メンション", "mute": "ミュート", - "muted": "ミュートしています!", + "muted": "ミュートしています", "per_day": "/日", "remote_follow": "リモートフォロー", "report": "通報", @@ -547,11 +678,11 @@ "subscribe": "購読", "unsubscribe": "購読を解除", "unblock": "ブロック解除", - "unblock_progress": "ブロックを解除しています...", - "block_progress": "ブロックしています...", + "unblock_progress": "ブロックを解除しています…", + "block_progress": "ブロックしています…", "unmute": "ミュート解除", - "unmute_progress": "ミュートを解除しています...", - "mute_progress": "ミュートしています...", + "unmute_progress": "ミュートを解除しています…", + "mute_progress": "ミュートしています…", "admin_menu": { "moderation": "モデレーション", "grant_admin": "管理者権限を付与", @@ -570,7 +701,16 @@ "quarantine": "他のインスタンスからの投稿を止める", "delete_user": "ユーザーを削除", "delete_user_confirmation": "あなたの精神状態に何か問題はございませんか? この操作を取り消すことはできません。" - } + }, + "roles": { + "moderator": "モデレーター", + "admin": "管理者" + }, + "show_repeats": "リピートを見る", + "hide_repeats": "リピートを隠す", + "message": "メッセージ", + "hidden": "隠す", + "bot": "bot" }, "user_profile": { "timeline_title": "ユーザータイムライン", @@ -595,13 +735,18 @@ "repeat": "リピート", "reply": "返信", "favorite": "お気に入り", - "user_settings": "ユーザー設定" + "user_settings": "ユーザー設定", + "bookmark": "ブックマーク", + "reject_follow_request": "フォローリクエストを拒否", + "accept_follow_request": "フォローリクエストを許可", + "add_reaction": "リアクションを追加" }, - "upload":{ + "upload": { "error": { - "base": "アップロードに失敗しました。", - "file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]", - "default": "しばらくしてから試してください" + "base": "アップロードに失敗しました。", + "file_too_big": "ファイルが大きすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]", + "default": "しばらくしてから試してください", + "message": "アップロードに失敗: {0}" }, "file_size_units": { "B": "B", @@ -626,6 +771,77 @@ "check_email": "パスワードをリセットするためのリンクが記載されたメールが届いているか確認してください。", "return_home": "ホームページに戻る", "too_many_requests": "試行回数の制限に達しました。しばらく時間を置いてから再試行してください。", - "password_reset_disabled": "このインスタンスではパスワードリセットは無効になっています。インスタンスの管理者に連絡してください。" + "password_reset_disabled": "このインスタンスではパスワードリセットは無効になっています。インスタンスの管理者に連絡してください。", + "password_reset_required_but_mailer_is_disabled": "パスワードの初期化が必要ですが、初期化は使えません。インスタンスの管理者に連絡してください。", + "password_reset_required": "ログインするためにパスワードを初期化してください。" + }, + "about": { + "mrf": { + "mrf_policies_desc": "MRFポリシーは、インスタンスの振る舞いを操作します。以下のポリシーが有効になっています:", + "federation": "連合", + "simple": { + "media_nsfw_desc": "このインスタンスでは、以下のインスタンスからの投稿に対して、メディアを閲覧注意に設定します:", + "media_nsfw": "メディアを閲覧注意に設定", + "media_removal_desc": "このインスタンスでは、以下のインスタンスからの投稿に対して、メディアを除去します:", + "media_removal": "メディア除去", + "ftl_removal": "「接続しているすべてのネットワーク」タイムラインから除外", + "ftl_removal_desc": "このインスタンスでは、以下のインスタンスを「接続しているすべてのネットワーク」タイムラインから除外します:", + "quarantine_desc": "このインスタンスでは、以下のインスタンスに対して公開投稿のみを送信します:", + "quarantine": "検疫", + "reject_desc": "このインスタンスでは、以下のインスタンスからのメッセージを受け付けません:", + "accept_desc": "このインスタンスでは、以下のインスタンスからのメッセージのみを受け付けます:", + "accept": "許可", + "simple_policies": "インスタンス固有のポリシー", + "reject": "拒否" + }, + "mrf_policies": "有効なMRFポリシー", + "keyword": { + "replace": "置き換え", + "ftl_removal": "「接続しているすべてのネットワーク」タイムラインから除外", + "keyword_policies": "キーワードポリシー", + "is_replaced_by": "→", + "reject": "拒否" + } + }, + "staff": "スタッフ" + }, + "display_date": { + "today": "今日" + }, + "file_type": { + "file": "ファイル", + "image": "画像", + "video": "ビデオ", + "audio": "オーディオ" + }, + "remote_user_resolver": { + "error": "見つかりませんでした。", + "searching_for": "検索中", + "remote_user_resolver": "リモートユーザーリゾルバ" + }, + "errors": { + "storage_unavailable": "ブラウザのストレージに接続できなかったため、ログインや設定情報は保存されません。Cookieを有効にしてください。" + }, + "shoutbox": { + "title": "Shoutbox" + }, + "chats": { + "empty_chat_list_placeholder": "チャットはありません。新規チャットのボタンを押して始めましょう!", + "error_sending_message": "メッセージの送信に失敗しました。", + "error_loading_chat": "チャットの読み込みに失敗しました。", + "delete_confirm": "このメッセージを本当に消してもいいですか?", + "more": "もっと見る", + "empty_message_error": "メッセージを入力して下さい", + "new": "新規チャット", + "chats": "チャット一覧", + "delete": "削除", + "message_user": "{nickname} にメッセージ", + "you": "あなた:" + }, + "domain_mute_card": { + "unmute_progress": "ミュート解除中…", + "unmute": "ミュート解除", + "mute_progress": "ミュート中…", + "mute": "ミュート" } } diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 0968949b..c8d213f3 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -9,7 +9,9 @@ "scope_options": "범위 옵션", "text_limit": "텍스트 제한", "title": "기능", - "who_to_follow": "팔로우 추천" + "who_to_follow": "팔로우 추천", + "upload_limit": "최대 파일용량", + "pleroma_chat_messages": "Pleroma 채트" }, "finder": { "error_fetching_user": "사용자 정보 불러오기 실패", @@ -17,7 +19,27 @@ }, "general": { "apply": "적용", - "submit": "보내기" + "submit": "보내기", + "loading": "로딩중…", + "peek": "숨기기", + "close": "닫기", + "verify": "검사", + "confirm": "확인", + "enable": "유효", + "disable": "무효", + "cancel": "취소", + "dismiss": "무시", + "show_less": "접기", + "show_more": "더 보기", + "optional": "필수 아님", + "retry": "다시 시도하십시오", + "error_retry": "다시 시도하십시오", + "generic_error": "잘못되었습니다", + "more": "더 보기", + "role": { + "moderator": "중재자", + "admin": "관리자" + } }, "login": { "login": "로그인", @@ -26,10 +48,19 @@ "password": "암호", "placeholder": "예시: lain", "register": "가입", - "username": "사용자 이름" + "username": "사용자 이름", + "heading": { + "recovery": "2단계 복구", + "totp": "2단계인증" + }, + "recovery_code": "복구 코드", + "enter_two_factor_code": "2단계인증 코드를 입력하십시오", + "enter_recovery_code": "복구 코드를 입력하십시오", + "authentication_code": "인증 코드", + "hint": "로그인하여 대화에 참가합시다" }, "nav": { - "about": "About", + "about": "인스턴스 소개", "back": "뒤로", "chat": "로컬 챗", "friend_requests": "팔로우 요청", @@ -37,18 +68,29 @@ "dms": "다이렉트 메시지", "public_tl": "공개 타임라인", "timeline": "타임라인", - "twkn": "모든 알려진 네트워크", + "twkn": "알려진 네트워크", "user_search": "사용자 검색", - "preferences": "환경설정" + "preferences": "환경설정", + "chats": "채트", + "timelines": "타임라인", + "who_to_follow": "추천된 사용자", + "search": "검색", + "bookmarks": "북마크", + "interactions": "대화", + "administration": "관리" }, "notifications": { - "broken_favorite": "알 수 없는 게시물입니다, 검색 합니다...", + "broken_favorite": "알 수 없는 게시물입니다, 검색합니다…", "favorited_you": "당신의 게시물을 즐겨찾기", "followed_you": "당신을 팔로우", "load_older": "오래 된 알림 불러오기", "notifications": "알림", "read": "읽음!", - "repeated_you": "당신의 게시물을 리핏" + "repeated_you": "당신의 게시물을 리핏", + "no_more_notifications": "알림이 없습니다", + "migrated_to": "이사했습니다", + "reacted_with": "{0} 로 반응했습니다", + "error": "알림 불러오기 실패: {0}" }, "post_status": { "new_status": "새 게시물 게시", @@ -56,10 +98,13 @@ "account_not_locked_warning_link": "잠김", "attachments_sensitive": "첨부물을 민감함으로 설정", "content_type": { - "text/plain": "평문" + "text/plain": "평문", + "text/bbcode": "BBCode", + "text/markdown": "Markdown", + "text/html": "HTML" }, "content_warning": "주제 (필수 아님)", - "default": "LA에 도착!", + "default": "인천공항에 도착했습니다.", "direct_warning": "이 게시물을 멘션 된 사용자들에게만 보여집니다", "posting": "게시", "scope": { @@ -67,7 +112,15 @@ "private": "팔로워 전용 - 팔로워들에게만", "public": "공개 - 공개 타임라인으로", "unlisted": "비공개 - 공개 타임라인에 게시 안 함" - } + }, + "preview_empty": "아무것도 없습니다", + "preview": "미리보기", + "scope_notice": { + "public": "이 글은 누구나 볼 수 있습니다" + }, + "media_description_error": "파일을 올리지 못하였습니다. 다시한번 시도하여 주십시오", + "empty_status_error": "글을 입력하십시오", + "media_description": "첨부파일 설명" }, "registration": { "bio": "소개", @@ -85,7 +138,9 @@ "password_required": "공백으로 둘 수 없습니다", "password_confirmation_required": "공백으로 둘 수 없습니다", "password_confirmation_match": "패스워드와 일치해야 합니다" - } + }, + "fullname_placeholder": "예: 김례인", + "username_placeholder": "예: lain" }, "settings": { "attachmentRadius": "첨부물", @@ -112,7 +167,7 @@ "data_import_export_tab": "데이터 불러오기 / 내보내기", "default_vis": "기본 공개 범위", "delete_account": "계정 삭제", - "delete_account_description": "계정과 메시지를 영구히 삭제.", + "delete_account_description": "데이터가 영구히 삭제되고 계정이 불활성화됩니다.", "delete_account_error": "계정을 삭제하는데 문제가 있습니다. 계속 발생한다면 인스턴스 관리자에게 문의하세요.", "delete_account_instructions": "계정 삭제를 확인하기 위해 아래에 패스워드 입력.", "export_theme": "프리셋 저장", @@ -156,7 +211,7 @@ "notification_visibility_repeats": "반복", "no_rich_text_description": "모든 게시물의 서식을 지우기", "hide_follows_description": "내가 팔로우하는 사람을 표시하지 않음", - "hide_followers_description": "나를 따르는 사람을 보여주지 마라.", + "hide_followers_description": "나를 따르는 사람을 숨기기", "nsfw_clickthrough": "NSFW 이미지 \"클릭해서 보이기\"를 활성화", "oauth_tokens": "OAuth 토큰", "token": "토큰", @@ -247,7 +302,16 @@ "borders": "테두리", "buttons": "버튼", "inputs": "입력칸", - "faint_text": "흐려진 텍스트" + "faint_text": "흐려진 텍스트", + "chat": { + "border": "경계선", + "outgoing": "송신", + "incoming": "수신" + }, + "selectedMenu": "선택된 메뉴 요소", + "selectedPost": "선택된 글", + "icons": "아이콘", + "alert_warning": "경고" }, "radii": { "_tab_label": "둥글기" @@ -303,14 +367,45 @@ "button": "버튼", "text": "더 많은 {0} 그리고 {1}", "mono": "내용", - "input": "LA에 막 도착!", + "input": "인천공항에 도착했습니다.", "faint_link": "도움 되는 설명서", "fine_print": "우리의 {0} 를 읽고 도움 되지 않는 것들을 배우자!", "header_faint": "이건 괜찮아", "checkbox": "나는 약관을 대충 훑어보았습니다", "link": "작고 귀여운 링크" } - } + }, + "block_export": "차단 목록 내보내기", + "mfa": { + "scan": { + "secret_code": "키", + "title": "스캔" + }, + "authentication_methods": "인증 방법", + "waiting_a_recovery_codes": "예비 코드를 수신하고 있습니다…", + "recovery_codes": "복구 코드.", + "generate_new_recovery_codes": "새로운 복구 코드를 작성", + "title": "2단계인증", + "confirm_and_enable": "OTP 확인과 활성화", + "setup_otp": "OTP 설치", + "otp": "OTP" + }, + "security": "보안", + "emoji_reactions_on_timeline": "이모지 반응을 타임라인으로 표시", + "avatar_size_instruction": "크기를 150x150 이상으로 설정할 것을 추장합니다.", + "blocks_tab": "차단", + "notification_setting_privacy": "보안", + "user_mutes": "사용자", + "notification_visibility_emoji_reactions": "반응", + "profile_fields": { + "value": "내용" + }, + "mutes_and_blocks": "침묵과 차단", + "chatMessageRadius": "챗 메시지", + "change_email": "전자메일 주소 바꾸기", + "changed_email": "메일주소가 갱신되었습니다!", + "bot": "이 계정은 bot입니다", + "mutes_tab": "침묵" }, "timeline": { "collapse": "접기", @@ -339,7 +434,7 @@ "its_you": "당신입니다!", "mute": "침묵", "muted": "침묵 됨", - "per_day": " / 하루", + "per_day": "/ 하루", "remote_follow": "원격 팔로우", "statuses": "게시물" }, @@ -357,11 +452,11 @@ "favorite": "즐겨찾기", "user_settings": "사용자 설정" }, - "upload":{ + "upload": { "error": { - "base": "업로드 실패.", - "file_too_big": "파일이 너무 커요 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", - "default": "잠시 후에 다시 시도해 보세요" + "base": "업로드 실패.", + "file_too_big": "파일이 너무 커요 [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", + "default": "잠시 후에 다시 시도해 보세요" }, "file_size_units": { "B": "바이트", @@ -370,5 +465,122 @@ "GiB": "기비바이트", "TiB": "테비바이트" } + }, + "interactions": { + "follows": "새 팔로워", + "favs_repeats": "반복과 즐겨찾기" + }, + "emoji": { + "load_all": "전체 {emojiAmount} 이모지 불러오기", + "unicode": "Unicode 이모지", + "custom": "전용 이모지", + "add_emoji": "이모지 넣기", + "search_emoji": "이모지 검색", + "emoji": "이모지", + "stickers": "스티커" + }, + "polls": { + "add_poll": "투표를 추가", + "votes": "표", + "vote": "투표", + "type": "투표 형식", + "expiry": "투표 기간", + "votes_count": "{count} 표 | {count} 표", + "people_voted_count": "{count} 명 투표 | {count} 명 투표", + "option": "선택지", + "add_option": "선택지 추가" + }, + "media_modal": { + "next": "다음", + "previous": "이전" + }, + "importer": { + "error": "이 파일을 가져올 때 오류가 발생하였습니다.", + "success": "정상히 불러왔습니다.", + "submit": "보내기" + }, + "image_cropper": { + "cancel": "취소", + "save_without_cropping": "그대로 저장", + "save": "저장", + "crop_picture": "사진 자르기" + }, + "exporter": { + "processing": "처리중입니다, 처리가 끝나면 파일을 다운로드하라는 지시가 있겠습니다", + "export": "내보내기" + }, + "domain_mute_card": { + "unmute_progress": "침묵을 해제중…", + "unmute": "침묵 해제", + "mute_progress": "침묵으로 설정중…", + "mute": "침묵" + }, + "about": { + "staff": "운영자", + "mrf": { + "simple": { + "media_nsfw_desc": "이 인스턴스에서는 아래의 인스턴스로부터 보내온 투고에 붙혀 있는 매체는 민감함으로 설정됩니다:", + "media_nsfw": "매체를 민감함으로 설정", + "media_removal_desc": "이 인스턴스에서는 아래의 인스턴스로부터 보내온 투고에 붙혀 있는 매체는 제거됩니다:", + "media_removal": "매체 제거", + "ftl_removal_desc": "이 인스턴스에서 아래의 인스턴스들은 \"알려진 모든 네트워크\" 타임라인에서 제외됩니다:", + "ftl_removal": "\"알려진 모든 네트워크\" 타임라인에서 제외", + "quarantine_desc": "이 인스턴스는 아래의 인스턴스에게 공개투고만을 보냅니다:", + "quarantine": "검역", + "reject_desc": "이 인스턴스에서는 아래의 인스턴스로부터 보내온 투고를 받아들이지 않습니다:", + "accept_desc": "이 인스턴스에서는 아래의 인스턴스로부터 보내온 투고만이 접수됩니다:", + "reject": "거부", + "accept": "허가", + "simple_policies": "인스턴스 특유의 폴리시" + }, + "mrf_policies": "사용되는 MRF 폴리시", + "keyword": { + "is_replaced_by": "→", + "replace": "바꾸기", + "reject": "거부", + "ftl_removal": "\"알려진 모든 네트워크\" 타임라인에서 제외", + "keyword_policies": "단어 폴리시" + }, + "federation": "연합" + } + }, + "shoutbox": { + "title": "Shoutbox" + }, + "time": { + "years_short": "{0} 년", + "year_short": "{0} 년", + "years": "{0} 년", + "year": "{0} 년", + "weeks_short": "{0} 주일", + "week_short": "{0} 주일", + "weeks": "{0} 주일", + "week": "{0} 주일", + "seconds_short": "{0} 초", + "second_short": "{0} 초", + "seconds": "{0} 초", + "second": "{0} 초", + "now_short": "방금", + "now": "방끔", + "months_short": "{0} 달 전", + "month_short": "{0} 달 전", + "months": "{0} 달 전", + "month": "{0} 달 전", + "minutes_short": "{0} 분", + "minute_short": "{0} 분", + "minutes": "{0} 분", + "minute": "{0} 분", + "in_past": "{0} 전", + "hours_short": "{0} 시간", + "hour_short": "{0} 시간", + "hours": "{0} 시간", + "hour": "{0} 시간", + "days_short": "{0} 일", + "day_short": "{0} 일", + "days": "{0} 일", + "day": "{0} 일" + }, + "remote_user_resolver": { + "error": "찾을 수 없습니다." } } diff --git a/src/i18n/nb.json b/src/i18n/nb.json index b9669a35..8bd745f0 100644 --- a/src/i18n/nb.json +++ b/src/i18n/nb.json @@ -57,9 +57,9 @@ "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" + "heading": { + "totp": "To-faktors autentisering", + "recovery": "To-faktors gjenoppretting" } }, "media_modal": { @@ -72,7 +72,7 @@ "chat": "Lokal nettprat", "friend_requests": "Følgeforespørsler", "mentions": "Nevnt", - "interactions": "Interaksjooner", + "interactions": "Interaksjoner", "dms": "Direktemeldinger", "public_tl": "Offentlig Tidslinje", "timeline": "Tidslinje", @@ -80,7 +80,9 @@ "user_search": "Søk etter brukere", "search": "Søk", "who_to_follow": "Kontoer å følge", - "preferences": "Innstillinger" + "preferences": "Innstillinger", + "timelines": "Tidslinjer", + "bookmarks": "Bokmerker" }, "notifications": { "broken_favorite": "Ukjent status, leter etter den...", @@ -90,7 +92,8 @@ "notifications": "Varslinger", "read": "Les!", "repeated_you": "Gjentok din status", - "no_more_notifications": "Ingen gjenstående varsler" + "no_more_notifications": "Ingen gjenstående varsler", + "follow_request": "ønsker å følge deg" }, "polls": { "add_poll": "Legg til undersøkelse", @@ -134,7 +137,7 @@ "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", @@ -171,17 +174,17 @@ "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", + "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.", + "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", + "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", @@ -579,7 +582,7 @@ "favorite": "Lik", "user_settings": "Brukerinnstillinger" }, - "upload":{ + "upload": { "error": { "base": "Det oppsto en feil under opplastning.", "file_too_big": "Fil for stor [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 1b8694d9..841516c0 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -5,37 +5,66 @@ "features_panel": { "chat": "Chat", "gopher": "Gopher", - "media_proxy": "Proxy de mídia", + "media_proxy": "Proxy de multimédia", "scope_options": "Opções de privacidade", "text_limit": "Limite de caracteres", - "title": "Funções", - "who_to_follow": "Quem seguir" + "title": "Características", + "who_to_follow": "Quem seguir", + "upload_limit": "Limite de carregamento", + "pleroma_chat_messages": "Chat do Pleroma" }, "finder": { - "error_fetching_user": "Erro ao procurar usuário", - "find_user": "Buscar usuário" + "error_fetching_user": "Erro ao pesquisar utilizador", + "find_user": "Pesquisar utilizador" }, "general": { "apply": "Aplicar", "submit": "Enviar", "more": "Mais", - "generic_error": "Houve um erro", - "optional": "opcional" + "generic_error": "Ocorreu um erro", + "optional": "opcional", + "peek": "Espreitar", + "close": "Fechar", + "verify": "Verificar", + "confirm": "Confirmar", + "enable": "Ativar", + "disable": "Desativar", + "cancel": "Cancelar", + "show_less": "Mostrar menos", + "show_more": "Mostrar mais", + "retry": "Tenta novamente", + "error_retry": "Por favor, tenta novamente", + "loading": "A carregar…", + "dismiss": "Ignorar", + "role": + { + "moderator": "Moderador", + "admin": "Admin" + } }, "image_cropper": { "crop_picture": "Cortar imagem", - "save": "Salvar", - "cancel": "Cancelar" + "save": "Guardar", + "cancel": "Cancelar", + "save_without_cropping": "Guardar sem recortar" }, "login": { - "login": "Entrar", - "description": "Entrar com OAuth", - "logout": "Sair", - "password": "Senha", - "placeholder": "p.e. lain", - "register": "Registrar", - "username": "Usuário", - "hint": "Entre para participar da discussão" + "login": "Iniciar Sessão", + "description": "Iniciar sessão com OAuth", + "logout": "Terminar sessão", + "password": "Palavra-passe", + "placeholder": "ex. lain", + "register": "Registar", + "username": "Nome de Utilizador", + "hint": "Entra para participar na discussão", + "heading": { + "totp": "Autenticação de dois fatores", + "recovery": "Recuperação de dois fatores" + }, + "recovery_code": "Código de recuperação", + "authentication_code": "Código de autenticação", + "enter_two_factor_code": "Introduza o código de dois fatores", + "enter_recovery_code": "Introduza um código de recuperação" }, "media_modal": { "previous": "Anterior", @@ -45,100 +74,125 @@ "about": "Sobre", "back": "Voltar", "chat": "Chat local", - "friend_requests": "Solicitações de seguidores", + "friend_requests": "Pedidos de seguidores", "mentions": "Menções", - "dms": "Mensagens diretas", - "public_tl": "Linha do tempo pública", - "timeline": "Linha do tempo", - "twkn": "Toda a rede conhecida", - "user_search": "Buscar usuários", + "dms": "Mensagens Diretas", + "public_tl": "Cronologia Pública", + "timeline": "Cronologia", + "twkn": "Rede conhecida", + "user_search": "Pesquisa por Utilizadores", "who_to_follow": "Quem seguir", - "preferences": "Preferências" + "preferences": "Preferências", + "search": "Pesquisar", + "interactions": "Interações", + "administration": "Administração", + "chats": "Salas de Chat", + "timelines": "Cronologias", + "bookmarks": "Itens Guardados" }, "notifications": { - "broken_favorite": "Status desconhecido, buscando...", - "favorited_you": "favoritou sua postagem", - "followed_you": "seguiu você", + "broken_favorite": "Publicação desconhecida, a procurar…", + "favorited_you": "gostou do teu post", + "followed_you": "seguiu-te", "load_older": "Carregar notificações antigas", "notifications": "Notificações", "read": "Lido!", - "repeated_you": "repetiu sua postagem", - "no_more_notifications": "Mais nenhuma notificação" + "repeated_you": "partilhou o teu post", + "no_more_notifications": "Sem mais notificações", + "reacted_with": "reagiu com {0}", + "migrated_to": "migrou para", + "follow_request": "quer seguir-te", + "error": "Erro ao obter notificações: {0}" }, "post_status": { - "new_status": "Postar novo status", - "account_not_locked_warning": "Sua conta não é {0}. Qualquer pessoa pode te seguir e ver seus posts privados (só para seguidores).", - "account_not_locked_warning_link": "restrita", + "new_status": "Publicar nova publicação", + "account_not_locked_warning": "A sua conta não é {0}. Qualquer pessoa pode seguir-te e ver os seus posts privados (só para seguidores).", + "account_not_locked_warning_link": "restrito", "attachments_sensitive": "Marcar anexos como sensíveis", "content_type": { - "text/plain": "Texto puro" + "text/plain": "Texto puro", + "text/bbcode": "BBCode", + "text/html": "HTML", + "text/markdown": "Remarcação" }, "content_warning": "Assunto (opcional)", - "default": "Acabei de chegar no Rio!", + "default": "Acabei de chegar a Lisboa.", "direct_warning": "Este post será visível apenas para os usuários mencionados.", - "posting": "Publicando", + "posting": "A publicar", "scope": { "direct": "Direto - Enviar somente aos usuários mencionados", "private": "Apenas para seguidores - Enviar apenas para seguidores", - "public": "Público - Enviar a linhas do tempo públicas", - "unlisted": "Não listado - Não enviar a linhas do tempo públicas" - } + "public": "Público - Publicar em cronologias públicas", + "unlisted": "Não listado - Não exibir em cronologias públicas" + }, + "scope_notice": { + "unlisted": "Esta publicação não será visível na Cronologia pública e na Rede conhecida por todos", + "private": "Esta publicação será apenas visível para os teus seguidores", + "public": "Esta publicação será visível para todos" + }, + "empty_status_error": "Não consegues publicar um post vazio e sem ficheiros", + "preview_empty": "Vazio", + "preview": "Pré-visualização", + "media_description": "Descrição da multimédia", + "media_description_error": "Falha ao atualizar ficheiro, tente novamente", + "direct_warning_to_first_only": "Esta publicação só será visível para os utilizadores mencionados no início da mensagem.", + "direct_warning_to_all": "Esta publicação será visível para todos os utilizadores mencionados." }, "registration": { "bio": "Biografia", - "email": "Correio eletrônico", + "email": "Endereço de e-mail", "fullname": "Nome para exibição", - "password_confirm": "Confirmação de senha", - "registration": "Registro", + "password_confirm": "Confirmação de palavra-passe", + "registration": "Registo", "token": "Código do convite", "captcha": "CAPTCHA", "new_captcha": "Clique na imagem para carregar um novo captcha", - "username_placeholder": "p. ex. lain", - "fullname_placeholder": "p. ex. Lain Iwakura", - "bio_placeholder": "e.g.\nOi, sou Lain\nSou uma garota que vive no subúrbio do Japão. Você deve me conhecer da Rede.", + "username_placeholder": "ex. lain", + "fullname_placeholder": "ex. Lain Iwakura", + "bio_placeholder": "ex.\nOlá, sou a Lain\nSou uma menina de anime que vive no Japão suburbano. Devem conhecer-me do \"the Wired\".", "validations": { "username_required": "não pode ser deixado em branco", "fullname_required": "não pode ser deixado em branco", "email_required": "não pode ser deixado em branco", "password_required": "não pode ser deixado em branco", "password_confirmation_required": "não pode ser deixado em branco", - "password_confirmation_match": "deve ser idêntica à senha" + "password_confirmation_match": "deve corresponder à palavra-passe" } }, "settings": { - "app_name": "Nome do aplicativo", + "app_name": "Nome da aplicação", "attachmentRadius": "Anexos", "attachments": "Anexos", "avatar": "Avatar", "avatarAltRadius": "Avatares (Notificações)", "avatarRadius": "Avatares", - "background": "Pano de Fundo", + "background": "Imagem de Fundo", "bio": "Biografia", "blocks_tab": "Bloqueios", "btnRadius": "Botões", "cBlue": "Azul (Responder, seguir)", - "cGreen": "Verde (Repetir)", + "cGreen": "Verde (Partilhar)", "cOrange": "Laranja (Favoritar)", "cRed": "Vermelho (Cancelar)", - "change_password": "Mudar senha", - "change_password_error": "Houve um erro ao modificar sua senha.", - "changed_password": "Senha modificada com sucesso!", + "change_password": "Mudar palavra-passe", + "change_password_error": "Ocorreu um erro ao modificar a sua palavra-passe.", + "changed_password": "Palavra-passe modificada com sucesso!", "collapse_subject": "Esconder posts com assunto", "composing": "Escrita", - "confirm_new_password": "Confirmar nova senha", + "confirm_new_password": "Confirmar nova palavra-passe", "current_avatar": "Seu avatar atual", - "current_password": "Sua senha atual", + "current_password": "Palavra-passe atual", "current_profile_banner": "Sua capa de perfil atual", "data_import_export_tab": "Importação/exportação de dados", "default_vis": "Opção de privacidade padrão", - "delete_account": "Deletar conta", - "delete_account_description": "Deletar sua conta e mensagens permanentemente.", - "delete_account_error": "Houve um problema ao deletar sua conta. Se ele persistir, por favor entre em contato com o/a administrador/a da instância.", - "delete_account_instructions": "Digite sua senha no campo abaixo para confirmar a exclusão da conta.", + "delete_account": "Eliminar conta", + "delete_account_description": "Apagar os seus dados permanentemente e desativar a sua conta.", + "delete_account_error": "Ocorreu um erro ao remover a sua conta. Se este persistir, por favor entre em contato com o/a administrador/a da instância.", + "delete_account_instructions": "Escreva a sua palavra-passe no campo abaixo para confirmar a remoção da conta.", "avatar_size_instruction": "O tamanho mínimo recomendado para imagens de avatar é 150x150 pixels.", - "export_theme": "Salvar predefinições", + "export_theme": "Guardar predefinições", "filtering": "Filtragem", - "filtering_explanation": "Todas as postagens contendo estas palavras serão silenciadas; uma palavra por linha.", + "filtering_explanation": "Todas as publicações que contenham estas palavras serão silenciadas; uma palavra por linha", "follow_export": "Exportar quem você segue", "follow_export_button": "Exportar quem você segue para um arquivo CSV", "follow_export_processing": "Processando. Em breve você receberá a solicitação de download do arquivo", @@ -148,7 +202,7 @@ "foreground": "Primeiro Plano", "general": "Geral", "hide_attachments_in_convo": "Ocultar anexos em conversas", - "hide_attachments_in_tl": "Ocultar anexos na linha do tempo.", + "hide_attachments_in_tl": "Ocultar anexos na cronologia", "max_thumbnails": "Número máximo de miniaturas por post", "hide_isp": "Esconder painel específico da instância", "preload_images": "Pré-carregar imagens", @@ -159,7 +213,7 @@ "import_followers_from_a_csv_file": "Importe seguidores a partir de um arquivo CSV", "import_theme": "Carregar pré-definição", "inputRadius": "Campos de entrada", - "checkboxRadius": "Checkboxes", + "checkboxRadius": "Caixas de seleção", "instance_default": "(padrão: {value})", "instance_default_simple": "(padrão)", "interface": "Interface", @@ -171,16 +225,16 @@ "loop_video": "Repetir vídeos", "loop_video_silent_only": "Repetir apenas vídeos sem som (como os \"gifs\" do Mastodon)", "mutes_tab": "Silenciados", - "play_videos_in_modal": "Tocar vídeos diretamente no visualizador de mídia", + "play_videos_in_modal": "Reproduzir vídeos diretamente no visualizador de multimédia", "use_contain_fit": "Não cortar o anexo na miniatura", "name": "Nome", "name_bio": "Nome & Biografia", - "new_password": "Nova senha", + "new_password": "Nova palavra-passe", "notification_visibility": "Tipos de notificação para mostrar", "notification_visibility_follows": "Seguidas", "notification_visibility_likes": "Favoritos", "notification_visibility_mentions": "Menções", - "notification_visibility_repeats": "Repetições", + "notification_visibility_repeats": "Partilhas", "no_rich_text_description": "Remover formatação de todos os posts", "no_blocks": "Sem bloqueios", "no_mutes": "Sem silenciados", @@ -188,7 +242,7 @@ "hide_followers_description": "Não mostrar quem me segue", "show_admin_badge": "Mostrar título de Administrador em meu perfil", "show_moderator_badge": "Mostrar título de Moderador em meu perfil", - "nsfw_clickthrough": "Habilitar clique para ocultar anexos sensíveis", + "nsfw_clickthrough": "Ativar clique em anexos e pré-visualizações de links para ocultar anexos NSFW", "oauth_tokens": "Token OAuth", "token": "Token", "refresh_token": "Atualizar Token", @@ -201,7 +255,7 @@ "profile_banner": "Capa de perfil", "profile_tab": "Perfil", "radii_help": "Arredondar arestas da interface (em pixel)", - "replies_in_timeline": "Respostas na linha do tempo", + "replies_in_timeline": "Respostas na cronologia", "reply_visibility_all": "Mostrar todas as respostas", "reply_visibility_following": "Só mostrar respostas direcionadas a mim ou a usuários que sigo", "reply_visibility_self": "Só mostrar respostas direcionadas a mim", @@ -215,7 +269,7 @@ "settings": "Configurações", "subject_input_always_show": "Sempre mostrar campo de assunto", "subject_line_behavior": "Copiar assunto ao responder", - "subject_line_email": "Como em email: \"re: assunto\"", + "subject_line_email": "Como num e-mail: \"re: assunto\"", "subject_line_mastodon": "Como o Mastodon: copiar como está", "subject_line_noop": "Não copiar", "post_status_content_type": "Tipo de conteúdo do status", @@ -225,7 +279,7 @@ "theme": "Tema", "theme_help": "Use cores em código hexadecimal (#rrggbb) para personalizar seu esquema de cores.", "theme_help_v2_1": "Você também pode sobrescrever as cores e opacidade de alguns componentes ao modificar o checkbox, use \"Limpar todos\" para limpar todas as modificações.", - "theme_help_v2_2": "Alguns ícones sob registros são indicadores de fundo/contraste de textos, passe por cima para informações detalhadas. Tenha ciência de que os indicadores de contraste não funcionam muito bem com transparência.", + "theme_help_v2_2": "Alguns ícones em registo são indicadores de fundo/contraste de textos, passe por cima para obter informações detalhadas. Tenha em atenção que os indicadores de contraste não funcionam muito bem com transparência.", "tooltipRadius": "Dicas/alertas", "upload_a_photo": "Enviar uma foto", "user_settings": "Configurações de Usuário", @@ -245,7 +299,24 @@ "save_load_hint": "Manter as opções preserva as opções atuais ao selecionar ou carregar temas; também salva as opções ao exportar um tempo. Quanto todos os campos estiverem desmarcados, tudo será salvo ao exportar o tema.", "reset": "Restaurar o padrão", "clear_all": "Limpar tudo", - "clear_opacity": "Limpar opacidade" + "clear_opacity": "Limpar opacidade", + "help": { + "upgraded_from_v2": "O PleromaFE foi atualizado, a aparência do tema poderá ser um pouco diferente.", + "snapshot_source_mismatch": "Conflito de versões: o mais provável é que o FE tenha revertido e voltado a atualizar, foi alterado o tema numa versão anterior do FE, o mais provável é desejar utilizar a versão anterior; caso contrário, utilize a nova versão.", + "migration_napshot_gone": "Por algum motivo, a pré-visualização estava em falta, algumas coisas poderão parecer diferentes do que se lembra.", + "migration_snapshot_ok": "Para estar seguro, foi carregada uma versão de pré-visualização do tema. Pode tentar carregar dados do tema.", + "fe_downgraded": "Versão do PleromaFE revertida.", + "fe_upgraded": "O criador de temas do PleromaFE foi atualizado depois da atualização da versão.", + "snapshot_missing": "Não existia nenhuma pré-visualização do tema no ficheiro, então pode parecer diferente do previsto originalmente.", + "snapshot_present": "Foi carregada uma pré-visualização do tema, todos os valores são substituídos. Caso contrário, pode carregar o tema completo.", + "older_version_imported": "O ficheiro que importaste foi criado numa versão antiga do FE.", + "future_version_imported": "O ficheiro que importaste foi criado para uma versão mais recente do FE.", + "v2_imported": "O ficheiro que importaste foi feito para uma versão antiga do FE. Tentamos maximizar a compatibilidade, porém, poderão existir incongruências." + }, + "use_source": "Nova versão", + "use_snapshot": "Versão antiga", + "keep_as_is": "Manter como está", + "load_theme": "Carregar tema" }, "common": { "color": "Cor", @@ -280,7 +351,27 @@ "borders": "Bordas", "buttons": "Botões", "inputs": "Caixas de entrada", - "faint_text": "Texto esmaecido" + "faint_text": "Texto esmaecido", + "chat": { + "border": "Borda", + "outgoing": "Enviadas", + "incoming": "Recebidas" + }, + "tabs": "Abas", + "toggled": "Alternado", + "disabled": "Desativado", + "selectedMenu": "Elemento do menu seleccionado", + "selectedPost": "Publicação seleccionada", + "pressed": "Pressionado", + "highlight": "Elementos destacados", + "icons": "Ícones", + "poll": "Gráfico da sondagem", + "wallpaper": "Fundo de ecrã", + "underlay": "Sublinhado", + "popover": "Sugestões, menus, etiquetas", + "post": "Publicações/Bios", + "alert_neutral": "Neutro", + "alert_warning": "Precaução" }, "radii": { "_tab_label": "Arredondado" @@ -298,7 +389,7 @@ "always_drop_shadow": "Atenção, esta sombra sempre utiliza {0} quando compatível com o navegador.", "drop_shadow_syntax": "{0} não é compatível com o parâmetro {1} e a palavra-chave {2}.", "avatar_inset": "Tenha em mente que combinar as sombras de inserção e a não-inserção em avatares pode causar resultados inesperados em avatares transparentes.", - "spread_zero": "Sombras com uma difusão > 0 aparecerão como se fossem definidas como 0.", + "spread_zero": "Sombras com difusão > 0 aparecerão como se fossem definidas como zero", "inset_classic": "Sombras de inserção utilizarão {0}" }, "components": { @@ -313,7 +404,8 @@ "buttonPressed": "Botão (pressionado)", "buttonPressedHover": "Botão (pressionado+em cima)", "input": "Campo de entrada" - } + }, + "hintV3": "Para as sombras, também pode usar a notação {0} para usar outro espaço de cor." }, "fonts": { "_tab_label": "Fontes", @@ -336,30 +428,143 @@ "button": "Botão", "text": "Vários {0} e {1}", "mono": "conteúdo", - "input": "Acabei de chegar no Rio!", + "input": "Acabei de chegar a Lisboa.", "faint_link": "manual útil", "fine_print": "Leia nosso {0} para não aprender nada!", - "header_faint": "Está ok!", + "header_faint": "Isto está bem", "checkbox": "Li os termos e condições", "link": "um belo link" } - } + }, + "mfa": { + "scan": { + "secret_code": "Chave", + "title": "Scan", + "desc": "Utilizando a sua aplicação de dois fatores, faça scan deste código QR ou insira a chave de texto:" + }, + "authentication_methods": "Métodos de autenticação", + "recovery_codes": "Códigos de recuperação.", + "generate_new_recovery_codes": "Gerar novos códigos de recuperação", + "confirm_and_enable": "Confirmar e ativar a palavra-passe de utilização única", + "otp": "Palavra-passe de utilização única", + "verify": { + "desc": "Para ativar a autenticação de dois fatores, introduza o código da sua aplicação de dois fatores:" + }, + "recovery_codes_warning": "Anote os códigos ou armazene-os num lugar seguro - caso contrário, não os voltará a ver. Se perder acesso à sua aplicação de dois fatores e aos códigos de recuperação, a sua conta ficará bloqueada.", + "waiting_a_recovery_codes": "A receber códigos de recuperação…", + "warning_of_generate_new_codes": "Quando gera novos códigos de recuperação, os antigos deixam de funcionar.", + "title": "Autenticação de Dois Fatores", + "wait_pre_setup_otp": "pré-configuração de palavra-passe de utilização única", + "setup_otp": "Configurar palavra-passe de utilização única" + }, + "security": "Segurança", + "mute_import_error": "Erro ao importar os silenciados", + "mute_import": "Importar silenciados", + "mute_export_button": "Exporta os silenciados para um ficheiro csv", + "mute_export": "Exportar silenciados", + "blocks_imported": "Lista de utilizadores bloqueados importada! O processo pode demorar alguns instantes.", + "block_import_error": "Erro ao importar a lista de utilizadores bloqueados", + "block_import": "Importar utilizadores bloqueados", + "block_export_button": "Exporta a tua lista de utilizadores bloqueados para um ficheiro csv", + "block_export": "Exportar utilizadores bloqueados", + "enter_current_password_to_confirm": "Introduza a sua palavra-passe atual para confirmar a sua identidade", + "mutes_and_blocks": "Silenciados e Bloqueados", + "chatMessageRadius": "Mensagem de texto", + "changed_email": "Endereço de e-mail modificado com sucesso!", + "change_email_error": "Ocorreu um erro ao modificar o seu endereço de e-mail.", + "change_email": "Mudar Endereço de E-mail", + "bot": "Esta uma conta robô", + "import_mutes_from_a_csv_file": "Importar silenciados de um ficheiro csv", + "mutes_imported": "Silenciados importados! Processá-los pode demorar alguns instantes.", + "allow_following_move": "Permitir seguimento automático quando a conta for migrada para outra instância", + "domain_mutes": "Domínios", + "discoverable": "Permitir a descoberta desta conta em resultados de busca e outros serviços", + "emoji_reactions_on_timeline": "Mostrar reações de emoji na timeline", + "hide_muted_posts": "Esconder posts de utilizadores silenciados", + "hide_follows_count_description": "Não mostrar o número de contas seguidas", + "hide_followers_count_description": "Não mostrar o número de seguidores", + "notification_visibility_emoji_reactions": "Reações", + "new_email": "Novo endereço de e-mail", + "profile_fields": { + "value": "Conteúdo", + "add_field": "Adicionar campo", + "label": "Metadados do perfil", + "name": "Etiqueta" + }, + "import_blocks_from_a_csv_file": "Importar bloqueados a partir de um arquivo CSV", + "hide_wallpaper": "Esconder papel de parede da instância", + "notification_setting_privacy": "Privacidade", + "notification_setting_filters": "Filtros", + "fun": "Divertido", + "user_mutes": "Utilizadores", + "type_domains_to_mute": "Pesquisar domínios para silenciar", + "useStreamingApiWarning": "(não recomendado, experimental, pode omitir publicações)", + "useStreamingApi": "Receber publicações e notificações em tempo real", + "minimal_scopes_mode": "Minimizar as opções de publicação", + "search_user_to_mute": "Pesquisar utilizadores que pretende silenciar", + "search_user_to_block": "Pesquisa quais utilizadores desejas bloquear", + "notification_setting_hide_notification_contents": "Ocultar o remetente e o conteúdo das notificações push", + "version": { + "frontend_version": "Versão do Frontend", + "backend_version": "Versão do Backend", + "title": "Versão" + }, + "notification_blocks": "Bloquear um utilizador previne todas as notificações, bem como as desativa.", + "notification_mutes": "Para deixar de receber notificações de um utilizador específico, silencia-o.", + "notification_setting_block_from_strangers": "Bloqueia as notificações de utilizadores que não segues", + "greentext": "Texto verde (meme arrows)", + "virtual_scrolling": "Otimizar a apresentação da cronologia", + "reset_background_confirm": "Tens a certeza que desejas redefinir o fundo?", + "reset_banner_confirm": "Tens a certeza que desejas redefinir a imagem do cabeçalho?", + "reset_avatar_confirm": "Tens a certeza que desejas redefinir o avatar?", + "reset_profile_banner": "Redefinir imagem do cabeçalho do perfil", + "reset_profile_background": "Redefinir fundo de perfil", + "reset_avatar": "Redefinir avatar", + "autohide_floating_post_button": "Automaticamente ocultar o botão 'Nova Publicação' (telemóvel)", + "notification_visibility_moves": "Utilizador Migrado", + "accent": "Destaque", + "pad_emoji": "Preencher espaços ao adicionar emojis do seletor" }, "timeline": { "collapse": "Esconder", "conversation": "Conversa", "error_fetching": "Erro ao buscar atualizações", "load_older": "Carregar postagens antigas", - "no_retweet_hint": "Posts apenas para seguidores ou diretos não podem ser repetidos", - "repeated": "Repetido", + "no_retweet_hint": "Posts apenas para seguidores ou diretos não podem ser partilhados", + "repeated": "partilhado", "show_new": "Mostrar novas", "up_to_date": "Atualizado", "no_more_statuses": "Sem mais posts", - "no_statuses": "Sem posts" + "no_statuses": "Sem posts", + "reload": "Recarregar", + "error": "Erro a obter a cronologia: {0}" }, "status": { "reply_to": "Responder a", - "replies_list": "Respostas:" + "replies_list": "Respostas:", + "unbookmark": "Remover post dos Items Guardados", + "expand": "Expandir", + "nsfw": "NSFW (Não apropriado para trabalho)", + "status_deleted": "Esta publicação foi apagada", + "hide_content": "Ocultar o conteúdo", + "show_content": "Mostrar o conteúdo", + "hide_full_subject": "Ocultar o assunto completo", + "show_full_subject": "Mostrar o assunto completo", + "thread_muted_and_words": ", contém:", + "thread_muted": "Conversação silenciada", + "external_source": "Fonte externa", + "copy_link": "Copiar o link do post", + "status_unavailable": "Publicação indisponível", + "unmute_conversation": "Mostrar a conversação", + "mute_conversation": "Silenciar a conversação", + "delete_confirm": "Tens a certeza que desejas apagar a publicação?", + "bookmark": "Guardar", + "pin": "Fixar no perfil", + "pinned": "Afixado", + "unpin": "Desafixar do perfil", + "delete": "Eliminar publicação", + "repeats": "Partilhados", + "favorites": "Favoritos" }, "user_card": { "approve": "Aprovar", @@ -377,21 +582,48 @@ "following": "Seguindo!", "follows_you": "Segue você!", "its_you": "É você!", - "media": "Mídia", + "media": "Multimédia", "mute": "Silenciar", "muted": "Silenciado", "per_day": "por dia", "remote_follow": "Seguir remotamente", "statuses": "Postagens", "unblock": "Desbloquear", - "unblock_progress": "Desbloqueando...", - "block_progress": "Bloqueando...", + "unblock_progress": "A desbloquear…", + "block_progress": "A bloquear…", "unmute": "Retirar silêncio", - "unmute_progress": "Retirando silêncio...", - "mute_progress": "Silenciando..." + "unmute_progress": "A retirar silêncio…", + "mute_progress": "A silenciar…", + "admin_menu": { + "delete_user_confirmation": "Tens a certeza? Esta ação não pode ser revertida.", + "delete_user": "Eliminar utilizador", + "quarantine": "Não permitir publicações de utilizadores de instâncias remotas", + "disable_any_subscription": "Não permitir que nenhum utilizador te siga", + "disable_remote_subscription": "Não permitir seguidores de instâncias remotas", + "sandbox": "Forçar publicações apenas para seguidores", + "force_unlisted": "Forçar publicações como não listadas", + "strip_media": "Eliminar ficheiros multimédia das publicações", + "force_nsfw": "Marcar todas as publicações como NSFW (não apropriado para o trabalho)", + "delete_account": "Eliminar Conta", + "deactivate_account": "Desativar conta", + "activate_account": "Ativar conta", + "revoke_moderator": "Revogar permissões de Moderador", + "grant_moderator": "Conceder permissões de Moderador", + "revoke_admin": "Revogar permissões de Admin", + "grant_admin": "Conceder permissões de Admin", + "moderation": "Moderação" + }, + "show_repeats": "Mostrar partilhas", + "hide_repeats": "Ocultar partilhas", + "unsubscribe": "Retirar subscrição", + "subscribe": "Subscrever", + "report": "Denunciar", + "message": "Mensagem", + "mention": "Mencionar", + "hidden": "Ocultar" }, "user_profile": { - "timeline_title": "Linha do tempo do usuário", + "timeline_title": "Cronologia do Utilizador", "profile_does_not_exist": "Desculpe, este perfil não existe.", "profile_loading_error": "Desculpe, houve um erro ao carregar este perfil." }, @@ -400,17 +632,22 @@ "who_to_follow": "Quem seguir" }, "tool_tip": { - "media_upload": "Envio de mídia", - "repeat": "Repetir", + "media_upload": "Envio de multimédia", + "repeat": "Partilhar", "reply": "Responder", "favorite": "Favoritar", - "user_settings": "Configurações do usuário" + "user_settings": "Configurações do usuário", + "bookmark": "Guardar", + "reject_follow_request": "Rejeitar o pedido de seguimento", + "accept_follow_request": "Aceitar o pedido de seguimento", + "add_reaction": "Adicionar Reação" }, - "upload":{ + "upload": { "error": { "base": "Falha no envio.", "file_too_big": "Arquivo grande demais [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", - "default": "Tente novamente mais tarde" + "default": "Tente novamente mais tarde", + "message": "Falha ao enviar: {0}" }, "file_size_units": { "B": "B", @@ -419,5 +656,179 @@ "GiB": "GiB", "TiB": "TiB" } + }, + "about": { + "mrf": { + "simple": { + "quarantine": "Quarentena", + "reject": "Rejeitar", + "accept": "Aceitar", + "media_removal_desc": "Este domínio remove multimédia das publicações dos seguintes domínios:", + "media_removal": "Remoção de multimédia", + "ftl_removal_desc": "Este domínio remove os seguintes domínios da cronologia \"Rede conhecida por todos\":", + "quarantine_desc": "Este domínio apenas irá publicar nos seguintes domínios:", + "reject_desc": "Este domínio não aceitará mensagens dos seguintes domínios:", + "accept_desc": "Este domínio aceita apenas mensagens dos seguintes domínios:", + "simple_policies": "Políticas especificas do domínio", + "media_nsfw": "Forçar definição de multimédia como Sensível", + "ftl_removal": "Remoção da cronologia da \"Rede conhecida por todos\"", + "media_nsfw_desc": "Este domínio força a multimédia a ser marcada como sensível nos seguintes domínios:" + }, + "keyword": { + "replace": "Substituir", + "reject": "Rejeitar", + "is_replaced_by": "→", + "keyword_policies": "Política de Palavras-Chave", + "ftl_removal": "Remoção da cronologia da \"Rede conhecida por todos\"" + }, + "federation": "Federação", + "mrf_policies": "Ativar Políticas MRF", + "mrf_policies_desc": "Políticas MRF manipulam o comportamento da federação nos domínios. As seguintes políticas estão ativadas:" + }, + "staff": "Staff" + }, + "remote_user_resolver": { + "searching_for": "A pesquisar por", + "error": "Não encontrado.", + "remote_user_resolver": "Resolução de utilizador remoto" + }, + "emoji": { + "unicode": "Emoji Unicode", + "custom": "Emoji customizado", + "add_emoji": "Inserir emoji", + "search_emoji": "Pesquisar por um emoji", + "emoji": "Emoji", + "load_all": "A carregar todos os {emojiAmount} emojis", + "load_all_hint": "Carregado o primeiro emoji {saneAmount}, carregar todos os emojis pode causar problemas de desempenho.", + "keep_open": "Manter o seletor aberto", + "stickers": "Autocolantes" + }, + "polls": { + "single_choice": "Escolha única", + "vote": "Vota", + "votes": "votos", + "option": "Opção", + "add_option": "Adicionar Opção", + "not_enough_options": "Demasiado poucas opções únicas na sondagem", + "expired": "A sondagem terminou há {0}", + "expires_in": "A sondagem termina em {0}", + "expiry": "Tempo para finalizar sondagem", + "multiple_choices": "Escolha múltipla", + "type": "Tipo de sondagem", + "add_poll": "Adicionar Sondagem" + }, + "importer": { + "error": "Ocorreu um erro ao importar este ficheiro.", + "success": "Importado com sucesso.", + "submit": "Enviar" + }, + "exporter": { + "processing": "A processar, brevemente ser-te-á pedido que descarregues o ficheiro", + "export": "Exportar" + }, + "domain_mute_card": { + "mute_progress": "A silenciar…", + "mute": "Silenciar", + "unmute": "Remover silêncio", + "unmute_progress": "A remover o silêncio…" + }, + "selectable_list": { + "select_all": "Seleccionar tudo" + }, + "interactions": { + "load_older": "Carregar interações mais antigas", + "follows": "Novos seguidores", + "favs_repeats": "Gostos e Partilhas", + "moves": "O utilizador migra" + }, + "errors": { + "storage_unavailable": "O Pleroma não conseguiu aceder ao armazenamento do navegador. A sua sessão ou definições locais não serão armazenadas e poderá encontrar problemas inesperados. Tente ativar as cookies." + }, + "shoutbox": { + "title": "Chat Geral" + }, + "chats": { + "chats": "Chats", + "empty_chat_list_placeholder": "Não tens conversações ainda. Inicia uma nova conversa!", + "error_sending_message": "Ocorreu algo de errado ao enviar a mensagem.", + "error_loading_chat": "Ocorreu algo de errado ao carregar o chat.", + "delete_confirm": "Desejas realmente apagar esta mensagem?", + "more": "Mais", + "empty_message_error": "Não podes publicar uma mensagem vazia", + "new": "Nova conversação", + "delete": "Apagar", + "message_user": "Mensagem de {nickname}", + "you": "Tu:" + }, + "search": { + "hashtags": "Hashtags", + "no_results": "Sem resultados", + "person_talking": "{count} pessoa a falar", + "people_talking": "{0} pessoas a falar", + "people": "Pessoas" + }, + "display_date": { + "today": "Hoje" + }, + "file_type": { + "file": "Ficheiro", + "image": "Imagem", + "video": "Vídeo", + "audio": "Áudio" + }, + "password_reset": { + "password_reset_required_but_mailer_is_disabled": "Deves repor a tua palavra-passe, porém, a reposição de palavra-passe está desativada. Contacta o administrador da tua instância.", + "password_reset_required": "Deves repor a tua palavra-passe para iniciar sessão.", + "password_reset_disabled": "A reposição da palavra-passe foi desativada. Contacta o administrador da tua instância.", + "too_many_requests": "Alcançaste o limite de tentativas, tenta novamente mais tarde.", + "return_home": "Voltar à página principal", + "check_email": "Verifica o teu endereço de e-mail para obter um link para repor a tua palavra-passe.", + "placeholder": "O teu endereço de e-mail ou nome de utilizador", + "instruction": "Introduz o teu endereço de e-mail ou nome de utilizador. Enviaremos um link para repores a tua palavra-passe.", + "password_reset": "Repor palavra-passe", + "forgot_password": "Esqueceu-se da palavra-passe?" + }, + "user_reporting": { + "generic_error": "Ocorreu um erro ao processar o teu pedido.", + "submit": "Enviar", + "forward_to": "Encaminhar para {0}", + "forward_description": "A conta é de outro servidor. Enviar também uma cópia da denúncia à outra instância?", + "additional_comments": "Comentários adicionais", + "add_comment_description": "Esta denúncia será enviada aos moderadores desta instância. Podes fornecer uma explicação pela qual te encontras a denunciar esta conta abaixo:", + "title": "Denunciar {0}" + }, + "time": { + "years_short": "{0}a", + "year_short": "{0}a", + "years": "{0} anos", + "year": "{0} ano", + "weeks_short": "{0}sem", + "week_short": "{0}sem", + "weeks": "{0} semanas", + "week": "{0} semana", + "seconds_short": "{0}s", + "second_short": "{0}s", + "seconds": "{0} segundos", + "second": "{0} segundo", + "now": "agora mesmo", + "now_short": "agora", + "months_short": "{0}m", + "month_short": "{0}m", + "months": "{0} meses", + "month": "{0} mês", + "minutes_short": "{0}min", + "minute_short": "{0}min", + "minutes": "{0} minutos", + "minute": "{0} minuto", + "in_past": "há {0}", + "in_future": "em {0}", + "hours_short": "{0}h", + "hour_short": "{0}h", + "hours": "{0} horas", + "hour": "{0} hora", + "days_short": "{0}d", + "day_short": "{0}d", + "days": "{0} dias", + "day": "{0} dia" } } diff --git a/src/i18n/ru.json b/src/i18n/ru.json index e063f09f..6d230e69 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -24,7 +24,11 @@ "retry": "Попробуйте еще раз", "error_retry": "Пожалуйста попробуйте еще раз", "close": "Закрыть", - "loading": "Загрузка…" + "loading": "Загрузка…", + "role": { + "moderator": "Модератор", + "admin": "Администратор" + } }, "login": { "login": "Войти", @@ -183,14 +187,14 @@ "change_password": "Сменить пароль", "change_password_error": "Произошла ошибка при попытке изменить пароль.", "changed_password": "Пароль изменён успешно!", - "collapse_subject": "Сворачивать посты с темой", + "collapse_subject": "Сворачивать статусы с темой", "confirm_new_password": "Подтверждение нового пароля", "current_avatar": "Текущий аватар", "current_password": "Текущий пароль", "current_profile_banner": "Текущий баннер профиля", "data_import_export_tab": "Импорт / Экспорт данных", "delete_account": "Удалить аккаунт", - "delete_account_description": "Удалить ваш аккаунт и все ваши сообщения.", + "delete_account_description": "Удалить вашу учётную запись и все ваши сообщения.", "delete_account_error": "Возникла ошибка в процессе удаления вашего аккаунта. Если это повторяется, свяжитесь с администратором вашего сервера.", "delete_account_instructions": "Введите ваш пароль в поле ниже для подтверждения удаления.", "export_theme": "Сохранить Тему", @@ -238,7 +242,7 @@ "hide_followers_count_description": "Не показывать число моих подписчиков", "show_admin_badge": "Показывать значок администратора в моем профиле", "show_moderator_badge": "Показывать значок модератора в моем профиле", - "nsfw_clickthrough": "Включить скрытие NSFW вложений и не показывать изображения в предпросмотре ссылок для NSFW статусов", + "nsfw_clickthrough": "Включить скрытие вложений и предпросмотра ссылок для NSFW статусов", "oauth_tokens": "OAuth токены", "token": "Токен", "refresh_token": "Рефреш токен", @@ -295,7 +299,14 @@ "use_source": "Новая версия", "use_snapshot": "Старая версия", "keep_as_is": "Оставить, как есть", - "load_theme": "Загрузить тему" + "load_theme": "Загрузить тему", + "help": { + "fe_upgraded": "Движок тем для фронт-энда Pleroma был изменен после обновления.", + "older_version_imported": "Файл, который вы импортировали, был сделан в старой версии фронт-энда.", + "future_version_imported": "Файл, который вы импортировали, был сделан в новой версии фронт-энда.", + "v2_imported": "Файл, который вы импортировали, был сделан под старый фронт-энд. Мы стараемся улучшить совместимость, но все еще возможны несостыковки.", + "upgraded_from_v2": "Фронт-энд Pleroma был изменен. Выбранная тема может выглядеть слегка по-другому." + } }, "common": { "color": "Цвет", @@ -330,7 +341,9 @@ "borders": "Границы", "buttons": "Кнопки", "inputs": "Поля ввода", - "faint_text": "Маловажный текст" + "faint_text": "Маловажный текст", + "post": "Сообщения и описание пользователя", + "alert_neutral": "Нейтральный" }, "radii": { "_tab_label": "Скругление" @@ -451,7 +464,19 @@ "virtual_scrolling": "Оптимизировать рендеринг ленты", "hide_wallpaper": "Скрыть обои узла", "accent": "Акцент", - "upload_a_photo": "Загрузить фото" + "upload_a_photo": "Загрузить фото", + "notification_mutes": "Чтобы не получать уведомления от определённого пользователя, заглушите его.", + "reset_avatar_confirm": "Вы действительно хотите сбросить личный образ?", + "reset_profile_banner": "Сбросить личный баннер", + "reset_profile_background": "Сбросить личные обои", + "reset_avatar": "Сбросить личный образ", + "search_user_to_mute": "Искать, кого вы хотите заглушить", + "search_user_to_block": "Искать, кого вы хотите заблокировать", + "pad_emoji": "Выделять эмодзи пробелами при добавлении из панели", + "avatar_size_instruction": "Желательный наименьший размер личного образа 150 на 150 пикселей.", + "enable_web_push_notifications": "Включить web push-уведомления", + "notification_blocks": "Блокировка пользователя выключает все уведомления от него, а также отписывает вас от него.", + "notification_setting_hide_notification_contents": "Скрыть отправителя и содержимое push-уведомлений" }, "timeline": { "collapse": "Свернуть", @@ -465,7 +490,7 @@ "error": "Ошибка при обновлении ленты: {0}" }, "status": { - "bookmark": "В закладки", + "bookmark": "Добавить в закладки", "unbookmark": "Удалить из закладок", "status_deleted": "Пост удален", "reply_to": "Ответ", @@ -473,7 +498,11 @@ "favorites": "Понравилось", "unmute_conversation": "Прекратить игнорировать разговор", "mute_conversation": "Игнорировать разговор", - "thread_muted": "Разговор игнорируется" + "thread_muted": "Разговор игнорируется", + "external_source": "Перейти к источнику", + "delete_confirm": "Вы действительно хотите удалить данный статус?", + "delete": "Удалить", + "copy_link": "Скопировать ссылку" }, "user_card": { "block": "Заблокировать", @@ -515,7 +544,8 @@ "media": "С вложениями", "mention": "Упомянуть", "show_repeats": "Показывать повторы", - "hide_repeats": "Скрыть повторы" + "hide_repeats": "Скрыть повторы", + "report": "Пожаловаться" }, "user_profile": { "timeline_title": "Лента пользователя" @@ -584,7 +614,9 @@ "title": "Особенности", "gopher": "Gopher", "who_to_follow": "Предложения кого читать", - "pleroma_chat_messages": "Pleroma Чат" + "pleroma_chat_messages": "Pleroma Чат", + "upload_limit": "Наибольший размер загружаемого файла", + "scope_options": "Настраиваемая видимость статусов" }, "tool_tip": { "accept_follow_request": "Принять запрос на чтение", @@ -673,6 +705,7 @@ "you": "Вы:" }, "remote_user_resolver": { - "error": "Не найдено." + "error": "Не найдено.", + "searching_for": "Ищем" } } diff --git a/src/i18n/uk.json b/src/i18n/uk.json index 4ead8790..98f65a0e 100644 --- a/src/i18n/uk.json +++ b/src/i18n/uk.json @@ -17,7 +17,11 @@ "more": "Більше", "submit": "Відправити", "apply": "Застосувати", - "peek": "Глянути" + "peek": "Глянути", + "role": { + "moderator": "Модератор", + "admin": "Адміністратор" + } }, "finder": { "error_fetching_user": "Користувача не знайдено", @@ -25,11 +29,11 @@ }, "features_panel": { "gopher": "Gopher", - "pleroma_chat_messages": "Локальні балачки", + "pleroma_chat_messages": "Чати", "chat": "Міні-чат", "who_to_follow": "Кого відстежувати", "title": "Особливості", - "scope_options": "Параметри осягу", + "scope_options": "Параметри обсягу", "media_proxy": "Посередник медіа-даних", "text_limit": "Ліміт символів", "upload_limit": "Обмеження завантажень" @@ -39,9 +43,9 @@ "export": "Експорт" }, "domain_mute_card": { - "unmute_progress": "Вимикаю…", + "unmute_progress": "Вмикаю…", "unmute": "Вимкнути заглушення", - "mute_progress": "Вмикаю…", + "mute_progress": "Вимикаю…", "mute": "Ігнорувати" }, "shoutbox": { @@ -51,13 +55,13 @@ "staff": "Адміністрація", "mrf": { "simple": { - "media_nsfw_desc": "Даний інстанс примусово позначає медіа в наступних інстансах як NSFW:", + "media_nsfw_desc": "Даний інстанс примусово позначає медіа в наступних інстансах як дратівливий:", "media_nsfw": "Примусове визначення медіа як дратівливого", "media_removal_desc": "Поточний інстанс видаляє медіа з дописів на перелічених інстансах:", "media_removal": "Видалення медіа", - "ftl_removal_desc": "Цей інстанс видаляє перелічені інстанси з \"Усієї відомої мережі\":", - "ftl_removal": "Видалення з \"Усієї відомої мережі\"", - "quarantine_desc": "Поточний інстанс буде надсилати тільки публічні дописи наступним інстансам:", + "ftl_removal_desc": "Цей інстанс видаляє перелічені інстанси з Федеративної стрічки:", + "ftl_removal": "Видалення зі стрічки Федеративної мережі", + "quarantine_desc": "Поточний інстанс надсилатиме тільки публічні дописи наступним інстансам:", "quarantine": "Карантин", "reject_desc": "Поточний інстанс не прийматиме повідомлення з перелічених інстансів:", "accept": "Прийняти", @@ -66,7 +70,7 @@ "simple_policies": "Правила поточного інстансу" }, "mrf_policies_desc": "Правила MRF розповсюджуються на даний інстанс. Наступні правила активні:", - "mrf_policies": "Активні правила MRF (модуль переписування повідомлень)", + "mrf_policies": "Активувати правила MRF (модуль переписування повідомлень)", "keyword": { "is_replaced_by": "→", "replace": "Замінити", @@ -135,7 +139,7 @@ "error": "Помилка при оновленні сповіщень: {0}" }, "nav": { - "chats": "Локальні балачки", + "chats": "Чати", "timelines": "Стрічки", "twkn": "Уся відома мережа", "about": "Інформація", @@ -546,7 +550,8 @@ "disabled": "Вимкнено", "selectedMenu": "Вибраний пункт меню", "tabs": "Вкладки", - "pressed": "Натиснуто" + "pressed": "Натиснуто", + "wallpaper": "Шпалери" }, "common_colors": { "rgbo": "Піктограми, акценти, значки", @@ -602,7 +607,8 @@ "frontend_version": "Версія фронтенду", "backend_version": "Версія бекенду", "title": "Версія" - } + }, + "hide_wallpaper": "Сховати шпалери екземпляру" }, "selectable_list": { "select_all": "Вибрати все" diff --git a/src/i18n/zh.json b/src/i18n/zh.json index b4185b30..5e377f7c 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -39,7 +39,11 @@ "close": "关闭", "retry": "重试", "error_retry": "请重试", - "loading": "载入中…" + "loading": "载入中…", + "role": { + "moderator": "监察员", + "admin": "管理员" + } }, "image_cropper": { "crop_picture": "裁剪图片", @@ -120,7 +124,9 @@ "expiry": "投票期限", "expires_in": "投票于 {0} 后结束", "expired": "投票 {0} 前已结束", - "not_enough_options": "投票的选项太少" + "not_enough_options": "投票的选项太少", + "votes_count": "{count} 票 | {count} 票", + "people_voted_count": "{count} 人已投票 | {count} 人已投票" }, "stickers": { "add_sticker": "添加贴纸" @@ -183,7 +189,9 @@ "password_required": "不能留空", "password_confirmation_required": "不能留空", "password_confirmation_match": "密码不一致" - } + }, + "reason_placeholder": "此实例的注册需要手动批准。\n请让管理员知道您为什么想要注册。", + "reason": "注册理由" }, "selectable_list": { "select_all": "选择全部" @@ -552,7 +560,8 @@ "mute_import": "隐藏名单导入", "mute_export_button": "导出你的隐藏名单到一个 csv 文件", "mute_export": "隐藏名单导出", - "hide_wallpaper": "隐藏实例壁纸" + "hide_wallpaper": "隐藏实例壁纸", + "setting_changed": "与默认设置不同" }, "time": { "day": "{0} 天", @@ -683,7 +692,8 @@ "show_repeats": "显示转发", "hide_repeats": "隐藏转发", "message": "消息", - "mention": "提及" + "mention": "提及", + "bot": "机器人" }, "user_profile": { "timeline_title": "用户时间线", diff --git a/src/i18n/zh_Hant.json b/src/i18n/zh_Hant.json index f63acab8..b7b6d7ac 100644 --- a/src/i18n/zh_Hant.json +++ b/src/i18n/zh_Hant.json @@ -25,7 +25,7 @@ "add_poll": "增加投票" }, "notifications": { - "reacted_with": "和 {0} 互動過", + "reacted_with": "作出了 {0} 的反應", "migrated_to": "遷移到", "no_more_notifications": "沒有更多的通知", "repeated_you": "轉發了你的發文", @@ -54,7 +54,7 @@ "mentions": "提及", "friend_requests": "關注請求", "back": "後退", - "administration": "管理", + "administration": "管理員", "about": "關於" }, "media_modal": { @@ -216,7 +216,8 @@ "incoming": "收到", "outgoing": "發出", "border": "邊框" - } + }, + "wallpaper": "桌布" }, "preview": { "header_faint": "這很正常", @@ -412,7 +413,7 @@ "hide_follows_description": "不要顯示我所關注的人", "hide_followers_description": "不要顯示關注我的人", "hide_follows_count_description": "不顯示關注數", - "nsfw_clickthrough": "將敏感附件隱藏,點擊才能打開", + "nsfw_clickthrough": "將敏感附件和鏈接隱藏,點擊才能打開", "valid_until": "有效期至", "panelRadius": "面板", "pause_on_unfocused": "在離開頁面時暫停時間線推送", @@ -526,7 +527,8 @@ "mute_import": "靜音導入", "mute_import_error": "導入靜音時出錯", "mute_export_button": "將靜音導出到csv文件", - "mute_export": "靜音導出" + "mute_export": "靜音導出", + "hide_wallpaper": "隱藏實例桌布" }, "chats": { "more": "更多", @@ -571,16 +573,20 @@ "thread_muted_and_words": ",有这些字:", "hide_full_subject": "隱藏完整標題", "show_content": "顯示內容", - "hide_content": "隱藏內容" + "hide_content": "隱藏內容", + "status_deleted": "該帖已被刪除", + "expand": "展开", + "external_source": "外部來源", + "nsfw": "工作不安全" }, "time": { - "hours": "{0} 小時", + "hours": "{0} 時", "days_short": "{0}天", "day_short": "{0}天", "days": "{0} 天", - "hour": "{0} 小时", - "hour_short": "{0}h", - "hours_short": "{0}h", + "hour": "{0} 時", + "hour_short": "{0}時", + "hours_short": "{0}時", "years_short": "{0} y", "now": "剛剛", "day": "{0} 天", @@ -654,7 +660,8 @@ "reload": "重新載入", "up_to_date": "已是最新", "no_more_statuses": "没有更多發文", - "no_statuses": "没有發文" + "no_statuses": "没有發文", + "error": "取得時間線時發生錯誤:{0}" }, "interactions": { "load_older": "載入更早的互動", @@ -745,7 +752,11 @@ "unmute": "取消靜音", "unmute_progress": "取消靜音中…", "hide_repeats": "隱藏轉發", - "show_repeats": "顯示轉發" + "show_repeats": "顯示轉發", + "roles": { + "moderator": "主持人", + "admin": "管理員" + } }, "user_profile": { "timeline_title": "用戶時間線", @@ -787,7 +798,8 @@ "error": { "base": "上傳失敗。", "file_too_big": "文件太大[{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]", - "default": "稍後再試" + "default": "稍後再試", + "message": "上傳錯誤:{0}" } }, "search": { diff --git a/src/main.js b/src/main.js index 90ee2887..e1cac748 100644 --- a/src/main.js +++ b/src/main.js @@ -28,7 +28,6 @@ import pushNotifications from './lib/push_notifications_plugin.js' import messages from './i18n/messages.js' -import VueChatScroll from 'vue-chat-scroll' import VueClickOutside from 'v-click-outside' import PortalVue from 'portal-vue' import VBodyScrollLock from './directives/body_scroll_lock' @@ -42,7 +41,6 @@ const currentLocale = (window.navigator.language || 'en').split('-')[0] Vue.use(Vuex) Vue.use(VueRouter) Vue.use(VueI18n) -Vue.use(VueChatScroll) Vue.use(VueClickOutside) Vue.use(PortalVue) Vue.use(VBodyScrollLock) diff --git a/src/modules/chat.js b/src/modules/chat.js index c798549d..ffeb272b 100644 --- a/src/modules/chat.js +++ b/src/modules/chat.js @@ -18,6 +18,7 @@ const chat = { actions: { initializeChat (store, socket) { const channel = socket.channel('chat:public') + channel.on('new_msg', (msg) => { store.commit('addMessage', msg) }) diff --git a/src/modules/chats.js b/src/modules/chats.js index 0a373d88..69d683bd 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -115,6 +115,9 @@ const chats = { }, handleMessageError ({ commit }, value) { commit('handleMessageError', { commit, ...value }) + }, + cullOlderMessages ({ commit }, chatId) { + commit('cullOlderMessages', chatId) } }, mutations: { @@ -227,6 +230,9 @@ const chats = { handleMessageError (state, { chatId, fakeId, isRetry }) { const chatMessageService = state.openedChatMessageServices[chatId] chatService.handleMessageError(chatMessageService, fakeId, isRetry) + }, + cullOlderMessages (state, chatId) { + chatService.cullOlderMessages(state.openedChatMessageServices[chatId]) } } } diff --git a/src/modules/config.js b/src/modules/config.js index cd088737..eca58c12 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -67,7 +67,8 @@ export const defaultState = { greentext: undefined, // instance default hidePostStats: undefined, // instance default hideUserStats: undefined, // instance default - virtualScrolling: undefined // instance default + virtualScrolling: undefined, // instance default + sensitiveByDefault: undefined // instance default } // caching the instance default properties @@ -76,18 +77,22 @@ export const instanceDefaultProperties = Object.entries(defaultState) .map(([key, value]) => key) const config = { - state: defaultState, + state: { ...defaultState }, getters: { - mergedConfig (state, getters, rootState, rootGetters) { + defaultConfig (state, getters, rootState, rootGetters) { const { instance } = rootState return { - ...state, - ...instanceDefaultProperties - .map(key => [key, state[key] === undefined - ? instance[key] - : state[key] - ]) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) + ...defaultState, + ...Object.fromEntries( + instanceDefaultProperties.map(key => [key, instance[key]]) + ) + } + }, + mergedConfig (state, getters, rootState, rootGetters) { + const { defaultConfig } = rootGetters + return { + ...defaultConfig, + ...state } } }, diff --git a/src/modules/instance.js b/src/modules/instance.js index 411b1caa..96de73ca 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -43,6 +43,7 @@ const defaultState = { subjectLineBehavior: 'email', theme: 'pleroma-dark', virtualScrolling: true, + sensitiveByDefault: false, // Nasty stuff customEmoji: [], diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 33c68c57..ac5d25c4 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -13,7 +13,11 @@ import { omitBy } from 'lodash' import { set } from 'vue' -import { isStatusNotification, maybeShowNotification } from '../services/notification_utils/notification_utils.js' +import { + isStatusNotification, + isValidNotification, + maybeShowNotification +} from '../services/notification_utils/notification_utils.js' import apiService from '../services/api/api.service.js' const emptyTl = (userId = 0) => ({ @@ -310,8 +314,24 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us } } +const updateNotificationsMinMaxId = (state, notification) => { + state.notifications.maxId = notification.id > state.notifications.maxId + ? notification.id + : state.notifications.maxId + state.notifications.minId = notification.id < state.notifications.minId + ? notification.id + : state.notifications.minId +} + const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters, newNotificationSideEffects }) => { each(notifications, (notification) => { + // If invalid notification, update ids but don't add it to store + if (!isValidNotification(notification)) { + console.error('Invalid notification:', notification) + updateNotificationsMinMaxId(state, notification) + return + } + if (isStatusNotification(notification.type)) { notification.action = addStatusToGlobalStorage(state, notification.action).item notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item @@ -323,12 +343,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot // Only add a new notification if we don't have one for the same action if (!state.notifications.idStore.hasOwnProperty(notification.id)) { - state.notifications.maxId = notification.id > state.notifications.maxId - ? notification.id - : state.notifications.maxId - state.notifications.minId = notification.id < state.notifications.minId - ? notification.id - : state.notifications.minId + updateNotificationsMinMaxId(state, notification) state.notifications.data.push(notification) state.notifications.idStore[notification.id] = notification diff --git a/src/services/chat_service/chat_service.js b/src/services/chat_service/chat_service.js index e653ebc1..92ff689d 100644 --- a/src/services/chat_service/chat_service.js +++ b/src/services/chat_service/chat_service.js @@ -48,6 +48,22 @@ const deleteMessage = (storage, messageId) => { } } +const cullOlderMessages = (storage) => { + const maxIndex = storage.messages.length + const minIndex = maxIndex - 50 + if (maxIndex <= 50) return + + storage.messages = _.sortBy(storage.messages, ['id']) + storage.minId = storage.messages[minIndex].id + for (const message of storage.messages) { + if (message.id < storage.minId) { + delete storage.idIndex[message.id] + delete storage.idempotencyKeyIndex[message.idempotency_key] + } + } + storage.messages = storage.messages.slice(minIndex, maxIndex) +} + const handleMessageError = (storage, fakeId, isRetry) => { if (!storage) { return } const fakeMessage = storage.idIndex[fakeId] @@ -201,6 +217,7 @@ const ChatService = { empty, getView, deleteMessage, + cullOlderMessages, resetNewMessageCount, clear, handleMessageError diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 625f593e..9b2b30e6 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -188,7 +188,12 @@ export const parseUser = (data) => { output.follow_request_count = data.pleroma.follow_request_count output.tags = data.pleroma.tags - output.deactivated = data.pleroma.deactivated + + // deactivated was changed to is_active in Pleroma 2.3.0 + // so check if is_active is present + output.deactivated = typeof data.pleroma.is_active !== 'undefined' + ? !data.pleroma.is_active // new backend + : data.pleroma.deactivated // old backend output.notification_settings = data.pleroma.notification_settings output.unread_chat_count = data.pleroma.unread_chat_count @@ -198,7 +203,8 @@ export const parseUser = (data) => { output.rights = output.rights || {} output.notification_settings = output.notification_settings || {} - // Convert punycode to unicode + // Convert punycode to unicode for UI + output.screen_name_ui = output.screen_name if (output.screen_name.includes('@')) { const parts = output.screen_name.split('@') let unicodeDomain = punycode.toUnicode(parts[1]) @@ -206,7 +212,7 @@ export const parseUser = (data) => { // Add some identifier so users can potentially spot spoofing attempts: // lain.com and xn--lin-6cd.com would appear identical otherwise. unicodeDomain = '🌏' + unicodeDomain - output.screen_name = [parts[0], unicodeDomain].join('@') + output.screen_name_ui = [parts[0], unicodeDomain].join('@') } } diff --git a/src/services/locale/locale.service.js b/src/services/locale/locale.service.js new file mode 100644 index 00000000..5be99d81 --- /dev/null +++ b/src/services/locale/locale.service.js @@ -0,0 +1,12 @@ +const specialLanguageCodes = { + 'ja_easy': 'ja', + 'zh_Hant': 'zh-HANT' +} + +const internalToBrowserLocale = code => specialLanguageCodes[code] || code + +const localeService = { + internalToBrowserLocale +} + +export default localeService diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index d912d19f..6fef1022 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -22,6 +22,13 @@ const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reactio export const isStatusNotification = (type) => includes(statusNotifications, type) +export const isValidNotification = (notification) => { + if (isStatusNotification(notification.type) && !notification.status) { + return false + } + return true +} + const sortById = (a, b) => { const seqA = Number(a.id) const seqB = Number(b.id) diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 07425abd..a2bba67b 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -242,9 +242,18 @@ export const generateShadows = (input, colors) => { panelHeader: 'panel', input: 'input' } - const inputShadows = input.shadows && !input.themeEngineVersion - ? shadows2to3(input.shadows, input.opacity) - : input.shadows || {} + + const cleanInputShadows = Object.fromEntries( + Object.entries(input.shadows || {}) + .map(([name, shadowSlot]) => [ + name, + // defaulting color to black to avoid potential problems + shadowSlot.map(shadowDef => ({ color: '#000000', ...shadowDef })) + ]) + ) + const inputShadows = cleanInputShadows && !input.themeEngineVersion + ? shadows2to3(cleanInputShadows, input.opacity) + : cleanInputShadows || {} const shadows = Object.entries({ ...DEFAULT_SHADOWS, ...inputShadows diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js index 80092b41..142db73c 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -31,13 +31,15 @@ const testGetters = { const localUser = { id: 100, is_local: true, - screen_name: 'testUser' + screen_name: 'testUser', + screen_name_ui: 'testUser' } const extUser = { id: 100, is_local: false, - screen_name: 'testUser@test.instance' + screen_name: 'testUser@test.instance', + screen_name_ui: 'testUser@test.instance' } const externalProfileStore = new Vuex.Store({ diff --git a/test/unit/specs/services/chat_service/chat_service.spec.js b/test/unit/specs/services/chat_service/chat_service.spec.js index 0251cae7..fbbca436 100644 --- a/test/unit/specs/services/chat_service/chat_service.spec.js +++ b/test/unit/specs/services/chat_service/chat_service.spec.js @@ -88,4 +88,21 @@ describe('chatService', () => { expect(view.map(i => i.type)).to.eql(['date', 'message', 'message', 'date', 'message']) }) }) + + describe('.cullOlderMessages', () => { + it('keeps 50 newest messages and idIndex matches', () => { + const chat = chatService.empty() + + for (let i = 100; i > 0; i--) { + // Use decimal values with toFixed to hack together constant length predictable strings + chatService.add(chat, { messages: [{ ...message1, id: 'a' + (i / 1000).toFixed(3), idempotency_key: i }] }) + } + chatService.cullOlderMessages(chat) + expect(chat.messages.length).to.eql(50) + expect(chat.messages[0].id).to.eql('a0.051') + expect(chat.minId).to.eql('a0.051') + expect(chat.messages[49].id).to.eql('a0.100') + expect(Object.keys(chat.idIndex).length).to.eql(50) + }) + }) }) diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js index a3f49b2c..759539e0 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -315,7 +315,7 @@ describe('API Entities normalizer', () => { it('converts IDN to unicode and marks it as internatonal', () => { const user = makeMockUserMasto({ acct: 'lain@xn--lin-6cd.com' }) - expect(parseUser(user)).to.have.property('screen_name').that.equal('lain@🌏lаin.com') + expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@🌏lаin.com') }) }) @@ -7842,9 +7842,10 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" -shelljs@^0.7.4: - version "0.7.8" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" +shelljs@^0.8.4: + version "0.8.4" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" + integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== dependencies: glob "^7.0.0" interpret "^1.0.0" @@ -8922,10 +8923,6 @@ void-elements@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" -vue-chat-scroll@^1.2.1: - version "1.3.5" - resolved "https://registry.yarnpkg.com/vue-chat-scroll/-/vue-chat-scroll-1.3.5.tgz#a5ee5bae5058f614818a96eac5ee3be4394a2f68" - vue-eslint-parser@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1" |
