translatomatic 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitattributes +1 -0
- data/.gitignore +15 -12
- data/.rspec +3 -3
- data/.travis.yml +32 -50
- data/CODE_OF_CONDUCT.md +74 -74
- data/Gemfile +29 -5
- data/Guardfile +48 -0
- data/LICENSE.txt +21 -21
- data/README.de.md +92 -0
- data/README.es.md +92 -0
- data/README.fr.md +92 -0
- data/README.it.md +92 -0
- data/README.ja.md +92 -0
- data/README.md +96 -74
- data/Rakefile +6 -6
- data/bin/setup +8 -8
- data/bin/translatomatic +6 -6
- data/bin/travis +26 -0
- data/db/database.yml +9 -9
- data/db/migrate/201712170000_initial.rb +24 -23
- data/lib/translatomatic/cli.rb +204 -80
- data/lib/translatomatic/config.rb +12 -26
- data/lib/translatomatic/converter.rb +206 -142
- data/lib/translatomatic/converter_stats.rb +27 -27
- data/lib/translatomatic/database.rb +139 -99
- data/lib/translatomatic/escaped_unicode.rb +90 -90
- data/lib/translatomatic/extractor/base.rb +14 -0
- data/lib/translatomatic/extractor/ruby.rb +5 -0
- data/lib/translatomatic/extractor.rb +4 -0
- data/lib/translatomatic/http_request.rb +133 -0
- data/lib/translatomatic/locale.rb +52 -0
- data/lib/translatomatic/logger.rb +28 -0
- data/lib/translatomatic/model/locale.rb +21 -22
- data/lib/translatomatic/model/text.rb +17 -13
- data/lib/translatomatic/model.rb +4 -4
- data/lib/translatomatic/option.rb +24 -24
- data/lib/translatomatic/progress_updater.rb +15 -0
- data/lib/translatomatic/resource_file/base.rb +169 -137
- data/lib/translatomatic/resource_file/html.rb +46 -28
- data/lib/translatomatic/resource_file/markdown.rb +54 -0
- data/lib/translatomatic/resource_file/plist.rb +30 -29
- data/lib/translatomatic/resource_file/properties.rb +72 -60
- data/lib/translatomatic/resource_file/resw.rb +30 -0
- data/lib/translatomatic/resource_file/text.rb +29 -28
- data/lib/translatomatic/resource_file/xcode_strings.rb +71 -65
- data/lib/translatomatic/resource_file/xml.rb +79 -59
- data/lib/translatomatic/resource_file/yaml.rb +82 -80
- data/lib/translatomatic/resource_file.rb +76 -74
- data/lib/translatomatic/string.rb +160 -0
- data/lib/translatomatic/tmx/document.rb +100 -0
- data/lib/translatomatic/tmx/translation_unit.rb +19 -0
- data/lib/translatomatic/tmx.rb +4 -0
- data/lib/translatomatic/translation_result.rb +75 -57
- data/lib/translatomatic/translator/base.rb +83 -47
- data/lib/translatomatic/translator/frengly.rb +57 -64
- data/lib/translatomatic/translator/google.rb +31 -30
- data/lib/translatomatic/translator/microsoft.rb +33 -32
- data/lib/translatomatic/translator/my_memory.rb +64 -55
- data/lib/translatomatic/translator/yandex.rb +39 -37
- data/lib/translatomatic/translator.rb +63 -63
- data/lib/translatomatic/util.rb +15 -24
- data/lib/translatomatic/version.rb +4 -3
- data/lib/translatomatic.rb +32 -27
- data/translatomatic.gemspec +43 -45
- metadata +52 -18
- data/Gemfile.lock +0 -137
@@ -1,74 +1,76 @@
|
|
1
|
-
|
2
|
-
module Translatomatic
|
3
|
-
module ResourceFile
|
4
|
-
class << self
|
5
|
-
include Translatomatic::Util
|
6
|
-
end
|
7
|
-
|
8
|
-
# Load a resource file. If locale is not specified, the locale of the
|
9
|
-
# file will be determined from the filename, or else the current default
|
10
|
-
# locale will be used.
|
11
|
-
# @param [String] path Path to the resource file
|
12
|
-
# @param [String] locale Locale of the resource file
|
13
|
-
# @return [Translatomatic::ResourceFile::Base] The resource file, or nil
|
14
|
-
# if the file type is unsupported.
|
15
|
-
def self.load(path, locale = nil)
|
16
|
-
path = path.kind_of?(Pathname) ? path : Pathname.new(path)
|
17
|
-
modules.each do |mod|
|
18
|
-
# match on entire filename to support extensions containing locales
|
19
|
-
if extension_match(mod, path)
|
20
|
-
log.debug("attempting to load #{path.to_s} using #{mod.name.demodulize}")
|
21
|
-
file = mod.new(path, locale)
|
22
|
-
return file if file.valid?
|
23
|
-
end
|
24
|
-
end
|
25
|
-
nil
|
26
|
-
end
|
27
|
-
|
28
|
-
# Find all resource files under the given directory. Follows symlinks.
|
29
|
-
# @param [String, Pathname] path The path to search from
|
30
|
-
# @return [Array<Translatomatic::ResourceFile>] Resource files found
|
31
|
-
def self.find(path, options = {})
|
32
|
-
files = []
|
33
|
-
include_dot_directories = options[:include_dot_directories]
|
34
|
-
path = Pathname.new(path) unless path.kind_of?(Pathname)
|
35
|
-
path.find do |file|
|
36
|
-
if !include_dot_directories && file.basename.to_s[0] == ?.
|
37
|
-
Find.prune
|
38
|
-
else
|
39
|
-
resource = load(file)
|
40
|
-
files << resource if resource
|
41
|
-
end
|
42
|
-
end
|
43
|
-
files
|
44
|
-
end
|
45
|
-
|
46
|
-
# Find all configured resource file classes
|
47
|
-
# @return [Array<Class>] Available resource file classes
|
48
|
-
def self.modules
|
49
|
-
self.constants.map { |c| self.const_get(c) }.select do |klass|
|
50
|
-
klass.is_a?(Class) && klass != Base
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def self.extension_match(mod, path)
|
57
|
-
filename = path.basename.to_s.downcase
|
58
|
-
mod.extensions.each do |extension|
|
59
|
-
# don't match end of line in case file has locale extension
|
60
|
-
return true if filename.match(/\.#{extension}/)
|
61
|
-
end
|
62
|
-
false
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
require 'translatomatic/resource_file/base'
|
68
|
-
require 'translatomatic/resource_file/yaml'
|
69
|
-
require 'translatomatic/resource_file/properties'
|
70
|
-
require 'translatomatic/resource_file/text'
|
71
|
-
require 'translatomatic/resource_file/xml'
|
72
|
-
require 'translatomatic/resource_file/html'
|
73
|
-
require 'translatomatic/resource_file/
|
74
|
-
require 'translatomatic/resource_file/
|
1
|
+
|
2
|
+
module Translatomatic
|
3
|
+
module ResourceFile
|
4
|
+
class << self
|
5
|
+
include Translatomatic::Util
|
6
|
+
end
|
7
|
+
|
8
|
+
# Load a resource file. If locale is not specified, the locale of the
|
9
|
+
# file will be determined from the filename, or else the current default
|
10
|
+
# locale will be used.
|
11
|
+
# @param [String] path Path to the resource file
|
12
|
+
# @param [String] locale Locale of the resource file
|
13
|
+
# @return [Translatomatic::ResourceFile::Base] The resource file, or nil
|
14
|
+
# if the file type is unsupported.
|
15
|
+
def self.load(path, locale = nil)
|
16
|
+
path = path.kind_of?(Pathname) ? path : Pathname.new(path)
|
17
|
+
modules.each do |mod|
|
18
|
+
# match on entire filename to support extensions containing locales
|
19
|
+
if extension_match(mod, path)
|
20
|
+
log.debug("attempting to load #{path.to_s} using #{mod.name.demodulize}")
|
21
|
+
file = mod.new(path, locale)
|
22
|
+
return file if file.valid?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Find all resource files under the given directory. Follows symlinks.
|
29
|
+
# @param [String, Pathname] path The path to search from
|
30
|
+
# @return [Array<Translatomatic::ResourceFile>] Resource files found
|
31
|
+
def self.find(path, options = {})
|
32
|
+
files = []
|
33
|
+
include_dot_directories = options[:include_dot_directories]
|
34
|
+
path = Pathname.new(path) unless path.kind_of?(Pathname)
|
35
|
+
path.find do |file|
|
36
|
+
if !include_dot_directories && file.basename.to_s[0] == ?.
|
37
|
+
Find.prune
|
38
|
+
else
|
39
|
+
resource = load(file)
|
40
|
+
files << resource if resource
|
41
|
+
end
|
42
|
+
end
|
43
|
+
files
|
44
|
+
end
|
45
|
+
|
46
|
+
# Find all configured resource file classes
|
47
|
+
# @return [Array<Class>] Available resource file classes
|
48
|
+
def self.modules
|
49
|
+
self.constants.map { |c| self.const_get(c) }.select do |klass|
|
50
|
+
klass.is_a?(Class) && klass != Base
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def self.extension_match(mod, path)
|
57
|
+
filename = path.basename.to_s.downcase
|
58
|
+
mod.extensions.each do |extension|
|
59
|
+
# don't match end of line in case file has locale extension
|
60
|
+
return true if filename.match(/\.#{extension}/)
|
61
|
+
end
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
require 'translatomatic/resource_file/base'
|
68
|
+
require 'translatomatic/resource_file/yaml'
|
69
|
+
require 'translatomatic/resource_file/properties'
|
70
|
+
require 'translatomatic/resource_file/text'
|
71
|
+
require 'translatomatic/resource_file/xml'
|
72
|
+
require 'translatomatic/resource_file/html'
|
73
|
+
require 'translatomatic/resource_file/markdown'
|
74
|
+
require 'translatomatic/resource_file/plist'
|
75
|
+
require 'translatomatic/resource_file/resw'
|
76
|
+
require 'translatomatic/resource_file/xcode_strings'
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module Translatomatic
|
2
|
+
class String
|
3
|
+
|
4
|
+
# @return [String] The string
|
5
|
+
attr_reader :value
|
6
|
+
|
7
|
+
# @return [Translatomatic::Locale] The string's locale
|
8
|
+
attr_reader :locale
|
9
|
+
|
10
|
+
# @return [Translatomatic::String] If this string is a substring of
|
11
|
+
# another string, returns the original string. Otherwise, returns nil.
|
12
|
+
attr_reader :parent
|
13
|
+
|
14
|
+
# @return [Number] If this string is a substring of another string,
|
15
|
+
# returns the starting offset of this string in the original.
|
16
|
+
attr_reader :offset
|
17
|
+
|
18
|
+
def initialize(value, locale, options = {})
|
19
|
+
@value = value || ''
|
20
|
+
@locale = Translatomatic::Locale.parse(locale)
|
21
|
+
@offset = options[:offset] || 0
|
22
|
+
@parent = options[:parent]
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] The value of the string
|
26
|
+
def to_s
|
27
|
+
@value
|
28
|
+
end
|
29
|
+
|
30
|
+
def length
|
31
|
+
@value.length
|
32
|
+
end
|
33
|
+
|
34
|
+
def empty?
|
35
|
+
@value.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def match(regex)
|
39
|
+
@value.match(regex)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [boolean] true if this string is a substring of another string
|
43
|
+
def substring?
|
44
|
+
@parent ? true : false
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Symbol] The type of string, corresponding to TMX segtype.
|
48
|
+
# @see http://xml.coverpages.org/tmxSpec971212.html#SEGTYPE
|
49
|
+
def type
|
50
|
+
if sentences.length >= 2
|
51
|
+
:paragraph
|
52
|
+
else
|
53
|
+
script = script_data
|
54
|
+
@value.strip.match(/#{script.delimiter}\s*$/) ? :sentence : :phrase
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Find all sentences in the string
|
59
|
+
# @return [Array<Translatomatic::String] List of sentences
|
60
|
+
def sentences
|
61
|
+
sentences = @value.scan(sentence_regex)
|
62
|
+
strings = []
|
63
|
+
offset = 0
|
64
|
+
sentences.each do |sentence|
|
65
|
+
# find leading and trailing whitespace
|
66
|
+
next if sentence.length == 0
|
67
|
+
|
68
|
+
parts = sentence.match(/^(\s*)(.*?)(\s*)$/).to_a
|
69
|
+
value = parts[2]
|
70
|
+
offset += parts[1].length # leading whitespace
|
71
|
+
strings << self.class.new(value, locale, offset: offset, parent: self)
|
72
|
+
offset += value.length + parts[3].length
|
73
|
+
end
|
74
|
+
|
75
|
+
# return [self] if there's only one sentence and it's equal to self
|
76
|
+
strings.length == 1 && strings[0].eql?(self) ? [self] : strings
|
77
|
+
end
|
78
|
+
|
79
|
+
def eql?(other)
|
80
|
+
other.kind_of?(Translatomatic::String) && other.hash == hash
|
81
|
+
end
|
82
|
+
|
83
|
+
def ==(other)
|
84
|
+
eql?(other)
|
85
|
+
end
|
86
|
+
|
87
|
+
def hash
|
88
|
+
[value, locale].hash
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
class Script
|
94
|
+
attr_reader :language
|
95
|
+
attr_reader :delimiter # sentence delimiter
|
96
|
+
attr_reader :trailing_space # delimiter requires trailing space or eol
|
97
|
+
attr_reader :left_to_right # script direction
|
98
|
+
|
99
|
+
def initialize(language:, delimiter:, trailing_space:, direction:)
|
100
|
+
@language = language
|
101
|
+
@delimiter = delimiter
|
102
|
+
@trailing_space = trailing_space
|
103
|
+
@left_to_right = direction == :ltr
|
104
|
+
raise "invalid direction" unless [:ltr, :rtl].include?(direction)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
SCRIPT_DATA = [
|
109
|
+
# [language, delimiter, trailing space, direction]
|
110
|
+
# japanese, no space after
|
111
|
+
["ja", "\u3002", false, :ltr],
|
112
|
+
# chinese, no space after
|
113
|
+
["zh", "\u3002", false, :ltr], # can be written any direction
|
114
|
+
# armenian, space after
|
115
|
+
["hy", ":", true, :ltr],
|
116
|
+
# hindi, space after
|
117
|
+
["hi", "।", true, :ltr],
|
118
|
+
# urdu, space after, right to left
|
119
|
+
["ur", "\u06d4", true, :rtl],
|
120
|
+
# thai, spaces used to separate sentences
|
121
|
+
["th", "\\s", false, :ltr],
|
122
|
+
# arabic, right to left
|
123
|
+
["ar", "\\.", true, :rtl],
|
124
|
+
# hebrew, right to left
|
125
|
+
["he", "\\.", true, :rtl],
|
126
|
+
# all other languages
|
127
|
+
["default", "\\.", true, :ltr],
|
128
|
+
]
|
129
|
+
|
130
|
+
class << self
|
131
|
+
attr_reader :script_data
|
132
|
+
end
|
133
|
+
|
134
|
+
begin
|
135
|
+
script_data = {}
|
136
|
+
SCRIPT_DATA.each do |lang, delimiter, trailing, ltr|
|
137
|
+
script = Script.new(language: lang, delimiter: delimiter,
|
138
|
+
trailing_space: trailing, direction: ltr)
|
139
|
+
script_data[lang] = script
|
140
|
+
end
|
141
|
+
@script_data = script_data
|
142
|
+
end
|
143
|
+
|
144
|
+
def sentence_regex
|
145
|
+
script = script_data
|
146
|
+
if script.trailing_space
|
147
|
+
regex = /.*?(?:#{script.delimiter}\s+|$)/
|
148
|
+
else
|
149
|
+
# no trailing space after delimiter
|
150
|
+
regex = /.*?(?:#{script.delimiter}|$)/
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def script_data
|
155
|
+
data = self.class.script_data
|
156
|
+
data[locale.language] || data["default"]
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Translatomatic::TMX
|
2
|
+
# Translation Memory Exchange document
|
3
|
+
class Document
|
4
|
+
|
5
|
+
# Create a new instance
|
6
|
+
# @param [Array<TranslationUnit>] A list of translation units
|
7
|
+
# @param [Locale] Source locale
|
8
|
+
# @return A new TMX object
|
9
|
+
def initialize(units, source_locale, origin)
|
10
|
+
units = [units] unless units.kind_of?(Array)
|
11
|
+
@units = units
|
12
|
+
@source_locale = source_locale
|
13
|
+
@origin = origin
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String] An XML string
|
17
|
+
def to_xml(options = {})
|
18
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
19
|
+
dtd = options[:dtd] || TMX_DTD
|
20
|
+
xml.doc.create_internal_subset('tmx', nil, dtd)
|
21
|
+
xml.tmx(version: "1.4") do
|
22
|
+
xml.header(creationtool: "Translatomatic",
|
23
|
+
creationtoolversion: Translatomatic::VERSION,
|
24
|
+
datatype: "PlainText",
|
25
|
+
segtype: "phrase", # default segtype
|
26
|
+
adminlang: @source_locale.to_s,
|
27
|
+
srclang: @source_locale.to_s,
|
28
|
+
"o-tmx": @origin
|
29
|
+
)
|
30
|
+
xml.body { tmx_body(xml) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
builder.to_xml
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a TMX document from the given converter
|
37
|
+
# @param [Array<Translatomatic::Model::Text>] List of texts
|
38
|
+
# @return [Translatomatic::TMX::Document] TMX document
|
39
|
+
def self.from_texts(texts)
|
40
|
+
# group texts by from_text_id to create units
|
41
|
+
# source_locale: use from_text.locale
|
42
|
+
# origin: use text.translator
|
43
|
+
origins = texts.collect { |i| i.translator }.compact.uniq
|
44
|
+
raise "Multiple origins in texts" if origins.length > 1
|
45
|
+
sources = texts.select { |i| i.from_text.nil? }
|
46
|
+
source_locales = sources.collect { |i| i.locale }.uniq
|
47
|
+
raise "Multiple source locales in texts" if source_locales.length > 1
|
48
|
+
units = units_from_texts(texts)
|
49
|
+
|
50
|
+
return new(units, source_locales[0], origins[0])
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.valid?(xml)
|
54
|
+
options = Nokogiri::XML::ParseOptions::DTDVALID
|
55
|
+
doc = Nokogiri::XML::Document.parse(xml, nil, nil, options)
|
56
|
+
doc.internal_subset.validate(doc)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
class << self
|
62
|
+
include Translatomatic::Util
|
63
|
+
end
|
64
|
+
|
65
|
+
TMX_DTD = "http://www.ttt.org/oscarstandards/tmx/tmx14.dtd"
|
66
|
+
|
67
|
+
def tmx_body(xml)
|
68
|
+
@units.each do |unit|
|
69
|
+
xml.tu("segtype": unit.strings[0].type) do
|
70
|
+
unit.strings.each do |string|
|
71
|
+
xml.tuv("xml:lang": string.locale.to_s) do
|
72
|
+
xml.seg string.value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Array<Translatomatic::TMX::TranslationUnit] translation unit list
|
80
|
+
def self.units_from_texts(texts)
|
81
|
+
# group texts by from_text_id
|
82
|
+
texts_by_from_id = {}
|
83
|
+
texts.each do |text|
|
84
|
+
id = text.from_text_id || text.id
|
85
|
+
list = (texts_by_from_id[id] ||= [])
|
86
|
+
list << text
|
87
|
+
end
|
88
|
+
|
89
|
+
# create list of Translation Units
|
90
|
+
texts_by_from_id.values.collect do |list|
|
91
|
+
tmx_unit(list.uniq.collect { |i| string(i.value, i.locale) })
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.tmx_unit(strings)
|
96
|
+
Translatomatic::TMX::TranslationUnit.new(strings)
|
97
|
+
end
|
98
|
+
|
99
|
+
end # class
|
100
|
+
end # module
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Translatomatic::TMX
|
2
|
+
class TranslationUnit
|
3
|
+
|
4
|
+
# @return [Array<Translatomatic::String>] Strings in this translation unit
|
5
|
+
attr_reader :strings
|
6
|
+
|
7
|
+
# @param [Array<Translatomatic::String>] list of strings
|
8
|
+
def initialize(strings)
|
9
|
+
@strings = strings || []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Test translation unit validity.
|
13
|
+
# A translation unit must contain at least two strings.
|
14
|
+
# @return [boolean] true if this translation unit is valid
|
15
|
+
def valid?
|
16
|
+
@strings.length >= 2
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,68 +1,86 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
|
-
module Translatomatic
|
4
|
-
class TranslationResult
|
5
|
-
|
6
|
-
# Translation results
|
7
|
-
# @return [Hash<String,String>] Translation results
|
8
|
-
attr_reader :properties
|
9
|
-
|
10
|
-
# @return [Locale] The locale of the original strings
|
11
|
-
attr_reader :from_locale
|
12
|
-
|
13
|
-
# @return [Locale] The target locale
|
14
|
-
attr_reader :to_locale
|
15
|
-
|
16
|
-
# @return [Set<String>] Untranslated strings
|
17
|
-
attr_reader :untranslated
|
18
|
-
|
19
|
-
# 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)
|
24
|
-
@
|
25
|
-
@
|
26
|
-
@untranslated = Set.new
|
27
|
-
properties.each do |key, value|
|
28
|
-
@untranslated << value
|
29
|
-
keylist = (@value_to_keys[value] ||= [])
|
30
|
-
keylist << key
|
31
|
-
end
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Translatomatic
|
4
|
+
class TranslationResult
|
5
|
+
|
6
|
+
# Translation results
|
7
|
+
# @return [Hash<String,String>] Translation results
|
8
|
+
attr_reader :properties
|
9
|
+
|
10
|
+
# @return [Locale] The locale of the original strings
|
11
|
+
attr_reader :from_locale
|
12
|
+
|
13
|
+
# @return [Locale] The target locale
|
14
|
+
attr_reader :to_locale
|
15
|
+
|
16
|
+
# @return [Set<String>] Untranslated strings
|
17
|
+
attr_reader :untranslated
|
18
|
+
|
19
|
+
# 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)
|
24
|
+
@value_to_keys = {}
|
25
|
+
@untranslated = Set.new
|
32
26
|
@from_locale = from_locale
|
33
27
|
@to_locale = to_locale
|
34
|
-
end
|
35
28
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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)
|
35
|
+
string.sentences.each do |sentence|
|
36
|
+
@untranslated << sentence
|
37
|
+
keylist = (@value_to_keys[sentence.to_s] ||= [])
|
38
|
+
keylist << key
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Update result with a list of translated strings.
|
44
|
+
# @param [Array<String>] original Original strings
|
45
|
+
# @param [Array<String>] translated Translated strings
|
46
|
+
# @return [void]
|
47
|
+
def update_strings(original, translated)
|
41
48
|
raise "strings length mismatch" unless original.length == translated.length
|
42
|
-
|
43
|
-
|
49
|
+
|
50
|
+
# create list of [from, to] text conversions
|
51
|
+
conversions = []
|
52
|
+
original.zip(translated).each do |text1, text2|
|
53
|
+
conversions << [text1, text2]
|
44
54
|
end
|
45
|
-
end
|
46
55
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
original = t.from_text.value
|
53
|
-
translated = t.value
|
54
|
-
update(original, translated)
|
56
|
+
# sort conversion list by largest offset first so that we replace
|
57
|
+
# from the end of the string to the front, so substring offsets
|
58
|
+
# are correct in the target string.
|
59
|
+
conversions.sort_by! do |t1, t2|
|
60
|
+
t1.respond_to?(:offset) ? -t1.offset : 0
|
55
61
|
end
|
56
|
-
end
|
57
62
|
|
63
|
+
conversions.each do |text1, text2|
|
64
|
+
update(text1, text2)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
58
68
|
private
|
59
69
|
|
60
|
-
|
61
|
-
|
70
|
+
include Translatomatic::Util
|
71
|
+
|
72
|
+
def update(original, translated)
|
73
|
+
keys = @value_to_keys[original.to_s]
|
62
74
|
raise "no key mapping for text '#{original}'" unless keys
|
63
|
-
keys.each
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
75
|
+
keys.each do |key|
|
76
|
+
if original.kind_of?(Translatomatic::String) && original.substring?
|
77
|
+
@properties[key][original.offset, original.length] = translated
|
78
|
+
else
|
79
|
+
@properties[key] = translated
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@untranslated.delete(original)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|