translatomatic 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +20 -20
  3. data/.gitignore +19 -15
  4. data/.rspec +3 -3
  5. data/.rubocop.yml +28 -0
  6. data/.translatomatic/config.yml +4 -0
  7. data/.travis.yml +4 -6
  8. data/.yardopts +9 -9
  9. data/Gemfile +8 -4
  10. data/Guardfile +4 -5
  11. data/README.de.md +55 -50
  12. data/README.en.md +177 -0
  13. data/README.es.md +53 -48
  14. data/README.fr.md +53 -48
  15. data/README.it.md +54 -49
  16. data/README.ja.md +63 -58
  17. data/README.ko.md +59 -54
  18. data/README.md +17 -13
  19. data/README.ms.md +50 -45
  20. data/README.pt.md +54 -49
  21. data/README.ru.md +57 -52
  22. data/README.sv.md +51 -46
  23. data/README.zh.md +60 -55
  24. data/Rakefile +3 -3
  25. data/TODO.txt +6 -0
  26. data/bin/console +3 -3
  27. data/bin/translatomatic +4 -2
  28. data/config/i18n-tasks.yml +130 -0
  29. data/config/locales/translatomatic/de.yml +141 -99
  30. data/config/locales/translatomatic/en.yml +129 -89
  31. data/config/locales/translatomatic/es.yml +136 -99
  32. data/config/locales/translatomatic/fr.yml +139 -100
  33. data/config/locales/translatomatic/it.yml +135 -97
  34. data/config/locales/translatomatic/ja.yml +137 -98
  35. data/config/locales/translatomatic/ko.yml +138 -98
  36. data/config/locales/translatomatic/ms.yml +138 -100
  37. data/config/locales/translatomatic/pt.yml +137 -101
  38. data/config/locales/translatomatic/ru.yml +136 -98
  39. data/config/locales/translatomatic/sv.yml +134 -96
  40. data/config/locales/translatomatic/zh.yml +136 -97
  41. data/db/migrate/201712170000_initial.rb +2 -3
  42. data/lib/translatomatic.rb +40 -25
  43. data/lib/translatomatic/cli.rb +5 -1
  44. data/lib/translatomatic/cli/base.rb +61 -58
  45. data/lib/translatomatic/cli/common_options.rb +14 -11
  46. data/lib/translatomatic/cli/config.rb +96 -91
  47. data/lib/translatomatic/cli/database.rb +85 -23
  48. data/lib/translatomatic/cli/main.rb +158 -104
  49. data/lib/translatomatic/cli/thor.rb +29 -0
  50. data/lib/translatomatic/cli/translate.rb +134 -157
  51. data/lib/translatomatic/config.rb +10 -301
  52. data/lib/translatomatic/config/display.rb +78 -0
  53. data/lib/translatomatic/config/files.rb +60 -0
  54. data/lib/translatomatic/config/location_settings.rb +133 -0
  55. data/lib/translatomatic/config/options.rb +68 -0
  56. data/lib/translatomatic/config/selector.rb +127 -0
  57. data/lib/translatomatic/config/settings.rb +148 -0
  58. data/lib/translatomatic/converter.rb +40 -28
  59. data/lib/translatomatic/database.rb +127 -110
  60. data/lib/translatomatic/define_options.rb +4 -5
  61. data/lib/translatomatic/escaped_unicode.rb +86 -76
  62. data/lib/translatomatic/extractor.rb +5 -2
  63. data/lib/translatomatic/extractor/base.rb +12 -12
  64. data/lib/translatomatic/extractor/ruby.rb +7 -6
  65. data/lib/translatomatic/file_translator.rb +101 -244
  66. data/lib/translatomatic/flattenation.rb +39 -0
  67. data/lib/translatomatic/http.rb +13 -0
  68. data/lib/translatomatic/http/client.rb +144 -0
  69. data/lib/translatomatic/http/exception.rb +43 -0
  70. data/lib/translatomatic/http/file_param.rb +27 -0
  71. data/lib/translatomatic/http/param.rb +37 -0
  72. data/lib/translatomatic/http/request.rb +91 -0
  73. data/lib/translatomatic/i18n.rb +43 -0
  74. data/lib/translatomatic/locale.rb +71 -59
  75. data/lib/translatomatic/logger.rb +43 -28
  76. data/lib/translatomatic/metadata.rb +58 -0
  77. data/lib/translatomatic/model.rb +4 -2
  78. data/lib/translatomatic/model/locale.rb +5 -5
  79. data/lib/translatomatic/model/text.rb +5 -5
  80. data/lib/translatomatic/option.rb +57 -34
  81. data/lib/translatomatic/path_utils.rb +126 -0
  82. data/lib/translatomatic/progress_updater.rb +13 -16
  83. data/lib/translatomatic/provider.rb +101 -0
  84. data/lib/translatomatic/provider/base.rb +136 -0
  85. data/lib/translatomatic/provider/frengly.rb +55 -0
  86. data/lib/translatomatic/provider/google.rb +78 -0
  87. data/lib/translatomatic/provider/google_web.rb +50 -0
  88. data/lib/translatomatic/provider/microsoft.rb +144 -0
  89. data/lib/translatomatic/provider/my_memory.rb +75 -0
  90. data/lib/translatomatic/provider/yandex.rb +61 -0
  91. data/lib/translatomatic/resource_file.rb +59 -53
  92. data/lib/translatomatic/resource_file/base.rb +171 -237
  93. data/lib/translatomatic/resource_file/csv.rb +176 -24
  94. data/lib/translatomatic/resource_file/html.rb +21 -42
  95. data/lib/translatomatic/resource_file/key_value_support.rb +117 -0
  96. data/lib/translatomatic/resource_file/markdown.rb +36 -38
  97. data/lib/translatomatic/resource_file/plist.rb +121 -126
  98. data/lib/translatomatic/resource_file/po.rb +104 -82
  99. data/lib/translatomatic/resource_file/properties.rb +48 -77
  100. data/lib/translatomatic/resource_file/properties.treetop +87 -0
  101. data/lib/translatomatic/resource_file/resw.rb +56 -41
  102. data/lib/translatomatic/resource_file/subtitle.rb +86 -54
  103. data/lib/translatomatic/resource_file/text.rb +18 -18
  104. data/lib/translatomatic/resource_file/xcode_strings.rb +32 -63
  105. data/lib/translatomatic/resource_file/xcode_strings.treetop +85 -0
  106. data/lib/translatomatic/resource_file/xml.rb +94 -81
  107. data/lib/translatomatic/resource_file/yaml.rb +54 -68
  108. data/lib/translatomatic/retry_executor.rb +37 -0
  109. data/lib/translatomatic/slurp.rb +32 -0
  110. data/lib/translatomatic/string_batcher.rb +50 -0
  111. data/lib/translatomatic/string_escaping.rb +61 -0
  112. data/lib/translatomatic/text.rb +263 -0
  113. data/lib/translatomatic/text_collection.rb +66 -0
  114. data/lib/translatomatic/tmx.rb +5 -3
  115. data/lib/translatomatic/tmx/document.rb +107 -82
  116. data/lib/translatomatic/tmx/translation_unit.rb +19 -18
  117. data/lib/translatomatic/translation.rb +8 -28
  118. data/lib/translatomatic/translation/collection.rb +199 -0
  119. data/lib/translatomatic/translation/fetcher.rb +123 -0
  120. data/lib/translatomatic/translation/munging.rb +112 -0
  121. data/lib/translatomatic/translation/result.rb +50 -0
  122. data/lib/translatomatic/translation/sharer.rb +32 -0
  123. data/lib/translatomatic/translation/stats.rb +44 -0
  124. data/lib/translatomatic/translator.rb +91 -88
  125. data/lib/translatomatic/type_cast.rb +63 -0
  126. data/lib/translatomatic/util.rb +37 -33
  127. data/lib/translatomatic/version.rb +2 -2
  128. data/translatomatic.gemspec +57 -46
  129. metadata +136 -59
  130. data/lib/translatomatic/http_request.rb +0 -162
  131. data/lib/translatomatic/string.rb +0 -188
  132. data/lib/translatomatic/translation_result.rb +0 -86
  133. data/lib/translatomatic/translation_stats.rb +0 -31
  134. data/lib/translatomatic/translator/base.rb +0 -128
  135. data/lib/translatomatic/translator/frengly.rb +0 -62
  136. data/lib/translatomatic/translator/google.rb +0 -37
  137. data/lib/translatomatic/translator/microsoft.rb +0 -41
  138. data/lib/translatomatic/translator/my_memory.rb +0 -68
  139. data/lib/translatomatic/translator/yandex.rb +0 -56
