Skip to content

Commit e37b111

Browse files
authored
Tools improvements (#673)
* update word translation tool, allow multiple wbw translation for same language * fix wbw translation progress calculation * add resources tab on profile page * allow adding missing footntoe to draft translation * improve the profile page * show permission info
1 parent 75ea81b commit e37b111

32 files changed

Lines changed: 902 additions & 270 deletions

app/admin/draft/translation.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,34 @@
3838
end
3939
end
4040

41+
action_item :add_footnote, only: :show do
42+
if !resource.imported?
43+
link_to 'Add footnote', add_footnote_cms_draft_translation_path(resource),
44+
data: { controller: 'ajax-modal', url: add_footnote_cms_draft_translation_path(resource), css_class: 'modal-lg' }
45+
end
46+
end
47+
4148
member_action :import, method: 'put' do
4249
translation = resource.import!
4350

4451
redirect_to [:cms, translation], notice: 'Draft translation is approved and imported successfully'
4552
end
4653

54+
member_action :add_footnote, method: 'get' do
55+
@draft = resource
56+
render partial: 'admin/draft/add_footnote'
57+
end
58+
59+
member_action :save_footnote, method: 'put' do
60+
if params[:draft_text].to_s.match?(Draft::Translation::INLINE_FOOTNOTE_REGEXP)
61+
resource.add_inline_footnotes!(params[:draft_text])
62+
redirect_to [:cms, resource], notice: 'Footnote(s) added to draft translation'
63+
else
64+
redirect_to add_footnote_cms_draft_translation_path(resource),
65+
alert: 'No footnote markers found. Wrap footnote text in << and >>.'
66+
end
67+
end
68+
4769
index do
4870
id_column
4971
column :text_matched

app/admin/settings/resource_permission.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
row :permission_to_host_info
1414
row :permission_to_share_info
1515
row :source_info
16+
row :copyright_notice
17+
row :contact_info
1618
row :created_at
1719
row :updated_at
1820
end
@@ -44,6 +46,7 @@
4446
f.input :permission_to_share_info
4547
f.input :source_info, as: :text
4648
f.input :copyright_notice, as: :text
49+
f.input :contact_info, as: :text
4750
end
4851

4952
f.actions

app/admin/user.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
row :first_name
6868
row :last_name
6969
row :email
70+
row :about_me
7071
row :created_at
7172

7273
if can?(:moderate, resource)

app/controllers/profiles_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ def submitted_params
4848
end
4949

5050
def account_update_params
51-
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :current_password)
51+
params.require(:user).permit(:first_name, :last_name, :email, :about_me, :password, :password_confirmation, :current_password)
5252
end
5353

5454
def profile_update_params
55-
params.require(:user).permit(:first_name, :last_name, :email)
55+
params.require(:user).permit(:first_name, :last_name, :email, :about_me)
5656
end
5757

5858
def update_notice

app/controllers/word_translations_controller.rb

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
11
class WordTranslationsController < CommunityController
22
before_action :authorize_access!, only: [:new, :edit, :update, :create]
3-
before_action :init_presenter
4-
def index
5-
@word_translation_languages = Language.where(id: ResourceContent.translations.one_word.select('language_id'))
3+
before_action :require_resource, except: [:select_resource]
64

7-
verses = Verse
8-
if params[:filter_juz].to_i > 0
9-
verses = verses.where(juz_number: params[:filter_juz].to_i)
10-
end
11-
12-
if params[:filter_chapter].to_i > 0
13-
verses = verses.where(chapter_id: params[:filter_chapter].to_i)
14-
end
15-
16-
if params[:verse_number].to_i > 0
17-
verses = verses.where(verse_number: params[:verse_number].to_i)
18-
end
19-
20-
if params[:filter_missing] == 'true'
21-
verses = verses.verses_with_missing_translations(language.id)
22-
end
5+
def select_resource
6+
end
237

24-
@pagy, @verses = pagy(verses.order("verse_index #{sort_order}"))
8+
def index
259
end
2610

