Назад в блог
Terminal window showing a Gemfile entry for the PolyLingo gem alongside a short Ruby translate call and its output hash.

Перевод структурированного контента из Ruby с помощью библиотеки PolyLingo

By Robert M

Перевод структурированного контента из Ruby с помощью библиотеки PolyLingo

Библиотека PolyLingo для Ruby теперь доступна на RubyGems. Она охватывает весь API PolyLingo: синхронный перевод, пакетные запросы, асинхронные задания с опросом и все вспомогательные конечные точки. Не имеет зависимостей во время выполнения и требует Ruby 2.7 или новее.

В этом посте описывается установка, настройка клиента и все доступные методы библиотеки.


Установка

Добавьте в ваш Gemfile:

gem "polylingo"

Затем выполните:

bundle install

Или установите напрямую:

gem install polylingo

Нет зависимостей во время выполнения. Библиотека использует только стандартную библиотеку Ruby для HTTP.


Настройка клиента

require "polylingo"

client = PolyLingo.new(api_key: ENV.fetch("POLYLINGO_API_KEY"))

Доступны две необязательные настройки:

client = PolyLingo.new(
  api_key:  ENV.fetch("POLYLINGO_API_KEY"),
  base_url: "https://api.usepolylingo.com/v1", # по умолчанию, переопределите для self-hosted инстансов
  timeout:  120,                               # секунды (открытие + чтение), по умолчанию 120
)

Храните ваш API ключ в переменной окружения. Никогда не хардкодьте его и не коммитьте в систему контроля версий.


Перевод контента

Один запрос

Передайте ваш контент, массив кодов целевых языков и необязательный формат:

result = client.translate(
  content: "# Привет\n\nЭто **структурированный** контент.",
  targets: %w[es fr de],
  format:  "markdown"
)

puts result["translations"]["es"]
puts result["usage"]["total_tokens"]

Параметр format принимает значения plain, markdown, json или html. Если опущен, API автоматически определяет формат. Также можно передать source как подсказку языка и model как "standard" (по умолчанию) или "advanced".

Сохранение формата работает так же, как и в остальной части API PolyLingo. Для json переводятся только строковые значения, ключи, вложенность и нестроковые типы остаются без изменений. Для markdown заголовки остаются заголовками, а блоки кода сохраняются без изменений. Для html теги и атрибуты сохраняются, переводятся только текстовые узлы.

Перевод JSON-файла локализации

require "json"

source = JSON.parse(File.read("config/locales/en.yml"))

result = client.translate(
  content: source.to_json,
  format:  "json",
  targets: %w[fr de ja]
)

%w[fr de ja].each do |locale|
  translated = JSON.parse(result["translations"][locale])
  File.write(
    "config/locales/#{locale}.json",
    JSON.pretty_generate(translated)
  )
  puts "Записан config/locales/#{locale}.json"
end

Один запрос обрабатывает все три локали. Ключи идентичны исходным во всех выходных файлах.


Пакетные запросы

Отправьте до 100 элементов контента в одном запросе, каждый с собственным id и необязательным format:

result = client.batch(
  items: [
    { id: "hero_title",    content: "С возвращением",   format: "plain" },
    { id: "hero_subtitle", content: "Вот что изменилось сегодня", format: "plain" },
    { id: "cta",           content: "Начать",    format: "plain" },
  ],
  targets: %w[es fr de]
)

result["results"].each do |row|
  puts "#{row["id"]}: #{row["translations"]["es"]}"
end

Все элементы используют один и тот же массив targets. Переданный вами id сохраняется в ответе, чтобы вы могли сопоставить результаты с исходными данными без зависимости от порядка.


Асинхронные задания

Для длительных переводов API заданий принимает запрос, сразу возвращает job_id и позволяет опрашивать результат. Библиотека обрабатывает это двумя способами.

Добавить в очередь и опрашивать вручную

accepted = client.jobs.create(
  content: File.read("content/long-article.md"),
  targets: %w[es fr de ja zh],
  format:  "markdown"
)

job_id = accepted["job_id"]

loop do
  sleep 5
  state = client.jobs.get(job_id)
  break if %w[complete failed].include?(state["status"])
end

if state["status"] == "complete"
  translations = state["translations"]
end

Один вызов, который опрашивает до завершения

done = client.jobs.translate(
  content:       File.read("content/long-article.md"),
  targets:       %w[es fr de],
  format:        "markdown",

  # Необязательные переопределения (показаны по умолчанию):
  poll_interval: 5,    # секунд между опросами, по умолчанию 5
  timeout:       1200, # общий лимит ожидания в секундах, по умолчанию 1200 (20 минут)
  on_progress:   ->(queue_position) { puts "Позиция в очереди: #{queue_position.inspect}" }
)

translations = done["translations"]
usage        = done["usage"]

Все временные значения в Ruby-библиотеке указаны в секундах, в соответствии с конвенциями Ruby. Лямбда on_progress вызывается при каждом опросе и получает текущую позицию в очереди как целое число или nil, если API не возвращает её.


Вспомогательные конечные точки

health    = client.health
# => { "status" => "ok", "timestamp" => "..." }

languages = client.languages
# => { "languages" => [{ "code" => "en", "name" => "English", "rtl" => false }, ...] }

usage     = client.usage
# => { "usage" => { "tokens_used" => 12000, "tokens_remaining" => 38000, ... } }

health и languages не требуют ключа API. usage возвращает потребление токенов за текущий календарный месяц для аутентифицированного аккаунта.


Обработка ошибок

Все ошибки наследуются от PolyLingo::PolyLingoError. Обрабатывайте конкретные подклассы, которые хотите:

begin
  result = client.translate(
    content: "# Привет",
    targets: %w[es],
    format:  "markdown"
  )
rescue PolyLingo::AuthError => e
  # HTTP 401 — неверный, отсутствующий или отозванный ключ API
  puts "Ошибка аутентификации: #{e.message}"
rescue PolyLingo::RateLimitError => e
  # HTTP 429 — достигнут лимит запросов в минуту
  retry_after = e.retry_after # целые секунды или nil
  sleep retry_after if retry_after
  retry
rescue PolyLingo::JobFailedError => e
  # Асинхронная задача завершилась с ошибкой или время опроса истекло
  puts "Задача не выполнена: #{e.job_id}"
rescue PolyLingo::PolyLingoError => e
  # Все остальные ошибки API
  puts "#{e.status}: #{e.error} — #{e.message}"
end

RateLimitError#retry_after возвращает целое число из тела JSON-ответа или заголовка Retry-After, что присутствует, или nil, если ни того, ни другого нет. JobFailedError#job_id возвращает ID неудачной задачи, если он известен.


Быстрая справка

МетодEndpointТребуется аутентификация
client.healthGET /healthНет
client.languagesGET /languagesНет
client.translate(...)POST /translateДа
client.batch(...)POST /translate/batchДа
client.usageGET /usageДа
client.jobs.create(...)POST /jobsДа
client.jobs.get(job_id)GET /jobs/:idДа
client.jobs.translate(...)POST /jobs + опросДа

Начало работы

Библиотека доступна на RubyGems по адресу rubygems.org/gems/polylingo. Исходный код на github.com/UsePolyLingo/polylingo-ruby. Полная документация API на usepolylingo.com/docs.

Бесплатный тариф включает 50 000 токенов в месяц. Кредитная карта не требуется.

gem install polylingo

Получите ваш API ключ