translatomatic 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +19 -0
  3. data/.gitignore +0 -0
  4. data/.travis.yml +7 -7
  5. data/.yardopts +9 -0
  6. data/Gemfile +4 -4
  7. data/Guardfile +0 -0
  8. data/README.de.md +61 -16
  9. data/README.es.md +60 -15
  10. data/README.fr.md +61 -16
  11. data/README.it.md +60 -15
  12. data/README.ja.md +59 -14
  13. data/README.ko.md +137 -0
  14. data/README.md +58 -13
  15. data/README.ms.md +137 -0
  16. data/README.pt.md +137 -0
  17. data/README.ru.md +137 -0
  18. data/README.sv.md +137 -0
  19. data/README.zh.md +137 -0
  20. data/bin/setup +8 -8
  21. data/bin/translatomatic +6 -6
  22. data/bin/travis +1 -1
  23. data/config/locales/translatomatic/de.yml +104 -0
  24. data/config/locales/translatomatic/en.yml +101 -0
  25. data/config/locales/translatomatic/es.yml +105 -0
  26. data/config/locales/translatomatic/fr.yml +105 -0
  27. data/config/locales/translatomatic/it.yml +103 -0
  28. data/config/locales/translatomatic/ja.yml +102 -0
  29. data/config/locales/translatomatic/ko.yml +101 -0
  30. data/config/locales/translatomatic/ms.yml +103 -0
  31. data/config/locales/translatomatic/pt.yml +105 -0
  32. data/config/locales/translatomatic/ru.yml +103 -0
  33. data/config/locales/translatomatic/sv.yml +103 -0
  34. data/config/locales/translatomatic/zh.yml +102 -0
  35. data/db/migrate/201712170000_initial.rb +2 -1
  36. data/lib/translatomatic/cli/base.rb +73 -0
  37. data/lib/translatomatic/cli/common_options.rb +14 -0
  38. data/lib/translatomatic/cli/config.rb +81 -0
  39. data/lib/translatomatic/cli/database.rb +29 -0
  40. data/lib/translatomatic/cli/main.rb +112 -0
  41. data/lib/translatomatic/cli/translate.rb +146 -0
  42. data/lib/translatomatic/cli.rb +8 -216
  43. data/lib/translatomatic/config.rb +143 -0
  44. data/lib/translatomatic/converter.rb +196 -149
  45. data/lib/translatomatic/converter_stats.rb +18 -14
  46. data/lib/translatomatic/database.rb +35 -37
  47. data/lib/translatomatic/escaped_unicode.rb +1 -1
  48. data/lib/translatomatic/extractor/base.rb +2 -0
  49. data/lib/translatomatic/extractor/ruby.rb +1 -0
  50. data/lib/translatomatic/extractor.rb +1 -0
  51. data/lib/translatomatic/http_request.rb +43 -14
  52. data/lib/translatomatic/locale.rb +28 -4
  53. data/lib/translatomatic/logger.rb +21 -13
  54. data/lib/translatomatic/model/locale.rb +5 -1
  55. data/lib/translatomatic/model/text.rb +2 -0
  56. data/lib/translatomatic/model.rb +1 -0
  57. data/lib/translatomatic/option.rb +75 -10
  58. data/lib/translatomatic/progress_updater.rb +7 -3
  59. data/lib/translatomatic/resource_file/base.rb +41 -18
  60. data/lib/translatomatic/resource_file/html.rb +11 -14
  61. data/lib/translatomatic/resource_file/markdown.rb +13 -12
  62. data/lib/translatomatic/resource_file/plist.rb +3 -14
  63. data/lib/translatomatic/resource_file/properties.rb +21 -3
  64. data/lib/translatomatic/resource_file/resw.rb +1 -0
  65. data/lib/translatomatic/resource_file/text.rb +1 -0
  66. data/lib/translatomatic/resource_file/xcode_strings.rb +23 -14
  67. data/lib/translatomatic/resource_file/xml.rb +34 -22
  68. data/lib/translatomatic/resource_file/yaml.rb +39 -5
  69. data/lib/translatomatic/resource_file.rb +7 -5
  70. data/lib/translatomatic/string.rb +40 -12
  71. data/lib/translatomatic/tmx/document.rb +11 -12
  72. data/lib/translatomatic/tmx/translation_unit.rb +5 -1
  73. data/lib/translatomatic/tmx.rb +2 -0
  74. data/lib/translatomatic/translation.rb +30 -0
  75. data/lib/translatomatic/translation_result.rb +45 -45
  76. data/lib/translatomatic/translator/base.rb +128 -83
  77. data/lib/translatomatic/translator/frengly.rb +62 -57
  78. data/lib/translatomatic/translator/google.rb +35 -31
  79. data/lib/translatomatic/translator/microsoft.rb +41 -33
  80. data/lib/translatomatic/translator/my_memory.rb +68 -64
  81. data/lib/translatomatic/translator/yandex.rb +56 -39
  82. data/lib/translatomatic/translator.rb +99 -63
  83. data/lib/translatomatic/util.rb +31 -1
  84. data/lib/translatomatic/version.rb +4 -1
  85. data/lib/translatomatic.rb +17 -0
  86. data/translatomatic.gemspec +5 -4
  87. metadata +56 -16
