From 6be72a3ec6d87af4de245b96fea45191ac01114e Mon Sep 17 00:00:00 2001 From: SerCom_KC Date: Sat, 18 Nov 2017 13:35:11 +0800 Subject: Updating Chinese (Simplified) translations (#5725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * i18n: (zh-CN) Improve translations * i18n: (zh-CN) Change `静音` to `隐藏` * i18n: (zh-CN) Add translations for #5087 & #5669 * i18n: (zh-CN) Improve translations * i18n: (zh-CN) Improve translations * i18n: (zh-CN) Improve translations * i18n: (zh-CN) Improve translations * i18n: (zh-CN) Add missing translation for #5728 --- app/javascript/mastodon/locales/zh-CN.json | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 0f4bbb7c1..bbdf34d2f 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -1,6 +1,6 @@ { "account.block": "屏蔽 @{name}", - "account.block_domain": "隐藏一切来自 {domain} 的嘟文", + "account.block_domain": "隐藏来自 {domain} 的内容", "account.disclaimer_full": "此处显示的信息可能不是全部内容。", "account.edit_profile": "修改个人资料", "account.follow": "关注", @@ -9,15 +9,17 @@ "account.follows_you": "关注了你", "account.media": "媒体", "account.mention": "提及 @{name}", - "account.mute": "静音 @{name}", + "account.mute": "隐藏 @{name}", + "account.mute_notifications": "隐藏来自 @{name} 的通知", + "account.unmute_notifications": "不再隐藏来自 @{name} 的通知", "account.posts": "嘟文", "account.report": "举报 @{name}", "account.requested": "正在等待对方同意。点击以取消发送关注请求", "account.share": "分享 @{name} 的个人资料", "account.unblock": "不再屏蔽 @{name}", - "account.unblock_domain": "不再隐藏 {domain}", + "account.unblock_domain": "不再隐藏来自 {domain} 的内容", "account.unfollow": "取消关注", - "account.unmute": "不再静音 @{name}", + "account.unmute": "不再隐藏 @{name}", "account.view_full_profile": "查看完整资料", "boost_modal.combo": "下次按住 {combo} 即可跳过此提示", "bundle_column_error.body": "载入组件出错。", @@ -31,7 +33,7 @@ "column.favourites": "收藏过的嘟文", "column.follow_requests": "关注请求", "column.home": "主页", - "column.mutes": "被静音的用户", + "column.mutes": "被隐藏的用户", "column.notifications": "通知", "column.pins": "置顶嘟文", "column.public": "跨站公共时间轴", @@ -57,10 +59,10 @@ "confirmations.block.message": "想好了,真的要屏蔽 {name}?", "confirmations.delete.confirm": "删除", "confirmations.delete.message": "想好了,真的要删除这条嘟文?", - "confirmations.domain_block.confirm": "隐藏整个网站", - "confirmations.domain_block.message": "你真的真的确定要隐藏整个 {domain}?多数情况下,屏蔽或静音几个特定的用户就应该能满足你的需要了。", - "confirmations.mute.confirm": "静音", - "confirmations.mute.message": "想好了,真的要静音 {name}?", + "confirmations.domain_block.confirm": "隐藏整个网站的内容", + "confirmations.domain_block.message": "你真的真的确定要隐藏所有来自 {domain} 的内容吗?多数情况下,屏蔽或隐藏几个特定的用户就应该能满足你的需要了。", + "confirmations.mute.confirm": "隐藏", + "confirmations.mute.message": "想好了,真的要隐藏 {name}?", "confirmations.unfollow.confirm": "取消关注", "confirmations.unfollow.message": "确定要取消关注 {name} 吗?", "embed.instructions": "要在你的网站上嵌入这条嘟文,请复制以下代码。", @@ -104,6 +106,7 @@ "loading_indicator.label": "加载中……", "media_gallery.toggle_visible": "切换显示/隐藏", "missing_indicator.label": "找不到内容", + "mute_modal.hide_notifications": "隐藏来自这个用户的通知", "navigation_bar.blocks": "被屏蔽的用户", "navigation_bar.community_timeline": "本站时间轴", "navigation_bar.edit_profile": "修改个人资料", @@ -111,7 +114,7 @@ "navigation_bar.follow_requests": "关注请求", "navigation_bar.info": "关于本站", "navigation_bar.logout": "注销", - "navigation_bar.mutes": "被静音的用户", + "navigation_bar.mutes": "被隐藏的用户", "navigation_bar.pins": "置顶嘟文", "navigation_bar.preferences": "首选项", "navigation_bar.public_timeline": "跨站公共时间轴", @@ -184,7 +187,7 @@ "status.media_hidden": "隐藏媒体内容", "status.mention": "提及 @{name}", "status.more": "更多", - "status.mute_conversation": "静音此对话", + "status.mute_conversation": "隐藏此对话", "status.open": "展开嘟文", "status.pin": "在个人资料页面置顶", "status.reblog": "转嘟", @@ -197,7 +200,7 @@ "status.share": "分享", "status.show_less": "隐藏内容", "status.show_more": "显示内容", - "status.unmute_conversation": "不再静音此对话", + "status.unmute_conversation": "不再隐藏此对话", "status.unpin": "在个人资料页面取消置顶", "tabs_bar.compose": "撰写", "tabs_bar.federated_timeline": "跨站", -- cgit From 58cede4808baa4db6cc143b80ef23e8179a8415b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 18 Nov 2017 19:39:02 +0100 Subject: Profile redirect notes (#5746) * Serialize moved accounts into REST and ActivityPub APIs * Parse federated moved accounts from ActivityPub * Add note about moved accounts to public profiles * Add moved account message to web UI * Fix code style issues --- .../mastodon/features/account/components/header.js | 7 ++- .../features/account_timeline/components/header.js | 3 ++ .../account_timeline/components/moved_note.js | 48 +++++++++++++++++ app/javascript/mastodon/reducers/accounts.js | 5 ++ app/javascript/mastodon/selectors/index.js | 8 ++- app/javascript/styles/mastodon/components.scss | 49 +++++++++++++++++ app/javascript/styles/mastodon/landing_strip.scss | 63 ++++++++++++++++++++++ app/lib/activitypub/adapter.rb | 1 + app/models/account.rb | 8 +++ app/serializers/activitypub/actor_serializer.rb | 8 +++ app/serializers/rest/account_serializer.rb | 4 ++ .../activitypub/process_account_service.rb | 7 +++ app/views/accounts/_header.html.haml | 2 +- app/views/accounts/_moved_strip.html.haml | 17 ++++++ app/views/accounts/show.html.haml | 2 + config/locales/en.yml | 1 + ...18012443_add_moved_to_account_id_to_accounts.rb | 6 +++ db/schema.rb | 4 +- 18 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 app/javascript/mastodon/features/account_timeline/components/moved_note.js create mode 100644 app/views/accounts/_moved_strip.html.haml create mode 100644 db/migrate/20171118012443_add_moved_to_account_id_to_accounts.rb (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index f0d2d481f..b2399ae9b 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -7,6 +7,7 @@ import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { autoPlayGif, me } from '../../../initial_state'; +import classNames from 'classnames'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -102,6 +103,10 @@ export default class Header extends ImmutablePureComponent { } } + if (account.get('moved')) { + actionBtn = ''; + } + if (account.get('locked')) { lockedIcon = ; } @@ -110,7 +115,7 @@ export default class Header extends ImmutablePureComponent { const displayNameHtml = { __html: account.get('display_name_html') }; return ( -
+
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 8cf7b92ca..5e251c0e5 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -5,6 +5,7 @@ import InnerHeader from '../../account/components/header'; import ActionBar from '../../account/components/action_bar'; import MissingIndicator from '../../../components/missing_indicator'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import MovedNote from './moved_note'; export default class Header extends ImmutablePureComponent { @@ -68,6 +69,8 @@ export default class Header extends ImmutablePureComponent { return (
+ {account.get('moved') && } + { + if (e.button === 0) { + e.preventDefault(); + this.context.router.history.push(`/accounts/${this.props.to.get('id')}`); + } + + e.stopPropagation(); + } + + render () { + const { from, to } = this.props; + const displayNameHtml = { __html: from.get('display_name_html') }; + + return ( +
+
+
+ }} /> +
+ + +
+ +
+
+ ); + } + +} diff --git a/app/javascript/mastodon/reducers/accounts.js b/app/javascript/mastodon/reducers/accounts.js index 8a4d69f26..5891d0fdd 100644 --- a/app/javascript/mastodon/reducers/accounts.js +++ b/app/javascript/mastodon/reducers/accounts.js @@ -59,6 +59,11 @@ const normalizeAccount = (state, account) => { account.display_name_html = emojify(escapeTextContentForBrowser(displayName)); account.note_emojified = emojify(account.note); + if (account.moved) { + state = normalizeAccount(state, account.moved); + account.moved = account.moved.id; + } + return state.set(account.id, fromJS(account)); }; diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index d26d1b727..e47ec5183 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -4,14 +4,18 @@ import { List as ImmutableList } from 'immutable'; const getAccountBase = (state, id) => state.getIn(['accounts', id], null); const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null); const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null); +const getAccountMoved = (state, id) => state.getIn(['accounts', state.getIn(['accounts', id, 'moved'])]); export const makeGetAccount = () => { - return createSelector([getAccountBase, getAccountCounters, getAccountRelationship], (base, counters, relationship) => { + return createSelector([getAccountBase, getAccountCounters, getAccountRelationship, getAccountMoved], (base, counters, relationship, moved) => { if (base === null) { return null; } - return base.merge(counters).set('relationship', relationship); + return base.merge(counters).withMutations(map => { + map.set('relationship', relationship); + map.set('moved', moved); + }); }); }; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 0ded6f159..b70769e18 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -917,6 +917,18 @@ background-position: center; position: relative; + &.inactive { + opacity: 0.5; + + .account__header__avatar { + filter: grayscale(100%); + } + + .account__header__username { + color: $ui-primary-color; + } + } + & > div { background: rgba(lighten($ui-base-color, 4%), 0.9); padding: 20px 10px; @@ -4375,3 +4387,40 @@ noscript { } } } + +.account__moved-note { + padding: 14px 10px; + padding-bottom: 16px; + background: lighten($ui-base-color, 4%); + border-top: 1px solid lighten($ui-base-color, 8%); + border-bottom: 1px solid lighten($ui-base-color, 8%); + + &__message { + position: relative; + margin-left: 58px; + color: $ui-base-lighter-color; + padding: 8px 0; + padding-top: 0; + padding-bottom: 4px; + font-size: 14px; + + > span { + display: block; + overflow: hidden; + text-overflow: ellipsis; + } + } + + &__icon-wrapper { + left: -26px; + position: absolute; + } + + .detailed-status__display-avatar { + position: relative; + } + + .detailed-status__display-name { + margin-bottom: 0; + } +} diff --git a/app/javascript/styles/mastodon/landing_strip.scss b/app/javascript/styles/mastodon/landing_strip.scss index 0bf9daafd..2f7c98d54 100644 --- a/app/javascript/styles/mastodon/landing_strip.scss +++ b/app/javascript/styles/mastodon/landing_strip.scss @@ -34,3 +34,66 @@ .memoriam-strip { background: rgba($base-shadow-color, 0.7); } + +.moved-strip { + padding: 14px; + border-radius: 4px; + background: rgba(darken($ui-base-color, 7%), 0.8); + color: $ui-secondary-color; + font-weight: 400; + margin-bottom: 20px; + + strong, + a { + font-weight: 500; + } + + a { + color: inherit; + text-decoration: underline; + + &.mention { + text-decoration: none; + + span { + text-decoration: none; + } + + &:focus, + &:hover, + &:active { + text-decoration: none; + + span { + text-decoration: underline; + } + } + } + } + + &__message { + margin-bottom: 15px; + + .fa { + margin-right: 5px; + color: $ui-primary-color; + } + } + + &__card { + .detailed-status__display-avatar { + position: relative; + cursor: pointer; + } + + .detailed-status__display-name { + margin-bottom: 0; + text-decoration: none; + + span { + color: $ui-highlight-color; + font-weight: 400; + } + } + } +} diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb index 790d2025c..90d589d90 100644 --- a/app/lib/activitypub/adapter.rb +++ b/app/lib/activitypub/adapter.rb @@ -9,6 +9,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive', + 'movedTo' => 'as:movedTo', 'Hashtag' => 'as:Hashtag', 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri', diff --git a/app/models/account.rb b/app/models/account.rb index 9353c40da..f3f604eda 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -42,6 +42,7 @@ # followers_url :string default(""), not null # protocol :integer default("ostatus"), not null # memorial :boolean default(FALSE), not null +# moved_to_account_id :integer # class Account < ApplicationRecord @@ -100,6 +101,9 @@ class Account < ApplicationRecord has_many :list_accounts, inverse_of: :account, dependent: :destroy has_many :lists, through: :list_accounts + # Account migrations + belongs_to :moved_to_account, class_name: 'Account' + scope :remote, -> { where.not(domain: nil) } scope :local, -> { where(domain: nil) } scope :without_followers, -> { where(followers_count: 0) } @@ -133,6 +137,10 @@ class Account < ApplicationRecord domain.nil? end + def moved? + moved_to_account_id.present? + end + def acct local? ? username : "#{username}@#{domain}" end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index 896d67115..622bdde0c 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -10,6 +10,8 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer has_one :public_key, serializer: ActivityPub::PublicKeySerializer + attribute :moved_to, if: :moved? + class EndpointsSerializer < ActiveModel::Serializer include RoutingHelper @@ -25,6 +27,8 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer has_one :icon, serializer: ActivityPub::ImageSerializer, if: :avatar_exists? has_one :image, serializer: ActivityPub::ImageSerializer, if: :header_exists? + delegate :moved?, to: :object + def id account_url(object) end @@ -92,4 +96,8 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer def manually_approves_followers object.locked end + + def moved_to + ActivityPub::TagManager.instance.uri_for(object.moved_to_account) + end end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 65fdb0308..bab944c5a 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -7,6 +7,10 @@ class REST::AccountSerializer < ActiveModel::Serializer :note, :url, :avatar, :avatar_static, :header, :header_static, :followers_count, :following_count, :statuses_count + has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved? + + delegate :moved?, to: :object + def id object.id.to_s end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index f93baf4b5..5ee7d89ee 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -74,6 +74,7 @@ class ActivityPub::ProcessAccountService < BaseService @account.statuses_count = outbox_total_items if outbox_total_items.present? @account.following_count = following_total_items if following_total_items.present? @account.followers_count = followers_total_items if followers_total_items.present? + @account.moved_to_account = moved_account if @json['movedTo'].present? end def after_protocol_change! @@ -137,6 +138,12 @@ class ActivityPub::ProcessAccountService < BaseService @collections[type] = nil end + def moved_account + account = ActivityPub::TagManager.instance.uri_to_resource(@json['movedTo'], Account) + account ||= ActivityPub::FetchRemoteAccountService.new.call(@json['movedTo'], id: true) + account + end + def skip_download? @account.suspended? || domain_block&.reject_media? end diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index e4c258acd..76371b656 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -1,6 +1,6 @@ .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" } .card__illustration - - unless account.memorial? + - unless account.memorial? || account.moved? - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) .controls - if current_account.following?(account) diff --git a/app/views/accounts/_moved_strip.html.haml b/app/views/accounts/_moved_strip.html.haml new file mode 100644 index 000000000..6a14a5dd3 --- /dev/null +++ b/app/views/accounts/_moved_strip.html.haml @@ -0,0 +1,17 @@ +- moved_to_account = account.moved_to_account + +.moved-strip + .moved-strip__message + = fa_icon 'suitcase' + = t('accounts.moved_html', name: content_tag(:strong, display_name(account), class: :emojify), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), TagManager.instance.url_for(moved_to_account), class: 'mention')) + + .moved-strip__card + = link_to TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'noopener' do + .detailed-status__display-avatar + .account__avatar-overlay + .account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" } + .account__avatar-overlay-overlay{ style: "background-image: url('#{account.avatar.url(:original)}')" } + + %span.display-name + %strong.emojify= display_name(moved_to_account) + %span @#{moved_to_account.acct} diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index fd8ad5530..accad5f78 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -14,6 +14,8 @@ - if @account.memorial? .memoriam-strip= t('in_memoriam_html') +- elsif @account.moved? + = render partial: 'moved_strip', locals: { account: @account } - elsif show_landing_strip? = render partial: 'shared/landing_strip', locals: { account: @account } diff --git a/config/locales/en.yml b/config/locales/en.yml index 11c34b912..cef001341 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -39,6 +39,7 @@ en: followers: Followers following: Following media: Media + moved_html: "%{name} has moved to %{new_profile_link}:" nothing_here: There is nothing here! people_followed_by: People whom %{name} follows people_who_follow: People who follow %{name} diff --git a/db/migrate/20171118012443_add_moved_to_account_id_to_accounts.rb b/db/migrate/20171118012443_add_moved_to_account_id_to_accounts.rb new file mode 100644 index 000000000..0c8a894cc --- /dev/null +++ b/db/migrate/20171118012443_add_moved_to_account_id_to_accounts.rb @@ -0,0 +1,6 @@ +class AddMovedToAccountIdToAccounts < ActiveRecord::Migration[5.1] + def change + add_column :accounts, :moved_to_account_id, :bigint, null: true, default: nil + add_foreign_key :accounts, :accounts, column: :moved_to_account_id, on_delete: :nullify + end +end diff --git a/db/schema.rb b/db/schema.rb index 345b05850..16df5d7c9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171116161857) do +ActiveRecord::Schema.define(version: 20171118012443) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -72,6 +72,7 @@ ActiveRecord::Schema.define(version: 20171116161857) do t.string "followers_url", default: "", null: false t.integer "protocol", default: 0, null: false t.boolean "memorial", default: false, null: false + t.bigint "moved_to_account_id" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower" t.index ["uri"], name: "index_accounts_on_uri" @@ -486,6 +487,7 @@ ActiveRecord::Schema.define(version: 20171116161857) do add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade add_foreign_key "account_moderation_notes", "accounts" add_foreign_key "account_moderation_notes", "accounts", column: "target_account_id" + add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify add_foreign_key "blocks", "accounts", column: "target_account_id", name: "fk_9571bfabc1", on_delete: :cascade add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade -- cgit From 53b7b81b439e96dd594499b18d95abdeee22e97b Mon Sep 17 00:00:00 2001 From: Joan Montané Date: Tue, 21 Nov 2017 15:17:28 +0100 Subject: Update and fix Catalan translation (#5773) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update activerecord.ca.yml * Update ca.yml * Update devise.ca.yml * Update doorkeeper.ca.yml * Update simple_form.ca.yml * fix syntax error for ca translation * fix syntax errors in ca translations Signed-off-by: Marcin Mikołajczak --- app/javascript/mastodon/locales/ca.json | 5 + .../mastodon/locales/defaultMessages.json | 63 +++-- config/locales/activerecord.ca.yml | 2 +- config/locales/ca.yml | 300 ++++++++++----------- config/locales/devise.ca.yml | 76 +++--- config/locales/doorkeeper.ca.yml | 86 +++--- config/locales/simple_form.ca.yml | 58 ++-- 7 files changed, 313 insertions(+), 277 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index af732921d..c4edf3058 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -9,7 +9,9 @@ "account.follows_you": "et segueix", "account.media": "Media", "account.mention": "Esmentar @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Silenciar @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Publicacions", "account.report": "Informe @{name}", "account.requested": "Esperant aprovació", @@ -18,6 +20,7 @@ "account.unblock_domain": "Mostra {domain}", "account.unfollow": "Deixar de seguir", "account.unmute": "Treure silenci de @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Veure el perfil complet", "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop", "bundle_column_error.body": "S'ha produït un error en carregar aquest component.", @@ -104,6 +107,7 @@ "loading_indicator.label": "Carregant...", "media_gallery.toggle_visible": "Alternar visibilitat", "missing_indicator.label": "No trobat", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Usuaris bloquejats", "navigation_bar.community_timeline": "Línia de temps Local", "navigation_bar.edit_profile": "Editar perfil", @@ -204,6 +208,7 @@ "tabs_bar.home": "Inici", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacions", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Arrossega i deixa anar per carregar", "upload_button.label": "Afegir multimèdia", "upload_form.description": "Descriure els problemes visuals", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index f400b283f..571a763b6 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -29,6 +29,14 @@ { "defaultMessage": "Unmute @{name}", "id": "account.unmute" + }, + { + "defaultMessage": "Mute notifications from @{name}", + "id": "account.mute_notifications" + }, + { + "defaultMessage": "Unmute notifications from @{name}", + "id": "account.unmute_notifications" } ], "path": "app/javascript/mastodon/components/account.json" @@ -283,17 +291,9 @@ "defaultMessage": "Block", "id": "confirmations.block.confirm" }, - { - "defaultMessage": "Mute", - "id": "confirmations.mute.confirm" - }, { "defaultMessage": "Are you sure you want to block {name}?", "id": "confirmations.block.message" - }, - { - "defaultMessage": "Are you sure you want to mute {name}?", - "id": "confirmations.mute.message" } ], "path": "app/javascript/mastodon/containers/status_container.json" @@ -307,6 +307,15 @@ ], "path": "app/javascript/mastodon/features/account_gallery/index.json" }, + { + "descriptors": [ + { + "defaultMessage": "{name} has moved to:", + "id": "account.moved_to" + } + ], + "path": "app/javascript/mastodon/features/account_timeline/components/moved_note.json" + }, { "descriptors": [ { @@ -317,10 +326,6 @@ "defaultMessage": "Block", "id": "confirmations.block.confirm" }, - { - "defaultMessage": "Mute", - "id": "confirmations.mute.confirm" - }, { "defaultMessage": "Hide entire domain", "id": "confirmations.domain_block.confirm" @@ -333,10 +338,6 @@ "defaultMessage": "Are you sure you want to block {name}?", "id": "confirmations.block.message" }, - { - "defaultMessage": "Are you sure you want to mute {name}?", - "id": "confirmations.mute.message" - }, { "defaultMessage": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", "id": "confirmations.domain_block.message" @@ -1207,6 +1208,27 @@ ], "path": "app/javascript/mastodon/features/ui/components/media_modal.json" }, + { + "descriptors": [ + { + "defaultMessage": "Are you sure you want to mute {name}?", + "id": "confirmations.mute.message" + }, + { + "defaultMessage": "Hide notifications from this user?", + "id": "mute_modal.hide_notifications" + }, + { + "defaultMessage": "Cancel", + "id": "confirmation_modal.cancel" + }, + { + "defaultMessage": "Mute", + "id": "confirmations.mute.confirm" + } + ], + "path": "app/javascript/mastodon/features/ui/components/mute_modal.json" + }, { "descriptors": [ { @@ -1359,6 +1381,15 @@ ], "path": "app/javascript/mastodon/features/ui/components/upload_area.json" }, + { + "descriptors": [ + { + "defaultMessage": "Your draft will be lost if you leave Mastodon.", + "id": "ui.beforeunload" + } + ], + "path": "app/javascript/mastodon/features/ui/index.json" + }, { "descriptors": [ { diff --git a/config/locales/activerecord.ca.yml b/config/locales/activerecord.ca.yml index 12e347ad9..39f6839aa 100644 --- a/config/locales/activerecord.ca.yml +++ b/config/locales/activerecord.ca.yml @@ -10,4 +10,4 @@ ca: status: attributes: reblog: - taken: del estat ja existeix + taken: de l'estat ja existeix diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 1dc6c492b..f32c8b44b 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -2,11 +2,11 @@ ca: about: about_mastodon_html: Mastodon és un servidor de xarxa social lliure i de codi obert. Una alternativa descentralitzada a plataformes comercials, que evita el risc que una única companyia monopolitzi la teva comunicació. Qualsevol pot executar Mastodon i participar sense problemes en la xarxa social. - about_this: Sobre aquesta instància + about_this: Quant a aquesta instància closed_registrations: Els registres estan actualment tancats en aquesta instància. contact: Contacte contact_missing: No configurat - contact_unavailable: N/A + contact_unavailable: N/D description_headline: Què es %{domain}? domain_count_after: altres instàncies domain_count_before: Connectat a @@ -14,47 +14,47 @@ ca:

Un bon lloc per les regles

Encara no s'ha configurat la descripció ampliada.

features: - humane_approach_body: Aprenent dels errors d'altres xarxes, Mastodon té com a objectiu fer ètiques eleccions de disseny per combatre el mal ús de les xarxes socials. + humane_approach_body: Aprenent dels errors d'altres xarxes, Mastodon té com a objectiu fer eleccions ètiques de disseny per a combatre el mal ús de les xarxes socials. humane_approach_title: Un enfocament més humà - not_a_product_body: Mastodon no és una xarxa comercial. Sense publicitat, sense mineria de dades, sense jardins amurallats. No hi ha autoritat central. + not_a_product_body: Mastodon no és una xarxa comercial. Sense publicitat, sense mineria de dades, sense jardins emmurallats. No hi ha autoritat central. not_a_product_title: Ets una persona, no un producte real_conversation_body: Amb 500 caràcters a la teva disposició i suport per a continguts granulars i avisos multimèdia, pots expressar-te de la manera que vulguis. real_conversation_title: Construït per a converses reals - within_reach_body: Diverses aplicacions per a iOS, Android i altres plataformes gràcies a un ecosistema API amable amb el desenvolupador, et permet mantenir-te al dia amb els teus amics en qualsevol lloc.. + within_reach_body: Diverses aplicacions per a iOS, Android i altres plataformes gràcies a un ecosistema API amable amb el desenvolupador, et permet mantenir-te al dia amb els amics en qualsevol lloc.. within_reach_title: Sempre a l'abast find_another_instance: Troba altres instàncies generic_description: "%{domain} és un servidor a la xarxa" hosted_on: Mastodon allotjat a %{domain} - learn_more: Aprèn més + learn_more: Més informació other_instances: Altres instàncies source_code: Codi font status_count_after: estats - status_count_before: Que han escrit + status_count_before: que han escrit user_count_after: usuaris registrats user_count_before: Tenim what_is_mastodon: Què és Mastodon? accounts: - follow: Seguir + follow: Segueix followers: Seguidors following: Seguint nothing_here: No hi ha res aquí! people_followed_by: Usuaris a qui %{name} segueix - people_who_follow: Usuaris que segueixn a %{name} + people_who_follow: Usuaris que segueixen %{name} posts: Toots - remote_follow: Seguir + remote_follow: Segueix reserved_username: El nom d'usuari està reservat - unfollow: Deixar de seguir + unfollow: Deixa de seguir admin: accounts: are_you_sure: Estàs segur? confirm: Confirma confirmed: Confirmat - disable_two_factor_authentication: Desactivar 2FA + disable_two_factor_authentication: Desactiva 2FA display_name: Nom de visualització domain: Domini - edit: Editar - email: E-mail - feed_url: URL del feed + edit: Edita + email: Correu-e + feed_url: URL del canal followers: Seguidors follows: Segueix ip: IP @@ -62,7 +62,7 @@ ca: all: Tot local: Local remote: Remot - title: Localització + title: Ubicació media_attachments: Adjunts multimèdia moderation: all: Tot @@ -76,14 +76,14 @@ ca: alphabetic: Alfabètic most_recent: Més recent title: Ordre - perform_full_suspension: Aplicar suspensió completa + perform_full_suspension: Aplica la suspensió completa profile_url: URL del perfil public: Públic push_subscription_expires: La subscripció PuSH expira - redownload: Refrescar avatar - reset: Reajustar - reset_password: Restablir la contrasenya - resubscribe: Resubscribir + redownload: Actualitza l'avatar + reset: Reajusta + reset_password: Restableix la contrasenya + resubscribe: Torna a subscriure salmon_url: URL Salmon search: Cerca show: @@ -92,45 +92,45 @@ ca: targeted_reports: Informes realitzats sobre aquest compte silence: Silenci statuses: Estats - subscribe: Subscribir + subscribe: Subscriu title: Comptes - undo_silenced: Desfer silenci - undo_suspension: Desfer suspensió + undo_silenced: Deixa de silenciar + undo_suspension: Desfés la suspensió unsubscribe: Donar-se de baixa username: Nom d'usuari web: Web domain_blocks: - add_new: Afegir nou + add_new: Afegeix created_msg: El bloqueig de domini ara s'està processant destroyed_msg: El bloqueig de domini s'ha desfet domain: Domini new: - create: Crear bloqueig + create: Crea un bloqueig hint: El bloqueig de domini no impedirà la creació de nous comptes en la base de dades, però s´aplicaran mètodes de moderació específics sobre aquests comptes severity: - desc_html: "Silenci farà les publicacions del compte invisibles a tothom que no l'estigui seguint. Suspendre eliminarà tots els continguts, multimèdia i les dades del perfil del compte." + desc_html: "Silenci farà les publicacions del compte invisibles a tothom que no l'estigui seguint. La suspencsió eliminarà tots els continguts, multimèdia i les dades del perfil del compte." noop: Cap silence: Silenci - suspend: Suspendre + suspend: Suspensió title: Nou bloqueig de domini - reject_media: Rebutjar arxius multimèdia - reject_media_hint: Elimina arxius multimèdia emmagatzamats localment i impideix descarregar cap en el futur. Irrellevant per suspensions + reject_media: Rebutja els fitxers multimèdia + reject_media_hint: Elimina els fitxers multimèdia emmagatzamats localment i impideix descarregar-ne cap en el futur. Irrellevant en les suspensions severities: noop: Cap silence: Silenci - suspend: Suspendre + suspend: Suspensió severity: Severitat show: affected_accounts: one: Un compte afectat en la base de dades other: "%{count} comptes afectats en la base de dades" retroactive: - silence: Desfer el silenci a tots els comptes existents d'aquest domini - suspend: Desfer la suspensió de tots els comptes d'aquest domini - title: Desfer el bloqueig de domini de %{domain} - undo: Desfer + silence: Desfés el silenci a tots els comptes existents d'aquest domini + suspend: Desfés la suspensió de tots els comptes d'aquest domini + title: Desfés el bloqueig de domini de %{domain} + undo: Desfés title: Bloquejos de domini - undo: Desfer + undo: Desfés instances: account_count: Comptes coneguts domain_name: Domini @@ -145,31 +145,31 @@ ca: id: ID mark_as_resolved: Marca com a resolt nsfw: - 'false': NSFW OFF - 'true': NSFW ON + 'false': APTE + 'true': NO APTE (NSFW) report: 'Informe #%{id}' report_contents: Continguts reported_account: Compte reportat reported_by: Reportat per resolved: Resolt - silence_account: Silenciar compte + silence_account: Silencia el compte status: Estat - suspend_account: Suspendre compte + suspend_account: Suspèn el compte target: Objectiu title: Informes unresolved: No resolt - view: Vista + view: Visualització settings: contact_information: - email: Introduir una adreça de correu electrònic pùblica - username: Introduir un nom d'usuari + email: Introdueix una adreça de correu electrònic pùblica + username: Introdueix un nom d'usuari registrations: closed_message: - desc_html: Apareix en la primera pàgina quan es tanquen els registres
Pot utilitzar etiquetes HTML + desc_html: Apareix en la primera pàgina quan es tanquen els registres
Pots utilitzar etiquetes HTML title: Missatge de registre tancat deletion: desc_html: Permet a qualsevol esborrar el seu compte - title: Obrir la supressió del compte + title: Obre la supressió del compte open: desc_html: Permet que qualsevol pugui crear un compte title: Registre obert @@ -177,8 +177,8 @@ ca: desc_html: Es mostra com un paràgraf a la pàgina principal i s'utilitza com una etiqueta meta.
Pots utilitzar etiquetes HTML, en particular <a> i <em>. title: Descripció del lloc site_description_extended: - desc_html: Un bon lloc per al vostre codi de conducta, regles, directrius i altres coses que distingeixen la vostra instància. Podeu utilitzar etiquetes HTML - title: Descripció estesa del lloc + desc_html: Un bon lloc per al codi de conducta, regles, directrius i altres coses que distingeixen la vostra instància. Pots utilitzar etiquetes HTML + title: Descripció ampliada del lloc site_terms: desc_html: Pots escriure la teva pròpia política de privadesa, els termes del servei o d'altres normes legals. Pots utilitzar etiquetes HTML title: Termes del servei personalitzats @@ -191,17 +191,17 @@ ca: back_to_account: Torna a la pàgina del compte batch: delete: Esborra - nsfw_off: NSFW OFF - nsfw_on: NSFW ON + nsfw_off: APTE + nsfw_on: NO APTE (NSFW) execute: Executa failed_to_execute: No s'ha pogut executar media: - hide: Amaga multimèdia - show: Mostra multimèdia - title: Multimèdia - no_media: Sense multimèdia + hide: Amaga el contingut multimèdia + show: Mostra el contingut multimèdia + title: Contingut multimèdia + no_media: Sense contingut multimèdia title: Estats del compte - with_media: Amb multimèdia + with_media: Amb contingut multimèdia subscriptions: callback_url: Callback URL confirmed: Confirmat @@ -216,127 +216,127 @@ ca: subject: Nou informe per a %{instance} (#%{id}) application_mailer: salutation: "%{name}," - settings: 'Canviar preferències de correu: %{link}' - signature: Notificacions de Mastodon desde %{instance} + settings: 'Canvia les preferències de correu: %{link}' + signature: Notificacions de Mastodon des de %{instance} view: 'Vista:' applications: - invalid_url: La URL proporcionada es incorrecte + invalid_url: L'URL proporcionat no és correcte auth: - agreement_html: En inscriure't, acceptes les nostres termes del servei i la nostra política de privadesa. - change_password: Canviar contrasenya - delete_account: Esborrar el compte - delete_account_html: Si vols esborrar el teu compte pots fer-ho aquí. S'et demanarà confirmació. - didnt_get_confirmation: No vas rebre el correu de confirmació? + agreement_html: En inscriure't, acceptes els nostres termes del servei i la nostra política de privadesa. + change_password: Canvia la contrasenya + delete_account: Esborra el compte + delete_account_html: Si vols esborrar el teu compte pots fer-ho aquí. Se't demanarà confirmació. + didnt_get_confirmation: No ha rebut el correu de confirmació? forgot_password: Has oblidat la contrasenya? - invalid_reset_password_token: L'enllaç de restabliment de la contrasenya no és vàlid o caducat. Siusplau torna-ho a provar.. - login: Iniciar sessió - logout: Tancar sessió - register: Enregistrarse - resend_confirmation: Tornar a enviar el correu de confirmació - reset_password: Restablir contrasenya - set_new_password: Establir nova contrasenya + invalid_reset_password_token: L'enllaç de restabliment de la contrasenya no és vàlid o ha caducat. Torna-ho a provar.. + login: Inicia sessió + logout: Tanca sessió + register: Registra't + resend_confirmation: Torna a enviar el correu de confirmació + reset_password: Restableix la contrasenya + set_new_password: Estableix nova contrasenya authorize_follow: error: Malauradament, ha ocorregut un error buscant el compte remot - follow: Seguir + follow: Segueix follow_request: 'Heu enviat una sol·licitud de seguiment a:' - following: 'Èxit! Ara segueixes:' + following: 'Perfecte! Ara segueixes:' post_follow: close: O bé, pots tancar aquesta finestra. return: Torna al perfil de l'usuari - web: Anar a la web - title: Seguir %{acct} + web: Vés a la web + title: Segueix %{acct} datetime: distance_in_words: - about_x_hours: "%{count}h" - about_x_months: "%{count}m" - about_x_years: "%{count}y" - almost_x_years: "%{count}y" + about_x_hours: "%{count} h" + about_x_months: "%{count} mesos" + about_x_years: "%{count} anys" + almost_x_years: "%{count}anys" half_a_minute: Ara mateix less_than_x_minutes: "%{count}m" less_than_x_seconds: Ara mateix - over_x_years: "%{count}y" - x_days: "%{count}d" - x_minutes: "%{count}m" - x_months: "%{count}m" - x_seconds: "%{count}s" + over_x_years: "%{count} anys" + x_days: "%{count} dies" + x_minutes: "%{count} min" + x_months: "%{count} mesos" + x_seconds: "%{count} s" deletes: - bad_password_msg: Bon intent hackers! Contrasenya incorrecta - confirm_password: Introdueix la contrasenya actual per verificar la teva identitat + bad_password_msg: Bon intent hackers! La contrasenya no és correcta + confirm_password: Introdueix la contrasenya actual per a verificar la teva identitat description_html: Això eliminarà de forma irreversible i permanent el contingut del teu compte i el desactivarà. El teu nom d'usuari romandrà reservat per evitar que algú volgués fer-se passar per tu. - proceed: Esborrar el compte - success_msg: El teu compte s'ha eliminat correctament - warning_html: Només està garantida l'eliminació d'aquesta particular instància. El contingut que ha estat àmpliament compartit que deixi petjades. Els servidors fora de línia i els que ja no estan subscrits no actualitzaran les seves bases de dades. + proceed: Esborra el compte + success_msg: El compte s'ha eliminat correctament + warning_html: Només està garantida l'eliminació en aquesta instància en particular. El contingut que ha estat àmpliament compartit que deixi petjades. Els servidors fora de línia i els que ja no estan subscrits no actualitzaran les seves bases de dades. warning_title: Disponibilitat de contingut disseminat errors: - '403': No tens permís per veure aquesta pàgina. - '404': La pàgina que estàs buscant no existeix. - '410': La pàgina que estaves buscant ja no existeix. + '403': No tens permís per a veure aquesta pàgina. + '404': La pàgina que estàs cercant no existeix. + '410': La pàgina que estàs cercant ja no existeix. '422': - content: La verificació de seguretat ha fallat. Bloquejes les galetes? + content: La verificació de seguretat ha fallat. Bloques les galetes? title: La verificació de seguretat ha fallat '429': Estrangulat - noscript_html: Per utilitzar Mastodon si us plau activa JavaScript. + noscript_html: Activa JavaScript per a utilitzar Mastodon. exports: blocks: Persones que has bloquejat csv: CSV follows: Persones que segueixes - mutes: Persones apagades (muted) + mutes: Persones silenciades storage: Emmagatzematge followers: domain: Domini - explanation_html: Si desitges garantir la privacitat de les teves publicacions, has de ser conscient de qui t'està seguint. Les teves publicacions privades es lliuren a totes les instàncies on es té seguidors . És possible que vulguis revisar-los i eliminar seguidors si no confies en que la teva privacitat sigui respectada pel personal o el programari d'aquests casos + explanation_html: Si desitges garantir la privacitat de les teves publicacions, has de ser conscient de qui t'està seguint. Les publicacions privades es lliuren a totes les instàncies on tens seguidors . És possible que vulguis revisar-los i eliminar seguidors si no confies en que la teva privacitat sigui respectada pel personal o el programari d'aquests casos followers_count: Nombre de seguidors - lock_link: Bloqueja el teu compte - purge: Eliminar de seguidors + lock_link: Bloca el teu compte + purge: Elimina dels seguidors success: one: En el procés de bloqueig suau de seguidors d'un domini... other: En el procés de bloqueig suau de seguidors de %{count} dominis... - true_privacy_html: Si us plau considera que la autèntica privacitat només es pot aconseguir amb xifrat d'extrem a extrem. - unlocked_warning_html: Tothom pot seguir-te per veure inmediatament les teves publicacions privades. %{lock_link} per poder revisar i rebutjar seguidors. - unlocked_warning_title: El teu compte no està bloquejat + true_privacy_html: Considera que la autèntica privacitat només es pot aconseguir amb xifratge d'extrem a extrem. + unlocked_warning_html: Tothom pot seguir-te per a veure inmediatament les teves publicacions privades. %{lock_link} per poder revisar i rebutjar seguidors. + unlocked_warning_title: El teu compte no està blocat generic: - changes_saved_msg: Canvis guardats amb èxit! - powered_by: powered by %{link} - save_changes: Guardar canvis + changes_saved_msg: Els canvis s'han desat correctament! + powered_by: amb tecnologia %{link} + save_changes: Desa els canvis validation_errors: - one: Alguna cosa no esta bé! Si us plau, revisi l'error - other: Alguna cosa no esta bé! Si us plau, revisi %{count} errors més a baix + one: Alguna cosa no va bé! Si us plau, revisa l'error + other: Alguna cosa no va bé! Si us plau, revisa %{count} errors més a baix imports: - preface: Pots importar algunes dades, com ara totes les persones que estàs seguint o bloquejant, en el teu compte en aquesta instància, desde arxius exportats desde una altra instància. - success: Dades rebudes correctament i seran processades en breu + preface: Pots importar algunes dades, com ara totes les persones que estàs seguint o blocant, en el teu compte en aquesta instància, des de fitxers exportats en una altra instància. + success: Les dades s'han rebut correctament i es processaran en breu types: - blocking: Llista de bloqueajats + blocking: Llista de blocats following: Llista de seguits - muting: Llista d'apagats - upload: Carregar - landing_strip_html: "%{name} és un usuari/a de %{link_to_root_path}. Pots seguir-lo/la o interactuar amb ell/a si tens un compte a qualsevol node del fediverse." + muting: Llista de silenciats + upload: Carrega + landing_strip_html: "%{name} és un usuari/a de %{link_to_root_path}. Pots seguir-lo o interactuar amb ell si tens un compte a qualsevol node del fediverse." landing_strip_signup_html: Si no en tens, pots registrar-te aquí. media_attachments: validations: images_and_video: No es pot adjuntar un vídeo a una publicació que ja contingui imatges - too_many: No es poden adjuntar més de 4 arxius + too_many: No es poden adjuntar més de 4 fitxers notification_mailer: digest: - body: 'Un resum del que et vas perdre en %{instance} desde la teva darrera visita el %{since}:' + body: 'Un resum del que et vas perdre en %{instance} desde la darrera visita el %{since}:' mention: "%{name} t'ha mencionat en:" new_followers_summary: one: Visca!. Algú més t´ha començat a seguir - other: Genial!. T'han seguit %{count} noves persones + other: Genial!. Et segueixen %{count} persones més subject: - one: "1 nova notificació desde la teva darrera visita \U0001F418" - other: "%{count} noves notificacions desde la teva darrera visita \U0001F418" + one: "1 notificació nova des de la darrera visita \U0001F418" + other: "%{count} notificacions noves des de la darrera visita \U0001F418" favourite: - body: 'El teu estat ha estat marcat com a favorit per %{name}:' + body: "%{name} ha marcat com a favorit el teu estat:" subject: "%{name} ha marcat com a favorit el teu estat" follow: - body: "¡%{name} t'està seguint!" + body: "%{name} t'està seguint!" subject: "%{name} t'està seguint" follow_request: body: "%{name} ha sol·licitat seguir-te" subject: 'Seguidor pendent: %{name}' mention: - body: 'Has estat mencionat per %{name} en:' - subject: Has estat mencionat per %{name} + body: "%{name} t'ha mencionat en:" + subject: "%{name} t'ha mencionat" reblog: body: "%{name} ha retootejat el teu estat" subject: "%{name} ha retootejat el teu estat" @@ -348,31 +348,31 @@ ca: billion: B million: M quadrillion: Q - thousand: K + thousand: m trillion: T unit: '' pagination: - next: Pròxim - prev: Anterior + next: Següent + prev: Enrere truncate: "…" push_notifications: favourite: - title: "%{name} ha afavorit el teu estat" + title: "%{name} ha marcat com a preferit el teu estat" follow: title: "%{name} ara et segueix" group: title: "%{count} notificacions" mention: - action_boost: Boost - action_expand: Mostra més - action_favourite: Favorit + action_boost: Retooteja + action_expand: Mostra'n més + action_favourite: Preferit title: "%{name} t'ha mencionat" reblog: title: "%{name} t'ha retootejat" remote_follow: - acct: Escriu el usuari@domini de la persona que vols seguir - missing_resource: No s'ha pogut trobar la URL de redirecció necessaria per el compte. - proceed: Procedir a seguir + acct: Escriu l'usuari@domini de la persona que vols seguir + missing_resource: No s'ha pogut trobar la URL de redirecció necessaria per al compte. + proceed: Comença a seguir prompt: 'Seguiràs a:' sessions: activity: Última activitat @@ -395,7 +395,7 @@ ca: weibo: Weibo current_session: Sessió actual description: "%{browser} de %{platform}" - explanation: Aquests són els navegadors web que actualment han iniciat la sessió al teu compte de Mastodon. + explanation: Aquests són els navegadors web que actualment han iniciat la sessió amb el teu compte de Mastodon. ip: IP platforms: adobe_air: Adobe Air @@ -415,29 +415,29 @@ ca: title: Sessions settings: authorized_apps: Aplicacions autoritzades - back: Tornar al inici + back: Torna a l'inici delete: Eliminació del compte - edit_profile: Editar perfil - export: Exportar informació + edit_profile: Edita el perfil + export: Exporta la informació followers: Seguidors autoritzats - import: Importar + import: Importa preferences: Preferències settings: Configuració two_factor_authentication: Autenticació de dos factors statuses: - open_in_web: Obrir en la web + open_in_web: Obre en la web over_character_limit: Límit de caràcters de %{max} superat - show_more: Mostrar més + show_more: Mostra'n més visibilities: private: Només seguidors - private_long: Només mostrar a seguidors + private_long: Mostra només als seguidors public: Públic public_long: Tothom pot veure-ho unlisted: No llistat unlisted_long: Tothom ho pot veure, però no es mostra en la història federada stream_entries: - click_to_show: Clic per mostrar - reblogged: retooteado + click_to_show: Clic per a mostrar + reblogged: retootejat sensitive_content: Contingut sensible terms: body_html: "

Política de privacitat

\n\n

Quina informació recollim?

\n\n

Recopilem informació teva quan et registres en aquesta instància i recopilem dades quan participes en el fòrum llegint, escrivint i avaluant el contingut aquí compartit.

\n\n

En registrar-te en aquesta instància, se't pot demanar que introduexisu el teu nom i l'adreça de correu electrònic. També pots visitar el nostre lloc sense registrar-te. La teva adreça de correu electrònic es verificarà mitjançant un correu electrònic que conté un enllaç únic. Si es visita aquest enllaç, sabem que controles l'adreça de correu electrònic.

\n\n

Quan es registra i publica, registrem l'adreça IP de la qual es va originar la publicació. També podrem conservar els registres del servidor que inclouen l'adreça IP de cada sol·licitud al nostre servidor.

\n\n

Per a què utilitzem la teva informació?

\n\n

Qualsevol de la informació que recopilem de tu pot utilitzar-se d'una de les maneres següents:

\n\n
    \n
  • Per a personalitzar la teva experiència — la teva informació ens ajuda a respondre millor a les teves necessitats individuals.
  • \n
  • Per millorar el nostre lloc — ens esforcem contínuament per millorar les nostres ofertes de llocs basats en la informació i els comentaris que rebem de tu.
  • \n
  • Per millorar el servei al client — la teva informació ens ajuda a respondre més eficaçment a les teves sol·licituds de servei al client i a les necessitats de suport.
  • \n
  • Per enviar correus electrònics periòdics — l'adreça electrònica que proporcionis es pot utilitzar per enviar-te informació, notificacions que sol·licitis sobre canvis en temes o en resposta al teu nom d'usuari, respondre a les consultes i/o altres sol·licituds o preguntes.
  • \n
\n\n

Com protegim la teva informació?

\n\n

Implementem diverses mesures de seguretat per mantenir la seguretat de la teva informació personal quan introdueixes, envies o accedeixes a la teva informació personal.

\n\n

Quina és la nostre política de retenció de dades?

\n\n

Farem un esforç de bona fe per a:

\n\n
    \n
  • Conserva els registres de servidor que continguin l'adreça IP de totes les sol·licituds a aquest servidor no més de 90 dies.
  • \n
  • Conserva les adreces IP associades als usuaris registrats i les seves publicacions no més de 5 anys.
  • \n
\n\n

Utilitzem galetes?

\n\n

Sí. Les cookies són fitxers petits que un lloc o el proveïdor de serveis transfereix al disc dur del vostre ordinador a través del navegador web (si ho permet). Aquestes galetes permeten al lloc reconèixer el vostre navegador i, si teniu un compte registrat, associar-lo al vostre compte registrat.

\n\n

Utilitzem cookies per comprendre i desar les vostres preferències per a futures visites i compilar dades agregades sobre el trànsit del lloc i la interacció del lloc, de manera que podrem oferir millors experiències i eines del lloc en el futur. Podem contractar amb proveïdors de serveis de tercers per ajudar-nos a comprendre millor els visitants del nostre lloc. Aquests proveïdors de serveis no estan autoritzats a utilitzar la informació recollida en nom nostre, excepte per ajudar-nos a dur a terme i millorar el nostre negoci.

\n\n

Publiquem informació al exterior?

\n\n

No venem, comercialitzem ni transmetem a tercers la vostra informació d'identificació personal. Això no inclou tercers de confiança que ens ajudin a operar el nostre lloc, a dur a terme el nostre negoci o a fer-ho, sempre que aquestes parts acceptin mantenir confidencial aquesta informació. També podem publicar la vostra informació quan creiem que l'alliberament és apropiat per complir amb la llei, fer complir les polítiques del nostre lloc o protegir els nostres drets o altres drets, propietat o seguretat. No obstant això, la informació de visitant que no sigui personalment identificable es pot proporcionar a altres parts per a la comercialització, la publicitat o altres usos.

\n\n

Vincles de tercers

\n\n

De tant en tant, segons el nostre criteri, podem incloure o oferir productes o serveis de tercers al nostre lloc. Aquests llocs de tercers tenen polítiques de privadesa separades i independents. Per tant, no tenim responsabilitat ni responsabilitat civil pel contingut i les activitats d'aquests llocs enllaçats. No obstant això, busquem protegir la integritat del nostre lloc i donem la benvinguda a qualsevol comentari sobre aquests llocs.

\n\n

Compliment de la Llei de protecció de la privacitat en línia dels nens

\n\n

El nostre lloc, productes i serveis estan dirigits a persones que tenen almenys 13 anys. Si aquest servidor es troba als EUA, i teniu menys de 13 anys, segons els requisits de COPPA (Children's Online Privacy Protection Act) no feu servir aquest lloc.

\n\n

Només la política de privacitat en línia

\n\n

Aquesta política de privacitat en línia només s'aplica a la informació recopilada a través del nostre lloc i no a la informació recopilada fora de línia.

\n\n

El vostre consentiment

\n\n

En utilitzar el nostre lloc, accepta la política de privadesa del nostre lloc web.

\n\n

Canvis a la nostra política de privacitat

\n\n

Si decidim canviar la nostra política de privadesa, publicarem aquests canvis en aquesta pàgina.

\n\n

Aquest document és CC-BY-SA. Es va actualitzar per última vegada el 31 de maig de 2013.

\n\n

Originalment adaptat a la política de privadesa del Discurs.

\n" @@ -447,21 +447,21 @@ ca: default: "%b %d, %Y, %H:%M" two_factor_authentication: code_hint: Introdueix el codi generat per l'aplicació autenticadora per a confirmar - description_html: Si habilites la autenticació de dos factors, et caldrà tenir el teu telèfon, que generarà tokens per a que puguis iniciar sessió. - disable: Deshabilitarr - enable: Habilitar + description_html: Si habilites l'autenticació de dos factors, et caldrà tenir el teu telèfon, que generarà tokens per a que puguis iniciar sessió. + disable: Desactiva + enable: Activa enabled: Autenticació de dos factors activada - enabled_success: Autenticació de dos factors activada amb èxit - generate_recovery_codes: Generar codis de recuperació + enabled_success: Autenticació de dos factors activada correctament + generate_recovery_codes: Genera codis de recuperació instructions_html: "Escaneja aquest codi QR desde Google Authenticator o una aplicació similar del teu telèfon. Desde ara, aquesta aplicació generarà tokens que tens que ingresar quan volguis iniciar sessió." - lost_recovery_codes: Els codis de recuperació et permeten recuperar l'accés al teu compte si perds el telèfon. Si has perdut els teus codis de recuperació els pots regenerar aquí. Els codis de recuperació anteriors seran anul·lats. - manual_instructions: 'Si no pots escanejar el codi QR code i necessites introduir-lo manualment, aquí tens el secret en text plà:' + lost_recovery_codes: Els codis de recuperació et permeten recuperar l'accés al teu compte si perds el telèfon. Si has perdut els codis de recuperació els pots tornar a generar aquí. Els codis de recuperació anteriors s'anul·laran. + manual_instructions: 'Si no pots escanejar el codi QR code i necessites introduir-lo manualment, aquí tens el secret en text pla:' recovery_codes: Codis de recuperació de còpia de seguretat recovery_codes_regenerated: Codis de recuperació regenerats amb èxit - recovery_instructions_html: Si alguna vegada perds l'accéss al telèfon pots utilitzar un dels codis de recuperació a continuació per recuperar l'accés al teu compte. Cal mantenir els codis de recuperació en lloc segur, per exemple imprimint-los i guardar-los amb altres documents importants. + recovery_instructions_html: Si mai perds l'accéss al telèfon pots utilitzar un dels codis de recuperació a continuació per a recuperar l'accés al teu compte. Cal mantenir els codis de recuperació en lloc segur, per exemple imprimint-los i guardar-los amb altres documents importants. setup: Establir - wrong_code: El codi introduït es invalid! Es correcta la hora del servidor i del dispositiu? + wrong_code: El codi introduït no és vàlid! És correcta l'hora del servidor i del dispositiu? users: - invalid_email: La direcció de correu es incorrecte - invalid_otp_token: Codi de dos factors incorrecte + invalid_email: L'adreça de correu no és correcta + invalid_otp_token: El codi de dos factors no és correcte signed_in_as: 'Sessió iniciada com a:' diff --git a/config/locales/devise.ca.yml b/config/locales/devise.ca.yml index 4ff51b6e0..fe95c402d 100644 --- a/config/locales/devise.ca.yml +++ b/config/locales/devise.ca.yml @@ -2,60 +2,60 @@ ca: devise: confirmations: - confirmed: La seva adreça de correu ha estat confirmada amb èxit. - send_instructions: Rebrà un correu electrònic amb instruccions sobre com confirmar la seva adreça de correu en pocs minuts. - send_paranoid_instructions: Si la seva adreça de correu electrònic existeix en la nostre base de dades, rebrà un correu electrònic amb instruccions sobre com confirmar la seva adreça de correu en pocs minuts. + confirmed: L'adreça de correu s'ha confirmat correctament. + send_instructions: En pocs minuts rebràs un correu electrònic amb instruccions sobre com confirmar l'adreça de correu. + send_paranoid_instructions: Si l'adreça de correu electrònic existeix en la nostra base de dades, en pocs minuts rebràs un correu electrònic amb instruccions sobre com confirmar l'adreça de correu. failure: - already_authenticated: Vosté ja està registrat. - inactive: el seu compte encara no està actiu. - invalid: Invàlid %{authentication_keys} o contrassenya. - last_attempt: Té un intent més abans que el seu compte sigui bloquejat. - locked: el seu compte ha estat bloquejat. - not_found_in_database: Invàlit %{authentication_keys} o contrassenya. - timeout: la sessió ha expirat. Si us plau iniciï sessió de nou per a continuar. - unauthenticated: Necessita iniciar sessió o registrar-se abans de continuar. - unconfirmed: Ha de confirmar la seva adreça de correu electrònic abans de continuar. + already_authenticated: Ja estàs registrat. + inactive: el teu compte encara no s'ha activat. + invalid: "%{authentication_keys} o contrasenya no són vàlids." + last_attempt: Tens un intent més, abans que es bloqui el compte. + locked: el compte s'ha blocat. + not_found_in_database: "%{authentication_keys} o contrasenya no vàlids." + timeout: la sessió ha expirat. Incia sessió una altra vegada per a continuar. + unauthenticated: Cal iniciar sessió o registrar-se abans de continuar. + unconfirmed: Has de confirmar l'adreça de correu electrònic abans de continuar. mailer: confirmation_instructions: subject: 'Mastodon: Instruccions de confirmació' password_change: - subject: 'Mastodon: Contrassenya canviada' + subject: 'Mastodon: Contrasenya canviada' reset_password_instructions: subject: 'Mastodon: Instruccions per a reiniciar contrassenya' unlock_instructions: - subject: 'Mastodon: Instruccions per a desbloquejar' + subject: 'Mastodon: Instruccions per a desblocar' omniauth_callbacks: - failure: No podem autentificar-lo desde %{kind} degut a "%{reason}". - success: Autentificat amb èxit desde el compte %{kind} . + failure: No podem autentificar-te desde %{kind} degut a "%{reason}". + success: Autentificat amb èxit des del compte %{kind} . passwords: - no_token: No pot accedir aquesta pàgina sense provenir desde el correu de reinici de contrassenya. Si ve desde el correu de reinici de contrassenya, si us pla asseguri que està emprant la adreça complerta proporcionada. - send_instructions: Rebrà un correu electrònic amb instruccions sobre com reiniciar la seva contrassenya en pocs minuts. - send_paranoid_instructions: Si el seu correu electrònic existeix en la nostre base de dades, rebrà un enllaç de recuperació de contrassenya en la seva adreça de correu en pocs minuts. - updated: La seva contrassenya ha estat canviada amb èxit. Ara ja està registrat. - updated_not_active: La seva contrassenya ha estat canviada amb èxit. + no_token: No pots accedir a aquesta pàgina sense provenir des del correu de restabliment de la contrasenya. Si vens des del correu de restabliment de contrasenya, assegura't que estàs emprant l'adreça completa proporcionada. + send_instructions: Rebràs un correu electrònic amb instruccions sobre com reiniciar la contrasenya en pocs minuts. + send_paranoid_instructions: Si el seu correu electrònic existeix en la nostra base de dades, rebràs un enllaç de restabliment de contrasenya en l'adreça de correu en pocs minuts. + updated: La contrassenya s'ha canviat correctament. Ara ja estàs registrat. + updated_not_active: La contrassenya s'ha canviat correctament. registrations: - destroyed: Adéu! el seu compte ha estat cancel·lat amb èxit. Esperem veure'l aviat de nou. - signed_up: Benvingut! S'ha registrat amb èxit. - signed_up_but_inactive: S´ha registrat amb èxit. No obstant, no podem identificar-lo perque el seu compte no ha estat activat encara. - signed_up_but_locked: S´ha registrat amb èxit. No obstant, no podem identificar-lo perque el seu compte està bloquejat. + destroyed: Adéu! el compte s'ha cancel·lat amb èxit. Desitgem veure't de nou aviat. + signed_up: Benvingut! T'has registrat amb èxit. + signed_up_but_inactive: T´has registrat amb èxit. No obstant, no podem identificar-te perquè el compte encara no s'ha activat. + signed_up_but_locked: T´has registrat amb èxit. No obstant, no podem identificar-te perquè el compte està blocat. signed_up_but_unconfirmed: Un missatge amb un enllaç de confirmació ha estat enviat per correu electrònic. Si us plau segueixi l'enllaç per activar el seu compte. update_needs_confirmation: Ha actualitzat el seu compte amb èxit, però necessitem verificar la nova adreça de correu. Si us plau comprovi el correu i segueixi l'enllaç per confirmar la nova adreça de correu. updated: el seu compte ha estat actualitzat amb èxit. sessions: - already_signed_out: Ha tancat la sessió amb èxit. - signed_in: S'ha registrat amb èxit. - signed_out: Ha tancat la sessió amb èxit. + already_signed_out: Has tancat la sessió amb èxit. + signed_in: T'has registrat amb èxit. + signed_out: Has tancat la sessió amb èxit. unlocks: - send_instructions: Rebrà un correu electrònic amb instruccions sobre com desbloquejar el seu compte en pocs minuts. - send_paranoid_instructions: Si el seu compte existeix, rebrà un correu electrònic amb instruccions sobre com desbloquejarla en pocs minuts. - unlocked: El seu compte ha estat bloquejat amb èxit. Si us plau iniciï sessió per continuar. + send_instructions: Rebràs un correu electrònic amb instruccions sobre com desblocar el compte en pocs minuts. + send_paranoid_instructions: Si el compte existeix, rebràs un correu electrònic amb instruccions sobre com desblocar-lo en pocs minuts. + unlocked: El compte s'ha blocat correctament. Inicia sessió per a continuar. errors: messages: - already_confirmed: ja ha estat confirmat, si us plau intenti iniciar sessió - confirmation_period_expired: necessita ser confirmat dins de %{period}, si us plau demani una nova - expired: ha expirat, si us plau demani una nova - not_found: no trobat - not_locked: no va ser bloquejada + already_confirmed: ja està confirmat. Intenta d'iniciar sessió + confirmation_period_expired: calia fer la confirmació dins de %{period}, torna a sol·licitar-la + expired: ha expirat, demana'n una altra + not_found: no s'ha trobat + not_locked: no està blocada not_saved: - one: '1 error ha prohibit aquest %{resource} de ser guardat:' - other: "%{count} errors va prohibir aquest %{resource} de ser guardat:" + one: '1 error ha impedit desar aquest %{resource}:' + other: "%{count} errors hab impedit desar aquest %{resource}:" diff --git a/config/locales/doorkeeper.ca.yml b/config/locales/doorkeeper.ca.yml index 38dbbdde9..c2d2b79b9 100644 --- a/config/locales/doorkeeper.ca.yml +++ b/config/locales/doorkeeper.ca.yml @@ -3,7 +3,7 @@ ca: activerecord: attributes: doorkeeper/application: - name: Nombre + name: Nom redirect_uri: URI per a redirecció errors: models: @@ -11,58 +11,58 @@ ca: attributes: redirect_uri: fragment_present: no pot contenir un fragment. - invalid_uri: ha de ser una URI válid. - relative_uri: ha de ser una URI absoluta. - secured_uri: ha de ser una URI HTTPS/SSL. + invalid_uri: ha de ser un URI válid. + relative_uri: ha de ser un URI absoluta. + secured_uri: ha de ser un URI HTTPS/SSL. doorkeeper: applications: buttons: - authorize: Autoritzar - cancel: Cancel⋅lar - destroy: Destruir - edit: Editar - submit: Enviar + authorize: Autoritza + cancel: Cancel⋅la + destroy: Destrueix + edit: Edita + submit: Envia confirmations: - destroy: Està segur? + destroy: Estàs segur? edit: - title: Editar aplicació + title: Edita l'aplicació form: - error: Uuups! Comprovi el formulari + error: Ep! Comprova el formulari help: - native_redirect_uri: Utilitzi %{native_redirect_uri} per a proves locals - redirect_uri: Utilitzi una línia per URI - scopes: Separi els àmbits amb espais. Deixa-ho en blanc per utilitzar els àmbits per defecte. + native_redirect_uri: Utilitza %{native_redirect_uri} per a proves locals + redirect_uri: Utilitza una línia per URI + scopes: Separa els àmbits amb espais. Deixa-ho en blanc per a utilitzar els àmbits per defecte. index: callback_url: Callback URL name: Nom - new: Nova aplicació + new: Aplicació nova title: Les teves aplicacions new: - title: Nova aplicació + title: Aplicació nova show: actions: Accions - application_id: Id de la aplicació - callback_urls: Callback urls + application_id: Id de l'aplicació + callback_urls: Callback URL scopes: Àmbits secret: Secret title: 'Aplicació: %{name}' authorizations: buttons: - authorize: Autoritzar - deny: Desautoritzar + authorize: Autoritza + deny: Desautoritza error: - title: Ha ocorregut un error + title: S'a produit un error new: able_to: Serà capaç de - prompt: La aplicació %{client_name} sol⋅licita tenir accés al teu compte - title: Es requereix autorizació + prompt: L'aplicació %{client_name} sol⋅licita tenir accés al teu compte + title: Cal autorizació show: - title: Copy this authorization code and paste it to the application. + title: Copia aquest coddi d'autorització i enganxa'l en l'aplicació. authorized_applications: buttons: - revoke: Revocar + revoke: Revoca confirmations: - revoke: Està segur? + revoke: Estàs segur? index: application: Aplicació created_at: Creat el @@ -72,22 +72,22 @@ ca: messages: access_denied: El propietari del recurs o servidor de autorizació ha denegat la petició. credential_flow_not_configured: Les credencials de contrasenya del propietari del recurs han fallat degut a que Doorkeeper.configure.resource_owner_from_credentials està sense configurar. - invalid_client: La autentificación del cliente falló debido o a que es un cliente desconocido o no está incluída la autentificación del cliente o el método de autentificación no está confirmado. - invalid_grant: La concesión de autorización ofrecida es inválida, venció, se revocó, no coincide con la URI de redirección utilizada en la petición de autorización, o fue emitida para otro cliente. - invalid_redirect_uri: La URI de redirección incluida no es válida. - invalid_request: En la petición falta un parámetro necesario o incluye un valor de parámetro no soportado o tiene otro tipo de formato incorrecto. - invalid_resource_owner: Las credenciales del propietario del recurso proporcionado no son válidas, o el propietario del recurso no puede ser encontrado. - invalid_scope: El ámbito pedido es inválido, desconocido o erróneo. + invalid_client: La autentificació del client falló perquè és un client desconegut o no està inclòs l'autentificació del client o el mètode d'autenticació no està confirmat. + invalid_grant: La concessió d'autorizació oferida és invàlida, ha vençut, s'ha revocat, no coincideix amb l'URI de redirecció utilizada en la petició d'autorizació, o fou emesa per a un altre client. + invalid_redirect_uri: L'URI de redirecció inclòs no és vàlid. + invalid_request: En la petició manca un paràmetre necessari o inclou un valor de paràmetre no suportat o te un altre tipus de format incorrecte. + invalid_resource_owner: Les credencials del propietari del recurso proporcionat no son vàlides, o el propietari del recurs no pot ser trobat. + invalid_scope: L'àmbit demanat és invàlid, desconegut o erroni. invalid_token: - expired: El identificador de acceso finalizó. - revoked: El identificador de acceso fue revocado. - unknown: El identificador de acceso es inválido. - resource_owner_authenticator_not_configured: El propietario del recurso falló debido a que Doorkeeper.configure.resource_owner_authenticator está sin configurar. - server_error: El servidor de la autorización entontró una condición inesperada que le impidió cumplir con la solicitud. - temporarily_unavailable: El servidor de la autorización es actualmente incapaz de manejar la petición debido a una sobrecarga temporal o un trabajo de mantenimiento del servidor. - unauthorized_client: El cliente no está autorizado a realizar esta petición utilizando este método. - unsupported_grant_type: El tipo de concesión de autorización no está soportado por el servidor de autorización. - unsupported_response_type: El servidor de autorización no soporta este tipo de respuesta. + expired: L'identificador d'accés ha caducat. + revoked: L'identificador d'accés fou revocat. + unknown: L'identificador d'accés és invàlid. + resource_owner_authenticator_not_configured: El propietari del recurs ha fallat perquè Doorkeeper.configure.resource_owner_authenticator està sense configurar. + server_error: El servidor de l'autorizació ha trobat unca condició inesperada que ha impedit complir la sol·licitud. + temporarily_unavailable: El servidor de l'autorizació és actualment incapaç de gestionar la petició degut a una sobrecàrrega temporal o una tasca de manteniment del servidor. + unauthorized_client: El client no està autoritzat a fer aquesta petició utilitzant aquest mètode. + unsupported_grant_type: El tipus de concessió d'autorització no està suportat pel servidor d'autorizació. + unsupported_response_type: El servidor d'autorizació no suporta aquest tipus de resposta. flash: applications: create: @@ -107,6 +107,6 @@ ca: application: title: OAuth autorització requerida scopes: - follow: seguir, bloquejar, desbloquejar i deixar de seguir comptes + follow: seguir, blocar, desblocar i deixar de seguir comptes read: llegir les dades del teu compte write: publicar en el teu nom diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index b5b7f26d2..22902ba24 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -5,52 +5,52 @@ ca: defaults: avatar: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 120x120px display_name: - one: 1 character left - other: %{count} characters left + one: Queda 1 caràcter + other: Queden %{count} caràcters header: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 700x335px - locked: Requereix que aprovis manualment seguidors i les publicacions seran mostrades només als teus seguidors + locked: Cal que aprovis manualment els seguidors i les publicacions es mostraran només als teus seguidors note: - one: 1 character left - other: %{count} characters left + one: Queda 1 caràcgter + other: Queden %{count} caràcters imports: - data: Arxiu CSV exportat desde una altra instància de Mastodon + data: Fitxers CSV exportat des d'una altra instància de Mastodon sessions: - otp: Introdueix el codi de dos factors des del teu telèfon o utilitza un dels teus codis de recuperació. + otp: Introdueix el codi de dos factors des del telèfon o utilitza un dels teus codis de recuperació. user: - filtered_languages: Els idiomes seleccionats seran eliminats de les línies de temps públiques. + filtered_languages: Els missatges en les llengües seleccionades s'eliminaran de les línies de temps públiques. labels: defaults: avatar: Avatar - confirm_new_password: Confirmar nova contrasenya - confirm_password: Confirmar contrasenya + confirm_new_password: Confirma la contrasenya nova + confirm_password: Confirma la contrasenya current_password: Contrasenya actual data: Informació - display_name: Mostrar nom - email: Direcció de correu electrònic - header: Img. capçalera - locale: Idioma - locked: Fer privat aquest compte - new_password: Nova contrasenya + display_name: Nom visibile + email: Adreça de correu electrònic + header: Imgatge de capçalera + locale: Llengua + locked: Fes privat aquest compte + new_password: Contrasenya nova note: Biografia otp_attempt: Codi de dos factors password: Contrasenya - setting_auto_play_gif: Auto-reproducció de GIFs animats - setting_boost_modal: Mostrar finestra de confirmació abans d'un Retoot - setting_default_privacy: Privacitat de publicacions - setting_delete_modal: Mostrar finestra de confirmació abans d'esborrar un toot + setting_auto_play_gif: Reprodueix automàticament els GIF animats + setting_boost_modal: Mostra la finestra de confirmació abans d'un retoot + setting_default_privacy: Privacitat de les publicacions + setting_delete_modal: Mostra la finestra de confirmació abans d'esborrar un toot severity: Severitat - type: Importar tipus + type: Importa tipus username: Nom d´usuari interactions: - must_be_follower: Bloquejar notificacions de persones que no et segueixen - must_be_following: Bloquejar notificacions de persones que no segueixes + must_be_follower: Blocar les notificacions de persones que no et segueixen + must_be_following: Blocar les notificacions de persones que no segueixes notification_emails: - digest: Enviar resum de correus electrònics - favourite: Enviar correu electrònic quan algú marqui com a favorit en la teva publicació - follow: Enviar correu electrònic quan algú et segueixi - follow_request: Enviar correu electrònic quan algú sol·liciti seguir-te - mention: Enviar correu electrònic quan algú et mencioni - reblog: Enviar correu electrònic quan algú comparteixi la seva publicació + digest: Envia un resum de correus electrònics + favourite: Envia un correu electrònic quan algú marqui com a preferit en la teva publicació + follow: Envia un correu electrònic quan algú et segueixi + follow_request: Envia un correu electrònic quan algú sol·liciti seguir-te + mention: Envia un correu electrònic quan algú et mencioni + reblog: Envia un correu electrònic quan algú comparteixi la teva publicació 'no': 'No' required: mark: "*" -- cgit From 3c83b7e06e2999a46ebdaed212b4e7b2bc9f2f92 Mon Sep 17 00:00:00 2001 From: Marcin Mikołajczak Date: Tue, 21 Nov 2017 17:53:13 +0100 Subject: i18n: Update Polish translation - front-end added (#231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * i18n: Update Polish translation Signed-off-by: Marcin Mikołajczak * i18n: Update Polish translation Signed-off-by: Marcin Mikołajczak --- app/javascript/glitch/locales/pl.json | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 app/javascript/glitch/locales/pl.json (limited to 'app/javascript') diff --git a/app/javascript/glitch/locales/pl.json b/app/javascript/glitch/locales/pl.json new file mode 100644 index 000000000..1481b6a2a --- /dev/null +++ b/app/javascript/glitch/locales/pl.json @@ -0,0 +1,44 @@ +{ + "getting_started.open_source_notice": "Glitchsoc jest wolnym i otwartoźródłowym forkiem oprogramowania {Mastodon}. Możesz współtworzyć projekt lub zgłaszać błędy na GitHubie pod adresem {github}.", + "layout.auto": "Automatyczny", + "layout.current_is": "Twój obecny układ to:", + "layout.desktop": "Desktopowy", + "layout.mobile": "Mobilny", + "navigation_bar.app_settings": "Ustawienia aplikacji", + "getting_started.onboarding": "Rozejrzyj się", + "onboarding.page_one.federation": "{domain} jest 'instancją' Mastodona. Mastodon to sieć działających niezależnie serwerów tworzących jedną sieć społecznościową. Te serwery nazywane są instancjami.", + "onboarding.page_one.welcome": "Witamy na {domain}!", + "onboarding.page_six.github": "{domain} jest oparty na Glitchsoc. Glitchsoc jest {forkiem} {Mastodon}a kompatybilnym z każdym klientem i aplikacją Mastodona. Glitchsoc jest całkowicie wolnym i otwartoźródłowym oprogramowaniem. Możesz zgłaszać błędy i sugestie funkcji oraz współtworzyć projekt na {github}.", + "settings.auto_collapse": "Automatyczne zwijanie", + "settings.auto_collapse_all": "Wszystko", + "settings.auto_collapse_lengthy": "Długie wpisy", + "settings.auto_collapse_media": "Wpisy z zawartością multimedialną", + "settings.auto_collapse_notifications": "Powiadomienia", + "settings.auto_collapse_reblogs": "Podbicia", + "settings.auto_collapse_replies": "Odpowiedzi", + "settings.close": "Zamknij", + "settings.collapsed_statuses": "Zwijanie wpisów", + "settings.enable_collapsed": "Włącz zwijanie wpisów", + "settings.general": "Ogólne", + "settings.image_backgrounds": "Obrazy w tle", + "settings.image_backgrounds_media": "Wyświetlaj zawartość multimedialną zwiniętych wpisów", + "settings.image_backgrounds_users": "Nadaj tło zwiniętym wpisom", + "settings.media": "Zawartość multimedialna", + "settings.media_letterbox": "Letterbox media", + "settings.media_fullwidth": "Podgląd zawartości multimedialnej o pełnej szerokości", + "settings.preferences": "Preferencje użyytkownika", + "settings.wide_view": "Szeroki widok (tylko w trybie desktopowym)", + "settings.navbar_under": "Pasek nawigacji na dole (tylko w trybie mobilnym)", + "status.collapse": "Zwiń", + "status.uncollapse": "Rozwiń", + + "notification.markForDeletion": "Oznacz do usunięcia", + "notifications.clear": "Wyczyść wszystkie powiadomienia", + "notifications.marked_clear_confirmation": "Czy na pewno chcesz bezpowrtonie usunąć wszystkie powiadomienia?", + "notifications.marked_clear": "Usuń zaznaczone powiadomienia", + + "notification_purge.btn_all": "Zaznacz\nwszystkie", + "notification_purge.btn_none": "Odznacz\nwszystkie", + "notification_purge.btn_invert": "Odwróć\nzaznaczenie", + "notification_purge.btn_apply": "Usuń\nzaznaczone" +} -- cgit From 12c0011fee2594a023bc7417e7023216d61dcd7d Mon Sep 17 00:00:00 2001 From: kibigo! Date: Tue, 21 Nov 2017 18:27:34 -0800 Subject: Update statuses on hidden toggle (Fixes #227) --- app/javascript/themes/glitch/components/status.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/javascript') diff --git a/app/javascript/themes/glitch/components/status.js b/app/javascript/themes/glitch/components/status.js index e2ef47f5f..327c7f5c1 100644 --- a/app/javascript/themes/glitch/components/status.js +++ b/app/javascript/themes/glitch/components/status.js @@ -59,6 +59,7 @@ export default class Status extends ImmutablePureComponent { 'muted', 'collapse', 'notification', + 'hidden', ] updateOnStates = [ -- cgit From 2585649b207d66b06133d1b5e151c2043fe30656 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Tue, 21 Nov 2017 19:00:07 -0800 Subject: Styling fixes --- app/javascript/themes/glitch/styles/_mixins.scss | 1 + app/javascript/themes/glitch/styles/components.scss | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/themes/glitch/styles/_mixins.scss b/app/javascript/themes/glitch/styles/_mixins.scss index 79a8149fd..102723e39 100644 --- a/app/javascript/themes/glitch/styles/_mixins.scss +++ b/app/javascript/themes/glitch/styles/_mixins.scss @@ -46,6 +46,7 @@ margin-left: -22px; margin-right: -22px; width: inherit; + max-width: none; height: 250px; } } diff --git a/app/javascript/themes/glitch/styles/components.scss b/app/javascript/themes/glitch/styles/components.scss index 3be8db4b4..ade8df018 100644 --- a/app/javascript/themes/glitch/styles/components.scss +++ b/app/javascript/themes/glitch/styles/components.scss @@ -1058,6 +1058,14 @@ color: $ui-secondary-color; } + .account__avatar { + @include avatar-radius(); + @include avatar-size(90px); + display: block; + margin: 0 auto 10px; + overflow: hidden; + } + .account__header__display-name { color: $primary-text-color; display: inline-block; @@ -1248,14 +1256,6 @@ } } -.account__header__avatar { - @include avatar-radius(); - @include avatar-size(90px); - display: block; - margin: 0 auto 10px; - overflow: hidden; -} - .account-authorize { padding: 14px 10px; -- cgit From e84fecb7e97851ed56f4d954e2d68128bb87da37 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 24 Nov 2017 02:05:53 +0100 Subject: Add logging of admin actions (#5757) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add logging of admin actions * Update brakeman whitelist * Log creates, updates and destroys with history of changes * i18n: Update Polish translation (#5782) Signed-off-by: Marcin Mikołajczak * Split admin navigation into moderation and administration * Redesign audit log page * 🇵🇱 (#5795) * Add color coding to audit log * Change dismiss->resolve, log all outcomes of report as resolve * Update terminology (e-mail blacklist) (#5796) * Update terminology (e-mail blacklist) imho looks better * Update en.yml * Fix code style issues * i18n-tasks normalize --- .../admin/account_moderation_notes_controller.rb | 2 +- app/controllers/admin/accounts_controller.rb | 3 + app/controllers/admin/action_logs_controller.rb | 9 ++ app/controllers/admin/base_controller.rb | 1 + app/controllers/admin/confirmations_controller.rb | 1 + app/controllers/admin/custom_emojis_controller.rb | 8 +- app/controllers/admin/domain_blocks_controller.rb | 2 + .../admin/email_domain_blocks_controller.rb | 4 +- .../admin/reported_statuses_controller.rb | 6 +- app/controllers/admin/reports_controller.rb | 9 +- app/controllers/admin/resets_controller.rb | 1 + app/controllers/admin/roles_controller.rb | 2 + app/controllers/admin/silences_controller.rb | 6 +- app/controllers/admin/statuses_controller.rb | 6 +- app/controllers/admin/suspensions_controller.rb | 2 + .../admin/two_factor_authentications_controller.rb | 1 + app/controllers/concerns/accountable_concern.rb | 9 ++ app/helpers/admin/action_logs_helper.rb | 103 +++++++++++++++++++++ app/javascript/styles/mastodon/admin.scss | 101 ++++++++++++++++++++ app/models/admin.rb | 7 ++ app/models/admin/action_log.rb | 40 ++++++++ app/models/form/status_batch.rb | 8 +- app/views/admin/action_logs/_action_log.html.haml | 15 +++ app/views/admin/action_logs/index.html.haml | 7 ++ config/brakeman.ignore | 55 +++++++---- config/i18n-tasks.yml | 1 + config/locales/en.yml | 44 +++++++-- config/locales/pl.yml | 30 ++++++ config/navigation.rb | 12 ++- config/routes.rb | 1 + .../20171119172437_create_admin_action_logs.rb | 12 +++ db/schema.rb | 15 ++- spec/fabricators/admin_action_log_fabricator.rb | 5 + spec/models/admin/action_log_spec.rb | 5 + 34 files changed, 490 insertions(+), 43 deletions(-) create mode 100644 app/controllers/admin/action_logs_controller.rb create mode 100644 app/controllers/concerns/accountable_concern.rb create mode 100644 app/helpers/admin/action_logs_helper.rb create mode 100644 app/models/admin.rb create mode 100644 app/models/admin/action_log.rb create mode 100644 app/views/admin/action_logs/_action_log.html.haml create mode 100644 app/views/admin/action_logs/index.html.haml create mode 100644 db/migrate/20171119172437_create_admin_action_logs.rb create mode 100644 spec/fabricators/admin_action_log_fabricator.rb create mode 100644 spec/models/admin/action_log_spec.rb (limited to 'app/javascript') diff --git a/app/controllers/admin/account_moderation_notes_controller.rb b/app/controllers/admin/account_moderation_notes_controller.rb index 7f69a3363..7d5b9bf52 100644 --- a/app/controllers/admin/account_moderation_notes_controller.rb +++ b/app/controllers/admin/account_moderation_notes_controller.rb @@ -21,7 +21,7 @@ module Admin def destroy authorize @account_moderation_note, :destroy? - @account_moderation_note.destroy + @account_moderation_note.destroy! redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg') end diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 0829bc769..e9a512e70 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -32,18 +32,21 @@ module Admin def memorialize authorize @account, :memorialize? @account.memorialize! + log_action :memorialize, @account redirect_to admin_account_path(@account.id) end def enable authorize @account.user, :enable? @account.user.enable! + log_action :enable, @account.user redirect_to admin_account_path(@account.id) end def disable authorize @account.user, :disable? @account.user.disable! + log_action :disable, @account.user redirect_to admin_account_path(@account.id) end diff --git a/app/controllers/admin/action_logs_controller.rb b/app/controllers/admin/action_logs_controller.rb new file mode 100644 index 000000000..e273dfeae --- /dev/null +++ b/app/controllers/admin/action_logs_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Admin + class ActionLogsController < BaseController + def index + @action_logs = Admin::ActionLog.page(params[:page]) + end + end +end diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index db4839a8f..7fb69d578 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -3,6 +3,7 @@ module Admin class BaseController < ApplicationController include Authorization + include AccountableConcern before_action :require_staff! diff --git a/app/controllers/admin/confirmations_controller.rb b/app/controllers/admin/confirmations_controller.rb index c10b0ebee..34dfb458e 100644 --- a/app/controllers/admin/confirmations_controller.rb +++ b/app/controllers/admin/confirmations_controller.rb @@ -7,6 +7,7 @@ module Admin def create authorize @user, :confirm? @user.confirm! + log_action :confirm, @user redirect_to admin_accounts_path end diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index 509f7a48f..3fa2a0b72 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -20,6 +20,7 @@ module Admin @custom_emoji = CustomEmoji.new(resource_params) if @custom_emoji.save + log_action :create, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg') else render :new @@ -30,6 +31,7 @@ module Admin authorize @custom_emoji, :update? if @custom_emoji.update(resource_params) + log_action :update, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg') else redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.update_failed_msg') @@ -38,7 +40,8 @@ module Admin def destroy authorize @custom_emoji, :destroy? - @custom_emoji.destroy + @custom_emoji.destroy! + log_action :destroy, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg') end @@ -49,6 +52,7 @@ module Admin emoji.image = @custom_emoji.image if emoji.save + log_action :create, emoji flash[:notice] = I18n.t('admin.custom_emojis.copied_msg') else flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg') @@ -60,12 +64,14 @@ module Admin def enable authorize @custom_emoji, :enable? @custom_emoji.update!(disabled: false) + log_action :enable, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg') end def disable authorize @custom_emoji, :disable? @custom_emoji.update!(disabled: true) + log_action :disable, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg') end diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index e383dc831..64de2cbf0 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -21,6 +21,7 @@ module Admin if @domain_block.save DomainBlockWorker.perform_async(@domain_block.id) + log_action :create, @domain_block redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.created_msg') else render :new @@ -34,6 +35,7 @@ module Admin def destroy authorize @domain_block, :destroy? UnblockDomainService.new.call(@domain_block, retroactive_unblock?) + log_action :destroy, @domain_block redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb index 01058bf46..9fe85064e 100644 --- a/app/controllers/admin/email_domain_blocks_controller.rb +++ b/app/controllers/admin/email_domain_blocks_controller.rb @@ -20,6 +20,7 @@ module Admin @email_domain_block = EmailDomainBlock.new(resource_params) if @email_domain_block.save + log_action :create, @email_domain_block redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg') else render :new @@ -28,7 +29,8 @@ module Admin def destroy authorize @email_domain_block, :destroy? - @email_domain_block.destroy + @email_domain_block.destroy! + log_action :destroy, @email_domain_block redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb index 4f66ce708..535bd11d4 100644 --- a/app/controllers/admin/reported_statuses_controller.rb +++ b/app/controllers/admin/reported_statuses_controller.rb @@ -8,7 +8,7 @@ module Admin def create authorize :status, :update? - @form = Form::StatusBatch.new(form_status_batch_params) + @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_report_path(@report) @@ -16,13 +16,15 @@ module Admin def update authorize @status, :update? - @status.update(status_params) + @status.update!(status_params) + log_action :update, @status redirect_to admin_report_path(@report) end def destroy authorize @status, :destroy? RemovalWorker.perform_async(@status.id) + log_action :destroy, @status render json: @status end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 745757ee8..75db6b78a 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -25,12 +25,17 @@ module Admin def process_report case params[:outcome].to_s when 'resolve' - @report.update(action_taken_by_current_attributes) + @report.update!(action_taken_by_current_attributes) + log_action :resolve, @report when 'suspend' Admin::SuspensionWorker.perform_async(@report.target_account.id) + log_action :resolve, @report + log_action :suspend, @report.target_account resolve_all_target_account_reports when 'silence' - @report.target_account.update(silenced: true) + @report.target_account.update!(silenced: true) + log_action :resolve, @report + log_action :silence, @report.target_account resolve_all_target_account_reports else raise ActiveRecord::RecordNotFound diff --git a/app/controllers/admin/resets_controller.rb b/app/controllers/admin/resets_controller.rb index 00b590bf6..3e27d01ac 100644 --- a/app/controllers/admin/resets_controller.rb +++ b/app/controllers/admin/resets_controller.rb @@ -7,6 +7,7 @@ module Admin def create authorize @user, :reset_password? @user.send_reset_password_instructions + log_action :reset_password, @user redirect_to admin_accounts_path end diff --git a/app/controllers/admin/roles_controller.rb b/app/controllers/admin/roles_controller.rb index 8f8685827..af7ec0740 100644 --- a/app/controllers/admin/roles_controller.rb +++ b/app/controllers/admin/roles_controller.rb @@ -7,12 +7,14 @@ module Admin def promote authorize @user, :promote? @user.promote! + log_action :promote, @user redirect_to admin_account_path(@user.account_id) end def demote authorize @user, :demote? @user.demote! + log_action :demote, @user redirect_to admin_account_path(@user.account_id) end diff --git a/app/controllers/admin/silences_controller.rb b/app/controllers/admin/silences_controller.rb index 01fb292de..4c06a9c0c 100644 --- a/app/controllers/admin/silences_controller.rb +++ b/app/controllers/admin/silences_controller.rb @@ -6,13 +6,15 @@ module Admin def create authorize @account, :silence? - @account.update(silenced: true) + @account.update!(silenced: true) + log_action :silence, @account redirect_to admin_accounts_path end def destroy authorize @account, :unsilence? - @account.update(silenced: false) + @account.update!(silenced: false) + log_action :unsilence, @account redirect_to admin_accounts_path end diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index b54a9b824..5d4325f57 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -26,7 +26,7 @@ module Admin def create authorize :status, :update? - @form = Form::StatusBatch.new(form_status_batch_params) + @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_account_statuses_path(@account.id, current_params) @@ -34,13 +34,15 @@ module Admin def update authorize @status, :update? - @status.update(status_params) + @status.update!(status_params) + log_action :update, @status redirect_to admin_account_statuses_path(@account.id, current_params) end def destroy authorize @status, :destroy? RemovalWorker.perform_async(@status.id) + log_action :destroy, @status render json: @status end diff --git a/app/controllers/admin/suspensions_controller.rb b/app/controllers/admin/suspensions_controller.rb index 778feea5e..5f222e125 100644 --- a/app/controllers/admin/suspensions_controller.rb +++ b/app/controllers/admin/suspensions_controller.rb @@ -7,12 +7,14 @@ module Admin def create authorize @account, :suspend? Admin::SuspensionWorker.perform_async(@account.id) + log_action :suspend, @account redirect_to admin_accounts_path end def destroy authorize @account, :unsuspend? @account.unsuspend! + log_action :unsuspend, @account redirect_to admin_accounts_path end diff --git a/app/controllers/admin/two_factor_authentications_controller.rb b/app/controllers/admin/two_factor_authentications_controller.rb index 5a45d25cd..022107203 100644 --- a/app/controllers/admin/two_factor_authentications_controller.rb +++ b/app/controllers/admin/two_factor_authentications_controller.rb @@ -7,6 +7,7 @@ module Admin def destroy authorize @user, :disable_2fa? @user.disable_two_factor! + log_action :disable_2fa, @user redirect_to admin_accounts_path end diff --git a/app/controllers/concerns/accountable_concern.rb b/app/controllers/concerns/accountable_concern.rb new file mode 100644 index 000000000..3cdcffc51 --- /dev/null +++ b/app/controllers/concerns/accountable_concern.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module AccountableConcern + extend ActiveSupport::Concern + + def log_action(action, target) + Admin::ActionLog.create(account: current_account, action: action, target: target) + end +end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb new file mode 100644 index 000000000..e85243e57 --- /dev/null +++ b/app/helpers/admin/action_logs_helper.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +module Admin::ActionLogsHelper + def log_target(log) + if log.target + linkable_log_target(log.target) + else + log_target_from_history(log.target_type, log.recorded_changes) + end + end + + def linkable_log_target(record) + case record.class.name + when 'Account' + link_to record.acct, admin_account_path(record.id) + when 'User' + link_to record.account.acct, admin_account_path(record.account_id) + when 'CustomEmoji' + record.shortcode + when 'Report' + link_to "##{record.id}", admin_report_path(record) + when 'DomainBlock', 'EmailDomainBlock' + link_to record.domain, "https://#{record.domain}" + when 'Status' + link_to record.account.acct, TagManager.instance.url_for(record) + end + end + + def log_target_from_history(type, attributes) + case type + when 'CustomEmoji' + attributes['shortcode'] + when 'DomainBlock', 'EmailDomainBlock' + link_to attributes['domain'], "https://#{attributes['domain']}" + when 'Status' + tmp_status = Status.new(attributes) + link_to tmp_status.account.acct, TagManager.instance.url_for(tmp_status) + end + end + + def relevant_log_changes(log) + if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action) + log.recorded_changes.slice('domain') + elsif log.target_type == 'CustomEmoji' && log.action == :update + log.recorded_changes.slice('domain', 'visible_in_picker') + elsif log.target_type == 'User' && [:promote, :demote].include?(log.action) + log.recorded_changes.slice('moderator', 'admin') + elsif log.target_type == 'DomainBlock' + log.recorded_changes.slice('severity', 'reject_media') + elsif log.target_type == 'Status' && log.action == :update + log.recorded_changes.slice('sensitive') + end + end + + def log_extra_attributes(hash) + safe_join(hash.to_a.map { |key, value| safe_join([content_tag(:span, key, class: 'diff-key'), '=', log_change(value)]) }, ' ') + end + + def log_change(val) + return content_tag(:span, val, class: 'diff-neutral') unless val.is_a?(Array) + safe_join([content_tag(:span, val.first, class: 'diff-old'), content_tag(:span, val.last, class: 'diff-new')], '→') + end + + def icon_for_log(log) + case log.target_type + when 'Account', 'User' + 'user' + when 'CustomEmoji' + 'file' + when 'Report' + 'flag' + when 'DomainBlock' + 'lock' + when 'EmailDomainBlock' + 'envelope' + when 'Status' + 'pencil' + end + end + + def class_for_log_icon(log) + case log.action + when :enable, :unsuspend, :unsilence, :confirm, :promote, :resolve + 'positive' + when :create + opposite_verbs?(log) ? 'negative' : 'positive' + when :update, :reset_password, :disable_2fa, :memorialize + 'neutral' + when :demote, :silence, :disable, :suspend + 'negative' + when :destroy + opposite_verbs?(log) ? 'positive' : 'negative' + else + '' + end + end + + private + + def opposite_verbs?(log) + %w(DomainBlock EmailDomainBlock).include?(log.target_type) + end +end diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 87bc710af..d4d62336f 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -347,3 +347,104 @@ } } } + +.spacer { + flex: 1 1 auto; +} + +.log-entry { + margin-bottom: 8px; + line-height: 20px; + + &__header { + display: flex; + justify-content: flex-start; + align-items: center; + padding: 10px; + background: $ui-base-color; + color: $ui-primary-color; + border-radius: 4px 4px 0 0; + font-size: 14px; + position: relative; + } + + &__avatar { + margin-right: 10px; + + .avatar { + display: block; + margin: 0; + border-radius: 50%; + width: 40px; + height: 40px; + } + } + + &__title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__timestamp { + color: lighten($ui-base-color, 34%); + } + + &__extras { + background: lighten($ui-base-color, 6%); + border-radius: 0 0 4px 4px; + padding: 10px; + color: $ui-primary-color; + font-family: 'mastodon-font-monospace', monospace; + font-size: 12px; + white-space: nowrap; + min-height: 20px; + } + + &__icon { + font-size: 28px; + margin-right: 10px; + color: lighten($ui-base-color, 34%); + } + + &__icon__overlay { + position: absolute; + top: 10px; + right: 10px; + width: 10px; + height: 10px; + border-radius: 50%; + + &.positive { + background: $success-green; + } + + &.negative { + background: $error-red; + } + + &.neutral { + background: $ui-highlight-color; + } + } + + a, + .username, + .target { + color: $ui-secondary-color; + text-decoration: none; + font-weight: 500; + } + + .diff-old { + color: $error-red; + } + + .diff-neutral { + color: $ui-secondary-color; + } + + .diff-new { + color: $success-green; + } +} diff --git a/app/models/admin.rb b/app/models/admin.rb new file mode 100644 index 000000000..d41d18449 --- /dev/null +++ b/app/models/admin.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Admin + def self.table_name_prefix + 'admin_' + end +end diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb new file mode 100644 index 000000000..4e950fbf7 --- /dev/null +++ b/app/models/admin/action_log.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: admin_action_logs +# +# id :integer not null, primary key +# account_id :integer +# action :string default(""), not null +# target_type :string +# target_id :integer +# recorded_changes :text default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class Admin::ActionLog < ApplicationRecord + serialize :recorded_changes + + belongs_to :account, required: true + belongs_to :target, required: true, polymorphic: true + + default_scope -> { order('id desc') } + + def action + super.to_sym + end + + before_validation :set_changes + + private + + def set_changes + case action + when :destroy, :create + self.recorded_changes = target.attributes + when :update, :promote, :demote + self.recorded_changes = target.previous_changes + end + end +end diff --git a/app/models/form/status_batch.rb b/app/models/form/status_batch.rb index a97b4aa28..4f08a3049 100644 --- a/app/models/form/status_batch.rb +++ b/app/models/form/status_batch.rb @@ -2,8 +2,9 @@ class Form::StatusBatch include ActiveModel::Model + include AccountableConcern - attr_accessor :status_ids, :action + attr_accessor :status_ids, :action, :current_account ACTION_TYPE = %w(nsfw_on nsfw_off delete).freeze @@ -20,11 +21,14 @@ class Form::StatusBatch def change_sensitive(sensitive) media_attached_status_ids = MediaAttachment.where(status_id: status_ids).pluck(:status_id) + ApplicationRecord.transaction do Status.where(id: media_attached_status_ids).find_each do |status| status.update!(sensitive: sensitive) + log_action :update, status end end + true rescue ActiveRecord::RecordInvalid false @@ -33,7 +37,9 @@ class Form::StatusBatch def delete_statuses Status.where(id: status_ids).find_each do |status| RemovalWorker.perform_async(status.id) + log_action :destroy, status end + true end end diff --git a/app/views/admin/action_logs/_action_log.html.haml b/app/views/admin/action_logs/_action_log.html.haml new file mode 100644 index 000000000..72816d731 --- /dev/null +++ b/app/views/admin/action_logs/_action_log.html.haml @@ -0,0 +1,15 @@ +%li.log-entry + .log-entry__header + .log-entry__avatar + = image_tag action_log.account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar' + .log-entry__content + .log-entry__title + = t("admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}", name: content_tag(:span, action_log.account.username, class: 'username'), target: content_tag(:span, log_target(action_log), class: 'target')).html_safe + .log-entry__timestamp + %time= l action_log.created_at + .spacer + .log-entry__icon + = fa_icon icon_for_log(action_log) + .log-entry__icon__overlay{ class: class_for_log_icon(action_log) } + .log-entry__extras + = log_extra_attributes relevant_log_changes(action_log) diff --git a/app/views/admin/action_logs/index.html.haml b/app/views/admin/action_logs/index.html.haml new file mode 100644 index 000000000..bb6d7b5d7 --- /dev/null +++ b/app/views/admin/action_logs/index.html.haml @@ -0,0 +1,7 @@ +- content_for :page_title do + = t('admin.action_logs.title') + +%ul + = render @action_logs + += paginate @action_logs diff --git a/config/brakeman.ignore b/config/brakeman.ignore index f7cf89dff..db7e37bb9 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -7,10 +7,10 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 122, + "line": 143, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).inbox_url, Account.find(params[:id]).inbox_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -26,10 +26,10 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 128, + "line": 149, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).shared_inbox_url, Account.find(params[:id]).shared_inbox_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -45,10 +45,10 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 35, + "line": 54, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).url, Account.find(params[:id]).url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -76,6 +76,25 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "4b6a895e2805578d03ceedbe1d469cc75a0c759eba093722523edb4b8683c873", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/admin/action_logs/index.html.haml", + "line": 5, + "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => Admin::ActionLog.page(params[:page]), {})", + "render_path": [{"type":"controller","class":"Admin::ActionLogsController","method":"index","line":7,"file":"app/controllers/admin/action_logs_controller.rb"}], + "location": { + "type": "template", + "template": "admin/action_logs/index" + }, + "user_input": "params[:page]", + "confidence": "Weak", + "note": "" + }, { "warning_type": "Cross-Site Scripting", "warning_code": 4, @@ -83,10 +102,10 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 131, + "line": 152, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).followers_url, Account.find(params[:id]).followers_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -102,10 +121,10 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 106, + "line": 127, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).salmon_url, Account.find(params[:id]).salmon_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -124,7 +143,7 @@ "line": 31, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]), {})", - "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":9,"file":"app/controllers/admin/custom_emojis_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":10,"file":"app/controllers/admin/custom_emojis_controller.rb"}], "location": { "type": "template", "template": "admin/custom_emojis/index" @@ -163,7 +182,7 @@ "line": 64, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_accounts.page(params[:page]), {})", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"index","line":10,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"index","line":12,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/index" @@ -179,10 +198,10 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 95, + "line": 116, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).remote_url, Account.find(params[:id]).remote_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -221,7 +240,7 @@ "line": 25, "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", "code": "render(action => filtered_reports.page(params[:page]), {})", - "render_path": [{"type":"controller","class":"Admin::ReportsController","method":"index","line":9,"file":"app/controllers/admin/reports_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::ReportsController","method":"index","line":10,"file":"app/controllers/admin/reports_controller.rb"}], "location": { "type": "template", "template": "admin/reports/index" @@ -237,10 +256,10 @@ "check_name": "LinkToHref", "message": "Potentially unsafe model attribute in link_to href", "file": "app/views/admin/accounts/show.html.haml", - "line": 125, + "line": 146, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).outbox_url, Account.find(params[:id]).outbox_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -269,6 +288,6 @@ "note": "" } ], - "updated": "2017-10-20 00:00:54 +0900", + "updated": "2017-11-19 20:34:18 +0100", "brakeman_version": "4.0.1" } diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 08a96f727..014055804 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -60,3 +60,4 @@ ignore_unused: - 'activerecord.errors.models.doorkeeper/*' - 'errors.429' - 'admin.accounts.roles.*' + - 'admin.action_logs.actions.*' diff --git a/config/locales/en.yml b/config/locales/en.yml index cadedab8b..13b90cf0f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -133,6 +133,32 @@ en: unsubscribe: Unsubscribe username: Username web: Web + action_logs: + actions: + confirm_user: "%{name} confirmed e-mail address of user %{target}" + create_custom_emoji: "%{name} uploaded new emoji %{target}" + create_domain_block: "%{name} blocked domain %{target}" + create_email_domain_block: "%{name} blacklisted e-mail domain %{target}" + demote_user: "%{name} demoted user %{target}" + destroy_domain_block: "%{name} unblocked domain %{target}" + destroy_email_domain_block: "%{name} whitelisted e-mail domain %{target}" + destroy_status: "%{name} removed status by %{target}" + disable_2fa_user: "%{name} disabled two factor requirement for user %{target}" + disable_custom_emoji: "%{name} disabled emoji %{target}" + disable_user: "%{name} disabled login for user %{target}" + enable_custom_emoji: "%{name} enabled emoji %{target}" + enable_user: "%{name} enabled login for user %{target}" + memorialize_account: "%{name} turned %{target}'s account into a memoriam page" + promote_user: "%{name} promoted user %{target}" + reset_password_user: "%{name} reset password of user %{target}" + resolve_report: "%{name} dismissed report %{target}" + silence_account: "%{name} silenced %{target}'s account" + suspend_account: "%{name} suspended %{target}'s account" + unsilence_account: "%{name} unsilenced %{target}'s account" + unsuspend_account: "%{name} unsuspended %{target}'s account" + update_custom_emoji: "%{name} updated emoji %{target}" + update_status: "%{name} updated status by %{target}" + title: Audit log custom_emojis: copied_msg: Successfully created local copy of the emoji copy: Copy @@ -187,24 +213,24 @@ en: suspend: Unsuspend all existing accounts from this domain title: Undo domain block for %{domain} undo: Undo - title: Domain Blocks + title: Domain blocks undo: Undo email_domain_blocks: add_new: Add new - created_msg: Email domain block successfully created + created_msg: Successfully added e-mail domain to blacklist delete: Delete - destroyed_msg: Email domain block successfully deleted + destroyed_msg: Successfully deleted e-mail domain from blacklist domain: Domain new: - create: Create block - title: New email domain block - title: Email Domain Block + create: Add domain + title: New e-mail blacklist entry + title: E-mail blacklist instances: account_count: Known accounts domain_name: Domain reset: Reset search: Search - title: Known Instances + title: Known instances reports: action_taken_by: Action taken by are_you_sure: Are you sure? @@ -265,7 +291,7 @@ en: timeline_preview: desc_html: Display public timeline on landing page title: Timeline preview - title: Site Settings + title: Site settings statuses: back_to_account: Back to account page batch: @@ -404,6 +430,8 @@ en: validations: images_and_video: Cannot attach a video to a status that already contains images too_many: Cannot attach more than 4 files + moderation: + title: Moderation notification_mailer: digest: body: 'Here is a brief summary of what you missed on %{instance} since your last visit on %{since}:' diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 047d3df9b..a738fcea1 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -49,6 +49,7 @@ pl: reserved_username: Ta nazwa użytkownika jest zarezerwowana. roles: admin: Administrator + moderator: Moderator unfollow: Przestań śledzić admin: account_moderation_notes: @@ -132,6 +133,32 @@ pl: unsubscribe: Przestań subskrybować username: Nazwa użytkownika web: Sieć + action_logs: + actions: + confirm_user: "%{name} potwierdził adres e-mail użytkownika %{target}" + create_custom_emoji: "%{name} dodał nowe emoji %{target}" + create_domain_block: "%{name} zablokował domenę %{target}" + create_email_domain_block: "%{name} dodał domenę e-mail %{target} na czarną listę" + demote_user: "%{name} zdegradował użytkownika %{target}" + destroy_domain_block: "%{name} odblokował domenę %{target}" + destroy_email_domain_block: "%{name} usunął domenę e-mail %{target} z czarnej listy" + destroy_status: "%{name} usunął wpis użytkownika %{target}" + disable_2fa_user: "%{name} wyłączył uwierzytelnianie dwustopniowe użytkownikowi %{target}" + disable_custom_emoji: "%{name} wyłączył emoji %{target}" + disable_user: "%{name} zablokował możliwość logowania użytkownikowi %{target}" + enable_custom_emoji: "%{name} włączył emoji %{target}" + enable_user: "%{name} przywrócił możliwość logowania użytkownikowi %{target}" + memorialize_account: "%{name} nadał kontu %{target} status in memoriam" + promote_user: "%{name} podniósł uprawnienia użytkownikowi %{target}" + reset_password_user: "%{name} przywrócił hasło użytkownikowi %{target}" + resolve_report: "%{name} odrzucił zgłoszenie %{target}" + silence_account: "%{name} wyciszył konto %{target}" + suspend_account: "%{name} zawiesił konto %{target}" + unsilence_account: "%{name} cofnął wyciszenie konta %{target}" + unsuspend_account: "%{name} cofnął zawieszenie konta %{target}" + update_custom_emoji: "%{name} zaktualizował emoji %{target}" + update_status: "%{name} zaktualizował wpis użytkownika %{target}" + title: Dziennik działań administracyjnych custom_emojis: copied_msg: Pomyślnie utworzono lokalną kopię emoji copy: Kopiuj @@ -148,6 +175,7 @@ pl: listed: Widoczne new: title: Dodaj nowe niestandardowe emoji + overwrite: Zastąp shortcode: Shortcode shortcode_hint: Co najmniej 2 znaki, tylko znaki alfanumeryczne i podkreślniki title: Niestandardowe emoji @@ -403,6 +431,8 @@ pl: validations: images_and_video: Nie możesz załączyć pliku wideo do wpisu, który zawiera już zdjęcia too_many: Nie możesz załączyć więcej niż 4 plików + moderation: + title: Moderacja notification_mailer: digest: body: 'Oto krótkie podsumowanie co Cię ominęło na %{instance} od Twojej ostatniej wizyty (%{since}):' diff --git a/config/navigation.rb b/config/navigation.rb index 5b4800f07..d2432ba2a 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -20,17 +20,21 @@ SimpleNavigation::Configuration.run do |navigation| development.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url, highlights_on: %r{/settings/applications} end - primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.staff? } do |admin| + primary.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |admin| + admin.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports} admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts} admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}, if: -> { current_user.admin? } - admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? } admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}, if: -> { current_user.admin? } admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? } - admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? } - admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? } + end + + primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), edit_admin_settings_url, if: proc { current_user.staff? } do |admin| admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? } admin.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis} + admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? } + admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? } + admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? } end primary.item :logout, safe_join([fa_icon('sign-out fw'), t('auth.logout')]), destroy_user_session_url, link_html: { 'data-method' => 'delete' } diff --git a/config/routes.rb b/config/routes.rb index cf0ba59d5..d675fa846 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -110,6 +110,7 @@ Rails.application.routes.draw do resources :subscriptions, only: [:index] resources :domain_blocks, only: [:index, :new, :create, :show, :destroy] resources :email_domain_blocks, only: [:index, :new, :create, :destroy] + resources :action_logs, only: [:index] resource :settings, only: [:edit, :update] resources :instances, only: [:index] do diff --git a/db/migrate/20171119172437_create_admin_action_logs.rb b/db/migrate/20171119172437_create_admin_action_logs.rb new file mode 100644 index 000000000..0c2b6c623 --- /dev/null +++ b/db/migrate/20171119172437_create_admin_action_logs.rb @@ -0,0 +1,12 @@ +class CreateAdminActionLogs < ActiveRecord::Migration[5.1] + def change + create_table :admin_action_logs do |t| + t.belongs_to :account, foreign_key: { on_delete: :cascade } + t.string :action, null: false, default: '' + t.references :target, polymorphic: true + t.text :recorded_changes, null: false, default: '' + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 16df5d7c9..77f6a2d10 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171118012443) do +ActiveRecord::Schema.define(version: 20171119172437) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -80,6 +80,18 @@ ActiveRecord::Schema.define(version: 20171118012443) do t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true end + create_table "admin_action_logs", force: :cascade do |t| + t.bigint "account_id" + t.string "action", default: "", null: false + t.string "target_type" + t.bigint "target_id" + t.text "recorded_changes", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_admin_action_logs_on_account_id" + t.index ["target_type", "target_id"], name: "index_admin_action_logs_on_target_type_and_target_id" + end + create_table "blocks", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -488,6 +500,7 @@ ActiveRecord::Schema.define(version: 20171118012443) do add_foreign_key "account_moderation_notes", "accounts" add_foreign_key "account_moderation_notes", "accounts", column: "target_account_id" add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify + add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade add_foreign_key "blocks", "accounts", column: "target_account_id", name: "fk_9571bfabc1", on_delete: :cascade add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade diff --git a/spec/fabricators/admin_action_log_fabricator.rb b/spec/fabricators/admin_action_log_fabricator.rb new file mode 100644 index 000000000..2f44e953d --- /dev/null +++ b/spec/fabricators/admin_action_log_fabricator.rb @@ -0,0 +1,5 @@ +Fabricator('Admin::ActionLog') do + account nil + action "MyString" + target nil +end diff --git a/spec/models/admin/action_log_spec.rb b/spec/models/admin/action_log_spec.rb new file mode 100644 index 000000000..59206a36b --- /dev/null +++ b/spec/models/admin/action_log_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Admin::ActionLog, type: :model do + +end -- cgit From 5a551b530a2f9127e74e27aa5f86bbaba36cc2d3 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Fri, 24 Nov 2017 21:13:17 +0900 Subject: Do not require onClose property in NavigationBar (#5802) NavigationBar can be used as mock as it is in OnboardingModal. In such a case, onClose property is not required. --- app/javascript/mastodon/features/compose/components/navigation_bar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js index 7f346854c..3014c4033 100644 --- a/app/javascript/mastodon/features/compose/components/navigation_bar.js +++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js @@ -11,7 +11,7 @@ export default class NavigationBar extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - onClose: PropTypes.func.isRequired, + onClose: PropTypes.func, }; render () { -- cgit From 2b51b4094c18c56fe8252d90db2b62ef69913fac Mon Sep 17 00:00:00 2001 From: sdukhovni Date: Fri, 24 Nov 2017 08:43:53 -0500 Subject: Don't remove originals of boosted toots from timeline (#5479) * Don't remove originals of boosted toots from timeline * Remove unused argument to updateTimeline --- app/javascript/mastodon/reducers/timelines.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index bee4c4ef9..9984c3b5d 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -55,7 +55,7 @@ const appendNormalizedTimeline = (state, timeline, statuses, next) => { })); }; -const updateTimeline = (state, timeline, status, references) => { +const updateTimeline = (state, timeline, status) => { const top = state.getIn([timeline, 'top']); const ids = state.getIn([timeline, 'items'], ImmutableList()); const includesId = ids.includes(status.get('id')); @@ -70,7 +70,6 @@ const updateTimeline = (state, timeline, status, references) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { if (!top) mMap.set('unread', unread + 1); if (top && ids.size > 40) newIds = newIds.take(20); - if (status.getIn(['reblog', 'id'], null) !== null) newIds = newIds.filterNot(item => references.includes(item)); mMap.set('items', newIds.unshift(status.get('id'))); })); }; @@ -129,7 +128,7 @@ export default function timelines(state = initialState, action) { case TIMELINE_EXPAND_SUCCESS: return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next); case TIMELINE_UPDATE: - return updateTimeline(state, action.timeline, fromJS(action.status), action.references); + return updateTimeline(state, action.timeline, fromJS(action.status)); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); case ACCOUNT_BLOCK_SUCCESS: -- cgit From 31ac5f0e00b003f060788d7a335f4ec33dd77d9a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 25 Nov 2017 00:35:37 +0100 Subject: Display list column (#5750) --- app/javascript/mastodon/actions/lists.js | 28 ++++++ app/javascript/mastodon/actions/streaming.js | 1 + app/javascript/mastodon/actions/timelines.js | 2 + .../mastodon/features/list_timeline/index.js | 106 +++++++++++++++++++++ .../features/ui/components/columns_area.js | 3 +- app/javascript/mastodon/features/ui/index.js | 2 + .../mastodon/features/ui/util/async-components.js | 4 + app/javascript/mastodon/reducers/index.js | 2 + app/javascript/mastodon/reducers/lists.js | 15 +++ app/serializers/rest/list_serializer.rb | 4 + 10 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 app/javascript/mastodon/actions/lists.js create mode 100644 app/javascript/mastodon/features/list_timeline/index.js create mode 100644 app/javascript/mastodon/reducers/lists.js (limited to 'app/javascript') diff --git a/app/javascript/mastodon/actions/lists.js b/app/javascript/mastodon/actions/lists.js new file mode 100644 index 000000000..332e42166 --- /dev/null +++ b/app/javascript/mastodon/actions/lists.js @@ -0,0 +1,28 @@ +import api from '../api'; + +export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST'; +export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS'; +export const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL'; + +export const fetchList = id => (dispatch, getState) => { + dispatch(fetchListRequest(id)); + + api(getState).get(`/api/v1/lists/${id}`) + .then(({ data }) => dispatch(fetchListSuccess(data))) + .catch(err => dispatch(fetchListFail(err))); +}; + +export const fetchListRequest = id => ({ + type: LIST_FETCH_REQUEST, + id, +}); + +export const fetchListSuccess = list => ({ + type: LIST_FETCH_SUCCESS, + list, +}); + +export const fetchListFail = error => ({ + type: LIST_FETCH_FAIL, + error, +}); diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index dcce048ca..c22152edd 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -51,3 +51,4 @@ export const connectCommunityStream = () => connectTimelineStream('community', ' export const connectMediaStream = () => connectTimelineStream('community', 'public:local'); export const connectPublicStream = () => connectTimelineStream('public', 'public'); export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); +export const connectListStream = (id) => connectTimelineStream(`list:${id}`, `list&list=${id}`); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 09abe2702..f8843d1d9 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -118,6 +118,7 @@ export const refreshCommunityTimeline = () => refreshTimeline('community', '/ export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); +export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); export function refreshTimelineFail(timeline, error, skipLoading) { return { @@ -158,6 +159,7 @@ export const expandCommunityTimeline = () => expandTimeline('community', '/ap export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); +export const expandListTimeline = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); export function expandTimelineRequest(timeline) { return { diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js new file mode 100644 index 000000000..71f6e36a8 --- /dev/null +++ b/app/javascript/mastodon/features/list_timeline/index.js @@ -0,0 +1,106 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import StatusListContainer from '../ui/containers/status_list_container'; +import Column from '../../components/column'; +import ColumnHeader from '../../components/column_header'; +import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; +import { FormattedMessage } from 'react-intl'; +import { connectListStream } from '../../actions/streaming'; +import { refreshListTimeline, expandListTimeline } from '../../actions/timelines'; +import { fetchList } from '../../actions/lists'; + +const mapStateToProps = (state, props) => ({ + list: state.getIn(['lists', props.params.id]), + hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0, +}); + +@connect(mapStateToProps) +export default class ListTimeline extends React.PureComponent { + + static propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + columnId: PropTypes.string, + hasUnread: PropTypes.bool, + multiColumn: PropTypes.bool, + list: ImmutablePropTypes.map, + }; + + handlePin = () => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('LIST', { id: this.props.params.id })); + } + } + + handleMove = (dir) => { + const { columnId, dispatch } = this.props; + dispatch(moveColumn(columnId, dir)); + } + + handleHeaderClick = () => { + this.column.scrollTop(); + } + + componentDidMount () { + const { dispatch } = this.props; + const { id } = this.props.params; + + dispatch(fetchList(id)); + dispatch(refreshListTimeline(id)); + + this.disconnect = dispatch(connectListStream(id)); + } + + componentWillUnmount () { + if (this.disconnect) { + this.disconnect(); + this.disconnect = null; + } + } + + setRef = c => { + this.column = c; + } + + handleLoadMore = () => { + const { id } = this.props.params; + this.props.dispatch(expandListTimeline(id)); + } + + render () { + const { hasUnread, columnId, multiColumn, list } = this.props; + const { id } = this.props.params; + const pinned = !!columnId; + const title = list ? list.get('title') : id; + + return ( + + + + } + /> + + ); + } + +} diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 5610095b9..93ed9e605 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -11,7 +11,7 @@ import BundleContainer from '../containers/bundle_container'; import ColumnLoading from './column_loading'; import DrawerLoading from './drawer_loading'; import BundleColumnError from './bundle_column_error'; -import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components'; +import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components'; import detectPassiveEvents from 'detect-passive-events'; import { scrollRight } from '../../../scroll'; @@ -24,6 +24,7 @@ const componentMap = { 'COMMUNITY': CommunityTimeline, 'HASHTAG': HashtagTimeline, 'FAVOURITES': FavouritedStatuses, + 'LIST': ListTimeline, }; @component => injectIntl(component, { withRef: true }) diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index f28b37099..57289f519 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -33,6 +33,7 @@ import { FollowRequests, GenericNotFound, FavouritedStatuses, + ListTimeline, Blocks, Mutes, PinnedStatuses, @@ -372,6 +373,7 @@ export default class UI extends React.Component { + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 39663d5ca..ec1630ed6 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -26,6 +26,10 @@ export function HashtagTimeline () { return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline'); } +export function ListTimeline () { + return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline'); +} + export function Status () { return import(/* webpackChunkName: "features/status" */'../../status'); } diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index 17c870351..425a2acdd 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -22,6 +22,7 @@ import media_attachments from './media_attachments'; import notifications from './notifications'; import height_cache from './height_cache'; import custom_emojis from './custom_emojis'; +import lists from './lists'; const reducers = { timelines, @@ -47,6 +48,7 @@ const reducers = { notifications, height_cache, custom_emojis, + lists, }; export default combineReducers(reducers); diff --git a/app/javascript/mastodon/reducers/lists.js b/app/javascript/mastodon/reducers/lists.js new file mode 100644 index 000000000..3e3908869 --- /dev/null +++ b/app/javascript/mastodon/reducers/lists.js @@ -0,0 +1,15 @@ +import { LIST_FETCH_SUCCESS } from '../actions/lists'; +import { Map as ImmutableMap, fromJS } from 'immutable'; + +const initialState = ImmutableMap(); + +const normalizeList = (state, list) => state.set(list.id, fromJS(list)); + +export default function lists(state = initialState, action) { + switch(action.type) { + case LIST_FETCH_SUCCESS: + return normalizeList(state, action.list); + default: + return state; + } +}; diff --git a/app/serializers/rest/list_serializer.rb b/app/serializers/rest/list_serializer.rb index c0150888e..977da7439 100644 --- a/app/serializers/rest/list_serializer.rb +++ b/app/serializers/rest/list_serializer.rb @@ -2,4 +2,8 @@ class REST::ListSerializer < ActiveModel::Serializer attributes :id, :title + + def id + object.id.to_s + end end -- cgit From 520d1478037bd22081f04d57ed7f03c4549b20fe Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 25 Nov 2017 11:39:57 +0900 Subject: Add Japanese translations (#5810) * yarn manage:translations * Add Japanese translation for #5087 * Add Japanese translation for #5616 * Add Japanese translation for #5746 * Add Japanese translation for #5750 --- app/javascript/mastodon/locales/ar.json | 6 ++++++ app/javascript/mastodon/locales/bg.json | 6 ++++++ app/javascript/mastodon/locales/ca.json | 1 + app/javascript/mastodon/locales/de.json | 6 ++++++ app/javascript/mastodon/locales/defaultMessages.json | 9 +++++++++ app/javascript/mastodon/locales/en.json | 6 ++++++ app/javascript/mastodon/locales/eo.json | 6 ++++++ app/javascript/mastodon/locales/es.json | 6 ++++++ app/javascript/mastodon/locales/fa.json | 6 ++++++ app/javascript/mastodon/locales/fi.json | 6 ++++++ app/javascript/mastodon/locales/fr.json | 6 ++++++ app/javascript/mastodon/locales/he.json | 6 ++++++ app/javascript/mastodon/locales/hr.json | 6 ++++++ app/javascript/mastodon/locales/hu.json | 6 ++++++ app/javascript/mastodon/locales/id.json | 6 ++++++ app/javascript/mastodon/locales/io.json | 6 ++++++ app/javascript/mastodon/locales/it.json | 6 ++++++ app/javascript/mastodon/locales/ja.json | 6 ++++++ app/javascript/mastodon/locales/ko.json | 6 ++++++ app/javascript/mastodon/locales/nl.json | 6 ++++++ app/javascript/mastodon/locales/no.json | 6 ++++++ app/javascript/mastodon/locales/oc.json | 6 ++++++ app/javascript/mastodon/locales/pl.json | 6 ++++++ app/javascript/mastodon/locales/pt-BR.json | 6 ++++++ app/javascript/mastodon/locales/pt.json | 6 ++++++ app/javascript/mastodon/locales/ru.json | 6 ++++++ app/javascript/mastodon/locales/sv.json | 14 ++++++++++++-- app/javascript/mastodon/locales/th.json | 6 ++++++ app/javascript/mastodon/locales/tr.json | 6 ++++++ app/javascript/mastodon/locales/uk.json | 6 ++++++ app/javascript/mastodon/locales/whitelist_sv.json | 2 ++ app/javascript/mastodon/locales/zh-CN.json | 4 +++- app/javascript/mastodon/locales/zh-HK.json | 6 ++++++ app/javascript/mastodon/locales/zh-TW.json | 6 ++++++ 34 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 app/javascript/mastodon/locales/whitelist_sv.json (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 7cc8ea237..6573b4a7c 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -9,7 +9,9 @@ "account.follows_you": "يتابعك", "account.media": "وسائط", "account.mention": "أُذكُر @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "أكتم @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "المشاركات", "account.report": "أبلغ عن @{name}", "account.requested": "في انتظار الموافقة", @@ -18,6 +20,7 @@ "account.unblock_domain": "فك حظر {domain}", "account.unfollow": "إلغاء المتابعة", "account.unmute": "إلغاء الكتم عن @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "عرض الملف الشخصي كاملا", "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة", "bundle_column_error.body": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.", "empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.", "empty_column.home.public_timeline": "الخيط العام", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.", "empty_column.public": "لا يوجد شيء هنا ! قم بتحرير شيء ما بشكل عام، أو اتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام.", "follow_request.authorize": "ترخيص", @@ -104,6 +108,7 @@ "loading_indicator.label": "تحميل ...", "media_gallery.toggle_visible": "عرض / إخفاء", "missing_indicator.label": "تعذر العثور عليه", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "الحسابات المحجوبة", "navigation_bar.community_timeline": "الخيط العام المحلي", "navigation_bar.edit_profile": "تعديل الملف الشخصي", @@ -204,6 +209,7 @@ "tabs_bar.home": "الرئيسية", "tabs_bar.local_timeline": "المحلي", "tabs_bar.notifications": "الإخطارات", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "إسحب ثم أفلت للرفع", "upload_button.label": "إضافة وسائط", "upload_form.description": "وصف للمعاقين بصريا", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index da2372cff..cb02a12a0 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -9,7 +9,9 @@ "account.follows_you": "Твой последовател", "account.media": "Media", "account.mention": "Споменаване", + "account.moved_to": "{name} has moved to:", "account.mute": "Mute @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Публикации", "account.report": "Report @{name}", "account.requested": "В очакване на одобрение", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Не следвай", "account.unmute": "Unmute @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", "follow_request.authorize": "Authorize", @@ -104,6 +108,7 @@ "loading_indicator.label": "Зареждане...", "media_gallery.toggle_visible": "Toggle visibility", "missing_indicator.label": "Not found", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", "navigation_bar.edit_profile": "Редактирай профил", @@ -204,6 +209,7 @@ "tabs_bar.home": "Начало", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Известия", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Добави медия", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index c4edf3058..d8c2cc3e5 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -86,6 +86,7 @@ "empty_column.hashtag": "Encara no hi ha res amb aquesta etiqueta.", "empty_column.home": "Encara no segueixes ningú. Visita {public} o fes cerca per començar i conèixer altres usuaris.", "empty_column.home.public_timeline": "la línia de temps pública", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Encara no tens notificacions. Interactua amb altres per iniciar la conversa.", "empty_column.public": "No hi ha res aquí! Escriu alguna cosa públicament o segueix manualment usuaris d'altres instàncies per omplir-ho", "follow_request.authorize": "Autoritzar", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 283a2946f..1f625252b 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -9,7 +9,9 @@ "account.follows_you": "Folgt dir", "account.media": "Medien", "account.mention": "@{name} erwähnen", + "account.moved_to": "{name} has moved to:", "account.mute": "@{name} stummschalten", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Beiträge", "account.report": "@{name} melden", "account.requested": "Warte auf Erlaubnis. Klicke zum Abbrechen", @@ -18,6 +20,7 @@ "account.unblock_domain": "{domain} wieder anzeigen", "account.unfollow": "Entfolgen", "account.unmute": "@{name} nicht mehr stummschalten", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Vollständiges Profil anzeigen", "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen", "bundle_column_error.body": "Etwas ist beim Laden schiefgelaufen.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.", "empty_column.home": "Deine Startseite ist leer! Besuche {public} oder nutze die Suche, um loszulegen und andere Leute zu finden.", "empty_column.home.public_timeline": "die öffentliche Zeitleiste", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um ins Gespräch zu kommen.", "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Instanzen, um die Zeitleiste aufzufüllen", "follow_request.authorize": "Erlauben", @@ -104,6 +108,7 @@ "loading_indicator.label": "Wird geladen …", "media_gallery.toggle_visible": "Sichtbarkeit umschalten", "missing_indicator.label": "Nicht gefunden", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blockierte Profile", "navigation_bar.community_timeline": "Lokale Zeitleiste", "navigation_bar.edit_profile": "Profil bearbeiten", @@ -204,6 +209,7 @@ "tabs_bar.home": "Startseite", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Mitteilungen", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Zum Hochladen hereinziehen", "upload_button.label": "Mediendatei hinzufügen", "upload_form.description": "Für Menschen mit Sehbehinderung beschreiben", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 571a763b6..555c904ff 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -923,6 +923,15 @@ ], "path": "app/javascript/mastodon/features/home_timeline/index.json" }, + { + "descriptors": [ + { + "defaultMessage": "There is nothing in this list yet.", + "id": "empty_column.list" + } + ], + "path": "app/javascript/mastodon/features/list_timeline/index.json" + }, { "descriptors": [ { diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 1d0bbcee5..4cb2631bc 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -9,7 +9,9 @@ "account.follows_you": "Follows you", "account.media": "Media", "account.mention": "Mention @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Mute @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Posts", "account.report": "Report @{name}", "account.requested": "Awaiting approval. Click to cancel follow request", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Unfollow", "account.unmute": "Unmute @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", "follow_request.authorize": "Authorize", @@ -104,6 +108,7 @@ "loading_indicator.label": "Loading...", "media_gallery.toggle_visible": "Toggle visibility", "missing_indicator.label": "Not found", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", "navigation_bar.edit_profile": "Edit profile", @@ -204,6 +209,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 3f67a8fff..3263f5677 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -9,7 +9,9 @@ "account.follows_you": "Sekvas vin", "account.media": "Sonbildaĵoj", "account.mention": "Mencii @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Silentigi @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Mesaĝoj", "account.report": "Signali @{name}", "account.requested": "Atendas aprobon", @@ -18,6 +20,7 @@ "account.unblock_domain": "Malkaŝi {domain}", "account.unfollow": "Ne plus sekvi", "account.unmute": "Malsilentigi @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Vidi plenan profilon", "boost_modal.combo": "La proksiman fojon, premu {combo} por pasigi", "bundle_column_error.body": "Io malfunkciis ŝargante tiun ĉi komponanton.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Ĝise, neniu enhavo estas asociita kun tiu kradvorto.", "empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.", "empty_column.home.public_timeline": "la publika tempolinio", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Vi dume ne havas sciigojn. Interagi kun aliajn uzantojn por komenci la konversacion.", "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj instancoj por plenigi la publikan tempolinion.", "follow_request.authorize": "Akcepti", @@ -104,6 +108,7 @@ "loading_indicator.label": "Ŝarganta…", "media_gallery.toggle_visible": "Baskuli videblecon", "missing_indicator.label": "Ne trovita", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blokitaj uzantoj", "navigation_bar.community_timeline": "Loka tempolinio", "navigation_bar.edit_profile": "Redakti la profilon", @@ -204,6 +209,7 @@ "tabs_bar.home": "Hejmo", "tabs_bar.local_timeline": "Loka tempolinio", "tabs_bar.notifications": "Sciigoj", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Algliti por alŝuti", "upload_button.label": "Aldoni sonbildaĵon", "upload_form.description": "Priskribi por la misvidantaj", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 6e8e94700..3bfa68576 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -9,7 +9,9 @@ "account.follows_you": "Te sigue", "account.media": "Media", "account.mention": "Mencionar a @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Silenciar a @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Publicaciones", "account.report": "Reportar a @{name}", "account.requested": "Esperando aprobación", @@ -18,6 +20,7 @@ "account.unblock_domain": "Mostrar a {domain}", "account.unfollow": "Dejar de seguir", "account.unmute": "Dejar de silenciar a @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Ver perfil completo", "boost_modal.combo": "Puedes presionar {combo} para saltear este aviso la próxima vez", "bundle_column_error.body": "Algo salió mal al cargar este componente.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "No hay nada en este hashtag aún.", "empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.", "empty_column.home.public_timeline": "la línea de tiempo pública", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.", "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo.", "follow_request.authorize": "Autorizar", @@ -104,6 +108,7 @@ "loading_indicator.label": "Cargando…", "media_gallery.toggle_visible": "Cambiar visibilidad", "missing_indicator.label": "No encontrado", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Usuarios bloqueados", "navigation_bar.community_timeline": "Historia local", "navigation_bar.edit_profile": "Editar perfil", @@ -204,6 +209,7 @@ "tabs_bar.home": "Inicio", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificaciones", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Arrastra y suelta para subir", "upload_button.label": "Subir multimedia", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 995d1b5ae..fb9b8489e 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -9,7 +9,9 @@ "account.follows_you": "پیگیر شماست", "account.media": "رسانه", "account.mention": "نام‌بردن از @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "بی‌صدا کردن @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "نوشته‌ها", "account.report": "گزارش @{name}", "account.requested": "در انتظار پذیرش", @@ -18,6 +20,7 @@ "account.unblock_domain": "رفع پنهان‌سازی از {domain}", "account.unfollow": "پایان پیگیری", "account.unmute": "باصدا کردن @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "نمایش نمایهٔ کامل", "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید", "bundle_column_error.body": "هنگام بازکردن این بخش خطایی رخ داد.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.", "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.", "empty_column.home.public_timeline": "فهرست نوشته‌های همه‌جا", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشته‌های دیگران واکنش نشان دهید تا گفتگو آغاز شود.", "empty_column.public": "این‌جا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران دیگر را پی بگیرید تا این‌جا پر شود", "follow_request.authorize": "اجازه دهید", @@ -104,6 +108,7 @@ "loading_indicator.label": "بارگیری...", "media_gallery.toggle_visible": "تغییر پیدایی", "missing_indicator.label": "پیدا نشد", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "کاربران مسدودشده", "navigation_bar.community_timeline": "نوشته‌های محلی", "navigation_bar.edit_profile": "ویرایش نمایه", @@ -204,6 +209,7 @@ "tabs_bar.home": "خانه", "tabs_bar.local_timeline": "محلی", "tabs_bar.notifications": "اعلان‌ها", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "برای بارگذاری به این‌جا بکشید", "upload_button.label": "افزودن تصویر", "upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index af08be5d1..913c27e33 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -9,7 +9,9 @@ "account.follows_you": "Seuraa sinua", "account.media": "Media", "account.mention": "Mainitse @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Mute @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Postit", "account.report": "Report @{name}", "account.requested": "Odottaa hyväksyntää", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Lopeta seuraaminen", "account.unmute": "Unmute @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", "follow_request.authorize": "Authorize", @@ -104,6 +108,7 @@ "loading_indicator.label": "Ladataan...", "media_gallery.toggle_visible": "Toggle visibility", "missing_indicator.label": "Not found", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Paikallinen aikajana", "navigation_bar.edit_profile": "Muokkaa profiilia", @@ -204,6 +209,7 @@ "tabs_bar.home": "Koti", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Ilmoitukset", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Lisää mediaa", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 219bf4da1..8cb35619b 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -9,7 +9,9 @@ "account.follows_you": "Vous suit", "account.media": "Média", "account.mention": "Mentionner", + "account.moved_to": "{name} has moved to:", "account.mute": "Masquer", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Statuts", "account.report": "Signaler", "account.requested": "Invitation envoyée", @@ -18,6 +20,7 @@ "account.unblock_domain": "Ne plus masquer {domain}", "account.unfollow": "Ne plus suivre", "account.unmute": "Ne plus masquer", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Afficher le profil complet", "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois", "bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag", "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateur⋅ice⋅s.", "empty_column.home.public_timeline": "le fil public", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateur⋅ice⋅s pour débuter la conversation.", "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice⋅s d’autres instances pour remplir le fil public.", "follow_request.authorize": "Accepter", @@ -104,6 +108,7 @@ "loading_indicator.label": "Chargement…", "media_gallery.toggle_visible": "Modifier la visibilité", "missing_indicator.label": "Non trouvé", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Comptes bloqués", "navigation_bar.community_timeline": "Fil public local", "navigation_bar.edit_profile": "Modifier le profil", @@ -204,6 +209,7 @@ "tabs_bar.home": "Accueil", "tabs_bar.local_timeline": "Fil public local", "tabs_bar.notifications": "Notifications", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Glissez et déposez pour envoyer", "upload_button.label": "Joindre un média", "upload_form.description": "Décrire pour les malvoyants", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index a260f0968..2e26a95e5 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -9,7 +9,9 @@ "account.follows_you": "במעקב אחריך", "account.media": "מדיה", "account.mention": "אזכור של @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "להשתיק את @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "הודעות", "account.report": "לדווח על @{name}", "account.requested": "בהמתנה לאישור", @@ -18,6 +20,7 @@ "account.unblock_domain": "הסר חסימה מקהילת {domain}", "account.unfollow": "הפסקת מעקב", "account.unmute": "הפסקת השתקת @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "אין כלום בהאשתג הזה עדיין.", "empty_column.home": "אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.", "empty_column.home.public_timeline": "ציר זמן בין-קהילתי", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב!", "empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות.", "follow_request.authorize": "קבלה", @@ -104,6 +108,7 @@ "loading_indicator.label": "טוען...", "media_gallery.toggle_visible": "נראה\\בלתי נראה", "missing_indicator.label": "לא נמצא", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "חסימות", "navigation_bar.community_timeline": "ציר זמן מקומי", "navigation_bar.edit_profile": "עריכת פרופיל", @@ -204,6 +209,7 @@ "tabs_bar.home": "בבית", "tabs_bar.local_timeline": "ציר זמן מקומי", "tabs_bar.notifications": "התראות", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "ניתן להעלות על ידי Drag & drop", "upload_button.label": "הוספת מדיה", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 6ac7fc3b4..e63da993e 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -9,7 +9,9 @@ "account.follows_you": "te slijedi", "account.media": "Media", "account.mention": "Spomeni @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Utišaj @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Postovi", "account.report": "Prijavi @{name}", "account.requested": "Čeka pristanak", @@ -18,6 +20,7 @@ "account.unblock_domain": "Poništi sakrivanje {domain}", "account.unfollow": "Prestani slijediti", "account.unmute": "Poništi utišavanje @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.", "empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.", "empty_column.home.public_timeline": "javni timeline", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.", "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio", "follow_request.authorize": "Autoriziraj", @@ -104,6 +108,7 @@ "loading_indicator.label": "Učitavam...", "media_gallery.toggle_visible": "Preklopi vidljivost", "missing_indicator.label": "Nije nađen", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blokirani korisnici", "navigation_bar.community_timeline": "Lokalni timeline", "navigation_bar.edit_profile": "Uredi profil", @@ -204,6 +209,7 @@ "tabs_bar.home": "Dom", "tabs_bar.local_timeline": "Lokalno", "tabs_bar.notifications": "Notifikacije", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Povuci i spusti kako bi uploadao", "upload_button.label": "Dodaj media", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 5892e606e..4f8a68448 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -9,7 +9,9 @@ "account.follows_you": "Követnek téged", "account.media": "Media", "account.mention": "Említés", + "account.moved_to": "{name} has moved to:", "account.mute": "Mute @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Posts", "account.report": "Report @{name}", "account.requested": "Awaiting approval", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Követés abbahagyása", "account.unmute": "Unmute @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", "follow_request.authorize": "Authorize", @@ -104,6 +108,7 @@ "loading_indicator.label": "Betöltés...", "media_gallery.toggle_visible": "Toggle visibility", "missing_indicator.label": "Not found", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", "navigation_bar.edit_profile": "Profil szerkesztése", @@ -204,6 +209,7 @@ "tabs_bar.home": "Kezdőlap", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Média hozzáadása", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index f73ef0e19..9f5bbd140 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -9,7 +9,9 @@ "account.follows_you": "Mengikuti anda", "account.media": "Media", "account.mention": "Balasan @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Bisukan @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Postingan", "account.report": "Laporkan @{name}", "account.requested": "Menunggu persetujuan", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Berhenti mengikuti", "account.unmute": "Berhenti membisukan @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.", "empty_column.home": "Anda sedang tidak mengikuti siapapun. Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.", "empty_column.home.public_timeline": "linimasa publik", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.", "empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisinya secara manual", "follow_request.authorize": "Izinkan", @@ -104,6 +108,7 @@ "loading_indicator.label": "Tunggu sebentar...", "media_gallery.toggle_visible": "Tampil/Sembunyikan", "missing_indicator.label": "Tidak ditemukan", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Pengguna diblokir", "navigation_bar.community_timeline": "Linimasa lokal", "navigation_bar.edit_profile": "Ubah profil", @@ -204,6 +209,7 @@ "tabs_bar.home": "Beranda", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Notifikasi", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Seret & lepaskan untuk mengunggah", "upload_button.label": "Tambahkan media", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 53371bece..00705c7f8 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -9,7 +9,9 @@ "account.follows_you": "Sequas tu", "account.media": "Media", "account.mention": "Mencionar @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Celar @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Mesaji", "account.report": "Denuncar @{name}", "account.requested": "Vartante aprobo", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Ne plus sequar", "account.unmute": "Ne plus celar @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Esas ankore nulo en ta gretovorto.", "empty_column.home": "Tu sequas ankore nulu. Vizitez {public} od uzez la serchilo por komencar e renkontrar altra uzeri.", "empty_column.home.public_timeline": "la publika tempolineo", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.", "empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.", "follow_request.authorize": "Yurizar", @@ -104,6 +108,7 @@ "loading_indicator.label": "Kargante...", "media_gallery.toggle_visible": "Chanjar videbleso", "missing_indicator.label": "Ne trovita", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blokusita uzeri", "navigation_bar.community_timeline": "Lokala tempolineo", "navigation_bar.edit_profile": "Modifikar profilo", @@ -204,6 +209,7 @@ "tabs_bar.home": "Hemo", "tabs_bar.local_timeline": "Lokala", "tabs_bar.notifications": "Savigi", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Tranar faligar por kargar", "upload_button.label": "Adjuntar kontenajo", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 3873d797e..92ab88d2e 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -9,7 +9,9 @@ "account.follows_you": "Ti segue", "account.media": "Media", "account.mention": "Menziona @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Silenzia @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Posts", "account.report": "Segnala @{name}", "account.requested": "In attesa di approvazione", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Non seguire", "account.unmute": "Non silenziare @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.", "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.", "empty_column.home.public_timeline": "la timeline pubblica", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.", "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio.", "follow_request.authorize": "Autorizza", @@ -104,6 +108,7 @@ "loading_indicator.label": "Carico...", "media_gallery.toggle_visible": "Imposta visibilità", "missing_indicator.label": "Non trovato", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Utenti bloccati", "navigation_bar.community_timeline": "Timeline locale", "navigation_bar.edit_profile": "Modifica profilo", @@ -204,6 +209,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Locale", "tabs_bar.notifications": "Notifiche", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Trascina per caricare", "upload_button.label": "Aggiungi file multimediale", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index fb6d11ebe..19728d93c 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -9,7 +9,9 @@ "account.follows_you": "フォローされています", "account.media": "メディア", "account.mention": "返信", + "account.moved_to": "{name}さんは引っ越しました:", "account.mute": "ミュート", + "account.mute_notifications": "@{name}からの通知を受け取る", "account.posts": "投稿", "account.report": "通報", "account.requested": "承認待ち", @@ -18,6 +20,7 @@ "account.unblock_domain": "{domain}を表示", "account.unfollow": "フォロー解除", "account.unmute": "ミュート解除", + "account.unmute_notifications": "@{name}からの通知を受け取らない", "account.view_full_profile": "全ての情報を見る", "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。", "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。", @@ -83,6 +86,7 @@ "empty_column.hashtag": "このハッシュタグはまだ使われていません。", "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。", "empty_column.home.public_timeline": "連合タイムライン", + "empty_column.list": "このリストにはまだなにもありません。", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!", "follow_request.authorize": "許可", @@ -104,6 +108,7 @@ "loading_indicator.label": "読み込み中...", "media_gallery.toggle_visible": "表示切り替え", "missing_indicator.label": "見つかりません", + "mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?", "navigation_bar.blocks": "ブロックしたユーザー", "navigation_bar.community_timeline": "ローカルタイムライン", "navigation_bar.edit_profile": "プロフィールを編集", @@ -204,6 +209,7 @@ "tabs_bar.home": "ホーム", "tabs_bar.local_timeline": "ローカル", "tabs_bar.notifications": "通知", + "ui.beforeunload": "Mastodonから離れるとあなたのドラフトは失われます。", "upload_area.title": "ドラッグ&ドロップでアップロード", "upload_button.label": "メディアを追加", "upload_form.description": "視覚障害者のための説明", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 2919928af..63957d5e6 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -9,7 +9,9 @@ "account.follows_you": "날 팔로우합니다", "account.media": "미디어", "account.mention": "답장", + "account.moved_to": "{name} has moved to:", "account.mute": "뮤트", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "포스트", "account.report": "신고", "account.requested": "승인 대기 중", @@ -18,6 +20,7 @@ "account.unblock_domain": "{domain} 숨김 해제", "account.unfollow": "팔로우 해제", "account.unmute": "뮤트 해제", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "전체 프로필 보기", "boost_modal.combo": "다음부터 {combo}를 누르면 이 과정을 건너뛸 수 있습니다.", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.", "empty_column.home": "아직 아무도 팔로우 하고 있지 않습니다. {public}를 보러 가거나, 검색하여 다른 사용자를 찾아 보세요.", "empty_column.home.public_timeline": "연합 타임라인", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요!", "empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 인스턴스 유저를 팔로우 해서 가득 채워보세요!", "follow_request.authorize": "허가", @@ -104,6 +108,7 @@ "loading_indicator.label": "불러오는 중...", "media_gallery.toggle_visible": "표시 전환", "missing_indicator.label": "찾을 수 없습니다", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "차단한 사용자", "navigation_bar.community_timeline": "로컬 타임라인", "navigation_bar.edit_profile": "프로필 편집", @@ -204,6 +209,7 @@ "tabs_bar.home": "홈", "tabs_bar.local_timeline": "로컬", "tabs_bar.notifications": "알림", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "드래그 & 드롭으로 업로드", "upload_button.label": "미디어 추가", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index d0727a24d..e27d03ead 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -9,7 +9,9 @@ "account.follows_you": "Volgt jou", "account.media": "Media", "account.mention": "Vermeld @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Negeer @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Toots", "account.report": "Rapporteer @{name}", "account.requested": "Wacht op goedkeuring. Klik om volgverzoek te annuleren.", @@ -18,6 +20,7 @@ "account.unblock_domain": "{domain} niet meer negeren", "account.unfollow": "Ontvolgen", "account.unmute": "@{name} niet meer negeren", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Volledig profiel tonen", "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan", "bundle_column_error.body": "Tijdens het laden van dit onderdeel is er iets fout gegaan.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.", "empty_column.home": "Jij volgt nog niemand. Bezoek {public} of gebruik het zoekvenster om andere mensen te ontmoeten.", "empty_column.home.public_timeline": "de globale tijdlijn", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Je hebt nog geen meldingen. Heb interactie met andere mensen om het gesprek aan te gaan.", "empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere Mastodon-servers om het te vullen.", "follow_request.authorize": "Goedkeuren", @@ -104,6 +108,7 @@ "loading_indicator.label": "Laden…", "media_gallery.toggle_visible": "Media wel/niet tonen", "missing_indicator.label": "Niet gevonden", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Geblokkeerde gebruikers", "navigation_bar.community_timeline": "Lokale tijdlijn", "navigation_bar.edit_profile": "Profiel bewerken", @@ -204,6 +209,7 @@ "tabs_bar.home": "Start", "tabs_bar.local_timeline": "Lokaal", "tabs_bar.notifications": "Meldingen", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Hierin slepen om te uploaden", "upload_button.label": "Media toevoegen", "upload_form.description": "Omschrijf dit voor mensen met een visuele beperking", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index d74ae0091..b3dbed03f 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -9,7 +9,9 @@ "account.follows_you": "Følger deg", "account.media": "Media", "account.mention": "Nevn @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Demp @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Innlegg", "account.report": "Rapportér @{name}", "account.requested": "Venter på godkjennelse", @@ -18,6 +20,7 @@ "account.unblock_domain": "Vis {domain}", "account.unfollow": "Avfølg", "account.unmute": "Avdemp @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.", "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.", "empty_column.home.public_timeline": "en offentlig tidslinje", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.", "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp", "follow_request.authorize": "Autorisér", @@ -104,6 +108,7 @@ "loading_indicator.label": "Laster...", "media_gallery.toggle_visible": "Veksle synlighet", "missing_indicator.label": "Ikke funnet", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blokkerte brukere", "navigation_bar.community_timeline": "Lokal tidslinje", "navigation_bar.edit_profile": "Rediger profil", @@ -204,6 +209,7 @@ "tabs_bar.home": "Hjem", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Varslinger", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Dra og slipp for å laste opp", "upload_button.label": "Legg til media", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index d826423b5..f4854ae9a 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -9,7 +9,9 @@ "account.follows_you": "Vos sèc", "account.media": "Mèdias", "account.mention": "Mencionar @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Rescondre @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Estatuts", "account.report": "Senhalar @{name}", "account.requested": "Invitacion mandada. Clicatz per anullar.", @@ -18,6 +20,7 @@ "account.unblock_domain": "Desblocar {domain}", "account.unfollow": "Quitar de sègre", "account.unmute": "Quitar de rescondre @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Veire lo perfil complet", "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven", "bundle_column_error.body": "Quicòm a fach meuca pendent lo cargament d’aqueste compausant.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag", "empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.", "empty_column.home.public_timeline": "lo flux public", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.", "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.", "follow_request.authorize": "Autorizar", @@ -104,6 +108,7 @@ "loading_indicator.label": "Cargament…", "media_gallery.toggle_visible": "Modificar la visibilitat", "missing_indicator.label": "Pas trobat", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Personas blocadas", "navigation_bar.community_timeline": "Flux public local", "navigation_bar.edit_profile": "Modificar lo perfil", @@ -204,6 +209,7 @@ "tabs_bar.home": "Acuèlh", "tabs_bar.local_timeline": "Flux public local", "tabs_bar.notifications": "Notificacions", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Lisatz e depausatz per mandar", "upload_button.label": "Ajustar un mèdia", "upload_form.description": "Descripcion pels mal vesents", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index b23a5e69f..245772bde 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -9,7 +9,9 @@ "account.follows_you": "Śledzi Cię", "account.media": "Media", "account.mention": "Wspomnij o @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Wycisz @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Wpisy", "account.report": "Zgłoś @{name}", "account.requested": "Oczekująca prośba, kliknij aby anulować", @@ -18,6 +20,7 @@ "account.unblock_domain": "Odblokuj domenę {domain}", "account.unfollow": "Przestań śledzić", "account.unmute": "Cofnij wyciszenie @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Wyświetl pełny profil", "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem", "bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy!", "empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.", "empty_column.home.public_timeline": "publiczna oś czasu", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.", "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić.", "follow_request.authorize": "Autoryzuj", @@ -104,6 +108,7 @@ "loading_indicator.label": "Ładowanie…", "media_gallery.toggle_visible": "Przełącz widoczność", "missing_indicator.label": "Nie znaleziono", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Zablokowani użytkownicy", "navigation_bar.community_timeline": "Lokalna oś czasu", "navigation_bar.edit_profile": "Edytuj profil", @@ -204,6 +209,7 @@ "tabs_bar.home": "Strona główna", "tabs_bar.local_timeline": "Lokalne", "tabs_bar.notifications": "Powiadomienia", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Przeciągnij i upuść aby wysłać", "upload_button.label": "Dodaj zawartość multimedialną", "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index a04d1cc31..4de58408c 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -9,7 +9,9 @@ "account.follows_you": "Segue você", "account.media": "Mídia", "account.mention": "Mencionar @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Silenciar @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Posts", "account.report": "Denunciar @{name}", "account.requested": "Aguardando aprovação. Clique para cancelar a solicitação.", @@ -18,6 +20,7 @@ "account.unblock_domain": "Desbloquear {domain}", "account.unfollow": "Deixar de seguir", "account.unmute": "Não silenciar @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Ver perfil completo", "boost_modal.combo": "Você pode pressionar {combo} para ignorar este diálogo na próxima vez", "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag", "empty_column.home": "Você ainda não segue usuário algo. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.", "empty_column.home.public_timeline": "global", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar!", "empty_column.public": "Não há nada aqui! Escreva algo publicamente ou siga manualmente usuários de outras instâncias.", "follow_request.authorize": "Autorizar", @@ -104,6 +108,7 @@ "loading_indicator.label": "Carregando...", "media_gallery.toggle_visible": "Esconder/Mostrar", "missing_indicator.label": "Não encontrado", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Usuários bloqueados", "navigation_bar.community_timeline": "Local", "navigation_bar.edit_profile": "Editar perfil", @@ -204,6 +209,7 @@ "tabs_bar.home": "Página inicial", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar mídia", "upload_form.description": "Descreva a imagem para deficientes visuais", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 9ae140df9..1447bc2be 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -9,7 +9,9 @@ "account.follows_you": "É teu seguidor", "account.media": "Media", "account.mention": "Mencionar @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Silenciar @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Posts", "account.report": "Denunciar @{name}", "account.requested": "A aguardar aprovação", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Deixar de seguir", "account.unmute": "Não silenciar @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "Pode clicar {combo} para não voltar a ver", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag", "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.", "empty_column.home.public_timeline": "global", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.", "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.", "follow_request.authorize": "Autorizar", @@ -104,6 +108,7 @@ "loading_indicator.label": "Carregando...", "media_gallery.toggle_visible": "Esconder/Mostrar", "missing_indicator.label": "Não encontrado", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Utilizadores bloqueados", "navigation_bar.community_timeline": "Local", "navigation_bar.edit_profile": "Editar perfil", @@ -204,6 +209,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar media", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 65bc5b374..989519058 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -9,7 +9,9 @@ "account.follows_you": "Подписан(а) на Вас", "account.media": "Медиаконтент", "account.mention": "Упомянуть", + "account.moved_to": "{name} has moved to:", "account.mute": "Заглушить", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Посты", "account.report": "Пожаловаться", "account.requested": "Ожидает подтверждения", @@ -18,6 +20,7 @@ "account.unblock_domain": "Разблокировать {domain}", "account.unfollow": "Отписаться", "account.unmute": "Снять глушение", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Показать полный профиль", "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз", "bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.", "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.", "empty_column.home.public_timeline": "публичные ленты", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.", "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.", "follow_request.authorize": "Авторизовать", @@ -104,6 +108,7 @@ "loading_indicator.label": "Загрузка...", "media_gallery.toggle_visible": "Показать/скрыть", "missing_indicator.label": "Не найдено", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Список блокировки", "navigation_bar.community_timeline": "Локальная лента", "navigation_bar.edit_profile": "Изменить профиль", @@ -204,6 +209,7 @@ "tabs_bar.home": "Главная", "tabs_bar.local_timeline": "Локальная", "tabs_bar.notifications": "Уведомления", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Перетащите сюда, чтобы загрузить", "upload_button.label": "Добавить медиаконтент", "upload_form.description": "Описать для людей с нарушениями зрения", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 70beb70f7..5afedecbc 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -9,7 +9,9 @@ "account.follows_you": "Följer dig", "account.media": "Media", "account.mention": "Nämna @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Tysta @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Inlägg", "account.report": "Rapportera @{name}", "account.requested": "Inväntar godkännande. Klicka för att avbryta följförfrågan", @@ -18,6 +20,7 @@ "account.unblock_domain": "Ta fram {domain}", "account.unfollow": "Sluta följa", "account.unmute": "Ta bort tystad @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "Visa hela profilen", "boost_modal.combo": "Du kan trycka {combo} för att slippa denna nästa gång", "bundle_column_error.body": "Något gick fel när du laddade denna komponent.", @@ -82,8 +85,8 @@ "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att få bollen att rulla!", "empty_column.hashtag": "Det finns inget i denna hashtag ännu.", "empty_column.home": "Din hemma-tidslinje är tom! Besök {public} eller använd sökning för att komma igång och träffa andra användare.", - "empty_column.home.inactivity": "Ditt hemmafeed är tomt. Om du har varit inaktiv ett tag kommer det att regenereras för dig snart.", "empty_column.home.public_timeline": "den publika tidslinjen", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.", "empty_column.public": "Det finns inget här! Skriv något offentligt, eller följ manuellt användarna från andra instanser för att fylla på det", "follow_request.authorize": "Godkänn", @@ -105,6 +108,7 @@ "loading_indicator.label": "Laddar...", "media_gallery.toggle_visible": "Växla synlighet", "missing_indicator.label": "Hittades inte", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blockerade användare", "navigation_bar.community_timeline": "Lokal tidslinje", "navigation_bar.edit_profile": "Redigera profil", @@ -114,7 +118,6 @@ "navigation_bar.logout": "Logga ut", "navigation_bar.mutes": "Tystade användare", "navigation_bar.pins": "Nålade inlägg (toots)", - "navigation_bar.preferences": "Inställningar", "navigation_bar.public_timeline": "Förenad tidslinje", "notification.favourite": "{name} favoriserade din status", @@ -161,6 +164,11 @@ "privacy.public.short": "Publik", "privacy.unlisted.long": "Skicka inte till publik tidslinje", "privacy.unlisted.short": "Olistad", + "relative_time.days": "{number}d", + "relative_time.hours": "{number}h", + "relative_time.just_now": "now", + "relative_time.minutes": "{number}m", + "relative_time.seconds": "{number}s", "reply_indicator.cancel": "Ångra", "report.placeholder": "Ytterligare kommentarer", "report.submit": "Skicka", @@ -180,6 +188,7 @@ "status.load_more": "Ladda fler", "status.media_hidden": "Media dold", "status.mention": "Omnämn @{name}", + "status.more": "More", "status.mute_conversation": "Tysta konversation", "status.open": "Utvidga denna status", "status.pin": "Fäst i profil", @@ -200,6 +209,7 @@ "tabs_bar.home": "Hem", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Meddelanden", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Dra & släpp för att ladda upp", "upload_button.label": "Lägg till media", "upload_form.description": "Beskriv för synskadade", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index db3f3dd0d..3e8422dd1 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -9,7 +9,9 @@ "account.follows_you": "Follows you", "account.media": "Media", "account.mention": "Mention @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Mute @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Posts", "account.report": "Report @{name}", "account.requested": "Awaiting approval", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Unfollow", "account.unmute": "Unmute @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", "follow_request.authorize": "Authorize", @@ -104,6 +108,7 @@ "loading_indicator.label": "Loading...", "media_gallery.toggle_visible": "Toggle visibility", "missing_indicator.label": "Not found", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", "navigation_bar.edit_profile": "Edit profile", @@ -204,6 +209,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notifications", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index cdd3581da..42f67729e 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -9,7 +9,9 @@ "account.follows_you": "Seni takip ediyor", "account.media": "Media", "account.mention": "Bahset @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "Sustur @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Gönderiler", "account.report": "Rapor et @{name}", "account.requested": "Onay bekleniyor", @@ -18,6 +20,7 @@ "account.unblock_domain": "Unhide {domain}", "account.unfollow": "Takipten vazgeç", "account.unmute": "Sesi aç @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "Bir dahaki sefere {combo} tuşuna basabilirsiniz", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Henüz bu hashtag’e sahip hiçbir gönderi yok.", "empty_column.home": "Henüz kimseyi takip etmiyorsunuz. {public} ziyaret edebilir veya arama kısmını kullanarak diğer kullanıcılarla iletişime geçebilirsiniz.", "empty_column.home.public_timeline": "herkese açık zaman tüneli", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "Henüz hiçbir bildiriminiz yok. Diğer insanlarla sobhet edebilmek için etkileşime geçebilirsiniz.", "empty_column.public": "Burada hiçbir gönderi yok! Herkese açık bir şeyler yazın, veya diğer sunucudaki insanları takip ederek bu alanın dolmasını sağlayın", "follow_request.authorize": "Yetkilendir", @@ -104,6 +108,7 @@ "loading_indicator.label": "Yükleniyor...", "media_gallery.toggle_visible": "Görünürlüğü değiştir", "missing_indicator.label": "Bulunamadı", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Engellenen kullanıcılar", "navigation_bar.community_timeline": "Yerel zaman tüneli", "navigation_bar.edit_profile": "Profili düzenle", @@ -204,6 +209,7 @@ "tabs_bar.home": "Ana sayfa", "tabs_bar.local_timeline": "Yerel", "tabs_bar.notifications": "Bildirimler", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Upload için sürükle bırak yapınız", "upload_button.label": "Görsel ekle", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 930529f15..514f988c7 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -9,7 +9,9 @@ "account.follows_you": "Підписаний(-а) на Вас", "account.media": "Медія", "account.mention": "Згадати", + "account.moved_to": "{name} has moved to:", "account.mute": "Заглушити", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "Пости", "account.report": "Поскаржитися", "account.requested": "Очікує підтвердження", @@ -18,6 +20,7 @@ "account.unblock_domain": "Розблокувати {domain}", "account.unfollow": "Відписатися", "account.unmute": "Зняти глушення", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "View full profile", "boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу", "bundle_column_error.body": "Something went wrong while loading this component.", @@ -83,6 +86,7 @@ "empty_column.hashtag": "Дописів з цим хештегом поки не існує.", "empty_column.home": "Ви поки ні на кого не підписані. Погортайте {public}, або скористуйтесь пошуком, щоб освоїтися та познайомитися з іншими користувачами.", "empty_column.home.public_timeline": "публічні стрічки", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "У вас ще немає сповіщень. Переписуйтесь з іншими користувачами, щоб почати розмову.", "empty_column.public": "Тут поки нічого немає! Опублікуйте щось, або вручну підпишіться на користувачів інших інстанцій, щоб заповнити стрічку.", "follow_request.authorize": "Авторизувати", @@ -104,6 +108,7 @@ "loading_indicator.label": "Завантаження...", "media_gallery.toggle_visible": "Показати/приховати", "missing_indicator.label": "Не знайдено", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "Заблоковані користувачі", "navigation_bar.community_timeline": "Локальна стрічка", "navigation_bar.edit_profile": "Редагувати профіль", @@ -204,6 +209,7 @@ "tabs_bar.home": "Головна", "tabs_bar.local_timeline": "Локальна", "tabs_bar.notifications": "Сповіщення", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Перетягніть сюди, щоб завантажити", "upload_button.label": "Додати медіаконтент", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/whitelist_sv.json b/app/javascript/mastodon/locales/whitelist_sv.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/app/javascript/mastodon/locales/whitelist_sv.json @@ -0,0 +1,2 @@ +[ +] diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index bbdf34d2f..a820b9790 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -9,9 +9,9 @@ "account.follows_you": "关注了你", "account.media": "媒体", "account.mention": "提及 @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "隐藏 @{name}", "account.mute_notifications": "隐藏来自 @{name} 的通知", - "account.unmute_notifications": "不再隐藏来自 @{name} 的通知", "account.posts": "嘟文", "account.report": "举报 @{name}", "account.requested": "正在等待对方同意。点击以取消发送关注请求", @@ -20,6 +20,7 @@ "account.unblock_domain": "不再隐藏来自 {domain} 的内容", "account.unfollow": "取消关注", "account.unmute": "不再隐藏 @{name}", + "account.unmute_notifications": "不再隐藏来自 @{name} 的通知", "account.view_full_profile": "查看完整资料", "boost_modal.combo": "下次按住 {combo} 即可跳过此提示", "bundle_column_error.body": "载入组件出错。", @@ -85,6 +86,7 @@ "empty_column.hashtag": "这个话题标签下暂时没有内容。", "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。", "empty_column.home.public_timeline": "公共时间轴", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "你还没有收到过通知信息,快向其他用户搭讪吧。", "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户,这里就会有嘟文出现了哦!", "follow_request.authorize": "同意", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index f6f56fee1..be5a47370 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -9,7 +9,9 @@ "account.follows_you": "關注你", "account.media": "媒體", "account.mention": "提及 @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "將 @{name} 靜音", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "文章", "account.report": "舉報 @{name}", "account.requested": "等候審批", @@ -18,6 +20,7 @@ "account.unblock_domain": "不再隱藏 {domain}", "account.unfollow": "取消關注", "account.unmute": "取消 @{name} 的靜音", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "查看完整資料", "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},", "bundle_column_error.body": "加載本組件出錯。", @@ -83,6 +86,7 @@ "empty_column.hashtag": "這個標籤暫時未有內容。", "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。", "empty_column.home.public_timeline": "公共時間軸", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。", "empty_column.public": "跨站時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。", "follow_request.authorize": "批准", @@ -104,6 +108,7 @@ "loading_indicator.label": "載入中...", "media_gallery.toggle_visible": "打開或關上", "missing_indicator.label": "找不到內容", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "被你封鎖的用戶", "navigation_bar.community_timeline": "本站時間軸", "navigation_bar.edit_profile": "修改個人資料", @@ -204,6 +209,7 @@ "tabs_bar.home": "主頁", "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "將檔案拖放至此上載", "upload_button.label": "上載媒體檔案", "upload_form.description": "Describe for the visually impaired", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 1f43c6a20..9aa9973fe 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -9,7 +9,9 @@ "account.follows_you": "關注你", "account.media": "媒體", "account.mention": "提到 @{name}", + "account.moved_to": "{name} has moved to:", "account.mute": "消音 @{name}", + "account.mute_notifications": "Mute notifications from @{name}", "account.posts": "貼文", "account.report": "檢舉 @{name}", "account.requested": "正在等待許可", @@ -18,6 +20,7 @@ "account.unblock_domain": "不再隱藏 {domain}", "account.unfollow": "取消關注", "account.unmute": "不再消音 @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", "account.view_full_profile": "查看完整資訊", "boost_modal.combo": "下次你可以按 {combo} 來跳過", "bundle_column_error.body": "加載本組件出錯。", @@ -83,6 +86,7 @@ "empty_column.hashtag": "這個主題標籤下什麼都沒有。", "empty_column.home": "你還沒關注任何人。造訪{public}或利用搜尋功能找到其他用者。", "empty_column.home.public_timeline": "公開時間軸", + "empty_column.list": "There is nothing in this list yet.", "empty_column.notifications": "還沒有任何通知。和別的使用者互動來開始對話。", "empty_column.public": "這裡什麼都沒有!公開寫些什麼,或是關注其他副本的使用者。", "follow_request.authorize": "授權", @@ -104,6 +108,7 @@ "loading_indicator.label": "讀取中...", "media_gallery.toggle_visible": "切換可見性", "missing_indicator.label": "找不到", + "mute_modal.hide_notifications": "Hide notifications from this user?", "navigation_bar.blocks": "封鎖的使用者", "navigation_bar.community_timeline": "本地時間軸", "navigation_bar.edit_profile": "編輯用者資訊", @@ -204,6 +209,7 @@ "tabs_bar.home": "家", "tabs_bar.local_timeline": "本地", "tabs_bar.notifications": "通知", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "拖放來上傳", "upload_button.label": "增加媒體", "upload_form.description": "Describe for the visually impaired", -- cgit From d07983b56d83832e179c5d8ba41ee1284e215278 Mon Sep 17 00:00:00 2001 From: spla Date: Sat, 25 Nov 2017 15:22:59 +0100 Subject: Updated Catalan strings (#5801) * Updated Catalan strings * Update ca.yml * Update ca.yml * Update ca.yml * Update ca.yml * Update ca.yml * Update ca.yml * Update ca.yml * Update simple_form.ca.yml * Update simple_form.ca.yml * Update simple_form.ca.yml * bundle exec i18n-tasks * Update ca.json * Update simple_form.ca.yml --- app/javascript/mastodon/locales/ca.json | 28 ++-- config/locales/ca.yml | 238 +++++++++++++++++++++++++++----- config/locales/simple_form.ca.yml | 69 +++++---- 3 files changed, 258 insertions(+), 77 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index d8c2cc3e5..7ffdbfe5a 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -9,18 +9,18 @@ "account.follows_you": "et segueix", "account.media": "Media", "account.mention": "Esmentar @{name}", - "account.moved_to": "{name} has moved to:", + "account.moved_to": "{name} s'ha mogut a:", "account.mute": "Silenciar @{name}", - "account.mute_notifications": "Mute notifications from @{name}", + "account.mute_notifications": "Notificacions desactivades de @{name}", "account.posts": "Publicacions", "account.report": "Informe @{name}", - "account.requested": "Esperant aprovació", + "account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment", "account.share": "Compartir el perfil de @{name}", "account.unblock": "Desbloquejar @{name}", "account.unblock_domain": "Mostra {domain}", "account.unfollow": "Deixar de seguir", "account.unmute": "Treure silenci de @{name}", - "account.unmute_notifications": "Unmute notifications from @{name}", + "account.unmute_notifications": "Activar notificacions de @{name}", "account.view_full_profile": "Veure el perfil complet", "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop", "bundle_column_error.body": "S'ha produït un error en carregar aquest component.", @@ -54,7 +54,7 @@ "compose_form.publish_loud": "{publish}!", "compose_form.sensitive": "Marcar multimèdia com a sensible", "compose_form.spoiler": "Amagar text darrera l'advertència", - "compose_form.spoiler_placeholder": "Advertència de contingut", + "compose_form.spoiler_placeholder": "Escriu l'advertència aquí", "confirmation_modal.cancel": "Cancel·lar", "confirmations.block.confirm": "Bloquejar", "confirmations.block.message": "Estàs segur que vols bloquejar {name}?", @@ -67,7 +67,7 @@ "confirmations.unfollow.confirm": "Deixar de seguir", "confirmations.unfollow.message": "Estàs segur que vols deixar de seguir {name}?", "embed.instructions": "Incrusta aquest estat al lloc web copiant el codi a continuació.", - "embed.preview": "A continuació s'explica com:", + "embed.preview": "Aquí tenim quin aspecte tindrá:", "emoji_button.activity": "Activitat", "emoji_button.custom": "Personalitzat", "emoji_button.flags": "Flags", @@ -99,7 +99,7 @@ "home.column_settings.advanced": "Avançat", "home.column_settings.basic": "Bàsic", "home.column_settings.filter_regex": "Filtrar per expressió regular", - "home.column_settings.show_reblogs": "Mostrar 'boosts'", + "home.column_settings.show_reblogs": "Mostrar impulsos", "home.column_settings.show_replies": "Mostrar respostes", "home.settings": "Ajustos de columna", "lightbox.close": "Tancar", @@ -108,7 +108,7 @@ "loading_indicator.label": "Carregant...", "media_gallery.toggle_visible": "Alternar visibilitat", "missing_indicator.label": "No trobat", - "mute_modal.hide_notifications": "Hide notifications from this user?", + "mute_modal.hide_notifications": "Amagar notificacions d'aquest usuari?", "navigation_bar.blocks": "Usuaris bloquejats", "navigation_bar.community_timeline": "Línia de temps Local", "navigation_bar.edit_profile": "Editar perfil", @@ -132,7 +132,7 @@ "notifications.column_settings.mention": "Mencions:", "notifications.column_settings.push": "Push notificacions", "notifications.column_settings.push_meta": "Aquest dispositiu", - "notifications.column_settings.reblog": "Boosts:", + "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Mostrar en la columna", "notifications.column_settings.sound": "Reproduïr so", "onboarding.done": "Fet", @@ -164,11 +164,11 @@ "privacy.public.short": "Públic", "privacy.unlisted.long": "No publicar en línies de temps públiques", "privacy.unlisted.short": "No llistat", - "relative_time.days": "fa {number} jorns", + "relative_time.days": "fa {number} dies", "relative_time.hours": "fa {number} hores", "relative_time.just_now": "ara", - "relative_time.minutes": "fa {number} minutes", - "relative_time.seconds": "fa {number} segondes", + "relative_time.minutes": "fa {number} minuts", + "relative_time.seconds": "fa {number} segons", "reply_indicator.cancel": "Cancel·lar", "report.placeholder": "Comentaris addicionals", "report.submit": "Enviar", @@ -192,7 +192,7 @@ "status.mute_conversation": "Silenciar conversació", "status.open": "Ampliar aquest estat", "status.pin": "Fixat en el perfil", - "status.reblog": "Boost", + "status.reblog": "Impuls", "status.reblogged_by": "{name} ha retootejat", "status.reply": "Respondre", "status.replyAll": "Respondre al tema", @@ -209,7 +209,7 @@ "tabs_bar.home": "Inici", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacions", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "ui.beforeunload": "El vostre esborrany es perdrà si sortiu de Mastodon.", "upload_area.title": "Arrossega i deixa anar per carregar", "upload_button.label": "Afegir multimèdia", "upload_form.description": "Descriure els problemes visuals", diff --git a/config/locales/ca.yml b/config/locales/ca.yml index f32c8b44b..fa8cf49f0 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1,6 +1,7 @@ --- ca: about: + about_hashtag_html: Aquests són toots públics etiquetats amb #%{hashtag}. Pots interactuar amb ells si tens un compte a qualsevol lloc del fediverse. about_mastodon_html: Mastodon és un servidor de xarxa social lliure i de codi obert. Una alternativa descentralitzada a plataformes comercials, que evita el risc que una única companyia monopolitzi la teva comunicació. Qualsevol pot executar Mastodon i participar sense problemes en la xarxa social. about_this: Quant a aquesta instància closed_registrations: Els registres estan actualment tancats en aquesta instància. @@ -37,38 +38,62 @@ ca: follow: Segueix followers: Seguidors following: Seguint + media: Mèdia + moved_html: "%{name} s'ha mogut a %{new_profile_link}:" nothing_here: No hi ha res aquí! people_followed_by: Usuaris a qui %{name} segueix people_who_follow: Usuaris que segueixen %{name} posts: Toots - remote_follow: Segueix + posts_with_replies: Toots i respostes + remote_follow: Seguiment remot reserved_username: El nom d'usuari està reservat - unfollow: Deixa de seguir + roles: + admin: Admin + moderator: Mod + unfollow: Deixar de seguir admin: + account_moderation_notes: + account: Moderador + create: Crear + created_at: Data + created_msg: La nota de moderació s'ha creat correctament. + delete: Suprimeix + destroyed_msg: S'ha destruït la nota de moderació. accounts: are_you_sure: Estàs segur? + by_domain: Domini confirm: Confirma confirmed: Confirmat - disable_two_factor_authentication: Desactiva 2FA + demote: Degrada + disable: Inhabilita + disable_two_factor_authentication: Desactivar 2FA + disabled: Inhabilita display_name: Nom de visualització domain: Domini - edit: Edita - email: Correu-e - feed_url: URL del canal + edit: Editar + email: E-mail + enable: Habilitar + enabled: Habilitat + feed_url: URL del feed followers: Seguidors + followers_url: URL dels seguidors follows: Segueix + inbox_url: URL de la safata d'entrada ip: IP location: all: Tot local: Local remote: Remot - title: Ubicació + title: Localització + login_status: Estat d'accés media_attachments: Adjunts multimèdia + memorialize: Es converteix en memoriam moderation: all: Tot silenced: Silenciat suspended: Suspès title: Moderació + moderation_notes: Notes de moderació most_recent_activity: Activitat més recent most_recent_ip: IP més recent not_subscribed: No subscrit @@ -76,16 +101,25 @@ ca: alphabetic: Alfabètic most_recent: Més recent title: Ordre - perform_full_suspension: Aplica la suspensió completa + outbox_url: URL de la bústia de sortida + perform_full_suspension: Aplicar suspensió completa profile_url: URL del perfil + promote: Promociona + protocol: Protocol public: Públic push_subscription_expires: La subscripció PuSH expira - redownload: Actualitza l'avatar - reset: Reajusta - reset_password: Restableix la contrasenya - resubscribe: Torna a subscriure + redownload: Refrescar avatar + reset: Reajustar + reset_password: Restablir la contrasenya + resubscribe: Resubscribir + role: Permisos + roles: + admin: Administrador + moderator: Moderador + user: Usuari salmon_url: URL Salmon search: Cerca + shared_inbox_url: URL de la safata d'entrada compartida show: created_reports: Informes creats per aquest compte report: informe @@ -99,6 +133,30 @@ ca: unsubscribe: Donar-se de baixa username: Nom d'usuari web: Web + custom_emojis: + copied_msg: S'ha creat correctament la còpia local del emoji + copy: Copia + copy_failed_msg: No s'ha pogut fer una còpia local d'aquest emoji + created_msg: Emoji creat amb èxit! + delete: Suprimeix + destroyed_msg: Emojo s'ha destruït amb èxit! + disable: Inhabilita + disabled_msg: S'ha inhabilitat l'emoji amb èxit + emoji: Emoji + enable: Habilita + enabled_msg: S'ha habilitat amb èxit emoji + image_hint: PNG de fins a 50 KB + listed: Enumerat + new: + title: Afegeix nou emoji personalitzat + overwrite: Sobreescriure + shortcode: Codi curt + shortcode_hint: Com a mínim 2 caràcters, només caràcters alfanumèrics i guions baixos + title: Emojis personatlitzats + unlisted: Sense classificar + update_failed_msg: No s'ha pogut actualitzar aquest emoji + updated_msg: Emoji s'ha actualitzat correctament. + upload: Carrega domain_blocks: add_new: Afegeix created_msg: El bloqueig de domini ara s'està processant @@ -130,10 +188,22 @@ ca: title: Desfés el bloqueig de domini de %{domain} undo: Desfés title: Bloquejos de domini - undo: Desfés + undo: Desfer + email_domain_blocks: + add_new: Afegir nou + created_msg: S'ha creat el bloc de domini de correu electrònic + delete: Suprimeix + destroyed_msg: S'ha eliminat correctament el bloc del domini de correu + domain: Domini + new: + create: Crear bloc + title: Nou bloc de domini de correu electrònic + title: Bloc de domini de correu electrònic instances: account_count: Comptes coneguts domain_name: Domini + reset: Restablir + search: Cerca title: Instàncies conegudes reports: action_taken_by: Mesures adoptades per @@ -141,12 +211,12 @@ ca: comment: label: Comentari none: Cap - delete: Esborra + delete: Suprimeix id: ID mark_as_resolved: Marca com a resolt nsfw: - 'false': APTE - 'true': NO APTE (NSFW) + 'false': Mostra els fitxers multimèdia adjunts + 'true': Amaga els fitxers multimèdia adjunts report: 'Informe #%{id}' report_contents: Continguts reported_account: Compte reportat @@ -160,6 +230,9 @@ ca: unresolved: No resolt view: Visualització settings: + bootstrap_timeline_accounts: + desc_html: Separa diversos noms d'usuari amb comes. Només funcionaran els comptes locals i desbloquejats. El valor predeterminat quan està buit és tots els administradors locals.. + title: El seguiment per defecte per als nous usuaris contact_information: email: Introdueix una adreça de correu electrònic pùblica username: Introdueix un nom d'usuari @@ -173,6 +246,9 @@ ca: open: desc_html: Permet que qualsevol pugui crear un compte title: Registre obert + show_staff_badge: + desc_html: Mostra una insígnia de personal en una pàgina d'usuari + title: Mostra insígnia de personal site_description: desc_html: Es mostra com un paràgraf a la pàgina principal i s'utilitza com una etiqueta meta.
Pots utilitzar etiquetes HTML, en particular <a> i <em>. title: Descripció del lloc @@ -183,6 +259,9 @@ ca: desc_html: Pots escriure la teva pròpia política de privadesa, els termes del servei o d'altres normes legals. Pots utilitzar etiquetes HTML title: Termes del servei personalitzats site_title: Títol del lloc + thumbnail: + desc_html: S'utilitza per obtenir visualitzacions prèvies a través d'OpenGraph i API. Es recomana 1200x630px + title: Miniatura de la Instància timeline_preview: desc_html: Mostra la línia de temps pública a la pàgina inicial title: Vista prèvia de la línia de temps @@ -190,9 +269,9 @@ ca: statuses: back_to_account: Torna a la pàgina del compte batch: - delete: Esborra - nsfw_off: APTE - nsfw_on: NO APTE (NSFW) + delete: Suprimeix + nsfw_off: NSFW OFF + nsfw_on: NSFW ON execute: Executa failed_to_execute: No s'ha pogut executar media: @@ -220,7 +299,13 @@ ca: signature: Notificacions de Mastodon des de %{instance} view: 'Vista:' applications: - invalid_url: L'URL proporcionat no és correcte + created: L'aplicació s'ha creat correctament + destroyed: L'aplicació s'ha suprimit correctament + invalid_url: La URL proporcionada es incorrecte + regenerate_token: Regenerar token d'accés + token_regenerated: Token d'accés s'ha generat correctament + warning: Aneu amb compte amb aquestes dades. No ho compartiu mai amb ningú! + your_token: El token d'accés auth: agreement_html: En inscriure't, acceptes els nostres termes del servei i la nostra política de privadesa. change_password: Canvia la contrasenya @@ -263,9 +348,9 @@ ca: bad_password_msg: Bon intent hackers! La contrasenya no és correcta confirm_password: Introdueix la contrasenya actual per a verificar la teva identitat description_html: Això eliminarà de forma irreversible i permanent el contingut del teu compte i el desactivarà. El teu nom d'usuari romandrà reservat per evitar que algú volgués fer-se passar per tu. - proceed: Esborra el compte - success_msg: El compte s'ha eliminat correctament - warning_html: Només està garantida l'eliminació en aquesta instància en particular. El contingut que ha estat àmpliament compartit que deixi petjades. Els servidors fora de línia i els que ja no estan subscrits no actualitzaran les seves bases de dades. + proceed: Suprimir el compte + success_msg: El teu compte s'ha eliminat correctament + warning_html: Només està garantida l'eliminació d'aquesta particular instància. El contingut que ha estat àmpliament compartit que deixi petjades. Els servidors fora de línia i els que ja no estan subscrits no actualitzaran les seves bases de dades. warning_title: Disponibilitat de contingut disseminat errors: '403': No tens permís per a veure aquesta pàgina. @@ -275,7 +360,10 @@ ca: content: La verificació de seguretat ha fallat. Bloques les galetes? title: La verificació de seguretat ha fallat '429': Estrangulat - noscript_html: Activa JavaScript per a utilitzar Mastodon. + '500': + content: We're sorry, but something went wrong on our end. + title: This page is not correct + noscript_html: Per utilitzar Mastodon si us plau activa JavaScript. També podeu provar una de les aplicacions natives per Mastodon per a la vostra plataforma. exports: blocks: Persones que has bloquejat csv: CSV @@ -307,9 +395,10 @@ ca: types: blocking: Llista de blocats following: Llista de seguits - muting: Llista de silenciats - upload: Carrega - landing_strip_html: "%{name} és un usuari/a de %{link_to_root_path}. Pots seguir-lo o interactuar amb ell si tens un compte a qualsevol node del fediverse." + muting: Llista d'apagats + upload: Carregar + in_memoriam_html: En Memòria. + landing_strip_html: "%{name} és un usuari/a de %{link_to_root_path}. Pots seguir-lo/la o interactuar amb ell/a si tens un compte a qualsevol node del fediverse." landing_strip_signup_html: Si no en tens, pots registrar-te aquí. media_attachments: validations: @@ -355,6 +444,11 @@ ca: next: Següent prev: Enrere truncate: "…" + preferences: + languages: Idiomes + other: Altre + publishing: Publicació + web: Web push_notifications: favourite: title: "%{name} ha marcat com a preferit el teu estat" @@ -417,17 +511,25 @@ ca: authorized_apps: Aplicacions autoritzades back: Torna a l'inici delete: Eliminació del compte - edit_profile: Edita el perfil - export: Exporta la informació + development: Desenvolupament + edit_profile: Editar perfil + export: Exportar informació followers: Seguidors autoritzats - import: Importa + import: Importar + notifications: Notificacions preferences: Preferències settings: Configuració two_factor_authentication: Autenticació de dos factors + your_apps: Les teves aplicacions statuses: open_in_web: Obre en la web over_character_limit: Límit de caràcters de %{max} superat - show_more: Mostra'n més + pin_errors: + limit: S'han fixat massa toots + ownership: El toot d'algú altre no es pot fixar + private: No es pot fixar el toot no públic + reblog: No es pot fixar un impuls + show_more: Mostrar més visibilities: private: Només seguidors private_long: Mostra només als seguidors @@ -436,11 +538,79 @@ ca: unlisted: No llistat unlisted_long: Tothom ho pot veure, però no es mostra en la història federada stream_entries: - click_to_show: Clic per a mostrar - reblogged: retootejat + click_to_show: Clic per mostrar + pinned: Toot fixat + reblogged: impulsat sensitive_content: Contingut sensible terms: - body_html: "

Política de privacitat

\n\n

Quina informació recollim?

\n\n

Recopilem informació teva quan et registres en aquesta instància i recopilem dades quan participes en el fòrum llegint, escrivint i avaluant el contingut aquí compartit.

\n\n

En registrar-te en aquesta instància, se't pot demanar que introduexisu el teu nom i l'adreça de correu electrònic. També pots visitar el nostre lloc sense registrar-te. La teva adreça de correu electrònic es verificarà mitjançant un correu electrònic que conté un enllaç únic. Si es visita aquest enllaç, sabem que controles l'adreça de correu electrònic.

\n\n

Quan es registra i publica, registrem l'adreça IP de la qual es va originar la publicació. També podrem conservar els registres del servidor que inclouen l'adreça IP de cada sol·licitud al nostre servidor.

\n\n

Per a què utilitzem la teva informació?

\n\n

Qualsevol de la informació que recopilem de tu pot utilitzar-se d'una de les maneres següents:

\n\n
    \n
  • Per a personalitzar la teva experiència — la teva informació ens ajuda a respondre millor a les teves necessitats individuals.
  • \n
  • Per millorar el nostre lloc — ens esforcem contínuament per millorar les nostres ofertes de llocs basats en la informació i els comentaris que rebem de tu.
  • \n
  • Per millorar el servei al client — la teva informació ens ajuda a respondre més eficaçment a les teves sol·licituds de servei al client i a les necessitats de suport.
  • \n
  • Per enviar correus electrònics periòdics — l'adreça electrònica que proporcionis es pot utilitzar per enviar-te informació, notificacions que sol·licitis sobre canvis en temes o en resposta al teu nom d'usuari, respondre a les consultes i/o altres sol·licituds o preguntes.
  • \n
\n\n

Com protegim la teva informació?

\n\n

Implementem diverses mesures de seguretat per mantenir la seguretat de la teva informació personal quan introdueixes, envies o accedeixes a la teva informació personal.

\n\n

Quina és la nostre política de retenció de dades?

\n\n

Farem un esforç de bona fe per a:

\n\n
    \n
  • Conserva els registres de servidor que continguin l'adreça IP de totes les sol·licituds a aquest servidor no més de 90 dies.
  • \n
  • Conserva les adreces IP associades als usuaris registrats i les seves publicacions no més de 5 anys.
  • \n
\n\n

Utilitzem galetes?

\n\n

Sí. Les cookies són fitxers petits que un lloc o el proveïdor de serveis transfereix al disc dur del vostre ordinador a través del navegador web (si ho permet). Aquestes galetes permeten al lloc reconèixer el vostre navegador i, si teniu un compte registrat, associar-lo al vostre compte registrat.

\n\n

Utilitzem cookies per comprendre i desar les vostres preferències per a futures visites i compilar dades agregades sobre el trànsit del lloc i la interacció del lloc, de manera que podrem oferir millors experiències i eines del lloc en el futur. Podem contractar amb proveïdors de serveis de tercers per ajudar-nos a comprendre millor els visitants del nostre lloc. Aquests proveïdors de serveis no estan autoritzats a utilitzar la informació recollida en nom nostre, excepte per ajudar-nos a dur a terme i millorar el nostre negoci.

\n\n

Publiquem informació al exterior?

\n\n

No venem, comercialitzem ni transmetem a tercers la vostra informació d'identificació personal. Això no inclou tercers de confiança que ens ajudin a operar el nostre lloc, a dur a terme el nostre negoci o a fer-ho, sempre que aquestes parts acceptin mantenir confidencial aquesta informació. També podem publicar la vostra informació quan creiem que l'alliberament és apropiat per complir amb la llei, fer complir les polítiques del nostre lloc o protegir els nostres drets o altres drets, propietat o seguretat. No obstant això, la informació de visitant que no sigui personalment identificable es pot proporcionar a altres parts per a la comercialització, la publicitat o altres usos.

\n\n

Vincles de tercers

\n\n

De tant en tant, segons el nostre criteri, podem incloure o oferir productes o serveis de tercers al nostre lloc. Aquests llocs de tercers tenen polítiques de privadesa separades i independents. Per tant, no tenim responsabilitat ni responsabilitat civil pel contingut i les activitats d'aquests llocs enllaçats. No obstant això, busquem protegir la integritat del nostre lloc i donem la benvinguda a qualsevol comentari sobre aquests llocs.

\n\n

Compliment de la Llei de protecció de la privacitat en línia dels nens

\n\n

El nostre lloc, productes i serveis estan dirigits a persones que tenen almenys 13 anys. Si aquest servidor es troba als EUA, i teniu menys de 13 anys, segons els requisits de COPPA (Children's Online Privacy Protection Act) no feu servir aquest lloc.

\n\n

Només la política de privacitat en línia

\n\n

Aquesta política de privacitat en línia només s'aplica a la informació recopilada a través del nostre lloc i no a la informació recopilada fora de línia.

\n\n

El vostre consentiment

\n\n

En utilitzar el nostre lloc, accepta la política de privadesa del nostre lloc web.

\n\n

Canvis a la nostra política de privacitat

\n\n

Si decidim canviar la nostra política de privadesa, publicarem aquests canvis en aquesta pàgina.

\n\n

Aquest document és CC-BY-SA. Es va actualitzar per última vegada el 31 de maig de 2013.

\n\n

Originalment adaptat a la política de privadesa del Discurs.

\n" + body_html: | +

Política de privacitat

+ +

Quina informació recollim?

+ +

Recopilem informació teva quan et registres en aquesta instància i recopilem dades quan participes en el fòrum llegint, escrivint i avaluant el contingut aquí compartit.

+ +

En registrar-te en aquesta instància, se't pot demanar que introduexisu el teu nom i l'adreça de correu electrònic. També pots visitar el nostre lloc sense registrar-te. La teva adreça de correu electrònic es verificarà mitjançant un correu electrònic que conté un enllaç únic. Si es visita aquest enllaç, sabem que controles l'adreça de correu electrònic.

+ +

Quan es registra i publica, registrem l'adreça IP de la qual es va originar la publicació. També podrem conservar els registres del servidor que inclouen l'adreça IP de cada sol·licitud al nostre servidor.

+ +

Per a què utilitzem la teva informació?

+ +

Qualsevol de la informació que recopilem de tu pot utilitzar-se d'una de les maneres següents:

+ +
    +
  • Per a personalitzar la teva experiència — la teva informació ens ajuda a respondre millor a les teves necessitats individuals.
  • +
  • Per millorar el nostre lloc — ens esforcem contínuament per millorar les nostres ofertes de llocs basats en la informació i els comentaris que rebem de tu.
  • +
  • Per millorar el servei al client — la teva informació ens ajuda a respondre més eficaçment a les teves sol·licituds de servei al client i a les necessitats de suport.
  • +
  • Per enviar correus electrònics periòdics — l'adreça electrònica que proporcionis es pot utilitzar per enviar-te informació, notificacions que sol·licitis sobre canvis en temes o en resposta al teu nom d'usuari, respondre a les consultes i/o altres sol·licituds o preguntes.
  • +
+ +

Com protegim la teva informació?

+ +

Implementem diverses mesures de seguretat per mantenir la seguretat de la teva informació personal quan introdueixes, envies o accedeixes a la teva informació personal.

+ +

Quina és la nostre política de retenció de dades?

+ +

Farem un esforç de bona fe per a:

+ +
    +
  • Conserva els registres de servidor que continguin l'adreça IP de totes les sol·licituds a aquest servidor no més de 90 dies.
  • +
  • Conserva les adreces IP associades als usuaris registrats i les seves publicacions no més de 5 anys.
  • +
+ +

Utilitzem galetes?

+ +

Sí. Les cookies són fitxers petits que un lloc o el proveïdor de serveis transfereix al disc dur del vostre ordinador a través del navegador web (si ho permet). Aquestes galetes permeten al lloc reconèixer el vostre navegador i, si teniu un compte registrat, associar-lo al vostre compte registrat.

+ +

Utilitzem cookies per comprendre i desar les vostres preferències per a futures visites i compilar dades agregades sobre el trànsit del lloc i la interacció del lloc, de manera que podrem oferir millors experiències i eines del lloc en el futur. Podem contractar amb proveïdors de serveis de tercers per ajudar-nos a comprendre millor els visitants del nostre lloc. Aquests proveïdors de serveis no estan autoritzats a utilitzar la informació recollida en nom nostre, excepte per ajudar-nos a dur a terme i millorar el nostre negoci.

+ +

Publiquem informació al exterior?

+ +

No venem, comercialitzem ni transmetem a tercers la vostra informació d'identificació personal. Això no inclou tercers de confiança que ens ajudin a operar el nostre lloc, a dur a terme el nostre negoci o a fer-ho, sempre que aquestes parts acceptin mantenir confidencial aquesta informació. També podem publicar la vostra informació quan creiem que l'alliberament és apropiat per complir amb la llei, fer complir les polítiques del nostre lloc o protegir els nostres drets o altres drets, propietat o seguretat. No obstant això, la informació de visitant que no sigui personalment identificable es pot proporcionar a altres parts per a la comercialització, la publicitat o altres usos.

+ +

Vincles de tercers

+ +

De tant en tant, segons el nostre criteri, podem incloure o oferir productes o serveis de tercers al nostre lloc. Aquests llocs de tercers tenen polítiques de privadesa separades i independents. Per tant, no tenim responsabilitat ni responsabilitat civil pel contingut i les activitats d'aquests llocs enllaçats. No obstant això, busquem protegir la integritat del nostre lloc i donem la benvinguda a qualsevol comentari sobre aquests llocs.

+ +

Compliment de la Llei de protecció de la privacitat en línia dels nens

+ +

El nostre lloc, productes i serveis estan dirigits a persones que tenen almenys 13 anys. Si aquest servidor es troba als EUA, i teniu menys de 13 anys, segons els requisits de COPPA (Children's Online Privacy Protection Act) no feu servir aquest lloc.

+ +

Només la política de privacitat en línia

+ +

Aquesta política de privacitat en línia només s'aplica a la informació recopilada a través del nostre lloc i no a la informació recopilada fora de línia.

+ + + +

En utilitzar el nostre lloc, accepta la política de privadesa del nostre lloc web.

+ +

Canvis a la nostra política de privacitat

+ +

Si decidim canviar la nostra política de privadesa, publicarem aquests canvis en aquesta pàgina.

+ +

Aquest document és CC-BY-SA. Es va actualitzar per última vegada el 31 de maig de 2013.

+ +

Originalment adaptat a la política de privadesa del Discurs.

title: "%{instance} Condicions del servei i política de privadesa" time: formats: diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 22902ba24..f8402af21 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -4,53 +4,64 @@ ca: hints: defaults: avatar: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 120x120px + digest: S'envia després d'un llarg període d'inactivitat amb un resum de les mencions que has rebut en la teva absència display_name: - one: Queda 1 caràcter - other: Queden %{count} caràcters + one: 1 càracter + other: %{count} càracters header: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 700x335px - locked: Cal que aprovis manualment els seguidors i les publicacions es mostraran només als teus seguidors + locked: Requereix que aprovis manualment seguidors i les publicacions seran mostrades només als teus seguidors note: - one: Queda 1 caràcgter - other: Queden %{count} caràcters + one: 1 càracter left + other: %{count} càracters + setting_noindex: Afecta el teu perfil públic i les pàgines d'estat + setting_theme: Afecta la manera en què Mastodon es veu quan està connectat des de qualsevol dispositiu. imports: - data: Fitxers CSV exportat des d'una altra instància de Mastodon + data: Arxiu CSV exportat desde una altra instància de Mastodon sessions: - otp: Introdueix el codi de dos factors des del telèfon o utilitza un dels teus codis de recuperació. + otp: Introdueix el codi de dos factors des del teu telèfon o utilitza un dels teus codis de recuperació. user: - filtered_languages: Els missatges en les llengües seleccionades s'eliminaran de les línies de temps públiques. + filtered_languages: Els idiomes seleccionats seran eliminats de les línies de temps públiques. labels: defaults: avatar: Avatar - confirm_new_password: Confirma la contrasenya nova - confirm_password: Confirma la contrasenya + confirm_new_password: Confirmar nova contrasenya + confirm_password: Confirmar contrasenya current_password: Contrasenya actual data: Informació - display_name: Nom visibile - email: Adreça de correu electrònic - header: Imgatge de capçalera - locale: Llengua - locked: Fes privat aquest compte - new_password: Contrasenya nova + display_name: Mostrar nom + email: Direcció de correu electrònic + filtered_languages: Idiomes filtrats + header: Img. capçalera + locale: Idioma + locked: Fer privat aquest compte + new_password: Nova contrasenya note: Biografia otp_attempt: Codi de dos factors password: Contrasenya - setting_auto_play_gif: Reprodueix automàticament els GIF animats - setting_boost_modal: Mostra la finestra de confirmació abans d'un retoot - setting_default_privacy: Privacitat de les publicacions - setting_delete_modal: Mostra la finestra de confirmació abans d'esborrar un toot + setting_auto_play_gif: Auto-reproducció de GIFs animats + setting_boost_modal: Mostrar finestra de confirmació abans d'un Retoot + setting_default_privacy: Privacitat de publicacions + setting_default_sensitive: Marca sempre els multimèdia com a sensibles + setting_delete_modal: Mostrar finestra de confirmació abans d'esborrar un toot + setting_noindex: Desactivació de la indexació del motor de cerca + setting_reduce_motion: Redueix el moviment en animacions + setting_system_font_ui: Utilitzeu el tipus de lletra predeterminat del sistema + setting_theme: Tema del lloc + setting_unfollow_modal: Mostra el diàleg de confirmació abans de deixar de seguir a algú severity: Severitat - type: Importa tipus + type: Importar tipus username: Nom d´usuari interactions: - must_be_follower: Blocar les notificacions de persones que no et segueixen - must_be_following: Blocar les notificacions de persones que no segueixes + must_be_follower: Bloquejar notificacions de persones que no et segueixen + must_be_following: Bloquejar notificacions de persones que no segueixes + must_be_following_dm: Bloqueja missatges directes de persones que no segueixes notification_emails: - digest: Envia un resum de correus electrònics - favourite: Envia un correu electrònic quan algú marqui com a preferit en la teva publicació - follow: Envia un correu electrònic quan algú et segueixi - follow_request: Envia un correu electrònic quan algú sol·liciti seguir-te - mention: Envia un correu electrònic quan algú et mencioni - reblog: Envia un correu electrònic quan algú comparteixi la teva publicació + digest: Enviar resum de correus electrònics + favourite: Enviar correu electrònic quan algú marqui com a favorit en la teva publicació + follow: Enviar correu electrònic quan algú et segueixi + follow_request: Enviar correu electrònic quan algú sol·liciti seguir-te + mention: Enviar correu electrònic quan algú et mencioni + reblog: Enviar correu electrònic quan algú comparteixi la seva publicació 'no': 'No' required: mark: "*" -- cgit From 1266c66f79aac2fc6dc77fe08ad41b72e1887796 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 25 Nov 2017 23:41:08 +0900 Subject: Rename ariaLabel property of Dropdown to title (#5813) DropdownMenu has ariaLabel property, but it is actually applied to title property of IconButton. Keep it consistent. --- app/javascript/mastodon/components/dropdown_menu.js | 8 ++++---- app/javascript/mastodon/components/status_action_bar.js | 2 +- app/javascript/mastodon/features/status/components/action_bar.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index 3a3ebf487..43dc0d6e3 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -110,7 +110,7 @@ export default class Dropdown extends React.PureComponent { icon: PropTypes.string.isRequired, items: PropTypes.array.isRequired, size: PropTypes.number.isRequired, - ariaLabel: PropTypes.string, + title: PropTypes.string, disabled: PropTypes.bool, status: ImmutablePropTypes.map, isUserTouching: PropTypes.func, @@ -120,7 +120,7 @@ export default class Dropdown extends React.PureComponent { }; static defaultProps = { - ariaLabel: 'Menu', + title: 'Menu', }; state = { @@ -186,14 +186,14 @@ export default class Dropdown extends React.PureComponent { } render () { - const { icon, items, size, ariaLabel, disabled } = this.props; + const { icon, items, size, title, disabled } = this.props; const { expanded } = this.state; return (
- +
); diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 7b65420d0..99834df6c 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -120,7 +120,7 @@ export default class ActionBar extends React.PureComponent { {shareButton}
- +
); -- cgit From bf7757cbbc7677fb46aee9a0a8d1b8a37ded7bb6 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sat, 25 Nov 2017 23:41:45 +0900 Subject: Allow to open a modal for embedded photo (#5777) --- .../mastodon/features/status/components/card.js | 36 ++++++++++++++++++++-- .../features/status/components/detailed_status.js | 2 +- .../features/ui/components/image_loader.js | 4 +-- .../mastodon/features/ui/components/media_modal.js | 2 +- app/javascript/styles/mastodon/components.scss | 11 +++---- 5 files changed, 41 insertions(+), 14 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index bb83374b9..680bf63ab 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import Immutable from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import punycode from 'punycode'; import classnames from 'classnames'; @@ -24,6 +25,7 @@ export default class Card extends React.PureComponent { static propTypes = { card: ImmutablePropTypes.map, maxDescription: PropTypes.number, + onOpenMedia: PropTypes.func.isRequired, }; static defaultProps = { @@ -34,6 +36,27 @@ export default class Card extends React.PureComponent { width: 0, }; + handlePhotoClick = () => { + const { card, onOpenMedia } = this.props; + + onOpenMedia( + Immutable.fromJS([ + { + type: 'image', + url: card.get('url'), + description: card.get('title'), + meta: { + original: { + width: card.get('width'), + height: card.get('height'), + }, + }, + }, + ]), + 0 + ); + }; + renderLink () { const { card, maxDescription } = this.props; @@ -73,9 +96,16 @@ export default class Card extends React.PureComponent { const { card } = this.props; return ( - - {card.get('title')} - + {card.get('title')} ); } diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 81f71749b..abdb9a3f6 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -73,7 +73,7 @@ export default class DetailedStatus extends ImmutablePureComponent { ); } } else if (status.get('spoiler_text').length === 0) { - media = ; + media = ; } if (status.get('application')) { diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js index aad594380..e3e7197c5 100644 --- a/app/javascript/mastodon/features/ui/components/image_loader.js +++ b/app/javascript/mastodon/features/ui/components/image_loader.js @@ -7,7 +7,7 @@ export default class ImageLoader extends React.PureComponent { static propTypes = { alt: PropTypes.string, src: PropTypes.string.isRequired, - previewSrc: PropTypes.string.isRequired, + previewSrc: PropTypes.string, width: PropTypes.number, height: PropTypes.number, } @@ -47,7 +47,7 @@ export default class ImageLoader extends React.PureComponent { this.removeEventListeners(); this.setState({ loading: true, error: false }); Promise.all([ - this.loadPreviewCanvas(props), + props.previewSrc && this.loadPreviewCanvas(props), this.hasSize() && this.loadOriginalImage(props), ].filter(Boolean)) .then(() => { diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index f41a83089..02591a51f 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -92,7 +92,7 @@ export default class MediaModal extends ImmutablePureComponent { const height = image.getIn(['meta', 'original', 'height']) || null; if (image.get('type') === 'image') { - return ; + return ; } else if (image.get('type') === 'gifv') { return ; } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index b70769e18..f4ad66271 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2182,15 +2182,12 @@ button.icon-button.active i.fa-retweet { } .status-card-photo { + cursor: zoom-in; display: block; text-decoration: none; - - img { - display: block; - width: 100%; - height: auto; - margin: 0; - } + width: 100%; + height: auto; + margin: 0; } .status-card-video { -- cgit From 57fe4102ea0f94cd451b440d4b23d9c11d91b49f Mon Sep 17 00:00:00 2001 From: SerCom_KC Date: Sun, 26 Nov 2017 01:38:40 +0800 Subject: i18n: (zh-CN) Add translations for #5746 & #5750 (#5816) --- app/javascript/mastodon/locales/zh-CN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index a820b9790..1c2c97067 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -9,7 +9,7 @@ "account.follows_you": "关注了你", "account.media": "媒体", "account.mention": "提及 @{name}", - "account.moved_to": "{name} has moved to:", + "account.moved_to": "{name} 已经迁移到:", "account.mute": "隐藏 @{name}", "account.mute_notifications": "隐藏来自 @{name} 的通知", "account.posts": "嘟文", @@ -86,7 +86,7 @@ "empty_column.hashtag": "这个话题标签下暂时没有内容。", "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。", "empty_column.home.public_timeline": "公共时间轴", - "empty_column.list": "There is nothing in this list yet.", + "empty_column.list": "这个列表中暂时没有内容。", "empty_column.notifications": "你还没有收到过通知信息,快向其他用户搭讪吧。", "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户,这里就会有嘟文出现了哦!", "follow_request.authorize": "同意", -- cgit From fd87e5a53bcaafb886a675c76e9256015e9db897 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 26 Nov 2017 09:45:17 +0900 Subject: Do not filter the status collection after muting and blocking (#5815) Filtering the status collection wipes out even the profiles of muted and blocked accounts. However, the behavior is inconsistent with the server- side behavior. --- app/javascript/mastodon/reducers/contexts.js | 13 +++++++++++++ app/javascript/mastodon/reducers/statuses.js | 19 ------------------- 2 files changed, 13 insertions(+), 19 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index 64d584a01..fe8308d0c 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -1,3 +1,7 @@ +import { + ACCOUNT_BLOCK_SUCCESS, + ACCOUNT_MUTE_SUCCESS, +} from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from '../actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; @@ -31,6 +35,12 @@ const deleteFromContexts = (state, id) => { return state; }; +const filterContexts = (state, relationship) => { + return state.map( + statuses => statuses.filter( + status => status.get('account') !== relationship.id)); +}; + const updateContext = (state, status, references) => { return state.update('descendants', map => { references.forEach(parentId => { @@ -49,6 +59,9 @@ const updateContext = (state, status, references) => { export default function contexts(state = initialState, action) { switch(action.type) { + case ACCOUNT_BLOCK_SUCCESS: + case ACCOUNT_MUTE_SUCCESS: + return filterContexts(state, action.relationship); case CONTEXT_FETCH_SUCCESS: return normalizeContext(state, action.id, action.ancestors, action.descendants); case TIMELINE_DELETE: diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index b1fb4c5da..5120b2b67 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -22,10 +22,6 @@ import { TIMELINE_DELETE, TIMELINE_EXPAND_SUCCESS, } from '../actions/timelines'; -import { - ACCOUNT_BLOCK_SUCCESS, - ACCOUNT_MUTE_SUCCESS, -} from '../actions/accounts'; import { NOTIFICATIONS_UPDATE, NOTIFICATIONS_REFRESH_SUCCESS, @@ -88,18 +84,6 @@ const deleteStatus = (state, id, references) => { return state.delete(id); }; -const filterStatuses = (state, relationship) => { - state.forEach(status => { - if (status.get('account') !== relationship.id) { - return; - } - - state = deleteStatus(state, status.get('id'), state.filter(item => item.get('reblog') === status.get('id'))); - }); - - return state; -}; - const initialState = ImmutableMap(); export default function statuses(state = initialState, action) { @@ -139,9 +123,6 @@ export default function statuses(state = initialState, action) { return normalizeStatuses(state, action.statuses); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.references); - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - return filterStatuses(state, action.relationship); default: return state; } -- cgit From 740f8a95a905e949b6a74bc69dcaf638d2d46248 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 27 Nov 2017 16:07:59 +0100 Subject: Add consumable invites (#5814) * Add consumable invites * Add UI for generating invite codes * Add tests * Display max uses and expiration in invites table, delete invite * Remove unused column and redundant validator - Default follows not used, probably bad idea - InviteCodeValidator is redundant because RegistrationsController checks invite code validity * Add admin setting to disable invites * Add admin UI for invites, configurable role for invite creation - Admin UI that lists everyone's invites, always available - Admin setting min_invite_role to control who can invite people - Non-admin invite UI only visible if users are allowed to * Do not remove invites from database, expire them instantly --- app/controllers/admin/invites_controller.rb | 33 ++++++++++++++++ app/controllers/admin/settings_controller.rb | 1 + app/controllers/auth/registrations_controller.rb | 21 ++++++++-- app/controllers/invites_controller.rb | 43 +++++++++++++++++++++ app/javascript/styles/mastodon/admin.scss | 16 ++++++++ app/models/form/admin_settings.rb | 2 + app/models/invite.rb | 45 ++++++++++++++++++++++ app/models/user.rb | 22 +++++++++++ app/policies/invite_policy.rb | 25 ++++++++++++ app/views/admin/action_logs/_action_log.html.haml | 2 +- app/views/admin/invites/_invite.html.haml | 15 ++++++++ app/views/admin/invites/index.html.haml | 22 +++++++++++ app/views/admin/settings/edit.html.haml | 5 +++ app/views/auth/registrations/new.html.haml | 1 + app/views/invites/_form.html.haml | 9 +++++ app/views/invites/_invite.html.haml | 11 ++++++ app/views/invites/index.html.haml | 19 +++++++++ config/locales/en.yml | 24 ++++++++++++ config/locales/simple_form.en.yml | 2 + config/navigation.rb | 3 ++ config/routes.rb | 6 +++ config/settings.yml | 1 + db/migrate/20171125024930_create_invites.rb | 15 ++++++++ .../20171125031751_add_invite_id_to_users.rb | 5 +++ db/schema.rb | 17 +++++++- spec/fabricators/invite_fabricator.rb | 6 +++ spec/models/invite_spec.rb | 30 +++++++++++++++ spec/models/user_spec.rb | 43 +++++++++++++++++++++ 28 files changed, 439 insertions(+), 5 deletions(-) create mode 100644 app/controllers/admin/invites_controller.rb create mode 100644 app/controllers/invites_controller.rb create mode 100644 app/models/invite.rb create mode 100644 app/policies/invite_policy.rb create mode 100644 app/views/admin/invites/_invite.html.haml create mode 100644 app/views/admin/invites/index.html.haml create mode 100644 app/views/invites/_form.html.haml create mode 100644 app/views/invites/_invite.html.haml create mode 100644 app/views/invites/index.html.haml create mode 100644 db/migrate/20171125024930_create_invites.rb create mode 100644 db/migrate/20171125031751_add_invite_id_to_users.rb create mode 100644 spec/fabricators/invite_fabricator.rb create mode 100644 spec/models/invite_spec.rb (limited to 'app/javascript') diff --git a/app/controllers/admin/invites_controller.rb b/app/controllers/admin/invites_controller.rb new file mode 100644 index 000000000..f4207e3e2 --- /dev/null +++ b/app/controllers/admin/invites_controller.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Admin + class InvitesController < BaseController + def index + authorize :invite, :index? + + @invites = Invite.includes(user: :account).page(params[:page]) + @invite = Invite.new + end + + def create + authorize :invite, :create? + + @invite = Invite.new(resource_params) + @invite.user = current_user + + if @invite.save + redirect_to admin_invites_path + else + @invites = Invite.page(params[:page]) + render :index + end + end + + def destroy + @invite = Invite.find(params[:id]) + authorize @invite, :destroy? + @invite.expire! + redirect_to admin_invites_path + end + end +end diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index d9199b3d5..eed5fb6b5 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -16,6 +16,7 @@ module Admin show_staff_badge bootstrap_timeline_accounts thumbnail + min_invite_role ).freeze BOOLEAN_SETTINGS = %w( diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 223db96ff..da0b6512f 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -16,13 +16,16 @@ class Auth::RegistrationsController < Devise::RegistrationsController def build_resource(hash = nil) super(hash) - resource.locale = I18n.locale + + resource.locale = I18n.locale + resource.invite_code = params[:invite_code] if resource.invite_code.blank? + resource.build_account if resource.account.nil? end def configure_sign_up_params devise_parameter_sanitizer.permit(:sign_up) do |u| - u.permit({ account_attributes: [:username] }, :email, :password, :password_confirmation) + u.permit({ account_attributes: [:username] }, :email, :password, :password_confirmation, :invite_code) end end @@ -35,7 +38,19 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def check_enabled_registrations - redirect_to root_path if single_user_mode? || !Setting.open_registrations + redirect_to root_path if single_user_mode? || !allowed_registrations? + end + + def allowed_registrations? + Setting.open_registrations || (invite_code.present? && Invite.find_by(code: invite_code)&.valid_for_use?) + end + + def invite_code + if params[:user] + params[:user][:invite_code] + else + params[:invite_code] + end end private diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb new file mode 100644 index 000000000..38d6c8d73 --- /dev/null +++ b/app/controllers/invites_controller.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class InvitesController < ApplicationController + include Authorization + + layout 'admin' + + before_action :authenticate_user! + + def index + authorize :invite, :create? + + @invites = Invite.where(user: current_user) + @invite = Invite.new(expires_in: 1.day.to_i) + end + + def create + authorize :invite, :create? + + @invite = Invite.new(resource_params) + @invite.user = current_user + + if @invite.save + redirect_to invites_path + else + @invites = Invite.where(user: current_user) + render :index + end + end + + def destroy + @invite = Invite.where(user: current_user).find(params[:id]) + authorize @invite, :destroy? + @invite.expire! + redirect_to invites_path + end + + private + + def resource_params + params.require(:invite).permit(:max_uses, :expires_in) + end +end diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index d4d62336f..7f078470e 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -448,3 +448,19 @@ color: $success-green; } } + +.name-tag { + display: flex; + align-items: center; + + .avatar { + display: block; + margin: 0; + margin-right: 5px; + border-radius: 50%; + } + + .username { + font-weight: 500; + } +} diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 6e9a2cd4b..c1d2cf420 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -28,6 +28,8 @@ class Form::AdminSettings :show_staff_badge=, :bootstrap_timeline_accounts, :bootstrap_timeline_accounts=, + :min_invite_role, + :min_invite_role=, to: Setting ) end diff --git a/app/models/invite.rb b/app/models/invite.rb new file mode 100644 index 000000000..ceca04686 --- /dev/null +++ b/app/models/invite.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: invites +# +# id :integer not null, primary key +# user_id :integer +# code :string default(""), not null +# expires_at :datetime +# max_uses :integer +# uses :integer default(0), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class Invite < ApplicationRecord + belongs_to :user, required: true + has_many :users, inverse_of: :invite + + before_validation :set_code + + attr_reader :expires_in + + def expires_in=(interval) + self.expires_at = interval.to_i.seconds.from_now unless interval.blank? + @expires_in = interval + end + + def valid_for_use? + (max_uses.nil? || uses < max_uses) && (expires_at.nil? || expires_at >= Time.now.utc) + end + + def expire! + touch(:expires_at) + end + + private + + def set_code + loop do + self.code = ([*('a'..'z'), *('A'..'Z'), *('0'..'9')] - %w(0 1 I l O)).sample(8).join + break if Invite.find_by(code: code).nil? + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index b9b228c00..578622fdf 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -33,6 +33,7 @@ # account_id :integer not null # disabled :boolean default(FALSE), not null # moderator :boolean default(FALSE), not null +# invite_id :integer # class User < ApplicationRecord @@ -47,6 +48,7 @@ class User < ApplicationRecord otp_number_of_backup_codes: 10 belongs_to :account, inverse_of: :user, required: true + belongs_to :invite, counter_cache: :uses accepts_nested_attributes_for :account has_many :applications, class_name: 'Doorkeeper::Application', as: :owner @@ -77,6 +79,8 @@ class User < ApplicationRecord :reduce_motion, :system_font_ui, :noindex, :theme, to: :settings, prefix: :setting, allow_nil: false + attr_accessor :invite_code + def confirmed? confirmed_at.present? end @@ -95,6 +99,19 @@ class User < ApplicationRecord end end + def role?(role) + case role + when 'user' + true + when 'moderator' + staff? + when 'admin' + admin? + else + false + end + end + def disable! update!(disabled: true, last_sign_in_at: current_sign_in_at, @@ -169,6 +186,11 @@ class User < ApplicationRecord session.web_push_subscription.nil? ? nil : session.web_push_subscription.as_payload end + def invite_code=(code) + self.invite = Invite.find_by(code: code) unless code.blank? + @invite_code = code + end + protected def send_devise_notification(notification, *args) diff --git a/app/policies/invite_policy.rb b/app/policies/invite_policy.rb new file mode 100644 index 000000000..e5c68af19 --- /dev/null +++ b/app/policies/invite_policy.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class InvitePolicy < ApplicationPolicy + def index? + staff? + end + + def create? + min_required_role? + end + + def destroy? + owner? || staff? + end + + private + + def owner? + record.user_id == current_user&.id + end + + def min_required_role? + current_user&.role?(Setting.min_invite_role) + end +end diff --git a/app/views/admin/action_logs/_action_log.html.haml b/app/views/admin/action_logs/_action_log.html.haml index 72816d731..ec90961cb 100644 --- a/app/views/admin/action_logs/_action_log.html.haml +++ b/app/views/admin/action_logs/_action_log.html.haml @@ -1,7 +1,7 @@ %li.log-entry .log-entry__header .log-entry__avatar - = image_tag action_log.account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar' + = image_tag action_log.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar' .log-entry__content .log-entry__title = t("admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}", name: content_tag(:span, action_log.account.username, class: 'username'), target: content_tag(:span, log_target(action_log), class: 'target')).html_safe diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml new file mode 100644 index 000000000..81edfd912 --- /dev/null +++ b/app/views/admin/invites/_invite.html.haml @@ -0,0 +1,15 @@ +%tr + %td + .name-tag + = image_tag invite.user.account.avatar.url(:original), alt: '', width: 16, height: 16, class: 'avatar' + %span.username= invite.user.account.username + %td + = invite.uses + = " / #{invite.max_uses}" unless invite.max_uses.nil? + %td + - if invite.expires_at.nil? + ∞ + - else + = l invite.expires_at + %td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code) + %td= table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete if policy(invite).destroy? diff --git a/app/views/admin/invites/index.html.haml b/app/views/admin/invites/index.html.haml new file mode 100644 index 000000000..52a748fe0 --- /dev/null +++ b/app/views/admin/invites/index.html.haml @@ -0,0 +1,22 @@ +- content_for :page_title do + = t('admin.invites.title') + +- if policy(:invite).create? + %p= t('invites.prompt') + + = render 'invites/form' + + %hr/ + +%table.table + %thead + %tr + %th + %th= t('invites.table.uses') + %th= t('invites.table.expires_at') + %th + %th + %tbody + = render @invites + += paginate @invites diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml index b07718315..c7c25f528 100644 --- a/app/views/admin/settings/edit.html.haml +++ b/app/views/admin/settings/edit.html.haml @@ -32,6 +32,11 @@ %hr/ + .fields-group + = f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, as: :radio_buttons, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + + %hr/ + .fields-group = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 } diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml index f71675df0..2d4c0f5ac 100644 --- a/app/views/auth/registrations/new.html.haml +++ b/app/views/auth/registrations/new.html.haml @@ -16,6 +16,7 @@ = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' } = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' } = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' } + = f.input :invite_code, as: :hidden .actions = f.button :button, t('auth.register'), type: :submit diff --git a/app/views/invites/_form.html.haml b/app/views/invites/_form.html.haml new file mode 100644 index 000000000..99647f597 --- /dev/null +++ b/app/views/invites/_form.html.haml @@ -0,0 +1,9 @@ += simple_form_for(@invite) do |f| + = render 'shared/error_messages', object: @invite + + .fields-group + = f.input :max_uses, wrapper: :with_label, collection: [1, 5, 10, 25, 50, 100], label_method: lambda { |num| I18n.t('invites.max_uses', count: num) }, prompt: I18n.t('invites.max_uses_prompt') + = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') + + .actions + = f.button :button, t('invites.generate'), type: :submit diff --git a/app/views/invites/_invite.html.haml b/app/views/invites/_invite.html.haml new file mode 100644 index 000000000..d794d72e4 --- /dev/null +++ b/app/views/invites/_invite.html.haml @@ -0,0 +1,11 @@ +%tr + %td + = invite.uses + = " / #{invite.max_uses}" unless invite.max_uses.nil? + %td + - if invite.expires_at.nil? + ∞ + - else + = l invite.expires_at + %td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code) + %td= table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete if policy(invite).destroy? diff --git a/app/views/invites/index.html.haml b/app/views/invites/index.html.haml new file mode 100644 index 000000000..f4c5047fa --- /dev/null +++ b/app/views/invites/index.html.haml @@ -0,0 +1,19 @@ +- content_for :page_title do + = t('invites.title') + +- if policy(:invite).create? + %p= t('invites.prompt') + + = render 'form' + + %hr/ + +%table.table + %thead + %tr + %th= t('invites.table.uses') + %th= t('invites.table.expires_at') + %th + %th + %tbody + = render @invites diff --git a/config/locales/en.yml b/config/locales/en.yml index 13b90cf0f..36b6981cb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -231,6 +231,8 @@ en: reset: Reset search: Search title: Known instances + invites: + title: Invites reports: action_taken_by: Action taken by are_you_sure: Are you sure? @@ -269,6 +271,9 @@ en: deletion: desc_html: Allow anyone to delete their account title: Open account deletion + min_invite_role: + disabled: No one + title: Allow invitations by open: desc_html: Allow anyone to create an account title: Open registration @@ -424,6 +429,25 @@ en: muting: Muting list upload: Upload in_memoriam_html: In Memoriam. + invites: + delete: Delete + expires_in: + '1800': 30 minutes + '21600': 6 hours + '3600': 1 hour + '43200': 12 hours + '86400': 1 day + expires_in_prompt: Never + generate: Generate + max_uses: + one: 1 use + other: "%{count} uses" + max_uses_prompt: No limit + prompt: Generate and share links with others to grant access to this instance + table: + expires_at: Expires + uses: Uses + title: Invite people landing_strip_html: "%{name} is a user on %{link_to_root_path}. You can follow them or interact with them if you have an account anywhere in the fediverse." landing_strip_signup_html: If you don't, you can sign up here. media_attachments: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index faf41f316..ff1a40ccd 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -30,10 +30,12 @@ en: data: Data display_name: Display name email: E-mail address + expires_in: Expire after filtered_languages: Filtered languages header: Header locale: Language locked: Lock account + max_uses: Max number of uses new_password: New password note: Bio otp_attempt: Two-factor code diff --git a/config/navigation.rb b/config/navigation.rb index 26e6d386a..fdfd72b4c 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -16,6 +16,8 @@ SimpleNavigation::Configuration.run do |navigation| settings.item :follower_domains, safe_join([fa_icon('users fw'), t('settings.followers')]), settings_follower_domains_url end + primary.item :invites, safe_join([fa_icon('user-plus fw'), t('invites.title')]), invites_path, if: proc { Setting.min_invite_role == 'user' } + primary.item :development, safe_join([fa_icon('code fw'), t('settings.development')]), settings_applications_url do |development| development.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url, highlights_on: %r{/settings/applications} end @@ -24,6 +26,7 @@ SimpleNavigation::Configuration.run do |navigation| admin.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports} admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts} + admin.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}, if: -> { current_user.admin? } admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}, if: -> { current_user.admin? } admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? } diff --git a/config/routes.rb b/config/routes.rb index d675fa846..59c3d4fdb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -22,6 +22,10 @@ Rails.application.routes.draw do get 'manifest', to: 'manifests#show', defaults: { format: 'json' } get 'intent', to: 'intents#show' + devise_scope :user do + get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite + end + devise_for :users, path: 'auth', controllers: { sessions: 'auth/sessions', registrations: 'auth/registrations', @@ -99,6 +103,7 @@ Rails.application.routes.draw do resources :media, only: [:show] resources :tags, only: [:show] resources :emojis, only: [:show] + resources :invites, only: [:index, :create, :destroy] get '/media_proxy/:id/(*any)', to: 'media_proxy#show', as: :media_proxy @@ -112,6 +117,7 @@ Rails.application.routes.draw do resources :email_domain_blocks, only: [:index, :new, :create, :destroy] resources :action_logs, only: [:index] resource :settings, only: [:edit, :update] + resources :invites, only: [:index, :create, :destroy] resources :instances, only: [:index] do collection do diff --git a/config/settings.yml b/config/settings.yml index 5a0170fb4..f03a32e50 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -16,6 +16,7 @@ defaults: &defaults open_registrations: true closed_registrations_message: '' open_deletion: true + min_invite_role: 'admin' timeline_preview: true show_staff_badge: true default_sensitive: false diff --git a/db/migrate/20171125024930_create_invites.rb b/db/migrate/20171125024930_create_invites.rb new file mode 100644 index 000000000..bcf03bd72 --- /dev/null +++ b/db/migrate/20171125024930_create_invites.rb @@ -0,0 +1,15 @@ +class CreateInvites < ActiveRecord::Migration[5.1] + def change + create_table :invites do |t| + t.belongs_to :user, foreign_key: { on_delete: :cascade } + t.string :code, null: false, default: '' + t.datetime :expires_at, null: true, default: nil + t.integer :max_uses, null: true, default: nil + t.integer :uses, null: false, default: 0 + + t.timestamps + end + + add_index :invites, :code, unique: true + end +end diff --git a/db/migrate/20171125031751_add_invite_id_to_users.rb b/db/migrate/20171125031751_add_invite_id_to_users.rb new file mode 100644 index 000000000..16829f866 --- /dev/null +++ b/db/migrate/20171125031751_add_invite_id_to_users.rb @@ -0,0 +1,5 @@ +class AddInviteIdToUsers < ActiveRecord::Migration[5.1] + def change + add_reference :users, :invite, null: true, default: nil, foreign_key: { on_delete: :nullify }, index: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 110a19845..7422bd127 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171122120436) do +ActiveRecord::Schema.define(version: 20171125031751) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -183,6 +183,18 @@ ActiveRecord::Schema.define(version: 20171122120436) do t.bigint "account_id", null: false end + create_table "invites", force: :cascade do |t| + t.bigint "user_id" + t.string "code", default: "", null: false + t.datetime "expires_at" + t.integer "max_uses" + t.integer "uses", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["code"], name: "index_invites_on_code", unique: true + t.index ["user_id"], name: "index_invites_on_user_id" + end + create_table "list_accounts", force: :cascade do |t| t.bigint "list_id", null: false t.bigint "account_id", null: false @@ -473,6 +485,7 @@ ActiveRecord::Schema.define(version: 20171122120436) do t.bigint "account_id", null: false t.boolean "disabled", default: false, null: false t.boolean "moderator", default: false, null: false + t.bigint "invite_id" t.index ["account_id"], name: "index_users_on_account_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true @@ -513,6 +526,7 @@ ActiveRecord::Schema.define(version: 20171122120436) do add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade + add_foreign_key "invites", "users", on_delete: :cascade add_foreign_key "list_accounts", "accounts", on_delete: :cascade add_foreign_key "list_accounts", "follows", on_delete: :cascade add_foreign_key "list_accounts", "lists", on_delete: :cascade @@ -546,5 +560,6 @@ ActiveRecord::Schema.define(version: 20171122120436) do add_foreign_key "stream_entries", "accounts", name: "fk_5659b17554", on_delete: :cascade add_foreign_key "subscriptions", "accounts", name: "fk_9847d1cbb5", on_delete: :cascade add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade + add_foreign_key "users", "invites", on_delete: :nullify add_foreign_key "web_settings", "users", name: "fk_11910667b2", on_delete: :cascade end diff --git a/spec/fabricators/invite_fabricator.rb b/spec/fabricators/invite_fabricator.rb new file mode 100644 index 000000000..62b9b3904 --- /dev/null +++ b/spec/fabricators/invite_fabricator.rb @@ -0,0 +1,6 @@ +Fabricator(:invite) do + user + expires_at nil + max_uses nil + uses 0 +end diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb new file mode 100644 index 000000000..0ba1dccb3 --- /dev/null +++ b/spec/models/invite_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +RSpec.describe Invite, type: :model do + describe '#valid_for_use?' do + it 'returns true when there are no limitations' do + invite = Invite.new(max_uses: nil, expires_at: nil) + expect(invite.valid_for_use?).to be true + end + + it 'returns true when not expired' do + invite = Invite.new(max_uses: nil, expires_at: 1.hour.from_now) + expect(invite.valid_for_use?).to be true + end + + it 'returns false when expired' do + invite = Invite.new(max_uses: nil, expires_at: 1.hour.ago) + expect(invite.valid_for_use?).to be false + end + + it 'returns true when uses still available' do + invite = Invite.new(max_uses: 250, uses: 249, expires_at: nil) + expect(invite.valid_for_use?).to be true + end + + it 'returns false when maximum uses reached' do + invite = Invite.new(max_uses: 250, uses: 250, expires_at: nil) + expect(invite.valid_for_use?).to be false + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 77a12c26d..5ed7ed88b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -273,4 +273,47 @@ RSpec.describe User, type: :model do expect(user.token_for_app(app)).to be_nil end end + + describe '#role' do + it 'returns admin for admin' do + user = User.new(admin: true) + expect(user.role).to eq 'admin' + end + + it 'returns moderator for moderator' do + user = User.new(moderator: true) + expect(user.role).to eq 'moderator' + end + + it 'returns user otherwise' do + user = User.new + expect(user.role).to eq 'user' + end + end + + describe '#role?' do + it 'returns false when invalid role requested' do + user = User.new(admin: true) + expect(user.role?('disabled')).to be false + end + + it 'returns true when exact role match' do + user = User.new + mod = User.new(moderator: true) + admin = User.new(admin: true) + + expect(user.role?('user')).to be true + expect(mod.role?('moderator')).to be true + expect(admin.role?('admin')).to be true + end + + it 'returns true when role higher than needed' do + mod = User.new(moderator: true) + admin = User.new(admin: true) + + expect(mod.role?('user')).to be true + expect(admin.role?('user')).to be true + expect(admin.role?('moderator')).to be true + end + end end -- cgit From ff78c1177a180f1b9aec8464734ac3aa1c888874 Mon Sep 17 00:00:00 2001 From: Joshua Wood Date: Mon, 27 Nov 2017 12:31:58 -0800 Subject: Add Keyboard Shortcuts Legend (#5823) * Add Keyboard Shortcuts Legend Adds a "Keyboard Shortcuts" legend (displayed in the rightmost column) which is toggled via a new "?" hotkey. When subsequently pressed from the Keyboard Shortcuts legend, "?" will navigate back to the previous location. * Add hidden table headings. Makes the headings available for accessibility but hides them visually. --- .../mastodon/features/keyboard_shortcuts/index.js | 68 ++++++++++++++++++++++ app/javascript/mastodon/features/ui/index.js | 12 ++++ .../mastodon/features/ui/util/async-components.js | 4 ++ app/javascript/styles/mastodon/components.scss | 21 +++++++ 4 files changed, 105 insertions(+) create mode 100644 app/javascript/mastodon/features/keyboard_shortcuts/index.js (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.js new file mode 100644 index 000000000..00771b79c --- /dev/null +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.js @@ -0,0 +1,68 @@ +import React from 'react'; +import Column from '../ui/components/column'; +import { defineMessages, injectIntl } from 'react-intl'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +const messages = defineMessages({ + heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, + hotkey: { id: 'keyboard_shortcuts.hotkey', defaultMessage: 'Hotkey' }, + description: { id: 'keyboard_shortcuts.description', defaultMessage: 'Description' }, + reply: { id: 'keyboard_shortcuts.reply', defaultMessage: 'to reply' }, + mention: { id: 'keyboard_shortcuts.mention', defaultMessage: 'to mention author' }, + favourite: { id: 'keyboard_shortcuts.favourite', defaultMessage: 'to favourite' }, + boost: { id: 'keyboard_shortcuts.boost', defaultMessage: 'to boost' }, + enter: { id: 'keyboard_shortcuts.enter', defaultMessage: 'to open status' }, + profile: { id: 'keyboard_shortcuts.profile', defaultMessage: 'to open author\'s profile' }, + up: { id: 'keyboard_shortcuts.up', defaultMessage: 'to move up in the list' }, + down: { id: 'keyboard_shortcuts.down', defaultMessage: 'to move down in the list' }, + column: { id: 'keyboard_shortcuts.column', defaultMessage: 'to focus a status in one of the columns' }, + compose: { id: 'keyboard_shortcuts.compose', defaultMessage: 'to focus the compose textarea' }, + toot: { id: 'keyboard_shortcuts.toot', defaultMessage: 'to start a brand new toot' }, + back: { id: 'keyboard_shortcuts.back', defaultMessage: 'to navigate back' }, + search: { id: 'keyboard_shortcuts.search', defaultMessage: 'to focus search' }, + unfocus: { id: 'keyboard_shortcuts.unfocus', defaultMessage: 'to un-focus compose textarea/search' }, + legend: { id: 'keyboard_shortcuts.legend', defaultMessage: 'to display this legend' }, +}); + +@injectIntl +export default class KeyboardShortcuts extends ImmutablePureComponent { + + static propTypes = { + intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, + }; + + render () { + const { intl } = this.props; + + return ( + +
+ + + + + + + + + + + + + + + + + + + + +
{intl.formatMessage(messages.hotkey)}{intl.formatMessage(messages.description)}
r{intl.formatMessage(messages.reply)}
m{intl.formatMessage(messages.mention)}
f{intl.formatMessage(messages.favourite)}
b{intl.formatMessage(messages.boost)}
enter{intl.formatMessage(messages.enter)}
up{intl.formatMessage(messages.up)}
down{intl.formatMessage(messages.down)}
1-9{intl.formatMessage(messages.column)}
n{intl.formatMessage(messages.compose)}
alt+n{intl.formatMessage(messages.toot)}
backspace{intl.formatMessage(messages.back)}
s{intl.formatMessage(messages.search)}
esc{intl.formatMessage(messages.unfocus)}
?{intl.formatMessage(messages.legend)}
+
+
+ ); + } + +} diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 57289f519..361326961 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -19,6 +19,7 @@ import { Compose, Status, GettingStarted, + KeyboardShortcuts, PublicTimeline, CommunityTimeline, AccountTimeline, @@ -56,6 +57,7 @@ const mapStateToProps = state => ({ }); const keyMap = { + help: '?', new: 'n', search: 's', forceNew: 'option+n', @@ -298,6 +300,14 @@ export default class UI extends React.Component { this.hotkeys = c; } + handleHotkeyToggleHelp = () => { + if (this.props.location.pathname === '/keyboard-shortcuts') { + this.context.router.history.goBack(); + } else { + this.context.router.history.push('/keyboard-shortcuts'); + } + } + handleHotkeyGoToHome = () => { this.context.router.history.push('/timelines/home'); } @@ -343,6 +353,7 @@ export default class UI extends React.Component { const { children } = this.props; const handlers = { + help: this.handleHotkeyToggleHelp, new: this.handleHotkeyNew, search: this.handleHotkeySearch, forceNew: this.handleHotkeyForceNew, @@ -369,6 +380,7 @@ export default class UI extends React.Component { + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index ec1630ed6..b741f668e 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -38,6 +38,10 @@ export function GettingStarted () { return import(/* webpackChunkName: "features/getting_started" */'../../getting_started'); } +export function KeyboardShortcuts () { + return import(/* webpackChunkName: "features/keyboard_shortcuts" */'../../keyboard_shortcuts'); +} + export function PinnedStatuses () { return import(/* webpackChunkName: "features/pinned_statuses" */'../../pinned_statuses'); } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index f4ad66271..80060a00f 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2099,6 +2099,27 @@ } } +.keyboard-shortcuts { + padding: 8px 0 0; + overflow: hidden; + + thead { + position: absolute; + left: -9999px; + } + + td { + padding: 0 10px 8px; + } + + code { + display: inline-block; + padding: 3px 5px; + background-color: lighten($ui-base-color, 8%); + border: 1px solid darken($ui-base-color, 4%); + } +} + .setting-text { color: $ui-primary-color; background: transparent; -- cgit From f3c3df62ab0f460ec9eea558a09aefc161a9d8d5 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Mon, 27 Nov 2017 13:17:12 -0800 Subject: Implement status hotkeys + spoiler expanding --- app/javascript/themes/glitch/components/status.js | 39 ++++--- .../themes/glitch/containers/status_container.js | 1 + .../features/notifications/components/follow.js | 5 +- .../notifications/components/notification.js | 117 +++++++++++---------- .../containers/notification_container.js | 1 - app/javascript/themes/glitch/features/ui/index.js | 1 + 6 files changed, 84 insertions(+), 80 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/themes/glitch/components/status.js b/app/javascript/themes/glitch/components/status.js index 327c7f5c1..9288bcafa 100644 --- a/app/javascript/themes/glitch/components/status.js +++ b/app/javascript/themes/glitch/components/status.js @@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { MediaGallery, Video } from 'themes/glitch/util/async-components'; import { HotKeys } from 'react-hotkeys'; import NotificationOverlayContainer from 'themes/glitch/features/notifications/containers/overlay_container'; +import classNames from 'classnames'; // We use the component (and not the container) since we do not want // to use the progress bar to show download progress @@ -21,6 +22,7 @@ export default class Status extends ImmutablePureComponent { }; static propTypes = { + containerId: PropTypes.string, id: PropTypes.string, status: ImmutablePropTypes.map, account: ImmutablePropTypes.map, @@ -188,7 +190,9 @@ export default class Status extends ImmutablePureComponent { } handleExpandedToggle = () => { - this.setExpansion(this.state.isExpanded || !this.props.status.get('spoiler') ? null : true); + if (this.props.status.get('spoiler_text')) { + this.setExpansion(this.state.isExpanded ? null : true); + } }; handleOpenVideo = startTime => { @@ -222,11 +226,11 @@ export default class Status extends ImmutablePureComponent { } handleHotkeyMoveUp = () => { - this.props.onMoveUp(this.props.status.get('id')); + this.props.onMoveUp(this.props.containerId || this.props.id); } handleHotkeyMoveDown = () => { - this.props.onMoveDown(this.props.status.get('id')); + this.props.onMoveDown(this.props.containerId || this.props.id); } handleRef = c => { @@ -371,31 +375,24 @@ export default class Status extends ImmutablePureComponent { openProfile: this.handleHotkeyOpenProfile, moveUp: this.handleHotkeyMoveUp, moveDown: this.handleHotkeyMoveDown, + toggleSpoiler: this.handleExpandedToggle, }; + const computedClass = classNames('status', `status-${status.get('visibility')}`, { + collapsed: isExpanded === false, + 'has-background': isExpanded === false && background, + 'marked-for-delete': this.state.markedForDelete, + muted, + }, 'focusable'); + return (
{prepend && account ? ( { } return { + containerId : props.containerId || props.id, // Should match reblogStatus's id for reblogs status : status, account : account || props.account, settings : state.get('local_settings'), diff --git a/app/javascript/themes/glitch/features/notifications/components/follow.js b/app/javascript/themes/glitch/features/notifications/components/follow.js index 8a0f01736..96cfe83e6 100644 --- a/app/javascript/themes/glitch/features/notifications/components/follow.js +++ b/app/javascript/themes/glitch/features/notifications/components/follow.js @@ -14,6 +14,7 @@ import NotificationOverlayContainer from '../containers/overlay_container'; export default class NotificationFollow extends ImmutablePureComponent { static propTypes = { + hidden: PropTypes.bool, id: PropTypes.string.isRequired, account: ImmutablePropTypes.map.isRequired, notification: ImmutablePropTypes.map.isRequired, @@ -57,7 +58,7 @@ export default class NotificationFollow extends ImmutablePureComponent { } render () { - const { account, notification } = this.props; + const { account, notification, hidden } = this.props; // Links to the display name. const displayName = account.get('display_name_html') || account.get('username'); @@ -87,7 +88,7 @@ export default class NotificationFollow extends ImmutablePureComponent { />
- +
diff --git a/app/javascript/themes/glitch/features/notifications/components/notification.js b/app/javascript/themes/glitch/features/notifications/components/notification.js index a309d3a42..47f5770bf 100644 --- a/app/javascript/themes/glitch/features/notifications/components/notification.js +++ b/app/javascript/themes/glitch/features/notifications/components/notification.js @@ -16,70 +16,75 @@ export default class Notification extends ImmutablePureComponent { onMoveUp: PropTypes.func.isRequired, onMoveDown: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, - settings: ImmutablePropTypes.map.isRequired, }; - renderFollow () { - const { notification } = this.props; - return ( - - ); - } - - renderMention () { - const { notification } = this.props; - return ( - - ); - } - - renderFavourite () { - const { notification } = this.props; - return ( - - ); - } - - renderReblog () { - const { notification } = this.props; - return ( - - ); - } - render () { - const { notification } = this.props; + const { + hidden, + notification, + onMoveDown, + onMoveUp, + onMention, + } = this.props; + switch(notification.get('type')) { case 'follow': - return this.renderFollow(); + return ( +