Skip to content

Commit

Permalink
version 5.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
bodrovis committed May 10, 2024
1 parent c27de4a commit 5995b11
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 118 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 5.1.1

* Update documentation, minor code fixes

## 5.1.0 (09-Feb-2024)

* Handle rare case when the server returns HTML instead of JSON which happens when too many requests are sent
Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@
[![Maintainability](https://api.codeclimate.com/v1/badges/9b682367a274ee3dcdee/maintainability)](https://codeclimate.com/github/bodrovis/lokalise_manager/maintainability)
![Downloads total](https://img.shields.io/gem/dt/lokalise_manager)

This gem provides [Lokalise](http://lokalise.com) integration for Ruby and allows to exchange translation files between your project and TMS easily. It relies on [ruby-lokalise-api](https://lokalise.github.io/ruby-lokalise-api) to send APIv2 requests.
The LokaliseManager gem provides seamless integration with [Lokalise](http://lokalise.com), enabling easy exchange of translation files between your Ruby project and the Lokalise translation management system (TMS). It leverages the [ruby-lokalise-api](https://lokalise.github.io/ruby-lokalise-api) to send and manage APIv2 requests.

If you are looking for a Rails integration, please check [lokalise_rails](https://github.com/bodrovis/lokalise_rails) which provides a set of Rake tasks for importing/exporting.
For integration directly with Rails applications, refer to [lokalise_rails](https://github.com/bodrovis/lokalise_rails), which offers a suite of Rake tasks specifically designed for importing and exporting translation files.

## Getting started
## Getting Started

### Requirements

This gem requires Ruby 3.0+. You will also need to [setup a Lokalise account](https://app.lokalise.com/signup) and create a [translation project](https://docs.lokalise.com/en/articles/1400460-projects). Finally, you will need to generate a [read/write API token](https://docs.lokalise.com/en/articles/1929556-api-tokens) at your Lokalise profile.
- **Ruby version**: Ruby 3.0 or higher is required.
- **Lokalise account**: You must have an active [Lokalise account](https://app.lokalise.com/signup).
- **Project setup**: Create a [translation project](https://docs.lokalise.com/en/articles/1400460-projects) within your Lokalise account.
- **API token**: Obtain a read/write [API token](https://docs.lokalise.com/en/articles/1929556-api-tokens) from your Lokalise profile.

Alternatively, you can utilize a token obtained via OAuth 2 flow. When using such a token, you'll have to set `:use_oauth2_token` option to `true` (see below).
### Optional

- **OAuth 2 token**: If you prefer using an OAuth 2 token instead of a standard API token, set the `:use_oauth2_token` option to `true` in your configuration settings.

### Installation

Expand Down
32 changes: 16 additions & 16 deletions lib/lokalise_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,34 @@
loader = Zeitwerk::Loader.for_gem
loader.setup

# LokaliseManager main module that exposes helper methods:
#
# importer = LokaliseManager.importer api_token: '1234abc', project_id: '123.abc'
# exporter = LokaliseManager.exporter api_token: '1234abc', project_id: '123.abc'
#
# Use the instantiated objects to import or export your translation files:
# The LokaliseManager module provides functionalities to import and export translation
# files to and from the Lokalise TMS. It simplifies interactions with the Lokalise API
# by providing a straightforward interface to instantiate importers and exporters.
#
# Example:
# importer = LokaliseManager.importer(api_token: '1234abc', project_id: '123.abc')
# exporter = LokaliseManager.exporter(api_token: '1234abc', project_id: '123.abc')
# importer.import!
# exporter.export!
#
module LokaliseManager
class << self
# Initializes a new importer client which is used to download
# translation files from Lokalise to the current project
# Creates an importer object for downloading translation files from Lokalise.
#
# @param custom_opts [Hash] Custom options for the importer (e.g., API token and project ID).
# @param global_config [Object] Global configuration settings, defaults to LokaliseManager::GlobalConfig.
# @return [LokaliseManager::TaskDefinitions::Importer] An instance of the importer.
#
# @return [LokaliseManager::TaskDefinitions::Importer]
# @param custom_opts [Hash]
# @param global_config [Object]
def importer(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
LokaliseManager::TaskDefinitions::Importer.new custom_opts, global_config
end

# Initializes a new exporter client which is used to upload
# translation files from the current project to Lokalise
# Creates an exporter object for uploading translation files to Lokalise.
#
# @param custom_opts [Hash] Custom options for the exporter (e.g., API token and project ID).
# @param global_config [Object] Global configuration settings, defaults to LokaliseManager::GlobalConfig.
# @return [LokaliseManager::TaskDefinitions::Exporter] An instance of the exporter.
#
# @return [LokaliseManager::TaskDefinitions::Exporter]
# @param custom_opts [Hash]
# @param global_config [Object]
def exporter(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
LokaliseManager::TaskDefinitions::Exporter.new custom_opts, global_config
end
Expand Down
4 changes: 3 additions & 1 deletion lib/lokalise_manager/error.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# frozen_string_literal: true

module LokaliseManager
# LokaliseManager error class, subclass of StandardError
# The Error class provides a custom exception type for the LokaliseManager,
# allowing the library to raise specific errors that can be easily identified
# and handled separately from other StandardError exceptions in Ruby.
class Error < StandardError
# Initializes a new Error object
def initialize(message = '')
Expand Down
43 changes: 21 additions & 22 deletions lib/lokalise_manager/global_config.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# frozen_string_literal: true

module LokaliseManager
# Global configuration options available for LokaliseManager
# GlobalConfig provides a central place to manage configuration settings for LokaliseManager.
# It allows setting various operational parameters such as API tokens, paths, and behavior modifiers.
class GlobalConfig
class << self
attr_accessor :api_token, :project_id
Expand All @@ -11,58 +12,57 @@ class << self
:max_retries_export, :max_retries_import, :use_oauth2_token, :silent_mode,
:raise_on_export_fail

# Main interface to provide configuration options
# Yield self to block for configuration
def config
yield self
end

# When enabled, will re-raise any exception that happens during file exporting
# Return whether to raise on export failure
def raise_on_export_fail
@raise_on_export_fail || true
@raise_on_export_fail.nil? ? true : @raise_on_export_fail
end

# When enabled, won't print any debugging info to $stdout
# Return whether debugging information is suppressed
def silent_mode
@silent_mode || false
end

# When enabled, will use OAuth 2 Lokalise client and will require to provide a token obtained via OAuth 2 flow
# rather than via Lokalise profile
# Return whether to use OAuth2 tokens instead of regular API tokens
def use_oauth2_token
@use_oauth2_token || false
end

# Full path to directory with translation files
# Return the path to locales
def locales_path
@locales_path || "#{Dir.getwd}/locales"
end

# Project branch to use
# Return the project branch
def branch
@branch || ''
end

# Set request timeouts for the Lokalise API client
# Return API request timeouts
def timeouts
@timeouts || {}
end

# Maximum number of retries for file exporting
# Return the max retries for export
def max_retries_export
@max_retries_export || 5
end

# Maximum number of retries for file importing
# Return the max retries for import
def max_retries_import
@max_retries_import || 5
end

# Regular expression used to select translation files with proper extensions
# Return the regex for file extensions
def file_ext_regexp
@file_ext_regexp || /\.ya?ml\z/i
end

# Options for import rake task
# Return import options with defaults
def import_opts
@import_opts || {
format: 'ruby_yaml',
Expand All @@ -74,33 +74,32 @@ def import_opts
}
end

# Options for export rake task
# Return export options
def export_opts
@export_opts || {}
end

# Enables safe mode for import. When enabled, will check whether the target folder is empty or not
# Return whether import should check if target is empty
def import_safe_mode
@import_safe_mode.nil? ? false : @import_safe_mode
end

# Additional file skip criteria to apply when performing export
# Return whether to skip file export based on a lambda condition
def skip_file_export
@skip_file_export || ->(_) { false }
end

# Load translations from raw data
def translations_loader
@translations_loader || ->(raw_data) { YAML.safe_load raw_data }
@translations_loader || ->(raw_data) { YAML.safe_load(raw_data) }
end

# Converts translations data to the proper format
# Convert raw translation data to YAML format
def translations_converter
@translations_converter || ->(raw_data) { YAML.dump(raw_data).gsub('\\\\n', '\n') }
end

# Infers lang ISO for the given translation file
# The lambda expects to accept the raw contents of the translation file
# and the full path to the file (instance of the `Pathname` class)
# Infer language ISO code from translation file
def lang_iso_inferer
@lang_iso_inferer || ->(data, _path) { YAML.safe_load(data)&.keys&.first }
end
Expand Down
46 changes: 22 additions & 24 deletions lib/lokalise_manager/task_definitions/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@

module LokaliseManager
module TaskDefinitions
# Base class for LokaliseManager task definitions that includes common methods and logic
# Base class for LokaliseManager task definitions, including common methods and logic.
# This class serves as the foundation for importer and exporter classes, handling API
# client interactions and configuration merging.
class Base
using LokaliseManager::Utils::HashUtils

attr_accessor :config

# Creates a new importer or exporter. It accepts custom config and merges it
# with the global config (custom config take precendence)
# Initializes a new task object by merging custom and global configurations.
#
# @param custom_opts [Hash]
# @param global_config [Object]
# @param custom_opts [Hash] Custom configurations for specific tasks.
# @param global_config [Object] Reference to the global configuration.
def initialize(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
primary_opts = global_config
.singleton_methods
Expand All @@ -32,10 +33,12 @@ def initialize(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
@config = config_klass.new all_opts
end

# Creates a Lokalise API client
# Creates or retrieves a Lokalise API client based on configuration.
#
# @return [RubyLokaliseApi::Client]
# @return [RubyLokaliseApi::Client] Lokalise API client.
def api_client
return @api_client if @api_client

client_opts = [config.api_token, config.timeouts]
client_method = config.use_oauth2_token ? :oauth2_client : :client

Expand All @@ -51,49 +54,44 @@ def reset_api_client!

private

# Checks task options
#
# @return Array
# Checks and validates task options, raising errors if configurations are missing.
def check_options_errors!
errors = []
errors << 'Project ID is not set!' if config.project_id.nil? || config.project_id.empty?
errors << 'Lokalise API token is not set!' if config.api_token.nil? || config.api_token.empty?

raise(LokaliseManager::Error, errors.join(' ')) if errors.any?
raise LokaliseManager::Error, errors.join(' ') if errors.any?
end

# Checks whether the provided file has a proper extension as dictated by the `file_ext_regexp` option
# Determines if the file has the correct extension based on the configuration.
#
# @return Boolean
# @param raw_path [String, Pathname]
# @param raw_path [String, Pathname] Path to check.
# @return [Boolean] True if the extension matches, false otherwise.
def proper_ext?(raw_path)
path = raw_path.is_a?(Pathname) ? raw_path : Pathname.new(raw_path)
config.file_ext_regexp.match? path.extname
end

# Returns directory and filename for the given entry
# Extracts the directory and filename from a given path.
#
# @return Array
# @param entry [String]
# @param entry [String] The file path.
# @return [Array] Contains [Pathname, Pathname] representing the directory and filename.
def subdir_and_filename_for(entry)
Pathname.new(entry).split
end

# Returns Lokalise project ID and branch, semicolumn separated
# Constructs a project identifier string that may include a branch.
#
# @return [String]
# @return [String] Project identifier potentially including the branch.
def project_id_with_branch
return config.project_id.to_s if config.branch.to_s.strip.empty?

"#{config.project_id}:#{config.branch}"
config.branch.to_s.strip.empty? ? config.project_id.to_s : "#{config.project_id}:#{config.branch}"
end

# In rare cases the server might return HTML instead of JSON.
# It happens when too many requests are being sent.
# Until this is fixed, we revert to this quick'n'dirty solution.
EXCEPTIONS = [JSON::ParserError, RubyLokaliseApi::Error::TooManyRequests].freeze

# Sends request with exponential backoff mechanism
# Implements an exponential backoff strategy for handling retries after failures.
def with_exp_backoff(max_retries)
return unless block_given?

Expand Down

0 comments on commit 5995b11

Please sign in to comment.