@@ -1,11 +1,14 @@
1
1
  require 'set'
2
2
 
3
- module Translatomatic
3
+ module Translatomatic
4
+ # Stores results of a translation
4
5
  class TranslationResult
6
+
7
+ # @return [Translatomatic::ResourceFile::Base] The resource file
8
+ attr_reader :file
5
9
 
6
- # Translation results
7
10
  # @return [Hash<String,String>] Translation results
8
- attr_reader :properties
11
+ attr_reader :properties
9
12
 
10
13
  # @return [Locale] The locale of the original strings
11
14
  attr_reader :from_locale
@@ -17,70 +20,67 @@ module Translatomatic
17
20
  attr_reader :untranslated
18
21
 
19
22
  # Create a translation result
20
- # @param [Hash<String,String>] properties Untranslated properties
21
- # @param [Locale] from_locale The locale of the untranslated strings
22
- # @param [Locale] to_locale The target locale
23
- def initialize(properties, from_locale, to_locale)
23
+ # @param file [Translatomatic::ResourceFile::Base] A resource file
24
+ # @param to_locale [Locale] The target locale
25
+ def initialize(file, to_locale)
26
+ @file = file
24
27
  @value_to_keys = {}
25
28
  @untranslated = Set.new
26
- @from_locale = from_locale
27
- @to_locale = to_locale
28
-
29
- # duplicate strings
30
- @properties = properties.transform_values { |i| i.dup }
31
-
32
- properties.each do |key, value|
33
- # split property value into sentences
34
- string = string(value, from_locale)
29
+ @from_locale = file.locale
30
+ @to_locale = to_locale
31
+
32
+ # duplicate strings
33
+ @properties = file.properties.transform_values { |i| i.dup }
34
+
35
+ @properties.each do |key, value|
36
+ # split property value into sentences
37
+ string = string(value, from_locale)
35
38
  string.sentences.each do |sentence|
36
39
  @untranslated << sentence
37
40
  keylist = (@value_to_keys[sentence.to_s] ||= [])
38
- keylist << key
41
+ keylist << key
39
42
  end
40
43
  end
41
44
  end
42
45
 
43
- # Update result with a list of translated strings.
44
- # @param [Array<String>] original Original strings
45
- # @param [Array<String>] translated Translated strings
46
+ # Update result with a list of translated strings.
47
+ # @param translations [Array<Translatomatic::Translation>] Translations
46
48
  # @return [void]
47
- def update_strings(original, translated)
48
- raise "strings length mismatch" unless original.length == translated.length
49
-
50
- # create list of [from, to] text conversions
51
- conversions = []
52
- original.zip(translated).each do |text1, text2|
53
- conversions << [text1, text2]
54
- end
55
-
56
- # sort conversion list by largest offset first so that we replace
49
+ def update_strings(translations)
50
+ # sort translation list by largest offset first so that we replace
57
51
  # from the end of the string to the front, so substring offsets
