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/en.svg b/app/assets/images/icons/en.svg new file mode 100644 index 0000000..a722047 --- /dev/null +++ b/app/assets/images/icons/en.svg @@ -0,0 +1,9 @@ + + + + + + + + + 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/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7b0f46e..4053b6a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,14 +1,32 @@ class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? + before_action :set_current_locale + helper_method :current_locale rescue_from ActionPolicy::Unauthorized do |_e| redirect_to root_path end + def set_current_locale + Current.locale = extract_locale_from_user || extract_locale_from_session || 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 + + 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 a15ef40..51f948e 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -1,3 +1,33 @@ class PagesController < ApplicationController def welcome; end + + def switch_locale + Current.locale = locale + I18n.locale = Current.locale + store_locale + + redirect_back(fallback_location: root_path) + end + + 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) + 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..5753c44 --- /dev/null +++ b/app/helpers/navbar_helper.rb @@ -0,0 +1,11 @@ +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: "23", class: "rounded-2", title: t("navbar.language") + end + end + + def next_locale + I18n.locale == :ru ? :en : :ru + end +end diff --git a/app/models/current.rb b/app/models/current.rb new file mode 100644 index 0000000..43c511a --- /dev/null +++ b/app/models/current.rb @@ -0,0 +1,3 @@ +class Current < ActiveSupport::CurrentAttributes + thread_mattr_accessor :locale +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 e1f063e..f1b7f55 100644 --- a/app/views/layouts/_header.html.slim +++ b/app/views/layouts/_header.html.slim @@ -13,10 +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-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 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 0537a94..4f43680 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 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