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,123 @@
1
+ module Translatomatic
2
+ module Translation
3
+ # Fetches translations from the database and translation providers
4
+ class Fetcher
5
+ def initialize(options = {})
6
+ ATTRIBUTES.each do |i|
7
+ instance_variable_set("@#{i}", options[i])
8
+ end
9
+ end
10
+
11
+ # Fetch a list of translations for all texts given in the constructor
12
+ # for all providers.
13
+ # Translations are fetched from the database first, then from providers.
14
+ # @return [Array<Result>] List of translations
15
+ def translations
16
+ collection = Collection.new
17
+
18
+ # add all translations from the database to the collection
19
+ collection.add(find_database_translations(@texts)) if @use_db
20
+
21
+ # request translations for all texts that aren't in the database
22
+ untranslated = untranslated(collection)
23
+ if untranslated.present?
24
+ provider_translations = find_provider_translations(untranslated)
25
+ save_database_translations(provider_translations)
26
+ collection.add(provider_translations)
27
+ end
28
+
29
+ # puts collection.description
30
+ collection
31
+ end
32
+
33
+ private
34
+
35
+ include Util
36
+ include Munging
37
+
38
+ ATTRIBUTES = %i[provider texts from_locale to_locale
39
+ use_db listener].freeze
40
+
41
+ # find texts that we do not have translations for
42
+ # @param collection [Collection] Translation collection
43
+ # @return [Array<String>] Untranslated texts
44
+ def untranslated(collection)
45
+ @texts.reject { |i| collection.translated?(i, @provider.name) }
46
+ end
47
+
48
+ # @return [Array<Result>] translations from the database
49
+ def find_database_translations(texts)
50
+ from = db_locale(@from_locale)
51
+ to = db_locale(@to_locale)
52
+
53
+ db_texts = Translatomatic::Model::Text.where(
54
+ locale: to, provider: @provider.name,
55
+ from_texts_texts: {
56
+ locale_id: from,
57
+ # convert untranslated texts to strings
58
+ value: texts.collect(&:to_s)
59
+ }
60
+ ).joins(:from_text).includes(:from_text)
61
+
62
+ texts_to_translations(db_texts, texts)
63
+ end
64
+
65
+ # @return [Array<Result>] translations from provider
66
+ def find_provider_translations(texts)
67
+ texts = wrap_notranslate(texts)
68
+ translations = @provider.translate(
69
+ texts, @from_locale, @to_locale
70
+ )
71
+ # check for valid response from provider and restore variables
72
+ translations.each do |tr|
73
+ raise t('provider.invalid_response') unless tr.is_a?(Result)
74
+ end
75
+ munge_translation_results(translations)
76
+ end
77
+
78
+ # use the original text from the translation rather than
79
+ # db_text.from_text.value, as the original string has required
80
+ # information such as offset and context.
81
+ def texts_to_translations(db_texts, texts)
82
+ db_text_map = hashify(db_texts, proc { |i| i.from_text.value })
83
+ texts.collect do |text|
84
+ next unless (db_text = db_text_map[text.to_s])
85
+ @listener.update_progress(1) if @listener
86
+ provider = db_text.provider
87
+ translation = build_text(db_text.value, @to_locale)
88
+ Result.new(text, translation, provider, from_database: true)
89
+ end.compact
90
+ end
91
+
92
+ def save_database_translations(translations)
93
+ return unless @use_db
94
+ ActiveRecord::Base.transaction do
95
+ from = db_locale(@from_locale)
96
+ to = db_locale(@to_locale)
97
+ translations.each do |tr|
98
+ next if tr.result.nil? # skip invalid translations
99
+ save_database_translation(from, to, tr)
100
+ end
101
+ end
102
+ end
103
+
104
+ def save_database_translation(from_locale, to_locale, translation)
105
+ original_text = Translatomatic::Model::Text.find_or_create_by!(
106
+ locale: from_locale,
107
+ value: translation.original.to_s
108
+ )
109
+
110
+ text = Translatomatic::Model::Text.find_or_create_by!(
111
+ locale: to_locale, value: translation.result.to_s,
112
+ from_text: original_text,
113
+ provider: @provider.name
114
+ )
115
+ text
116
+ end
117
+
118
+ def db_locale(locale)
119
+ Translatomatic::Model::Locale.from_tag(locale)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,112 @@
1
+ module Translatomatic
2
+ module Translation
3
+ # Translation result mungification code
4
+ module Munging
5
+ private
6
+
7
+ # @private
8
+ NoTranslateTag = Struct.new(:leading_space, :trailing_space)
9
+
10
+ def munge_translation_results(translations)
11
+ if use_notranslate?
12
+ translations.collect { |tr| remove_notranslate(tr) }
13
+ else
14
+ translations.select { |tr| restore_preserved_text(tr) }
15
+ end
16
+ end
17
+
18
+ def use_notranslate?
19
+ @provider.class.supports_no_translate_html?
20
+ end
21
+
22
+ # Restore parts of text that should be preserved in the translation.
23
+ # Used for providers that don't support translate="no" html5 attribute.
24
+ # This works when the translator preserves parts of the string that
25
+ # match the preserve_regex, e.g. for variables like '%{name}' if the
26
+ # translation matches /%{.*}/ then the original text can be restored.
27
+ # @return [Boolean] True if no errors were encountered
28
+ def restore_preserved_text(tr)
29
+ preserve_regex = tr.original.preserve_regex
30
+ return true unless preserve_regex
31
+
32
+ # find parts to preserve in the original string
33
+ list1 = tr.original.substrings(preserve_regex)
34
+ # find corresponding parts in the translated string
35
+ list2 = tr.result.substrings(preserve_regex)
36
+
37
+ return false unless list1.length == list2.length
38
+
39
+ # we can restore text. sort by largest offset first.
40
+ conversions = list1.zip(list2).collect.sort_by { |i| -i[0].offset }
41
+ conversions.each do |v1, v2|
42
+ tr.result[v2.offset, v2.length] = v1.value
43
+ end
44
+ true
45
+ end
46
+
47
+ # @param texts [Array<Text>] Texts to modify
48
+ # @return [Array<Text>] Texts with sections to preserve wrapped in a
49
+ # notranslate directive.
50
+ def wrap_notranslate(texts)
51
+ return texts unless use_notranslate? && texts.any?(&:preserve_regex)
52
+ texts.collect do |text|
53
+ if text.preserve_regex
54
+ text.gsub(text.preserve_regex) do |i|
55
+ '<span translate="no">' + i[0] + '</span>'
56
+ end
57
+ else
58
+ text
59
+ end
60
+ end
61
+ end
62
+
63
+ # Update the translation to remove notranslate directives.
64
+ # Fixes spacing to match the original.
65
+ # @param tr [Result] Translation result
66
+ # @return [Result] Updated translation result
67
+ def remove_notranslate(tr)
68
+ return tr unless tr.original.preserve_regex
69
+ original_spacing = find_notranslate_spacing(tr.original)
70
+ result_spacing = find_notranslate_spacing(tr.result)
71
+ if original_spacing.length != result_spacing.length
72
+ # the number of notranslate directives in the result doesn't
73
+ # match the number in the original. this could mean an invalid
74
+ # translation?
75
+ log.debug("possible invalid translation: #{tr.description}")
76
+ original_spacing = nil # don't attempt to fix spacing.
77
+ end
78
+ original = remove_notranslate_text(tr.original)
79
+ result = remove_notranslate_text(tr.result, original_spacing)
80
+ Result.new(
81
+ original, result, tr.provider, from_database: tr.from_database
82
+ )
83
+ end
84
+
85
+ REGEX1 = %r{<span translate="no">\s*(.*?)\s*</span>}
86
+ REGEX2 = %r{(\s*)<span translate="no">\s*(.*?)\s*</span>(\s*)}
87
+
88
+ # scan text for no translate tags, record leading and trailing space.
89
+ def find_notranslate_spacing(text)
90
+ text.scan(REGEX2).collect { |i| [i[0], i[2]] }
91
+ end
92
+
93
+ def remove_notranslate_text(text, fix_spacing = nil)
94
+ if fix_spacing
95
+ num = 0 # match number
96
+ text.gsub(REGEX2) do |i|
97
+ notranslate_replacement(i[2], fix_spacing, num += 1)
98
+ end
99
+ else
100
+ text.gsub(REGEX1) { |i| i[1] }
101
+ end
102
+ end
103
+
104
+ def notranslate_replacement(string, fix_spacing, num)
105
+ spacing = fix_spacing[num - 1]
106
+ leading = spacing[0]
107
+ trailing = spacing[1]
108
+ leading + string + trailing
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,50 @@
1
+ module Translatomatic
2
+ module Translation
3
+ # Data object describing a text translation
4
+ class Result
5
+ # @return [Translatomatic::Text] original string
6
+ attr_reader :original
7
+
8
+ # @return [Translatomatic::Text] translated string
9
+ attr_reader :result
10
+
11
+ # @return [Symbol] The name of the provider. Can be nil for translations
12
+ # that have been reconstituted from substrings.
13
+ attr_reader :provider
14
+
15
+ # @return [boolean] True if this translation came from the database
16
+ attr_reader :from_database
17
+
18
+ def initialize(original, result, provider, options = {})
19
+ raise 'original required' unless original.present?
20
+ raise 'result required' unless result.present?
21
+ @original = build_text(original)
22
+ @result = build_text(result)
23
+ @provider = provider
24
+ @from_database = options[:from_database]
25
+ end
26
+
27
+ # @return [String] The translated string
28
+ def to_s
29
+ result.to_s
30
+ end
31
+
32
+ # @return [String] A description of this translation
33
+ def description
34
+ format('%<original>s (%<from_locale>s) -> %<result>s (%<to_locale>s)',
35
+ original: original.to_s, result: result.to_s,
36
+ from_locale: original.locale, to_locale: result.locale)
37
+ end
38
+
39
+ private
40
+
41
+ def build_text(string)
42
+ if string.is_a?(Translatomatic::Text)
43
+ string
44
+ else
45
+ Translatomatic::Text.new(string, Locale.default)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ module Translatomatic
2
+ module Translation
3
+ # Share translations with providers
4
+ class Sharer
5
+ def initialize(options = {})
6
+ @options = options
7
+ end
8
+
9
+ # @param collection [Translatomatic::Translation::Collection]
10
+ # Translation collection
11
+ # @return [void]
12
+ def share(collection)
13
+ return if collection.empty?
14
+
15
+ tmx = Translatomatic::TMX::Document.from_collection(collection)
16
+ available = Translatomatic::Provider.available(@options)
17
+ available.each do |provider|
18
+ if provider.respond_to?(:upload)
19
+ log.info(t('sharer.uploading_tmx', name: provider.name))
20
+ provider.upload(tmx)
21
+ end
22
+ end
23
+
24
+ ActiveRecord::Base.transaction do
25
+ db_texts.each do |text|
26
+ text.update(shared: true) if text.translated?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ module Translatomatic
2
+ module Translation
3
+ # Translation statistics
4
+ class Stats
5
+ include Translatomatic::Util
6
+
7
+ # @return [Array<Result>] A list of all translations
8
+ attr_reader :translations
9
+
10
+ # @return [Number] The number of translations that came from the database.
11
+ attr_reader :from_db
12
+
13
+ # @return [Number] The number of translations that came from the provider.
14
+ attr_reader :from_provider
15
+
16
+ # @return [Number] The number of untranslated strings
17
+ attr_reader :untranslated
18
+
19
+ # Combine stats with another object
20
+ # @param other [Stats] Another stats object
21
+ # @return [Stats] The result of adding this to other
22
+ def +(other)
23
+ raise "expected Stats, got #{other.class}" unless other.is_a?(Stats)
24
+ Stats.new(translations + other.translations)
25
+ end
26
+
27
+ private
28
+
29
+ def initialize(translations = [])
30
+ @translations = list = translations
31
+ @from_db = list.count { |i| i.from_database && i.result }
32
+ @from_provider = list.count { |i| !i.from_database && i.result }
33
+ @untranslated = list.count { |i| i.result.nil? }
34
+ end
35
+
36
+ def to_s
37
+ key = 'translator.total_translations'
38
+ t(key, total: @translations.length,
39
+ from_db: @from_db, from_provider: @from_provider,
40
+ untranslated: @untranslated)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,107 +1,110 @@
1
- require 'translatomatic/translator/base'
2
- require 'translatomatic/translator/yandex'
3
- require 'translatomatic/translator/google'
4
- require 'translatomatic/translator/microsoft'
5
- require 'translatomatic/translator/frengly'
6
- require 'translatomatic/translator/my_memory'
7
-
8
- # Provides methods to access and create instances of
9
- # interfaces to translation APIs.
10
- module Translatomatic::Translator
11
-
12
- class << self
13
- include Translatomatic::Util
14
- end
1
+ module Translatomatic
2
+ # Translates strings from one language to another
3
+ class Translator
4
+ attr_reader :stats
15
5
 