@@ -0,0 +1,101 @@
1
+ module Translatomatic
2
+ # Provides methods to access and create instances of
3
+ # interfaces to translation APIs.
4
+ module Provider
5
+ class << self
6
+ include Translatomatic::Util
7
+
8
+ # @return [Class] The provider class corresponding to the given name
9
+ def find(name)
10
+ load_providers
11
+ name && !name.empty? ? const_get(name) : nil
12
+ end
13
+
14
+ # Resolve the given list of provider names to a list of providers.
15
+ # If the list is empty, return all providers that are configured.
16
+ # @param list [Array<String>] Provider names or providers
17
+ # @param options [Hash<String,String>] Provider options
18
+ # @return [Array<Translatomatic::Provider::Base>] Providers
19
+ def resolve(list, options = {})
20
+ list = [list].flatten.compact.collect do |provider|
21
+ if provider.respond_to?(:translate)
22
+ provider
23
+ else
24
+ klass = find(provider)
25
+ provider = create_provider(klass, options)
26
+ end
27
+ provider
28
+ end
29
+
30
+ # if we didn't resolve to any providers, find all available
31
+ # providers that work with the given options.
32
+ list.empty? ? available(options) : list
33
+ end
34
+
35
+ # @return [List<Class>] A list of all provider classes
36
+ def types
37
+ load_providers
38
+ constants.collect { |c| const_get(c) }.select do |klass|
39
+ klass.is_a?(Class) && klass != Translatomatic::Provider::Base
40
+ end
41
+ end
42
+
43
+ # @return [List<String>] A list of all providers
44
+ def names
45
+ types.collect { |i| i.name.demodulize }
46
+ end
47
+
48
+ # Find all configured providers
49
+ # @param options [Hash<String,String>] Provider options
50
+ # @return [Array<#translate>] A list of provider instances
51
+ def available(options = {})
52
+ available = types.collect { |klass| create_provider(klass, options) }
53
+ available.compact
54
+ end
55
+
56
+ # Get errors for the specified provider
57
+ # @param name [String] Provider name
58
+ def get_error(name)
59
+ @provider_errors[name]
60
+ end
61
+
62
+ private
63
+
64
+ def test_provider(provider)
65
+ provider.languages
66
+ end
67
+
68
+ def create_provider(klass, options = {})
69
+ return nil unless klass
70
+ klass.new(options)
71
+ rescue StandardError => e
72
+ name = klass.name.demodulize
73
+ log.debug(t('provider.unavailable', name: name))
74
+ provider_error(name, e)
75
+ nil
76
+ end
77
+
78
+ def loaded_providers?
79
+ @loaded_providers
80
+ end
81
+
82
+ def provider_error(name, e)
83
+ @provider_errors ||= {}
84
+ @provider_errors[name] = e
85
+ end
86
+
87
+ def load_providers
88
+ return if loaded_providers?
89
+ Dir[File.join(__dir__, 'provider/*.rb')].sort.each do |file|
90
+ begin
91
+ require file
92
+ rescue StandardError => e
93
+ name = File.basename(file, '.rb').classify
94
+ provider_error(name, e)
95
+ end
96
+ end
97
+ @loaded_providers = true
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,136 @@
1
+ module Translatomatic
2
+ module Provider
3
+ # Base class for interfaces to translation APIs
4
+ # @abstract
5
+ class Base
6
+ include Translatomatic::DefineOptions
7
+
8
+ # Listener for translation events
9
+ attr_accessor :listener
10
+
11
+ # @return [boolean] True if a string can have alternative translations
12
+ def self.supports_alternative_translations?
13
+ false
14
+ end
15
+
16
+ # @return [boolean] true if this provider supports html5
17
+ # <span translate="no"></span> tags.
18
+ def self.supports_no_translate_html?
19
+ false
20
+ end
21
+
22
+ def initialize(options = {})
23
+ @listener = options[:listener]
24
+ end
25
+
26
+ # @return [String] The name of this provider.
27
+ def name
28
+ self.class.name.demodulize
29
+ end
30
+
31
+ # @return [String] The name of this provider
32
+ def to_s
33
+ name
34
+ end
35
+
36
+ # @return [Array<String>] A list of languages
37
+ # supported by this provider.
38
+ def languages
39
+ []
40
+ end
41
+
42
+ # Translate strings from one locale to another
43
+ # @param strings [Array<String,Text>] A list of text/strings to translate.
44
+ # @param from [String, Translatomatic::Locale] The locale of the
45
+ # given strings.
46
+ # @param to [String, Translatomatic::Locale] The locale to translate to.
47
+ # @return [Array<Translatomatic::Translation::Result>] Translations
48
+ def translate(strings, from, to)
49
+ @updated_listener = false
50
+ @translations = []
51
+ @from = from
52
+ @to = to
53
+ strings = [strings] unless strings.is_a?(Array)
54
+ from = build_locale(from)
55
+ to = build_locale(to)
56
+ if from.language == to.language
57
+ strings.each { |i| add_translations(i, i) }
58
+ else
59
+ perform_translate(strings, from, to)
60
+ end
61
+ @translations
62
+ end
63
+
64
+ private
65
+
66
+ include Translatomatic::Util
67
+
68
+ TRANSLATION_RETRIES = 3
69
+
70
+ # all subclasses must implement this
71
+ def perform_translate(_strings, _from, _to)
72
+ raise 'subclass must implement perform_translate'
73
+ end
74
+
75
+ # subclasses that call perform_fetch_translations must implement this
76
+ def fetch_translations(_string, _from, _to)
77
+ raise 'subclass must implement fetch_translations'
78
+ end
79
+
80
+ def http_client(*args)
81
+ @http_client ||= Translatomatic::HTTP::Client.new(*args)
82
+ end
83
+
84
+ # Fetch translations for the given strings, one at a time, by
85
+ # opening a http connection to the given url and calling
86
+ # fetch_translation() on each string. Error handling and recovery
87
+ # is performed by this method.
88
+ # (subclass must implement fetch_translation if this method is used)
89
+ def perform_fetch_translations(url, strings, from, to)
90
+ untranslated = strings.dup
91
+
92
+ http_client.start(url) do |_http|
93
+ until untranslated.empty?
94
+ # get next string to translate
95
+ string = untranslated[0]
96
+ # fetch translation
97
+ fetch_translations(string, from, to)
98
+ untranslated.shift
99
+ end
100
+ end
101
+ end
102
+
103
+ def add_translations(original, result)
104
+ # successful translation
105
+ result = [result] unless result.is_a?(Array)
106
+ result = convert_to_translations(original, result)
107
+ @listener.update_progress(1) if @listener
108
+ @translations += result
109
+ end
110
+
111
+ def convert_to_translations(original, result)
112
+ result.collect { |i| translation(original, i) }.compact
113
+ end
114
+
115
+ def translation(original, translated)
116
+ return nil if translated.blank?
117
+ string1 = Translatomatic::Text[original, @from]
118
+ string2 = Translatomatic::Text[translated, @to]
119
+ Translatomatic::Translation::Result.new(string1, string2, name)
120
+ end
121
+
122
+ def batcher(strings, max_count:, max_length:)
123
+ StringBatcher.new(strings, max_count: max_count, max_length: max_length)
124
+ end
125
+
126
+ def try_hash(hash, *keys)
127
+ result = hash
128
+ keys.each do |key|
129
+ result ||= {}
130
+ result = result.is_a?(Hash) ? result[key] : nil
131
+ end
132
+ result
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,55 @@
1
+ require 'net/http'
2
+
3
+ module Translatomatic
4
+ module Provider
5
+ # Interface to the Frengly translation API
6
+ # @see http://www.frengly.com/api
7
+ class Frengly < Base
8
+ define_option :frengly_api_key, use_env: true,
9
+ desc: t('provider.frengly.api_key')
10
+ define_option :frengly_email, use_env: true,
11
+ desc: t('provider.email_address')
12
+ define_option :frengly_password, use_env: true,
13
+ desc: t('provider.password')
14
+
15
+ # Create a new Frengly provider instance
16
+ def initialize(options = {})
17
+ super(options)
18
+ @key = options[:frengly_api_key] || ENV['FRENGLY_API_KEY'] # optional
19
+ @email = options[:frengly_email]
20
+ @password = options[:frengly_password]
21
+ raise t('provider.email_required') unless @email
22
+ raise t('provider.password_required') unless @password
23
+ end
24
+
25
+ # (see Base#languages)
26
+ def languages
27
+ %w[en fr de es pt it nl tl fi el iw pl ru sv]
28
+ end
29
+
30
+ private
31
+
32
+ URL = 'http://frengly.com/frengly/data/translateREST'.freeze
33
+
34
+ def perform_translate(strings, from, to)
35
+ perform_fetch_translations(URL, strings, from, to)
36
+ end
37
+
38
+ def fetch_translations(string, from, to)
39
+ body = {
40
+ src: from.language,
41
+ dest: to.language,
42
+ text: string,
43
+ email: @email,
44
+ password: @password,
45
+ premiumkey: @key
46
+ }.to_json
47
+
48
+ # TODO: work out what the response looks like
49
+ response = http_client.post(URL, body, content_type: 'application/json')
50
+ data = JSON.parse(response.body)
51
+ add_translations(string, data['text'])
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,78 @@
1
+
2
+ module Translatomatic
3
+ module Provider
4
+ # Interface to the Google translation API
5
+ # @see https://cloud.google.com/translate/
6
+ class Google < Base
7
+ define_option :google_api_key,
8
+ desc: t('provider.google.api_key'), use_env: true
9
+ define_option :google_model, enum: %i[base nmt],
10
+ desc: t('provider.google.model'), use_env: true
11
+
12
+ # (see Base.supports_no_translate_html?)
13
+ def self.supports_no_translate_html?
14
+ true
15
+ end
16
+
17
+ # Create a new Google provider instance
18
+ def initialize(options = {})
19
+ super(options)
20
+ @key = options[:google_api_key] || ENV['GOOGLE_API_KEY']
21
+ raise t('provider.google.key_required') if @key.nil?
22
+ @model = options[:google_model]
23
+ end
24
+
25
+ # (see Base#languages)
26
+ def languages
27
+ @languages ||= fetch_languages
28
+ end
29
+
30
+ private
31
+
32
+ BASE_URL = 'https://translation.googleapis.com'.freeze
33
+ TRANSLATE_URL = (BASE_URL + '/language/translate/v2').freeze
34
+ LANGUAGES_URL = (BASE_URL + '/language/translate/v2/languages').freeze
35
+ # https://cloud.google.com/translate/faq
36
+ # TODO: limit requested characters per second?
37
+ LIMIT = [128, 5000].freeze # strings, characters per request
38
+
39
+ def perform_translate(strings, from, to)
40
+ batcher(strings, max_count: LIMIT[0], max_length: LIMIT[1])
41
+ .each_batch do |texts|
42
+ perform_translate_texts(texts, from, to)
43
+ end
44
+ end
45
+
46
+ def perform_translate_texts(texts, from, to)
47
+ request_body = request_body(texts, from, to)
48
+ response = http_client.post(TRANSLATE_URL, request_body)
49
+ body = JSON.parse(response.body)
50
+ data = body['data'] || {}
51
+ translations = data['translations'] || []
52
+ translations = translations.collect { |i| i['translatedText'] }
53
+ texts.zip(translations).each do |original, translated|
54
+ add_translations(original, translated)
55
+ end
56
+ end
57
+
58
+ def request_body(strings, from, to)
59
+ body = {
60
+ q: strings,
61
+ source: from.language,
62
+ target: to.language,
63
+ format: 'html', # required for <span translate="no"></span>
64
+ key: @key
65
+ }
66
+ body[:model] = @model if @model
67
+ body
68
+ end
69
+
70
+ def fetch_languages
71
+ response = http_client.get(LANGUAGES_URL, key: @key)
72
+ body = JSON.parse(response.body)
73
+ languages = try_hash(body, 'data', 'languages') || []
74
+ languages.collect { |i| i['language'] }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,50 @@
1
+ module Translatomatic
2
+ module Provider
3
+ # Google translation web interface.
4
+ # supports multiple translations
5
+ # @see https://translate.google.com.au
6
+ class GoogleWeb < Base
7
+ attr_accessor :dt
8
+
9
+ # (see Base.supports_alternative_translations?)
10
+ def self.supports_alternative_translations?
11
+ true
12
+ end
13
+
14
+ # Create a new GoogleWeb provider instance
15
+ def initialize(options = {})
16
+ super(options)
17
+ require 'google_web_translate'
18
+ @dt = %w[t at]
19
+ @debug = options[:debug]
20
+ end
21
+
22
+ # (see Base#languages)
23
+ def languages
24
+ api.respond_to?(:languages) ? api.languages : []
25
+ end
26
+
27
+ private
28
+
29
+ def api
30
+ options = { debug: @debug, dt: @dt, http_client: http_client }
31
+ @api ||= GoogleWebTranslate::API.new(options)
32
+ end
33
+
34
+ def perform_translate(strings, from, to)
35
+ strings.each do |string|
36
+ result = api.translate(string, from, to)
37
+ add_translations(string, translations_from_result(result))
38
+ end
39
+ end
40
+
41
+ def translations_from_result(result)
42
+ if result.alternatives.present?
43
+ result.alternatives
44
+ else
45
+ result.translation
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,144 @@
1
+ require 'builder'
2
+
3
+ module Translatomatic
4
+ module Provider
5
+ # Interface to the Microsoft translation API
6
+ # @see https://www.microsoft.com/en-us/translator/translatorapi.aspx
7
+ # @see http://docs.microsofttranslator.com/text-translate.html
8
+ class Microsoft < Base
9
+ define_option :microsoft_api_key,
10
+ desc: t('provider.microsoft.api_key'), use_env: true
11
+
12
+ # (see Base.supports_alternative_translations?)
13
+ def self.supports_alternative_translations?
14
+ true
15
+ end
16
+
17
+ # (see Base.supports_no_translate_html?)
18
+ def self.supports_no_translate_html?
19
+ true
20
+ end
21
+
22
+ # Create a new Microsoft provider instance
23
+ def initialize(options = {})
24
+ super(options)
25
+ @key = options[:microsoft_api_key] || ENV['MICROSOFT_API_KEY']
26
+ raise t('provider.microsoft.key_required') if @key.nil?
27
+ end
28
+
29
+ # (see Base#languages)
30
+ def languages
31
+ @languages ||= fetch_languages
32
+ end
33
+
34
+ private
35
+
36
+ BASE_URL = 'https://api.microsofttranslator.com/V2/Http.svc'.freeze
37
+ # this endpoint returns one translation per source text
38
+ TRANSLATE_URL1 = "#{BASE_URL}/TranslateArray".freeze
39
+ LIMITS_URL1 = [2000, 10_000].freeze # strings, characters per request
40
+ # this url returns multiple translations
41
+ TRANSLATE_URL2 = "#{BASE_URL}/GetTranslationsArray".freeze
42
+ LIMITS_URL2 = [10, 10_000].freeze # strings, characters per request
43
+ MAX_TRANSLATIONS = 10 # for URL2
44
+ LANGUAGES_URL = "#{BASE_URL}/GetLanguagesForTranslate".freeze
45
+ ARRAYS_NS = 'http://schemas.microsoft.com/2003/10/Serialization/Arrays'.freeze
46
+ WEB_SERVICE_NS = 'http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2'.freeze
47
+
48
+ def perform_translate(strings, from, to)
49
+ # get multiple translations for strings with context, so we
50
+ # can choose the best translation.
51
+ strings_with_context = strings.select { |i| context?(i) }
52
+ strings_without_context = strings.reject { |i| context?(i) }
53
+
54
+ fetch_translation_array(strings_with_context, from, to, true)
55
+ fetch_translation_array(strings_without_context, from, to, false)
56
+ end
57
+
58
+ # fetch translations for given strings
59
+ def fetch_translation_array(strings, from, to, multiple)
60
+ limit = multiple ? LIMITS_URL2 : LIMITS_URL1
61
+ batcher(strings, max_count: limit[0], max_length: limit[1])
62
+ .each_batch do |texts|
63
+ translate_texts(texts, from, to, multiple)
64
+ end
65
+ end
66
+
67
+ def translate_texts(texts, from, to, multiple)
68
+ url = multiple ? TRANSLATE_URL2 : TRANSLATE_URL1
69
+ headers = { 'Ocp-Apim-Subscription-Key' => @key }
70
+ body = build_body_xml(texts, from, to, multiple)
71
+ response = http_client.post(url, body,
72
+ headers: headers,
73
+ content_type: 'application/xml')
74
+ add_translations_from_response(response, texts, multiple)
75
+ end
76
+
77
+ def add_translations_from_response(response, texts, multiple)
78
+ doc = Nokogiri::XML(response.body)
79
+ if multiple
80
+ add_translations_from_response_multiple(doc, texts)
81
+ else
82
+ results = doc.search('//xmlns:TranslatedText').collect(&:content)
83
+ texts.zip(results).each do |original, tr|
84
+ add_translations(original, tr)
85
+ end
86
+ end
87
+ end
88
+
89
+ def add_translations_from_response_multiple(doc, texts)
90
+ # there should be one GetTranslationsResponse for each string
91
+ responses = doc.search('//xmlns:GetTranslationsResponse')
92
+ texts.zip(responses).each do |original, tr|
93
+ results = tr.search('TranslatedText').collect(&:content)
94
+ add_translations(original, results)
95
+ end
96
+ end
97
+
98
+ def fetch_languages
99
+ # this request redirects to a html page
100
+ headers = { 'Ocp-Apim-Subscription-Key' => @key }
101
+ query = { 'appid' => '' }
102
+ response = http_client.get(LANGUAGES_URL, query, headers: headers)
103
+ log.debug("#{name} response: #{response.body}")
104
+ doc = Nokogiri::XML(response.body)
105
+ doc.search('//xmlns:string').collect(&:content)
106
+ end
107
+
108
+ def xml_root(multiple)
109
+ multiple ? 'GetTranslationsArray' : 'TranslateArray'
110
+ end
111
+
112
+ def build_body_xml(strings, from, to, multiple)
113
+ root = xml_root(multiple) + 'Request'
114
+ xml = Builder::XmlMarkup.new
115
+ xml.tag!(root, 'xmlns:a' => ARRAYS_NS) do
116
+ xml.AppId
117
+ xml.From(from)
118
+ build_options_xml(xml) if multiple
119
+ build_texts_xml(xml, strings)
120
+ xml.To(to)
121
+ xml.MaxTranslations MAX_TRANSLATIONS if multiple
122
+ end
123
+ end
124
+
125
+ def build_texts_xml(xml, strings)
126
+ xml.Texts do
127
+ strings.each do |string|
128
+ xml.tag!('a:string', string)
129
+ end
130
+ end
131
+ end
132
+
133
+ def build_options_xml(xml)
134
+ xml.tag!('Options', 'xmlns:o' => WEB_SERVICE_NS) do
135
+ xml.tag!('o:IncludeMultipleMTAlternatives', 'true')
136
+ end
137
+ end
138
+
139
+ def context?(text)
140
+ text.is_a?(Translatomatic::Text) && text.context.present?
141
+ end
142
+ end
143
+ end
144
+ end