From d750301792c08b35bfff9b4f997f6afcd517bff3 Mon Sep 17 00:00:00 2001 From: Peter Yates Date: Thu, 19 Dec 2019 10:51:43 +0000 Subject: [PATCH 1/4] Use symbols instead of strings for contexts --- lib/govuk_design_system_formbuilder/containers/fieldset.rb | 2 +- lib/govuk_design_system_formbuilder/elements/hint.rb | 2 +- lib/govuk_design_system_formbuilder/elements/label.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/govuk_design_system_formbuilder/containers/fieldset.rb b/lib/govuk_design_system_formbuilder/containers/fieldset.rb index 928456b2..bc70539c 100644 --- a/lib/govuk_design_system_formbuilder/containers/fieldset.rb +++ b/lib/govuk_design_system_formbuilder/containers/fieldset.rb @@ -36,7 +36,7 @@ def build_legend end def legend_text - [@legend.dig(:text), localised_text('fieldset')].compact.first + [@legend.dig(:text), localised_text(:fieldset)].compact.first end def fieldset_classes diff --git a/lib/govuk_design_system_formbuilder/elements/hint.rb b/lib/govuk_design_system_formbuilder/elements/hint.rb index 5c997c45..83a31af0 100644 --- a/lib/govuk_design_system_formbuilder/elements/hint.rb +++ b/lib/govuk_design_system_formbuilder/elements/hint.rb @@ -23,7 +23,7 @@ def html def hint_text(supplied) [ supplied.presence, - localised_text('hint') + localised_text(:hint) ].compact.first end diff --git a/lib/govuk_design_system_formbuilder/elements/label.rb b/lib/govuk_design_system_formbuilder/elements/label.rb index 9d475e16..dc0c8652 100644 --- a/lib/govuk_design_system_formbuilder/elements/label.rb +++ b/lib/govuk_design_system_formbuilder/elements/label.rb @@ -37,7 +37,7 @@ def build_label end def label_text(option_text, hidden) - text = [option_text, @value, localised_text('label'), @attribute_name.capitalize].compact.first.to_s + text = [option_text, @value, localised_text(:label), @attribute_name.capitalize].compact.first.to_s if hidden tag.span(text, class: %w(govuk-visually-hidden)) From 9a107c2632b429d845fec001a3a60ca71e69aa4c Mon Sep 17 00:00:00 2001 From: Peter Yates Date: Fri, 20 Dec 2019 14:46:45 +0000 Subject: [PATCH 2/4] Rename fieldset localisation context to legend Fieldset was a throwback to the MoJ GOV.UK Elements Form Builder, but now we refer to the fieldset's legend everywhere rather than the fieldset itself. --- lib/govuk_design_system_formbuilder/containers/fieldset.rb | 2 +- spec/support/locales/sample.en.yaml | 2 +- spec/support/shared/shared_localisation_examples.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/govuk_design_system_formbuilder/containers/fieldset.rb b/lib/govuk_design_system_formbuilder/containers/fieldset.rb index bc70539c..dcf2ce1d 100644 --- a/lib/govuk_design_system_formbuilder/containers/fieldset.rb +++ b/lib/govuk_design_system_formbuilder/containers/fieldset.rb @@ -36,7 +36,7 @@ def build_legend end def legend_text - [@legend.dig(:text), localised_text(:fieldset)].compact.first + [@legend.dig(:text), localised_text(:legend)].compact.first end def fieldset_classes diff --git a/spec/support/locales/sample.en.yaml b/spec/support/locales/sample.en.yaml index 37ba22c6..b95553e8 100644 --- a/spec/support/locales/sample.en.yaml +++ b/spec/support/locales/sample.en.yaml @@ -5,7 +5,7 @@ helpers: cv: Tell us about your employment history favourite_colour: What is your favourite colour? photo: Upload a recent passport photo - fieldset: + legend: person: favourite_colour: To which colours are you most partial? born_on: What is your date of birth? diff --git a/spec/support/shared/shared_localisation_examples.rb b/spec/support/shared/shared_localisation_examples.rb index 0c708773..b0d9a04c 100644 --- a/spec/support/shared/shared_localisation_examples.rb +++ b/spec/support/shared/shared_localisation_examples.rb @@ -65,7 +65,7 @@ let(:localisations) { LOCALISATIONS } context 'localising when no text is supplied' do - let(:expected_legend) { I18n.translate("helpers.fieldset.person.#{attribute}") } + let(:expected_legend) { I18n.translate("helpers.legend.person.#{attribute}") } subject { builder.send(*args) { arbitrary_html_content } } specify 'should set the legend from the locales' do From 2a94e77c23f0ccd5217b5183b41dc91bcbef2b1b Mon Sep 17 00:00:00 2001 From: Peter Yates Date: Fri, 20 Dec 2019 15:11:31 +0000 Subject: [PATCH 3/4] Add support for flexible localisation schemas The configuration block now supports the following keys for customising how the builder's localisation functionality works: * localisation_schema_fallback * localisation_schema_legend * localisation_schema_hint * localisation_schema_label The localisation_schema_fallback setting is the only one set by default, and it will be used for legends, hints and labels if they are not individually configured by their key respectively. Configuration must be provided as an array; it will be suffixed by the object name and attribute name. The special value `__context__` will be substituted for the contexual object; either label, hint or legend. It can be placed anywhere in the schema. Refs #80 --- lib/govuk_design_system_formbuilder.rb | 37 ++++++++- .../traits/localisation.rb | 22 ++++- .../builder/configuration_spec.rb | 83 +++++++++++++++++++ spec/support/locales/custom_contexts.en.yaml | 18 ++++ spec/support/locales/custom_fallback.en.yaml | 5 ++ 5 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 spec/support/locales/custom_contexts.en.yaml create mode 100644 spec/support/locales/custom_fallback.en.yaml diff --git a/lib/govuk_design_system_formbuilder.rb b/lib/govuk_design_system_formbuilder.rb index 396a5a07..bcb968f9 100644 --- a/lib/govuk_design_system_formbuilder.rb +++ b/lib/govuk_design_system_formbuilder.rb @@ -52,12 +52,47 @@ module GOVUKDesignSystemFormBuilder include ActiveSupport::Configurable + # @!group Defaults + + # Default form builder configuration + # + # * +:default_legend_size+ controls the default size of legend text. + # Can be either +xl+, +l+, +m+ or +s+. + # + # * +:default_legend_tag+ controls the default tag that legends are + # wrapped in. Defaults to +h1+. + # + # * +:default_submit_button_text+ sets the value assigned to +govuk_submit+, + # defaults to 'Continue'. + # + # * +:default_submit_button_text+ sets the text used to divide the last radio + # button in radio button fieldsets. As per the GOV.UK Design System spec, + # it defaults to 'or'. + # + # * +:default_error_summary_title+ sets the text used in error summary + # blocks. As per the GOV.UK Design System spec, it defaults to + # 'There is a problem'. + # + # * +:localisation_schema_fallback+ sets the prefix elements for the array + # used to build the localisation string. The final two elements are always + # are the object name and attribute name. The _special_ value +__context__+, + # is used as a placeholder for the context (label, fieldset or hint). + # + # * +:localisation_schema_legend+, +:localisation_schema_hint+ and + # +:localisation_schema_label+ each override the schema root for their + # particular context, allowing them to be independently customised. + # === DEFAULTS = { default_legend_size: 'm', default_legend_tag: 'h1', default_submit_button_text: 'Continue', default_radio_divider_text: 'or', - default_error_summary_title: 'There is a problem' + default_error_summary_title: 'There is a problem', + + localisation_schema_fallback: %i(helpers __context__), + localisation_schema_label: nil, + localisation_schema_hint: nil, + localisation_schema_legend: nil }.freeze DEFAULTS.keys.each { |k| config_accessor(k) { DEFAULTS[k] } } diff --git a/lib/govuk_design_system_formbuilder/traits/localisation.rb b/lib/govuk_design_system_formbuilder/traits/localisation.rb index d6f8200f..4bc9a698 100644 --- a/lib/govuk_design_system_formbuilder/traits/localisation.rb +++ b/lib/govuk_design_system_formbuilder/traits/localisation.rb @@ -14,7 +14,27 @@ def localised_text(context) def localisation_key(context) return nil unless @object_name.present? && @attribute_name.present? - ['helpers', context, @object_name, @attribute_name].join('.') + schema(context) + end + + def schema(context) + schema_root(context) + .push(@object_name, @attribute_name) + .map { |e| e == :__context__ ? context : e } + .join('.') + end + + def schema_root(context) + contextual_schema = case context + when :legend + config.localisation_schema_legend + when :hint + config.localisation_schema_hint + when :label + config.localisation_schema_label + end + + (contextual_schema || config.localisation_schema_fallback).dup end end end diff --git a/spec/govuk_design_system_formbuilder/builder/configuration_spec.rb b/spec/govuk_design_system_formbuilder/builder/configuration_spec.rb index f2da5b00..22da5fc6 100644 --- a/spec/govuk_design_system_formbuilder/builder/configuration_spec.rb +++ b/spec/govuk_design_system_formbuilder/builder/configuration_spec.rb @@ -163,5 +163,88 @@ end end end + + describe 'localisation configuration' do + let(:args) { [method, attribute] } + let(:attribute) { :name } + let(:method) { :govuk_text_field } + subject { builder.send(*args) } + + context 'with a custom fallback localisation schema root' do + let(:localisations) { { en: YAML.load_file('spec/support/locales/custom_fallback.en.yaml') } } + let(:label_text) { I18n.t(%i(my_shiny_new_app label widgets person name).join('.')) } + + before do + GOVUKDesignSystemFormBuilder.configure do |conf| + conf.localisation_schema_fallback = %i(my_shiny_new_app __context__ widgets) + end + end + + specify 'should use the configured localisation fallback schema' do + with_localisations(localisations) do + expect(subject).to have_tag('label', text: label_text) + end + end + end + + context 'with custom contextual localisation schema roots' do + let(:localisations) { { en: YAML.load_file('spec/support/locales/custom_contexts.en.yaml') } } + + context 'labels' do + let(:label_text) { I18n.t(%i(a long convoluted schema nested_far_far label too_inconsistently person name).join('.')) } + + before do + GOVUKDesignSystemFormBuilder.configure do |conf| + conf.localisation_schema_label = %i(a long convoluted schema nested_far_far __context__ too_inconsistently) + conf.localisation_schema_fallback = %i(unused) + end + end + + specify 'should use the configured localisation schema for labels' do + with_localisations(localisations) do + expect(subject).to have_tag('label', text: label_text) + end + end + end + + context 'hints' do + let(:hint_text) { I18n.t(%i(a long convoluted schema hint nested_far_too_deeply person name).join('.')) } + + before do + GOVUKDesignSystemFormBuilder.configure do |conf| + conf.localisation_schema_hint = %i(a long convoluted schema __context__ nested_far_too_deeply) + conf.localisation_schema_fallback = %i(unused) + end + end + + specify 'should use the configured localisation schema for hints' do + with_localisations(localisations) do + expect(subject).to have_tag('span', text: hint_text, with: { class: 'govuk-hint' }) + end + end + end + + context 'legends' do + let(:attribute) { :projects } + let(:method) { :govuk_collection_check_boxes } + let(:legend_text) { I18n.t(%i(a long convoluted schema legend nested_far_too_deeply person projects).join('.')) } + + subject { builder.send(method, attribute, projects, :id, :name) } + + before do + GOVUKDesignSystemFormBuilder.configure do |conf| + conf.localisation_schema_legend = %i(a long convoluted schema __context__ nested_far_too_deeply) + conf.localisation_schema_fallback = %i(unused) + end + end + + specify 'should use the configured localisation schema for legends' do + with_localisations(localisations) do + expect(subject).to have_tag('legend', text: legend_text, with: { class: 'govuk-fieldset__legend' }) + end + end + end + end + end end end diff --git a/spec/support/locales/custom_contexts.en.yaml b/spec/support/locales/custom_contexts.en.yaml new file mode 100644 index 00000000..fcd55d96 --- /dev/null +++ b/spec/support/locales/custom_contexts.en.yaml @@ -0,0 +1,18 @@ +a: + long: + convoluted: + schema: + legend: + nested_far_too_deeply: + person: + projects: The things that keep you busiest at work + hint: + nested_far_too_deeply: + person: + name: It should say it on your birth certificate + nested_far_far: + label: + too_inconsistently: + person: + name: Your name, what is it? + projects: What do you do at work? diff --git a/spec/support/locales/custom_fallback.en.yaml b/spec/support/locales/custom_fallback.en.yaml new file mode 100644 index 00000000..4c319841 --- /dev/null +++ b/spec/support/locales/custom_fallback.en.yaml @@ -0,0 +1,5 @@ +my_shiny_new_app: + label: + widgets: + person: + name: Who goes there? From a37bcc15989c61041b3a212916975b5567e6bbb3 Mon Sep 17 00:00:00 2001 From: Peter Yates Date: Sat, 21 Dec 2019 12:55:46 +0000 Subject: [PATCH 4/4] Add documentation on customising locale schemas Refs #80 --- .../content/building-blocks/localisation.slim | 32 +++++++++++++++++++ guide/layouts/partials/example-fig.slim | 21 +++++++++++- guide/lib/examples/localisation.rb | 30 +++++++++++++++++ guide/lib/helpers/link_helpers.rb | 4 +++ guide/lib/helpers/person.rb | 3 +- guide/lib/setup/example_data.rb | 13 ++++++++ 6 files changed, 101 insertions(+), 2 deletions(-) diff --git a/guide/content/building-blocks/localisation.slim b/guide/content/building-blocks/localisation.slim index 91c62b3d..5de2e825 100644 --- a/guide/content/building-blocks/localisation.slim +++ b/guide/content/building-blocks/localisation.slim @@ -36,3 +36,35 @@ section | Note that despite the text attribute being omitted from the label options hash, the other display and formatting parameters can be supplied and work in the normal manner. + + == render('/partials/example-fig.*', + caption: "Customising locale structure", + localisation: custom_locale, + code: role_name, + custom_config: custom_locale_config, + raw_config: custom_locale_config_raw, + hide_html_output: true) do + + p.govuk-body + | There are many approaches to organising localisation data and while the default + will work for most projects, sometimes a different approach can be beneficial. This + is especially true when working with external localisation agencies or when dealing + with large volumes of copy. + + p.govuk-body + | To customise the location of our localisation strings, we can + #{link_to('configure', '/introduction/configuration').html_safe} the schema as + part of the application's initialisation process. + + h4.govuk-heading-s Contexts + + p.govuk-body + | There are three contexts supported by the form builder: label, + legend and hint. Custom locale schemas are + configured using an array of symbols that match your locale structure. + + p.govuk-body + | The special value __context__ is used to represent the + current translation context. It will automatically be replaced with + either label, legend or hint when the + translation key is generated. diff --git a/guide/layouts/partials/example-fig.slim b/guide/layouts/partials/example-fig.slim index 397da38a..2dc6ec72 100644 --- a/guide/layouts/partials/example-fig.slim +++ b/guide/layouts/partials/example-fig.slim @@ -1,3 +1,6 @@ +- if defined?(custom_config) + - custom_config + figure figcaption h3.govuk-heading-m.example-heading id=anchor_id(caption) @@ -17,6 +20,19 @@ figure code.highlight.language-ruby | #{sample_data} + - if defined?(raw_config) + section + h4.govuk-heading-s.example-subheading.locale Configuration + + div.govuk-inset-text + | Place your configuration in an + #{link_to('initializer', rails_initializer_link).html_safe} to ensure + it's loaded every time you start your Rails application. + + pre.example-input + code.highlight.language-ruby + | #{raw_config} + - if defined?(localisation) - I18n.backend.store_translations(:en, YAML.load(localisation)) @@ -27,7 +43,6 @@ figure code.highlight.language-yaml | #{localisation} - section h4.govuk-heading-s.example-subheading.input Input pre.example-input @@ -49,3 +64,7 @@ figure - if defined?(sample_data) - eval(sample_data) = format_slim(code, f: builder(display_errors), **form_data) + + +- if defined?(custom_config) + - reset_config diff --git a/guide/lib/examples/localisation.rb b/guide/lib/examples/localisation.rb index ea7ebf30..f2a15869 100644 --- a/guide/lib/examples/localisation.rb +++ b/guide/lib/examples/localisation.rb @@ -19,5 +19,35 @@ def favourite_kind_of_hat = f.govuk_text_field :favourite_kind_of_hat, label: { size: 'm' } SNIPPET end + + def role_name + <<~SNIPPET + = f.govuk_text_field :role, label: { size: 'm' } + SNIPPET + end + + def custom_locale + <<~LOCALE + helpers: + label: + person: + role: What role do you play? + + copy: + descriptions: + hint: + subdivision: + person: + role: |- + Roles may be achieved or ascribed or they can be accidental + in different situations. An achieved role is a position + that a person assumes voluntarily which reflects personal + skills, abilities, and effort. + LOCALE + end + + def reset_config + GOVUKDesignSystemFormBuilder.reset! + end end end diff --git a/guide/lib/helpers/link_helpers.rb b/guide/lib/helpers/link_helpers.rb index 7f07693a..f2ec945e 100644 --- a/guide/lib/helpers/link_helpers.rb +++ b/guide/lib/helpers/link_helpers.rb @@ -46,6 +46,10 @@ def dfe_rails_boilerplate_link 'https://github.com/DFE-Digital/govuk-rails-boilerplate' end + def rails_initializer_link + 'https://guides.rubyonrails.org/configuring.html#using-initializer-files' + end + def slim_link 'https://slim-lang.com' end diff --git a/guide/lib/helpers/person.rb b/guide/lib/helpers/person.rb index 33d92b1b..e41c5325 100644 --- a/guide/lib/helpers/person.rb +++ b/guide/lib/helpers/person.rb @@ -95,6 +95,7 @@ class Person # localisation attr_accessor( - :favourite_kind_of_hat + :favourite_kind_of_hat, + :role ) end diff --git a/guide/lib/setup/example_data.rb b/guide/lib/setup/example_data.rb index 24db73b5..7c2e025f 100644 --- a/guide/lib/setup/example_data.rb +++ b/guide/lib/setup/example_data.rb @@ -55,6 +55,15 @@ def laptops_data_raw DATA end + def custom_locale_config_raw + <<~CONFIG + GOVUKDesignSystemFormBuilder.configure do |conf| + conf.localisation_schema_fallback = %i(helpers __context__) + conf.localisation_schema_hint = %i(copy descriptions __context__ subdivision) + end + CONFIG + end + # Yes, eval is bad, but when you want to display code in documentation as # well as run it, it's kind of necessary. Not considering this a security # threat as it's only used in the guide 👮 @@ -75,6 +84,10 @@ def primary_colours def laptops eval(laptops_data_raw) end + + def custom_locale_config + eval(custom_locale_config_raw) + end # rubocop:enable Security/Eval def form_data