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,66 @@
1
+ module Translatomatic
2
+ # A collection of texts
3
+ class TextCollection
4
+ attr_reader :originals
5
+
6
+ def initialize(texts = [])
7
+ texts = [texts] unless texts.is_a?(Array)
8
+ texts = texts.select { |i| translatable?(i) }
9
+ texts = textify(texts) # convert to Text objects
10
+ @originals = texts
11
+ @sentences = find_sentences(texts) # convert to sentences
12
+ contexts = find_contexts(texts)
13
+ @all_texts = @sentences + contexts
14
+ group_by_locale(@all_texts)
15
+ end
16
+
17
+ # Iterate over texts in the collection grouped by locale
18
+ def each_locale
19
+ @by_locale.each do |locale, list|
20
+ yield locale, list
21
+ end
22
+ end
23
+
24
+ # @return [Number] The total number of texts in the collection,
25
+ # equal to the number of sentences and context strings.
26
+ def count
27
+ @all_texts.length
28
+ end
29
+
30
+ private
31
+
32
+ def translatable?(text)
33
+ # don't translate numbers
34
+ text && !text.match(/\A\s*\z/) && !text.match(/\A[\d,]+\z/)
35
+ end
36
+
37
+ def textify(texts)
38
+ texts.collect { |i| build_text(i) }
39
+ end
40
+
41
+ def find_sentences(texts)
42
+ texts.collect(&:sentences).flatten.uniq
43
+ end
44
+
45
+ def find_contexts(texts)
46
+ texts.collect { |i| build_text(i.context) }.flatten.uniq.compact
47
+ end
48
+
49
+ def group_by_locale(texts)
50
+ @by_locale = {}
51
+ texts.each do |text|
52
+ list = @by_locale[text.locale] ||= []
53
+ list << text
54
+ end
55
+ end
56
+
57
+ def build_text(value, locale = Locale.default)
58
+ return nil if value.nil?
59
+ if value.is_a?(Translatomatic::Text)
60
+ value
61
+ else
62
+ Translatomatic::Text.new(value, locale)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,6 +1,8 @@
1
- # Translation Memory Exchange
2
- # @see https://en.wikipedia.org/wiki/Translation_Memory_eXchange
3
- module Translatomatic::TMX; end
1
+ module Translatomatic
2
+ # Translation Memory Exchange
3
+ # @see https://en.wikipedia.org/wiki/Translation_Memory_eXchange
4
+ module TMX; end
5
+ end
4
6
 
5
7
  require 'translatomatic/tmx/translation_unit'
6
8
  require 'translatomatic/tmx/document'
@@ -1,99 +1,124 @@
1
- module Translatomatic::TMX
2
- # Translation Memory Exchange document
3
- class Document
4
-
5
- # Create a new instance
6
- # @param units [Array<TranslationUnit>] A list of translation units
7
- # @param source_locale [Locale] Source locale
8
- # @return [Translatomatic::TMX::Document] a new TMX object
9
- def initialize(units, source_locale)
10
- units = [units] unless units.kind_of?(Array)
11
- @units = units
12
- @source_locale = source_locale
13
- end
1
+ module Translatomatic
2
+ module TMX
3
+ # Translation Memory Exchange document
4
+ class Document
5
+ # Create a new instance
6
+ # @param units [Array<TranslationUnit>] A list of translation units
7
+ # @param source_locale [Locale] Source locale
8
+ # @return [Translatomatic::TMX::Document] a new TMX object
9
+ def initialize(units, source_locale)
10
+ units = [units] unless units.is_a?(Array)
11
+ @units = units
12
+ @source_locale = source_locale
13
+ end
14
14
 
