translatomatic 0.1.0 → 0.1.1

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 (67) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +1 -0
  3. data/.gitignore +15 -12
  4. data/.rspec +3 -3
  5. data/.travis.yml +32 -50
  6. data/CODE_OF_CONDUCT.md +74 -74
  7. data/Gemfile +29 -5
  8. data/Guardfile +48 -0
  9. data/LICENSE.txt +21 -21
  10. data/README.de.md +92 -0
  11. data/README.es.md +92 -0
  12. data/README.fr.md +92 -0
  13. data/README.it.md +92 -0
  14. data/README.ja.md +92 -0
  15. data/README.md +96 -74
  16. data/Rakefile +6 -6
  17. data/bin/setup +8 -8
  18. data/bin/translatomatic +6 -6
  19. data/bin/travis +26 -0
  20. data/db/database.yml +9 -9
  21. data/db/migrate/201712170000_initial.rb +24 -23
  22. data/lib/translatomatic/cli.rb +204 -80
  23. data/lib/translatomatic/config.rb +12 -26
  24. data/lib/translatomatic/converter.rb +206 -142
  25. data/lib/translatomatic/converter_stats.rb +27 -27
  26. data/lib/translatomatic/database.rb +139 -99
  27. data/lib/translatomatic/escaped_unicode.rb +90 -90
  28. data/lib/translatomatic/extractor/base.rb +14 -0
  29. data/lib/translatomatic/extractor/ruby.rb +5 -0
  30. data/lib/translatomatic/extractor.rb +4 -0
  31. data/lib/translatomatic/http_request.rb +133 -0
  32. data/lib/translatomatic/locale.rb +52 -0
  33. data/lib/translatomatic/logger.rb +28 -0
  34. data/lib/translatomatic/model/locale.rb +21 -22
  35. data/lib/translatomatic/model/text.rb +17 -13
  36. data/lib/translatomatic/model.rb +4 -4
  37. data/lib/translatomatic/option.rb +24 -24
  38. data/lib/translatomatic/progress_updater.rb +15 -0
  39. data/lib/translatomatic/resource_file/base.rb +169 -137
  40. data/lib/translatomatic/resource_file/html.rb +46 -28
  41. data/lib/translatomatic/resource_file/markdown.rb +54 -0
  42. data/lib/translatomatic/resource_file/plist.rb +30 -29
  43. data/lib/translatomatic/resource_file/properties.rb +72 -60
  44. data/lib/translatomatic/resource_file/resw.rb +30 -0
  45. data/lib/translatomatic/resource_file/text.rb +29 -28
  46. data/lib/translatomatic/resource_file/xcode_strings.rb +71 -65
  47. data/lib/translatomatic/resource_file/xml.rb +79 -59
  48. data/lib/translatomatic/resource_file/yaml.rb +82 -80
  49. data/lib/translatomatic/resource_file.rb +76 -74
  50. data/lib/translatomatic/string.rb +160 -0
  51. data/lib/translatomatic/tmx/document.rb +100 -0
  52. data/lib/translatomatic/tmx/translation_unit.rb +19 -0
  53. data/lib/translatomatic/tmx.rb +4 -0
  54. data/lib/translatomatic/translation_result.rb +75 -57
  55. data/lib/translatomatic/translator/base.rb +83 -47
  56. data/lib/translatomatic/translator/frengly.rb +57 -64
  57. data/lib/translatomatic/translator/google.rb +31 -30
  58. data/lib/translatomatic/translator/microsoft.rb +33 -32
  59. data/lib/translatomatic/translator/my_memory.rb +64 -55
  60. data/lib/translatomatic/translator/yandex.rb +39 -37
  61. data/lib/translatomatic/translator.rb +63 -63
  62. data/lib/translatomatic/util.rb +15 -24
  63. data/lib/translatomatic/version.rb +4 -3
  64. data/lib/translatomatic.rb +32 -27
  65. data/translatomatic.gemspec +43 -45
  66. metadata +52 -18
  67. data/Gemfile.lock +0 -137