16
- # @return [Class] The translator class corresponding to the given name
17
- def self.find(name)
18
- name && !name.empty? ? self.const_get(name) : nil
19
- end
6
+ def initialize(options = {})
7
+ @listener = options[:listener]
8
+ @providers = resolve_providers(options)
9
+ raise t('translator.provider_required') if @providers.empty?
10
+ @providers.each { |i| i.listener = @listener } if @listener
20
11
 
21
- # Resolve the given list of translator names to a list of translators.
22
- # If the list is empty, return all translators that are configured.
23
- # @param list [Array<String>] Translator names or translators
24
- # @param options [Hash<String,String>] Translator options
25
- # @return [Array<Translatomatic::Translator::Base>] Translators
26
- def self.resolve(list, options = {})
27
- list = [list] unless list.kind_of?(Array)
28
- list = list.compact.collect do |translator|
29
- if translator.respond_to?(:translate)
30
- translator
31
- else
32
- klass = Translatomatic::Translator.find(translator)
33
- translator = klass.new(options)
34
- end
35
- translator
12
+ # use database by default if we're connected to a database
13
+ @use_db = !options[:no_database] && ActiveRecord::Base.connected?
14
+ log.debug(t('translator.database_disabled')) unless @use_db
15
+
16
+ @stats = Translatomatic::Translation::Stats.new
36
17
  end
