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.
- checksums.yaml +5 -5
- data/.gitattributes +20 -20
- data/.gitignore +19 -15
- data/.rspec +3 -3
- data/.rubocop.yml +28 -0
- data/.translatomatic/config.yml +4 -0
- data/.travis.yml +4 -6
- data/.yardopts +9 -9
- data/Gemfile +8 -4
- data/Guardfile +4 -5
- data/README.de.md +55 -50
- data/README.en.md +177 -0
- data/README.es.md +53 -48
- data/README.fr.md +53 -48
- data/README.it.md +54 -49
- data/README.ja.md +63 -58
- data/README.ko.md +59 -54
- data/README.md +17 -13
- data/README.ms.md +50 -45
- data/README.pt.md +54 -49
- data/README.ru.md +57 -52
- data/README.sv.md +51 -46
- data/README.zh.md +60 -55
- data/Rakefile +3 -3
- data/TODO.txt +6 -0
- data/bin/console +3 -3
- data/bin/translatomatic +4 -2
- data/config/i18n-tasks.yml +130 -0
- data/config/locales/translatomatic/de.yml +141 -99
- data/config/locales/translatomatic/en.yml +129 -89
- data/config/locales/translatomatic/es.yml +136 -99
- data/config/locales/translatomatic/fr.yml +139 -100
- data/config/locales/translatomatic/it.yml +135 -97
- data/config/locales/translatomatic/ja.yml +137 -98
- data/config/locales/translatomatic/ko.yml +138 -98
- data/config/locales/translatomatic/ms.yml +138 -100
- data/config/locales/translatomatic/pt.yml +137 -101
- data/config/locales/translatomatic/ru.yml +136 -98
- data/config/locales/translatomatic/sv.yml +134 -96
- data/config/locales/translatomatic/zh.yml +136 -97
- data/db/migrate/201712170000_initial.rb +2 -3
- data/lib/translatomatic.rb +40 -25
- data/lib/translatomatic/cli.rb +5 -1
- data/lib/translatomatic/cli/base.rb +61 -58
- data/lib/translatomatic/cli/common_options.rb +14 -11
- data/lib/translatomatic/cli/config.rb +96 -91
- data/lib/translatomatic/cli/database.rb +85 -23
- data/lib/translatomatic/cli/main.rb +158 -104
- data/lib/translatomatic/cli/thor.rb +29 -0
- data/lib/translatomatic/cli/translate.rb +134 -157
- data/lib/translatomatic/config.rb +10 -301
- data/lib/translatomatic/config/display.rb +78 -0
- data/lib/translatomatic/config/files.rb +60 -0
- data/lib/translatomatic/config/location_settings.rb +133 -0
- data/lib/translatomatic/config/options.rb +68 -0
- data/lib/translatomatic/config/selector.rb +127 -0
- data/lib/translatomatic/config/settings.rb +148 -0
- data/lib/translatomatic/converter.rb +40 -28
- data/lib/translatomatic/database.rb +127 -110
- data/lib/translatomatic/define_options.rb +4 -5
- data/lib/translatomatic/escaped_unicode.rb +86 -76
- data/lib/translatomatic/extractor.rb +5 -2
- data/lib/translatomatic/extractor/base.rb +12 -12
- data/lib/translatomatic/extractor/ruby.rb +7 -6
- data/lib/translatomatic/file_translator.rb +101 -244
- data/lib/translatomatic/flattenation.rb +39 -0
- data/lib/translatomatic/http.rb +13 -0
- data/lib/translatomatic/http/client.rb +144 -0
- data/lib/translatomatic/http/exception.rb +43 -0
- data/lib/translatomatic/http/file_param.rb +27 -0
- data/lib/translatomatic/http/param.rb +37 -0
- data/lib/translatomatic/http/request.rb +91 -0
- data/lib/translatomatic/i18n.rb +43 -0
- data/lib/translatomatic/locale.rb +71 -59
- data/lib/translatomatic/logger.rb +43 -28
- data/lib/translatomatic/metadata.rb +58 -0
- data/lib/translatomatic/model.rb +4 -2
- data/lib/translatomatic/model/locale.rb +5 -5
- data/lib/translatomatic/model/text.rb +5 -5
- data/lib/translatomatic/option.rb +57 -34
- data/lib/translatomatic/path_utils.rb +126 -0
- data/lib/translatomatic/progress_updater.rb +13 -16
- data/lib/translatomatic/provider.rb +101 -0
- data/lib/translatomatic/provider/base.rb +136 -0
- data/lib/translatomatic/provider/frengly.rb +55 -0
- data/lib/translatomatic/provider/google.rb +78 -0
- data/lib/translatomatic/provider/google_web.rb +50 -0
- data/lib/translatomatic/provider/microsoft.rb +144 -0
- data/lib/translatomatic/provider/my_memory.rb +75 -0
- data/lib/translatomatic/provider/yandex.rb +61 -0
- data/lib/translatomatic/resource_file.rb +59 -53
- data/lib/translatomatic/resource_file/base.rb +171 -237
- data/lib/translatomatic/resource_file/csv.rb +176 -24
- data/lib/translatomatic/resource_file/html.rb +21 -42
- data/lib/translatomatic/resource_file/key_value_support.rb +117 -0
- data/lib/translatomatic/resource_file/markdown.rb +36 -38
- data/lib/translatomatic/resource_file/plist.rb +121 -126
- data/lib/translatomatic/resource_file/po.rb +104 -82
- data/lib/translatomatic/resource_file/properties.rb +48 -77
- data/lib/translatomatic/resource_file/properties.treetop +87 -0
- data/lib/translatomatic/resource_file/resw.rb +56 -41
- data/lib/translatomatic/resource_file/subtitle.rb +86 -54
- data/lib/translatomatic/resource_file/text.rb +18 -18
- data/lib/translatomatic/resource_file/xcode_strings.rb +32 -63
- data/lib/translatomatic/resource_file/xcode_strings.treetop +85 -0
- data/lib/translatomatic/resource_file/xml.rb +94 -81
- data/lib/translatomatic/resource_file/yaml.rb +54 -68
- data/lib/translatomatic/retry_executor.rb +37 -0
- data/lib/translatomatic/slurp.rb +32 -0
- data/lib/translatomatic/string_batcher.rb +50 -0
- data/lib/translatomatic/string_escaping.rb +61 -0
- data/lib/translatomatic/text.rb +263 -0
- data/lib/translatomatic/text_collection.rb +66 -0
- data/lib/translatomatic/tmx.rb +5 -3
- data/lib/translatomatic/tmx/document.rb +107 -82
- data/lib/translatomatic/tmx/translation_unit.rb +19 -18
- data/lib/translatomatic/translation.rb +8 -28
- data/lib/translatomatic/translation/collection.rb +199 -0
- data/lib/translatomatic/translation/fetcher.rb +123 -0
- data/lib/translatomatic/translation/munging.rb +112 -0
- data/lib/translatomatic/translation/result.rb +50 -0
- data/lib/translatomatic/translation/sharer.rb +32 -0
- data/lib/translatomatic/translation/stats.rb +44 -0
- data/lib/translatomatic/translator.rb +91 -88
- data/lib/translatomatic/type_cast.rb +63 -0
- data/lib/translatomatic/util.rb +37 -33
- data/lib/translatomatic/version.rb +2 -2
- data/translatomatic.gemspec +57 -46
- metadata +136 -59
- data/lib/translatomatic/http_request.rb +0 -162
- data/lib/translatomatic/string.rb +0 -188
- data/lib/translatomatic/translation_result.rb +0 -86
- data/lib/translatomatic/translation_stats.rb +0 -31
- data/lib/translatomatic/translator/base.rb +0 -128
- data/lib/translatomatic/translator/frengly.rb +0 -62
- data/lib/translatomatic/translator/google.rb +0 -37
- data/lib/translatomatic/translator/microsoft.rb +0 -41
- data/lib/translatomatic/translator/my_memory.rb +0 -68
- 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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
49
|
-
|
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
|
-
|
56
|
-
def self.names
|
57
|
-
modules.collect { |i| i.name.demodulize }
|
58
|
-
end
|
53
|
+
private
|
59
54
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
99
|
+
def resolve_providers(options)
|
100
|
+
Translatomatic::Provider.resolve(options[:provider], options)
|
101
|
+
end
|
102
102
|
|
103
|
-
|
104
|
-
|
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
|