Skip to content

Commit 5c67b2d

Browse files
committed
wip
1 parent ce6bdca commit 5c67b2d

File tree

12 files changed

+273
-164
lines changed

12 files changed

+273
-164
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseTranslator
4+
module Translatable
5+
extend ActiveSupport::Concern
6+
7+
prepended do
8+
has_many :translations,
9+
class_name: "DiscourseTranslator::#{name}Translation",
10+
dependent: :destroy
11+
has_one :content_locale, class_name: "DiscourseTranslator::#{name}Locale", dependent: :destroy
12+
end
13+
14+
def set_detected_locale(locale)
15+
# locales should be "en-US" instead of "en_US" per https://www.rfc-editor.org/rfc/rfc5646#section-2.1
16+
locale = locale.to_s.gsub("_", "-")
17+
(content_locale || build_content_locale).update!(detected_locale: locale)
18+
end
19+
20+
def set_translation(locale, text)
21+
locale = locale.to_s.gsub("_", "-")
22+
translations.find_or_initialize_by(locale: locale).update!(translation: text)
23+
end
24+
25+
def translation_for(locale)
26+
translations.find_by(locale: locale)&.translation
27+
end
28+
29+
def detected_locale
30+
content_locale&.detected_locale
31+
end
32+
33+
private
34+
35+
def clear_translations
36+
return if !SiteSetting.translator_enabled
37+
38+
translations.delete_all
39+
content_locale&.destroy
40+
end
41+
end
42+
end

app/services/discourse_translator/base.rb

Lines changed: 34 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ def self.cache_key
2727
# Returns the stored translation of a post or topic.
2828
# If the translation does not exist yet, it will be translated first via the API then stored.
2929
# If the detected language is the same as the target language, the original text will be returned.
30-
# @param topic_or_post [Post|Topic]
31-
def self.translate(topic_or_post)
32-
return if text_for_translation(topic_or_post).blank?
33-
detected_lang = detect(topic_or_post)
30+
# @param translatable [Post|Topic]
31+
def self.translate(translatable)
32+
return if text_for_translation(translatable).blank?
33+
detected_lang = detect(translatable)
3434

35-
return detected_lang, get_text(topic_or_post) if (detected_lang&.to_s == I18n.locale.to_s)
35+
return detected_lang, get_text(translatable) if (detected_lang&.to_s == I18n.locale.to_s)
3636

37-
existing_translation = get_translation(topic_or_post)
37+
existing_translation = get_translation(translatable)
3838
return detected_lang, existing_translation if existing_translation.present?
3939

4040
unless translate_supported?(detected_lang, I18n.locale)
@@ -46,27 +46,27 @@ def self.translate(topic_or_post)
4646
),
4747
)
4848
end
49-
[detected_lang, translate!(topic_or_post)]
49+
[detected_lang, translate!(translatable)]
5050
end
5151

5252
# Subclasses must implement this method to translate the text of a post or topic
5353
# then use the save_translation method to store the translated text.
54-
# @param topic_or_post [Post|Topic]
55-
def self.translate!(topic_or_post)
54+
# @param translatable [Post|Topic]
55+
def self.translate!(translatable)
5656
raise "Not Implemented"
5757
end
5858

5959
# Returns the stored detected locale of a post or topic.
6060
# If the locale does not exist yet, it will be detected first via the API then stored.
61-
# @param topic_or_post [Post|Topic]
62-
def self.detect(topic_or_post)
63-
return if text_for_detection(topic_or_post).blank?
64-
get_detected_locale(topic_or_post) || detect!(topic_or_post)
61+
# @param translatable [Post|Topic]
62+
def self.detect(translatable)
63+
return if text_for_detection(translatable).blank?
64+
get_detected_locale(translatable) || detect!(translatable)
6565
end
6666

67-
# Subclasses must implement this method to translate the text of a post or topic
68-
# then use the save_translation method to store the translated text.
69-
# @param topic_or_post [Post|Topic]
67+
# Subclasses must implement this method to detect the text of a post or topic
68+
# then use the save_detected_locale method to store the detected locale.
69+
# @param translatable [Post|Topic]
7070
def self.detect!(post)
7171
raise "Not Implemented"
7272
end
@@ -75,52 +75,33 @@ def self.access_token
7575
raise "Not Implemented"
7676
end
7777

78-
def self.get_translation(topic_or_post)
79-
translated_custom_field =
80-
topic_or_post.custom_fields[DiscourseTranslator::TRANSLATED_CUSTOM_FIELD] || {}
81-
translated_custom_field[I18n.locale]
78+
def self.get_translation(translatable)
79+
translatable.translation_for(I18n.locale)
8280
end
8381