58
52
  # are correct in the target string.
59
- conversions.sort_by! do |t1, t2|
60
- t1.respond_to?(:offset) ? -t1.offset : 0
61
- end
62
53
 
63
- conversions.each do |text1, text2|
64
- update(text1, text2)
54
+ #translations.sort_by! do |translation|
55
+ # t1 = translation.original
56
+ # t1.respond_to?(:offset) ? -t1.offset : 0
57
+ #end
58
+ translations.sort_by! { |t| -t.original.offset }
59
+
60
+ translations.each do |translation|
61
+ update(translation.original, translation.result)
65
62
  end
66
- end
63
+ end
64
+
65
+ private
67
66
 
68
- private
69
-
70
67
  include Translatomatic::Util
71
68
 
72
69
  def update(original, translated)
73
70
  keys = @value_to_keys[original.to_s]
74
- raise "no key mapping for text '#{original}'" unless keys
75
- keys.each do |key|
71
+ raise "no key mapping for text '#{original}'" unless keys
72
+ keys.each do |key|
73
+ #value = @properties[key]
76
74
  if original.kind_of?(Translatomatic::String) && original.substring?
77
- @properties[key][original.offset, original.length] = translated
78
- else
79
- @properties[key] = translated
80
- end
75
+ #log.debug("#{value[original.offset, original.length]} -> #{translated}")
76
+ @properties[key][original.offset, original.length] = translated
77
+ else
78
+ #log.debug("#{key} -> #{translated}")
79
+ @properties[key] = translated
80
+ end
81
81
  end
82
82
 
83
- @untranslated.delete(original)
83
+ @untranslated.delete(original) unless translated.nil?
84
84
  end
85
85
  end
86
86
  end