2711
def show
@@ -43,30 +27,24 @@ def new
4327
end
4428

4529
if @verse.blank?
46-
return redirect_back fallback_location: word_translations_path(language: language.id), alert: 'Verse not found'
30+
return redirect_back fallback_location: word_translations_path(resource_id: resource.id), alert: 'Verse not found'
4731
end
48-
49-
prepare_wbw_translations
5032
end
5133

5234
def create
5335
@verse = Verse.find(params[:verse_id])
5436
@verse.update_word_translations(wbw_translations_params)
5537

56-
redirect_to word_translation_path(@verse, language: language.id)
38+
redirect_to word_translation_path(@verse, resource_id: resource.id)
5739
end
5840

5941
def group_info
60-
@word_translation = WordTranslation.where(
61-
word_id: params[:word_id],
62-
language_id: language.id
63-
).first_or_initialize
42+
@word_translation = @presenter.group_word_translation(params[:word_id])
6443
@verse = @word_translation.word&.verse
6544

6645
if request.post?
6746
if @word_translation.create_or_update_group_translation(group_translation_params)
6847
flash[:notice] = 'Group info updated'
69-
prepare_wbw_translations
7048
render 'update_group_info'
7149
else
7250
render_turbo_validations(@word_translation, {action: :update})
@@ -92,6 +70,7 @@ def wbw_translations_params
9270
:id,
9371
:word_id,
9472
:language_id,
73+
:resource_content_id,
9574
:text,
9675
:group_text,
9776
:group_word_id
@@ -116,29 +95,27 @@ def eager_load_translations
11695
end
11796
end
11897

119-
def load_resource
120-
@resource ||= ResourceContent.translations.one_word.where(language: language).first
98+
def resource
99+
@resource ||= @presenter.resource
121100
end
122101

123-
def load_resource_access
124-
@access = can_manage?(load_resource)
102+
def require_resource
103+
if resource.blank?
104+
redirect_to select_resource_word_translations_path and return
105+
end
125106
end
126107

127-
def prepare_wbw_translations
128-
@wbw_translations = []
108+
def language
109+
@language ||= resource&.language || super
110+
end
129111

130-
@verse.words.order('position asc').each_with_index do |word, i|
131-
next if word.char_type_name == 'end'
132-
wbw_translation = @verse
133-
.word_translations
134-
.where(language_id: language.id)
135-
.find_or_initialize_by(word_id: word.id)
112+
alias current_language language
136113

137-
@wbw_translations << wbw_translation
138-
end
114+
def load_resource_access
115+
@access = can_manage?(resource)
139116
end
140117

141118
def init_presenter
142119
@presenter = WordTranslationsPresenter.new(self)
143120
end
144-
end
121+
end

app/helpers/profiles_helper.rb

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,20 @@ def profile_tab_link(name, tab, active_tab)
88
end
99