15
- # @return [String] An XML string
16
- def to_xml(options = {})
17
- builder = Nokogiri::XML::Builder.new do |xml|
18
- dtd = options[:dtd] || TMX_DTD
19
- xml.doc.create_internal_subset('tmx', nil, dtd)
20
- xml.tmx(version: "1.4") do
21
- xml.header(creationtool: "Translatomatic",
22
- creationtoolversion: Translatomatic::VERSION,
23
- datatype: "PlainText",
24
- segtype: "phrase", # default segtype
25
- adminlang: @source_locale.to_s,
26
- srclang: @source_locale.to_s,
27
- "o-tmf": DEFAULT_OTMF
28
- )
29
- xml.body { tmx_body(xml) }
15
+ # @return [String] An XML string
16
+ def to_xml(options = {})
17
+ builder = Nokogiri::XML::Builder.new do |xml|
18
+ dtd = options[:dtd] || TMX_DTD
19
+ xml.doc.create_internal_subset('tmx', nil, dtd)
20
+ xml.tmx(version: '1.4') do
21
+ xml.header(tmx_header)
22
+ xml.body { tmx_body(xml) }
23
+ end
30
24
  end
25
+ builder.to_xml
31
26
  end
32
- builder.to_xml
33
- end
34
27
 
35
- # Create a TMX document from the given converter
36
- # @param texts [Array<Translatomatic::Model::Text>] List of texts
37
- # @return [Translatomatic::TMX::Document] TMX document
38
- def self.from_texts(texts)
39
- # group texts by from_text_id to create units
40
- # source_locale: use from_text.locale
41
- # origin: use text.translator
42
- sources = texts.select { |i| i.from_text.nil? }
43
- source_locales = sources.collect { |i| i.locale }.uniq
44
- raise t("tmx.multiple_locales") if source_locales.length > 1
45
- units = units_from_texts(texts)
46
-
47
- return new(units, source_locales[0])
48
- end
28
+ # Create a TMX document from the given converter
29
+ # @param texts [Array<Translatomatic::Model::Text>] List of texts
30
+ # @return [Translatomatic::TMX::Document] TMX document
31
+ def self.from_texts(texts)
32
+ # group texts by from_text_id to create units
33
+ # source_locale: use from_text.locale
34
+ # origin: use text.provider
35
+ sources = texts.select { |i| i.from_text.nil? }
36
+ source_locales = sources.collect(&:locale).uniq
37
+ raise t('tmx.multiple_locales') if source_locales.length > 1
38
+ units = units_from_texts(texts)
49
39
 
50
- def self.valid?(xml)
51
- options = Nokogiri::XML::ParseOptions::DTDVALID
52
- doc = Nokogiri::XML::Document.parse(xml, nil, nil, options)
53
- doc.internal_subset.validate(doc)
54
- end
40
+ new(units, source_locales[0])
41
+ end
55
42
 
56
- private
43
+ # Create a TMX document from a translation collection
44
+ # @param collection [Translatomatic::Translation::Collection]
45
+ # Translation collection
46
+ # @return [Translatomatic::TMX::Document] TMX document
47
+ def self.from_collection(collection)
48
+ originals = collection.translations.collect(&:original)
49
+ source_locales = originals.collect(&:locale)
50
+ raise t('tmx.multiple_locales') if source_locales.length > 1
51
+ units = units_from_collection(collection)
57
52
 
58
- class << self
59
- include Translatomatic::Util
60
- end
53
+ new(units, source_locales[0])
54
+ end
61
55
 
62
- TMX_DTD = "http://www.ttt.org/oscarstandards/tmx/tmx14.dtd"
63
- DEFAULT_OTMF = "Translatomatic"
56
+ def self.valid?(xml)
57
+ options = Nokogiri::XML::ParseOptions::DTDVALID
58
+ doc = Nokogiri::XML::Document.parse(xml, nil, nil, options)
59
+ doc.internal_subset.validate(doc)
60
+ end
64
61
 
65
- def tmx_body(xml)
66
- @units.each do |unit|
67
- xml.tu("segtype": unit.strings[0].type) do
68
- unit.strings.each do |string|
69
- xml.tuv("xml:lang": string.locale.to_s) do
70
- xml.seg string.value
71
- end
62
+ private
63
+
64
+ class << self
65
+ include Translatomatic::Util
66
+
67
+ private
68
+
69
+ # @return [Array<Translatomatic::TMX::TranslationUnit]
70
+ # translation unit list
71
+ def units_from_texts(texts)
72
+ # group texts by from_text_id
73
+ texts_by_from_id = {}
74
+ texts.each do |text|
75
+ id = text.from_text_id || text.id
76
+ list = (texts_by_from_id[id] ||= [])
77
+ list << text
78
+ end
79
+
80
+ # create list of Translation Units
81
+ texts_by_from_id.values.collect do |list|
82
+ strings = list.uniq.collect { |i| build_text(i.value, i.locale) }
83
+ tmx_unit(strings)
72
84
  end
