Commit 5bc1d072 authored by xxxkurosukexxx's avatar xxxkurosukexxx

Merge branch 'master' into follow_master

parents a8525584 cb12a2cd
......@@ -4,7 +4,7 @@ FROM ubuntu:18.04 as build-dep
SHELL ["bash", "-c"]
# Install Node v12 (LTS)
ENV NODE_VER="12.14.0"
ENV NODE_VER="12.16.1"
RUN ARCH= && \
dpkgArch="$(dpkg --print-architecture)" && \
case "${dpkgArch##*-}" in \
......
......@@ -6,12 +6,12 @@ module Admin
def index
authorize :email_domain_block, :index?
@email_domain_blocks = EmailDomainBlock.page(params[:page])
@email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
end
def new
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new
@email_domain_block = EmailDomainBlock.new(domain: params[:_domain])
end
def create
......@@ -21,6 +21,28 @@ module Admin
if @email_domain_block.save
log_action :create, @email_domain_block
if @email_domain_block.with_dns_records?
hostnames = []
ips = []
Resolv::DNS.open do |dns|
dns.timeouts = 1
hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }
([@email_domain_block.domain] + hostnames).uniq.each do |hostname|
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s })
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s })
end
end
(hostnames + ips).each do |hostname|
another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: @email_domain_block)
log_action :create, another_email_domain_block if another_email_domain_block.save
end
end
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
else
render :new
......@@ -41,7 +63,7 @@ module Admin
end
def resource_params
params.require(:email_domain_block).permit(:domain)
params.require(:email_domain_block).permit(:domain, :with_dns_records)
end
end
end
......@@ -7,7 +7,7 @@ module Admin
def index
authorize :account_warning_preset, :index?
@warning_presets = AccountWarningPreset.all
@warning_presets = AccountWarningPreset.alphabetic
@warning_preset = AccountWarningPreset.new
end
......@@ -19,7 +19,7 @@ module Admin
if @warning_preset.save
redirect_to admin_warning_presets_path
else
@warning_presets = AccountWarningPreset.all
@warning_presets = AccountWarningPreset.alphabetic
render :index
end
end
......@@ -52,7 +52,7 @@ module Admin
end
def warning_preset_params
params.require(:account_warning_preset).permit(:text)
params.require(:account_warning_preset).permit(:title, :text)
end
end
end
......@@ -245,7 +245,7 @@ export function uploadCompose(files) {
const poll = () => {
api(getState).get(`/api/v1/media/${data.id}`).then(response => {
if (response.status === 200) {
dispatch(uploadComposeSuccess(data, f));
dispatch(uploadComposeSuccess(response.data, f));
} else if (response.status === 206) {
setTimeout(() => poll(), 1000);
}
......
......@@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
});
export default @injectIntl
......
......@@ -6,7 +6,7 @@ import Domain from '../components/domain';
import { openModal } from '../actions/modal';
const messages = defineMessages({
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
});
const makeMapStateToProps = () => {
......
......@@ -39,7 +39,7 @@ const messages = defineMessages({
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
......@@ -142,7 +142,7 @@ class Header extends ImmutablePureComponent {
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
info.push(<span key='muted' className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>);
info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain blocked' /></span>);
}
if (me !== account.get('id')) {
......
......@@ -13,8 +13,8 @@ import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_bloc
import ScrollableList from '../../components/scrollable_list';
const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
});
const mapStateToProps = state => ({
......@@ -55,7 +55,7 @@ class Blocks extends ImmutablePureComponent {
);
}
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
return (
<Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
......
......@@ -166,7 +166,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
reblogIcon = 'lock';
}
if (status.get('visibility') === 'private') {
if (['private', 'direct'].includes(status.get('visibility'))) {
reblogLink = <Icon id={reblogIcon} />;
} else if (this.context.router) {
reblogLink = (
......
......@@ -523,7 +523,7 @@
{
"descriptors": [
{
"defaultMessage": "Hide entire domain",
"defaultMessage": "Block entire domain",
"id": "confirmations.domain_block.confirm"
},
{
......@@ -737,7 +737,7 @@
"id": "navigation_bar.blocks"
},
{
"defaultMessage": "Hidden domains",
"defaultMessage": "Blocked domains",
"id": "navigation_bar.domain_blocks"
},
{
......@@ -773,7 +773,7 @@
"id": "account.muted"
},
{
"defaultMessage": "Domain hidden",
"defaultMessage": "Domain blocked",
"id": "account.domain_blocked"
},
{
......@@ -1466,7 +1466,7 @@
{
"descriptors": [
{
"defaultMessage": "Hidden domains",
"defaultMessage": "Blocked domains",
"id": "column.domain_blocks"
},
{
......@@ -1474,7 +1474,7 @@
"id": "account.unblock_domain"
},
{
"defaultMessage": "There are no hidden domains yet.",
"defaultMessage": "There are no blocked domains yet.",
"id": "empty_column.domain_blocks"
}
],
......@@ -2944,4 +2944,4 @@
],
"path": "app/javascript/mastodon/features/video/index.json"
}
]
]
\ No newline at end of file
......@@ -7,7 +7,7 @@
"account.blocked": "Blocked",
"account.cancel_follow_request": "Cancel follow request",
"account.direct": "Direct message @{name}",
"account.domain_blocked": "Domain hidden",
"account.domain_blocked": "Domain blocked",
"account.edit_profile": "Edit profile",
"account.endorse": "Feature on profile",
"account.follow": "Follow",
......@@ -57,7 +57,7 @@
"column.community": "Local timeline",
"column.direct": "Direct messages",
"column.directory": "Browse profiles",
"column.domain_blocks": "Hidden domains",
"column.domain_blocks": "Blocked domains",
"column.favourites": "Favourites",
"column.follow_requests": "Follow requests",
"column.home": "Home",
......@@ -103,7 +103,7 @@
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.confirm": "Block entire domain",
"confirmations.domain_block.message": "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. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
"confirmations.logout.confirm": "Log out",
"confirmations.logout.message": "Are you sure you want to log out?",
......@@ -146,7 +146,7 @@
"empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.domain_blocks": "There are no hidden domains yet.",
"empty_column.domain_blocks": "There are no blocked domains yet.",
"empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
"empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
......@@ -265,7 +265,7 @@
"navigation_bar.compose": "Compose new toot",
"navigation_bar.direct": "Direct messages",
"navigation_bar.discover": "Discover",
"navigation_bar.domain_blocks": "Hidden domains",
"navigation_bar.domain_blocks": "Blocked domains",
"navigation_bar.edit_profile": "Edit profile",
"navigation_bar.favourites": "Favourites",
"navigation_bar.filters": "Muted words",
......
......@@ -8,8 +8,11 @@
# text :text default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# title :string default(""), not null
#
class AccountWarningPreset < ApplicationRecord
validates :text, presence: true
scope :alphabetic, -> { order(title: :asc, text: :asc) }
end
......@@ -7,13 +7,27 @@
# domain :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# parent_id :bigint(8)
#
class EmailDomainBlock < ApplicationRecord
include DomainNormalizable
belongs_to :parent, class_name: 'EmailDomainBlock', optional: true
has_many :children, class_name: 'EmailDomainBlock', foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
validates :domain, presence: true, uniqueness: true, domain: true
def with_dns_records=(val)
@with_dns_records = ActiveModel::Type::Boolean.new.cast(val)
end
def with_dns_records?
@with_dns_records
end
alias with_dns_records with_dns_records?
def self.block?(email)
_, domain = email.split('@', 2)
......
......@@ -77,6 +77,23 @@ class MediaAttachment < ApplicationRecord
},
}.freeze
VIDEO_PASSTHROUGH_OPTIONS = {
video_codecs: ['h264'],
audio_codecs: ['aac', nil],
colorspaces: ['yuv420p'],
options: {
format: 'mp4',
convert_options: {
output: {
'loglevel' => 'fatal',
'map_metadata' => '-1',
'c:v' => 'copy',
'c:a' => 'copy',
},
},
},
}.freeze
VIDEO_STYLES = {
small: {
convert_options: {
......@@ -91,7 +108,7 @@ class MediaAttachment < ApplicationRecord
blurhash: BLURHASH_OPTIONS,
},
original: VIDEO_FORMAT,
original: VIDEO_FORMAT.merge(passthrough_options: VIDEO_PASSTHROUGH_OPTIONS),
}.freeze
AUDIO_STYLES = {
......
......@@ -5,6 +5,8 @@ class FetchResourceService < BaseService
ACCEPT_HEADER = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", text/html;q=0.1'
attr_reader :response_code
def call(url)
return if url.blank?
......@@ -27,6 +29,7 @@ class FetchResourceService < BaseService
end
def process_response(response, terminal = false)
@response_code = response.code
return nil if response.code != 200
if ['application/activity+json', 'application/ld+json'].include?(response.mime_type)
......
......@@ -12,7 +12,7 @@ class ResolveURLService < BaseService
process_local_url
elsif !fetched_resource.nil?
process_url
elsif @on_behalf_of.present?
else
process_url_from_db
end
end
......@@ -30,6 +30,8 @@ class ResolveURLService < BaseService
end
def process_url_from_db
return unless @on_behalf_of.present? && [401, 403, 404].include?(fetch_resource_service.response_code)
# It may happen that the resource is a private toot, and thus not fetchable,
# but we can return the toot if we already know about it.
status = Status.find_by(uri: @url) || Status.find_by(url: @url)
......@@ -40,7 +42,11 @@ class ResolveURLService < BaseService
end
def fetched_resource
@fetched_resource ||= FetchResourceService.new.call(@url)
@fetched_resource ||= fetch_resource_service.call(@url)
end
def fetch_resource_service
@_fetch_resource_service ||= FetchResourceService.new
end
def resource_url
......
......@@ -21,7 +21,7 @@
- unless @warning_presets.empty?
.fields-group
= f.input :warning_preset_id, collection: @warning_presets, label_method: :text, wrapper: :with_block_label
= f.input :warning_preset_id, collection: @warning_presets, label_method: ->(warning_preset) { [warning_preset.title.presence, truncate(warning_preset.text)].compact.join(' - ') }, wrapper: :with_block_label
.fields-group
= f.input :text, as: :text, wrapper: :with_block_label, hint: t('simple_form.hints.admin_account_action.text_html', path: admin_warning_presets_path)
......
......@@ -96,10 +96,17 @@
= table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user)
%tr
%th= t('admin.accounts.email')
%td= @account.user_email
%th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email')
%td{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= @account.user_email
%td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user)
%tr
%td= table_link_to 'search', t('admin.accounts.search_same_email_domain'), admin_accounts_path(email: "%@#{@account.user_email.split('@').last}")
- if can?(:create, :email_domain_block)
%tr
%td= table_link_to 'ban', t('admin.accounts.add_email_domain_block'), new_admin_email_domain_block_path(_domain: @account.user_email.split('@').last)
- if @account.user_unconfirmed_email.present?
%tr
%th= t('admin.accounts.unconfirmed_email')
......@@ -204,7 +211,7 @@
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account)
- unless @account.local?
- if DomainBlock.where(domain: @account.domain).exists?
- if DomainBlock.rule_for(@account.domain)
= link_to t('admin.domain_blocks.view'), admin_instance_path(@account.domain), class: 'button'
- else
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'
......
......@@ -3,3 +3,13 @@
%samp= email_domain_block.domain
%td
= table_link_to 'trash', t('admin.email_domain_blocks.delete'), admin_email_domain_block_path(email_domain_block), method: :delete
- email_domain_block.children.each do |child_email_domain_block|
%tr
%td
%samp= child_email_domain_block.domain
%span.muted-hint
= surround '(', ')' do
= t('admin.email_domain_blocks.from_html', domain: content_tag(:samp, email_domain_block.domain))
%td
= table_link_to 'trash', t('admin.email_domain_blocks.delete'), admin_email_domain_block_path(child_email_domain_block), method: :delete
......@@ -5,7 +5,10 @@
= render 'shared/error_messages', object: @email_domain_block
.fields-group
= f.input :domain, wrapper: :with_label, label: t('admin.email_domain_blocks.domain')
= f.input :domain, wrapper: :with_block_label, label: t('admin.email_domain_blocks.domain')
.fields-group
= f.input :with_dns_records, as: :boolean, wrapper: :with_label
.actions
= f.button :button, t('.create'), type: :submit
.announcements-list__item
= link_to edit_admin_warning_preset_path(warning_preset), class: 'announcements-list__item__title' do
= warning_preset.title.presence || truncate(warning_preset.text)
.announcements-list__item__action-bar
.announcements-list__item__meta
= truncate(warning_preset.text)
%div
= table_link_to 'trash', t('admin.warning_presets.delete'), admin_warning_preset_path(warning_preset), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, warning_preset)
......@@ -4,6 +4,9 @@
= simple_form_for @warning_preset, url: admin_warning_preset_path(@warning_preset) do |f|
= render 'shared/error_messages', object: @warning_preset
.fields-group
= f.input :title, wrapper: :with_block_label
.fields-group
= f.input :text, wrapper: :with_block_label
......
......@@ -5,6 +5,9 @@
= simple_form_for @warning_preset, url: admin_warning_presets_path do |f|
= render 'shared/error_messages', object: @warning_preset
.fields-group
= f.input :title, wrapper: :with_block_label
.fields-group
= f.input :text, wrapper: :with_block_label
......@@ -13,18 +16,9 @@
%hr.spacer/
- unless @warning_presets.empty?
.table-wrapper
%table.table
%thead
%tr
%th= t('simple_form.labels.account_warning_preset.text')
%th
%tbody
- @warning_presets.each do |preset|
%tr
%td
= Formatter.instance.linkify(preset.text)
%td
= table_link_to 'pencil', t('admin.warning_presets.edit'), edit_admin_warning_preset_path(preset)
= table_link_to 'trash', t('admin.warning_presets.delete'), admin_warning_preset_path(preset), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
- if @warning_presets.empty?
%div.muted-hint.center-text
= t 'admin.warning_presets.empty'
- else
.announcements-list
= render partial: 'warning_preset', collection: @warning_presets
......@@ -562,7 +562,6 @@ ar:
warning_presets:
add_new: إضافة واحد جديد
delete: حذف
edit: تعديل
edit_preset: تعديل نموذج التحذير
title: إدارة نماذج التحذير
admin_mailer:
......
......@@ -573,7 +573,6 @@ ca:
warning_presets:
add_new: Afegeix-ne un de nou
delete: Esborra
edit: Edita
edit_preset: Edita l'avís predeterminat
title: Gestiona les configuracions predefinides dels avisos
admin_mailer:
......
......@@ -573,7 +573,6 @@ co:
warning_presets:
add_new: Aghjunghje
delete: Sguassà
edit: Cambià
edit_preset: Cambià a preselezzione d'avertimentu
title: Amministrà e preselezzione d'avertimentu
admin_mailer:
......
......@@ -588,7 +588,6 @@ cs:
warning_presets:
add_new: Přidat nové
delete: Smazat
edit: Upravit
edit_preset: Upravit předlohu pro varování
title: Spravovat předlohy pro varování
admin_mailer:
......
......@@ -605,7 +605,6 @@ cy:
warning_presets:
add_new: Ychwanegu newydd
delete: Dileu
edit: Golygu
edit_preset: Golygu rhagosodiad rhybudd
title: Rheoli rhagosodiadau rhybudd
admin_mailer:
......
......@@ -489,7 +489,6 @@ da:
most_recent: Seneste
warning_presets:
delete: Slet
edit: Rediger
admin_mailer:
new_report:
body: "%{reporter} har anmeldt %{target}"
......
......@@ -573,7 +573,6 @@ de:
warning_presets:
add_new: Neu hinzufügen
delete: Löschen
edit: Bearbeiten
edit_preset: Warnungsvorlage bearbeiten
title: Warnungsvorlagen verwalten
admin_mailer:
......
......@@ -573,7 +573,6 @@ el:
warning_presets:
add_new: Πρόσθεση νέου
delete: Διαγραφή
edit: Ενημέρωση
edit_preset: Ενημέρωση προκαθορισμένης προειδοποίησης
title: Διαχείριση προκαθορισμένων προειδοποιήσεων
admin_mailer:
......
......@@ -92,6 +92,7 @@ en:
delete: Delete
destroyed_msg: Moderation note successfully destroyed!
accounts:
add_email_domain_block: Blacklist e-mail domain
approve: Approve
approve_all: Approve all
are_you_sure: Are you sure?
......@@ -172,6 +173,7 @@ en:
staff: Staff
user: User
search: Search
search_same_email_domain: Other users with the same e-mail domain
search_same_ip: Other users with the same IP
shared_inbox_url: Shared inbox URL
show:
......@@ -358,6 +360,7 @@ en:
destroyed_msg: Successfully deleted e-mail domain from blacklist
domain: Domain
empty: No e-mail domains currently blacklisted.
from_html: from %{domain}
new:
create: Add domain
title: New e-mail blacklist entry
......@@ -576,7 +579,6 @@ en:
warning_presets:
add_new: Add new
delete: Delete
edit: Edit
edit_preset: Edit warning preset
title: Manage warning presets
admin_mailer:
......
......@@ -474,7 +474,6 @@ en_GB:
warning_presets:
add_new: Add new
delete: Delete
edit: Edit
edit_preset: Edit warning preset
title: Manage warning presets
admin_mailer:
......
......@@ -557,7 +557,6 @@ eo:
warning_presets:
add_new: Aldoni novan
delete: Forigi
edit: Redakti
edit_preset: Redakti avertan antaŭagordon
title: Administri avertajn antaŭagordojn
admin_mailer:
......
......@@ -573,7 +573,6 @@ es-AR:
warning_presets:
add_new: Agregar nuevo
delete: Eliminar
edit: Editar
edit_preset: Editar preajuste de advertencia
title: Administrar preajustes de advertencia
admin_mailer:
......
......@@ -573,7 +573,6 @@ es:
warning_presets:
add_new: Añadir nuevo
delete: Borrar
edit: Editar
edit_preset: Editar aviso predeterminado
title: Editar configuración predeterminada de avisos
admin_mailer:
......
......@@ -576,7 +576,6 @@ et:
warning_presets:
add_new: Lisa uus
delete: Kustuta
edit: Redigeeri
edit_preset: Redigeeri hoiatuse eelseadistust
title: Halda hoiatuste eelseadistusi
admin_mailer:
......
......@@ -573,7 +573,6 @@ eu:
warning_presets:
add_new: Gehitu berria
delete: Ezabatu
edit: Editatu
edit_preset: Editatu abisu aurre-ezarpena
title: Kudeatu abisu aurre-ezarpenak
admin_mailer:
......
......@@ -575,7 +575,6 @@ fa:
warning_presets:
add_new: افزودن تازه
delete: زدودن
edit: ویرایش
edit_preset: ویرایش هشدار پیش‌فرض
title: مدیریت هشدارهای پیش‌فرض
admin_mailer:
......
......@@ -573,7 +573,6 @@ fr:
warning_presets:
add_new: Ajouter un nouveau
delete: Effacer
edit: Éditer
edit_preset: Éditer les avertissements prédéfinis
title: Gérer les avertissements prédéfinis
admin_mailer:
......
......@@ -573,7 +573,6 @@ gl:
warning_presets:
add_new: Engadir novo
delete: Eliminar
edit: Editar
edit_preset: Editar aviso preestablecido
title: Xestionar avisos preestablecidos
admin_mailer:
......
......@@ -575,7 +575,6 @@ hu: