translatomatic 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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