From 5ae649ae0dd91482a162e34e74d186cab0a5adff Mon Sep 17 00:00:00 2001 From: secretpray Date: Sun, 17 Dec 2023 21:58:57 +0200 Subject: [PATCH 1/5] add language dropdown --- Gemfile.lock | 4 ++++ app/assets/images/icons/ru.svg | 7 +++++++ app/assets/images/icons/us.svg | 9 +++++++++ app/controllers/application_controller.rb | 15 +++++++++++++++ app/controllers/pages_controller.rb | 23 +++++++++++++++++++++++ app/helpers/navbar_helper.rb | 20 ++++++++++++++++++++ app/views/layouts/_header.html.slim | 13 ++++++++++++- config/routes.rb | 2 ++ db/schema.rb | 6 +++--- 9 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 app/assets/images/icons/ru.svg create mode 100644 app/assets/images/icons/us.svg create mode 100644 app/helpers/navbar_helper.rb diff --git a/Gemfile.lock b/Gemfile.lock index 5f93136..5d56ca7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -191,6 +191,8 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.9) + nokogiri (1.15.4-x86_64-darwin) + racc (~> 1.4) nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) @@ -340,6 +342,7 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) + sqlite3 (1.6.4-x86_64-darwin) sqlite3 (1.6.4-x86_64-linux) stimulus-rails (1.2.2) railties (>= 6.0.0) @@ -375,6 +378,7 @@ GEM zeitwerk (2.6.11) PLATFORMS + x86_64-darwin-22 x86_64-linux DEPENDENCIES diff --git a/app/assets/images/icons/ru.svg b/app/assets/images/icons/ru.svg new file mode 100644 index 0000000..485c24e --- /dev/null +++ b/app/assets/images/icons/ru.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/assets/images/icons/us.svg b/app/assets/images/icons/us.svg new file mode 100644 index 0000000..a722047 --- /dev/null +++ b/app/assets/images/icons/us.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7b0f46e..9c2ac5c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,6 @@ class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? + before_action :set_locale rescue_from ActionPolicy::Unauthorized do |_e| redirect_to root_path @@ -11,4 +12,18 @@ def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: %i[name avatar]) devise_parameter_sanitizer.permit(:account_update, keys: %i[name avatar]) end + + def set_locale + return if locale == I18n.locale + + I18n.locale = locale + end + + def locale + @locale ||= if user_signed_in? + current_user.locale || I18n.default_locale + else + session[:locale] || I18n.default_locale + end + end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index a15ef40..0f2cb6f 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -1,3 +1,26 @@ class PagesController < ApplicationController def welcome; end + + def switch_locale + params_locale = params[:locale].to_sym + locale = I18n.available_locales.include?(params_locale) ? params_locale : default_locale + I18n.locale = locale + store_locale + + redirect_back(fallback_location: root_path) + end + + private + + def default_locale + @default_locale ||= current_user&.locale || I18n.default_locale + end + + def store_locale + if user_signed_in? + current_user.update(locale: I18n.locale) + else + session[:locale] = I18n.locale + end + end end diff --git a/app/helpers/navbar_helper.rb b/app/helpers/navbar_helper.rb new file mode 100644 index 0000000..6410e03 --- /dev/null +++ b/app/helpers/navbar_helper.rb @@ -0,0 +1,20 @@ +module NavbarHelper + def selected_language_name + Rails.cache.fetch("selected_language_name/#{I18n.locale}", expires_in: 1.day) do + case I18n.locale + when :en + image_tag "icons/us.svg", width: 20, height: 20, class: "me-2" + when :ru + image_tag "icons/ru.svg", width: 20, height: 20, class: "me-2" + end + end + end + + def ru_language_item + I18n.locale == :ru ? "Русский" : "Russian" + end + + def en_language_item + I18n.locale == :ru ? "Английский" : "English" + end +end \ No newline at end of file diff --git a/app/views/layouts/_header.html.slim b/app/views/layouts/_header.html.slim index e1f063e..90e6211 100644 --- a/app/views/layouts/_header.html.slim +++ b/app/views/layouts/_header.html.slim @@ -13,7 +13,18 @@ nav.navbar.navbar-expand-lg.bg-body-tertiary - if user_signed_in? li.nav-item = link_to "My Vacancies", my_vacancies_vacancies_path, class: "nav-link fs-4 text-black" - ul.navbar-nav.ms-3 + ul.navbar-nav.ms-3.gap-2 + li.nav-item.dropdown[data-turbo="false"] + = link_to selected_language_name, "#", class: "dropdown-toggle btn btn-outline-primary border-0", id: 'languageDropdown', role: "button", "data-bs-toggle" => "dropdown", "aria-expanded" => "false" + .dropdown-menu[aria-labelledby="languageDropdown"] + = link_to switch_locale_path(locale: :ru), class: "dropdown-item d-flex align-items-center #{'disabled' if I18n.locale == :ru}" do + = image_tag "icons/ru.svg", width: 20, height: 20, class: "me-2 #{'opacity-50' if I18n.locale == :ru}" + = ru_language_item + .li.hr.dropdown-divider + = link_to switch_locale_path(locale: :en), class: "dropdown-item d-flex align-items-center #{'disabled' if I18n.locale == :en}" do + = image_tag "icons/us.svg", width: 20, height: 20, class: "me-2 #{'opacity-50' if I18n.locale == :en}" + = en_language_item + li.nav-item.dropdown - if user_signed_in? = link_to current_user.name? ? current_user.name : current_user.email, "#", class: "dropdown-toggle btn btn-outline-primary", id: "navbarDropdown", role: "button", "data-bs-toggle" => "dropdown", "aria-expanded" => "false" diff --git a/config/routes.rb b/config/routes.rb index 0537a94..a1239f6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,4 +6,6 @@ resources :vacancies do get :my_vacancies, on: :collection end + + get '/switch_locale/:locale', to: 'pages#switch_locale', as: :switch_locale end diff --git a/db/schema.rb b/db/schema.rb index f55922e..2de1faa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -24,8 +24,8 @@ create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false - t.integer "record_id", null: false - t.integer "blob_id", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false t.datetime "created_at", null: false t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true @@ -44,7 +44,7 @@ end create_table "active_storage_variant_records", force: :cascade do |t| - t.integer "blob_id", null: false + t.bigint "blob_id", null: false t.string "variation_digest", null: false t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true end From 5103d689d9108ecc7cdd691c812d3e1a6435aea5 Mon Sep 17 00:00:00 2001 From: secretpray Date: Fri, 22 Dec 2023 23:59:27 +0200 Subject: [PATCH 2/5] fixed by git comment, ref current_local --- app/assets/images/icons/{us.svg => en.svg} | 0 app/controllers/application_controller.rb | 28 ++++++++++++---------- app/controllers/pages_controller.rb | 4 ++-- app/helpers/navbar_helper.rb | 21 +++++----------- app/models/current.rb | 3 +++ app/views/layouts/_header.html.slim | 15 ++++-------- config/locales/en.yml | 2 ++ config/locales/ru.yml | 3 +++ config/routes.rb | 2 +- 9 files changed, 36 insertions(+), 42 deletions(-) rename app/assets/images/icons/{us.svg => en.svg} (100%) create mode 100644 app/models/current.rb diff --git a/app/assets/images/icons/us.svg b/app/assets/images/icons/en.svg similarity index 100% rename from app/assets/images/icons/us.svg rename to app/assets/images/icons/en.svg diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9c2ac5c..3246868 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,29 +1,31 @@ class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? + delegate :locale, to: :current + helper_method :current_locale before_action :set_locale rescue_from ActionPolicy::Unauthorized do |_e| redirect_to root_path end - private - - def configure_permitted_parameters - devise_parameter_sanitizer.permit(:sign_up, keys: %i[name avatar]) - devise_parameter_sanitizer.permit(:account_update, keys: %i[name avatar]) + def current_locale + Current.locale ||= if user_signed_in? + current_user.locale || I18n.default_locale + else + session[:locale] || I18n.default_locale + end end def set_locale - return if locale == I18n.locale + return if current_locale == I18n.locale - I18n.locale = locale + I18n.locale = current_locale end - def locale - @locale ||= if user_signed_in? - current_user.locale || I18n.default_locale - else - session[:locale] || I18n.default_locale - end + private + + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:sign_up, keys: %i[name avatar]) + devise_parameter_sanitizer.permit(:account_update, keys: %i[name avatar]) end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 0f2cb6f..6f3b13d 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -2,8 +2,8 @@ class PagesController < ApplicationController def welcome; end def switch_locale - params_locale = params[:locale].to_sym - locale = I18n.available_locales.include?(params_locale) ? params_locale : default_locale + params_locale = params[:locale].to_sym + locale = I18n.available_locales.include?(params_locale) ? params_locale : default_locale I18n.locale = locale store_locale diff --git a/app/helpers/navbar_helper.rb b/app/helpers/navbar_helper.rb index 6410e03..0561a10 100644 --- a/app/helpers/navbar_helper.rb +++ b/app/helpers/navbar_helper.rb @@ -1,20 +1,11 @@ module NavbarHelper - def selected_language_name - Rails.cache.fetch("selected_language_name/#{I18n.locale}", expires_in: 1.day) do - case I18n.locale - when :en - image_tag "icons/us.svg", width: 20, height: 20, class: "me-2" - when :ru - image_tag "icons/ru.svg", width: 20, height: 20, class: "me-2" - end + def selected_language_icon + Rails.cache.fetch("selected_language_name/#{current_locale}", expires_in: 1.day) do + image_tag "icons/#{current_locale}.svg", size: "24", title: t("navbar.language") end end - def ru_language_item - I18n.locale == :ru ? "Русский" : "Russian" + def next_locale + I18n.locale == :ru ? :en : :ru end - - def en_language_item - I18n.locale == :ru ? "Английский" : "English" - end -end \ No newline at end of file +end diff --git a/app/models/current.rb b/app/models/current.rb new file mode 100644 index 0000000..6a5d5b9 --- /dev/null +++ b/app/models/current.rb @@ -0,0 +1,3 @@ +class Current < ActiveSupport::CurrentAttributes + attribute :locale +end diff --git a/app/views/layouts/_header.html.slim b/app/views/layouts/_header.html.slim index 90e6211..1f948cf 100644 --- a/app/views/layouts/_header.html.slim +++ b/app/views/layouts/_header.html.slim @@ -14,17 +14,10 @@ nav.navbar.navbar-expand-lg.bg-body-tertiary li.nav-item = link_to "My Vacancies", my_vacancies_vacancies_path, class: "nav-link fs-4 text-black" ul.navbar-nav.ms-3.gap-2 - li.nav-item.dropdown[data-turbo="false"] - = link_to selected_language_name, "#", class: "dropdown-toggle btn btn-outline-primary border-0", id: 'languageDropdown', role: "button", "data-bs-toggle" => "dropdown", "aria-expanded" => "false" - .dropdown-menu[aria-labelledby="languageDropdown"] - = link_to switch_locale_path(locale: :ru), class: "dropdown-item d-flex align-items-center #{'disabled' if I18n.locale == :ru}" do - = image_tag "icons/ru.svg", width: 20, height: 20, class: "me-2 #{'opacity-50' if I18n.locale == :ru}" - = ru_language_item - .li.hr.dropdown-divider - = link_to switch_locale_path(locale: :en), class: "dropdown-item d-flex align-items-center #{'disabled' if I18n.locale == :en}" do - = image_tag "icons/us.svg", width: 20, height: 20, class: "me-2 #{'opacity-50' if I18n.locale == :en}" - = en_language_item - + li.nav-item[data-turbo="false"] + = link_to switch_locale_path(locale: next_locale), class: "btn btn-outline-primary border-0 me-2" do + = selected_language_icon + li.nav-item.dropdown - if user_signed_in? = link_to current_user.name? ? current_user.name : current_user.email, "#", class: "dropdown-toggle btn btn-outline-primary", id: "navbarDropdown", role: "button", "data-bs-toggle" => "dropdown", "aria-expanded" => "false" diff --git a/config/locales/en.yml b/config/locales/en.yml index 8ca56fc..8bd27a9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -31,3 +31,5 @@ en: hello: "Hello world" + navbar: + language: Change language diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 81161ed..c351439 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -269,3 +269,6 @@ ru: long: "%d %B %Y, %H:%M" short: "%d %b, %H:%M" pm: вечера + navbar: + language: Смена языка + diff --git a/config/routes.rb b/config/routes.rb index a1239f6..4f43680 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,5 +7,5 @@ get :my_vacancies, on: :collection end - get '/switch_locale/:locale', to: 'pages#switch_locale', as: :switch_locale + get "/switch_locale/:locale", to: "pages#switch_locale", as: :switch_locale end From b7e760da673d79f1a8486d624755c9edcf0ff660 Mon Sep 17 00:00:00 2001 From: secretpray Date: Sat, 23 Dec 2023 00:51:06 +0200 Subject: [PATCH 3/5] add rspec test, check with linters --- spec/requests/pages_controller_spec.rb | 72 ++++++++++++++++++++++++++ spec/requests/pages_spec.rb | 8 --- 2 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 spec/requests/pages_controller_spec.rb delete mode 100644 spec/requests/pages_spec.rb diff --git a/spec/requests/pages_controller_spec.rb b/spec/requests/pages_controller_spec.rb new file mode 100644 index 0000000..c4766ba --- /dev/null +++ b/spec/requests/pages_controller_spec.rb @@ -0,0 +1,72 @@ +require "rails_helper" + +describe PagesController, type: :request do + describe "GET /welcome" do + it "returns http status success" do + get root_url + expect(response).to have_http_status(:success) + end + end + + describe "GET #switch_locale" do + let(:user) { create(:user) } + + context "when user is signed in" do + before { sign_in user } + + it "changes the I18n locale" do + get switch_locale_path(locale: "ru") + expect(I18n.locale).to eq(:ru) + end + + it "changes the user locale" do + get switch_locale_path(locale: "ru") + expect(user.reload.locale).to eq("ru") + end + + it "clears the session locale" do + get switch_locale_path(locale: "ru") + expect(session[:locale]).to be_nil + end + + it "redirects back" do + get switch_locale_path(locale: "ru") + expect(response).to redirect_to(root_path) + end + end + + context "when user is not signed in" do + it "changes the I18n locale" do + get switch_locale_path(locale: "ru") + expect(I18n.locale).to eq(:ru) + end + + it "changes the session locale" do + get switch_locale_path(locale: "ru") + expect(session[:locale]).to eq(:ru) + end + + it "redirects back" do + get switch_locale_path(locale: "ru") + expect(response).to redirect_to(root_path) + end + end + + context "when the requested locale is not available" do + it "falls back to the default I18n locale" do + get switch_locale_path(locale: "invalid_locale") + expect(I18n.locale).to eq(:en) + end + + it "falls back to the default session locale" do + get switch_locale_path(locale: "invalid_locale") + expect(session[:locale]).to eq(:en) + end + + it "redirects back" do + get switch_locale_path(locale: "invalid_locale") + expect(response).to redirect_to(root_path) + end + end + end +end diff --git a/spec/requests/pages_spec.rb b/spec/requests/pages_spec.rb deleted file mode 100644 index 7d39ee9..0000000 --- a/spec/requests/pages_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "rails_helper" - -describe "GET /welcome" do - it "returns http status success" do - get root_url - expect(response).to have_http_status(:success) - end -end From 483fc0098a0bc80bd2e1579de389f76dc79702ea Mon Sep 17 00:00:00 2001 From: secretpray Date: Sat, 23 Dec 2023 07:07:44 +0200 Subject: [PATCH 4/5] small refactoring, remove unused --- app/controllers/application_controller.rb | 29 ++++++++++++----------- app/controllers/pages_controller.rb | 13 +++++++--- app/models/current.rb | 2 +- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3246868..4053b6a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,25 +1,14 @@ class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? - delegate :locale, to: :current + before_action :set_current_locale helper_method :current_locale - before_action :set_locale rescue_from ActionPolicy::Unauthorized do |_e| redirect_to root_path end - def current_locale - Current.locale ||= if user_signed_in? - current_user.locale || I18n.default_locale - else - session[:locale] || I18n.default_locale - end - end - - def set_locale - return if current_locale == I18n.locale - - I18n.locale = current_locale + def set_current_locale + Current.locale = extract_locale_from_user || extract_locale_from_session || I18n.default_locale end private @@ -28,4 +17,16 @@ def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: %i[name avatar]) devise_parameter_sanitizer.permit(:account_update, keys: %i[name avatar]) end + + def current_locale + Current.locale + end + + def extract_locale_from_user + current_user&.locale if defined?(current_user) && current_user&.locale + end + + def extract_locale_from_session + session[:locale] + end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 6f3b13d..51f948e 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -2,9 +2,8 @@ class PagesController < ApplicationController def welcome; end def switch_locale - params_locale = params[:locale].to_sym - locale = I18n.available_locales.include?(params_locale) ? params_locale : default_locale - I18n.locale = locale + Current.locale = locale + I18n.locale = Current.locale store_locale redirect_back(fallback_location: root_path) @@ -12,10 +11,18 @@ def switch_locale private + def params_locale + params[:locale].to_sym + end + def default_locale @default_locale ||= current_user&.locale || I18n.default_locale end + def locale + I18n.available_locales.include?(params_locale) ? params_locale : default_locale + end + def store_locale if user_signed_in? current_user.update(locale: I18n.locale) diff --git a/app/models/current.rb b/app/models/current.rb index 6a5d5b9..43c511a 100644 --- a/app/models/current.rb +++ b/app/models/current.rb @@ -1,3 +1,3 @@ class Current < ActiveSupport::CurrentAttributes - attribute :locale + thread_mattr_accessor :locale end From fbfa30f6c2c31cbfffb745b6c8d3ba0ad5c24f11 Mon Sep 17 00:00:00 2001 From: secretpray Date: Tue, 26 Dec 2023 22:55:36 +0200 Subject: [PATCH 5/5] add user display name --- app/helpers/navbar_helper.rb | 2 +- app/models/user.rb | 19 +++++++++++++++++++ app/views/layouts/_header.html.slim | 8 ++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/helpers/navbar_helper.rb b/app/helpers/navbar_helper.rb index 0561a10..5753c44 100644 --- a/app/helpers/navbar_helper.rb +++ b/app/helpers/navbar_helper.rb @@ -1,7 +1,7 @@ module NavbarHelper def selected_language_icon Rails.cache.fetch("selected_language_name/#{current_locale}", expires_in: 1.day) do - image_tag "icons/#{current_locale}.svg", size: "24", title: t("navbar.language") + image_tag "icons/#{current_locale}.svg", size: "23", class: "rounded-2", title: t("navbar.language") end end diff --git a/app/models/user.rb b/app/models/user.rb index 7ba6348..9d3bdc4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -30,10 +30,29 @@ class User < ApplicationRecord foreign_key: :recipient_id, dependent: :destroy, inverse_of: :recipient enum :role, %i[applicant company moderator admin] + # Нежелательное использование колбека в модели, но это врменное решение до определения логики devise + before_validation :set_name, if: -> { name.blank? }, on: :create validates :locale, presence: true def owner?(vacancy) id == vacancy.user_id end + + def display_name + name || name_from_email + end + + private + + def set_name + self.name = name_from_email + end + + def name_from_email + name_part = email.split("@").first + return name_part if [".", "_", "-"].include?(name_part[1]) + + name_part.humanize + end end diff --git a/app/views/layouts/_header.html.slim b/app/views/layouts/_header.html.slim index 1f948cf..f1b7f55 100644 --- a/app/views/layouts/_header.html.slim +++ b/app/views/layouts/_header.html.slim @@ -13,14 +13,18 @@ nav.navbar.navbar-expand-lg.bg-body-tertiary - if user_signed_in? li.nav-item = link_to "My Vacancies", my_vacancies_vacancies_path, class: "nav-link fs-4 text-black" - ul.navbar-nav.ms-3.gap-2 + ul.navbar-nav.ms-3.gap-1 li.nav-item[data-turbo="false"] = link_to switch_locale_path(locale: next_locale), class: "btn btn-outline-primary border-0 me-2" do = selected_language_icon li.nav-item.dropdown - if user_signed_in? - = link_to current_user.name? ? current_user.name : current_user.email, "#", class: "dropdown-toggle btn btn-outline-primary", id: "navbarDropdown", role: "button", "data-bs-toggle" => "dropdown", "aria-expanded" => "false" + = link_to "javascript:void(0);", + class: "dropdown-toggle btn btn-outline-primary", id: "navbarDropdown", role: "button", + "data-bs-toggle" => "dropdown", "aria-expanded" => "false" do + span.ms-1.me-2 + = current_user.display_name .dropdown-menu[aria-labelledby="navbarDropdown"] = link_to "Edit profile", edit_user_registration_path, class: "dropdown-item" .li.hr.dropdown-divider