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