Skip to content

Commit

Permalink
Merge pull request #881 from ita-social-projects/feature/category-nam…
Browse files Browse the repository at this point in the history
…e-translation

Category name translation based on site locale
  • Loading branch information
loqimean authored May 31, 2024
2 parents 0ee6230 + ccf1512 commit ea39e37
Show file tree
Hide file tree
Showing 17 changed files with 152 additions and 34 deletions.
7 changes: 4 additions & 3 deletions app/controllers/account/categories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ class Account::CategoriesController < Account::BaseController
load_and_authorize_resource

def index
@q = collection.ransack(params[:q])
@categories = @q.result.page(params[:page])
@q = collection.ransack(params[:q])
@categories = @q.result.page(params[:page])
@search_attribute = :"#{I18n.locale}_name_cont"
end

def new
Expand Down Expand Up @@ -56,6 +57,6 @@ def resource
end

def category_params
params.require(:category).permit(:name, :priority, :preferable)
params.require(:category).permit(:uk_name, :en_name, :priority, :preferable)
end
end
11 changes: 7 additions & 4 deletions app/models/category.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# preferable :boolean default: false
#
class Category < ApplicationRecord
include Translatable
translates :name

PRIORITY_RANGE = 0..10

enum preferable: { not_preferable: false, preferable: true }
Expand All @@ -21,15 +24,15 @@ class Category < ApplicationRecord
has_many :category_categoryables, dependent: :restrict_with_exception
has_many :categoryables, through: :category_categoryables