@@ -1,90 +1,90 @@
1
- # Module to encode and decode unicode chars.
2
- # This code is highly influced by Florian Frank's JSON gem
3
- # @see https://github.com/jnbt/java-properties
4
- # @see https://github.com/flori/json/
5
-
6
- module Translatomatic::EscapedUnicode
7
-
8
- # Decodes all unicode chars from escape sequences
9
- # @param text [String]
10
- # @return [String] The encoded text for chaining
11
- def self.unescape(text)
12
- string = text.dup
13
- string = string.gsub(%r((?:\\[uU](?:[A-Fa-f\d]{4}))+)) do |c|
14
- c.downcase!
15
- bytes = EMPTY_8BIT_STRING.dup
16
- i = 0
17
- while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
18
- bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
19
- i += 1
20
- end
21
- bytes.encode("utf-8", "utf-16be")
22
- end
23
- string.force_encoding(::Encoding::UTF_8)
24
-
25
- text.replace string
26
- text
27
- end
28
-
29
- # Decodes all unicode chars into escape sequences
30
- # @param text [String]
31
- # @return [String] The decoded text for chaining
32
- def self.escape(text)
33
- string = text.dup
34
- string.force_encoding(::Encoding::ASCII_8BIT)
35
- string.gsub!(/["\\\x0-\x1f]/n) { |c| MAP[c] || c }
36
- string.gsub!(/(
37
- (?:
38
- [\xc2-\xdf][\x80-\xbf] |
39
- [\xe0-\xef][\x80-\xbf]{2} |
40
- [\xf0-\xf4][\x80-\xbf]{3}
41
- )+ |
42
- [\x80-\xc1\xf5-\xff] # invalid
43
- )/nx) { |c|
44
- c.size == 1 and raise "Invalid utf8 byte: '#{c}'"
45
- s = c.encode("utf-16be", "utf-8").unpack('H*')[0]
46
- s.force_encoding(::Encoding::ASCII_8BIT)
47
- s.gsub!(/.{4}/n, '\\\\u\&')
48
- s.force_encoding(::Encoding::UTF_8)
49
- }
50
- string.force_encoding(::Encoding::UTF_8)
51
- text.replace string
52
- text
53
- end
54
-
55
- private
56
-
57
- MAP = {
58
- "\x0" => '\u0000',
59
- "\x1" => '\u0001',
60
- "\x2" => '\u0002',
61
- "\x3" => '\u0003',
62
- "\x4" => '\u0004',
63
- "\x5" => '\u0005',
64
- "\x6" => '\u0006',
65
- "\x7" => '\u0007',
66
- "\xb" => '\u000b',
67
- "\xe" => '\u000e',
68
- "\xf" => '\u000f',
69
- "\x10" => '\u0010',
70
- "\x11" => '\u0011',
71
- "\x12" => '\u0012',
72
- "\x13" => '\u0013',
73
- "\x14" => '\u0014',
74
- "\x15" => '\u0015',
75
- "\x16" => '\u0016',
76
- "\x17" => '\u0017',
77
- "\x18" => '\u0018',
78
- "\x19" => '\u0019',
79
- "\x1a" => '\u001a',
80
- "\x1b" => '\u001b',
81
- "\x1c" => '\u001c',
82
- "\x1d" => '\u001d',
83
- "\x1e" => '\u001e',
84
- "\x1f" => '\u001f',
85
- }
86
-
87
- EMPTY_8BIT_STRING = ''
88
- EMPTY_8BIT_STRING.force_encoding(::Encoding::ASCII_8BIT)
89
-
90
- end
1
+ # Module to encode and decode unicode chars.
2
+ # This code is highly influced by Florian Frank's JSON gem
3
+ # @see https://github.com/jnbt/java-properties
4
+ # @see https://github.com/flori/json/
5
+
6
+ module Translatomatic::EscapedUnicode
7
+
8
+ # Decodes all unicode chars from escape sequences
9
+ # @param text [String]
10
+ # @return [String] The encoded text for chaining
11
+ def self.unescape(text)
12
+ string = text.dup
13
+ string = string.gsub(%r((?:\\[uU](?:[A-Fa-f\d]{4}))+)) do |c|
14
+ c.downcase!
15
+ bytes = EMPTY_8BIT_STRING.dup
16
+ i = 0
17
+ while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
18
+ bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
19
+ i += 1
20
+ end
21
+ bytes.encode("utf-8", "utf-16be")
22
+ end
23
+ string.force_encoding(::Encoding::UTF_8)
24
+
25
+ text.replace string
26
+ text
27
+ end
28
+
29
+ # Decodes all unicode chars into escape sequences
30
+ # @param text [String]
31
+ # @return [String] The decoded text for chaining
32
+ def self.escape(text)
33
+ string = text.dup
34
+ string.force_encoding(::Encoding::ASCII_8BIT)
35
+ string.gsub!(/["\\\x0-\x1f]/n) { |c| MAP[c] || c }
36
+ string.gsub!(/(
37
+ (?:
38
+ [\xc2-\xdf][\x80-\xbf] |
39
+ [\xe0-\xef][\x80-\xbf]{2} |
40
+ [\xf0-\xf4][\x80-\xbf]{3}
41
+ )+ |
42
+ [\x80-\xc1\xf5-\xff] # invalid
43
+ )/nx) { |c|
44
+ c.size == 1 and raise "Invalid utf8 byte: '#{c}'"
45
+ s = c.encode("utf-16be", "utf-8").unpack('H*')[0]
46
+ s.force_encoding(::Encoding::ASCII_8BIT)
47
+ s.gsub!(/.{4}/n, '\\\\u\&')
48
+ s.force_encoding(::Encoding::UTF_8)
49
+ }
50
+ string.force_encoding(::Encoding::UTF_8)
51
+ text.replace string
52
+ text
53
+ end
54
+
55
+ private
56
+
57
+ MAP = {
58
+ "\x0" => '\u0000',
59
+ "\x1" => '\u0001',
60
+ "\x2" => '\u0002',
61
+ "\x3" => '\u0003',
62
+ "\x4" => '\u0004',
63
+ "\x5" => '\u0005',
64
+ "\x6" => '\u0006',
65
+ "\x7" => '\u0007',
66
+ "\xb" => '\u000b',
67
+ "\xe" => '\u000e',
68
+ "\xf" => '\u000f',
69
+ "\x10" => '\u0010',
70
+ "\x11" => '\u0011',
71
+ "\x12" => '\u0012',
72
+ "\x13" => '\u0013',
73
+ "\x14" => '\u0014',
74
+ "\x15" => '\u0015',
75
+ "\x16" => '\u0016',
76
+ "\x17" => '\u0017',
77
+ "\x18" => '\u0018',
78
+ "\x19" => '\u0019',
79
+ "\x1a" => '\u001a',
80
+ "\x1b" => '\u001b',
81
+ "\x1c" => '\u001c',
82
+ "\x1d" => '\u001d',
83
+ "\x1e" => '\u001e',
84
+ "\x1f" => '\u001f',
85
+ }
86
+
87
+ EMPTY_8BIT_STRING = ''
88
+ EMPTY_8BIT_STRING.force_encoding(::Encoding::ASCII_8BIT)
89
+
90
+ end
@@ -0,0 +1,14 @@
1
+ module Translatomatic::Extractor
2
+ class Base
3
+
4
+ def initialize(path)
5
+ @path = path.kind_of?(Pathname) ? path : Pathname.new(path)
6
+ @contents = @path.read
7
+ end
8
+
9
+ def extract
10
+ @contents.scan(/\"(.*?[^\\])"|'(.*?[^\\])'/).flatten.compact
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Translatomatic::Extractor
2
+ class Ruby < Base
3
+
4
+ end # class
5
+ end # module
@@ -0,0 +1,4 @@
1
+ module Translatomatic::Extractor; end
2
+
3
+ require 'translatomatic/extractor/base'
4
+ require 'translatomatic/extractor/ruby'
@@ -0,0 +1,133 @@
1
+ require 'securerandom'
2
+ require 'net/http'
3
+
4
+ module Translatomatic
5
+ class HTTPRequest
6
+
7
+ attr_accessor :multipart_boundary
8
+
9
+ def initialize(url)
10
+ @uri = url.respond_to?(:host) ? url : URI.parse(url)
11
+ @multipart_boundary = SecureRandom.hex(16)
12
+ end
13
+
14
+ def start(options = {})
15
+ options = options.merge(use_ssl: @uri.scheme == "https")
16
+ Net::HTTP.start(@uri.host, @uri.port, options) do |http|
17
+ @http = http
18
+ yield http
19
+ end
20
+ @http = nil
21
+ end
22
+
23
+ def get(query = nil)
24
+ uri = @uri
25
+ if query
26
+ uri = @uri.dup
27
+ uri.query = URI.encode_www_form(query)
28
+ end
29
+ request = Net::HTTP::Get.new(uri)
30
+ request['User-Agent'] = USER_AGENT
31
+ send_request(request)
32
+ end
33
+
34
+ def post(body, options = {})
35
+ request = Net::HTTP::Post.new(@uri)
36
+ request['User-Agent'] = USER_AGENT
37
+ content_type = options[:content_type]
38
+
39
+ if options[:multipart]
40
+ content_type = "multipart/form-data; boundary=#{@multipart_boundary}"
41
+ request.body = multipartify(body)
42
+ elsif body.kind_of?(Hash)
43
+ request.set_form_data(body)
44
+ else
45
+ request.body = body
46
+ end
47
+ request.content_type = content_type if content_type
48
+
49
+ send_request(request)
50
+ end
51
+
52
+ def file(*args)
53
+ FileParam.new(*args)
54
+ end
55
+
56
+ def param(*args)
57
+ Param.new(*args)
58
+ end
59
+
60
+ private
61
+
62
+ USER_AGENT = "Translatomatic #{VERSION} (+#{URL})"
63
+
64
+ # Formats a basic string key/value pair for a multipart post
65
+ class Param
66
+ attr_accessor :key, :value
67
+
68
+ def initialize(key:, value:)
69
+ @key = key
70
+ @value = value
71
+ end
72
+
73
+ def to_s
74
+ return header(header_data) + "\r\n#{value}\r\n"
75
+ end
76
+
77
+ private
78
+
79
+ def header_data
80
+ name = CGI::escape(key.to_s)
81
+ { "Content-Disposition": "form-data", name: %Q("#{name}") }
82
+ end
83
+
84
+ def header(options)
85
+ out = []
86
+ idx = 0
87
+ options.each do |key, value|
88
+ separator = idx == 0 ? ": " : "="
89
+ out << "#{key}#{separator}#{value}"
90
+ idx += 1
91
+ end
92
+ out.join("; ") + "\r\n"
93
+ end
94
+ end
95
+
96
+ # Formats the contents of a file or string for a multipart post
97
+ class FileParam < Param
98
+ attr_accessor :filename, :content, :mime_type
99
+
100
+ def initialize(key:, filename:, content:, mime_type:)
101
+ @key = key
102
+ @filename = filename
103
+ @content = content
104
+ @mime_type = mime_type
105
+ end
106
+
107
+ def to_s
108
+ return header(header_data) +
109
+ header("Content-Type": mime_type) + "\r\n#{content}\r\n"
110
+ end
111
+
112
+ private
113
+
114
+ def header_data
115
+ super.merge({ filename: %Q("#{filename}") })
116
+ end
117
+ end
118
+
119
+ def multipartify(parts)
120
+ string_parts = parts.collect do |p|
121
+ "--" + @multipart_boundary + "\r\n" + p.to_s
122
+ end
123
+ string_parts.join("") + "--" + @multipart_boundary + "--\r\n"
124
+ end
125
+
126
+ def send_request(req)
127
+ response = @http.request(req)
128
+ raise response.body unless response.kind_of? Net::HTTPSuccess
129
+ response
130
+ end
131
+
132
+ end # class
133
+ end # module
@@ -0,0 +1,52 @@
1
+ class Translatomatic::Locale
2
+
3
+ attr_reader :language
4
+ attr_reader :script
5
+ attr_reader :region
6
+
7
+ def self.parse(tag, validate = true)
8
+ locale = tag.kind_of?(Translatomatic::Locale) ? tag : new(tag)
9
+ validate && !locale.valid? ? nil : locale
10
+ end
11
+
12
+ def self.default
13
+ DEFAULT_LOCALE
14
+ end
15
+
16
+ def initialize(tag)
17
+ data = I18n::Locale::Tag::Rfc4646.tag(tag)
18
+ if data
19
+ @language = data.language
20
+ @script = data.script
21
+ @region = data.region
22
+ end
23
+ end
24
+
25
+ def valid?
26
+ # test if lang is a valid ISO 639-1 language
27
+ VALID_LANGUAGES.include?(language)
28
+ end
29
+
30
+ def to_s
31
+ [language, script, region].compact.join("-")
32
+ end
33
+
34
+ def eql?(other)
35
+ other.kind_of?(Translatomatic::Locale) && other.hash == hash
36
+ end
37
+
38
+ def ==(other)
39
+ eql?(other)
40
+ end
41
+
42
+ def hash
43
+ [language, script, region].hash
44
+ end
45
+
46
+ private
47
+
48
+ # list of 2 letter country codes
49
+ VALID_LANGUAGES = ::I18nData.languages.keys.collect { |i| i.downcase }.sort
50
+ DEFAULT_LOCALE = parse(I18n.default_locale)
51
+
52
+ end
@@ -0,0 +1,28 @@
1
+ class Translatomatic::Logger
2
+ attr_accessor :progressbar
3
+
4
+ def initialize
5
+ @logger = Logger.new(STDOUT)
6
+ @logger.level = ENV['DEBUG'] ? Logger::DEBUG : Logger::INFO
7
+ @logger.formatter = proc do |severity, datetime, progname, msg|
8
+ "#{msg}\n"
9
+ end
10
+ end
11
+
12
+ def method_missing(name, *args)
13
+ handle_logger_method(name, args) if @logger.respond_to?(name)
14
+ end
15
+
16
+ def finish
17
+ @progressbar.finish if @progressbar
18
+ end
19
+
20
+ private
21
+
22
+ def handle_logger_method(name, args)
23
+ @progressbar.clear if @progressbar
24
+ @logger.send(name, *args)
25
+ @progressbar.refresh(force: true) if @progressbar && !@progressbar.stopped?
26
+ end
27
+
28
+ end
@@ -1,22 +1,21 @@
1
- module Translatomatic
2
- module Model
3
- class Locale < ActiveRecord::Base
4
- has_many :texts, class_name: "Translatomatic::Model::Text"
5
- validates_presence_of :language
6
- validates_uniqueness_of :language, scope: [:script, :region]
7
-
8
- class << self
9
- include Translatomatic::Util
10
- end
11
-
12
- # create a locale record from an I18n::Locale::Tag object or string
13
- def self.from_tag(tag)
14
- tag = parse_locale(tag) if tag.kind_of?(String)
15
- find_or_create_by!({
16
- language: tag.language, script: tag.script, region: tag.region
17
- })
18
- end
19
-
20
- end
21
- end
22
- end
1
+ module Translatomatic
2
+ module Model
3
+ class Locale < ActiveRecord::Base
4
+ has_many :texts, class_name: "Translatomatic::Model::Text"
5
+ validates_presence_of :language
6
+ validates_uniqueness_of :language, scope: [:script, :region]
7
+
8
+ # create a locale record from an I18n::Locale::Tag object or string
9
+ def self.from_tag(tag)
10
+ tag = Translatomatic::Locale.parse(tag)
11
+ find_or_create_by!({
12
+ language: tag.language, script: tag.script, region: tag.region
13
+ })
14
+ end
15
+
16
+ def to_s
17
+ [language, script, region].compact.join("-")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,13 +1,17 @@
1
- module Translatomatic
2
- module Model
3
- class Text < ActiveRecord::Base
4
- belongs_to :locale, class_name: "Translatomatic::Model::Locale"
5
- belongs_to :from_text, class_name: "Translatomatic::Model::Text"
6
- has_many :translations, class_name: "Translatomatic::Model::Text",
7
- foreign_key: :from_text_id, dependent: :delete_all
8
-
9
- validates_presence_of :value
10
- validates_presence_of :locale
11
- end
12
- end
13
- end
1
+ module Translatomatic
2
+ module Model
3
+ class Text < ActiveRecord::Base
4
+ belongs_to :locale, class_name: "Translatomatic::Model::Locale"
5
+ belongs_to :from_text, class_name: "Translatomatic::Model::Text"
6
+ has_many :translations, class_name: "Translatomatic::Model::Text",
7
+ foreign_key: :from_text_id, dependent: :delete_all
8
+
9
+ validates_presence_of :value
10
+ validates_presence_of :locale
11
+
12
+ def is_translated?
13
+ !from_text_id.nil?
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,4 +1,4 @@
1
- module Translatomatic::Model; end
2
-
3
- require 'translatomatic/model/locale'
4
- require 'translatomatic/model/text'
1
+ module Translatomatic::Model; end
2
+
3
+ require 'translatomatic/model/locale'
4
+ require 'translatomatic/model/text'
@@ -1,24 +1,24 @@
1
- module Translatomatic
2
- class Option
3
- attr_reader :name, :required, :use_env, :description
4
-
5
- def initialize(data = {})
6
- @name = data[:name]
7
- @required = data[:required]
8
- @use_env = data[:use_env]
9
- @description = data[:desc]
10
- @data = data
11
- end
12
-
13
- def to_hash
14
- @data
15
- end
16
- end
17
-
18
- module DefineOptions
19
- private
20
- def define_options(*options)
21
- @options = options.collect { |i| Translatomatic::Option.new(i) }
22
- end
23
- end
24
- end
1
+ module Translatomatic
2
+ class Option
3
+ attr_reader :name, :required, :use_env, :description
4
+
5
+ def initialize(data = {})
6
+ @name = data[:name]
7
+ @required = data[:required]
8
+ @use_env = data[:use_env]
9
+ @description = data[:desc]
10
+ @data = data
11
+ end
12
+
13
+ def to_hash
14
+ @data
15
+ end
16
+ end
17
+
18
+ module DefineOptions
19
+ private
20
+ def define_options(*options)
21
+ @options = options.collect { |i| Translatomatic::Option.new(i) }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ # implements Converter listener
2
+ class Translatomatic::ProgressUpdater
3
+ def initialize(progressbar)
4
+ @progressbar = progressbar
5
+ end
6
+
7
+ def translated_texts(texts)
8
+ @progressbar.progress += texts.length
9
+ end
10
+
11
+ def untranslated_texts(texts)
12
+ @progressbar.total -= texts.length
13
+ end
14
+
15
+ end