73
85
  end
74
- end
75
- end
76
86
 
77
- # @return [Array<Translatomatic::TMX::TranslationUnit] translation unit list
78
- def self.units_from_texts(texts)
79
- # group texts by from_text_id
80
- texts_by_from_id = {}
81
- texts.each do |text|
82
- id = text.from_text_id || text.id
83
- list = (texts_by_from_id[id] ||= [])
84
- list << text
87
+ # @return [Array<Translatomatic::TMX::TranslationUnit]
88
+ # translation unit list
89
+ def units_from_collection(collection); end
90
+
91
+ def tmx_unit(strings)
92
+ Translatomatic::TMX::TranslationUnit.new(strings)
93
+ end
85
94
  end
86
95
 
87
- # create list of Translation Units
88
- texts_by_from_id.values.collect do |list|
89
- strings = list.uniq.collect { |i| string(i.value, i.locale) }
90
- tmx_unit(strings)
96
+ TMX_DTD = 'http://www.ttt.org/oscarstandards/tmx/tmx14.dtd'.freeze
97
+ DEFAULT_OTMF = 'Translatomatic'.freeze
98
+
99
+ def tmx_header
100
+ {
101
+ creationtool: 'Translatomatic',
102
+ creationtoolversion: Translatomatic::VERSION,
103
+ datatype: 'PlainText',
104
+ segtype: 'phrase', # default segtype
105
+ adminlang: @source_locale.to_s,
106
+ srclang: @source_locale.to_s,
107
+ 'o-tmf' => DEFAULT_OTMF
108
+ }
91
109
  end
92
- end
93
110
 
94
- def self.tmx_unit(strings)
95
- Translatomatic::TMX::TranslationUnit.new(strings)
111
+ def tmx_body(xml)
112
+ @units.each do |unit|
113
+ xml.tu('segtype' => unit.strings[0].type) do
114
+ unit.strings.each do |string|
115
+ xml.tuv('xml:lang' => string.locale.to_s) do
116
+ xml.seg string.value
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
96
122
  end
97
-
98
- end # class
99
- end # module
123
+ end
124
+ end
@@ -1,23 +1,24 @@
1
- module Translatomatic::TMX
2
- # A TMX Translation Unit.
3
- # A translation unit contains a list of strings, and is part of a TMX
4
- # document.
5
- # @see Translatomatic::TMX::Document
6
- class TranslationUnit
1
+ module Translatomatic
2
+ module TMX
3
+ # A TMX Translation Unit.
4
+ # A translation unit contains a list of strings, and is part of a TMX
5
+ # document.
6
+ # @see Translatomatic::TMX::Document
7
+ class TranslationUnit
8
+ # @return [Array<Translatomatic::Text>] Strings in this translation unit
9
+ attr_reader :strings
7
10
 
8
- # @return [Array<Translatomatic::String>] Strings in this translation unit
9
- attr_reader :strings
11
+ # @param strings [Array<Translatomatic::Text>] List of strings
12
+ def initialize(strings)
13
+ @strings = strings || []
14
+ end
10
15
 
11
- # @param strings [Array<Translatomatic::String>] List of strings
12
- def initialize(strings)
13
- @strings = strings || []
14
- end
15
-
16
- # Test translation unit validity.
17
- # A translation unit must contain at least two strings.
18
- # @return [boolean] true if this translation unit is valid
19
- def valid?
20
- @strings.length >= 2
16
+ # Test translation unit validity.
17
+ # A translation unit must contain at least two strings.
18
+ # @return [boolean] true if this translation unit is valid
19
+ def valid?
20
+ @strings.length >= 2
21
+ end
21
22
  end
22
23
  end
23
24
  end
@@ -1,30 +1,10 @@
1
1
  module Translatomatic