validates :name, presence: true
validates :name,
validates :uk_name, :en_name, presence: true
validates :uk_name, :en_name,
length: { minimum: 3, maximum: 30 },
format: { with: /\A[\p{L}0-9\s'-]+\z/i },
uniqueness: { case_sensitive: false },
allow_blank: true
validates :priority, numericality: { greater_than_or_equal_to: 0 }

scope :ordered_by_name, -> { order(:name) }
scope :ordered_by_name, -> { order(:uk_name) }
scope :ordered_by_priority, -> { order(:priority) }
scope :unsigned_categories, ->(product) { where.not(id: product.categories_by_prices) }

Expand All @@ -49,6 +52,6 @@ class Category < ApplicationRecord
}

def self.ransackable_attributes(auth_object = nil)
["created_at", "id", "name", "priority", "updated_at"]
["created_at", "id", "#{I18n.locale}_name", "priority", "updated_at"]
end
end
26 changes: 26 additions & 0 deletions app/models/concerns/translatable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Translatable
extend ActiveSupport::Concern

class_methods do
def translates(*attributes)
attributes.each do |attribute|
define_method(attribute) do
translation_for(attribute)
end
end
end
end

private

def translation_for(attribute)
locale_attr = "#{I18n.locale}_#{attribute}"
default_locale_attr = "#{I18n.default_locale}_#{attribute}"

if respond_to?(locale_attr)
public_send(locale_attr)
elsif respond_to?(default_locale_attr)
public_send(default_locale_attr)
end
end
end
3 changes: 2 additions & 1 deletion app/views/account/categories/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<%= simple_form_for @category, url: { action: "update" }, html: { novalidate: false } do |f| %>
<div class="form-group row">
<div class="my-auto col-12 has-float-label">
<%= f.input :name, class: "form-control col-sm-11", required: true %>
<%= f.input :uk_name, class: "form-control col-sm-11", required: true %>
<%= f.input :en_name, class: "form-control col-sm-11", required: true %>
<%= f.input :priority, collection: Category::PRIORITY_RANGE, wrapper: :custom_vertical_select %>
<% if @unfilled_categories&.exclude?(@category) %>
<div class="my-4">
Expand Down
2 changes: 1 addition & 1 deletion app/views/account/categories/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<%= render partial: "account/shared/search_form", locals: {
q: @q,
search_url: account_categories_path,
search_attribute: :name_cont
search_attribute: @search_attribute
} %>

<div class="col-md-6 text-end">
Expand Down
11 changes: 6 additions & 5 deletions app/views/account/categories/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<%= simple_form_for @category, url: { action: 'create' }, html: { novalidate: false } do |f| %>
<%= simple_form_for @category, url: { action: "create" }, html: { novalidate: false } do |f| %>
<div class="form-group row">
<div class="my-auto col-12 has-float-label">
<%= f.input :name, class: 'form-control col-sm-11' %>
<%= f.input :uk_name, class: "form-control col-sm-11" %>
<%= f.input :en_name, class: "form-control col-sm-11" %>
<%= f.input :priority, collection: Category::PRIORITY_RANGE, wrapper: :custom_vertical_select %>
</div>
</div>
<div class="button-group d-flex">
<%= f.submit t('.form.create_category_button'), class: 'btn btn-green me-2' %>
<%= link_to account_categories_path, class: 'btn btn-danger d-flex align-items-center justify-content-center' do %>
<span><%= t('buttons.cancel') %></span>
<%= f.submit t(".form.create_category_button"), class: "btn btn-green me-2" %>
<%= link_to account_categories_path, class: "btn btn-danger d-flex align-items-center justify-content-center" do %>
<span><%= t("buttons.cancel") %></span>
<% end %>
</div>
<% end %>
2 changes: 2 additions & 0 deletions config/locales/en/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,8 @@ en:
preferable: "Preferable"
category:
name: "Title"
en_name: "Title in English"
uk_name: "Title in Ukrainian"
priority: "Priority"
preferable: "Main"
price:
Expand Down
6 changes: 5 additions & 1 deletion config/locales/uk/uk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ uk:
invalid: "містить неприпустимі символи"
category:
attributes:
name:
uk_name: &name_errors
blank: "не може бути порожньою"
too_long:
one: "занадто довга (максимум %{count} знак)"
Expand All @@ -560,6 +560,8 @@ uk:
other: "занадто коротка (мінімум %{count} знаків)"
invalid: "містить неприпустимі символи"
taken: "вже викориcтовується, будь ласка, оберіть іншу"
en_name:
<<: *name_errors
site_setting:
attributes:
title:
Expand Down Expand Up @@ -666,6 +668,8 @@ uk:
preferable: "Бажаний"
category:
name: "Назва"
en_name: "Назва Англійською"
uk_name: "Назва Українською"
priority: "Пріоритет"
preferable: "Основна"
price:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class AddEnNameAndRenameNameToUkNameInCategories < ActiveRecord::Migration[7.1]
def up
remove_index :categories, name: "index_categories_on_name"
rename_column :categories, :name, :uk_name
add_index :categories, "lower((uk_name)::text)", name: "index_categories_on_uk_name", unique: true
add_column :categories, :en_name, :string
add_index :categories, "lower((en_name)::text)", name: "index_categories_on_en_name", unique: true
end

def down
remove_index :categories, name: "index_categories_on_uk_name"
rename_column :categories, :uk_name, :name
add_index :categories, "lower((name)::text)", name: "index_categories_on_name", unique: true
remove_index :categories, name: "index_categories_on_en_name"
remove_column :categories, :en_name
end
end
8 changes: 5 additions & 3 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2024_02_08_154012) do
ActiveRecord::Schema[7.1].define(version: 2024_05_15_233604) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
Expand Down Expand Up @@ -56,12 +56,14 @@
end

create_table "categories", force: :cascade do |t|
t.string "name"
t.string "uk_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "priority", default: 0, null: false
t.boolean "preferable", default: false, null: false
t.index "lower((name)::text)", name: "index_categories_on_name", unique: true
t.string "en_name"
t.index "lower((en_name)::text)", name: "index_categories_on_en_name", unique: true
t.index "lower((uk_name)::text)", name: "index_categories_on_uk_name", unique: true
end

create_table "category_categoryables", force: :cascade do |t|
Expand Down
12 changes: 8 additions & 4 deletions spec/factories/categories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@
#
FactoryBot.define do
factory :category do
name { "name" }
en_name { "category" }
uk_name { "категорія" }

trait :budgetary do
name { "budgetary" }
en_name { "budgetary" }
uk_name { "бюджетна" }

after(:create) do |category|
create(:diapers_period, category: category)
end
end

trait :medium do
name { "medium" }
en_name { "medium" }
uk_name { "середня" }
preferable { true }

after(:create) do |category|
Expand All @@ -30,7 +33,8 @@
end

trait :without_diapers_period do
name { "without-diapers-period" }
en_name { "without-diapers-period" }
uk_name { "без-diapers-period" }
end
end
end
15 changes: 9 additions & 6 deletions spec/models/category_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@
end