37
18
 
38
- if list.empty?
39
- # find all available translators that work with the given options
40
- list = Translatomatic::Translator.available(options)
41
- if list.empty?
42
- raise t("cli.no_translators")
19
+ # Translate texts to a target locale
20
+ # @param texts [Array<Translatomatic::Text>] Texts to translate
21
+ # @param to_locales [Array<Locale>] Target locale(s)
22
+ # @return [Array<Translatomatic::Translation>] Translations
23
+ def translate(texts, to_locales)
24
+ text_collection = TextCollection.new(texts)
25
+ to_locales = [to_locales] unless to_locales.is_a?(Array)
26
+
27
+ # for each provider
28
+ # get translations for all texts from the database
29
+ # for texts that are untranslated, call the provider
30
+ # return translations
31
+
32
+ translation_collection = Translation::Collection.new
33
+ text_collection.each_locale do |from_locale, list|
34
+ next if list.blank?
35
+ @providers.each do |provider|
36
+ to_locales.each do |to_locale|
37
+ fetcher = Translation::Fetcher.new(
38
+ provider: provider, texts: list, use_db: @use_db,
39
+ from_locale: from_locale, to_locale: to_locale,
40
+ listener: @listener
41
+ )
42
+ translations = fetcher.translations
43
+ translation_collection += translations
44
+ update_stats(translations)
45
+ end
46
+ end
43
47
  end
