class PhraseAppUpdater::PhraseAppAPI

Constants

GIT_TAG_PREFIX
PAGE_SIZE
THREAD_COUNT

PhraseApp allows two concurrent connections at a time.

Public Class Methods

new(api_key, project_id, locale_file_class) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 15
def initialize(api_key, project_id, locale_file_class)
  @client            = PhraseApp::Client.new(PhraseApp::Auth::Credentials.new(token: api_key))
  @project_id        = project_id
  @locale_file_class = locale_file_class
end

Public Instance Methods

create_locale(name, default: false) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 68
def create_locale(name, default: false)
  phraseapp_request do
    params = PhraseApp::RequestParams::LocaleParams.new(
      name: name,
      code: name,
      default: default,
    )
    @client.locale_create(@project_id, params)
  end
end
create_project(name, parent_commit) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 21
def create_project(name, parent_commit)
  params = PhraseApp::RequestParams::ProjectParams.new(
    name: name,
    main_format: @locale_file_class.phraseapp_type)
  project = phraseapp_request { @client.project_create(params) }
  STDERR.puts "Created project #{name} for #{parent_commit}"

  @project_id = project.id
  store_parent_commit(parent_commit)

  project.id
end
download_file(locale, skip_unverified) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 111
def download_file(locale, skip_unverified)
  download_params = PhraseApp::RequestParams::LocaleDownloadParams.new

  download_params.file_format                  = @locale_file_class.phraseapp_type
  download_params.skip_unverified_translations = skip_unverified

  phraseapp_request { @client.locale_download(@project_id, locale.id, download_params) }
end
download_files(locales, skip_unverified:) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 79
def download_files(locales, skip_unverified:)
  results = threaded_request(locales) do |locale|
    STDERR.puts "Downloading file for #{locale}"
    download_file(locale, skip_unverified)
  end

  locales.zip(results).map do |locale, file_contents|
    @locale_file_class.from_file_content(locale.name, file_contents)
  end
end
fetch_locales() click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 60
def fetch_locales
  # This is a paginated API, however the maximum page size of 100 is well
  # above our expected locale size, so we take the first page only for now
  phraseapp_request { @client.locales_list(@project_id, 1, 100) }.map do |pa_locale|
    Locale.new(pa_locale)
  end
end
lookup_project_id(name) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 34
def lookup_project_id(name)
  result, = paginate_request(:projects_list, limit: 1) { |p| p.name == name }
  raise ProjectNotFoundError.new(name) if result.nil?

  result.id
end
read_parent_commit() click to toggle source

We mark projects with their parent git commit using a tag with a well-known prefix. We only allow one tag with this prefix at once.

# File lib/phraseapp_updater/phraseapp_api.rb, line 43
def read_parent_commit
  git_tag, = paginate_request(:tags_list, @project_id, limit: 1) do |t|
    t.name.start_with?(GIT_TAG_PREFIX)
  end
  raise MissingGitParentError.new if git_tag.nil?

  git_tag.name.delete_prefix(GIT_TAG_PREFIX)
end
remove_keys_not_in_upload(upload_id) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 134
def remove_keys_not_in_upload(upload_id)
  delete_params   = PhraseApp::RequestParams::KeysDeleteParams.new
  delete_params.q = "unmentioned_in_upload:#{upload_id}"

  begin
    phraseapp_request { @client.keys_delete(@project_id, delete_params) }
  rescue RuntimeError => _e
    # PhraseApp will accept but mark invalid uploads, however the gem
    # returns the same response in both cases. If we call this API
    # with the ID of an upload of a bad file, it will fail.
    # This usually occurs when sending up an empty file, which is
    # a case we can ignore. However, it'd be better to have a way
    # to detect a bad upload and find the cause.
  end
end
remove_keys_not_in_uploads(upload_ids) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 104
def remove_keys_not_in_uploads(upload_ids)
  threaded_request(upload_ids) do |upload_id|
    STDERR.puts "Removing keys not in upload #{upload_id}"
    remove_keys_not_in_upload(upload_id)
  end
end
update_parent_commit(commit_hash) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 52
def update_parent_commit(commit_hash)
  previous_parent = read_parent_commit
  phraseapp_request do
    @client.tag_delete(@project_id, GIT_TAG_PREFIX + previous_parent)
  end
  store_parent_commit(commit_hash)
end
upload_file(locale_file) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 120
def upload_file(locale_file)
  upload_params = create_upload_params(locale_file.locale_name)

  # The PhraseApp gem only accepts a filename to upload,
  # so we need to write the file out and pass it the path
  Tempfile.create([locale_file.locale_name, ".json"]) do |f|
    f.write(locale_file.content)
    f.close

    upload_params.file = f.path
    phraseapp_request { @client.upload_create(@project_id, upload_params) }.id
  end
end
upload_files(locale_files, default_locale:) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 90
def upload_files(locale_files, default_locale:)
  known_locales = fetch_locales.index_by(&:name)

  threaded_request(locale_files) do |locale_file|
    unless known_locales.has_key?(locale_file.locale_name)
      create_locale(locale_file.locale_name,
                    default: (locale_file.locale_name == default_locale))
    end

    STDERR.puts "Uploading #{locale_file}"
    upload_file(locale_file)
  end
end

Private Instance Methods

create_upload_params(locale_name) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 219
def create_upload_params(locale_name)
  upload_params = PhraseApp::RequestParams::UploadParams.new
  upload_params.file_encoding       = 'UTF-8'
  upload_params.file_format         = @locale_file_class.phraseapp_type
  upload_params.locale_id           = locale_name
  upload_params.skip_unverification = false
  upload_params.update_translations = true
  upload_params.tags                = [generate_upload_tag]
  upload_params
end
generate_upload_tag() click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 230
def generate_upload_tag
  "phraseapp_updater_upload_#{Time.now.strftime('%Y%m%d%H%M%S')}"
end
paginate_request(method, *params, limit: nil, &filter) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 158
def paginate_request(method, *params, limit: nil, &filter)
  page = 1
  results = []

  loop do
    response = phraseapp_request do
      @client.public_send(method, *params, page, PAGE_SIZE)
    end

    break if response.empty?

    matches = response.filter(&filter)
    matches = matches[0, limit - results.size] if limit

    unless matches.empty?
      results.concat(matches)

      break if results.size == limit
    end

    page += 1
  end

  results
end
phraseapp_request(&block) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 184
def phraseapp_request(&block)
  res, err = block.call

  unless err.nil?
    error =
      if err.respond_to?(:error)
        err.error
      else
        err.errors.join('|')
      end

    raise RuntimeError.new(error)
  end

  res

rescue RuntimeError => e
  if e.message.match?(/\(401\)/)
    raise BadAPIKeyError.new(e)
  elsif e.message.match?(/not found/)
    raise BadProjectIDError.new(e, @project_id)
  elsif e.message.match?(/has already been taken/)
    raise ProjectNameTakenError.new(e)
  else
    raise
  end
end
store_parent_commit(commit_hash) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 152
def store_parent_commit(commit_hash)
  params = PhraseApp::RequestParams::TagParams.new(
    name: GIT_TAG_PREFIX + commit_hash)
  phraseapp_request { @client.tag_create(@project_id, params) }
end
threaded_request(worklist, &block) click to toggle source
# File lib/phraseapp_updater/phraseapp_api.rb, line 215
def threaded_request(worklist, &block)
  Parallel.map(worklist, in_threads: THREAD_COUNT, &block)
end