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 SettingsConfiguration.

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 SettingsConfiguration

  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