44
- end
45
- list
46
- end
47
48
 
48
- # @return [List<Class>] A list of all translator classes
49
- def self.modules
50
- self.constants.collect { |c| self.const_get(c) }.select do |klass|
51
- klass.is_a?(Class) && klass != Translatomatic::Translator::Base
49
+ combine_substrings(translation_collection, text_collection, to_locales)
50
+ translation_collection
52
51
  end
53
- end
54
52
 
55
- # @return [List<String>] A list of all translators
56
- def self.names
57
- modules.collect { |i| i.name.demodulize }
58
- end
53
+ private
59
54
 
60
- # Find all configured translators
61
- # @param options [Hash<String,String>] Translator options
62
- # @return [Array<#translate>] A list of translator instances
63
- def self.available(options = {})
64
- available = []
65
- modules.each do |mod|
66
- begin
67
- translator = mod.new(options)
68
- available << translator
69
- rescue Exception
70
- log.debug(t("translator.unavailable", name: mod.name.demodulize))
55
+ include Util
56
+ include DefineOptions
57
+
58
+ define_option :no_database, type: :boolean, default: false,
59
+ desc: t('translator.no_database')
60
+
61
+ # Combine translations of substrings of the original strings
62
+ # @param tr_collection [Translatomatic::Translation::Collection]
63
+ # Translation collection
64
+ # @return [void]
65
+ def combine_substrings(tr_collection, text_collection, to_locales)
66
+ to_locales.each do |to_locale|
67
+ text_collection.originals.each do |parent|
68
+ combine_parent_substrings(tr_collection, parent, to_locale)
69
+ end
71
70
  end
