From faefd8ec8f164e174fd887f06d01c7fe8ed05531 Mon Sep 17 00:00:00 2001 From: Koala Yeung Date: Thu, 13 Apr 2017 18:57:41 +0800 Subject: [PATCH 1/9] Update javascript English translation files and some defaultValue (#1676) * Reorder javascript English locale file * Reorder translation string in order of the locale key. * Add javascript English locale missing language keys * Search all javascript language keys by command: `grep -REho '' ./app/assets/javascripts/.` * Add all the missing language keys and their values to `en.jsx`. * Add javascript English locale missing language keys (2) * Find all `defineMessages` calls with this command: `grep -Rl 'defineMessages({.*' ./app/assets/javascripts/.` * Open all these files. Find the language key (`id`) in these statements. * Add all the missing language keys and their values to `en.jsx`. * Remove javascript English locale obsoleted language keys * Find all language keys that no longer exists in the source code and remove them. The removed keys include: * "compose_form.private" * "compose_form.unlisted" * "getting_started.about_addressing" * "getting_started.about_shortcuts" * "notification.mention" * "search.account" * "search.hashtag" * "tabs_bar.mentions" * "tabs_bar.public" * Javascript English locale file add note * Add notes to contributors about the English translation files. Hope that will make translation process smoother. * Update javascript locale defaultValue in code * Update the defaultValue in code according to the relevant translation in English locale file. --- .../features/community_timeline/index.jsx | 2 +- .../compose/components/compose_form.jsx | 2 +- .../components/features/compose/index.jsx | 2 +- .../features/getting_started/index.jsx | 4 +- .../features/public_timeline/index.jsx | 2 +- .../javascripts/components/locales/en.jsx | 155 ++++++++++++------ 6 files changed, 112 insertions(+), 55 deletions(-) diff --git a/app/assets/javascripts/components/features/community_timeline/index.jsx b/app/assets/javascripts/components/features/community_timeline/index.jsx index 0957338cfa..acfc30b655 100644 --- a/app/assets/javascripts/components/features/community_timeline/index.jsx +++ b/app/assets/javascripts/components/features/community_timeline/index.jsx @@ -14,7 +14,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import createStream from '../../stream'; const messages = defineMessages({ - title: { id: 'column.community', defaultMessage: 'Local' } + title: { id: 'column.community', defaultMessage: 'Local timeline' } }); const mapStateToProps = state => ({ diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx index cb4b62f6cf..d2e65359fb 100644 --- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx +++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx @@ -19,7 +19,7 @@ import TextIconButton from './text_icon_button'; const messages = defineMessages({ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Content warning' }, - publish: { id: 'compose_form.publish', defaultMessage: 'Publish' } + publish: { id: 'compose_form.publish', defaultMessage: 'Toot' } }); const ComposeForm = React.createClass({ diff --git a/app/assets/javascripts/components/features/compose/index.jsx b/app/assets/javascripts/components/features/compose/index.jsx index 9421de3ff5..33e16472c4 100644 --- a/app/assets/javascripts/components/features/compose/index.jsx +++ b/app/assets/javascripts/components/features/compose/index.jsx @@ -12,7 +12,7 @@ import SearchResultsContainer from './containers/search_results_container'; const messages = defineMessages({ start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Whole Known Network' }, + public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' } diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx index 0656bf69af..05bfcc2213 100644 --- a/app/assets/javascripts/components/features/getting_started/index.jsx +++ b/app/assets/javascripts/components/features/getting_started/index.jsx @@ -7,11 +7,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Whole Known Network' }, + public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, - sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Sign out' }, + sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' } diff --git a/app/assets/javascripts/components/features/public_timeline/index.jsx b/app/assets/javascripts/components/features/public_timeline/index.jsx index 6d766a83b2..a7ac95ab44 100644 --- a/app/assets/javascripts/components/features/public_timeline/index.jsx +++ b/app/assets/javascripts/components/features/public_timeline/index.jsx @@ -14,7 +14,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import createStream from '../../stream'; const messages = defineMessages({ - title: { id: 'column.public', defaultMessage: 'Whole Known Network' } + title: { id: 'column.public', defaultMessage: 'Federated timeline' } }); const mapStateToProps = state => ({ diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx index f249b19677..1834567f17 100644 --- a/app/assets/javascripts/components/locales/en.jsx +++ b/app/assets/javascripts/components/locales/en.jsx @@ -1,72 +1,129 @@ +/** + * Note for Contributors: + * This file (en.jsx) serve as a template for other languages. + * To make other contributors' life easier, please REMEMBER: + * 1. to add your new string here; and + * 2. to remove old strings that are no longer needed; and + * 3. to sort the strings by the key. + * Thanks! + */ const en = { - "column_back_button.label": "Back", - "lightbox.close": "Close", - "loading_indicator.label": "Loading...", - "status.mention": "Mention @{name}", - "status.delete": "Delete", - "status.reply": "Reply", - "status.reblog": "Boost", - "status.favourite": "Favourite", - "status.reblogged_by": "{name} boosted", - "status.sensitive_warning": "Sensitive content", - "status.sensitive_toggle": "Click to view", - "status.show_more": "Show more", - "status.show_less": "Show less", - "status.open": "Expand this status", - "status.report": "Report @{name}", - "video_player.toggle_sound": "Toggle sound", - "account.mention": "Mention @{name}", - "account.edit_profile": "Edit profile", - "account.unblock": "Unblock @{name}", - "account.unfollow": "Unfollow", "account.block": "Block @{name}", + "account.disclaimer": "This user is from another instance. This number may be larger.", + "account.edit_profile": "Edit profile", "account.follow": "Follow", - "account.posts": "Posts", - "account.follows": "Follows", "account.followers": "Followers", "account.follows_you": "Follows you", + "account.follows": "Follows", + "account.mention": "Mention @{name}", + "account.mute": "Mute @{name}", + "account.posts": "Posts", + "account.report": "Report @{name}", "account.requested": "Awaiting approval", - "getting_started.heading": "Getting started", - "getting_started.about_addressing": "You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the search form.", - "getting_started.about_shortcuts": "If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.", - "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.", - "column.home": "Home", + "account.unblock": "Unblock @{name}", + "account.unfollow": "Unfollow", + "account.unmute": "Unmute @{name}", + "boost_modal.combo": "You can press {combo} to skip this next time", + "column_back_button.label": "Back", + "column.blocks": "Blocked users", "column.community": "Local timeline", - "column.public": "Federated timeline", + "column.favourites": "Favourites", + "column.follow_requests": "Follow requests", + "column.home": "Home", "column.notifications": "Notifications", - "tabs_bar.compose": "Compose", - "tabs_bar.home": "Home", - "tabs_bar.mentions": "Mentions", - "tabs_bar.public": "Federated timeline", - "tabs_bar.notifications": "Notifications", + "column.public": "Federated timeline", "compose_form.placeholder": "What is on your mind?", + "compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.", "compose_form.publish": "Toot", "compose_form.sensitive": "Mark media as sensitive", + "compose_form.spoiler_placeholder": "Content warning", "compose_form.spoiler": "Hide text behind warning", - "compose_form.private": "Mark as private", - "compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.", - "compose_form.unlisted": "Do not display on public timelines", - "navigation_bar.edit_profile": "Edit profile", - "navigation_bar.preferences": "Preferences", + "emoji_button.label": "Insert emoji", + "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", + "empty_column.hashtag": "There is nothing in this hashtag yet.", + "empty_column.home.public_timeline": "the public timeline", + "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.", + "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", + "follow_request.reject": "Rejec", + "getting_started.apps": "Various apps are available", + "getting_started.heading": "Getting started", + "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.", + "home.column_settings.advanced": "Advanced", + "home.column_settings.basic": "Basic", + "home.column_settings.filter_regex": "Filter out by regular expressions", + "home.column_settings.show_reblogs": "Show boosts", + "home.column_settings.show_replies": "Show replies", + "home.settings": "Column settings", + "lightbox.close": "Close", + "loading_indicator.label": "Loading...", + "media_gallery.toggle_visible": "Toggle visibility", + "missing_indicator.label": "Not found", + "navigation_bar.blocks": "Blocked users", "navigation_bar.community_timeline": "Local timeline", - "navigation_bar.public_timeline": "Federated timeline", + "navigation_bar.edit_profile": "Edit profile", + "navigation_bar.favourites": "Favourites", + "navigation_bar.follow_requests": "Follow requests", + "navigation_bar.info": "Extended information", "navigation_bar.logout": "Logout", - "reply_indicator.cancel": "Cancel", - "search.placeholder": "Search", - "search.account": "Account", - "search.hashtag": "Hashtag", - "upload_button.label": "Add media", - "upload_form.undo": "Undo", - "notification.follow": "{name} followed you", + "navigation_bar.preferences": "Preferences", + "navigation_bar.public_timeline": "Federated timeline", "notification.favourite": "{name} favourited your status", + "notification.follow": "{name} followed you", "notification.reblog": "{name} boosted your status", - "notification.mention": "{name} mentioned you", + "notifications.clear_confirmation": "Are you sure you want to clear all your notifications?", + "notifications.clear": "Clear notifications", "notifications.column_settings.alert": "Desktop notifications", - "notifications.column_settings.show": "Show in column", - "notifications.column_settings.follow": "New followers:", "notifications.column_settings.favourite": "Favourites:", + "notifications.column_settings.follow": "New followers:", "notifications.column_settings.mention": "Mentions:", "notifications.column_settings.reblog": "Boosts:", + "notifications.column_settings.show": "Show in column", + "notifications.column_settings.sound": "Play sound", + "notifications.settings": "Column settings", + "privacy.change": "Adjust status privacy", + "privacy.direct.long": "Post to mentioned users only", + "privacy.direct.short": "Direct", + "privacy.private.long": "Post to followers only", + "privacy.private.short": "Private", + "privacy.public.long": "Post to public timelines", + "privacy.public.short": "Public", + "privacy.unlisted.long": "Do not show in public timelines", + "privacy.unlisted.short": "Unlisted", + "reply_indicator.cancel": "Cancel", + "report.heading": "New report", + "report.placeholder": "Additional comments", + "report.submit": "Submit", + "report.target": "Reporting", + "search_results.total": "{count} {count, plural, one {result} other {results}}", + "search.placeholder": "Search", + "search.status_by": "Status by {name}", + "status.delete": "Delete", + "status.favourite": "Favourite", + "status.load_more": "Load more", + "status.media_hidden": "Media hidden", + "status.mention": "Mention @{name}", + "status.open": "Expand this status", + "status.reblog": "Boost", + "status.reblogged_by": "{name} boosted", + "status.reply": "Reply", + "status.report": "Report @{name}", + "status.sensitive_toggle": "Click to view", + "status.sensitive_warning": "Sensitive content", + "status.show_less": "Show less", + "status.show_more": "Show more", + "tabs_bar.compose": "Compose", + "tabs_bar.federated_timeline": "Federated", + "tabs_bar.home": "Home", + "tabs_bar.local_timeline": "Local", + "tabs_bar.notifications": "Notifications", + "upload_area.title": "Drag & drop to upload", + "upload_button.label": "Add media", + "upload_form.undo": "Undo", + "upload_progress.label": "Uploading...", + "video_player.toggle_sound": "Toggle sound", + "video_player.toggle_visible": "Toggle visibility", }; export default en; From 0e39cc6a35661416a1f1ccb8841863f7bf307020 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 13 Apr 2017 07:02:02 -0400 Subject: [PATCH 2/9] Settings export refactor (#1646) * Refactor Export to take an account and know about the export types * Use Export instance in settings/exports#show --- .../settings/exports/base_controller.rb | 2 +- .../exports/blocked_accounts_controller.rb | 4 +- .../exports/following_accounts_controller.rb | 4 +- .../exports/muted_accounts_controller.rb | 4 +- .../settings/exports_controller.rb | 5 +-- app/models/export.rb | 38 +++++++++++++++++-- app/views/settings/exports/show.html.haml | 8 ++-- .../settings/exports_controller_spec.rb | 3 ++ 8 files changed, 49 insertions(+), 19 deletions(-) diff --git a/app/controllers/settings/exports/base_controller.rb b/app/controllers/settings/exports/base_controller.rb index 0b790959fe..c082ed806c 100644 --- a/app/controllers/settings/exports/base_controller.rb +++ b/app/controllers/settings/exports/base_controller.rb @@ -6,7 +6,7 @@ module Settings before_action :authenticate_user! def index - export_data = Export.new(export_accounts).to_csv + @export = Export.new(current_account) respond_to do |format| format.csv { send_data export_data, filename: export_filename } diff --git a/app/controllers/settings/exports/blocked_accounts_controller.rb b/app/controllers/settings/exports/blocked_accounts_controller.rb index 9c4bcaa532..f1115b21e3 100644 --- a/app/controllers/settings/exports/blocked_accounts_controller.rb +++ b/app/controllers/settings/exports/blocked_accounts_controller.rb @@ -5,8 +5,8 @@ module Settings class BlockedAccountsController < BaseController private - def export_accounts - current_account.blocking + def export_data + @export.to_blocked_accounts_csv end end end diff --git a/app/controllers/settings/exports/following_accounts_controller.rb b/app/controllers/settings/exports/following_accounts_controller.rb index 8d06bcc954..0011d2463a 100644 --- a/app/controllers/settings/exports/following_accounts_controller.rb +++ b/app/controllers/settings/exports/following_accounts_controller.rb @@ -5,8 +5,8 @@ module Settings class FollowingAccountsController < BaseController private - def export_accounts - current_account.following + def export_data + @export.to_following_accounts_csv end end end diff --git a/app/controllers/settings/exports/muted_accounts_controller.rb b/app/controllers/settings/exports/muted_accounts_controller.rb index a77a9af6d6..dfe72cfcb9 100644 --- a/app/controllers/settings/exports/muted_accounts_controller.rb +++ b/app/controllers/settings/exports/muted_accounts_controller.rb @@ -5,8 +5,8 @@ module Settings class MutedAccountsController < BaseController private - def export_accounts - current_account.muting + def export_data + @export.to_muted_accounts_csv end end end diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb index 77dea32316..ae62f00c1e 100644 --- a/app/controllers/settings/exports_controller.rb +++ b/app/controllers/settings/exports_controller.rb @@ -6,9 +6,6 @@ class Settings::ExportsController < ApplicationController before_action :authenticate_user! def show - @total_storage = current_account.media_attachments.sum(:file_file_size) - @total_follows = current_account.following.count - @total_blocks = current_account.blocking.count - @total_mutes = current_account.muting.count + @export = Export.new(current_account) end end diff --git a/app/models/export.rb b/app/models/export.rb index cd1a58eb6d..f0d5dd2557 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -2,13 +2,43 @@ require 'csv' class Export - attr_reader :accounts + attr_reader :account - def initialize(accounts) - @accounts = accounts + def initialize(account) + @account = account end - def to_csv + def to_blocked_accounts_csv + to_csv account.blocking + end + + def to_muted_accounts_csv + to_csv account.muting + end + + def to_following_accounts_csv + to_csv account.following + end + + def total_storage + account.media_attachments.sum(:file_file_size) + end + + def total_follows + account.following.count + end + + def total_blocks + account.blocking.count + end + + def total_mutes + account.muting.count + end + + private + + def to_csv(accounts) CSV.generate do |csv| accounts.each do |account| csv << [(account.local? ? account.local_username_and_domain : account.acct)] diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml index 51be40fb62..f2f6f95563 100644 --- a/app/views/settings/exports/show.html.haml +++ b/app/views/settings/exports/show.html.haml @@ -5,17 +5,17 @@ %tbody %tr %th= t('exports.storage') - %td= number_to_human_size @total_storage + %td= number_to_human_size @export.total_storage %td %tr %th= t('exports.follows') - %td= @total_follows + %td= @export.total_follows %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv) %tr %th= t('exports.blocks') - %td= @total_blocks + %td= @export.total_blocks %td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv) %tr %th= t('exports.mutes') - %td= @total_mutes + %td= @export.total_mutes %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv) diff --git a/spec/controllers/settings/exports_controller_spec.rb b/spec/controllers/settings/exports_controller_spec.rb index ff98f3ad94..2be6e4744f 100644 --- a/spec/controllers/settings/exports_controller_spec.rb +++ b/spec/controllers/settings/exports_controller_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' describe Settings::ExportsController do + render_views + before do sign_in Fabricate(:user), scope: :user end @@ -8,6 +10,7 @@ describe Settings::ExportsController do describe 'GET #show' do it 'returns http success' do get :show + expect(response).to have_http_status(:success) end end From 3a9eb81a8006af0306e8abc54bd8aca8381eee25 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 13 Apr 2017 07:04:23 -0400 Subject: [PATCH 3/9] Admin accounts controller cleanup (#1664) * Remove unused account_params method in admin/accounts controller * Introduce AccountFilter to find accounts * Use AccountFilter in admin/accounts controller * Use more restful routes admin silence and suspension area * Add admin/silences and admin/suspensions controllers --- app/controllers/admin/accounts_controller.rb | 48 ++++++------------- app/controllers/admin/silences_controller.rb | 23 +++++++++ .../admin/suspensions_controller.rb | 23 +++++++++ app/models/account_filter.rb | 36 ++++++++++++++ app/views/admin/accounts/show.html.haml | 8 ++-- config/routes.rb | 9 ++-- .../admin/silences_controller_spec.rb | 24 ++++++++++ .../admin/suspensions_controller_spec.rb | 24 ++++++++++ spec/models/account_filter_spec.rb | 31 ++++++++++++ 9 files changed, 182 insertions(+), 44 deletions(-) create mode 100644 app/controllers/admin/silences_controller.rb create mode 100644 app/controllers/admin/suspensions_controller.rb create mode 100644 app/models/account_filter.rb create mode 100644 spec/controllers/admin/silences_controller_spec.rb create mode 100644 spec/controllers/admin/suspensions_controller_spec.rb create mode 100644 spec/models/account_filter_spec.rb diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 71cb8edd87..0e9e52f42a 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -2,49 +2,29 @@ module Admin class AccountsController < BaseController - before_action :set_account, except: :index - def index - @accounts = Account.alphabetic.page(params[:page]) - - @accounts = @accounts.local if params[:local].present? - @accounts = @accounts.remote if params[:remote].present? - @accounts = @accounts.where(domain: params[:by_domain]) if params[:by_domain].present? - @accounts = @accounts.silenced if params[:silenced].present? - @accounts = @accounts.recent if params[:recent].present? - @accounts = @accounts.suspended if params[:suspended].present? + @accounts = filtered_accounts.page(params[:page]) end - def show; end - - def suspend - Admin::SuspensionWorker.perform_async(@account.id) - redirect_to admin_accounts_path - end - - def unsuspend - @account.update(suspended: false) - redirect_to admin_accounts_path - end - - def silence - @account.update(silenced: true) - redirect_to admin_accounts_path - end - - def unsilence - @account.update(silenced: false) - redirect_to admin_accounts_path + def show + @account = Account.find(params[:id]) end private - def set_account - @account = Account.find(params[:id]) + def filtered_accounts + AccountFilter.new(filter_params).results end - def account_params - params.require(:account).permit(:silenced, :suspended) + def filter_params + params.permit( + :local, + :remote, + :by_domain, + :silenced, + :recent, + :suspended + ) end end end diff --git a/app/controllers/admin/silences_controller.rb b/app/controllers/admin/silences_controller.rb new file mode 100644 index 0000000000..81a3008b96 --- /dev/null +++ b/app/controllers/admin/silences_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Admin + class SilencesController < BaseController + before_action :set_account + + def create + @account.update(silenced: true) + redirect_to admin_accounts_path + end + + def destroy + @account.update(silenced: false) + redirect_to admin_accounts_path + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + end +end diff --git a/app/controllers/admin/suspensions_controller.rb b/app/controllers/admin/suspensions_controller.rb new file mode 100644 index 0000000000..5d9048d94d --- /dev/null +++ b/app/controllers/admin/suspensions_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Admin + class SuspensionsController < BaseController + before_action :set_account + + def create + Admin::SuspensionWorker.perform_async(@account.id) + redirect_to admin_accounts_path + end + + def destroy + @account.update(suspended: false) + redirect_to admin_accounts_path + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + end +end diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb new file mode 100644 index 0000000000..a8d8c88376 --- /dev/null +++ b/app/models/account_filter.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class AccountFilter + attr_reader :params + + def initialize(params) + @params = params + end + + def results + scope = Account.alphabetic + params.each do |key, value| + scope = scope.merge scope_for(key, value) + end + scope + end + + def scope_for(key, value) + case key + when /local/ + Account.local + when /remote/ + Account.remote + when /by_domain/ + Account.where(domain: value) + when /silenced/ + Account.silenced + when /recent/ + Account.recent + when /suspended/ + Account.suspended + else + raise "Unknown filter: #{key}" + end + end +end diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index ba1c3bae7b..22901aed1e 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -62,11 +62,11 @@ = number_to_human_size @account.media_attachments.sum('file_file_size') - if @account.silenced? - = link_to 'Undo silence', unsilence_admin_account_path(@account.id), method: :post, class: 'button' + = link_to 'Undo silence', admin_account_silence_path(@account.id), method: :delete, class: 'button' - else - = link_to 'Silence', silence_admin_account_path(@account.id), method: :post, class: 'button' + = link_to 'Silence', admin_account_silence_path(@account.id), method: :post, class: 'button' - if @account.suspended? - = link_to 'Undo suspension', unsuspend_admin_account_path(@account.id), method: :post, class: 'button' + = link_to 'Undo suspension', admin_account_suspension_path(@account.id), method: :delete, class: 'button' - else - = link_to 'Perform full suspension', suspend_admin_account_path(@account.id), method: :post, data: { confirm: 'Are you sure?' }, class: 'button' + = link_to 'Perform full suspension', admin_account_suspension_path(@account.id), method: :post, data: { confirm: 'Are you sure?' }, class: 'button' diff --git a/config/routes.rb b/config/routes.rb index 78bf7870cd..ca3f31055f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,4 @@ + # frozen_string_literal: true require 'sidekiq/web' @@ -89,12 +90,8 @@ Rails.application.routes.draw do end resources :accounts, only: [:index, :show] do - member do - post :silence - post :unsilence - post :suspend - post :unsuspend - end + resource :silence, only: [:create, :destroy] + resource :suspension, only: [:create, :destroy] end end diff --git a/spec/controllers/admin/silences_controller_spec.rb b/spec/controllers/admin/silences_controller_spec.rb new file mode 100644 index 0000000000..7c541d7970 --- /dev/null +++ b/spec/controllers/admin/silences_controller_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +describe Admin::SilencesController do + let(:account) { Fabricate(:account) } + before do + sign_in Fabricate(:user, admin: true), scope: :user + end + + describe 'POST #create' do + it 'redirects to admin accounts page' do + post :create, params: { account_id: account.id } + + expect(response).to redirect_to(admin_accounts_path) + end + end + + describe 'DELETE #destroy' do + it 'redirects to admin accounts page' do + delete :destroy, params: { account_id: account.id } + + expect(response).to redirect_to(admin_accounts_path) + end + end +end diff --git a/spec/controllers/admin/suspensions_controller_spec.rb b/spec/controllers/admin/suspensions_controller_spec.rb new file mode 100644 index 0000000000..9096f067ef --- /dev/null +++ b/spec/controllers/admin/suspensions_controller_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +describe Admin::SuspensionsController do + let(:account) { Fabricate(:account) } + before do + sign_in Fabricate(:user, admin: true), scope: :user + end + + describe 'POST #create' do + it 'redirects to admin accounts page' do + post :create, params: { account_id: account.id } + + expect(response).to redirect_to(admin_accounts_path) + end + end + + describe 'DELETE #destroy' do + it 'redirects to admin accounts page' do + delete :destroy, params: { account_id: account.id } + + expect(response).to redirect_to(admin_accounts_path) + end + end +end diff --git a/spec/models/account_filter_spec.rb b/spec/models/account_filter_spec.rb new file mode 100644 index 0000000000..1599c5ae8b --- /dev/null +++ b/spec/models/account_filter_spec.rb @@ -0,0 +1,31 @@ +require 'rails_helper' + +describe AccountFilter do + describe 'with empty params' do + it 'defaults to alphabetic account list' do + filter = AccountFilter.new({}) + + expect(filter.results).to eq Account.alphabetic + end + end + + describe 'with invalid params' do + it 'raises with key error' do + filter = AccountFilter.new(wrong: true) + + expect { filter.results }.to raise_error(/wrong/) + end + end + + describe 'with valid params' do + it 'combines filters on Account' do + filter = AccountFilter.new(by_domain: 'test.com', silenced: true) + + allow(Account).to receive(:where).and_return(Account.none) + allow(Account).to receive(:silenced).and_return(Account.none) + filter.results + expect(Account).to have_received(:where).with(domain: 'test.com') + expect(Account).to have_received(:silenced) + end + end +end From 137100dcf38c0da0fe7044a4c92aa06eae02c420 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 13 Apr 2017 07:09:07 -0400 Subject: [PATCH 4/9] Clean up well-known routes/controllers (#1649) * Add request spec for host meta route returning xml * Add routing spec for xrd routes * Update well-known routes * Move webfinger and host-meta actions to their own controllers --- .../well_known/host_meta_controller.rb | 13 +++++ .../well_known/webfinger_controller.rb | 43 +++++++++++++++ app/controllers/xrd_controller.rb | 55 ------------------- .../host_meta/show.xml.ruby} | 0 .../webfinger/show.json.rabl} | 0 .../webfinger/show.xml.ruby} | 0 config/routes.rb | 4 +- .../well_known/host_meta_controller_spec.rb | 13 +++++ .../well_known/webfinger_controller_spec.rb | 21 +++++++ spec/controllers/xrd_controller_spec.rb | 26 --------- spec/requests/host_meta_request_spec.rb | 12 ++++ spec/routing/well_known_routes_spec.rb | 15 +++++ 12 files changed, 119 insertions(+), 83 deletions(-) create mode 100644 app/controllers/well_known/host_meta_controller.rb create mode 100644 app/controllers/well_known/webfinger_controller.rb delete mode 100644 app/controllers/xrd_controller.rb rename app/views/{xrd/host_meta.xml.ruby => well_known/host_meta/show.xml.ruby} (100%) rename app/views/{xrd/webfinger.json.rabl => well_known/webfinger/show.json.rabl} (100%) rename app/views/{xrd/webfinger.xml.ruby => well_known/webfinger/show.xml.ruby} (100%) create mode 100644 spec/controllers/well_known/host_meta_controller_spec.rb create mode 100644 spec/controllers/well_known/webfinger_controller_spec.rb delete mode 100644 spec/controllers/xrd_controller_spec.rb create mode 100644 spec/requests/host_meta_request_spec.rb create mode 100644 spec/routing/well_known_routes_spec.rb diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb new file mode 100644 index 0000000000..2f0960acdb --- /dev/null +++ b/app/controllers/well_known/host_meta_controller.rb @@ -0,0 +1,13 @@ + # frozen_string_literal: true + +module WellKnown + class HostMetaController < ApplicationController + def show + @webfinger_template = "#{webfinger_url}?resource={uri}" + + respond_to do |format| + format.xml { render content_type: 'application/xrd+xml' } + end + end + end +end diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb new file mode 100644 index 0000000000..1a8ef5f90c --- /dev/null +++ b/app/controllers/well_known/webfinger_controller.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module WellKnown + class WebfingerController < ApplicationController + def show + @account = Account.find_local!(username_from_resource) + @canonical_account_uri = @account.to_webfinger_s + @magic_key = pem_to_magic_key(@account.keypair.public_key) + + respond_to do |format| + format.xml { render content_type: 'application/xrd+xml' } + format.json { render content_type: 'application/jrd+json' } + end + rescue ActiveRecord::RecordNotFound + head 404 + end + + private + + def username_from_resource + WebfingerResource.new(resource_param).username + end + + def pem_to_magic_key(public_key) + modulus, exponent = [public_key.n, public_key.e].map do |component| + result = [] + + until component.zero? + result << [component % 256].pack('C') + component >>= 8 + end + + result.reverse.join + end + + (['RSA'] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.') + end + + def resource_param + params.require(:resource) + end + end +end diff --git a/app/controllers/xrd_controller.rb b/app/controllers/xrd_controller.rb deleted file mode 100644 index 2886315ac0..0000000000 --- a/app/controllers/xrd_controller.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -class XrdController < ApplicationController - before_action :set_default_format_xml, only: :host_meta - - def host_meta - @webfinger_template = "#{webfinger_url}?resource={uri}" - - respond_to do |format| - format.xml { render content_type: 'application/xrd+xml' } - end - end - - def webfinger - @account = Account.find_local!(username_from_resource) - @canonical_account_uri = @account.to_webfinger_s - @magic_key = pem_to_magic_key(@account.keypair.public_key) - - respond_to do |format| - format.xml { render content_type: 'application/xrd+xml' } - format.json { render content_type: 'application/jrd+json' } - end - rescue ActiveRecord::RecordNotFound - head 404 - end - - private - - def set_default_format_xml - request.format = 'xml' if request.headers['HTTP_ACCEPT'].nil? && params[:format].nil? - end - - def username_from_resource - WebfingerResource.new(resource_param).username - end - - def pem_to_magic_key(public_key) - modulus, exponent = [public_key.n, public_key.e].map do |component| - result = [] - - until component.zero? - result << [component % 256].pack('C') - component >>= 8 - end - - result.reverse.join - end - - (['RSA'] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.') - end - - def resource_param - params.require(:resource) - end -end diff --git a/app/views/xrd/host_meta.xml.ruby b/app/views/well_known/host_meta/show.xml.ruby similarity index 100% rename from app/views/xrd/host_meta.xml.ruby rename to app/views/well_known/host_meta/show.xml.ruby diff --git a/app/views/xrd/webfinger.json.rabl b/app/views/well_known/webfinger/show.json.rabl similarity index 100% rename from app/views/xrd/webfinger.json.rabl rename to app/views/well_known/webfinger/show.json.rabl diff --git a/app/views/xrd/webfinger.xml.ruby b/app/views/well_known/webfinger/show.xml.ruby similarity index 100% rename from app/views/xrd/webfinger.xml.ruby rename to app/views/well_known/webfinger/show.xml.ruby diff --git a/config/routes.rb b/config/routes.rb index ca3f31055f..6e48d3b92b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,8 +15,8 @@ Rails.application.routes.draw do controllers authorizations: 'oauth/authorizations', authorized_applications: 'oauth/authorized_applications' end - get '.well-known/host-meta', to: 'xrd#host_meta', as: :host_meta - get '.well-known/webfinger', to: 'xrd#webfinger', as: :webfinger, defaults: { format: 'json' } + get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' } + get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger, defaults: { format: 'json' } devise_for :users, path: 'auth', controllers: { sessions: 'auth/sessions', diff --git a/spec/controllers/well_known/host_meta_controller_spec.rb b/spec/controllers/well_known/host_meta_controller_spec.rb new file mode 100644 index 0000000000..8a040021a6 --- /dev/null +++ b/spec/controllers/well_known/host_meta_controller_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +describe WellKnown::HostMetaController, type: :controller do + render_views + + describe 'GET #show' do + it 'returns http success' do + get :show, format: :xml + + expect(response).to have_http_status(:success) + end + end +end diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb new file mode 100644 index 0000000000..6e493b0378 --- /dev/null +++ b/spec/controllers/well_known/webfinger_controller_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe WellKnown::WebfingerController, type: :controller do + render_views + + describe 'GET #show' do + let(:alice) { Fabricate(:account, username: 'alice') } + + it 'returns http success when account can be found' do + get :show, params: { resource: alice.to_webfinger_s }, format: :json + + expect(response).to have_http_status(:success) + end + + it 'returns http not found when account cannot be found' do + get :show, params: { resource: 'acct:not@existing.com' }, format: :json + + expect(response).to have_http_status(:not_found) + end + end +end diff --git a/spec/controllers/xrd_controller_spec.rb b/spec/controllers/xrd_controller_spec.rb deleted file mode 100644 index 33b17f1523..0000000000 --- a/spec/controllers/xrd_controller_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'rails_helper' - -RSpec.describe XrdController, type: :controller do - render_views - - describe 'GET #host_meta' do - it 'returns http success' do - get :host_meta - expect(response).to have_http_status(:success) - end - end - - describe 'GET #webfinger' do - let(:alice) { Fabricate(:account, username: 'alice') } - - it 'returns http success when account can be found' do - get :webfinger, params: { resource: alice.to_webfinger_s }, format: :json - expect(response).to have_http_status(:success) - end - - it 'returns http not found when account cannot be found' do - get :webfinger, params: { resource: 'acct:not@existing.com' }, format: :json - expect(response).to have_http_status(:not_found) - end - end -end diff --git a/spec/requests/host_meta_request_spec.rb b/spec/requests/host_meta_request_spec.rb new file mode 100644 index 0000000000..0c51b5f484 --- /dev/null +++ b/spec/requests/host_meta_request_spec.rb @@ -0,0 +1,12 @@ +require "rails_helper" + +describe "The host_meta route" do + describe "requested without accepts headers" do + it "returns an xml response" do + get host_meta_url + + expect(response).to have_http_status(:success) + expect(response.content_type).to eq "application/xrd+xml" + end + end +end diff --git a/spec/routing/well_known_routes_spec.rb b/spec/routing/well_known_routes_spec.rb new file mode 100644 index 0000000000..9540c3de34 --- /dev/null +++ b/spec/routing/well_known_routes_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +describe 'the host-meta route' do + it 'routes to correct place with xml format' do + expect(get('/.well-known/host-meta')). + to route_to('well_known/host_meta#show', format: 'xml') + end +end + +describe 'the webfinger route' do + it 'routes to correct place with json format' do + expect(get('/.well-known/webfinger')). + to route_to('well_known/webfinger#show', format: 'json') + end +end From 4f781b17cc0644a1e464dc8fd968d393ccb9b605 Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Thu, 13 Apr 2017 13:13:17 +0200 Subject: [PATCH 5/9] Use input type `number` for Two-factor code (#1683) --- app/assets/stylesheets/forms.scss | 2 +- app/views/auth/sessions/two_factor.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss index 2e3a4f147f..e5e8697a03 100644 --- a/app/assets/stylesheets/forms.scss +++ b/app/assets/stylesheets/forms.scss @@ -88,7 +88,7 @@ code { } } - input[type=text], input[type=email], input[type=password], textarea { + input[type=text], input[type=number], input[type=email], input[type=password], textarea { background: transparent; box-sizing: border-box; border: 0; diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml index 8bf9985545..1deff82b2e 100644 --- a/app/views/auth/sessions/two_factor.html.haml +++ b/app/views/auth/sessions/two_factor.html.haml @@ -2,7 +2,7 @@ = t('auth.login') = simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| - = f.input :otp_attempt, placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt') }, required: true, autofocus: true, autocomplete: 'off' + = f.input :otp_attempt, type: :number, placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt') }, required: true, autofocus: true, autocomplete: 'off' .actions = f.button :button, t('auth.login'), type: :submit From aa7bf1515c4d00baa47217336a3a7191b7f15041 Mon Sep 17 00:00:00 2001 From: Svetlozar Todorov Date: Thu, 13 Apr 2017 14:16:28 +0300 Subject: [PATCH 6/9] Fix #624 - Add localization for Bulgarian (#645) * Add translation files and declarations for Bulgarian * Add a bunch of translations to bg.jsx * Add rest of translations to bg.jsx * Add devise translations * Fix devise translations --- .../components/containers/mastodon.jsx | 5 +- .../javascripts/components/locales/bg.jsx | 68 +++++++ .../javascripts/components/locales/index.jsx | 3 +- app/helpers/settings_helper.rb | 1 + config/application.rb | 1 + config/locales/bg.yml | 169 ++++++++++++++++++ config/locales/devise.bg.yml | 61 +++++++ config/locales/doorkeeper.bg.yml | 113 ++++++++++++ config/locales/simple_form.bg.yml | 46 +++++ 9 files changed, 463 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/components/locales/bg.jsx create mode 100644 config/locales/bg.yml create mode 100644 config/locales/devise.bg.yml create mode 100644 config/locales/doorkeeper.bg.yml create mode 100644 config/locales/simple_form.bg.yml diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx index d8810dc644..b9086de424 100644 --- a/app/assets/javascripts/components/containers/mastodon.jsx +++ b/app/assets/javascripts/components/containers/mastodon.jsx @@ -52,8 +52,8 @@ import no from 'react-intl/locale-data/no'; import ru from 'react-intl/locale-data/ru'; import uk from 'react-intl/locale-data/uk'; import zh from 'react-intl/locale-data/zh'; +import bg from 'react-intl/locale-data/bg'; import { localeData as zh_hk } from '../locales/zh-hk'; - import getMessagesForLocale from '../locales'; import { hydrateStore } from '../actions/store'; import createStream from '../stream'; @@ -66,7 +66,6 @@ const browserHistory = useRouterHistory(createBrowserHistory)({ basename: '/web' }); - addLocaleData([ ...en, ...de, @@ -82,9 +81,9 @@ addLocaleData([ ...uk, ...zh, ...zh_hk, + ...bg, ]); - const Mastodon = React.createClass({ propTypes: { diff --git a/app/assets/javascripts/components/locales/bg.jsx b/app/assets/javascripts/components/locales/bg.jsx new file mode 100644 index 0000000000..cac984aae8 --- /dev/null +++ b/app/assets/javascripts/components/locales/bg.jsx @@ -0,0 +1,68 @@ +const bg = { + "column_back_button.label": "Назад", + "lightbox.close": "Затвори", + "loading_indicator.label": "Зареждане...", + "status.mention": "Споменаване", + "status.delete": "Изтриване", + "status.reply": "Отговор", + "status.reblog": "Споделяне", + "status.favourite": "Предпочитани", + "status.reblogged_by": "{name} сподели", + "status.sensitive_warning": "Деликатно съдържание", + "status.sensitive_toggle": "Покажи", + "video_player.toggle_sound": "Звук", + "account.mention": "Споменаване", + "account.edit_profile": "Редактирай профила си", + "account.unblock": "Не блокирай", + "account.unfollow": "Не следвай", + "account.block": "Блокирай", + "account.follow": "Последвай", + "account.posts": "Публикации", + "account.follows": "Следвам", + "account.followers": "Последователи", + "account.follows_you": "Твой последовател", + "account.requested": "В очакване на одобрение", + "getting_started.heading": "Първи стъпки", + "getting_started.about_addressing": "Можеш да последваш потребител, ако знаеш потребителското му име и домейна, на който се намира, като в полето за търсене ги въведеш по този начин: име@домейн", + "getting_started.about_shortcuts": "Ако с търсения потребител се намирате на един и същ домейн, достатъчно е да въведеш само името. Същото важи и за споменаване на хора в публикации.", + "getting_started.about_developer": "Можеш да потърсиш разработчика на този проект като: Gargron@mastodon.social", + "getting_started.open_source_notice": "Mastodon е софтуер с отворен код. Можеш да помогнеш или да докладваш за проблеми в Github: {github}.", + "column.home": "Начало", + "column.mentions": "Споменавания", + "column.public": "Публичен канал", + "column.notifications": "Известия", + "tabs_bar.compose": "Съставяне", + "tabs_bar.home": "Начало", + "tabs_bar.mentions": "Споменавания", + "tabs_bar.public": "Публичен канал", + "tabs_bar.notifications": "Известия", + "compose_form.placeholder": "Какво си мислиш?", + "compose_form.publish": "Раздумай", + "compose_form.sensitive": "Отбележи съдържанието като деликатно", + "compose_form.spoiler": "Скрий текста зад предупреждение", + "compose_form.private": "Отбележи като поверително", + "compose_form.privacy_disclaimer": "Поверителни публикации ще бъдат изпратени до споменатите потребители на {domains}. Доверяваш ли се на {domainsCount, plural, one {that server} other {those servers}}, че няма да издаде твоята публикация?", + "compose_form.unlisted": "Не показвай в публичния канал", + "navigation_bar.edit_profile": "Редактирай профил", + "navigation_bar.preferences": "Предпочитания", + "navigation_bar.public_timeline": "Публичен канал", + "navigation_bar.logout": "Излизане", + "reply_indicator.cancel": "Отказ", + "search.placeholder": "Търсене", + "search.account": "Акаунт", + "search.hashtag": "Хаштаг", + "upload_button.label": "Добави медия", + "upload_form.undo": "Отмяна", + "notification.follow": "{name} те последва", + "notification.favourite": "{name} хареса твоята публикация", + "notification.reblog": "{name} сподели твоята публикация", + "notification.mention": "{name} те спомена", + "notifications.column_settings.alert": "Десктоп известия", + "notifications.column_settings.show": "Покажи в колона", + "notifications.column_settings.follow": "Нови последователи:", + "notifications.column_settings.favourite": "Предпочитани:", + "notifications.column_settings.mention": "Споменавания:", + "notifications.column_settings.reblog": "Споделяния:", +}; + +export default en; diff --git a/app/assets/javascripts/components/locales/index.jsx b/app/assets/javascripts/components/locales/index.jsx index e772c10744..f14568a3d5 100644 --- a/app/assets/javascripts/components/locales/index.jsx +++ b/app/assets/javascripts/components/locales/index.jsx @@ -11,7 +11,7 @@ import eo from './eo'; import ru from './ru'; import ja from './ja'; import zh_hk from './zh-hk'; - +import bg from './bg'; const locales = { en, @@ -27,6 +27,7 @@ const locales = { ru, ja, 'zh-HK': zh_hk, + bg, }; export default function getMessagesForLocale (locale) { diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 211b570429..212f88c39d 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -16,6 +16,7 @@ module SettingsHelper ja: '日本語', 'zh-CN': '简体中文', 'zh-HK': '繁體中文(香港)', + bg: 'Български', }.freeze def human_locale(locale) diff --git a/config/application.rb b/config/application.rb index 2c720474a0..1383d45a58 100644 --- a/config/application.rb +++ b/config/application.rb @@ -27,6 +27,7 @@ module Mastodon config.i18n.available_locales = [ :en, + :bg, :de, :eo, :es, diff --git a/config/locales/bg.yml b/config/locales/bg.yml new file mode 100644 index 0000000000..a8687f3cac --- /dev/null +++ b/config/locales/bg.yml @@ -0,0 +1,169 @@ +--- +bg: + about: + about_mastodon: Mastodon е безплатен сървър с отворен код за социални мрежи. Като децентрализирана алтернатива на комерсиалните платформи, той позволява избягването на риска от монополизация на твоята комуникация от единични компании. Изберете си сървър, на който се доверявате, и ще можете да контактувате с всички останали. Всеки може да пусне Mastodon и лесно да вземе участие в социалната мрежа. + about_this: За тази инстанция + apps: Приложения + business_email: 'Служебен e-mail:' + closed_registrations: В момента регистрациите за тази инстанция са затворени. + contact: За контакти + description_headline: Какво е %{domain}? + domain_count_after: други инстанции + domain_count_before: Свързани към + features: + api: Отворено API за приложения и услуги + blocks: Богат на инструменти за блокиране и заглушаване + characters: Публикации от 500 символа + chronology: Публикациите се показват хронологично + ethics: 'Етичен дизайн: без реклами и проследяване' + gifv: GIFV комплекти и кратки видео клипове + privacy: Настройване на поверителността за всяка публикация + public: Публични канали + features_headline: Какво откроява Mastodon + get_started: Първи стъпки + links: Връзки + other_instances: Други инстанции + source_code: Програмен код + status_count_after: публикации + status_count_before: Написали + terms: Условия + user_count_after: потребители + user_count_before: Дом на + accounts: + follow: Последвай + followers: Последователи + following: Следва + nothing_here: Тук няма никого! + people_followed_by: Хора, които %{name} следва + people_who_follow: Хора, които следват %{name} + posts: Публикации + remote_follow: Последвай + unfollow: Не следвай + application_mailer: + settings: 'Промяна на предпочитанията за e-mail: %{link}' + signature: Mastodon известия от %{instance} + view: 'Преглед:' + applications: + invalid_url: Предоставеният URL е невалиден + auth: + change_password: Идентификационни данни + didnt_get_confirmation: Не получих инструкции за потвърждение + forgot_password: Забравих си паролата + login: Влизане + logout: Излизане + register: Регистрация + resend_confirmation: Изпрати отново инструкции за потвърждение + reset_password: Подновяване на паролата + set_new_password: Задай нова парола + authorize_follow: + error: Възникна грешка в откриването на потребителя + follow: Последвай + prompt_html: '(%{self}), молбата ти беше изпратена до:' + title: Последвай %{acct} + datetime: + distance_in_words: + about_x_hours: "%{count} ч." + about_x_months: "%{count} м." + about_x_years: "%{count} г." + almost_x_years: "%{count} г." + half_a_minute: Току-що + less_than_x_minutes: "%{count} мин." + less_than_x_seconds: Току-що + over_x_years: "%{count} г." + x_days: "%{count} дни" + x_minutes: "%{count} мин." + x_months: "%{count} м." + x_seconds: "%{count} сек." + exports: + blocks: Вашите блокирания + csv: CSV + follows: Вашите следвания + storage: Съхранение на мултимедия + generic: + changes_saved_msg: Успешно запазване на промените! + powered_by: поддържано от %{link} + save_changes: Запази промените + validation_errors: + one: Нещо все още не е наред! Моля, прегледай грешката по-долу + other: Нещо все още не е наред! Моля, прегледай грешките по-долу + imports: + preface: Можеш да импортираш някои данни, като например всички хора, които следваш или блокираш в акаунта си на тази инстанция, от файлове, създадени чрез експорт в друга инстанция. + success: Твоите данни бяха успешно качени и ще бъдат обработени впоследствие. + types: + blocking: Списък на блокираните + following: Списък на последователите + upload: Качване + landing_strip_html: %{name} е потребител от %{domain}. Можеш да ги следваш, или да контактуваш с тях, ако имаш акаунт където и да е из федерираната вселена на Mastodon. Ако нямаш акаунт, можеш да си създадеш ето тук. + notification_mailer: + digest: + body: 'Ето кратко резюме на нещата, които се случиха от последното ти посещение в %{instance} на %{since}:' + mention: "%{name} те спомена в:" + new_followers_summary: + one: Имаш един нов последовател! Ура! + other: Имаш %{count} нови последователи! Изумително! + subject: + one: "1 ново известие от последното ти посещение \U0001F418" + other: "%{count} нови известия от последното ти посещение \U0001F418" + favourite: + body: 'Публикацията ти беше харесана от %{name}:' + subject: "%{name} хареса твоята публикация" + follow: + body: "%{name} те последва!" + subject: "%{name} те последва" + follow_request: + body: "%{name} помоли за разрешение да те последва" + subject: 'Чакащ последовател: %{name}' + mention: + body: '%{name} те спомена в:' + subject: '%{name} те спомена' + reblog: + body: 'Твоята публикация беше споделена от %{name}:' + subject: "%{name} сподели публикацията ти" + pagination: + next: Напред + prev: Назад + remote_follow: + acct: Въведи потребителско_име@домейн, от които искаш да следваш + missing_resource: Неуспешно търсене на нужния URL за пренасочване за твоя акаунт + proceed: Започни следване + prompt: 'Ще последваш:' + settings: + authorized_apps: Упълномощени приложения + back: Обратно към Mastodon + edit_profile: Редактирай профила си + export: Експортиране на данни + import: Импортиране + preferences: Предпочитания + settings: Настройки + two_factor_auth: Двустепенно удостоверяване + statuses: + open_in_web: Отвори в уеб + over_character_limit: прехвърлен лимит от %{max} символа + show_more: Покажи повече + visibilities: + private: Покажи само на последователите си + public: Публично + unlisted: Публично, но не показвай в публичния канал + stream_entries: + click_to_show: Покажи + reblogged: споделено + sensitive_content: Деликатно съдържание + time: + formats: + default: "%d %b, %Y, %H:%M" + two_factor_auth: + description_html: При активация на двустепенно удостоверяване, за да влезеш в приложението, ще трябва да използваш телефона си. През него ще се генерира код, който да въвеждаш при влизане. + disable: Деактивирай + enable: Активирай + instructions_html: "Сканирай този QR код с Google Authenticator или подобно приложение от своя телефон. Oтсега нататък, това приложение ще генерира код, който ще трябва да въвеждаш при всяко влизане." + plaintext_secret_html: "Тайна в обикновен текст: %{secret}" + warning: Ако не можеш да настроиш приложението за удостверяване сега, избери "Деактивирай". В противен случай, няма да можеш да влезеш в акаунта си. + users: + invalid_email: E-mail адресът е невалиден + invalid_otp_token: Невалиден код + will_paginate: + page_gap: "…" + media_attachments: + validations: + too_many: Не мога да прикача повече от 4 файла + images_and_video: Не мога да прикача видеоклип към публикация, която вече съдържа изображения diff --git a/config/locales/devise.bg.yml b/config/locales/devise.bg.yml new file mode 100644 index 0000000000..7485b8236c --- /dev/null +++ b/config/locales/devise.bg.yml @@ -0,0 +1,61 @@ +--- +bg: + devise: + confirmations: + confirmed: Твоят профил беше успешно потвърден. Влизането в профила е успешно. + send_instructions: Ще получиш писмо с инструкции как да потвърдиш своя профил до няколко минути. + send_paranoid_instructions: Ако твоят имейл адрес съществува в базата ни, ще получиш там инструкции как да потвърдиш своя профил. + failure: + already_authenticated: Вече си вътре в профила си. + inactive: Профилът ти все още не е активиран. + invalid: Невалиден имейл адрес или парола. + last_attempt: Разполагаш с още един опит преди профилът ти да бъде заключен. + locked: Профилът ти е заключен. + not_found_in_database: "Невалидни стойности за %{authentication_keys} или парола." + timeout: Сесията ти изтече, моля влез отново, за да продължиш. + unauthenticated: Преди да продължиш, трябва да влезеш в профила си или да се регистрираш. + unconfirmed: Преди да продължиш, трябва да потвърдиш регистрацията си. + mailer: + confirmation_instructions: + subject: 'Mastodon: Инструкции за потвърждаване' + password_change: + subject: 'Mastodon: Паролата е променена' + reset_password_instructions: + subject: 'Инструкции за смяна на паролата' + unlock_instructions: + subject: 'Инструкции за отключване' + omniauth_callbacks: + failure: "Не успяхме да те упълномощим чрез %{kind}, защото \"%{reason}\"." + success: "Успешно упълномощаване чрез %{kind} профил." + passwords: + no_token: Може да достъпваш тази страница само от имейл за промяна на паролата. Ако тази страница е отворена от такъв имейл, увери се, че използваш целия URL-адрес, който сме ти изпратили. + send_instructions: Ще получиш писмо с инструкции как да промениш паролата си до няколко минути. + send_paranoid_instructions: Ако твоят имейл адрес съществува в базата ни, ще получиш там инструкции за промяна на своята парола. + updated: Паролата ти беше променена успешно. Влизането в профила е успешно. + updated_not_active: Паролата ти беше променена успешно. + registrations: + destroyed: Довиждане! Твоят профил беше успешно изтрит. Надяваме се скоро да те видим отново. + signed_up: Привет! Регистрирацията ти е успешна. + signed_up_but_inactive: Регистрирацията ти е успешна. Въпреки това, не можеш да влезеш в профила си, защото той все още не е потвърден. + signed_up_but_locked: Регистрирацията ти е успешна. Въпреки това, не можеш да влезеш в профила си, защото той е заключен. + signed_up_but_unconfirmed: Писмо с връзка за потвърждаване на профила ти беше изпратено на твоя имейл адрес. Моля, отвори връзката, за да активираш своя профил. + update_needs_confirmation: Профилът ти е успешно променен, но ние трябва да проверим твоя нов имейл адрес. Моля, провери пощата си и отвори връзката за потвърждаване на новия адрес. + updated: Профилът ти е успешно променен. + sessions: + already_signed_out: Успешно излизане от профила. + signed_in: Успешно влизане. + signed_out: Успешно излизане. + unlocks: + send_instructions: Ще получиш писмо с инструкции как да отключиш профила си до няколко минути. + send_paranoid_instructions: Ако твоят профил съществува в базата ни, на своя имейл адрес ще получиш инструкции за отключването му до няколко минути. + unlocked: Твоят профил беше отключен успешно. За да продължиш, влез в него. + errors: + messages: + already_confirmed: е вече потвърден, моля опитай да влезеш в профила си с него + confirmation_period_expired: "трябва да се потвърди в рамките на %{period}, моля направи нова заявка за потвърждение" + expired: е изтекъл, моля заяви нов + not_found: не е намерен + not_locked: не бе заключен + not_saved: + one: "Една грешка попречи този %{resource} да бъде записан:" + other: "%{count} грешки попречиха този %{resource} да бъде записан:" diff --git a/config/locales/doorkeeper.bg.yml b/config/locales/doorkeeper.bg.yml new file mode 100644 index 0000000000..6fafdfc554 --- /dev/null +++ b/config/locales/doorkeeper.bg.yml @@ -0,0 +1,113 @@ +--- +bg: + activerecord: + attributes: + doorkeeper/application: + name: Име + redirect_uri: URI за пренасочване + errors: + models: + doorkeeper/application: + attributes: + redirect_uri: + fragment_present: не може да съдържа фрагмент. + invalid_uri: трябва да е валидно URI. + relative_uri: трябва да е абсолютно URI. + secured_uri: трябва да е HTTPS/SSL URI. + doorkeeper: + applications: + buttons: + authorize: Упълномощаване + cancel: Отказ + destroy: Унищожаване + edit: Редакция + submit: Изпращане + confirmations: + destroy: Потвърждаваш ли изтриването? + edit: + title: Редактиране на приложението + form: + error: О, не! Провери формата за възможни грешки + help: + native_redirect_uri: Изполвай %{native_redirect_uri} за локални тестове + redirect_uri: Използвай един ред за всяко URI + scopes: Разделяй диапазоните с интервал. Остави празно, за да използваш диапазона по подразбиране. + index: + callback_url: URL за обратно повикване + name: Име + new: Ново приложение + title: Твоите приложения + new: + title: Ново приложение + show: + actions: Действия + application_id: Идентификатор на приложението + callback_urls: URL-и за обратно повикване + scopes: Диапазони + secret: Тайна + title: 'Приложение: %{name}' + authorizations: + buttons: + authorize: Упълномощаване + deny: Отказ + error: + title: Възникна грешка + new: + able_to: Ще е възможно + prompt: Приложението %{client_name} заявява достъп до твоя акаунт + title: Изисква се упълномощаване + show: + title: Код за упълномощаване + authorized_applications: + buttons: + revoke: Отмяна + confirmations: + revoke: Потвърждаваш ли отмяната? + index: + application: Приложение + created_at: Създадено на + date_format: "%Y-%m-%d %H:%M:%S" + scopes: Диапазони + title: Твоите упълномощени приложения + errors: + messages: + access_denied: Заявката беше отказана от собственика на ресурса или от сървъра за упълномощаване. + credential_flow_not_configured: Resource Owner Password Credentials предизвика грешка, заради това, че настройките за Doorkeeper.configure.resource_owner_from_credentials липсват. + invalid_client: Удостоверяването на клиента предизвика грешка, поради непознат клиент, липсващо клиентско удостоверяване, или заради това, че методът на удостоверяване не се поддържа. + invalid_grant: Предоставеното удостоверение за достъп е невалидно, изтекло, отхвърлено, не съвпада с пренасочващото URI, използвано в заявката за удостоверение, или е бил издадено от друг клиент. + invalid_redirect_uri: Наличното пренасочващо URI е невалидно. + invalid_request: Заявката е с липсващ задължителен параметър, включва стойност на параметъра, която не се поддържа, или е изкривена по друг начин. + invalid_resource_owner: Предоставените идентификационни данни на притежателя на ресурса са невалидни, или притежателят не може да бъде намерен. + invalid_scope: Заявеният диапазон е невалиден, неизвестен или изкривен. + invalid_token: + expired: Маркерът за достъп изтече + revoked: Маркерът за достъп беше отхвърлен + unknown: Маркерът за достъп е невалиден + resource_owner_authenticator_not_configured: Намирането на Resource Owner се провали поради липса на конфигурация на Doorkeeper.configure.resource_owner_authenticator. + server_error: Сървърът за удостоверяване попадна на неочаквано условие, което предотврати изпълнението на заявката. + temporarily_unavailable: Сървърът за удостоверяване не може да се справи със заявката в момента поради временно претоварване или профилактика на сървъра. + unauthorized_client: Клиентът не е удостоверен да изпълни заявката по този начин. + unsupported_grant_type: Типът на удостоврението за достъп не се поддържа от сървъра за удостоверяване. + unsupported_response_type: Удостоверяващият сървър не поддържа този тип отговор. + flash: + applications: + create: + notice: Приложението е създадено. + destroy: + notice: Приложението е изтрито. + update: + notice: Приложението е обновено. + authorized_applications: + destroy: + notice: Приложението е отказано. + layouts: + admin: + nav: + applications: Приложения + oauth2_provider: OAuth2 доставчик + application: + title: Нужно е упълномощаване по OAuth + scopes: + follow: следването, блокирането, деблокирането и отмяната на следването на акаунтите + read: четенето на данните от твоя акаунт + write: публикуването от твое име diff --git a/config/locales/simple_form.bg.yml b/config/locales/simple_form.bg.yml new file mode 100644 index 0000000000..55b80277d7 --- /dev/null +++ b/config/locales/simple_form.bg.yml @@ -0,0 +1,46 @@ +--- +bg: + simple_form: + hints: + defaults: + avatar: PNG, GIF или JPG. До 2MB. Ще бъде смалена до 120x120 пиксела + display_name: До 30 символа + header: PNG, GIF или JPG. До 2MB. Ще бъде смалена до 700x335 пиксела + locked: Изисква ръчно одобрение на последователите. По подразбиране, публикациите са достъпни само до последователи. + note: До 160 символа + imports: + data: CSV файл, експортиран от друга инстанция на Mastodon + labels: + defaults: + avatar: Аватар + confirm_new_password: Потвърди новата парола + confirm_password: Потвърди паролата + current_password: Текуща парола + data: Данни + display_name: Показвано име + email: E-mail адрес + header: Заглавен ред + locale: Език + locked: Направи акаунта поверителен + new_password: Нова парола + note: Био + otp_attempt: Двустепенен код + password: Парола + setting_default_privacy: Поверителност на публикациите + type: Тип на импортиране + username: Потребителско име + interactions: + must_be_follower: Блокирай известия от не-последователи + must_be_following: Блокирай известия от хора, които не следваш + notification_emails: + digest: Изпращай извлечения на съобщенията + favourite: Изпращай e-mail, когато някой хареса твоя публикация + follow: Изпращай e-mail, когато някой те последва + follow_request: Изпращай e-mail, когато някой пожелае да те последва + mention: Изпращай e-mail, когато някой те спомене + reblog: Изпращай e-mail, когато някой сподели твоя публикация + 'no': 'Не' + required: + mark: "*" + text: задължително + 'yes': 'Да' From af7e880df568e76af88a19f471b267378b725794 Mon Sep 17 00:00:00 2001 From: Ratmir Karabut Date: Thu, 13 Apr 2017 14:23:23 +0300 Subject: [PATCH 7/9] Update Russian translation (#1570) * Add Russian translation (ru) * Fix a missing comma * Fix the wording for better consistency * Update Russian translation * Arrange Russian setting alphabetically * Fix syntax error --- .../javascripts/components/locales/ru.jsx | 43 ++++++++++++++++--- config/locales/ru.yml | 19 +++++--- config/locales/simple_form.ru.yml | 2 +- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/components/locales/ru.jsx b/app/assets/javascripts/components/locales/ru.jsx index e109005a7c..30a92df866 100644 --- a/app/assets/javascripts/components/locales/ru.jsx +++ b/app/assets/javascripts/components/locales/ru.jsx @@ -10,22 +10,29 @@ const ru = { "status.reblogged_by": "{name} продвинул(а)", "status.sensitive_warning": "Чувствительный контент", "status.sensitive_toggle": "Нажмите для просмотра", + "status.show_more": "Развернуть", + "status.show_less": "Свернуть", + "status.open": "Развернуть статус", + "status.report": "Пожаловаться", + "status.load_more": "Показать еще", "video_player.toggle_sound": "Вкл./выкл. звук", - "account.mention": "Упомянуть @{name}", + "account.mention": "Упомянуть", "account.edit_profile": "Изменить профиль", - "account.unblock": "Разблокировать @{name}", + "account.unblock": "Разблокировать", "account.unfollow": "Отписаться", - "account.block": "Блокировать @{name}", + "account.block": "Блокировать", + "account.mute": "Заглушить", "account.follow": "Подписаться", "account.posts": "Посты", "account.follows": "Подписки", - "account.followers": "Подписчики", + "account.followers": "Подписаны", "account.follows_you": "Подписан(а) на Вас", "account.requested": "Ожидает подтверждения", "getting_started.heading": "Добро пожаловать", "getting_started.about_addressing": "Вы можете подписаться на человека, зная имя пользователя и домен, на котором он находится, введя e-mail-подобный адрес в форму поиска.", "getting_started.about_shortcuts": "Если пользователь находится на одном с Вами домене, можно использовать только имя. То же правило применимо к упоминанию пользователей в статусах.", "getting_started.open_source_notice": "Mastodon - программа с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}. {apps}.", + "getting_started.apps": "Доступны различные приложения.", "column.home": "Главная", "column.community": "Локальная лента", "column.public": "Глобальная лента", @@ -36,7 +43,7 @@ const ru = { "tabs_bar.public": "Глобальная лента", "tabs_bar.notifications": "Уведомления", "compose_form.placeholder": "О чем Вы думаете?", - "compose_form.publish": "Протрубить", + "compose_form.publish": "Трубить", "compose_form.sensitive": "Отметить как чувствительный контент", "compose_form.spoiler": "Скрыть текст за предупреждением", "compose_form.private": "Отметить как приватное", @@ -47,6 +54,9 @@ const ru = { "navigation_bar.community_timeline": "Локальная лента", "navigation_bar.public_timeline": "Глобальная лента", "navigation_bar.logout": "Выйти", + "navigation_bar.info": "Об узле", + "navigation_bar.favourites": "Понравившееся", + "navigation_bar.blocks": "Список блокировки", "reply_indicator.cancel": "Отмена", "search.placeholder": "Поиск", "search.account": "Аккаунт", @@ -57,12 +67,35 @@ const ru = { "notification.favourite": "{name} понравился Ваш статус", "notification.reblog": "{name} продвинул(а) Ваш статус", "notification.mention": "{name} упомянул(а) Вас", + "home.settings": "Настройки колонки", + "home.column_settings.basic": "Основные", + "home.column_settings.advanced": "Дополнительные", + "home.column_settings.filter_regex": "Отфильтровать регулярным выражением", + "home.column_settings.show_replies": "Показывать продвижения", + "home.column_settings.show_replies": "Показывать ответы", + "notifications.clear": "Очистить уведомления", + "notifications.settings": "Настройки колонки", "notifications.column_settings.alert": "Десктопные уведомления", "notifications.column_settings.show": "Показывать в колонке", "notifications.column_settings.follow": "Новые подписчики:", "notifications.column_settings.favourite": "Нравится:", "notifications.column_settings.mention": "Упоминания:", "notifications.column_settings.reblog": "Продвижения:", + "notifications.column_settings.sound": "Проигрывать звук", + "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.", + "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.", + "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!", + "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.", + "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.", + "empty_column.home.public_timeline": "публичные ленты", + "privacy.public.short": "Публичный", + "privacy.public.long": "Показать в публичных лентах", + "privacy.unlisted.short": "Скрытый", + "privacy.unlisted.long": "Не показывать в лентах", + "privacy.private.short": "Приватный", + "privacy.private.long": "Показать только подписчикам", + "privacy.direct.short": "Направленный", + "privacy.direct.long": "Показать только упомянутым", }; export default ru; diff --git a/config/locales/ru.yml b/config/locales/ru.yml index fab178629e..0c27258554 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -9,7 +9,7 @@ ru: contact: Связаться description_headline: Что такое %{domain}? domain_count_after: другими узлами - domain_count_before: Связывается с + domain_count_before: Связан с features: api: Открытый API для приложений и сервисов blocks: Продвинутые инструменты блокирования и глушения @@ -25,7 +25,7 @@ ru: other_instances: Другие узлы source_code: Исходный код status_count_after: статусов - status_count_before: Автор + status_count_before: Опубликовано terms: Условия user_count_after: пользователей user_count_before: Здесь живет @@ -42,7 +42,7 @@ ru: application_mailer: settings: 'Изменить настройки e-mail: %{link}' signature: Уведомления Mastodon от %{instance} - view: 'View:' + view: 'Просмотр:' applications: invalid_url: Введенный URL неверен auth: @@ -126,7 +126,7 @@ ru: acct: Введите username@domain, откуда Вы хотите подписаться missing_resource: Поиск требуемого перенаправления URL для Вашего аккаунта завершился неудачей proceed: Продолжить подписку - prompt: 'Вы ходите подписаться на:' + prompt: 'Вы хотите подписаться на:' settings: authorized_apps: Авторизованные приложения back: Назад в Mastodon @@ -142,8 +142,8 @@ ru: show_more: Подробнее visibilities: private: Показывать только подписчикам - public: Публичный - unlisted: Публичный, но без отображения в публичных лентах + public: Показывать всем + unlisted: Показывать всем, но не отображать в публичных лентах stream_entries: click_to_show: Показать reblogged: продвинул(а) @@ -156,8 +156,13 @@ ru: disable: Отключить enable: Включить instructions_html: "Отсканируйте этот QR-код с помощью Google Authenticator или другого подобного приложения на Вашем телефоне. С этого момента приложение будет генерировать токены, которые будет необходимо ввести для входа." + manual_instructions: 'Если Вы не можете отсканировать QR-код и хотите ввести его вручную, секрет представлен здесь открытым текстом:' plaintext_secret_html: 'Секрет открытым текстом: %{secret}' - warning: Если сейчас у Вас не получается настроить аутентификатор, нажмите "отключить", иначе Вы не сможете войти! + code_hint: 'Для подтверждения введите код, сгенерированный приложением аутентификатора' + setup: Настроить + warning: 'Если сейчас у Вас не получается настроить аутентификатор, нажмите "отключить", иначе Вы не сможете войти!' users: invalid_email: Введенный e-mail неверен invalid_otp_token: Введен неверный код + will_paginate: + page_gap: "…" diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml index 6f4873bfd2..b7d8e4e05f 100644 --- a/config/locales/simple_form.ru.yml +++ b/config/locales/simple_form.ru.yml @@ -26,7 +26,7 @@ ru: note: О Вас otp_attempt: Двухфакторный код password: Пароль - setting_default_privacy: Приватность постов + setting_default_privacy: Видимость постов type: Тип импорта username: Имя пользователя interactions: From 0e4479bb3aec9dc6db3f352fd4933e2643e3e628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B0=E9=83=BD=E5=BF=83=28Neet=20Shin=29?= Date: Thu, 13 Apr 2017 16:53:45 +0530 Subject: [PATCH 8/9] Update Japanese translation files (#1640) * [l10n] ja: Improve Japanese Translations * ja: about: Fix highlighting * ja: Update Translations * ja: Translate admin settings Signed-off-by: lindwurm * Update ja.jsx * Update doorkeeper.ja.yml * Update ja.yml * Update ja.jsx * Update ja.jsx --- .../javascripts/components/locales/ja.jsx | 4 +-- config/locales/doorkeeper.ja.yml | 32 +++++++++---------- config/locales/ja.yml | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/components/locales/ja.jsx b/app/assets/javascripts/components/locales/ja.jsx index 25a6f7f675..fdfc91c29f 100644 --- a/app/assets/javascripts/components/locales/ja.jsx +++ b/app/assets/javascripts/components/locales/ja.jsx @@ -39,8 +39,8 @@ const ja = { "tabs_bar.compose": "投稿", "tabs_bar.home": "ホーム", "tabs_bar.mentions": "返信", - "tabs_bar.local_timeline": "ローカルTL", - "tabs_bar.federated_timeline": "連合TL", + "tabs_bar.local_timeline": "ローカル", + "tabs_bar.federated_timeline": "連合", "tabs_bar.notifications": "通知", "compose_form.placeholder": "今なにしてる?", "compose_form.publish": "トゥート", diff --git a/config/locales/doorkeeper.ja.yml b/config/locales/doorkeeper.ja.yml index 35592bc494..d3ea93789c 100644 --- a/config/locales/doorkeeper.ja.yml +++ b/config/locales/doorkeeper.ja.yml @@ -25,7 +25,7 @@ ja: confirmations: destroy: 本当に削除しますか? edit: - title: アプリケーションの編集 + title: アプリの編集 form: error: フォームにエラーが無いか確認してください。 help: @@ -35,17 +35,17 @@ ja: index: callback_url: コールバックURL name: 名前 - new: 新規アプリケーション - title: あなたのアプリケーション + new: 新規アプリ + title: アプリ new: - title: 新規アプリケーション + title: 新規アプリ show: actions: アクション application_id: アクションId callback_urls: コールバックurl scopes: アクセス権 secret: 非公開 - title: 'アプリケーション: %{name}' + title: 'アプリ: %{name}' authorizations: buttons: authorize: 承認 @@ -53,8 +53,8 @@ ja: error: title: エラーが発生しました。 new: - able_to: このアプリケーションは以下のことができます - prompt: アプリケーション %{client_name} があなたのアカウントへのアクセスを要求しています。 + able_to: このアプリは以下のことができます + prompt: アプリ %{client_name} があなたのアカウントへのアクセスを要求しています。 title: 認証が必要です。 show: title: 認証コード @@ -68,7 +68,7 @@ ja: created_at: 許可した日時 date_format: "%Y年%m月%d日 %H時%M分%S秒" scopes: アクセス権 - title: 認証済みアプリケーション + title: 認証済みアプリ errors: messages: access_denied: リソースの所有者または認証サーバーが要求を拒否しました。 @@ -92,22 +92,22 @@ ja: flash: applications: create: - notice: アプリケーションが作成されました。 + notice: アプリが作成されました。 destroy: - notice: アプリケーションが削除されました。 + notice: アプリが削除されました。 update: - notice: アプリケーションが更新されました。 + notice: アプリが更新されました。 authorized_applications: destroy: - notice: アプリケーションが取り消されました。 + notice: アプリが取り消されました。 layouts: admin: nav: - applications: アプリケーション + applications: アプリ oauth2_provider: OAuth2プロバイダー application: - title: OAuth認証が必要です。 + title: OAuth認証 scopes: follow: アカウントのフォロー, ブロック, ブロック解除, フォロー解除 - read: アカウントへのデータの読み取り - write: アカウントからの投稿の書き込み + read: アカウントからのデータの読み取り + write: アカウントへのデータの書き込み diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 9407c7669d..cd6b6543dc 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -46,7 +46,7 @@ ja: applications: invalid_url: URLが無効です auth: - change_password: 資格情報 + change_password: ログイン情報 didnt_get_confirmation: 確認メールを受信できませんか? forgot_password: パスワードをお忘れですか? login: ログイン From 282bb55c3cae07229d4c9a2fe58c1c2a136c57b9 Mon Sep 17 00:00:00 2001 From: Hugo Gameiro Date: Thu, 13 Apr 2017 12:25:34 +0100 Subject: [PATCH 9/9] fix Portuguese translation (#1661) * update portuguese translation added the missing fields and improved the translation * pt translations fix * improve last translation commit * fix damn quotes --- .../javascripts/components/locales/pt.jsx | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/components/locales/pt.jsx b/app/assets/javascripts/components/locales/pt.jsx index 8d1b88c752..cd345a585b 100644 --- a/app/assets/javascripts/components/locales/pt.jsx +++ b/app/assets/javascripts/components/locales/pt.jsx @@ -14,59 +14,115 @@ const pt = { "status.show_less": "Mostrar menos", "status.open": "Expandir", "status.report": "Reportar @{name}", + "status.load_more": "Carregar mais", + "status.media_hidden": "Media escondida", "video_player.toggle_sound": "Ligar/Desligar som", + "video_player.toggle_visible": "Ligar/Desligar vídeo", "account.mention": "Mencionar @{name}", "account.edit_profile": "Editar perfil", "account.unblock": "Não bloquear @{name}", "account.unfollow": "Não seguir", "account.block": "Bloquear @{name}", + "account.mute": "Mute", + "account.unmute": "Remover Mute", "account.follow": "Seguir", "account.posts": "Posts", "account.follows": "Segue", "account.followers": "Seguidores", "account.follows_you": "É teu seguidor", "account.requested": "A aguardar aprovação", + "account.report": "Denunciar", + "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.", "getting_started.heading": "Primeiros passos", "getting_started.about_addressing": "Podes seguir pessoas se sabes o nome de usuário deles e o domínio em que estão colocando um endereço similar a e-mail no campo no topo da barra lateral.", "getting_started.about_shortcuts": "Se o usuário alvo está no mesmo domínio, só o nome funcionará. A mesma regra se aplica a mencionar pessoas nas postagens.", + "getting_started.about_developer": "Pode seguir o developer deste projecto em Gargron@mastodon.social", "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.", "column.home": "Home", "column.community": "Local", - "column.public": "Público", + "column.public": "Global", "column.notifications": "Notificações", + "column.blocks": "Utilizadores Bloqueados", + "column.favourites": "Favoritos", + "column.follow_requests": "Seguidores Pendentes", + "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.", + "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.community": "Ainda não existem conteúdo local para mostrar!", + "empty_column.hashtag": "Não existe qualquer conteúdo com essa hashtag", "tabs_bar.compose": "Criar", "tabs_bar.home": "Home", "tabs_bar.mentions": "Menções", "tabs_bar.public": "Público", "tabs_bar.notifications": "Notificações", + "tabs_bar.local_timeline": "Local", + "tabs_bar.federated_timeline": "Global", "compose_form.placeholder": "Em que estás a pensar?", "compose_form.publish": "Publicar", - "compose_form.sensitive": "Media com conteúdo sensível", + "compose_form.sensitive": "Marcar media como conteúdo sensível", "compose_form.spoiler": "Esconder texto com aviso", + "compose_form.spoiler_placeholder": "Aviso", "compose_form.private": "Tornar privado", "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.", "compose_form.unlisted": "Não mostrar na listagem pública", + "emoji_button.label": "Inserir Emoji", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.preferences": "Preferências", "navigation_bar.community_timeline": "Local", - "navigation_bar.public_timeline": "Público", + "navigation_bar.public_timeline": "Global", + "navigation_bar.blocks": "Utilizadores bloqueados", + "navigation_bar.favourites": "Favoritos", + "navigation_bar.info": "Mais informações", "navigation_bar.logout": "Sair", + "navigation_bar.follow_requests": "Seguidores pendentes", "reply_indicator.cancel": "Cancelar", "search.placeholder": "Pesquisar", "search.account": "Conta", "search.hashtag": "Hashtag", + "search_results.total": "{count} {count, plural, one {resultado} other {resultados}}", + "search.status_by": "Post de {name}", "upload_button.label": "Adicionar media", "upload_form.undo": "Anular", + "upload_progress.label": "A gravar…", + "upload_area.title": "Arraste e solte para enviar", "notification.follow": "{name} seguiu-te", "notification.favourite": "{name} adicionou o teu post aos favoritos", "notification.reblog": "{name} partilhou o teu post", "notification.mention": "{name} mencionou-te", "notifications.column_settings.alert": "Notificações no computador", "notifications.column_settings.show": "Mostrar nas colunas", + "notifications.column_settings.sound": "Reproduzir som", "notifications.column_settings.follow": "Novos seguidores:", "notifications.column_settings.favourite": "Favoritos:", "notifications.column_settings.mention": "Menções:", "notifications.column_settings.reblog": "Partilhas:", + "notifications.clear": "Limpar notificações", + "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?", + "notifications.settings": "Parâmetros da lista de Notificações", + "privacy.public.short": "Público", + "privacy.public.long": "Publicar em todos os feeds", + "privacy.unlisted.short": "Não listar", + "privacy.unlisted.long": "Não publicar nos feeds públicos", + "privacy.private.short": "Privado", + "privacy.private.long": "Apenas para os seguidores", + "privacy.direct.short": "Directo", + "privacy.direct.long": "Apenas para utilizadores mencionados", + "privacy.change": "Ajustar a privacidade da mensagem", + "media_gallery.toggle_visible": "Modificar a visibilidade", + "missing_indicator.label": "Não encontrado", + "follow_request.authorize": "Autorizar", + "follow_request.reject": "Rejeitar", + "home.settings": "Parâmetros da coluna Home", + "home.column_settings.basic": "Básico", + "home.column_settings.show_reblogs": "Mostrar as partilhas", + "home.column_settings.show_replies": "Mostrar as respostas", + "home.column_settings.advanced": "Avançadas", + "home.column_settings.filter_regex": "Filtrar com uma expressão regular", + "report.heading": "Nova denuncia", + "report.placeholder": "Comentários adicionais", + "report.submit": "Enviar", + "report.target": "Denunciar" }; export default pt;