describe "validations" do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_length_of(:name).is_at_least(3).is_at_most(30) }
it { is_expected.to validate_uniqueness_of(:name).case_insensitive }
it { is_expected.to validate_presence_of(:en_name) }
it { is_expected.to validate_length_of(:en_name).is_at_least(3).is_at_most(30) }
it { is_expected.to validate_uniqueness_of(:en_name).case_insensitive }
it { is_expected.to validate_presence_of(:uk_name) }
it { is_expected.to validate_length_of(:uk_name).is_at_least(3).is_at_most(30) }
it { is_expected.to validate_uniqueness_of(:uk_name).case_insensitive }
it { is_expected.to validate_numericality_of(:priority).is_greater_than_or_equal_to(0) }
end

context "checking the number of errors" do
let(:category) { build(:category, name: "") }
let(:category) { build(:category, en_name: "") }

it "returns only one error message when field is blank" do
category.valid?
expect(category.errors.full_messages_for(:name).length).to eq 1
expect(category.errors.full_messages_for(:name)).to include("Title can't be blank")
expect(category.errors.full_messages_for(:en_name).length).to eq 1
expect(category.errors.full_messages_for(:en_name)).to include("Title in English can't be blank")
end
end
end
32 changes: 32 additions & 0 deletions spec/models/conserns/translatable_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require "rails_helper"

RSpec.describe Translatable do
describe "translations" do
context "when en_name and uk_name are exist in model" do
let(:dummy_translatable) { DummyTranslatable.new }

it "returns the en_name when locale is set to :en" do
allow(I18n).to receive(:locale).and_return(:en)

expect(dummy_translatable.name).to eq("Diapers")
end

it "returns the uk_name when locale is set to :uk" do
allow(I18n).to receive(:locale).and_return(:uk)

expect(dummy_translatable.name).to eq("Підгузки")
end
end

context "when en_name is not exist and a default locale is uk" do
let(:dummy_translatable) { DummyTranslatable.new(en_name: nil) }

it "returns the uk_name when locale is set to :en" do
allow(I18n).to receive(:locale).and_return(:en)
allow(I18n).to receive(:default_locale).and_return(:uk)

expect(dummy_translatable.name).to eq("Підгузки")
end
end
end
end
2 changes: 1 addition & 1 deletion spec/models/product_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@

describe "#find_or_build_price_for_category" do
let(:product) { create(:product, title: "Valid Title") }
let(:category) { create(:category, name: "Valid Name") }
let(:category) { create(:category, :medium) }
let(:valid_sum) { 10.0 }

context "when the product has a price for the category" do
Expand Down
6 changes: 3 additions & 3 deletions spec/requests/categories_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

RSpec.describe Account::CategoriesController, type: :request do
let!(:category) { create(:category, :budgetary) }
let(:valid_attributes) { { category: { name: "medium" }} }
let(:invalid_attributes) { { category: { name: "" }} }
let(:new_attributes) { { category: { name: "premium" }} }
let(:valid_attributes) { { category: { en_name: "medium", uk_name: "середня" }} }
let(:invalid_attributes) { { category: { en_name: "" }} }
let(:new_attributes) { { category: { en_name: "premium" }} }

include_context :authorize_admin

Expand Down
4 changes: 2 additions & 2 deletions spec/services/categories/preferable_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
RSpec.describe Categories::PreferableService do
describe "#call" do
let!(:become_preferable_category) { create(:category, :medium) }
let!(:category) { create(:category, name: "category") }
let!(:current_preferable_category) { create(:category, name: "current preferable category", preferable: true) }
let!(:category) { create(:category, :budgetary) }
let!(:current_preferable_category) { create(:category, preferable: true) }
let!(:service) { described_class.new(become_preferable_category) }

context "when the category is preferable" do
Expand Down
22 changes: 22 additions & 0 deletions spec/support/dummy_translatable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class DummyTranslatable
include Translatable

attr_accessor :uk_name

translates :name

def initialize(en_name: "Diapers", uk_name: "Підгузки")
@en_name = en_name
@uk_name = uk_name

define_en_name if @en_name
end

private

# Define en_name only if en_name is present for testing case with uk defaul locale
# when en_name not exist at all
def define_en_name
define_singleton_method(:en_name) { @en_name }
end
end

0 comments on commit ea39e37

Please sign in to comment.