tr4n5l4te 0.1.17 → 1.1.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 +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +4 -36
- data/.ruby-version +1 -0
- data/CHANGELOG +20 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +1 -31
- data/README.md +37 -5
- data/lib/tr4n5l4te/configuration.rb +2 -1
- data/lib/tr4n5l4te/runner.rb +34 -11
- data/lib/tr4n5l4te/translator.rb +53 -48
- data/lib/tr4n5l4te/version.rb +1 -1
- data/lib/tr4n5l4te.rb +2 -25
- data/tr4n5l4te.gemspec +2 -9
- metadata +7 -121
- data/lib/tr4n5l4te/agent.rb +0 -99
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de91f2f7be53edf09dac7ac551203443908e35313912974a829214b9171bf470
|
|
4
|
+
data.tar.gz: 6406468eb4da61b7fcb1448a24842b0a8125cb461680f673cbf7a82e5c0024d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd08cbe5857f621d10569ce363e34364c88497b5688025b66790e770602af8e1622cc72b1ee64685544b17b2993cea2643d89e6bb838000611379f971f4d39fc
|
|
7
|
+
data.tar.gz: '0381e45fd11649394d0002c8e648ff1cb2be416e47dc5fdb84425f6f4a3a6fe69dff574b967e13bbf0101e0470e39d050bf15e5dbd4b26bd7bd4beba0e6f2e1f'
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
require:
|
|
2
|
-
- rubocop-factory_bot
|
|
3
|
-
- rubocop-rspec
|
|
4
|
-
- rubocop-rails
|
|
5
|
-
|
|
6
1
|
AllCops:
|
|
7
2
|
TargetRubyVersion: 3.2
|
|
8
3
|
|
|
@@ -161,6 +156,10 @@ Metrics/MethodLength:
|
|
|
161
156
|
Metrics/ClassLength:
|
|
162
157
|
Max: 120
|
|
163
158
|
|
|
159
|
+
Metrics/ModuleLength:
|
|
160
|
+
Exclude:
|
|
161
|
+
- spec/**/*
|
|
162
|
+
|
|
164
163
|
Metrics/AbcSize:
|
|
165
164
|
Max: 20
|
|
166
165
|
|
|
@@ -172,37 +171,6 @@ Metrics/BlockLength:
|
|
|
172
171
|
- config/routes.rb
|
|
173
172
|
- config/environments/*.rb
|
|
174
173
|
|
|
175
|
-
########################################
|
|
176
|
-
# Rspec Cops
|
|
177
|
-
|
|
178
|
-
RSpec/MultipleExpectations:
|
|
179
|
-
Enabled: false
|
|
180
|
-
|
|
181
|
-
RSpec/IndexedLet:
|
|
182
|
-
Enabled: false
|
|
183
|
-
|
|
184
|
-
RSpec/MultipleMemoizedHelpers:
|
|
185
|
-
Enabled: false
|
|
186
|
-
|
|
187
|
-
RSpec/ExampleLength:
|
|
188
|
-
Enabled: false
|
|
189
|
-
|
|
190
|
-
RSpec/ContextWording:
|
|
191
|
-
Enabled: false
|
|
192
|
-
|
|
193
|
-
RSpec/NestedGroups:
|
|
194
|
-
Enabled: false
|
|
195
|
-
|
|
196
|
-
RSpec/AnyInstance:
|
|
197
|
-
Enabled: false
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
########################################
|
|
201
|
-
# Rails Cops
|
|
202
|
-
|
|
203
|
-
Rails/I18nLocaleTexts:
|
|
204
|
-
Enabled: false
|
|
205
|
-
|
|
206
174
|
########################################
|
|
207
175
|
# Bundler Cops
|
|
208
176
|
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.2.2
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
*1.1.0* (February 17, 2026)
|
|
2
|
+
|
|
3
|
+
* Add proxy support for Net::HTTP requests (host:port or user:pass@host:port)
|
|
4
|
+
* Add -p/--proxy CLI option
|
|
5
|
+
* Add configurable proxy via Tr4n5l4te.configure
|
|
6
|
+
* Update README with proxy documentation
|
|
7
|
+
|
|
8
|
+
*1.0.0* (February 14, 2026)
|
|
9
|
+
|
|
10
|
+
* Replace PhantomJS/Poltergeist with direct HTTP translation via Google's undocumented translate endpoint
|
|
11
|
+
* Delete Agent class and all cookie/browser infrastructure
|
|
12
|
+
* Remove capybara and poltergeist gem dependencies (net/http, json, uri are stdlib)
|
|
13
|
+
* Rewrite Translator to use Net::HTTP POST to translate.googleapis.com
|
|
14
|
+
* Add error handling that returns original text on translation failure
|
|
15
|
+
* Interpolation variable preservation (%{var}) still works as before
|
|
16
|
+
* Move development dependencies from gemspec to Gemfile
|
|
17
|
+
* Add required_ruby_version >= 3.2 to gemspec
|
|
18
|
+
* Remove rubocop-rails and rubocop-factory_bot from linter config
|
|
19
|
+
* Fix all rubocop offenses
|
|
20
|
+
|
|
1
21
|
*0.1.17* (February 06, 2024)
|
|
2
22
|
|
|
3
23
|
* Upgrade Nokogiri
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
tr4n5l4te (
|
|
5
|
-
capybara (~> 2.6)
|
|
4
|
+
tr4n5l4te (1.1.0)
|
|
6
5
|
colored (~> 1)
|
|
7
6
|
midwire_common (~> 0.1)
|
|
8
7
|
optimist (~> 3.0)
|
|
9
|
-
poltergeist (~> 1.9)
|
|
10
8
|
|
|
11
9
|
GEM
|
|
12
10
|
remote: https://rubygems.org/
|
|
13
11
|
specs:
|
|
14
|
-
addressable (2.8.6)
|
|
15
|
-
public_suffix (>= 2.0.2, < 6.0)
|
|
16
12
|
ast (2.4.2)
|
|
17
|
-
capybara (2.18.0)
|
|
18
|
-
addressable
|
|
19
|
-
mini_mime (>= 0.1.3)
|
|
20
|
-
nokogiri (>= 1.3.3)
|
|
21
|
-
rack (>= 1.0.0)
|
|
22
|
-
rack-test (>= 0.5.4)
|
|
23
|
-
xpath (>= 2.0, < 4.0)
|
|
24
|
-
cliver (0.3.2)
|
|
25
13
|
coderay (1.1.3)
|
|
26
14
|
colored (1.2)
|
|
27
15
|
diff-lcs (1.5.1)
|
|
@@ -31,30 +19,17 @@ GEM
|
|
|
31
19
|
method_source (1.0.0)
|
|
32
20
|
midwire_common (0.3.0)
|
|
33
21
|
thor (~> 0.19)
|
|
34
|
-
mini_mime (1.1.5)
|
|
35
|
-
mini_portile2 (2.8.5)
|
|
36
|
-
nokogiri (1.16.2)
|
|
37
|
-
mini_portile2 (~> 2.8.2)
|
|
38
|
-
racc (~> 1.4)
|
|
39
22
|
optimist (3.1.0)
|
|
40
23
|
parallel (1.24.0)
|
|
41
24
|
parser (3.3.0.5)
|
|
42
25
|
ast (~> 2.4.1)
|
|
43
26
|
racc
|
|
44
|
-
poltergeist (1.18.1)
|
|
45
|
-
capybara (>= 2.1, < 4)
|
|
46
|
-
cliver (~> 0.3.1)
|
|
47
|
-
websocket-driver (>= 0.2.0)
|
|
48
27
|
pry (0.14.2)
|
|
49
28
|
coderay (~> 1.1)
|
|
50
29
|
method_source (~> 1.0)
|
|
51
30
|
pry-nav (1.0.0)
|
|
52
31
|
pry (>= 0.9.10, < 0.15)
|
|
53
|
-
public_suffix (5.0.4)
|
|
54
32
|
racc (1.7.3)
|
|
55
|
-
rack (3.0.9)
|
|
56
|
-
rack-test (2.1.0)
|
|
57
|
-
rack (>= 1.3)
|
|
58
33
|
rainbow (3.1.1)
|
|
59
34
|
rake (13.1.0)
|
|
60
35
|
regexp_parser (2.9.0)
|
|
@@ -94,11 +69,6 @@ GEM
|
|
|
94
69
|
simplecov_json_formatter (0.1.4)
|
|
95
70
|
thor (0.20.3)
|
|
96
71
|
unicode-display_width (2.5.0)
|
|
97
|
-
websocket-driver (0.7.6)
|
|
98
|
-
websocket-extensions (>= 0.1.0)
|
|
99
|
-
websocket-extensions (0.1.5)
|
|
100
|
-
xpath (3.2.0)
|
|
101
|
-
nokogiri (~> 1.8)
|
|
102
72
|
|
|
103
73
|
PLATFORMS
|
|
104
74
|
ruby
|
data/README.md
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
# Tr4n5l4te
|
|
2
2
|
|
|
3
|
-
**Version:
|
|
3
|
+
**Version: 1.1.0**
|
|
4
4
|
|
|
5
5
|
Use Google Translate without an API key.
|
|
6
6
|
|
|
7
|
-
Like me, maybe you've found that Google makes it a pain to work with their API. Tr4n5l4te gets around all that.
|
|
7
|
+
Like me, maybe you've found that Google makes it a pain to work with their API. Tr4n5l4te gets around all that by using Google's free translation endpoint directly via HTTP — no browser, no API key, no headless dependencies.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
|
-
First, install [PhantomJS](http://phantomjs.org/).
|
|
12
|
-
|
|
13
11
|
Add this line to your application's Gemfile:
|
|
14
12
|
|
|
15
13
|
```ruby
|
|
@@ -24,6 +22,8 @@ Or install it yourself as:
|
|
|
24
22
|
|
|
25
23
|
$ gem install tr4n5l4te
|
|
26
24
|
|
|
25
|
+
Requires Ruby >= 3.2.
|
|
26
|
+
|
|
27
27
|
## Usage
|
|
28
28
|
|
|
29
29
|
In your code:
|
|
@@ -41,6 +41,8 @@ end
|
|
|
41
41
|
# => cómo estás
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Ruby string interpolation variables (`%{var}`) are preserved through translation.
|
|
45
|
+
|
|
44
46
|
### Command Line
|
|
45
47
|
|
|
46
48
|
➤ ./exe/translate -h
|
|
@@ -49,7 +51,8 @@ end
|
|
|
49
51
|
-l, --lang=<s> Destination language
|
|
50
52
|
-i, --list List known languages
|
|
51
53
|
-s, --sleep-time=<i> Sleep time (default: 2)
|
|
52
|
-
-t, --timeout=<i>
|
|
54
|
+
-t, --timeout=<i> HTTP request timeout (default: 30)
|
|
55
|
+
-p, --proxy=<s> Proxy - host:port or user:pass@host:port
|
|
53
56
|
-v, --verbose Be verbose with output
|
|
54
57
|
-h, --help Show this message
|
|
55
58
|
|
|
@@ -63,10 +66,39 @@ The translator will sleep for 2 seconds, by default, between each string transla
|
|
|
63
66
|
|
|
64
67
|
Warning: If you pass in '0' and translate a large file, it is very likely that Google will ban your IP address.
|
|
65
68
|
|
|
69
|
+
### Proxy Support
|
|
70
|
+
|
|
71
|
+
Route requests through an HTTP proxy to avoid IP bans or to use from behind a firewall:
|
|
72
|
+
|
|
73
|
+
$ ./exe/translate -y config/locales/en.yml -l French -p proxy.example.com:8080
|
|
74
|
+
|
|
75
|
+
With authentication:
|
|
76
|
+
|
|
77
|
+
$ ./exe/translate -y config/locales/en.yml -l French -p user:pass@proxy.example.com:8080
|
|
78
|
+
|
|
79
|
+
You can also configure a proxy programmatically:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
Tr4n5l4te.configure do |config|
|
|
83
|
+
config.proxy = { addr: 'proxy.example.com', port: 8080 }
|
|
84
|
+
# or with authentication:
|
|
85
|
+
config.proxy = { addr: 'proxy.example.com', port: 8080, user: 'user', pass: 'pass' }
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
66
89
|
To list all known languages
|
|
67
90
|
|
|
68
91
|
$ ./exe/translate --list
|
|
69
92
|
|
|
93
|
+
## Configuration
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
Tr4n5l4te.configure do |config|
|
|
97
|
+
config.timeout = 60 # HTTP request timeout in seconds (default: 30)
|
|
98
|
+
config.proxy = { addr: 'proxy.example.com', port: 8080 } # optional HTTP proxy
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
70
102
|
## Development
|
|
71
103
|
|
|
72
104
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/tr4n5l4te/runner.rb
CHANGED
|
@@ -7,6 +7,7 @@ require 'midwire_common/string'
|
|
|
7
7
|
require 'pry' unless ENV.fetch('GEM_ENV', nil).nil?
|
|
8
8
|
|
|
9
9
|
module Tr4n5l4te
|
|
10
|
+
# rubocop:disable Metrics/ClassLength
|
|
10
11
|
class Runner
|
|
11
12
|
attr_accessor :logger, :options, :count
|
|
12
13
|
|
|
@@ -24,10 +25,7 @@ module Tr4n5l4te
|
|
|
24
25
|
log_identifier(start_time)
|
|
25
26
|
@count = 0
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
Tr4n5l4te.configure do |config|
|
|
29
|
-
config.timeout = options[:timeout]
|
|
30
|
-
end
|
|
28
|
+
apply_configuration
|
|
31
29
|
|
|
32
30
|
hash = YAML.load_file(options[:yaml_file])
|
|
33
31
|
translated = process(hash)
|
|
@@ -76,7 +74,7 @@ module Tr4n5l4te
|
|
|
76
74
|
data = translated.to_yaml(line_width: -1)
|
|
77
75
|
dir = File.dirname(options[:yaml_file])
|
|
78
76
|
base = File.basename(options[:yaml_file]).gsub(/#{from_lang}\.yml$/, '')
|
|
79
|
-
File.
|
|
77
|
+
File.write(File.join(dir, "#{base}#{options[:lang]}.yml"), data)
|
|
80
78
|
end
|
|
81
79
|
|
|
82
80
|
# rubocop:disable Metrics/MethodLength
|
|
@@ -106,9 +104,14 @@ module Tr4n5l4te
|
|
|
106
104
|
)
|
|
107
105
|
opt(
|
|
108
106
|
:timeout,
|
|
109
|
-
'
|
|
107
|
+
'HTTP request timeout - default 30',
|
|
110
108
|
type: :integer, default: 30, short: 't'
|
|
111
109
|
)
|
|
110
|
+
opt(
|
|
111
|
+
:proxy,
|
|
112
|
+
'Proxy - host:port or user:pass@host:port',
|
|
113
|
+
type: :string, required: false, short: 'p'
|
|
114
|
+
)
|
|
112
115
|
opt(
|
|
113
116
|
:verbose,
|
|
114
117
|
'Be verbose with output',
|
|
@@ -121,26 +124,46 @@ module Tr4n5l4te
|
|
|
121
124
|
# rubocop:disable Metrics/AbcSize
|
|
122
125
|
def validate_args
|
|
123
126
|
if options[:list]
|
|
124
|
-
puts('Valid languages:'.yellow
|
|
125
|
-
puts(Language.list.join(', ').yellow
|
|
127
|
+
puts("#{'Valid languages:'.yellow}\n\n")
|
|
128
|
+
puts("#{Language.list.join(', ').yellow}\n\n")
|
|
126
129
|
exit
|
|
127
130
|
end
|
|
128
131
|
if !options[:lang_given] || !Language.valid?(options[:lang])
|
|
129
|
-
puts('Valid languages:'.red
|
|
130
|
-
puts(Language.list.join(', ').yellow
|
|
132
|
+
puts("#{'Valid languages:'.red}\n\n")
|
|
133
|
+
puts("#{Language.list.join(', ').yellow}\n\n")
|
|
131
134
|
Optimist.die(:lang, "'#{options[:lang]}' language unknown".red)
|
|
132
135
|
end
|
|
133
136
|
if !options[:yaml_file_given] || !File.exist?(options[:yaml_file])
|
|
134
|
-
puts('A YAML file is required:'.red
|
|
137
|
+
puts("#{'A YAML file is required:'.red}\n\n")
|
|
135
138
|
Optimist.die(:yaml_file, "'#{options[:yaml_file]}' not found".red)
|
|
136
139
|
end
|
|
137
140
|
options[:lang] = Language.ensure_code(options[:lang])
|
|
138
141
|
end
|
|
139
142
|
# rubocop:enable Metrics/AbcSize
|
|
140
143
|
|
|
144
|
+
def apply_configuration
|
|
145
|
+
Tr4n5l4te.configure do |config|
|
|
146
|
+
config.timeout = options[:timeout]
|
|
147
|
+
config.proxy = parse_proxy(options[:proxy]) if options[:proxy]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def parse_proxy(proxy_string)
|
|
152
|
+
if proxy_string.include?('@')
|
|
153
|
+
auth, host_port = proxy_string.split('@', 2)
|
|
154
|
+
user, pass = auth.split(':', 2)
|
|
155
|
+
addr, port = host_port.split(':', 2)
|
|
156
|
+
{ addr:, port: port.to_i, user:, pass: }
|
|
157
|
+
else
|
|
158
|
+
addr, port = proxy_string.split(':', 2)
|
|
159
|
+
{ addr:, port: port.to_i }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
141
163
|
def log_identifier(start_time)
|
|
142
164
|
timestr = start_time.strftime('%H:%M:%S.%3N')
|
|
143
165
|
puts("Starting Tr4n5l4te v#{Tr4n5l4te::VERSION} @#{timestr}".green)
|
|
144
166
|
end
|
|
145
167
|
end
|
|
168
|
+
# rubocop:enable Metrics/ClassLength
|
|
146
169
|
end
|
data/lib/tr4n5l4te/translator.rb
CHANGED
|
@@ -1,50 +1,75 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
4
6
|
|
|
5
7
|
module Tr4n5l4te
|
|
6
8
|
class Translator
|
|
7
|
-
|
|
9
|
+
API_URL = 'https://translate.googleapis.com/translate_a/single'
|
|
8
10
|
|
|
9
|
-
attr_reader :sleep_time
|
|
11
|
+
attr_reader :sleep_time
|
|
10
12
|
|
|
11
13
|
def initialize(args = {})
|
|
12
14
|
@sleep_time = args.fetch(:sleep_time, 2)
|
|
13
|
-
@agent = Agent.new
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def translate(text, from_lang, to_lang)
|
|
17
|
-
|
|
18
|
-
return '' if
|
|
18
|
+
preprocessed = validate_and_encode(text)
|
|
19
|
+
return '' if preprocessed == ''
|
|
20
|
+
|
|
21
|
+
response = http_translate(preprocessed, from_lang, to_lang)
|
|
22
|
+
translated = extract_translation(response)
|
|
23
|
+
postprocess(translated)
|
|
24
|
+
rescue Net::HTTPError, JSON::ParserError, SocketError, Errno::ECONNREFUSED, Timeout::Error => e
|
|
25
|
+
puts("WARNING: Translation failed for '#{text}': #{e.message}")
|
|
26
|
+
text
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
def http_translate(text, from_lang, to_lang)
|
|
32
|
+
uri = URI(API_URL)
|
|
33
|
+
params = { client: 'gtx', dt: 't', sl: from_lang.to_s, tl: to_lang.to_s, q: text }
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
36
|
+
request.set_form_data(params)
|
|
37
|
+
|
|
38
|
+
response = build_http(uri).request(request)
|
|
39
|
+
sleep_default
|
|
40
|
+
|
|
41
|
+
fail Net::HTTPError.new("HTTP #{response.code}", response) unless response.is_a?(Net::HTTPSuccess)
|
|
42
|
+
|
|
43
|
+
response.body
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def build_http(uri)
|
|
47
|
+
config = Tr4n5l4te.configuration
|
|
48
|
+
http = create_http_client(uri, config.proxy)
|
|
49
|
+
http.use_ssl = true
|
|
50
|
+
http.open_timeout = config.timeout
|
|
51
|
+
http.read_timeout = config.timeout
|
|
52
|
+
http
|
|
53
|
+
end
|
|
29
54
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if multiples.any?
|
|
34
|
-
puts("WARNING: '#{text}' has gender translations: [#{multiples.join(', ')}]")
|
|
35
|
-
postprocess(multiples.last) # take the male form
|
|
55
|
+
def create_http_client(uri, proxy)
|
|
56
|
+
if proxy
|
|
57
|
+
Net::HTTP.new(uri.host, uri.port, proxy[:addr], proxy[:port], proxy[:user], proxy[:pass])
|
|
36
58
|
else
|
|
37
|
-
|
|
59
|
+
Net::HTTP.new(uri.host, uri.port)
|
|
38
60
|
end
|
|
39
61
|
end
|
|
40
62
|
|
|
41
|
-
|
|
63
|
+
def extract_translation(body)
|
|
64
|
+
parsed = JSON.parse(body)
|
|
65
|
+
parsed[0].map { |segment| segment[0] }.join
|
|
66
|
+
end
|
|
42
67
|
|
|
43
68
|
def preprocess(text)
|
|
44
|
-
@interpolations = text.scan(/(%{
|
|
69
|
+
@interpolations = text.scan(/(%{.*?})/).flatten
|
|
45
70
|
@interpolations.each_with_index do |var, ndx|
|
|
46
71
|
stub = "VAR#{ndx}"
|
|
47
|
-
text.gsub
|
|
72
|
+
text = text.gsub(var, stub)
|
|
48
73
|
end
|
|
49
74
|
text
|
|
50
75
|
end
|
|
@@ -52,7 +77,7 @@ module Tr4n5l4te
|
|
|
52
77
|
def postprocess(text)
|
|
53
78
|
@interpolations.each_with_index do |interp, ndx|
|
|
54
79
|
stub = /VAR#{ndx}/i
|
|
55
|
-
text.gsub
|
|
80
|
+
text = text.gsub(stub, interp)
|
|
56
81
|
end
|
|
57
82
|
text
|
|
58
83
|
end
|
|
@@ -61,34 +86,14 @@ module Tr4n5l4te
|
|
|
61
86
|
return '' if text.nil?
|
|
62
87
|
fail "Cannot translate a [#{text.class}]: '#{text}'" unless text.respond_to?(:gsub)
|
|
63
88
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def smart_visit(url)
|
|
68
|
-
load_cookies
|
|
69
|
-
agent.visit(url)
|
|
70
|
-
store_cookies
|
|
71
|
-
sleep_default
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def translator_url(encoded_text, from_lang, to_lang)
|
|
75
|
-
"#{START_PAGE}/##{from_lang}/#{to_lang}/#{encoded_text}"
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def store_cookies
|
|
79
|
-
agent.store_cookies(Tr4n5l4te.cookie_file)
|
|
80
|
-
end
|
|
89
|
+
result = text.strip
|
|
90
|
+
return '' if result.empty?
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
agent.load_cookies(Tr4n5l4te.cookie_file)
|
|
92
|
+
preprocess(result)
|
|
84
93
|
end
|
|
85
94
|
|
|
86
95
|
def sleep_default
|
|
87
96
|
sleep(sleep_time)
|
|
88
97
|
end
|
|
89
|
-
|
|
90
|
-
def browser
|
|
91
|
-
agent.browser
|
|
92
|
-
end
|
|
93
98
|
end
|
|
94
99
|
end
|
data/lib/tr4n5l4te/version.rb
CHANGED
data/lib/tr4n5l4te.rb
CHANGED
|
@@ -17,30 +17,8 @@ module Tr4n5l4te
|
|
|
17
17
|
'tr4n5l4te'
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def default_config_directory
|
|
21
|
-
".#{string_id}"
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def default_cookie_filename
|
|
25
|
-
'cookies.yml'
|
|
26
|
-
end
|
|
27
|
-
|
|
28
20
|
def home_directory
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# If you don't have a HOME directory defined, you are on an OS that is
|
|
33
|
-
# retarded, and this call will fail.
|
|
34
|
-
def cookie_file
|
|
35
|
-
dir = File.join(home_directory, default_config_directory)
|
|
36
|
-
module_string = string_id.upcase
|
|
37
|
-
file = ENV.fetch(
|
|
38
|
-
"#{module_string}_COOKIES",
|
|
39
|
-
File.join(dir, default_cookie_filename)
|
|
40
|
-
)
|
|
41
|
-
FileUtils.mkdir_p(dir)
|
|
42
|
-
FileUtils.touch(file)
|
|
43
|
-
file
|
|
21
|
+
Dir.home
|
|
44
22
|
end
|
|
45
23
|
|
|
46
24
|
def configure
|
|
@@ -49,11 +27,10 @@ module Tr4n5l4te
|
|
|
49
27
|
end
|
|
50
28
|
end
|
|
51
29
|
|
|
52
|
-
autoload :Agent, 'tr4n5l4te/agent'
|
|
53
30
|
autoload :Configuration, 'tr4n5l4te/configuration'
|
|
54
31
|
autoload :Language, 'tr4n5l4te/language'
|
|
55
32
|
autoload :Runner, 'tr4n5l4te/runner'
|
|
56
33
|
autoload :Translator, 'tr4n5l4te/translator'
|
|
57
34
|
end
|
|
58
35
|
|
|
59
|
-
Tr4n5l4te.
|
|
36
|
+
Tr4n5l4te.configuration = Tr4n5l4te::Configuration.new
|
data/tr4n5l4te.gemspec
CHANGED
|
@@ -19,17 +19,10 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.bindir = 'exe'
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
21
21
|
spec.require_paths = ['lib']
|
|
22
|
+
spec.required_ruby_version = '>= 3.2'
|
|
22
23
|
|
|
23
|
-
spec.add_development_dependency 'bundler', '~> 2.1'
|
|
24
|
-
spec.add_development_dependency 'pry-nav'
|
|
25
|
-
spec.add_development_dependency 'rake', '~> 13.0'
|
|
26
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
27
|
-
spec.add_development_dependency 'rubocop'
|
|
28
|
-
spec.add_development_dependency 'simplecov', '~> 0.11'
|
|
29
|
-
|
|
30
|
-
spec.add_dependency 'capybara', '~> 2.6'
|
|
31
24
|
spec.add_dependency 'colored', '~> 1'
|
|
32
25
|
spec.add_dependency 'midwire_common', '~> 0.1'
|
|
33
26
|
spec.add_dependency 'optimist', '~> 3.0'
|
|
34
|
-
spec.
|
|
27
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
35
28
|
end
|
metadata
CHANGED
|
@@ -1,113 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tr4n5l4te
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Blackburn
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2026-02-17 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: bundler
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '2.1'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '2.1'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: pry-nav
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rake
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '13.0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '13.0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: rspec
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - "~>"
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '3.0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - "~>"
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '3.0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: rubocop
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - ">="
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0'
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: simplecov
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - "~>"
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0.11'
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - "~>"
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0.11'
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: capybara
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - "~>"
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: '2.6'
|
|
104
|
-
type: :runtime
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - "~>"
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '2.6'
|
|
111
12
|
- !ruby/object:Gem::Dependency
|
|
112
13
|
name: colored
|
|
113
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -150,20 +51,6 @@ dependencies:
|
|
|
150
51
|
- - "~>"
|
|
151
52
|
- !ruby/object:Gem::Version
|
|
152
53
|
version: '3.0'
|
|
153
|
-
- !ruby/object:Gem::Dependency
|
|
154
|
-
name: poltergeist
|
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
|
156
|
-
requirements:
|
|
157
|
-
- - "~>"
|
|
158
|
-
- !ruby/object:Gem::Version
|
|
159
|
-
version: '1.9'
|
|
160
|
-
type: :runtime
|
|
161
|
-
prerelease: false
|
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
-
requirements:
|
|
164
|
-
- - "~>"
|
|
165
|
-
- !ruby/object:Gem::Version
|
|
166
|
-
version: '1.9'
|
|
167
54
|
description: Use Google Translate without an API key.
|
|
168
55
|
email:
|
|
169
56
|
- 87a1779b@opayq.com
|
|
@@ -175,6 +62,7 @@ files:
|
|
|
175
62
|
- ".gitignore"
|
|
176
63
|
- ".rspec"
|
|
177
64
|
- ".rubocop.yml"
|
|
65
|
+
- ".ruby-version"
|
|
178
66
|
- ".travis.yml"
|
|
179
67
|
- CHANGELOG
|
|
180
68
|
- CODE_OF_CONDUCT.md
|
|
@@ -192,7 +80,6 @@ files:
|
|
|
192
80
|
- bin/setup
|
|
193
81
|
- exe/translate
|
|
194
82
|
- lib/tr4n5l4te.rb
|
|
195
|
-
- lib/tr4n5l4te/agent.rb
|
|
196
83
|
- lib/tr4n5l4te/configuration.rb
|
|
197
84
|
- lib/tr4n5l4te/language.rb
|
|
198
85
|
- lib/tr4n5l4te/runner.rb
|
|
@@ -202,8 +89,8 @@ files:
|
|
|
202
89
|
homepage: https://github.com/midwire/tr4n5l4te
|
|
203
90
|
licenses:
|
|
204
91
|
- MIT
|
|
205
|
-
metadata:
|
|
206
|
-
|
|
92
|
+
metadata:
|
|
93
|
+
rubygems_mfa_required: 'true'
|
|
207
94
|
rdoc_options: []
|
|
208
95
|
require_paths:
|
|
209
96
|
- lib
|
|
@@ -211,15 +98,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
211
98
|
requirements:
|
|
212
99
|
- - ">="
|
|
213
100
|
- !ruby/object:Gem::Version
|
|
214
|
-
version: '
|
|
101
|
+
version: '3.2'
|
|
215
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
216
103
|
requirements:
|
|
217
104
|
- - ">="
|
|
218
105
|
- !ruby/object:Gem::Version
|
|
219
106
|
version: '0'
|
|
220
107
|
requirements: []
|
|
221
|
-
rubygems_version: 3.
|
|
222
|
-
signing_key:
|
|
108
|
+
rubygems_version: 3.6.5
|
|
223
109
|
specification_version: 4
|
|
224
110
|
summary: Use Google Translate without an API key.
|
|
225
111
|
test_files: []
|
data/lib/tr4n5l4te/agent.rb
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'capybara'
|
|
4
|
-
require 'capybara/poltergeist'
|
|
5
|
-
require 'yaml'
|
|
6
|
-
|
|
7
|
-
module Tr4n5l4te
|
|
8
|
-
Capybara.register_driver :poltergeist do |app|
|
|
9
|
-
Capybara::Poltergeist::Driver.new(
|
|
10
|
-
app,
|
|
11
|
-
js_errors: false,
|
|
12
|
-
timeout: Tr4n5l4te.configuration.timeout,
|
|
13
|
-
phantomjs_options: ['--load-images=no', '--ignore-ssl-errors=yes', '--web-security=false', '--ssl-protocol=any'],
|
|
14
|
-
phantomjs_logger: StringIO.new,
|
|
15
|
-
logger: StringIO.new
|
|
16
|
-
)
|
|
17
|
-
end
|
|
18
|
-
Capybara.default_driver = :poltergeist
|
|
19
|
-
Capybara.javascript_driver = :poltergeist
|
|
20
|
-
|
|
21
|
-
class Agent
|
|
22
|
-
# rubocop:disable Metrics/LineLength
|
|
23
|
-
DEFAULT_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/601.4.4 (KHTML, like Gecko) Version/9.0.3 Safari/601.4.4'
|
|
24
|
-
# rubocop:enable Metrics/LineLength
|
|
25
|
-
|
|
26
|
-
attr_reader :browser
|
|
27
|
-
|
|
28
|
-
def initialize(options = {})
|
|
29
|
-
Capybara.ignore_hidden_elements = false
|
|
30
|
-
@browser = options[:browser] || Capybara.current_session
|
|
31
|
-
browser.driver.headers = { 'User-Agent' => DEFAULT_UA }
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def load_cookies(cookie_file)
|
|
35
|
-
return false unless cookie_hash = YAML.safe_load(
|
|
36
|
-
File.read(cookie_file), permitted_classes: [Capybara::Poltergeist::Cookie]
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
browser.driver.clear_cookies
|
|
40
|
-
cookie_hash.each do |key, cookie_obj|
|
|
41
|
-
browser.driver.set_cookie(
|
|
42
|
-
key,
|
|
43
|
-
cookie_obj.value,
|
|
44
|
-
domain: cookie_obj.domain,
|
|
45
|
-
path: cookie_obj.path,
|
|
46
|
-
secure: cookie_obj.secure?,
|
|
47
|
-
httponly: cookie_obj.httponly?,
|
|
48
|
-
expires: cookie_obj.expires
|
|
49
|
-
)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
# rubocop:enable
|
|
53
|
-
|
|
54
|
-
def store_cookies(cookie_file)
|
|
55
|
-
FileUtils.mkdir_p(File.dirname(cookie_file))
|
|
56
|
-
data = YAML.dump(browser.driver.cookies)
|
|
57
|
-
File.open(cookie_file, 'w') { |f| f.write(data) }
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def set_cookie(name, value, options = {})
|
|
61
|
-
browser.driver.set_cookie(
|
|
62
|
-
name.to_s,
|
|
63
|
-
value.to_s,
|
|
64
|
-
domain: options.fetch(:domain, nil),
|
|
65
|
-
path: options.fetch(:path, nil),
|
|
66
|
-
secure: options.fetch(:secure, false),
|
|
67
|
-
httponly: options.fetch(:httponly, false),
|
|
68
|
-
expires: options.fetch(:expires, time_plus_years)
|
|
69
|
-
)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def cookies
|
|
73
|
-
browser.driver.cookies
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def visit(url)
|
|
77
|
-
response = browser.visit(url)
|
|
78
|
-
response.recursively_symbolize_keys!
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def body
|
|
82
|
-
browser.body
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def elements(selector_string)
|
|
86
|
-
browser.all(selector_string)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
private
|
|
90
|
-
|
|
91
|
-
def time_plus_years(years = 30)
|
|
92
|
-
Time.now + days(365) * years
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def days(num)
|
|
96
|
-
num * 86_400
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|