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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e78b6048707337f79dc68ecffb853c1d6670c3fe50a9351d19f1acebebbab9e2
4
- data.tar.gz: d312afd20e09c94311be0569d460c60e62f610eee39472193d6c13b40f3185b6
3
+ metadata.gz: 1734fc8e2f609fb623df6ece874e2813b4dec11ad90606beb886e1b71f8e2b0b
4
+ data.tar.gz: a5bf942896e2fe463d11afe16c89c7dd28a31ff9ee9d7b4d24a9e90e2ee80f9f
5
5
  SHA512:
6
- metadata.gz: b96bcdcefd651cce4ebbb79b4efa1cd486ba1aa86e444efaa8304e7f56c06eddb933208d506020bfd0a460f8afb2894ab42b0d38ba57f7f928f30fadc7e2f6fe
7
- data.tar.gz: c25a3cf776ad93c5e9f8ccb5607bf9cb74b034f0c689ed426468cad547e9c5e1530a91503cc83c609da0fadfc82a27516414f6a9c342c16a6bc1cca0a7abc2bd
6
+ metadata.gz: 430b3edf84f18a097eb3635d642851ef97695a101ac72f9e0e4daf5d28a13823f369ad01195c947ab9bbcf409f7c615bae7088505fd39ea9bce6dc822234adb7
7
+ data.tar.gz: 477a3fc86c5d7c40deff6273c9b85f5dbb9c48e9039388a2c1fb71f7d6f7771ffd00009b18d3197bf09a8849f1d0263e788b98349ad68e7f0e60d94d0998ee7f
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/CHANGELOG.md CHANGED
@@ -1,5 +1,3 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2021-11-19
4
-
5
- - Initial release
3
+ - Initial release. Check the README.md for an introduction.
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.2.31
93
+ 2.4.17
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 Jens Ravens
3
+ Copyright (c) 2023 nerdgeschoss GmbH
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,24 +1,99 @@
1
- # TranslateClient
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
- Add this line to your application's Gemfile:
5
+ ### Install as part of your application
6
+
7
+ Add this line to your application's `Gemfile`:
10
8
 
11
9
  ```ruby
12
- gem "translate_client"
10
+ gem 'translate_client'
13
11
  ```
14
12
 
15
13
  And then execute:
16
14
 
17
- $ bundle install
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
- TODO: Write usage instructions here
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::Client 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).
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
@@ -1,4 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- task default: %i[]
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console CHANGED
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "bundler/setup"
5
- require "translate/client"
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,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.join(__dir__, "../lib"))
5
+ require "translate_client"
6
+
7
+ TranslateClient::CLI::Main.start(ARGV)
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TranslateClient
2
4
  class Railtie < Rails::Railtie
3
5
  rake_tasks do
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TranslateClient
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
@@ -1,10 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "./translate_client/version"
4
- require_relative "./translate_client/client"
5
- require_relative "./translate_client/railtie" if defined?(Rails::Railtie)
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
@@ -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 = ">= 2.6.0"
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/translate_client/blob/main/CHANGELOG.md"
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
- # Uncomment to register a new dependency of your gem
32
- # spec.add_dependency "example-gem", "~> 1.0"
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.1
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: 2021-11-19 00:00:00.000000000 Z
12
- dependencies: []
13
- description:
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
- - ".editorconfig"
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/client.rb
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/translate_client/blob/main/CHANGELOG.md
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: 2.6.0
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.1.6
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
@@ -1,6 +0,0 @@
1
- namespace :locale do
2
- desc "Synchronizes locale yml files with the translation server."
3
- task sync: :environment do
4
- TranslateClient::Client.new.sync
5
- end
6
- end