1010
def contribution_filter_link(label, item_type, active_type)
11-
active = item_type == active_type
11+
profile_filter_chip label, profile_path(tab: "contributions", item_type: item_type), item_type == active_type
12+
end
13+
14+
def download_filter_link(label, resource_type, active_type)
15+
profile_filter_chip label, profile_path(tab: "downloads", download_type: resource_type), resource_type == active_type
16+
end
17+
18+
def resource_filter_link(label, sub_type, active_type)
19+
profile_filter_chip label, profile_path(tab: "resources", resource_type: sub_type), sub_type == active_type
20+
end
21+
22+
def profile_filter_chip(label, url, active)
1223
classes = active ? "bg-[#46ac7a] text-white border-[#46ac7a]" : "text-gray-600 border-gray-200 hover:text-black"
13-
link_to label, profile_path(tab: "contributions", item_type: item_type),
24+
link_to label, url,
1425
class: "text-xs px-3 py-1 rounded-full border #{classes}",
1526
data: { turbo_frame: "profile_panel" }
1627
end
@@ -22,4 +33,50 @@ def contribution_cms_url(item)
2233
rescue StandardError
2334
nil
2435
end
36+
37+
def resource_tool(resource_content)
38+
return { label: nil, url: nil } unless resource_content
39+
40+
case resource_content.sub_type
41+
when ResourceContent::SubType::Translation
42+
if resource_content.cardinality_type == ResourceContent::CardinalityType::OneWord
43+
{ label: "Word translation", url: word_translations_path(resource_id: resource_content.id) }
44+
else
45+
{ label: "Ayah translation", url: translation_proofreadings_path(resource_id: resource_content.id) }
46+
end
47+
when ResourceContent::SubType::Tafsir
48+
{ label: "Tafsir", url: tafsir_proofreadings_path(resource_id: resource_content.id) }
49+
when ResourceContent::SubType::Transliteration
50+
{ label: "Transliteration", url: arabic_transliterations_path(resource_id: resource_content.id) }
51+
when ResourceContent::SubType::Audio
52+
audio_resource_tool(resource_content)
53+
when ResourceContent::SubType::Layout
54+
mushaf = Mushaf.find_by(resource_content_id: resource_content.id)
55+
{ label: "Mushaf layout", url: mushaf ? mushaf_layout_path(mushaf.id) : nil }
56+
when ResourceContent::SubType::Info
57+
{ label: "Surah info", url: surah_infos_path(language_id: resource_content.language_id) }
58+
when ResourceContent::SubType::Mutashabihat
59+
{ label: "Mutashabihat", url: morphology_phrases_path }
60+
else
61+
{ label: resource_content.sub_type&.humanize, url: nil }
62+
end
63+
rescue StandardError
64+
{ label: resource_content&.sub_type&.humanize, url: nil }
65+
end
66+
67+
private
68+
69+
def audio_resource_tool(resource_content)
70+
ayah_recitation = Recitation.find_by(resource_content_id: resource_content.id)
71+
if ayah_recitation
72+
return { label: "Ayah audio", url: ayah_audio_files_path(id: ayah_recitation.id) }
73+
end
74+
75+
surah_recitation = Audio::Recitation.find_by(resource_content_id: resource_content.id)
76+
if surah_recitation
77+
return { label: "Surah audio", url: surah_audio_files_path(recitation_id: surah_recitation.id) }
78+
end
79+
80+
{ label: "Audio", url: nil }
81+
end
2582
end

