Skip to content

Commit ce84805

Browse files
committed
feat: run the translation tasks in //
1 parent 9c2fae1 commit ce84805

File tree

1 file changed

+82
-26
lines changed

1 file changed

+82
-26
lines changed

app/services/maglev/translate_page.rb

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22

3+
# rubocop:disable Metrics/ClassLength
34
module Maglev
45
# Translate a page into a given locale from a source locale.
56
# This is a fake service that only copies the page attributes.
@@ -16,7 +17,13 @@ class TranslatePage
1617
def call
1718
return nil unless page.persisted?
1819

20+
@translations = {}
21+
1922
translate_all!
23+
24+
persist_changes!
25+
26+
page
2027
end
2128

2229
protected
@@ -29,13 +36,19 @@ def theme
2936
@theme ||= fetch_theme.call
3037
end
3138

39+
# this is a third-step process because we need to group all the translations to process
40+
# in order to process them in parallel
3241
def translate_all!
33-
translate_page_attributes
34-
translate_all_sections
42+
# 1. replace all the text to translatewith placeholders
43+
prepare_translate_page_attributes
44+
prepare_translate_all_sections # sections from page and site
3545

36-
persist_changes!
46+
# 2. Translate all the placeholders in parallel
47+
async_translate
3748

38-
page
49+
# 3. replace the placeholders with the actual translations that has been processed in parallel
50+
replace_translations_in_page_attributes
51+
replace_translations_in_all_sections
3952
end
4053

4154
def persist_changes!
@@ -45,80 +58,123 @@ def persist_changes!
4558
end
4659
end
4760

48-
def translate_page_attributes
61+
def async_translate
62+
tasks = @translations.map do |id, text|
63+
Concurrent::Promises.future do
64+
{ id => translate_text(text) }
65+
end
66+
end
67+
@translations = Concurrent::Promises.zip(*tasks).value!.reduce({}, :merge)
68+
end
69+
70+
def translate_text(text)
71+
# @note: implement actual translation logic in a subclass
72+
# by default we just add the locale to the text
73+
text + " [#{locale.upcase}]"
74+
end
75+
76+
# ===== Apply translations =====
77+
78+
def replace_translations_in_page_attributes
4979
%w[title seo_title meta_description og_title og_description].each do |attr|
50-
translate_page_attribute(attr)
80+
page.translations_for(attr)[locale] = replace_translated_text(page.translations_for(attr)[locale])
81+
end
82+
end
83+
84+
def replace_translations_in_all_sections
85+
[page, site].each do |source|
86+
source.sections_translations = JSON.parse(replace_translated_text(source.sections_translations.to_json))
87+
end
88+
end
89+
90+
# ===== Prepare translations =====
91+
92+
def prepare_translate_page_attributes
93+
%w[title seo_title meta_description og_title og_description].each do |attr|
94+
prepare_translate_page_attribute(attr)
5195
end
5296
# og_image_url is a special case because it's a URL, not content
5397
page.translations_for(:og_image_url)[locale] = page.translations_for(:og_image_url)[source_locale]
5498
end
5599

56-
def translate_page_attribute(attr)
57-
page.translations_for(attr)[locale] = translate_text(page.translations_for(attr)[source_locale])
100+
def prepare_translate_page_attribute(attr)
101+
page.translations_for(attr)[locale] = prepare_translate_text(page.translations_for(attr)[source_locale])
58102
end
59103

60-
def translate_all_sections
104+
def prepare_translate_all_sections
61105
[page, site].each do |source|
62-
translate_sections(source)
106+
prepare_translate_sections(source)
63107
end
64108
end
65109

66-
def translate_sections(source)
110+
def prepare_translate_sections(source)
67111
# @note no need to translate if there are no sections in the source locale
68112
return if source.translations_for(:sections, source_locale).blank?
69113

70114
# @note we don't want to overwrite existing translations
71115
return if source.translations_for(:sections, locale).present?
72116

73117
source.sections_translations[locale] = clone_array(source.sections_translations[source_locale]).tap do |sections|
74-
sections.each { |section| translate_section(section) }
118+
sections.each { |section| prepare_translate_section(section) }
75119
end
76120
end
77121

78-
def translate_section(section)
122+
def prepare_translate_section(section)
79123
definition = theme.sections.find(section['type'])
80-
translate_settings(section, definition)
81-
translate_section_blocks(section, definition)
124+
prepare_translate_settings(section, definition)
125+
prepare_translate_section_blocks(section, definition)
82126
end
83127

84-
def translate_settings(section_or_block, definition)
128+
def prepare_translate_settings(section_or_block, definition)
85129
section_or_block['settings'].each do |setting|
86130
type = definition.settings.find { |s| s.id == setting['id'] }&.type
87131
next if type.blank?
88132

89-
setting['value'] = translate_setting_value(setting['value'], type)
133+
setting['value'] = prepare_translate_setting_value(setting['value'], type)
90134
end
91135
end
92136

93-
def translate_section_blocks(section, definition)
137+
def prepare_translate_section_blocks(section, definition)
94138
section['blocks'].each do |block|
95139
block_definition = definition.blocks.find { |b| b.type == block['type'] }
96-
translate_settings(block, block_definition)
140+
prepare_translate_settings(block, block_definition)
97141
end
98142
end
99143

100-
def translate_setting_value(value, type)
144+
def prepare_translate_setting_value(value, type)
101145
case type
102146
when 'text'
103-
translate_text(value)
147+
prepare_translate_text(value)
104148
when 'link'
105-
value.merge(text: translate_text(value['text'])) if value.is_a?(Hash)
149+
value.merge(text: prepare_translate_text(value['text'])) if value.is_a?(Hash)
106150
when 'image'
107-
value.merge(alt: translate_text(value['alt'])) if value.is_a?(Hash)
151+
value.merge(alt: prepare_translate_text(value['alt'])) if value.is_a?(Hash)
108152
else
109153
value
110154
end
111155
end
112156

113157
# NOTE: this method is a placeholder for the actual translation logic.
114-
def translate_text(text)
115-
return nil if text.blank?
158+
def prepare_translate_text(text)
159+
return text if text.blank?
116160

117-
text + " [#{locale.upcase}]"
161+
id = SecureRandom.uuid
162+
@translations[id] = text
163+
164+
"{#{id}}"
165+
end
166+
167+
def replace_translated_text(text)
168+
return text if text.blank?
169+
170+
text.gsub(/\{([a-f0-9-]{36})\}/) do |_match|
171+
@translations[::Regexp.last_match(1)]
172+
end
118173
end
119174

120175
def clone_array(array)
121176
Marshal.load(Marshal.dump(array || []))
122177
end
123178
end
124179
end
180+
# rubocop:enable Metrics/ClassLength

0 commit comments

Comments
 (0)