From 6dbed2a43ac5bfdf7c05ccd67e905f64696412a5 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 5 Jan 2023 15:19:41 +0100 Subject: [PATCH 1/7] add rake task to generate testing locales --- .gitignore | 3 ++ lib/tasks/locale.rake | 64 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 lib/tasks/locale.rake diff --git a/.gitignore b/.gitignore index 90e50453..b71b2d63 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,9 @@ yarn-debug.log* # site configuration /config/justask.yml +# locales generated with `bin/rails locale:generate` +/config/locales/*.en-xx.yml + # local storage /public/export /public/system diff --git a/lib/tasks/locale.rake b/lib/tasks/locale.rake new file mode 100644 index 00000000..fb924b91 --- /dev/null +++ b/lib/tasks/locale.rake @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +namespace :locale do + desc "Generate en-xx locale" + task generate: :environment do + def destroy(val) + val + .gsub("A", "ÅÄ") + .gsub("E", "ÉÊ") + .gsub("I", "ÏÍ") + .gsub("O", "ÖØ") + .gsub("U", "ÜǓ") + .gsub("a", "åä") + .gsub("e", "éê") + .gsub("i", "ïí") + .gsub("o", "öø") + .gsub("u", "üǔ") + end + + def repair(val) + val + .gsub("ÅÄ", "A") + .gsub("ÉÊ", "E") + .gsub("ÏÍ", "I") + .gsub("ÖØ", "O") + .gsub("ÜǓ", "U") + .gsub("åä", "a") + .gsub("éê", "e") + .gsub("ïí", "i") + .gsub("öø", "o") + .gsub("üǔ", "u") + end + + def transform_locale(hash) + hash.transform_values do |val| + next transform_locale(val) if val.is_a? Hash + next val if val.is_a? Symbol + + val = destroy(val) + + # undo damage in %{variables} + val = val.gsub(/%{([^}]+)}/) { repair(_1) } + + # undo damage in + val = val.gsub(/<([^>]+)>/) { repair(_1) } + + "[#{val}]" + end + end + + en_locales = Dir[Rails.root.join("config/locales/*.en.yml")] + + en_locales.each do |locale_path| + destination = locale_path.sub(/\.en\.yml$/, ".en-xx.yml") + puts "* generating #{File.basename(destination)}" + + locale = YAML.load_file(locale_path)["en"] + new_locale = { "en-xx" => transform_locale(locale) } + File.open(destination, "w") do |f| + f.puts new_locale.to_yaml + end + end + end +end From 8323f39ecd0329e448351c95de37148eafaef1a8 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 5 Jan 2023 15:20:24 +0100 Subject: [PATCH 2/7] re-add locale switching logic based on https://guides.rubyonrails.org/i18n.html\#managing-the-locale-across-requests --- app/controllers/application_controller.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 530d7b94..b794daaa 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,17 +6,23 @@ class ApplicationController < ActionController::Base before_action :sentry_user_context before_action :configure_permitted_parameters, if: :devise_controller? - before_action :check_locale + around_action :switch_locale before_action :banned? before_action :find_active_announcements # check if user wants to read - def check_locale - return I18n.locale = "en" if Rails.env.test? + def switch_locale(&action) + return I18n.with_locale("en", &action) if Rails.env.test? - I18n.locale = "en" + locale = params[:lang] || current_user&.locale || cookies[:lang] || "en" + if params[:lang] && current_user.present? + current_user.locale = locale + current_user.save + end - cookies[:lang] = I18n.locale + cookies[:lang] = locale + + I18n.with_locale(locale, &action) end # check if user got hit by the banhammer of doom From fc62e2ddb211f1a5e42d00fcf31ada1093adc173 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 5 Jan 2023 15:42:56 +0100 Subject: [PATCH 3/7] fix english --- config/locales/views.en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index b498e58e..a631a7c3 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -27,9 +27,9 @@ en: header: "Share your answers" body_html: |

Want your followers on another platform to see your %{app_name} answers? - You can configure automatic sharing to your favorite platforms easily.

