about summary refs log tree commit diff
path: root/app/lib/translation_service/deepl.rb
blob: afcb7ecb2f9f0e624abad1b3cb01b34210d270b3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# frozen_string_literal: true

class TranslationService::DeepL < TranslationService
  include JsonLdHelper

  def initialize(plan, api_key)
    super()

    @plan    = plan
    @api_key = api_key
  end

  def translate(text, source_language, target_language)
    form = { text: text, source_lang: source_language&.upcase, target_lang: target_language, tag_handling: 'html' }
    request(:post, '/v2/translate', form: form) do |res|
      transform_response(res.body_with_limit)
    end
  end

  def languages
    source_languages = [nil] + fetch_languages('source')

    # In DeepL, EN and PT are deprecated in favor of EN-GB/EN-US and PT-BR/PT-PT, so
    # they are supported but not returned by the API.
    target_languages = %w(en pt) + fetch_languages('target')

    source_languages.index_with { |language| target_languages.without(nil, language) }
  end

  private

  def fetch_languages(type)
    request(:get, "/v2/languages?type=#{type}") do |res|
      Oj.load(res.body_with_limit).map { |language| normalize_language(language['language']) }
    end
  end

  def normalize_language(language)
    subtags = language.split(/[_-]/)
    subtags[0].downcase!
    subtags[1]&.upcase!
    subtags.join('-')
  end

  def request(verb, path, **options)
    req = Request.new(verb, "#{base_url}#{path}", **options)
    req.add_headers(Authorization: "DeepL-Auth-Key #{@api_key}")
    req.perform do |res|
      case res.code
      when 429
        raise TooManyRequestsError
      when 456
        raise QuotaExceededError
      when 200...300
        yield res
      else
        raise UnexpectedResponseError
      end
    end
  end

  def base_url
    if @plan == 'free'
      'https://api-free.deepl.com'
    else
      'https://api.deepl.com'
    end
  end

  def transform_response(str)
    json = Oj.load(str, mode: :strict)

    raise UnexpectedResponseError unless json.is_a?(Hash)

    Translation.new(text: json.dig('translations', 0, 'text'), detected_source_language: json.dig('translations', 0, 'detected_source_language')&.downcase, provider: 'DeepL.com')
  rescue Oj::ParseError
    raise UnexpectedResponseError
  end
end