@@ -1,83 +1,128 @@
1
- require 'bing_translator'
2
-
3
- module Translatomatic
4
- module Translator
5
- # @abstract
6
- class Base
7
-
8
- class << self
9
- attr_reader :options
10
- private
11
- include Translatomatic::DefineOptions
12
- end
13
-
14
- # @private
15
- attr_accessor :listener
16
-
17
- def initialize(options = {})
18
- @listener = options[:listener]
19
- end
20
-
21
- # @return [String] The name of this translator.
22
- def name
23
- self.class.name.demodulize
24
- end
25
-
26
- # @return [Array<String>] A list of languages supported by this translator.
27
- def languages
28
- []
29
- end
30
-
31
- # Translate strings from one locale to another
32
- # @param [Array<String>] strings A list of strings to translate.
33
- # @param [String, Translatomatic::Locale] from The locale of the given strings.
34
- # @param [String, Translatomatic::Locale] to The locale to translate to.
35
- # @return [Array<String>] Translated strings
36
- def translate(strings, from, to)
37
- strings = [strings] unless strings.kind_of?(Array)
38
- from = locale(from)
39
- to = locale(to)
40
- return strings if from.language == to.language
41
- translated = perform_translate(strings, from, to)
42
- update_translated(translated) unless @updated_listener
43
- translated
44
- end
45
-
46
- private
47
-
48
- include Translatomatic::Util
49
-
50
- # fetch translations for the given strings, one at a time, by
51
- # opening a http connection to the given url and calling
52
- # fetch_translation() on each string.
53
- # (subclass must implement fetch_translation if this method is used)
54
- def perform_fetch_translations(url, strings, from, to)
55
- translated = []
56
- request = Translatomatic::HTTPRequest.new(url)
57
- request.start do |http|
58
- strings.each do |string|
59
- result = fetch_translation(request, string, from, to)
60
- translated << result
61
- update_translated(result)
62
- end
63
- end
64
- translated
65
- end
66
-
67
- def fetch_translation(request, strings, from, to)
68
- raise "subclass must implement fetch_translation"
69
- end
70
-
71
- def update_translated(texts)
72
- texts = [texts] unless texts.kind_of?(Array)
73
- @updated_listener = true
74
- @listener.translated_texts(texts) if @listener
75
- end
76
-
77
- def perform_translate(strings, from, to)
78
- raise "subclass must implement perform_translate"
79
- end
80
-
81
- end
82
- end
83
- end
1
+ require 'bing_translator'
2
+
3
+ module Translatomatic
4
+ module Translator
5
+
6
+ # Base class for interfaces to translation APIs
7
+ # @abstract
8
+ class Base
9
+ include Translatomatic::DefineOptions
10
+
11
+ # Listener for translation events
12
+ attr_accessor :listener
13
+
14
+ def initialize(options = {})
15
+ @listener = options[:listener]
16
+ end
17
+
18
+ # @return [String] The name of this translator.
19
+ def name
20
+ self.class.name.demodulize
21
+ end
22
+
23
+ # @return [Array<String>] A list of languages supported by this translator.
24
+ def languages
25
+ []
26
+ end
27
+
28
+ # Translate strings from one locale to another
29
+ # @param strings [Array<String>] A list of strings to translate.
30
+ # @param from [String, Translatomatic::Locale] The locale of the given strings.
31
+ # @param to [String, Translatomatic::Locale] The locale to translate to.
32
+ # @return [Array<String>] Translated strings
33
+ def translate(strings, from, to)
34
+ @updated_listener = false
35
+ strings = [strings] unless strings.kind_of?(Array)
36
+ from = locale(from)
37
+ to = locale(to)
38
+ return strings if from.language == to.language
39
+ translated = perform_translate(strings, from, to)
40
+ update_translated(translated) unless @updated_listener
41
+ translated
42
+ end
43
+
44
+ private
45
+
46
+ include Translatomatic::Util
47
+
48
+ TRANSLATION_RETRIES = 3
49
+
50
+ # Fetch translations for the given strings, one at a time, by
51
+ # opening a http connection to the given url and calling
52
+ # fetch_translation() on each string. Error handling and recovery
53
+ # is performed by this method.
54
+ # (subclass must implement fetch_translation if this method is used)
55
+ def perform_fetch_translations(url, strings, from, to)
56
+ translated = []
57
+ untranslated = strings.dup
58
+ request = Translatomatic::HTTPRequest.new(url)
59
+
60
+ while !untranslated.empty? # request start block
61
+ fail_count = 0 # number of consecutive translation failures
62
+
63
+ request.start do |http|
64
+ while !untranslated.empty?
65
+ # get next string to translate
66
+ string = untranslated[0]
67
+ begin
68
+ # fetch translation
69
+ result = fetch_translation(request, string, from, to)
70
+
71
+ # successful translation
72
+ fail_count = 0 # reset fail count
73
+ translated << result
74
+ update_translated(result)
75
+ untranslated.shift
76
+
77
+ rescue Exception => e
78
+ # translation error
79
+ log.error(e)
80
+ fail_count += 1
81
+ if fail_count >= TRANSLATION_RETRIES
82
+ raise e # re-raise exception
83
+ else
84
+ # need to restart http connection
85
+ # break back out to request.start block
86
+ break
87
+ end
88
+ end # exception
89
+ end # while untranslated
90
+ end # request.start
91
+ end # while untranslated
92
+
93
+ translated
94
+ end
95
+
96
+ def fetch_translation(request, strings, from, to)
97
+ raise "subclass must implement fetch_translation"
98
+ end
99
+
100
+ def update_translated(texts)
101
+ texts = [texts] unless texts.kind_of?(Array)
102
+ @updated_listener = true
103
+ @listener.translated_texts(texts) if @listener
104
+ end
105
+
106
+ def perform_translate(strings, from, to)
107
+ raise "subclass must implement perform_translate"
108
+ end
109
+
110
+ # Attempt to run a block of code up to retries times.
111
+ # Reraises the exception if the block fails retries times.
112
+ # @param retries [Number] The maximum number of times to run
113
+ # @return [Object] the return value of the block
114
+ def attempt_with_retries(retries)
115
+ fail_count = 0
116
+ begin
117
+ yield
118
+ rescue Exception => e
119
+ log.error(e.message)
120
+ fail_count += 1
121
+ retry if fail_count < retries
122
+ raise e
123
+ end
124
+ end
125
+
126
+ end
127
+ end
128
+ end
@@ -1,57 +1,62 @@
1
- require 'net/http'
2
-
3
- module Translatomatic
4
- module Translator
5
-
6
- class Frengly < Base
7
-
8
- define_options({
9
- name: :frengly_api_key, desc: "Frengly API key", use_env: true
10
- },
11
- { name: :frengly_email, desc: "Email address", use_env: true
12
- },
13
- { name: :frengly_password, desc: "Password", use_env: true
14
- })
15
-
16
- # Create a new Frengly translator instance
17
- def initialize(options = {})
18
- super(options)
19
- @key = options[:frengly_api_key] || ENV["FRENGLY_API_KEY"] # optional
20
- @email = options[:frengly_email]
21
- @password = options[:frengly_password]
22
- raise "email address required" unless @email
23
- raise "password required" unless @password
24
- end
25
-
26
- # (see Translatomatic::Translator::Base#languages)
27
- def languages
28
- ['en','fr','de','es','pt','it','nl','tl','fi','el','iw','pl','ru','sv']
29
- end
30
-
31
- private
32
-
33
- URL = 'http://frengly.com/frengly/data/translateREST'
34
-
35
- def perform_translate(strings, from, to)
36
- perform_fetch_translations(URL, strings, from, to)
37
- end
38
-
39
- def fetch_translation(request, string, from, to)
40
- body = {
41
- src: from.language,
42
- dest: to.language,
43
- text: string,
44
- email: @email,
45
- password: @password,
46
- premiumkey: @key
47
- }.to_json
48
-
49
- # TODO: work out what the response looks like
50
- response = request.post(body, content_type: 'application/json')
51
- data = JSON.parse(response.body)
52
- data['text']
53
- end
54
-
55
- end # class
56
- end # module
57
- end
1
+ require 'net/http'
2
+
3
+ module Translatomatic
4
+ module Translator
5
+
6
+ # Interface to the Frengly translation API
7
+ # @see http://www.frengly.com/api
8
+ class Frengly < Base
9
+
10
+ define_options({
11
+ name: :frengly_api_key, use_env: true,
12
+ desc: t("translator.frengly_api_key"),
13
+ },
14
+ { name: :frengly_email, use_env: true,
15
+ desc: t("translator.email_address")
16
+ },
17
+ { name: :frengly_password, use_env: true,
18
+ desc: t("translator.password")
19
+ })
20
+
21
+ # Create a new Frengly translator instance
22
+ def initialize(options = {})
23
+ super(options)
24
+ @key = options[:frengly_api_key] || ENV["FRENGLY_API_KEY"] # optional
25
+ @email = options[:frengly_email]
26
+ @password = options[:frengly_password]
27
+ raise t("translator.email_required") unless @email
28
+ raise t("translator.password_required") unless @password
29
+ end
30
+
31
+ # (see Translatomatic::Translator::Base#languages)
32
+ def languages
33
+ ['en','fr','de','es','pt','it','nl','tl','fi','el','iw','pl','ru','sv']
34
+ end
35
+
36
+ private
37
+
38
+ URL = 'http://frengly.com/frengly/data/translateREST'
39
+
40
+ def perform_translate(strings, from, to)
41
+ perform_fetch_translations(URL, strings, from, to)
42
+ end
43
+
44
+ def fetch_translation(request, string, from, to)
45
+ body = {
46
+ src: from.language,
47
+ dest: to.language,
48
+ text: string,
49
+ email: @email,
50
+ password: @password,
51
+ premiumkey: @key
52
+ }.to_json
53
+
54
+ # TODO: work out what the response looks like
55
+ response = request.post(body, content_type: 'application/json')
56
+ data = JSON.parse(response.body)
57
+ data['text']
58
+ end
59
+
60
+ end # class
61
+ end # module
62
+ end
@@ -1,31 +1,35 @@
1
- module Translatomatic
2
- module Translator
3
-
4
- class Google < Base
5
-
6
- define_options({ name: :google_api_key, desc: "Google API key",
7
- use_env: true
8
- })
9
-
10
- # Create a new Google translator instance
11
- def initialize(options = {})
12
- super(options)
13
- key = options[:google_api_key] || ENV["GOOGLE_API_KEY"]
14
- raise "google api key required" if key.nil?
15
- EasyTranslate.api_key = key
16
- end
17
-
18
- # (see Translatomatic::Translator::Base#languages)
19
- def languages
20
- EasyTranslate::LANGUAGES.keys
21
- end
22
-
23
- private
24
-
25
- def perform_translate(strings, from, to)
26
- EasyTranslate.translate(strings, from: from.language, to: to.language)
27
- end
28
-
29
- end # class
30
- end # module
31
- end
1
+ module Translatomatic
2
+ module Translator
3
+
4
+ # Interface to the Google translation API
5
+ # @see https://cloud.google.com/translate/
6
+ class Google < Base
7
+
8
+ define_options(name: :google_api_key,
9
+ desc: t("translator.google_api_key"), use_env: true
10
+ )
11
+
12
+ # Create a new Google translator instance
13
+ def initialize(options = {})
14
+ super(options)
15
+ key = options[:google_api_key] || ENV["GOOGLE_API_KEY"]
16
+ raise t("translator.google_key_required") if key.nil?
17
+ EasyTranslate.api_key = key
18
+ end
19
+
20
+ # (see Translatomatic::Translator::Base#languages)
21
+ def languages
22
+ EasyTranslate::LANGUAGES.keys
23
+ end
24
+
25
+ private
26
+
27
+ def perform_translate(strings, from, to)
28
+ attempt_with_retries(3) do
29
+ EasyTranslate.translate(strings, from: from.language, to: to.language)
30
+ end
31
+ end
32
+
33
+ end # class
34
+ end # module
35
+ end
@@ -1,33 +1,41 @@
1
- require 'bing_translator'
2
-
3
- module Translatomatic
4
- module Translator
5
-
6
- class Microsoft < Base
7
-
8
- define_options({
9
- name: :microsoft_api_key, desc: "Microsoft API key", use_env: true
10
- })
11
-
12
- # Create a new Microsoft translator instance
13
- def initialize(options = {})
14
- super(options)
15
- key = options[:microsoft_api_key] || ENV["MICROSOFT_API_KEY"]
16
- raise "microsoft api key required" if key.nil?
17
- @impl = BingTranslator.new(key)
18
- end
19
-
20
- # TODO: implement language list
21
- # (see Translatomatic::Translator::Base#languages)
22
- #def languages
23
- #end
24
-
25
- private
26
-
27
- def perform_translate(strings, from, to)
28
- @impl.translate_array(strings, from: from.language, to: to.language)
29
- end
30
-
31
- end
32
- end
33
- end
1
+ require 'bing_translator'
2
+
3
+ module Translatomatic
4
+ module Translator
5
+
6
+ # Interface to the Microsoft translation API
7
+ # @see https://www.microsoft.com/en-us/translator/translatorapi.aspx
8
+ class Microsoft < Base
9
+
10
+ define_options({
11
+ name: :microsoft_api_key, desc: t("translator.microsoft_api_key"), use_env: true
12
+ })
13
+
14
+ # Create a new Microsoft translator instance
15
+ def initialize(options = {})
16
+ super(options)
17
+ key = options[:microsoft_api_key] || ENV["MICROSOFT_API_KEY"]
18
+ raise t("translator.microsoft_key_required") if key.nil?
19
+ @impl = BingTranslator.new(key)
20
+ end
21
+
22
+ # TODO: implement language list
23
+ # (see Translatomatic::Translator::Base#languages)
24
+ #def languages
25
+ #end
26
+
27
+ private
28
+
29
+ def perform_translate(strings, from, to)
30
+ attempt_with_retries(3) do
31
+ translations = @impl.translate_array(strings,
32
+ from: from.language,
33
+ to: to.language
34
+ )
35
+ translations.collect { |i| i.to_s }
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end