Ruby on Rails

This tutorial will show you how to use the Lokalise Ruby SDK to manage translation files and how to implement an OAuth 2 flow.

📘

In this tutorial you'll learn how to...

  • Work with Lokalise API tokens
  • Upload translation files with the Lokalise API
  • Download translation files with the Lokalise API
  • Implement an OAuth 2 flow
  • List customer projects
  • Act on the customer's behalf and upload/download translation files

You can find the source code at GitHub.

Prerequisites

This guide assumes that you have already created a Lokalise project (if not, learn how to create a new project in this guide). In this tutorial, we are going to upload English translations. Therefore, make sure that your project has the English language added with the en ISO code.

If you want to follow this guide locally on your computer, you need to have the following software installed:

What we are going to build

In the first part of this tutorial, we are going to create a simple Rails app that will upload an English translations file to your Lokalise project and download it back.

In the second part, we'll implement an OAuth 2 flow and act on the user's behalf to upload and download translation files to or from their project.

Preparing a new Rails app

Create a new Rails app by running the following command:

rails new LokaliseApiDemo

This is going to create an application skeleton for you.

Let’s also add two new files:

  • app/controllers/downloads_controller.rb
  • app/controllers/uploads_controller.rb

Open the config/routes.rb file and tweak it in the following way:

  resources :downloads, only: %i[new create]
  resources :uploads, only: %i[new create]

Installing dependencies

Open your Gemfile and add new gems in it, like so:

gem 'ruby-lokalise-api', '~> 6.0'
gem 'dotenv-rails', '~> 2.7'
gem 'rubyzip', '~> 2.3'
  • ruby-lokalise-api is our official API client
  • dotenv-rails will be used to safely store environment variables
  • rubyzip will be used to read ZIP files

Then run:

bundle install

To install the newly added dependencies.

Adding translation files

Next, proceed to the config/locales directory and open the en.yml file. This file contains English translations for your app. Let’s create four translation keys inside it:

en:
  upload_title: Upload translation file
  upload_btn: Upload!
  download_title: Download translation file
  download_btn: Download!

Working with API tokens

Getting an API token to use the Lokalise API

Create an .env file in the root of the project and add your Lokalise API token. Make sure to add an API token with read and write access to your Lokalise projects. Learn how to get a Lokalise API token.

LOKALISE_API_KEY=123secret456

❗️

Never publicly expose your API key!

Specifically, don’t forget to add the .env file to .gitignore.

Initializing the Client

Next, open uploads_controller.rb, load the Ruby SDK, and initialize an API client in the create action:

require 'ruby_lokalise_api'

class UploadsController < ApplicationController
  def new
  end

  def create
    client = RubyLokaliseApi.client ENV['LOKALISE_API_KEY']
  end
end

Getting your Lokalise project ID

Now add a new environment variable inside your .env file with your Lokalise project ID. Learn how to get the Lokalise project ID.

LOKALISE_PROJECT_ID=123.abc

We can now use these translations inside the views/uploads/new.html.erb file:

<h1><%= t 'upload_title' %></h1>

<%= button_to t('upload_btn'), uploads_path %>

Uploading translation files to your Lokalise project

Let’s write a simple script to upload the en.yml file to your Lokalise project. Please note that its contents must be Base64-encoded.

def create
  client = RubyLokaliseApi.client ENV['LOKALISE_API_KEY']

  file_content = File.read "#{Rails.root}/config/locales/en.yml"

  client.upload_file ENV['LOKALISE_PROJECT_ID'],
                              data: Base64.strict_encode64(file_content.strip),
                              filename: "en.yml",
                              lang_iso: "en"
                      
  redirect_to new_upload_path
end

Now that everything is ready, you can boot your server by running:

rails s

Then open your browser, proceed to http://localhost:3000/uploads/new, and click the "Upload!" button. After a couple of seconds your translation file should be uploaded to Lokalise.

Proceed to Lokalise, open your newly created project, and make sure the translation keys are present in the editor:

Great job!

Downloading translation files from Lokalise project

The download process is slightly more involved because we'll have to process a ZIP archive. First of all, let's tweak the view at views/downloads/new.html.erb:

<h1><%= t 'download_title' %></h1>

<%= button_to t('download_btn'), downloads_path %>

Now tweak the downloads_controller.rb:

require 'ruby_lokalise_api'
require 'open-uri'
require 'zip'

class DownloadsController < ApplicationController
  def new; end

  def create
    client = RubyLokaliseApi.client ENV['LOKALISE_API_KEY']

    zip_file = client.download_files(ENV['LOKALISE_PROJECT_ID'],
                                                      format: 'yaml',
                                                      placeholder_format: :icu,
                                                      yaml_include_root: true,
                                                      original_filenames: true,
                                                      directory_prefix: '',
                                                      indentation: '2sp',
                                                      filter_langs: %w[en])['bundle_url']

    open_and_process zip_file

    redirect_to new_download_path
  end
end

We are downloading the English translation files in YAML format. Of course, you can further adjust the download options: for example, if you exclude the filter_langs param, translations for all languages will be included in the download bundle.

