From f164aaecbf74d857975abe4cb05872def33e465c Mon Sep 17 00:00:00 2001 From: Ian Dickinson Date: Tue, 4 Aug 2020 10:41:54 +0100 Subject: [PATCH] Add language switching mechanism Continuing to add support for #233, this commit adds a language switcher element to the secondary navigation bar. The base application controller now inspects the user's language choice, whether by URL parameter or by HTTP header, and sets the `I18n.locale` accordingly. --- Gemfile | 1 + Gemfile.lock | 2 + app/assets/stylesheets/_ukhpi-header.scss | 6 +++ app/controllers/application_controller.rb | 15 ++++++ app/models/concerns/user_language.rb | 16 +++++++ app/models/user_compare_selections.rb | 2 + app/models/user_selections.rb | 3 ++ app/presenters/compare_locations_presenter.rb | 1 + app/services/language_selector.rb | 5 ++ app/views/common/_header.html.haml | 17 +++++-- app/views/layouts/application.html.haml | 2 +- config/locales/cy.yml | 6 +++ test/models/user_selections_test.rb | 47 +++++++++++++++++++ 13 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 app/models/concerns/user_language.rb create mode 100644 app/services/language_selector.rb create mode 100644 config/locales/cy.yml diff --git a/Gemfile b/Gemfile index ed066b33..29ad9db0 100644 --- a/Gemfile +++ b/Gemfile @@ -32,6 +32,7 @@ gem 'sentry-raven' gem 'faraday' gem 'faraday_middleware' +gem 'http_accept_language' gem 'puma' gem 'yajl-ruby', require: 'yajl' diff --git a/Gemfile.lock b/Gemfile.lock index fc0afb11..2ba1835d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,6 +134,7 @@ GEM haml (>= 4.0, < 6) nokogiri (>= 1.6.0) ruby_parser (~> 3.5) + http_accept_language (2.1.1) i18n (1.8.5) concurrent-ruby (~> 1.0) js-routes (1.4.9) @@ -322,6 +323,7 @@ DEPENDENCIES govuk_template haml-lint haml-rails + http_accept_language js-routes json_expressions m diff --git a/app/assets/stylesheets/_ukhpi-header.scss b/app/assets/stylesheets/_ukhpi-header.scss index d03e7eea..7255267d 100644 --- a/app/assets/stylesheets/_ukhpi-header.scss +++ b/app/assets/stylesheets/_ukhpi-header.scss @@ -40,3 +40,9 @@ .u-active { border-bottom: 2px solid $black; } + +.o-secondary-banner { + display: flex; + flex-direction: row; + justify-content: space-between; +} \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ad5ade97..e4f5e1f6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,4 +5,19 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + + before_action :set_locale + + private + + # Set the user's preferred locale. An explicit locale set via + # the URL param `lang` is preeminent, otherwise we look to the + # user's preferred language specified via browser headers + def set_locale + user_locale = + params['lang'] || + http_accept_language.compatible_language_from(I18n.available_locales) + + I18n.locale = user_locale if user_locale + end end diff --git a/app/models/concerns/user_language.rb b/app/models/concerns/user_language.rb new file mode 100644 index 00000000..f6461a7a --- /dev/null +++ b/app/models/concerns/user_language.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Shared code for working with user lanuage choices +module UserLanguage + def english? + I18n.locale != :cy + end + + def welsh? + I18n.locale == :cy + end + + def alternative_language_params + with('lang', english? ? 'cy' : 'en') + end +end diff --git a/app/models/user_compare_selections.rb b/app/models/user_compare_selections.rb index 3f74e35c..1388bfa0 100644 --- a/app/models/user_compare_selections.rb +++ b/app/models/user_compare_selections.rb @@ -4,6 +4,7 @@ # each other class UserCompareSelections include UserChoices + include UserLanguage DEFAULT_INDICATOR = 'hpi' DEFAULT_STATISTIC = 'all' @@ -16,6 +17,7 @@ class UserCompareSelections 'to' => Struct::UserParam.new(Time.zone.today, false, nil), 'st' => Struct::UserParam.new(DEFAULT_STATISTIC, false, nil), 'in' => Struct::UserParam.new(DEFAULT_INDICATOR, false, nil), + 'lang' => Struct::UserParam.new(:en, false, nil), # used by selections update form 'form-action' => Struct::UserParam.new(nil, false, nil), diff --git a/app/models/user_selections.rb b/app/models/user_selections.rb index 7f2767c7..ef7ca329 100644 --- a/app/models/user_selections.rb +++ b/app/models/user_selections.rb @@ -11,6 +11,7 @@ class UserSelections # rubocop:disable Metrics/ClassLength include UserChoices include UserSelectionValidations + include UserLanguage DEFAULT_INDICATORS = %w[hpi avg pmc pac].freeze DEFAULT_STATISTICS = %w[all].freeze @@ -18,6 +19,7 @@ class UserSelections # rubocop:disable Metrics/ClassLength DEFAULT_REGION = 'http://landregistry.data.gov.uk/id/region/united-kingdom' DEFAULT_REGION_TYPE = 'country' DEFAULT_THEMES = %w[property_type volume].freeze + DEFAULT_LANGUAGE = 'en' USER_PARAMS_MODEL = { 'location' => Struct::UserParam.new(DEFAULT_REGION, false, nil), @@ -31,6 +33,7 @@ class UserSelections # rubocop:disable Metrics/ClassLength 'st' => Struct::UserParam.new(DEFAULT_STATISTICS, true, nil), 'in' => Struct::UserParam.new(DEFAULT_INDICATORS, true, nil), 'thm' => Struct::UserParam.new(DEFAULT_THEMES, true, nil), + 'lang' => Struct::UserParam.new(DEFAULT_LANGUAGE, false, nil), # used by selections update form 'form-action' => Struct::UserParam.new(nil, false, nil), diff --git a/app/presenters/compare_locations_presenter.rb b/app/presenters/compare_locations_presenter.rb index 4cc3bffc..8c3334fb 100644 --- a/app/presenters/compare_locations_presenter.rb +++ b/app/presenters/compare_locations_presenter.rb @@ -9,6 +9,7 @@ class CompareLocationsPresenter # rubocop:disable Metrics/ClassLength include LocationsTable attr_reader :user_compare_selections, :query_results + alias user_selections user_compare_selections def initialize(user_compare_selections, query_results) @user_compare_selections = user_compare_selections diff --git a/app/services/language_selector.rb b/app/services/language_selector.rb new file mode 100644 index 00000000..194500dd --- /dev/null +++ b/app/services/language_selector.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Shared service for managing user language selection +class LanguageSelector +end \ No newline at end of file diff --git a/app/views/common/_header.html.haml b/app/views/common/_header.html.haml index 51c1d296..3a498cac 100644 --- a/app/views/common/_header.html.haml +++ b/app/views/common/_header.html.haml @@ -33,9 +33,20 @@ .o-lr-top-bar .container.o-container - .o-beta-banner - %p + .o-secondary-banner + .o-secondary-banner__phase-tag %strong.o-beta-tag = t('common.header.beta_tag') %span.text-muted - = t('common.header.feedback_request').html_safe \ No newline at end of file + = t('common.header.feedback_request').html_safe + + .o-secondary-banner__lang-select + - if @view_state.user_selections.english? + English + - else + = link_to('English', @view_state.user_selections.alternative_language_params.params) + | + - if @view_state.user_selections.welsh? + Cymraeg + - else + = link_to('Cymraeg', @view_state.user_selections.alternative_language_params.params) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index a34fb717..3bd18c41 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -38,7 +38,7 @@ = render partial: 'common/flash_message' %main.container.o-container - .o-beta-banner + .o-secondary-banner %p %strong.o-beta-tag BETA %span.text-muted diff --git a/config/locales/cy.yml b/config/locales/cy.yml new file mode 100644 index 00000000..9a0f7758 --- /dev/null +++ b/config/locales/cy.yml @@ -0,0 +1,6 @@ +cy: + common: + header: + home_link: "!!! Go to the UK HPI homepage" + app_title: "!!! UK House Price Index" + beta_tag: "!!! BETA" diff --git a/test/models/user_selections_test.rb b/test/models/user_selections_test.rb index deec2e22..d7987b65 100644 --- a/test/models/user_selections_test.rb +++ b/test/models/user_selections_test.rb @@ -230,5 +230,52 @@ class UserSelectionsTest < ActiveSupport::TestCase _(selections.valid?).must_equal(false) end end + + describe 'language handling' do + it 'should return English as the default' do + selections = user_selections({}) + assert selections.english? + assert_not selections.welsh? + end + + it 'should return Welsh when that language is selected' do + selections = user_selections('lang' => 'cy') + assert_not selections.english? + assert selections.welsh? + end + + it 'should return English when that language is selected' do + selections = user_selections('lang' => 'en') + assert selections.english? + assert_not selections.welsh? + end + + it 'should ignore other languages' do + selections = user_selections('lang' => 'fr') + assert selections.english? + assert_not selections.welsh? + end + + it 'should generate the correct Welsh language options' do + selections = user_selections( + 'from' => '2017-01' + ) + + alt_params = selections.alternative_language_params + _(alt_params.params['from']).must_equal('2017-01') + _(alt_params.params['lang']).must_equal('cy') + end + + it 'should generate the correct English language options' do + selections = user_selections( + 'from' => '2017-01', + 'lang' => 'cy' + ) + + alt_params = selections.alternative_language_params + _(alt_params.params['from']).must_equal('2017-01') + _(alt_params.params['lang']).must_equal('en') + end + end end end