Skip to content

Commit 422a3ed

Browse files
committed
FEATURE: Translate categories and display them when inline translations enabled
1 parent ecb8dc5 commit 422a3ed

File tree

13 files changed

+176
-49
lines changed

13 files changed

+176
-49
lines changed

app/jobs/scheduled/automatic_translation_backfill.rb

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ def fetch_untranslated_model_ids(model, content_column, limit, target_locale)
2424
SELECT m.id
2525
FROM #{model.table_name} m
2626
#{limit_to_public_clause(model)}
27-
WHERE m.deleted_at IS NULL
28-
AND m.#{content_column} != ''
29-
AND m.user_id > 0
30-
#{max_age_clause}
27+
WHERE m.#{content_column} != ''
28+
#{not_deleted_clause(model)}
29+
#{non_bot_clause(model)}
30+
#{max_age_clause(model)}
3131
ORDER BY m.updated_at DESC
3232
)
3333
EXCEPT
@@ -91,22 +91,29 @@ def process_batch
9191
topic_ids =
9292
fetch_untranslated_model_ids(Topic, "title", records_to_translate, target_locale)
9393
post_ids = fetch_untranslated_model_ids(Post, "raw", records_to_translate, target_locale)
94+
category_ids =
95+
fetch_untranslated_model_ids(Category, "name", records_to_translate, target_locale)
9496

95-
next if topic_ids.empty? && post_ids.empty?
97+
next if topic_ids.empty? && post_ids.empty? && category_ids.empty?
9698

9799
DiscourseTranslator::VerboseLogger.log(
98-
"Translating #{topic_ids.size} topics and #{post_ids.size} posts to #{target_locale}",
100+
"Translating #{topic_ids.size} topics, #{post_ids.size} posts, #{category_ids.size} categories, to #{target_locale}",
99101
)
100102

101103
translate_records(Topic, topic_ids, target_locale)
102104
translate_records(Post, post_ids, target_locale)
105+
translate_records(Category, category_ids, target_locale)
103106
end
104107
end
105108

106-
def max_age_clause
109+
def max_age_clause(model)
107110
return "" if SiteSetting.automatic_translation_backfill_max_age_days <= 0
108111

109-
"AND m.created_at > NOW() - INTERVAL '#{SiteSetting.automatic_translation_backfill_max_age_days} days'"
112+
if model == Post || model == Topic
113+
"AND m.created_at > NOW() - INTERVAL '#{SiteSetting.automatic_translation_backfill_max_age_days} days'"
114+
else
115+
""
116+
end
110117
end
111118

112119
def limit_to_public_clause(model)
@@ -130,5 +137,15 @@ def limit_to_public_clause(model)
130137

131138
limit_to_public_clause
132139
end
140+
141+
def non_bot_clause(model)
142+
return "AND m.user_id > 0" if model == Post || model == Topic
143+
""
144+
end
145+
146+
def not_deleted_clause(model)
147+
return "AND m.deleted_at IS NULL" if model == Post || model == Topic
148+
""
149+
end
133150
end
134151
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseTranslator
4+
class CategoryLocale < ActiveRecord::Base
5+
self.table_name = "discourse_translator_category_locales"
6+
7+
belongs_to :category
8+
9+
validates :category_id, presence: true
10+
validates :detected_locale, presence: true
11+
end
12+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseTranslator
4+
class CategoryTranslation < ActiveRecord::Base
5+
self.table_name = "discourse_translator_category_translations"
6+
7+
belongs_to :category
8+
9+
validates :category_id, presence: true
10+
validates :locale, presence: true
11+
validates :translation, presence: true
12+
validates :locale, uniqueness: { scope: :category_id }
13+
end
14+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
class CategoryTranslator < BaseTranslator
5+
PROMPT_TEMPLATE = <<~TEXT.freeze
6+
You are a translation service specializing in translating forum category names to the asked target_language. Your task is to provide accurate and contextually appropriate translations while adhering to the following guidelines:
7+
8+
1. Translate the category name to target_language asked
9+
2. Keep proper nouns and technical terms in their original language
10+
3. Keep the translated category name length short, and close to the original length
11+
4. Ensure the translation maintains the original meaning
12+
13+
Provide your translation in the following JSON format:
14+
15+
<output>
16+
{"translation": "Your target_language translation here"}
17+
</output>
18+
19+
Here are three examples of correct translation
20+
21+
Original: {"name":"Cats and Dogs", "target_language":"Chinese"}
22+
Correct translation: {"translation": "猫和狗"}
23+
24+
Original: {"name":"General", "target_language":"French"}
25+
Correct translation: {"translation": "Général"}
26+
27+
Original: {"name": "Q&A", "target_language": "Portuguese"}
28+
Correct translation: {"translation": "Perguntas e Respostas"}
29+
30+
Remember to keep proper nouns like "Minecraft" and "Toyota" in their original form. Translate the category name now and provide your answer in the specified JSON format.
31+
TEXT
32+
33+
private def prompt_template
34+
PROMPT_TEMPLATE
35+
end
36+
end
37+
end

app/services/discourse_ai/topic_translator.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
module DiscourseAi
44
class TopicTranslator < BaseTranslator
55
PROMPT_TEMPLATE = <<~TEXT.freeze
6-
You are a translation service specializing in translating forum post titles from English to the asked target_language. Your task is to provide accurate and contextually appropriate translations while adhering to the following guidelines:
6+
You are a translation service specializing in translating forum post titles to the asked target_language. Your task is to provide accurate and contextually appropriate translations while adhering to the following guidelines:
77
8-
1. Translate the given title from English to target_language asked.
8+
1. Translate the given title to target_language asked.
99
2. Keep proper nouns and technical terms in their original language.
1010
3. Attempt to keep the translated title length close to the original when possible.
1111
4. Ensure the translation maintains the original meaning and tone.

app/services/discourse_translator/base.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ def self.get_untranslated(translatable, raw: false)
137137
raw ? translatable.raw : translatable.cooked
138138
when "Topic"
139139
translatable.title
140+
when "Category"
141+
translatable.name
140142
end
141143
end
142144
end

app/services/discourse_translator/discourse_ai.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ def self.translate!(translatable, target_locale_sym = I18n.locale)
4242
.join("")
4343
when "Topic"
4444
::DiscourseAi::TopicTranslator.new(text_for_translation(translatable), language).translate
45+
when "Category"
46+
::DiscourseAi::CategoryTranslator.new(
47+
text_for_translation(translatable),
48+
language,
49+
).translate
4550
end
4651

4752
DiscourseTranslator::TranslatedContentNormalizer.normalize(translatable, translated)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
3+
class CreateCategoryTranslationTable < ActiveRecord::Migration[7.2]
4+
def change
5+
create_table :discourse_translator_category_locales do |t|
6+
t.integer :category_id, null: false
7+
t.string :detected_locale, limit: 20, null: false
8+
t.timestamps
9+
end
10+
11+
create_table :discourse_translator_category_translations do |t|
12+
t.integer :category_id, null: false
13+
t.string :locale, null: false
14+
t.text :translation, null: false
15+
t.timestamps
16+
end
17+
18+
add_index :discourse_translator_category_translations,
19+
%i[category_id locale],
20+
unique: true,
21+
name: "idx_category_translations_on_category_id_and_locale"
22+
end
23+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseTranslator
4+
module Extensions
5+
module CategoryExtension
6+
extend ActiveSupport::Concern
7+
prepended { before_update :clear_translations, if: :name_changed? }
8+
include Translatable
9+
end
10+
end
11+
end

lib/discourse_translator/inline_translation.rb

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ def inject(plugin)
1515
# always return early if topic and posts are in the user's effective_locale.
1616
# this prevents the need to load translations.
1717

18+
# posts
19+
1820
plugin.register_modifier(:basic_post_serializer_cooked) do |cooked, serializer|
1921
if !SiteSetting.experimental_inline_translation ||
2022
serializer.object.locale_matches?(InlineTranslation.effective_locale) ||
@@ -25,6 +27,14 @@ def inject(plugin)
2527
end
2628
end
2729

30+
plugin.add_to_serializer(:basic_post, :is_translated) do
31+
SiteSetting.experimental_inline_translation &&
32+
!object.locale_matches?(InlineTranslation.effective_locale) &&
33+
object.translation_for(InlineTranslation.effective_locale).present?
34+
end
35+
36+
# topics
37+
2838
plugin.register_modifier(:topic_serializer_fancy_title) do |fancy_title, serializer|
2939
if !SiteSetting.experimental_inline_translation ||
3040
serializer.object.locale_matches?(InlineTranslation.effective_locale) ||
@@ -54,12 +64,6 @@ def inject(plugin)
5464
end
5565
end
5666

57-
plugin.add_to_serializer(:basic_post, :is_translated) do
58-
SiteSetting.experimental_inline_translation &&
59-
!object.locale_matches?(InlineTranslation.effective_locale) &&
60-
object.translation_for(InlineTranslation.effective_locale).present?
61-
end
62-
6367
plugin.add_to_serializer(:topic_view, :is_translated) do
6468
SiteSetting.experimental_inline_translation &&
6569
!object.topic.locale_matches?(InlineTranslation.effective_locale) &&
@@ -72,6 +76,19 @@ def inject(plugin)
7276
plugin.register_topic_preloader_associations(:translations) do
7377
SiteSetting.translator_enabled && SiteSetting.experimental_inline_translation
7478
end
79+
80+
# categories
81+
82+
plugin.register_modifier(:basic_category_serializer_name) do |name, serializer|
83+
# "x"
84+
if !SiteSetting.experimental_inline_translation ||
85+
serializer.object.locale_matches?(InlineTranslation.effective_locale) ||
86+
serializer.scope&.request&.params&.[]("show") == "original"
87+
name
88+
else
89+
serializer.object.translation_for(InlineTranslation.effective_locale).presence
90+
end
91+
end
7592
end
7693
end
7794
end

0 commit comments

Comments
 (0)