Code the open_and_process private method:

class DownloadsController < ApplicationController
  # ...

  private

  def open_and_process(path)
    Zip::File.open_buffer(open_remote(path)) do |zip|
      zip.each do |entry|
        next unless entry.name == 'en.yml'
        process_zip(entry)
      end
    end
  end
end

As long as we would like to download a single translation file, we compare entry.name with the "en.yml" string. You can further adjust this condition to select all the files you are interested in.

Finally, add methods to open an archive and process its contents:

class DownloadsController < ApplicationController
  # ...

  private

  def open_remote(path)
    parsed_path = URI.parse(path)
    parsed_path.open
  end

  def process_zip(entry)
    data = YAML.safe_load(entry.get_input_stream.read)

    File.open(File.join(Rails.root, 'config', 'locales', entry.name), 'w+:UTF-8') do |f|
      f.write data.to_yaml
    end
  end
end

Basically, we are overwriting the existing content inside the config/locales/en.yml file with the content fetched from Lokalise.

🚧

Your existing translations will be overwritten!

Please remember that the current implementation overwrites any translations inside the en.yml file with the new ones. If you don't want this to happen, save your new translations to a file with another name. For example, you could put something like File.join(Rails.root, 'config', 'locales', "updated_#{entry.name})

Now you can adjust translations in Lokalise as you see fit, proceed to http://localhost:3000/downloads/new, click the "Download!" button, and make sure your translation file is updated accordingly. For example, you can try modifying the download_title translation key and making sure the header is updated accordingly after you press the "Download!" button.

Working with an OAuth 2 flow

Registering an OAuth 2 app