72
71
  end
73
- available
74
- end
75
72
 
76
- # @return [String] A description of all translators and options
77
- def self.list
78
- out = t("translator.translators") + "\n"
79
- configured_options = {}
80
- modules.each do |mod|
81
- out += "\n" + mod.name.demodulize + ":\n"
82
- opts = mod.options
83
- opts.each do |opt|
84
- configured_options[opt.name] = config.get(opt.name)
85
- optname = opt.name.to_s.gsub("_", "-")
86
- out += " --%-18s %18s %10s %15s\n" % [optname, opt.description,
87
- opt.required ? t("translator.required_option") : "",
88
- opt.env_name ? "ENV[#{opt.env_name}]" : ""]
73
+ def combine_parent_substrings(tr_collection, parent, to_locale)
74
+ # get a list of substring translations for this parent string
75
+ list = tr_collection.sentences(parent, to_locale)
76
+ # skip if we have no substrings for this string
77
+ return if list.blank?
78
+ list = list.sort_by { |tr| -tr.original.offset }
79
+
80
+ translated_parent = build_text(parent.value.dup, to_locale)
81
+ list.each do |tr|
82
+ original = tr.original
83
+ translated = tr.result
84
+ translated_parent[original.offset, original.length] = translated.to_s
89
85
  end
86
+
87
+ # add the translation that results from combining the translated
88
+ # substrings to the translation collection
89
+ new_translation = translation(parent, translated_parent)
90
+ tr_collection.add(new_translation)
90
91
  end
91
- out += "\n"
92
- out += t("translator.configured") + "\n"
93
- configured = available(configured_options)
94
- configured.each do |translator|
95
- out += " " + translator.name + "\n"
92
+
93
+ def translation(original, result, provider = nil, options = {})
94
+ Translatomatic::Translation::Result.new(
95
+ original, result, provider, options
96
+ )
96
97
  end
97
- out += t("translator.no_translators") if configured.empty?
98
- out + "\n"
99
- end
100
98
 
101
- private
99
+ def resolve_providers(options)
100
+ Translatomatic::Provider.resolve(options[:provider], options)
101
+ end
102
102
 
103
- def self.config
104
- Translatomatic.config
103
+ def update_stats(tr_collection)
104
+ stats = Translatomatic::Translation::Stats.new(
105
+ tr_collection.translations
106
+ )
107
+ @stats += stats
108
+ end
105
109
  end
106
-
107
110
  end