Machine Translations

To enable machine translations you need to enable the service and specify the class used for translation (this usually is the one contacting an external API from a 3d party service that actually translates the string).

class MyTranslationService
  attr_reader :text, :source_locale, :target_locale, :resource, :field_name

  def initialize(resource, field_name, text, target_locale, source_locale)
    @resource = resource
    @field_name = field_name
    @text = text
    @target_locale = target_locale
    @source_locale = source_locale
  end

  def translate
    # Actual code for translating the text
    translated_text = "#{target_locale} - #{text}"

    Decidim::MachineTranslationSaveJob.perform_later(
      resource,
      field_name,
      target_locale,
      translated_text
    )
  end
end

The arguments provided for the initialize method are:

  • resource - The object of the resource that being translated (ex: a Decidim::Meetings::Meeting instance )

  • field_name - The name of the field that is being translated (ex: "title")

  • text - The text that is going to be translated (ex: "This meeting is great" )

  • target_locale - The language in which you want to translate ( ex: "ca" )

  • source_locale - The language in which the content has been created (ex: "en")

Then you will need to configure it with the help of Environment Variables:

export DECIDIM_ENABLE_MACHINE_TRANSLATION="true"
export DECIDIM_MACHINE_TRANSLATION_SERVICE="MyTranslationService"

You also need to enable the setting "Enable machine translations" in Settings → Configuration.

You can read more about the details of how this service work at `Using machine translations.

Examples

LibreTranslate

LibreTranslate is a Free and Open Source Machine Translation API. Self-hosted, offline capable and easy to setup. If you do not want to self-host it, they also offer a paid service at libretranslate.com.

For using it, follow these steps:

  1. Install LibreTranslate with any of the methods proposed on their documentation. For demonstration purposes, it will be installed at http://localhost:5000. You have a Docker Compose configuration based on the LibreTranslate documentation.

  2. Add the file app/services/libre_translate_service.rb to your application

  3. Add these Environment Variables:

export DECIDIM_ENABLE_MACHINE_TRANSLATION="true"
export DECIDIM_MACHINE_TRANSLATION_SERVICE="LibreTranslateService"
export DECIDIM_LIBRE_TRANSLATE_SERVICE_URL="http://localhost:5000"
  1. Restart the web application and queue servers

  2. Sign in as administrator

  3. Go to the admin panel and go into in Settings → Configuration

  4. Enable the settings "Enable machine translations" and "Translated text first"

  5. Create a new Process or Edit an existing one

  6. Go to the frontend and change the language

Docker compose

services:
  libretranslate:
    image: libretranslate/libretranslate
    container_name: libretranslate
    ports:
      - "${LT_PORT:-5000}:5000"
    volumes:
      - lt-local:/home/libretranslate/.local
      # Uncomment the line below if using --api-keys
      # - lt-db:/app/db
    stdin_open: true
    tty: true
    restart: unless-stopped
    # Add any additional command arguments here
    # command: ["--api-keys"]

volumes:
  lt-local:
    name: lt-local

app/services/libre_translate_service.rb

# frozen_string_literal: true

require "faraday"
require "json"

# This translator uses LibreTranslate API to translate text
# from one locale to another.
class LibreTranslateService
  attr_reader :text, :source_locale, :target_locale, :resource, :field_name

  def initialize(resource, field_name, text, target_locale, source_locale)
    @resource = resource
    @field_name = field_name
    @text = text
    @target_locale = target_locale
    @source_locale = source_locale
  end

  def translate
    translated_text = fetch_translation || text

    Decidim::MachineTranslationSaveJob.perform_later(
      resource,
      field_name,
      target_locale,
      translated_text
    )
  end

  private

  def fetch_translation
    response = Faraday.post("#{host}/translate") do |req|
      req.headers["Content-Type"] = "application/json"
      req.body = translation_payload.to_json
    end

    parse_response(response)
  rescue => e
    log_error(e)
    nil
  end

  def translation_payload
    {
      q: text,
      source: source_locale,
      target: target_locale,
      api_key: api_key
    }.compact
  end

  def parse_response(response)
    result = JSON.parse(response.body)
    result["translatedText"]
  end

  def host
    Decidim::Env.new("DECIDIM_LIBRE_TRANSLATE_SERVICE_URL", "https://libretranslate.com").value
  end

  def api_key
    Decidim::Env.new("DECIDIM_LIBRE_TRANSLATE_API_KEY").value
  end

  def log_error(error)
    Rails.logger.error("LibreTranslate translation error: #{error.message}")
  end
end