The project is in a healthy, maintained state
A Ruby gem for translating ActionText rich text content using ChatGPT while perfectly preserving HTML structure, tags, and links. Extensible architecture allows adding additional translation providers.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 13.0
~> 3.0
~> 1.21
~> 0.22
~> 3.18

Runtime

~> 1.13
 Project Readme

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 install

Or install it yourself as:

gem install actiontext_translate

Configuration

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
end

Rails 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
end

Get Your OpenAI API Key

  1. Visit https://platform.openai.com/api-keys
  2. Create a new API key
  3. 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.save

Rails 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
end

Rake 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
end

Model 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
end

Advanced 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 preserved

HTML 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 preserved

Error 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.)
end

Supported 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 install

Run tests:

bundle exec rspec

Check code coverage (requires 90% minimum):

bundle exec rspec
open coverage/index.html

Run RuboCop:

bundle exec rubocop

Code 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.html

Contributing

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