app/helpers/tool_docs_helper.rb

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,26 @@ def word_translation_help
8888
},
8989
{
9090
type: 'step',
91-
title: 'Step 1: Find the word translation for a specific language',
92-
text: "Use the available filters to find the desired translation. You can filter by <code>Language</code> <code>Surah</code> and <code>Ayah</code> to narrow down the results. Click <code>Show</code> to display the selected Ayah, where a list of words and their corresponding translations will appear.",
91+
title: 'Step 1: Select a translation resource',
92+
text: "A language can have more than one word-by-word translation, so start by picking the resource you want to work on from the <code>Select resource</code> page. Each resource is maintained separately.",
9393
screenshot: 'word_translations.png'
9494
},
9595
{
9696
type: 'step',
97-
title: 'Step 2: Read the translation',
98-
text: "Review the translation of each word carefully. If an update is needed, click the <code>Edit</code> button to fix the translation.",
97+
title: 'Step 2: Find an ayah',
98+
text: "Once a resource is selected, use the <code>Surah</code> and <code>Ayah</code> filters to narrow down the results. Click <code>Show</code> to display the selected Ayah, where a list of words and their corresponding translations will appear.",
99+
screenshot: 'word_translations.png'
100+
},
101+
{
102+
type: 'step',
103+
title: 'Step 3: Read the translation',
104+
text: "Review the translation of each word carefully. Words without a translation are highlighted so you can spot gaps at a glance. If an update is needed, click the <code>Edit</code> button to fix the translation.",
99105
screenshot: 'ayah-words-translation.png'
100106
},
101107
{
102108
type: 'step',
103-
title: 'Step 3: Update the translation',
104-
text: "The page will display the Ayah along with a list of all its words. To help focus, only one word will be shown at a time. Each word comes with an input field where you can correct its translation. Once you're satisfied with the update, click <code>Save translations</code> to save the changes.",
109+
title: 'Step 4: Update the translation',
110+
text: "The page will display the Ayah along with a list of all its words. Each word is shown as its own card with an input field, its current translation, and the English translation for reference. Once you're satisfied with the update, click <code>Save translations</code> to save the changes.",
105111
screenshot: 'update-word-translation.png'
106112
},
107113
{
@@ -110,10 +116,10 @@ def word_translation_help
110116
},
111117
{
112118
type: 'step',
113-
title: 'Step 3.1: Update the group translation',
119+
title: 'Step 4.1: Update the group translation',
114120
sections: [
115121
{
116-
text: "If you find a group of words that should be translated together, you can group them. To do this, click the <code>Create Group translation</code> button(Or <code>Edit Group translation</code> if you need to change an existing group) to open the group translation modal.",
122+
text: "If you find a group of words that should be translated together, you can group them. To do this, click the <code>Create group</code> button (or <code>Edit group</code> if you need to change an existing group) to open the group translation modal.",
117123
screenshot: 'create-group.png'
118124
},
119125
{

app/helpers/tools_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def developer_tools
9292
ToolCard.new(
9393
title: 'Word by Word translation',
9494
description: 'Proofread and suggest fixes for word by word translations in multiple languages.',
95-
url: word_translations_path,
95+
url: select_resource_word_translations_path,
9696
type: 'corpus',
9797
tags: [['Translation', 'translation'], ['Word by Word', 'word-by-word']],
9898
icon: 'translation.svg',

app/models/draft/translation.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@
2828
# index_draft_translations_on_verse_id (verse_id)
2929
#
3030

31+
3132
class Draft::Translation < ApplicationRecord
3233
REGEXP_FOOTNOTE_ID = /foot_note=(?<id>\d+)/
34+
INLINE_FOOTNOTE_REGEXP = /<<(?<text>.+?)>>/m
35+
FOOTNOTE_SUP_REGEXP = %r{<sup foot_note=["']?(?<id>\d+)["']?>\d+</sup>}
3336
include HasMetaData
3437
include PaperTrailAttribution
3538

@@ -173,4 +176,35 @@ def original_footnote_text(foot_note_id)
173176
def update_footnote_count
174177
update_column :footnotes_count, foot_notes.count
175178
end
179+
180+
def add_inline_footnotes!(raw_text)
181+
footnote_resource_id = foot_notes.first&.resource_content_id ||
182+
resource_content.meta_value('related-footnote-resource-id')
183+
184+
text = raw_text.to_s.gsub(/<<\s*>>/, '').gsub(INLINE_FOOTNOTE_REGEXP) do
185+
inner = Regexp.last_match(:text).strip
186+
next '' if inner.blank?
187+
188+
footnote = foot_notes.create!(
189+
draft_text: inner,
190+
resource_content_id: footnote_resource_id
191+
)
192+
"<sup foot_note=#{footnote.id}>0</sup>"
193+
end
194+
195+
self.draft_text = renumber_footnote_sups(text)
196+
save!
197+
update_footnote_count
198+
self
199+
end
200+
201+
private
202+
203+
def renumber_footnote_sups(text)
204+
index = 0
205+
text.gsub(FOOTNOTE_SUP_REGEXP) do
206+
index += 1
207+
"<sup foot_note=#{Regexp.last_match(:id)}>#{index}</sup>"
208+
end
209+
end
176210
end

app/models/user_project.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class UserProject < ApplicationRecord
2121
belongs_to :resource_content, optional: true
2222
belongs_to :user, optional: true
2323

24+
scope :approved, -> { where(approved: true) }
25+
2426
validates :reason_for_request, :language_proficiency, :motivation_and_goals, presence: true
2527
validates :review_process_acknowledgment, presence: {message: 'Please check the review process acknowledge'}
2628
validate :unique_request_per_resource

0 commit comments

Comments
 (0)