+ You can configure automatic sharing to your favourite platforms easily.

-

Not sure if it's a favorite, but at the moment only +

Not sure if it's a favourite, but at the moment only Twitter is supported.

customize: header: "Customise your experience" From ebcf9d76764106620a9d59dd7672f6c5418e3778 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 6 Jan 2023 10:01:14 +0100 Subject: [PATCH 4/7] obey the dog --- app/controllers/application_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b794daaa..718f760e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,8 +11,8 @@ class ApplicationController < ActionController::Base before_action :find_active_announcements # check if user wants to read - def switch_locale(&action) - return I18n.with_locale("en", &action) if Rails.env.test? + def switch_locale(&) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity + return I18n.with_locale("en", &) if Rails.env.test? locale = params[:lang] || current_user&.locale || cookies[:lang] || "en" if params[:lang] && current_user.present? @@ -22,7 +22,7 @@ class ApplicationController < ActionController::Base cookies[:lang] = locale - I18n.with_locale(locale, &action) + I18n.with_locale(locale, &) end # check if user got hit by the banhammer of doom From bbdc3ac652fddeb1d360c92b832df8ed5a80c9e1 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 6 Jan 2023 12:56:20 +0100 Subject: [PATCH 5/7] tasks/locale: define substituted characters in a hash reducing code by adding moar! --- lib/tasks/locale.rake | 60 +++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/lib/tasks/locale.rake b/lib/tasks/locale.rake index fb924b91..049305e0 100644 --- a/lib/tasks/locale.rake +++ b/lib/tasks/locale.rake @@ -1,48 +1,46 @@ # frozen_string_literal: true +module TestLocaleTransformer + SUBSTITUTIONS = { + "a" => "åä", + "e" => "éê", + "i" => "ïí", + "o" => "öø", + "u" => "üǔ" + }.freeze + + refine String do + def test_locale_destroy + SUBSTITUTIONS.inject(self) do |val, (from, to)| + val.gsub(from, to).gsub(from.upcase, to.upcase) + end + end + + def test_locale_repair + SUBSTITUTIONS.inject(self) do |val, (from, to)| + val.gsub(to, from).gsub(to.upcase, from.upcase) + end + end + end +end + +using TestLocaleTransformer + namespace :locale do desc "Generate en-xx locale" task generate: :environment do - def destroy(val) - val - .gsub("A", "ÅÄ") - .gsub("E", "ÉÊ") - .gsub("I", "ÏÍ") - .gsub("O", "ÖØ") - .gsub("U", "ÜǓ") - .gsub("a", "åä") - .gsub("e", "éê") - .gsub("i", "ïí") - .gsub("o", "öø") - .gsub("u", "üǔ") - end - - def repair(val) - val - .gsub("ÅÄ", "A") - .gsub("ÉÊ", "E") - .gsub("ÏÍ", "I") - .gsub("ÖØ", "O") - .gsub("ÜǓ", "U") - .gsub("åä", "a") - .gsub("éê", "e") - .gsub("ïí", "i") - .gsub("öø", "o") - .gsub("üǔ", "u") - end - def transform_locale(hash) hash.transform_values do |val| next transform_locale(val) if val.is_a? Hash next val if val.is_a? Symbol - val = destroy(val) + val = val.test_locale_destroy # undo damage in %{variables} - val = val.gsub(/%{([^}]+)}/) { repair(_1) } + val = val.gsub(/%{([^}]+)}/, &:test_locale_repair) # undo damage in - val = val.gsub(/<([^>]+)>/) { repair(_1) } + val = val.gsub(/<([^>]+)>/, &:test_locale_repair) "[#{val}]" end From 68f5fad5f850ca696f08e3a9d25c0acd8e60e8d9 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 6 Jan 2023 13:00:30 +0100 Subject: [PATCH 6/7] tasks/locale: add some more substitutions Co-authored-by: Karina Kwiatek --- lib/tasks/locale.rake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/tasks/locale.rake b/lib/tasks/locale.rake index 049305e0..fa58f5c0 100644 --- a/lib/tasks/locale.rake +++ b/lib/tasks/locale.rake @@ -5,8 +5,12 @@ module TestLocaleTransformer "a" => "åä", "e" => "éê", "i" => "ïí", + "n" => "ñ", "o" => "öø", - "u" => "üǔ" + "r" => "ř", + "u" => "üǔ", + "y" => "ÿ", + "z" => "ż" }.freeze refine String do From 5b8e34aa1d441e11ca313f73c1386dc4ba40c660 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 6 Jan 2023 13:37:22 +0100 Subject: [PATCH 7/7] add integration specs for changing locales --- app/controllers/application_controller.rb | 4 +- spec/integration/locale_switch_spec.rb | 85 +++++++++++++++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 spec/integration/locale_switch_spec.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 718f760e..05b7ad28 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,9 +11,7 @@ class ApplicationController < ActionController::Base before_action :find_active_announcements # check if user wants to read - def switch_locale(&) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity - return I18n.with_locale("en", &) if Rails.env.test? - + def switch_locale(&) locale = params[:lang] || current_user&.locale || cookies[:lang] || "en" if params[:lang] && current_user.present? current_user.locale = locale diff --git a/spec/integration/locale_switch_spec.rb b/spec/integration/locale_switch_spec.rb new file mode 100644 index 00000000..47ea9dfa --- /dev/null +++ b/spec/integration/locale_switch_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "rails_helper" +require "nokogiri" + +describe "locale switching", type: :request do + matcher :have_html_lang do |expected_lang| + description { %(have the HTML "lang" attribute set to #{expected_lang.inspect}) } + + match do |result| + Nokogiri::HTML.parse(result).css("html[lang=#{expected_lang}]").size == 1 + end + end + + context "when user not signed in" do + it "uses the default :en locale" do + get "/" + expect(response.body).to have_html_lang "en" + expect(response.cookies["lang"]).to eq "en" + end + + context "when ?lang param is given" do + it "changes the locale" do + # 1. ensure we start with the default :en locale + get "/" + expect(response.body).to have_html_lang "en" + expect(response.cookies["lang"]).to eq "en" + + # 2. switch the language to en-xx + get "/?lang=en-xx" + expect(response.body).to have_html_lang "en-xx" + expect(response.cookies["lang"]).to eq "en-xx" + + # 3. remove the language parameter again + get "/" + expect(response.body).to have_html_lang "en-xx" + expect(response.cookies["lang"]).to be_nil # no new cookie here, it's already en-xx + end + end + end + + context "when user is signed in" do + let(:user) { FactoryBot.create(:user, password: "test1234", locale:) } + let(:locale) { "en" } + + before do + post "/sign_in", params: { user: { login: user.email, password: user.password } } + end + + it "uses the en locale" do + get "/" + expect(response.body).to have_html_lang "en" + expect(response.cookies["lang"]).to be_nil # no new cookie here, already set by the sign in + end + + context "when ?lang param is given" do + it "changes the locale" do + # 1. ensure we start with the :en locale + get "/" + expect(response.body).to have_html_lang "en" + expect(response.cookies["lang"]).to be_nil # no new cookie here, already set by the sign in + + # 2. switch the language to en-xx + expect { get "/?lang=en-xx" }.to change { user.reload.locale }.from("en").to("en-xx") + expect(response.body).to have_html_lang "en-xx" + expect(response.cookies["lang"]).to eq "en-xx" + + # 3. remove the language parameter again + get "/" + expect(response.body).to have_html_lang "en-xx" + expect(response.cookies["lang"]).to be_nil # no new cookie here, it's already en-xx + end + end + + context "when user has a different locale set" do + let(:locale) { "fi" } + + it "uses the different locale" do + get "/" + expect(response.body).to have_html_lang "fi" + expect(response.cookies["lang"]).to be_nil # no new cookie here, already set by the sign in + end + end + end +end