84-
def self.save_translation(topic_or_post)
85-
translated_custom_field =
86-
topic_or_post.custom_fields[DiscourseTranslator::TRANSLATED_CUSTOM_FIELD] || {}
87-
translated_text = translated_custom_field[I18n.locale]
88-
89-
if translated_text.nil?
90-
translated_text = yield
91-
92-
topic_or_post.custom_fields[
93-
DiscourseTranslator::TRANSLATED_CUSTOM_FIELD
94-
] = translated_custom_field.merge(I18n.locale => translated_text)
95-
96-
topic_or_post.save!
97-
end
98-
99-
translated_text
82+
def self.save_translation(translatable)
83+
translation = yield
84+
translatable.set_translation(I18n.locale, translation)
85+
translation
10086
end
10187

102-
def self.get_detected_locale(topic_or_post)
103-
topic_or_post.custom_fields[DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD]
88+
def self.get_detected_locale(translatable)
89+
translatable.detected_locale
10490
end
10591

106-
def self.save_detected_locale(topic_or_post)
92+
def self.save_detected_locale(translatable)
10793
detected_locale = yield
108-
topic_or_post.custom_fields[DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD] = detected_locale
109-
110-
if !topic_or_post.custom_fields_clean?
111-
topic_or_post.save_custom_fields
112-
topic_or_post.publish_change_to_clients!(:revised) if topic_or_post.class.name == "Post"
113-
end
94+
translatable.set_detected_locale(detected_locale)
11495

11596
detected_locale
11697
end
11798

118-
def self.get_text(topic_or_post)
119-
case topic_or_post.class.name
99+
def self.get_text(translatable)
100+
case translatable.class.name
120101
when "Post"
121-
topic_or_post.cooked
102+
translatable.cooked
122103
when "Topic"
123-
topic_or_post.title
104+
translatable.title
124105
end
125106
end
126107

@@ -143,15 +124,12 @@ def self.strip_tags_for_detection(detection_text)
143124
html_doc.to_html
144125
end
145126

146-
def self.text_for_detection(topic_or_post)
147-
strip_tags_for_detection(get_text(topic_or_post)).truncate(
148-
DETECTION_CHAR_LIMIT,
149-
omission: nil,
150-
)
127+
def self.text_for_detection(translatable)
128+
strip_tags_for_detection(get_text(translatable)).truncate(DETECTION_CHAR_LIMIT, omission: nil)
151129
end
152130

153-
def self.text_for_translation(topic_or_post)
154-
get_text(topic_or_post).truncate(SiteSetting.max_characters_per_translation, omission: nil)
131+
def self.text_for_translation(translatable)
132+
get_text(translatable).truncate(SiteSetting.max_characters_per_translation, omission: nil)
155133
end
156134
end
157135
end
Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
class MoveTranslationsCustomFieldsToTable < ActiveRecord::Migration[7.2] # frozen_string_literal: true
3+
class MoveTranslationsCustomFieldsToTable < ActiveRecord::Migration[7.2]
44
BATCH_SIZE = 1000
55

66
def up
@@ -18,42 +18,50 @@ def down
1818
private
1919

