translate_client 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/CHANGELOG.md +1 -3
- data/Gemfile +6 -0
- data/Gemfile.lock +75 -1
- data/LICENSE.txt +1 -1
- data/README.md +85 -10
- data/Rakefile +5 -1
- data/bin/console +1 -1
- data/exe/translate +7 -0
- data/lib/translate_client/api.rb +178 -0
- data/lib/translate_client/auth.rb +33 -0
- data/lib/translate_client/cli/auth.rb +41 -0
- data/lib/translate_client/cli/base.rb +32 -0
- data/lib/translate_client/cli/main.rb +83 -0
- data/lib/translate_client/credentials.rb +28 -0
- data/lib/translate_client/helpers.rb +27 -0
- data/lib/translate_client/project_config.rb +95 -0
- data/lib/translate_client/railtie.rb +2 -0
- data/lib/translate_client/version.rb +1 -1
- data/lib/translate_client.rb +22 -3
- data/translate_client.gemspec +8 -4
- metadata +106 -14
- data/.editorconfig +0 -12
- data/lib/translate_client/client.rb +0 -46
- data/lib/translate_client/tasks/locale/sync.rake +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1734fc8e2f609fb623df6ece874e2813b4dec11ad90606beb886e1b71f8e2b0b
|
4
|
+
data.tar.gz: a5bf942896e2fe463d11afe16c89c7dd28a31ff9ee9d7b4d24a9e90e2ee80f9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 430b3edf84f18a097eb3635d642851ef97695a101ac72f9e0e4daf5d28a13823f369ad01195c947ab9bbcf409f7c615bae7088505fd39ea9bce6dc822234adb7
|
7
|
+
data.tar.gz: 477a3fc86c5d7c40deff6273c9b85f5dbb9c48e9039388a2c1fb71f7d6f7771ffd00009b18d3197bf09a8849f1d0263e788b98349ad68e7f0e60d94d0998ee7f
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
|
+
ruby File.read(File.join(__dir__, "../.ruby-version")).strip
|
4
5
|
|
5
6
|
# Specify your gem's dependencies in translate_client.gemspec
|
6
7
|
gemspec
|
7
8
|
|
8
9
|
gem "rake", "~> 13.0"
|
10
|
+
|
11
|
+
group :test, :development do
|
12
|
+
gem "rspec"
|
13
|
+
gem "webmock"
|
14
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -2,18 +2,92 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
translate_client (0.0.1)
|
5
|
+
activesupport
|
6
|
+
faraday
|
7
|
+
faraday-multipart
|
8
|
+
launchy
|
9
|
+
netrc
|
10
|
+
thor
|
5
11
|
|
6
12
|
GEM
|
7
13
|
remote: https://rubygems.org/
|
8
14
|
specs:
|
15
|
+
activesupport (7.1.1)
|
16
|
+
base64
|
17
|
+
bigdecimal
|
18
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
|
+
connection_pool (>= 2.2.5)
|
20
|
+
drb
|
21
|
+
i18n (>= 1.6, < 2)
|
22
|
+
minitest (>= 5.1)
|
23
|
+
mutex_m
|
24
|
+
tzinfo (~> 2.0)
|
25
|
+
addressable (2.8.5)
|
26
|
+
public_suffix (>= 2.0.2, < 6.0)
|
27
|
+
base64 (0.1.1)
|
28
|
+
bigdecimal (3.1.4)
|
29
|
+
concurrent-ruby (1.2.2)
|
30
|
+
connection_pool (2.4.1)
|
31
|
+
crack (0.4.5)
|
32
|
+
rexml
|
33
|
+
diff-lcs (1.5.0)
|
34
|
+
drb (2.1.1)
|
35
|
+
ruby2_keywords
|
36
|
+
faraday (2.7.11)
|
37
|
+
base64
|
38
|
+
faraday-net_http (>= 2.0, < 3.1)
|
39
|
+
ruby2_keywords (>= 0.0.4)
|
40
|
+
faraday-multipart (1.0.4)
|
41
|
+
multipart-post (~> 2)
|
42
|
+
faraday-net_http (3.0.2)
|
43
|
+
hashdiff (1.0.1)
|
44
|
+
i18n (1.14.1)
|
45
|
+
concurrent-ruby (~> 1.0)
|
46
|
+
launchy (2.5.2)
|
47
|
+
addressable (~> 2.8)
|
48
|
+
minitest (5.20.0)
|
49
|
+
multipart-post (2.3.0)
|
50
|
+
mutex_m (0.1.2)
|
51
|
+
netrc (0.11.0)
|
52
|
+
public_suffix (5.0.3)
|
9
53
|
rake (13.0.6)
|
54
|
+
rexml (3.2.6)
|
55
|
+
rspec (3.12.0)
|
56
|
+
rspec-core (~> 3.12.0)
|
57
|
+
rspec-expectations (~> 3.12.0)
|
58
|
+
rspec-mocks (~> 3.12.0)
|
59
|
+
rspec-core (3.12.2)
|
60
|
+
rspec-support (~> 3.12.0)
|
61
|
+
rspec-expectations (3.12.3)
|
62
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
63
|
+
rspec-support (~> 3.12.0)
|
64
|
+
rspec-mocks (3.12.6)
|
65
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
66
|
+
rspec-support (~> 3.12.0)
|
67
|
+
rspec-support (3.12.1)
|
68
|
+
ruby2_keywords (0.0.5)
|
69
|
+
thor (1.3.0)
|
70
|
+
tzinfo (2.0.6)
|
71
|
+
concurrent-ruby (~> 1.0)
|
72
|
+
webmock (3.19.1)
|
73
|
+
addressable (>= 2.8.0)
|
74
|
+
crack (>= 0.3.2)
|
75
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
10
76
|
|
11
77
|
PLATFORMS
|
78
|
+
arm64-darwin-22
|
79
|
+
arm64-darwin-23
|
12
80
|
x86_64-darwin-21
|
81
|
+
x86_64-linux
|
13
82
|
|
14
83
|
DEPENDENCIES
|
15
84
|
rake (~> 13.0)
|
85
|
+
rspec
|
16
86
|
translate_client!
|
87
|
+
webmock
|
88
|
+
|
89
|
+
RUBY VERSION
|
90
|
+
ruby 3.1.4p223
|
17
91
|
|
18
92
|
BUNDLED WITH
|
19
|
-
2.
|
93
|
+
2.4.17
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,24 +1,99 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/translate/client`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
1
|
+
# Translate CLI
|
6
2
|
|
7
3
|
## Installation
|
8
4
|
|
9
|
-
|
5
|
+
### Install as part of your application
|
6
|
+
|
7
|
+
Add this line to your application's `Gemfile`:
|
10
8
|
|
11
9
|
```ruby
|
12
|
-
gem
|
10
|
+
gem 'translate_client'
|
13
11
|
```
|
14
12
|
|
15
13
|
And then execute:
|
16
14
|
|
17
|
-
|
15
|
+
```bash
|
16
|
+
bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
### Installing the `translate` command standalone
|
20
|
+
|
21
|
+
Install it yourself as:
|
22
|
+
|
23
|
+
```bash
|
24
|
+
gem install translate_client
|
25
|
+
```
|
18
26
|
|
19
27
|
## Usage
|
20
28
|
|
21
|
-
|
29
|
+
Run `translate help` to view available commands. Run `translate help [COMMAND]` for help with that specific command.
|
30
|
+
|
31
|
+
### `translate auth login`
|
32
|
+
|
33
|
+
Opens the Translate app in a web browser where you can allow access to the CLI.
|
34
|
+
|
35
|
+
|
36
|
+
### `translate auth whoami`
|
37
|
+
|
38
|
+
Prints the email address with which you're logged in.
|
39
|
+
|
40
|
+
```
|
41
|
+
$ translate auth whoami
|
42
|
+
> You're logged in as daniel@nerdgeschoss.de.
|
43
|
+
```
|
44
|
+
|
45
|
+
|
46
|
+
### `translate auth logout`
|
47
|
+
|
48
|
+
Logs you out again.
|
49
|
+
|
50
|
+
|
51
|
+
### `translate pull [--project=PROJECT] [--locale=LOCALE] [--format=FORMAT] [--out=OUT]`
|
52
|
+
|
53
|
+
Pull translations from the webapp down to your computer.
|
54
|
+
|
55
|
+
```bash
|
56
|
+
translate pull --project "12345678-1234-abcd-5678-ef1234567890" --locale en --in config/locales/en.yml --format rails
|
57
|
+
```
|
58
|
+
|
59
|
+
Most options can be inferred (see below), so depending on your configuration, you can also do:
|
60
|
+
|
61
|
+
```bash
|
62
|
+
translate pull
|
63
|
+
```
|
64
|
+
|
65
|
+
|
66
|
+
### `translate push [--project=PROJECT] [--locale=LOCALE] [--format=FORMAT] [--in=IN]`
|
67
|
+
|
68
|
+
Push translations from your computer back to the webapp.
|
69
|
+
|
70
|
+
|
71
|
+
```bash
|
72
|
+
translate push --project "12345678-1234-abcd-5678-ef1234567890" --locale en --in config/locales/en.yml --format rails
|
73
|
+
```
|
74
|
+
|
75
|
+
Most options can be inferred (see below), so depending on your configuration, you can also do:
|
76
|
+
|
77
|
+
```bash
|
78
|
+
translate push
|
79
|
+
```
|
80
|
+
|
81
|
+
|
82
|
+
### Automatically inferring options
|
83
|
+
|
84
|
+
- `--project/-p`: Unless provided, it will try to read the `translate.project` key from a `package.json` in the working directory:
|
85
|
+
```json
|
86
|
+
{
|
87
|
+
"name": "your-cool-project",
|
88
|
+
"translate": {
|
89
|
+
"project": "12345678-1234-abcd-5678-ef1234567890"
|
90
|
+
}
|
91
|
+
}
|
92
|
+
```
|
93
|
+
- `--format/-f`: Unless provided, it will try to automatically detect your project type. This currently only recognizes Ruby on Rails projects by checking whether you have a `Gemfile` matching `/gem.+\brails\b/` in the working directory
|
94
|
+
- `--locale/-l`: If you provide a locale, it will pull/push only the provided locale. If you don't provide it, it will either pull all locales on the server, or it will push all the locales in your project. (It can only push all your locales if it can recognize your locale directory. This currently only works for Ruby on Rails projects, where it will recognize `config/locales/*.yml`)
|
95
|
+
- `--in/-i` or `--out/-o`: If you provide a `--locale`, you can also provide the a file path. If it's a relative path, it will be resolved relative to the working directory.
|
96
|
+
|
22
97
|
|
23
98
|
## Development
|
24
99
|
|
@@ -36,4 +111,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
36
111
|
|
37
112
|
## Code of Conduct
|
38
113
|
|
39
|
-
Everyone interacting in the Translate
|
114
|
+
Everyone interacting in the Translate CLI project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/nerdgeschoss/translate-client/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "bundler/setup"
|
5
|
-
require "
|
5
|
+
require "translate_client"
|
6
6
|
|
7
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
data/exe/translate
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "faraday/multipart"
|
5
|
+
|
6
|
+
module TranslateClient
|
7
|
+
class API
|
8
|
+
# Temporarily set auth token while logging in, but before we have the viewer and can write their credentials to disk
|
9
|
+
attr_writer :auth_token
|
10
|
+
|
11
|
+
StartDeviceLoginResult = Struct.new(:auth_token, :redirect_url, keyword_init: true)
|
12
|
+
Viewer = Struct.new(:email, keyword_init: true)
|
13
|
+
|
14
|
+
def request_login
|
15
|
+
result = execute <<~GRAPHQL
|
16
|
+
mutation {
|
17
|
+
startDeviceLogin(input: {}) {
|
18
|
+
authToken
|
19
|
+
redirectUrl
|
20
|
+
}
|
21
|
+
}
|
22
|
+
GRAPHQL
|
23
|
+
|
24
|
+
StartDeviceLoginResult.new(
|
25
|
+
auth_token: result.data.startDeviceLogin.authToken,
|
26
|
+
redirect_url: result.data.startDeviceLogin.redirectUrl
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def viewer
|
31
|
+
result = execute <<~GRAPHQL
|
32
|
+
query {
|
33
|
+
viewer {
|
34
|
+
email
|
35
|
+
}
|
36
|
+
}
|
37
|
+
GRAPHQL
|
38
|
+
return nil if result.data.viewer.nil?
|
39
|
+
|
40
|
+
Viewer.new(email: result.data.viewer.email)
|
41
|
+
end
|
42
|
+
|
43
|
+
def remote_locales(project_id:)
|
44
|
+
query = <<~GRAPHQL
|
45
|
+
query($project_id: ID!) {
|
46
|
+
project(id: $project_id) {
|
47
|
+
locales {
|
48
|
+
nodes {
|
49
|
+
name
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
GRAPHQL
|
55
|
+
|
56
|
+
result = execute(query, variables: {project_id:})
|
57
|
+
|
58
|
+
result.data.project.locales.nodes.map(&:name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_import(project_id:, locale:, format:, file:)
|
62
|
+
query = <<~GRAPHQL
|
63
|
+
mutation ($project_id: ID!, $locale: String!, $format: String!, $file: Upload!) {
|
64
|
+
createImport(input: {
|
65
|
+
projectId: $project_id,
|
66
|
+
locale: $locale,
|
67
|
+
format: $format,
|
68
|
+
file: $file
|
69
|
+
}) {
|
70
|
+
import {
|
71
|
+
id
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
GRAPHQL
|
76
|
+
|
77
|
+
result = execute(query, variables: {
|
78
|
+
project_id:,
|
79
|
+
locale:,
|
80
|
+
format:,
|
81
|
+
file:
|
82
|
+
})
|
83
|
+
|
84
|
+
result.data.createImport.import.id
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_export(project_id:, locale:, format:)
|
88
|
+
query = <<~GRAPHQL
|
89
|
+
mutation ($project_id: ID!, $locale: String!, $format: String!) {
|
90
|
+
createExport(input: {
|
91
|
+
projectId: $project_id,
|
92
|
+
locale: $locale,
|
93
|
+
format: $format
|
94
|
+
}) {
|
95
|
+
export {
|
96
|
+
id
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
GRAPHQL
|
101
|
+
|
102
|
+
result = execute(query, variables: {
|
103
|
+
project_id:,
|
104
|
+
locale:,
|
105
|
+
format:
|
106
|
+
})
|
107
|
+
|
108
|
+
result.data.createExport.export.id
|
109
|
+
end
|
110
|
+
|
111
|
+
def download_export(export_id:)
|
112
|
+
query = <<~GRAPHQL
|
113
|
+
query ($export_id: ID!) {
|
114
|
+
export(id: $export_id) {
|
115
|
+
file {
|
116
|
+
url
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
GRAPHQL
|
121
|
+
|
122
|
+
result = execute(query, variables: {export_id:})
|
123
|
+
file_url = result.data.export.file&.url
|
124
|
+
|
125
|
+
return if file_url.nil?
|
126
|
+
|
127
|
+
response = Faraday.get(file_url)
|
128
|
+
|
129
|
+
Tempfile.new.tap do |tmpfile|
|
130
|
+
tmpfile.write(response.body)
|
131
|
+
tmpfile.rewind
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def execute(query, variables: {})
|
138
|
+
file = variables.delete(:file)
|
139
|
+
|
140
|
+
conn = Faraday.new(url: TranslateClient::BASE_URL.dup) do |faraday|
|
141
|
+
faraday.request :multipart
|
142
|
+
faraday.request :url_encoded
|
143
|
+
faraday.adapter :net_http
|
144
|
+
end
|
145
|
+
|
146
|
+
response = conn.post do |req|
|
147
|
+
req.url "/graphql"
|
148
|
+
token = @auth_token || Credentials.new.auth_token
|
149
|
+
req.headers["Authorization"] = "Bearer #{token}" if token
|
150
|
+
|
151
|
+
if file
|
152
|
+
req.headers["Content-Type"] = "multipart/form-data"
|
153
|
+
|
154
|
+
req.body = {
|
155
|
+
:operations => {query:, variables:}.to_json,
|
156
|
+
:map => '{ "0": ["variables.file"] }',
|
157
|
+
"0" => Faraday::UploadIO.new(file, "text/plain")
|
158
|
+
}
|
159
|
+
else
|
160
|
+
req.body = {query:, variables:}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
if response.status >= 500
|
165
|
+
raise PresentableError.new("Unknown error. Server responded with: #{response.status}")
|
166
|
+
end
|
167
|
+
|
168
|
+
result = JSON.parse(response.body, object_class: OpenStruct)
|
169
|
+
|
170
|
+
if result.errors
|
171
|
+
puts response.body
|
172
|
+
raise PresentableError.new(result.errors.map(&:message).join("\n"))
|
173
|
+
end
|
174
|
+
|
175
|
+
result
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TranslateClient
|
4
|
+
class Auth
|
5
|
+
def initialize(api, credentials)
|
6
|
+
@api = api
|
7
|
+
@credentials = credentials
|
8
|
+
end
|
9
|
+
|
10
|
+
def login(&block)
|
11
|
+
login_info = @api.request_login
|
12
|
+
@api.auth_token = login_info.auth_token
|
13
|
+
|
14
|
+
yield(login_info.redirect_url)
|
15
|
+
|
16
|
+
viewer = Helpers.poll_until(->(viewer) { viewer&.email }) do
|
17
|
+
@api.viewer
|
18
|
+
end
|
19
|
+
|
20
|
+
@credentials.save!(viewer.email, login_info.auth_token)
|
21
|
+
|
22
|
+
viewer
|
23
|
+
end
|
24
|
+
|
25
|
+
def logout
|
26
|
+
@credentials.delete!
|
27
|
+
end
|
28
|
+
|
29
|
+
def viewer
|
30
|
+
@api.viewer
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TranslateClient
|
4
|
+
module CLI
|
5
|
+
class AuthCommands < TranslateClient::CLI::Base
|
6
|
+
desc "login", "login to the translate web app"
|
7
|
+
def login
|
8
|
+
authenticator = Auth.new(api, credentials)
|
9
|
+
|
10
|
+
viewer = authenticator.viewer
|
11
|
+
if viewer
|
12
|
+
puts "You're already logged in as #{viewer.email}."
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
viewer = authenticator.login do |redirect_url|
|
17
|
+
puts "Opening #{redirect_url} ..."
|
18
|
+
Launchy.open(redirect_url)
|
19
|
+
end
|
20
|
+
|
21
|
+
puts "You're now logged in as #{viewer.email}."
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "logout", "logout of the translate web app"
|
25
|
+
def logout
|
26
|
+
Auth.new(api, credentials).logout
|
27
|
+
puts "You're logged out."
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "whoami", "displays the currently logged in user"
|
31
|
+
def whoami
|
32
|
+
viewer = Auth.new(api, credentials).viewer
|
33
|
+
if viewer
|
34
|
+
puts "You're logged in as #{viewer.email}."
|
35
|
+
else
|
36
|
+
raise PresentableError.new("You're logged out.")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TranslateClient
|
4
|
+
module CLI
|
5
|
+
class Base < Thor
|
6
|
+
check_unknown_options!
|
7
|
+
check_default_type!
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def exit_on_failure? = true
|
11
|
+
|
12
|
+
def start(*)
|
13
|
+
super
|
14
|
+
rescue PresentableError => e
|
15
|
+
puts e.message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
no_commands do
|
22
|
+
def api
|
23
|
+
@api ||= API.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def credentials
|
27
|
+
@credentials ||= Credentials.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TranslateClient
|
4
|
+
module CLI
|
5
|
+
class Main < TranslateClient::CLI::Base
|
6
|
+
desc "auth", "Manage Authentication"
|
7
|
+
subcommand "auth", TranslateClient::CLI::AuthCommands
|
8
|
+
|
9
|
+
desc "push", "Push local translations to the translate web app"
|
10
|
+
option :project, type: :string, aliases: "-p", desc: "project id"
|
11
|
+
option :locale, type: :string, aliases: "-l", desc: "locale to push"
|
12
|
+
option :format, type: :string, aliases: "-f", desc: "format (#{ProjectConfig.supported_formats})"
|
13
|
+
option :in, type: :string, aliases: "-i", desc: "input file"
|
14
|
+
def push
|
15
|
+
config = ProjectConfig.new(
|
16
|
+
project_id: options[:project],
|
17
|
+
locale: options[:locale],
|
18
|
+
format: options[:format],
|
19
|
+
file: options[:in]
|
20
|
+
)
|
21
|
+
|
22
|
+
puts "Starting import of locales: #{config.locales.join(", ")}"
|
23
|
+
|
24
|
+
config.locales.each do |locale|
|
25
|
+
api.create_import(
|
26
|
+
project_id: config.project_id,
|
27
|
+
locale:,
|
28
|
+
format: config.format,
|
29
|
+
file: config.file(locale:)
|
30
|
+
)
|
31
|
+
|
32
|
+
puts "Import for locale #{locale} created"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "pull", "Pulls remote translations into the current app"
|
37
|
+
option :project, type: :string, aliases: "-p", desc: "project id"
|
38
|
+
option :locale, type: :string, aliases: "-l", desc: "locale to push"
|
39
|
+
option :format, type: :string, aliases: "-f", desc: "format (#{ProjectConfig.supported_formats})"
|
40
|
+
option :out, type: :string, aliases: "-o", desc: "output file"
|
41
|
+
def pull
|
42
|
+
config = ProjectConfig.new(
|
43
|
+
project_id: options[:project],
|
44
|
+
locale: options[:locale],
|
45
|
+
format: options[:format],
|
46
|
+
file: options[:out]
|
47
|
+
)
|
48
|
+
|
49
|
+
remote_locales = api.remote_locales(project_id: config.project_id)
|
50
|
+
config.locales.each do |locale|
|
51
|
+
if remote_locales.exclude?(locale)
|
52
|
+
raise PresentableError.new("Requested locale \"#{locale}\" is not available in the project, use one of: #{remote_locales.join(", ")}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
puts "Starting export of locales: #{config.locales.join(", ")}"
|
57
|
+
|
58
|
+
export_ids = config.locales.index_with do |locale|
|
59
|
+
export_id = api.create_export(
|
60
|
+
project_id: config.project_id,
|
61
|
+
locale:,
|
62
|
+
format: config.format
|
63
|
+
)
|
64
|
+
|
65
|
+
puts "Export #{export_id} created for locale #{locale}"
|
66
|
+
export_id
|
67
|
+
end
|
68
|
+
|
69
|
+
# poll each export until it's done
|
70
|
+
export_ids.each do |locale, export_id|
|
71
|
+
print "Pulling \"#{locale}\" ..."
|
72
|
+
tmp_file = Helpers.poll_until do
|
73
|
+
api.download_export(export_id:)
|
74
|
+
end
|
75
|
+
|
76
|
+
out = config.file(locale:)
|
77
|
+
File.rename(tmp_file.path, out)
|
78
|
+
puts "Pulled \"#{locale}\" to #{out}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TranslateClient
|
4
|
+
class Credentials
|
5
|
+
def save!(email, auth_token)
|
6
|
+
n = Netrc.read
|
7
|
+
n[netrc_domain] = email, auth_token
|
8
|
+
n.save
|
9
|
+
end
|
10
|
+
|
11
|
+
def auth_token
|
12
|
+
n = Netrc.read
|
13
|
+
n[netrc_domain]&.password
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete!
|
17
|
+
n = Netrc.read
|
18
|
+
n.delete(netrc_domain)
|
19
|
+
n.save
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def netrc_domain
|
25
|
+
TranslateClient::BASE_URL.host
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TranslateClient
|
4
|
+
module Helpers
|
5
|
+
POLLING_INTERVAL = 2
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def poll_until(condition_proc = ->(e) { e.present? }, &block)
|
9
|
+
start = Time.now
|
10
|
+
|
11
|
+
loop do
|
12
|
+
result = block.call
|
13
|
+
return result if condition_proc.call(result)
|
14
|
+
|
15
|
+
if Time.now - start > 120
|
16
|
+
raise PresentableError.new("Timed out waiting for result. Please try again.")
|
17
|
+
end
|
18
|
+
|
19
|
+
sleep POLLING_INTERVAL
|
20
|
+
print "."
|
21
|
+
end
|
22
|
+
ensure
|
23
|
+
puts "" # finish line of dots
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TranslateClient
|
4
|
+
class ProjectConfig
|
5
|
+
SUPPORTED_FORMATS = ["rails", "i18n_js"].freeze
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def supported_formats
|
9
|
+
SUPPORTED_FORMATS.map(&:dasherize).join(", ")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(**options)
|
14
|
+
@options = options
|
15
|
+
|
16
|
+
project_id
|
17
|
+
locales
|
18
|
+
format
|
19
|
+
locales.each { |locale| file(locale:) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def project_id
|
23
|
+
@options[:project_id] || default_project_id
|
24
|
+
end
|
25
|
+
|
26
|
+
def locales
|
27
|
+
Array(@options[:locale] || default_locales)
|
28
|
+
end
|
29
|
+
|
30
|
+
def format
|
31
|
+
format = @options[:format]&.underscore
|
32
|
+
|
33
|
+
if format && SUPPORTED_FORMATS.exclude?(format)
|
34
|
+
raise PresentableError.new("Format \"#{@options[:format]}\" is not supported")
|
35
|
+
end
|
36
|
+
|
37
|
+
format || default_format
|
38
|
+
end
|
39
|
+
|
40
|
+
def file(locale:)
|
41
|
+
if locales.exclude?(locale)
|
42
|
+
raise PresentableError.new("Locale \"#{locale}\" not found in #{locales.join(", ")}")
|
43
|
+
end
|
44
|
+
|
45
|
+
@options[:file] || default_file(locale)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def default_project_id
|
51
|
+
project_id = package_json.dig("translate", "project")
|
52
|
+
|
53
|
+
unless project_id
|
54
|
+
raise PresentableError.new("Couldn't detect project id automatically. Please specify with --project.")
|
55
|
+
end
|
56
|
+
|
57
|
+
project_id
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_format
|
61
|
+
if rails?
|
62
|
+
"rails"
|
63
|
+
else
|
64
|
+
raise PresentableError.new("Couldn't detect format automatically. Please specify with --format.")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_locales
|
69
|
+
if rails?
|
70
|
+
Dir.glob("config/locales/*.yml").map do |file|
|
71
|
+
File.basename(file, ".yml")
|
72
|
+
end
|
73
|
+
else
|
74
|
+
raise PresentableError.new("Couldn't detect available locales automatically. Please specify with --locale.")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def default_file(locale)
|
79
|
+
if rails?
|
80
|
+
"config/locales/#{locale}.yml"
|
81
|
+
else
|
82
|
+
raise PresentableError.new("Couldn't detect file automatically. Please specify with --in/--out.")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def package_json
|
87
|
+
return {} unless File.exist?("package.json")
|
88
|
+
@package_json ||= JSON.parse(File.read("package.json"))
|
89
|
+
end
|
90
|
+
|
91
|
+
def rails?
|
92
|
+
@rails ||= File.exist?("Gemfile") && File.read("Gemfile").match?(/gem.+\brails\b/)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/translate_client.rb
CHANGED
@@ -1,10 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require "ostruct"
|
4
|
+
require "tempfile"
|
5
|
+
require "active_support/all"
|
6
|
+
require "launchy"
|
7
|
+
require "netrc"
|
8
|
+
require "thor"
|
9
|
+
|
10
|
+
require_relative "translate_client/version"
|
11
|
+
require_relative "translate_client/project_config"
|
12
|
+
require_relative "translate_client/cli/base"
|
13
|
+
require_relative "translate_client/cli/auth"
|
14
|
+
require_relative "translate_client/cli/main"
|
15
|
+
require_relative "translate_client/auth"
|
16
|
+
require_relative "translate_client/api"
|
17
|
+
require_relative "translate_client/credentials"
|
18
|
+
require_relative "translate_client/helpers"
|
19
|
+
require_relative "translate_client/railtie" if defined?(Rails::Railtie)
|
6
20
|
|
7
21
|
module TranslateClient
|
22
|
+
BASE_URL = URI.parse(ENV["TRANSLATE_BASE_URL"] || "https://translate.nerdgeschoss.de").freeze
|
23
|
+
|
8
24
|
class Error < StandardError; end
|
25
|
+
|
9
26
|
class NetworkError < Error; end
|
27
|
+
|
28
|
+
class PresentableError < Error; end
|
10
29
|
end
|
data/translate_client.gemspec
CHANGED
@@ -11,11 +11,11 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.summary = "Handle translations in your Rails app with the translate web app."
|
12
12
|
spec.homepage = "https://github.com/nerdgeschoss/translate"
|
13
13
|
spec.license = "MIT"
|
14
|
-
spec.required_ruby_version = ">=
|
14
|
+
spec.required_ruby_version = ">= 3.0.0"
|
15
15
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
17
17
|
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
-
spec.metadata["changelog_uri"] = "https://github.com/nerdgeschoss/
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/nerdgeschoss/translate/blob/main/cli/CHANGELOG.md"
|
19
19
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -28,8 +28,12 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
29
|
spec.require_paths = ["lib"]
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
spec.add_dependency "activesupport"
|
32
|
+
spec.add_dependency "launchy"
|
33
|
+
spec.add_dependency "netrc"
|
34
|
+
spec.add_dependency "thor"
|
35
|
+
spec.add_dependency "faraday"
|
36
|
+
spec.add_dependency "faraday-multipart"
|
33
37
|
|
34
38
|
# For more information and examples about making a new gem, checkout our
|
35
39
|
# guide at: https://bundler.io/guides/creating_gem.html
|
metadata
CHANGED
@@ -1,23 +1,108 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: translate_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jens Ravens
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
13
|
-
|
11
|
+
date: 2024-01-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: launchy
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
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: netrc
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: thor
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: faraday
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
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: faraday-multipart
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
14
98
|
email:
|
15
99
|
- jens@nerdgeschoss.de
|
16
|
-
executables:
|
100
|
+
executables:
|
101
|
+
- translate
|
17
102
|
extensions: []
|
18
103
|
extra_rdoc_files: []
|
19
104
|
files:
|
20
|
-
- ".
|
105
|
+
- ".rspec"
|
21
106
|
- CHANGELOG.md
|
22
107
|
- CODE_OF_CONDUCT.md
|
23
108
|
- Gemfile
|
@@ -27,10 +112,17 @@ files:
|
|
27
112
|
- Rakefile
|
28
113
|
- bin/console
|
29
114
|
- bin/setup
|
115
|
+
- exe/translate
|
30
116
|
- lib/translate_client.rb
|
31
|
-
- lib/translate_client/
|
117
|
+
- lib/translate_client/api.rb
|
118
|
+
- lib/translate_client/auth.rb
|
119
|
+
- lib/translate_client/cli/auth.rb
|
120
|
+
- lib/translate_client/cli/base.rb
|
121
|
+
- lib/translate_client/cli/main.rb
|
122
|
+
- lib/translate_client/credentials.rb
|
123
|
+
- lib/translate_client/helpers.rb
|
124
|
+
- lib/translate_client/project_config.rb
|
32
125
|
- lib/translate_client/railtie.rb
|
33
|
-
- lib/translate_client/tasks/locale/sync.rake
|
34
126
|
- lib/translate_client/version.rb
|
35
127
|
- translate_client.gemspec
|
36
128
|
homepage: https://github.com/nerdgeschoss/translate
|
@@ -39,8 +131,8 @@ licenses:
|
|
39
131
|
metadata:
|
40
132
|
homepage_uri: https://github.com/nerdgeschoss/translate
|
41
133
|
source_code_uri: https://github.com/nerdgeschoss/translate
|
42
|
-
changelog_uri: https://github.com/nerdgeschoss/
|
43
|
-
post_install_message:
|
134
|
+
changelog_uri: https://github.com/nerdgeschoss/translate/blob/main/cli/CHANGELOG.md
|
135
|
+
post_install_message:
|
44
136
|
rdoc_options: []
|
45
137
|
require_paths:
|
46
138
|
- lib
|
@@ -48,15 +140,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
140
|
requirements:
|
49
141
|
- - ">="
|
50
142
|
- !ruby/object:Gem::Version
|
51
|
-
version:
|
143
|
+
version: 3.0.0
|
52
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
145
|
requirements:
|
54
146
|
- - ">="
|
55
147
|
- !ruby/object:Gem::Version
|
56
148
|
version: '0'
|
57
149
|
requirements: []
|
58
|
-
rubygems_version: 3.
|
59
|
-
signing_key:
|
150
|
+
rubygems_version: 3.3.26
|
151
|
+
signing_key:
|
60
152
|
specification_version: 4
|
61
153
|
summary: Handle translations in your Rails app with the translate web app.
|
62
154
|
test_files: []
|
data/.editorconfig
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
; EditorConfig is awesome: http://EditorConfig.org
|
2
|
-
|
3
|
-
; top-most EditorConfig file
|
4
|
-
root = true
|
5
|
-
|
6
|
-
; Unix-style newlines with a newline ending every file
|
7
|
-
[*]
|
8
|
-
indent_style = spaces
|
9
|
-
end_of_line = lf
|
10
|
-
insert_final_newline = true
|
11
|
-
trim_trailing_whitespace = true
|
12
|
-
indent_size = 2
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module TranslateClient
|
2
|
-
class Client
|
3
|
-
class Error < StandardError; end
|
4
|
-
class NetworkError < Error; end
|
5
|
-
|
6
|
-
attr_reader :locales, :token, :base_url
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@locales = ([I18n.default_locale.to_s] + I18n.available_locales.map(&:to_s)).uniq
|
10
|
-
@token, @base_url = Rails.application.config_for(:translate).values_at(:token, :url)
|
11
|
-
end
|
12
|
-
|
13
|
-
def upload
|
14
|
-
locales.each do |locale|
|
15
|
-
path = locale_path(locale)
|
16
|
-
next unless path.exist?
|
17
|
-
|
18
|
-
puts "uploading #{locale} from #{path}"
|
19
|
-
response = HTTParty.put("#{base_url}#{token}", body: path.read, headers: { 'Content-Type' => 'text/x-yaml' })
|
20
|
-
raise NetworkError, response.body unless response.success?
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def download
|
25
|
-
locales.each do |locale|
|
26
|
-
path = locale_path(locale)
|
27
|
-
puts "downloading #{locale} to #{path}"
|
28
|
-
response = HTTParty.get("#{base_url}#{token}/#{locale}.yml")
|
29
|
-
raise NetworkError, response.body unless response.success?
|
30
|
-
|
31
|
-
File.write(path, response.body)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def sync
|
36
|
-
upload
|
37
|
-
download
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def locale_path(locale)
|
43
|
-
Rails.root.join("config", "locales", "#{locale}.yml")
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|