To get started, please reach out to our tech support and ask them to register an OAuth 2 app for you (if you don't have one registered already). You will be presented with the Lokalise client ID and client secret that we will be using in the next steps.

You can store these keys in the .env file:

OAUTH2_CLIENT_ID=your_id
OAUTH2_CLIENT_SECRET=your_secret

❗️

Never publicly expose your OAuth 2 client secret!

Specifically, don’t forget to add the .env file to .gitignore.

Implementing an OAuth 2 flow

Let's create a new oauth2_flows_controller.rb with the following content:

require 'ruby_lokalise_api'

class Oauth2FlowsController < ApplicationController
  def new
    @auth_url = auth_client.auth scope: %(read_projects read_files write_files),
                                                   redirect_uri: 'http://localhost:3000/oauth2_flows/callback',
                                                   state: rand(10_000)
  end

  private

  def auth_client
    RubyLokaliseApi.auth_client ENV['OAUTH2_CLIENT_ID'], ENV['OAUTH2_CLIENT_SECRET']
  end
end

In the new action, we are generating a special authentication URL that your customers will have to visit to log in via Lokalise and obtain a special token. We'll require this token to act on the customer's behalf.

The scope attribute contains an array of permissions you would like to request from the customer. As long as we want to fetch a list of customer projects and manage translation files, we'll provide three scopes.

The redirect_uri is the location where the customer will be redirected after granting the requested access rights.

state is a sequence of random characters to prevent CSRF attacks.

Now let's implement the callback action where the customer will land after logging in via Lokalise:

class Oauth2FlowsController < ApplicationController
  # ...

  def callback
    response = auth_client.token params[:code]
    session[:lokalise_token] = response.access_token
    session[:lokalise_refresh] = response.refresh_token

    redirect_to new_oauth2_flow_path
  end
end

params[:code] is a secret code that Lokalise will generate for you. By calling the token method along with the code, you are requesting an OAuth 2 access token as well as a refresh token. The access token is used to act on the user's behalf, but it has a limited lifespan (usually 3600 seconds; this value can be fetched with response.expires_in). After the token expires, you can refresh it by using refresh token: response = auth_client.refresh 'YOUR_REFRESH_TOKEN'. The response.access_token will contain a new token.

We are storing these two tokens in session and redirecting the user back to the new action.

Finally, we can add an action to clear the tokens and the chosen project (we'll get to this in a moment):

class Oauth2FlowsController < ApplicationController
  # ...

  def log_out
    session.delete :lokalise_token
    session.delete :lokalise_refresh
    session.delete :lokalise_project_id
    redirect_to new_oauth2_flow_path
  end
end

At this point, we can open views/oauth2_flows/new.html.erb and provide a minimalistic markup:

<h1>OAuth 2 flow</h1>

<% if session[:lokalise_token].present? %>
  Your OAuth 2 token: <%= session[:lokalise_token] %><br>
  Your OAuth 2 refresh token: <%= session[:lokalise_refresh] %><br>
  Your Lokalise project id: <%= session[:lokalise_project_id] %><br>

  <%= link_to 'Choose a project to work with', projects_path %><br>
  <%= link_to 'Upload file', new_upload_path %><br>
  <%= link_to 'Download file', new_download_path %><br><br>
  <%= link_to 'Log out', log_out_oauth2_flows_path %>
<% else %>
  <%= link_to 'Log in via Lokalise', @auth_url %>
<% end %>

Choosing a project to work with

The next step is allowing the customer to choose a project that they'd like to interact with. Therefore, create a new projects_controller.rb:

require 'ruby_lokalise_api'

class ProjectsController < ApplicationController
  before_action :check_token

  def index
    client = RubyLokaliseApi.oauth2_client session[:lokalise_token]
    @projects = client.projects(limit: 5000).collection.map { |p| { id: p.project_id, name: p.name } }
  end

  def choose
    session[:lokalise_project_id] = params[:project_id]
    redirect_to new_oauth2_flow_path
  end
end

Inside the index action, we use the customer's access token to fetch all projects and extract their IDs and names.

The choose action is used to actually pick one of the projects and store its ID in session.

Let's also open the application_controller.rb and add two methods:

class ApplicationController < ActionController::Base
  private

  def check_token
    redirect_to new_oauth2_flow_path unless session[:lokalise_token].present?
  end

  def check_project_id
    redirect_to new_oauth2_flow_path unless session[:lokalise_project_id].present?
  end
end

We'll use those to check whether the customer has logged in via Lokalise and has chosen a project.

Finally, open the views/projects/index.html.erb and add the following markup:

<h1>Choose a project to work with</h1>

<ul>
  <% @projects.each do |project| %>
    <li>
      <%= button_to project[:name], choose_projects_path(project_id: project[:id]) %>
      ID: <%= project[:id] %>
      <% if session[:lokalise_project_id] == project[:id] %>
        (currently chosen)
      <% end %>
    </li>
  <% end %>
</ul>

We simply display a list of all projects with buttons to choose one of them. Later, the customer can return to this page and choose another project as needed.

Setting up routes

Let's add all the necessary routes to the config/routes.rb file:

  # ...

  resources :oauth2_flows, only: %i[new] do
    collection do
      get 'callback'
      get 'log_out'
    end
  end

  resources :projects, only: %i[index] do
    collection do
      post 'choose'
    end
  end

  root 'oauth2_flows#new'

Uploading translation files on the user's behalf

The process of uploading files on the user's behalf is very similar to what we did with the regular API tokens. In fact, the only difference is how you instantiate the client and provide the project ID. Previously we wrote:

client = RubyLokaliseApi.client ENV['LOKALISE_API_KEY']

client.upload_file session[:lokalise_project_id] # ...

Now we should use the oauth2_client method instead, and fetch project ID from the session store:

client = RubyLokaliseApi.oauth2_client session[:lokalise_token]

client.upload_file session[:lokalise_project_id],
                            data: Base64.strict_encode64(file_content.strip),
                            filename: 'en.yml',
                            lang_iso: 'en'

Here's the full code for the "uploads" controller (note the usage of "before actions"):

class UploadsController < ApplicationController
  before_action :check_token
  before_action :check_project_id

  def new; end

  def create
    client = RubyLokaliseApi.oauth2_client session[:lokalise_token]

    file_content = File.read "#{Rails.root}/config/locales/en.yml"

    client.upload_file session[:lokalise_project_id],
                                data: Base64.strict_encode64(file_content.strip),
                                filename: 'en.yml',
                                lang_iso: 'en'

    redirect_to new_upload_path
  end
end

Downloading translation files on the user's behalf

The process of downloading files is once again very similar to what we did with the regular tokens. The only difference is how you instantiate the client and provide the project ID. Here's the full code for the "downloads" controller:

require 'ruby_lokalise_api'
require 'open-uri'
require 'zip'

class DownloadsController < ApplicationController
  before_action :check_token
  before_action :check_project_id

  def new; end

  def create
    client = RubyLokaliseApi.oauth2_client session[:lokalise_token]

    zip_file = client.download_files(session[:lokalise_project_id],
                                                      format: 'yaml',
                                                      placeholder_format: :icu,
                                                      yaml_include_root: true,
                                                      original_filenames: true,
                                                      directory_prefix: '',
                                                      indentation: '2sp',
                                                      filter_langs: %w[en])['bundle_url']

    open_and_process zip_file

    redirect_to new_download_path
  end

  private

  def open_and_process(path)
    Zip::File.open_buffer(open_remote(path)) do |zip|
      zip.each do |entry|
        next unless entry.name == 'en.yml'
        process_zip(entry)
      end
    end
  end

  def open_remote(path)
    parsed_path = URI.parse(path)
    parsed_path.open
  end

  def process_zip(entry)
    data = YAML.safe_load(entry.get_input_stream.read)

    File.open(File.join(Rails.root, 'config', 'locales', entry.name), 'w+:UTF-8') do |f|
      f.write data.to_yaml
    end
  end
end

Testing it out

Proceed to http://localhost:3000/oauth2_flows/new, and click "Log in via Lokalise". You'll be navigated to Lokalise and asked to grant the necessary permissions to your application:

Click "Allow access". You'll be navigated back to your app.

Next, click "Choose a project to work with", and select one of the projects.

Finally, click either "Upload file" or "Download file", and perform the uploading/downloading process as before. Make sure that your translations are updated accordingly.

That's it, great job!