2020
def migrate_custom_fields(model)
21-
start_id = 0
22-
loop do
23-
last_id = DB.query_single(<<~SQL, model:, start_id:, limit: BATCH_SIZE)
24-
WITH to_insert AS (
25-
SELECT id, #{model}_id, value
21+
bounds = DB.query_single(<<~SQL, model:)
22+
SELECT
23+
COALESCE(MIN(id), 0) as min_id,
24+
COALESCE(MAX(id), 0) as max_id
25+
FROM #{model}_custom_fields
26+
WHERE name IN ('post_detected_lang', 'translated_text')
27+
SQL
28+
29+
start_id = bounds[0]
30+
max_id = bounds[1]
31+
32+
while start_id < max_id
33+
DB.exec(<<~SQL, model:, start_id:, end_id: start_id + BATCH_SIZE)
34+
WITH to_detect AS (
35+
SELECT #{model}_id, value
2636
FROM #{model}_custom_fields
2737
WHERE name = 'post_detected_lang'
28-
AND id > :start_id
38+
AND id >= :start_id
39+
AND id < :end_id
2940
ORDER BY id
30-
LIMIT :limit
3141
),
32-
do_insert AS (
42+
do_detect AS (
3343
INSERT INTO discourse_translator_#{model}_locales (#{model}_id, detected_locale, created_at, updated_at)
3444
SELECT #{model}_id, value, NOW(), NOW()
35-
FROM to_insert
45+
FROM to_detect
3646
),
3747
to_translate AS (
38-
SELECT id, #{model}_id, value::jsonb, created_at, updated_at
48+
SELECT #{model}_id, value::jsonb, created_at, updated_at
3949
FROM #{model}_custom_fields
40-
WHERE id > :start_id
41-
AND name = 'translated_text'
50+
WHERE name = 'translated_text'
4251
AND value LIKE '{%}'
52+
AND id >= :start_id
53+
AND id < :end_id
4354
ORDER BY id
44-
LIMIT :limit
4555
),
4656
do_translate AS (
4757
INSERT INTO discourse_translator_#{model}_translations (#{model}_id, locale, translation, created_at, updated_at)
4858
SELECT b.#{model}_id, jb.key as locale, jb.value as translation, b.created_at, b.updated_at
4959
FROM to_translate b, jsonb_each_text(b.value) jb
5060
WHERE LENGTH(jb.key) <= 20
51-
),
52-
max_value AS (SELECT COALESCE(GREATEST((SELECT MAX(id) FROM to_insert), (SELECT MAX(id) FROM to_translate) ), -1) as max_id)
53-
SELECT max_id FROM max_value
61+
)
62+
SELECT 1
5463
SQL
55-
start_id = last_id.last
56-
break if start_id == -1
64+
start_id += BATCH_SIZE
5765
end
5866
end
5967
end

lib/discourse_translator/guardian_extension.rb

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,11 @@ def can_detect_language?(post)
2222
def can_translate?(post)
2323
return false if !user_group_allow_translate?
2424

25-
# we will deal with regionalized_strings (not syms) when comparing locales
26-
# e.g. "en_GB"
27-
# not "en-GB"
28-
# nor :en_GB (I18n.locale)
29-
detected_lang =
30-
post.custom_fields[::DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD].to_s.sub("-", "_")
31-
return false if detected_lang.blank?
25+
locale = post.detected_locale
26+
return false if locale.nil?
3227

28+
# I18n.locale is a symbol e.g. :en_GB
29+
detected_lang = locale.to_s.sub("-", "_")
3330
detected_lang != I18n.locale.to_s &&
3431
"DiscourseTranslator::#{SiteSetting.translator}".constantize.language_supported?(
3532
detected_lang,

lib/discourse_translator/post_extension.rb

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,7 @@
33
module DiscourseTranslator
44
module PostExtension
55
extend ActiveSupport::Concern
6-
76
prepended { before_update :clear_translations, if: :raw_changed? }
8-
9-
private
10-
11-
def clear_translations
12-
return if !SiteSetting.translator_enabled
13-
14-
self.custom_fields.delete(DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD)
15-
self.custom_fields.delete(DiscourseTranslator::TRANSLATED_CUSTOM_FIELD)
16-
end
7+
include Translatable
178
end
189
end

lib/discourse_translator/topic_extension.rb

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,7 @@
33
module DiscourseTranslator
44
module TopicExtension
55
extend ActiveSupport::Concern
6-
76
prepended { before_update :clear_translations, if: :title_changed? }
8-
9-
private
10-
11-
def clear_translations
12-
return if !SiteSetting.translator_enabled
13-
14-
self.custom_fields.delete(DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD)
15-
self.custom_fields.delete(DiscourseTranslator::TRANSLATED_CUSTOM_FIELD)
16-
end
7+
include Translatable
178
end
189
end

plugin.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ module ::DiscourseTranslator
2727
register_problem_check ProblemCheck::MissingTranslatorApiKey
2828
register_problem_check ProblemCheck::TranslatorError
2929

30-
Post.register_custom_field_type(::DiscourseTranslator::TRANSLATED_CUSTOM_FIELD, :json)
31-
Topic.register_custom_field_type(::DiscourseTranslator::TRANSLATED_CUSTOM_FIELD, :json)
32-
33-
topic_view_post_custom_fields_allowlister { [::DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD] }
34-
3530
reloadable_patch do
3631
Guardian.prepend(DiscourseTranslator::GuardianExtension)
3732
Post.prepend(DiscourseTranslator::PostExtension)

spec/fabricators/topic_locale.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
Fabricator(:topic_locale, from: DiscourseTranslator::TopicLocale) do
3+
topic
4+
detected_locale { %w[en de es en-GB ja pt pt-BR].sample }
5+
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
Fabricator(:topic_translation, from: DiscourseTranslator::TopicTranslation) do
3+
topic
4+
locale { %w[en de es en-GB ja pt pt-BR].sample }
5+
translation do |attrs|
6+
{
7+
"en" => "Hello",
8+
"de" => "Hallo",
9+
"es" => "Hola",
10+
"en-GB" => "Hello",
11+
"ja" => "こんにちは",
12+
"pt" => "Olá",
13+
"pt-BR" => "Olá",
14+
}[
15+
attrs[:locale]
16+
]
17+
end
18+
end

0 commit comments

Comments
 (0)