2
- # Data object describing a text translation
3
- class Translation
4
- # @return [Translatomatic::String] original string
5
- attr_reader :original
6
-
7
- # @return [Translatomatic::String] translated string
8
- attr_accessor :result
9
-
10
- # @return [Symbol] The name of the translator
11
- attr_reader :translator
12
-
13
- # @return [boolean] True if this translation came from the database
14
- attr_reader :from_database
15
-
16
- def initialize(original, result, translator = nil, from_database = false)
17
- @original = original
18
- @result = result
19
- @translator = translator
20
- @from_database = from_database
21
- end
22
-
23
- private
24
-
25
- def string(string)
26
- string.kind_of?(Translatomatic::String) ? string : Translatomatic::String.new(string)
27
- end
28
-
29
- end
2
+ # Classes for translating strings
3
+ module Translation; end
30
4
  end
5
+
6
+ require 'translatomatic/translation/collection'
7
+ require 'translatomatic/translation/munging'
8
+ require 'translatomatic/translation/fetcher'
9
+ require 'translatomatic/translation/result'
10
+ require 'translatomatic/translation/stats'
@@ -0,0 +1,199 @@
1
+ require 'set'
2
+
3
+ module Translatomatic
4
+ module Translation
5
+ # Stores results of translations.
6
+ # For each original text, there may be zero or more translations from
7
+ # one or more providers.
8
+ class Collection
9
+ # Create a translation collection
10
+ def initialize
11
+ # by_provider[provider] = [Result, ...]
12
+ @by_provider = {}
13
+ # by_original[text] = [Result, ...]
14
+ @by_original = {}
15
+ end
16
+
17
+ # @param string [String,Text] Original string
18
+ # @return [Array<Result>] All translations for the given string
19
+ def [](string)
20
+ @by_original[string.to_s]
21
+ end
22
+
23
+ def empty?
24
+ @by_original.empty?
25
+ end
26
+
27
+ # @param string [String,Text] Original string
28
+ # @param locale [Locale] Target locale
29
+ # @return [Result] The best translation for the given string
30
+ def get(string, locale)
31
+ locale = build_locale(locale)
32
+ list = @by_original[string.to_s] || []
33
+ list = sort_by_best_match(list)
34
+ if string.is_a?(Translatomatic::Text) && string.context
35
+ # string has a context
36
+ list = sort_by_context_match(list, string.context, locale)
37
+ end
38
+ list.find { |i| i.result.locale == locale }
39
+ end
40
+
41
+ # Add a list of translations to the collection
42
+ # @param translations [Array<Result>] Translation results
43
+ # @return [void]
44
+ def add(translations)
45
+ translations = [translations] unless translations.is_a?(Array)
46
+ translations.each do |tr|
47
+ next if tr.result.nil?
48
+ add_to_list(@by_provider, tr.provider, tr)
49
+ add_to_list(@by_original, tr.original, tr)
50
+ end
51
+ end
52
+
53
+ # @return [Number] The number of translations
54
+ def count
55
+ translations.length
56
+ end
57
+
58
+ # @return [Boolean] True if there is one or more translations
59
+ def present?
60
+ count > 0
61
+ end
62
+
63
+ # Get translations from this collection. If provider is specified,
64
+ # returns only translations from the given provider, otherwise all
65
+ # translations are returned.
66
+ # @param provider [String] Optional name of a provider.
67
+ # @return [Array<Result>] Translation results
68
+ def translations(provider = nil)
69
+ if provider.nil?
70
+ @by_provider.values.flatten
71
+ else
72
+ @by_provider[provider.to_s] || []
73
+ end
74
+ end
75
+
76
+ # Get a list of the best sentence translations for the given
77
+ # parent string.
78
+ # @param parent [Text] Parent text
79
+ # @param locale [Locale] Target locale
80
+ # @return [Array<Result>] Substring translation results
81
+ def sentences(parent, locale)
82
+ parent.sentences.collect do |sentence|
83
+ # get translation for sentence
84
+ translation = get(sentence, locale)
85
+ # create a new translation with the sentence as the original
86
+ # string, so that we can rely on the offset value.
87
+ if translation
88
+ Result.new(sentence, translation.result, translation.provider,
89
+ from_database: translation.from_database)
90
+ end
91
+ end.compact
92
+ end
93
+
94
+ # Return a new collection with only the translations that came from
95
+ # providers (not from the database).
96
+ # @return [Collection] The collection result
97
+ def from_providers
98
+ result = self.class.new
99
+ provider_translations = translations.reject(&:from_database)
100
+ result.add(provider_translations)
101
+ result
102
+ end
103
+
104
+ # @return [Array<Result>] Best translations for each string
105
+ # @param locale [Locale] Target locale
106
+ def best_translations(locale)
107
+ @by_original.keys.collect { |i| get(i, locale) }
108
+ end
109
+
110
+ # @return [Array<String>] A list of providers that translations were
111
+ # sourced from.
112
+ def providers
113
+ @by_provider.keys
114
+ end
115
+
116
+ # Combine this collection with another
117
+ # @param other [Collection] Another collection
118
+ # @return [Collection] The collection result
119
+ def +(other)
120
+ result = self.class.new
121
+ @by_provider.each_value { |i| result.add(i) }
122
+ other.translations.each { |i| result.add(i) }
123
+ result
124
+ end
125
+
126
+ # @param string [String,Text] Original string
127
+ # @param provider [String] Provider name
128
+ # @return [boolean] True if there is a translation for the given string.
129
+ def translated?(string, provider)
130
+ list = @by_provider[provider.to_s] || []
131
+ list.any? { |tr| tr.original.to_s == string.to_s }
132
+ end
133
+
134
+ # @return [String] String description of all translations
135
+ def description
136
+ translations.collect(&:description).join("\n")
137
+ end
138
+
139
+ private
140
+
141
+ include Translatomatic::Util
142
+
143
+ # sort the list of translations by comparing to the translation for
144
+ # the string context.
145
+ # for example:
146
+ # context is 'go right' with a translation of 'Geh rechts'.
147
+ # translation of 'right' is 'rechts', and 'richtig'.
148
+ # translation 'rechts' will be ordered first, as 'rechts' is in
149
+ # the translated context string (and is longer than 'recht').
150
+ def sort_by_context_match(list, context, locale)
151
+ return list if list.blank?
152
+ context_results = context_translation_results(list, context, locale)
153
+ # log.debug("context translations: #{context_results}")
154
+ # put translations that include the context string(s) first.
155
+ # also put longer translations that include the context string first.
156
+ # (fixes matching 'rechts' before 'recht')
157
+ list.sort_by do |tr|
158
+ tr_result = tr.result.downcase
159
+ ctx_match = context_results.any? { |ctx| ctx.include?(tr_result) }
160
+ ctx_match ? -tr_result.length : 1
161
+ end
162
+ end
163
+
164
+ # sort the list of translations by finding the most common
165
+ # translation. if there is an equal number of different translations
166
+ # use the first most common translation (first provider).
167
+ def sort_by_best_match(list)
168
+ by_count = {}
169
+ list.each do |tr|
170
+ key = tr.result.downcase
171
+ by_count[key] = (by_count[key] || 0) + 1
172
+ end
173
+
174
+ # not using sort_by to maintain original sort order
175
+ # unless count is different.
176
+ list.sort do |tr1, tr2|
177
+ by_count[tr1.result.downcase] <=> by_count[tr2.result.downcase]
178
+ end
179
+ end
180
+
181
+ # @param list [Array<Result>] translations
182
+ # @param context [Array<String>] context(s)
183
+ # @return [Array<String>] lowercased context translation strings
184
+ def context_translation_results(list, context, locale)
185
+ context_locale = list[0].original.locale
186
+ context = [context] unless context.is_a?(Array)
187
+ context.collect do |ctx|
188
+ context_tr = get(Text[ctx, context_locale], locale)
189
+ context_tr.result.downcase if context_tr
190
+ end.compact
191
+ end
192
+
193
+ def add_to_list(hash, key, value)
194
+ list = hash[key.to_s] ||= []
195
+ list << value
196
+ end
197
+ end
198
+ end
199
+ end