Add ability to translate server rules (#34494)
This commit is contained in:
parent
977164decc
commit
8c51a8ba94
17 changed files with 149 additions and 20 deletions
|
@ -7,7 +7,7 @@ module Admin
|
|||
def index
|
||||
authorize :rule, :index?
|
||||
|
||||
@rules = Rule.ordered
|
||||
@rules = Rule.ordered.includes(:translations)
|
||||
end
|
||||
|
||||
def new
|
||||
|
@ -27,7 +27,6 @@ module Admin
|
|||
if @rule.save
|
||||
redirect_to admin_rules_path
|
||||
else
|
||||
@rules = Rule.ordered
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
@ -74,7 +73,7 @@ module Admin
|
|||
|
||||
def resource_params
|
||||
params
|
||||
.expect(rule: [:text, :hint, :priority])
|
||||
.expect(rule: [:text, :hint, :priority, translations_attributes: [[:id, :language, :text, :hint, :_destroy]]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,6 +18,6 @@ class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController
|
|||
private
|
||||
|
||||
def set_rules
|
||||
@rules = Rule.ordered
|
||||
@rules = Rule.ordered.includes(:translations)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -126,7 +126,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
|
||||
def set_rules
|
||||
@rules = Rule.ordered
|
||||
@rules = Rule.ordered.includes(:translations)
|
||||
end
|
||||
|
||||
def require_rules_acceptance!
|
||||
|
|
|
@ -44,6 +44,7 @@ const severityMessages = {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
server: state.getIn(['server', 'server']),
|
||||
locale: state.getIn(['meta', 'locale']),
|
||||
extendedDescription: state.getIn(['server', 'extendedDescription']),
|
||||
domainBlocks: state.getIn(['server', 'domainBlocks']),
|
||||
});
|
||||
|
@ -91,6 +92,7 @@ class About extends PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
server: ImmutablePropTypes.map,
|
||||
locale: ImmutablePropTypes.string,
|
||||
extendedDescription: ImmutablePropTypes.map,
|
||||
domainBlocks: ImmutablePropTypes.contains({
|
||||
isLoading: PropTypes.bool,
|
||||
|
@ -114,7 +116,7 @@ class About extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props;
|
||||
const { multiColumn, intl, server, extendedDescription, domainBlocks, locale } = this.props;
|
||||
const isLoading = server.get('isLoading');
|
||||
|
||||
return (
|
||||
|
@ -168,12 +170,15 @@ class About extends PureComponent {
|
|||
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
|
||||
) : (
|
||||
<ol className='rules-list'>
|
||||
{server.get('rules').map(rule => (
|
||||
<li key={rule.get('id')}>
|
||||
<div className='rules-list__text'>{rule.get('text')}</div>
|
||||
{rule.get('hint').length > 0 && (<div className='rules-list__hint'>{rule.get('hint')}</div>)}
|
||||
</li>
|
||||
))}
|
||||
{server.get('rules').map(rule => {
|
||||
const text = rule.getIn(['translations', locale, 'text']) || rule.get('text');
|
||||
const hint = rule.getIn(['translations', locale, 'hint']) || rule.get('hint');
|
||||
return (
|
||||
<li key={rule.get('id')}>
|
||||
<div className='rules-list__text'>{text}</div>
|
||||
{hint.length > 0 && (<div className='rules-list__hint'>{hint}</div>)}
|
||||
</li>
|
||||
)})}
|
||||
</ol>
|
||||
))}
|
||||
</Section>
|
||||
|
|
|
@ -12,6 +12,7 @@ import Option from './components/option';
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
rules: state.getIn(['server', 'server', 'rules']),
|
||||
locale: state.getIn(['meta', 'locale']),
|
||||
});
|
||||
|
||||
class Rules extends PureComponent {
|
||||
|
@ -19,6 +20,7 @@ class Rules extends PureComponent {
|
|||
static propTypes = {
|
||||
onNextStep: PropTypes.func.isRequired,
|
||||
rules: ImmutablePropTypes.list,
|
||||
locale: PropTypes.string,
|
||||
selectedRuleIds: ImmutablePropTypes.set.isRequired,
|
||||
onToggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
@ -34,7 +36,7 @@ class Rules extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { rules, selectedRuleIds } = this.props;
|
||||
const { rules, locale, selectedRuleIds } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -49,7 +51,7 @@ class Rules extends PureComponent {
|
|||
value={item.get('id')}
|
||||
checked={selectedRuleIds.includes(item.get('id'))}
|
||||
onToggle={this.handleRulesToggle}
|
||||
label={item.get('text')}
|
||||
label={item.getIn(['translations', locale, 'text']) || item.get('text')}
|
||||
multiple
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -19,6 +19,9 @@ class Rule < ApplicationRecord
|
|||
|
||||
self.discard_column = :deleted_at
|
||||
|
||||
has_many :translations, inverse_of: :rule, class_name: 'RuleTranslation', dependent: :destroy
|
||||
accepts_nested_attributes_for :translations, reject_if: :all_blank, allow_destroy: true
|
||||
|
||||
validates :text, presence: true, length: { maximum: TEXT_SIZE_LIMIT }
|
||||
|
||||
scope :ordered, -> { kept.order(priority: :asc, id: :asc) }
|
||||
|
@ -36,4 +39,9 @@ class Rule < ApplicationRecord
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def translation_for(locale)
|
||||
@cached_translations ||= {}
|
||||
@cached_translations[locale] ||= translations.find_by(language: locale) || RuleTranslation.new(language: locale, text: text, hint: hint)
|
||||
end
|
||||
end
|
||||
|
|
20
app/models/rule_translation.rb
Normal file
20
app/models/rule_translation.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: rule_translations
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# hint :text default(""), not null
|
||||
# language :string not null
|
||||
# text :text default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# rule_id :bigint(8) not null
|
||||
#
|
||||
class RuleTranslation < ApplicationRecord
|
||||
belongs_to :rule
|
||||
|
||||
validates :language, presence: true, uniqueness: { scope: :rule_id }
|
||||
validates :text, presence: true, length: { maximum: Rule::TEXT_SIZE_LIMIT }
|
||||
end
|
|
@ -47,7 +47,7 @@ class InstancePresenter < ActiveModelSerializers::Model
|
|||
end
|
||||
|
||||
def rules
|
||||
Rule.ordered
|
||||
Rule.ordered.includes(:translations)
|
||||
end
|
||||
|
||||
def user_count
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::RuleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :text, :hint
|
||||
attributes :id, :text, :hint, :translations
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def translations
|
||||
object.translations.to_h do |translation|
|
||||
[translation.language, { text: translation.text, hint: translation.hint }]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
27
app/views/admin/rules/_translation_fields.html.haml
Normal file
27
app/views/admin/rules/_translation_fields.html.haml
Normal file
|
@ -0,0 +1,27 @@
|
|||
%tr.nested-fields
|
||||
%td
|
||||
.fields-row
|
||||
.fields-row__column.fields-group
|
||||
= f.input :language,
|
||||
collection: ui_languages,
|
||||
include_blank: false,
|
||||
label_method: ->(locale) { native_locale_name(locale) }
|
||||
|
||||
.fields-row__column.fields-group
|
||||
= f.hidden_field :id if f.object&.persisted? # Required so Rails doesn't put the field outside of the <tr/>
|
||||
= link_to_remove_association(f, class: 'table-action-link') do
|
||||
= safe_join([material_symbol('close'), t('filters.index.delete')])
|
||||
|
||||
.fields-group
|
||||
= f.input :text,
|
||||
label: I18n.t('simple_form.labels.rule.text'),
|
||||
hint: I18n.t('simple_form.hints.rule.text'),
|
||||
input_html: { lang: f.object&.language },
|
||||
wrapper: :with_block_label
|
||||
|
||||
.fields-group
|
||||
= f.input :hint,
|
||||
label: I18n.t('simple_form.labels.rule.hint'),
|
||||
hint: I18n.t('simple_form.hints.rule.hint'),
|
||||
input_html: { lang: f.object&.language },
|
||||
wrapper: :with_block_label
|
|
@ -6,5 +6,26 @@
|
|||
|
||||
= render form
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
%h4= t('admin.rules.translations')
|
||||
|
||||
%p.hint= t('admin.rules.translations_explanation')
|
||||
|
||||
.table-wrapper
|
||||
%table.table.keywords-table
|
||||
%thead
|
||||
%tr
|
||||
%th= t('admin.rules.translation')
|
||||
%th
|
||||
%tbody
|
||||
= form.simple_fields_for :translations do |translation|
|
||||
= render 'translation_fields', f: translation
|
||||
%tfoot
|
||||
%tr
|
||||
%td{ colspan: 3 }
|
||||
= link_to_add_association form, :translations, class: 'table-action-link', partial: 'translation_fields', 'data-association-insertion-node': '.keywords-table tbody', 'data-association-insertion-method': 'append' do
|
||||
= safe_join([material_symbol('add'), t('admin.rules.add_translation')])
|
||||
|
||||
.actions
|
||||
= form.button :button, t('generic.save_changes'), type: :submit
|
||||
|
|
|
@ -18,10 +18,11 @@
|
|||
|
||||
%ol.rules-list
|
||||
- @rules.each do |rule|
|
||||
- translation = rule.translation_for(I18n.locale.to_s)
|
||||
%li
|
||||
%button{ type: 'button', aria: { expanded: 'false' } }
|
||||
.rules-list__text= rule.text
|
||||
.rules-list__hint= rule.hint
|
||||
.rules-list__text= translation.text
|
||||
.rules-list__hint= translation.hint
|
||||
|
||||
.stacked-actions
|
||||
- accept_path = @invite_code.present? ? public_invite_url(invite_code: @invite_code, accept: @accept_token) : new_user_registration_path(accept: @accept_token)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue