ActionText Translate
A Ruby gem for translating ActionText rich text content using ChatGPT (OpenAI) while perfectly preserving HTML structure, tags, and links.
Features
- High-Quality Translation: Uses ChatGPT for natural, contextual translations
- HTML Preservation: Maintains all HTML tags, attributes, and structure
- Smart Link Protection: Prevents URL corruption during translation
- ActionText Support: Seamless integration with Rails ActionText
- Extensible Architecture: Easy to add additional translation providers
- Well Tested: Comprehensive test suite with 33+ test cases
Installation
Add this line to your application's Gemfile:
gem 'actiontext_translate'And then execute:
bundle installOr install it yourself as:
gem install actiontext_translateConfiguration
Basic Configuration
ActiontextTranslate.configure do |config|
config.provider = :chatgpt
config.api_key = ENV['OPENAI_API_KEY']
config.model = 'gpt-4o-mini' # Fast and cost-effective
config.temperature = 0.3 # Lower temperature for consistent translations
config.timeout = 30 # API timeout in seconds
endRails Configuration
Create an initializer config/initializers/actiontext_translate.rb:
# config/initializers/actiontext_translate.rb
ActiontextTranslate.configure do |config|
config.provider = :chatgpt
config.api_key = ENV['OPENAI_API_KEY']
config.model = 'gpt-4o-mini'
config.temperature = 0.3
endGet Your OpenAI API Key
- Visit https://platform.openai.com/api-keys
- Create a new API key
- Add it to your environment variables
Usage
Simple Text Translation
# Basic translation
ActiontextTranslate.translate("Привіт Світ", from: "uk", to: "en")
# => "Hello World"
# With custom translator instance
translator = ActiontextTranslate::Translator.new
translator.translate("Bonjour le monde", from: "fr", to: "en")
# => "Hello world"HTML Translation
html = '<p>Check <a href="https://example.com">this link</a> for more info.</p>'
result = ActiontextTranslate.translate_html(html, from: "uk", to: "en")
# => '<p>Check <a href="https://example.com">this link</a> for more info.</p>'
# All HTML structure and link attributes are perfectly preserved!ActionText Translation
# Translate ActionText rich text content
article = Article.find(1)
translated_html = ActiontextTranslate.translate_action_text(
article.body,
from: "uk",
to: "en"
)
# Set the translated content
article.body_en = translated_html
article.saveRails Integration
Background Job Example
# app/jobs/translate_content_job.rb
class TranslateContentJob < ApplicationJob
queue_as :default
def perform(model_class, model_id, from_locale, to_locale)
model = model_class.constantize.find_by(id: model_id)
return unless model
translator = ActiontextTranslate::Translator.new
# Translate ActionText body field
if model.respond_to?(:body) && model.body.present?
translated_html = translator.translate_action_text(
model.body,
from: from_locale,
to: to_locale
)
model.public_send("body_#{to_locale}=", translated_html)
end
# Translate regular text fields
if model.respond_to?(:title) && model.title.present?
translated_title = translator.translate(
model.title,
from: from_locale,
to: to_locale
)
model.public_send("title_#{to_locale}=", translated_title)
end
model.save!
rescue ActiontextTranslate::Translators::TranslationError => e
Rails.logger.error("Translation failed: #{e.message}")
raise
end
endRake Task Example
# lib/tasks/translations.rake
namespace :translations do
desc 'Translate content from Ukrainian to English'
task translate_articles: :environment do
Article.where(translated: false).find_each do |article|
puts "Translating Article ##{article.id}..."
TranslateContentJob.perform_later('Article', article.id, 'uk', 'en')
end
end
endModel Integration
# app/models/article.rb
class Article < ApplicationRecord
has_rich_text :body
has_rich_text :body_en
after_create :enqueue_translation
private
def enqueue_translation
TranslateContentJob.perform_later(self.class.name, id, 'uk', 'en')
end
endAdvanced Features
Link Protection
The gem automatically protects links during translation:
html = '<p>Visit <a href="https://example.com">our website</a> or <a href="https://docs.com">https://docs.com</a></p>'
result = ActiontextTranslate.translate_html(html, from: "uk", to: "en")
# - Links with descriptive text: text gets translated, URL preserved
# - Links where text = URL: kept unchanged
# - All attributes (href, rel, target, class) are preservedHTML Structure Preservation
html = <<~HTML
<div class="content">
<h1>Title</h1>
<p>Paragraph with <strong>bold</strong> and <em>italic</em> text.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
HTML
result = ActiontextTranslate.translate_html(html, from: "uk", to: "en")
# All tags, classes, and structure are perfectly preservedError Handling
begin
translator = ActiontextTranslate::Translator.new
result = translator.translate("Text", from: "uk", to: "en")
rescue ActiontextTranslate::Translators::TranslationError => e
Rails.logger.error("Translation failed: #{e.message}")
# Handle error (retry, fallback, notify, etc.)
endSupported Languages
The gem supports all languages supported by OpenAI's GPT models. Simply use the appropriate language code:
# Ukrainian to English
ActiontextTranslate.translate("Привіт", from: "uk", to: "en")
# French to German
ActiontextTranslate.translate("Bonjour", from: "fr", to: "de")
# Any language combination
ActiontextTranslate.translate(text, from: "source_lang", to: "target_lang")Cost Considerations
Using ChatGPT (GPT-4o-mini) is very cost-effective:
- Input: ~$0.15 per 1M tokens
- Output: ~$0.60 per 1M tokens
- Average translation: A typical blog post (1000 words) costs < $0.01
Example monthly costs for 1000 translations:
- ~$5-10/month for typical usage
- Pay only for what you use
Configuration Options
| Option | Default | Description |
|---|---|---|
provider |
:chatgpt |
Translation provider |
api_key |
nil |
OpenAI API key (required) |
model |
'gpt-4o-mini' |
OpenAI model to use |
temperature |
0.3 |
Response randomness (0.0-2.0) |
timeout |
30 |
API request timeout in seconds |
Development
After checking out the repo, run:
bundle installRun tests:
bundle exec rspecCheck code coverage (requires 90% minimum):
bundle exec rspec
open coverage/index.htmlRun RuboCop:
bundle exec rubocopCode Coverage
The gem maintains 96%+ code coverage with comprehensive tests:
- 48 test cases covering all major features
- Integration tests with realistic Trix/ActionText content
- Edge case coverage for HTML preservation
- Minimum coverage threshold: 90%
View coverage report after running tests:
open coverage/index.htmlContributing
Bug reports and pull requests are welcome on GitHub at https://github.com/mykbren/actiontext_translate.
License
The gem is available as open source under the terms of the MIT License.
Roadmap
Future enhancements planned:
- Additional translation providers (Google Translate, DeepL, Azure)
- Batch translation support for improved performance
- Translation memory/caching to reduce costs
- Automatic language detection
- Rails generators for easy setup
- Translation quality scoring
- Custom prompt templates
- Streaming support for large documents
Support
For questions, issues, or feature requests:
- Open an issue on GitHub
- Review the test suite for examples
- Check the documentation