<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Lokalise</title>
    <description>The latest articles on Forem by Lokalise (@lokalise).</description>
    <link>https://forem.com/lokalise</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F761%2Fba5c7bbf-22cb-4d90-9831-84f50bce486f.png</url>
      <title>Forem: Lokalise</title>
      <link>https://forem.com/lokalise</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lokalise"/>
    <language>en</language>
    <item>
      <title>Lokalise custom processors: Ruby on Rails</title>
      <dc:creator>Ilya Krukowski</dc:creator>
      <pubDate>Thu, 25 Aug 2022 14:41:00 +0000</pubDate>
      <link>https://forem.com/lokalise/lokalise-custom-processors-ruby-on-rails-565p</link>
      <guid>https://forem.com/lokalise/lokalise-custom-processors-ruby-on-rails-565p</guid>
      <description>&lt;p&gt;In this article, you will learn about a new Lokalise feature called &lt;strong&gt;Custom processor&lt;/strong&gt;. The Custom processor app enables you to intercept translation keys uploaded and downloaded on Lokalise, then analyze or transform those as needed.&lt;/p&gt;

&lt;p&gt;Today I'll show you how to create a Custom processor using Ruby on Rails framework, set it up on Lokalise, and test it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you'd like to know how to build a Custom processor with Node and Fastify, check out the corresponding tutorial. You can find even more code samples in our DevHub.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Custom processor?
&lt;/h2&gt;

&lt;p&gt;The Custom processor app enables you to utilize your own server or a third-party service to fetch translation keys imported to and exported from Lokalise, analyze, format, transform, or even remove those as needed.&lt;/p&gt;

&lt;p&gt;To better understand the idea, let's see how a regular upload is performed on Lokalise:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You choose one or more translation files to upload.&lt;/li&gt;
&lt;li&gt;Lokalise detects the translation file languages automatically, but you can adjust those as needed.&lt;/li&gt;
&lt;li&gt;You adjust any additional uploading options and click Upload.&lt;/li&gt;
&lt;li&gt;Lokalise parses the uploaded data, and extracts keys and translation values.&lt;/li&gt;
&lt;li&gt;Extracted data is displayed in the Lokalise TMS translation editor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, if you have a Custom processor set up, after performing step 4 Lokalise will automatically send the parsed data to your processor. There you can perform any additional actions, such as analyzing the uploaded data, transforming your translations, adding special formatting, removing unnecessary special characters or certain banned words, restructuring translation keys, and so on. The only requirement is that the data returned by your processor must preserve the initial structure.&lt;/p&gt;

&lt;p&gt;Now let's briefly cover the download process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You choose file formats to download.&lt;/li&gt;
&lt;li&gt;You adjust any additional options as needed and click Build and download.&lt;/li&gt;
&lt;li&gt;Lokalise collects the chosen keys and translations.&lt;/li&gt;
&lt;li&gt;Lokalise generates translation files based on the download options and zips those into an archive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, a Custom processor will intercept the data collected in step 3 and once again it can perform any additional modifications before these data are zipped into an archive.&lt;/p&gt;

&lt;p&gt;As you can see, the idea is quite simple, however with this feature you can automate repetitive manual work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom processor use cases
&lt;/h3&gt;

&lt;p&gt;There are various use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Translate the uploaded content using your own or  third-party service.&lt;/li&gt;
&lt;li&gt;Perform placeholder replacement.&lt;/li&gt;
&lt;li&gt;Clean up the imported translations by removing unnecessary special characters.&lt;/li&gt;
&lt;li&gt;Restructure or rename the exported translation keys.&lt;/li&gt;
&lt;li&gt;Perform text analysis for your localization workflow needs.&lt;/li&gt;
&lt;li&gt;Apply special formatting to the imported or exported translations.&lt;/li&gt;
&lt;li&gt;Remove or replace banned or undesired words.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's see how to code a custom processor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Custom processor
&lt;/h2&gt;

&lt;p&gt;In this section, you'll learn how to create a script performing pre-processing and post-processing of your translation data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-processing happens when you are uploading translation files. We are going to use a third-party API called funtranslations.com to translate regular English texts into pirate speech.&lt;/li&gt;
&lt;li&gt;Post-processing is performed when you are downloading your translation files back. We'll create a script to remove a banned phrase from all our translations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preparing the app
&lt;/h3&gt;

&lt;p&gt;To get started, let's create a new Rails app as usual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails new CustomProcessors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will be using Rails 7 but all the code samples should be relevant for Rails 5 and 6 as well.&lt;/p&gt;

&lt;p&gt;Create a new controller in the &lt;code&gt;app/controllers/processors_controller.rb&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'uri'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'net/http'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'openssl'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProcessorsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;skip_before_action&lt;/span&gt; &lt;span class="ss"&gt;:verify_authenticity_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="sx"&gt;%i[preprocess postprocess]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we import all the necessary modules that will be used to perform the API request.&lt;/p&gt;

&lt;p&gt;Next, we also have to skip authenticity token verification for preprocessing and postprocessing because these actions will be called from another resource.&lt;/p&gt;

&lt;p&gt;Let's also add two routes in the &lt;code&gt;config/routes.rb&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s1"&gt;'/preprocess'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'processors#preprocess'&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s1"&gt;'/postprocess'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'processors#postprocess'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it!&lt;/p&gt;

&lt;h3&gt;
  
  
  Performing pre-processing
&lt;/h3&gt;

&lt;p&gt;Now let's discover how to perform data pre-processing. I would like to find all English translations and turn them into funny pirate speech using funtranslations.com. To get started, let's iterate over translation keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;preprocess&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

  &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:collection&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:keys&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we have to modify translations for every key, but only if the language ID is equal to 640 (which is English), and respond with a JSON containing the uploaded data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;preprocess&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

  &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:collection&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:keys&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:translations&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;trans&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;trans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:languageId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;
        &lt;span class="n"&gt;trans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:translation&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:translation&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="n"&gt;trans&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's also add the &lt;code&gt;translate&lt;/code&gt; private method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="kp"&gt;private&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://api.funtranslations.com/translate/pirate.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode_www_form&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;initial_text&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;

  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s1"&gt;'contents'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'translated'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's nothing complex really: we generate a proper URL, add our initial text to the query, send the request, and then get the translated content. Great job!&lt;/p&gt;

&lt;h3&gt;
  
  
  Performing post-processing
&lt;/h3&gt;

&lt;p&gt;Post-processing is performed in a very similar way. Let's create a new action which is going to remove a banned phrase from all translations. Specifically, pirates fear the Flying Dutchman so let's get rid of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;postprocess&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

  &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:collection&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:keys&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:translations&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;trans&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;trans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:translation&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;gsub!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/flying\sdutchman/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;trans&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we are simply using &lt;code&gt;gsub!&lt;/code&gt; to remove all occurrences of the given phrase. Once again, make sure to respond with a JSON containing all translations data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up a Custom processor
&lt;/h2&gt;

&lt;p&gt;Once you have created a custom processor, it's time to enable it! Therefore, perform the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proceed to &lt;a href="http://lokalise.com/signup"&gt;lokalise.com&lt;/a&gt;, log in to the system, and open your translation project.&lt;/li&gt;
&lt;li&gt;Proceed to &lt;strong&gt;Apps&lt;/strong&gt;, find &lt;strong&gt;Custom processor&lt;/strong&gt; in the list, and click on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IKBftOr5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g5y0g8bcibj3blt2hgsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IKBftOr5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g5y0g8bcibj3blt2hgsd.png" alt="Image description" width="822" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then click &lt;strong&gt;Install&lt;/strong&gt; and configure the app by entering a pre-process and a post-process URL.&lt;/li&gt;
&lt;li&gt;If needed, choose a file format to run this processor for.&lt;/li&gt;
&lt;li&gt;When you are ready, click &lt;strong&gt;Save changes&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pp8jK01f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/602f4mr8aem6pkcx5g2f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pp8jK01f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/602f4mr8aem6pkcx5g2f.jpg" alt="Image description" width="641" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it, great job!&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it out
&lt;/h2&gt;

&lt;p&gt;Now that everything is ready, you can test your new processor. To do that, proceed to the &lt;strong&gt;Upload&lt;/strong&gt; page, choose an English translation file, and click &lt;strong&gt;Upload&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cIIpVoGG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5g807mhyng1lp6s91kfr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cIIpVoGG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5g807mhyng1lp6s91kfr.jpg" alt="Image description" width="880" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Return to the project editor and observe the results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vszDSHHA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a8w85ktxvqev7nbxh2jt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vszDSHHA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a8w85ktxvqev7nbxh2jt.jpg" alt="Image description" width="880" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To test the post-processing feature, simply add the "Flying Dutchman" phrase to any translation, proceed to the Download page, and click Build and download. You should not see the banned phrase in the resulting translations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Say&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;th'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secret&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;phrase&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;enter.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;It's&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;over&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;there"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;welcome&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;me&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bucko!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And this concludes our tutorial. As you can see, the Custom processor is a very powerful feature that can be used to build custom workflows, so be sure to check it out. You can find even more code samples in our DevHub. However, if you have any additional questions, please don't hesitate to &lt;a href="https://forms.gle/u5CoQpbe3qdcTPL29"&gt;drop us a line&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for sticking with me today, and happy coding!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Lokalise custom processors: Node and Fastify</title>
      <dc:creator>Ilya Krukowski</dc:creator>
      <pubDate>Fri, 19 Aug 2022 09:50:00 +0000</pubDate>
      <link>https://forem.com/lokalise/lokalise-custom-processors-5dkl</link>
      <guid>https://forem.com/lokalise/lokalise-custom-processors-5dkl</guid>
      <description>&lt;p&gt;In this article, we will discuss a new Lokalise feature called &lt;strong&gt;Custom processor&lt;/strong&gt;. The Custom processor app enables you to intercept translation keys uploaded and downloaded on Lokalise, then analyze or transform those as needed.&lt;/p&gt;

&lt;p&gt;Here we’ll show you how to create a Custom processor using Node and Fastify, set it up on Lokalise, and test it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can find even more &lt;a href="https://developers.lokalise.com/docs/lokalise-custom-processors-sample-apps" rel="noopener noreferrer"&gt;code samples in our DevHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Custom processor?
&lt;/h2&gt;

&lt;p&gt;The Custom processor app enables you to utilize your own server or a third-party service to fetch translation keys imported to and exported from Lokalise, analyze, format, transform, or even remove those as needed.&lt;/p&gt;

&lt;p&gt;To better understand the idea, let's see how a regular upload is performed on Lokalise:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You choose one or more translation files to upload.&lt;/li&gt;
&lt;li&gt;Lokalise detects the translation file languages automatically, but you can adjust those as needed.&lt;/li&gt;
&lt;li&gt;You adjust any additional uploading options and click Upload.&lt;/li&gt;
&lt;li&gt;Lokalise parses the uploaded data, and extracts keys and translation values.&lt;/li&gt;
&lt;li&gt;Extracted data is displayed in the translation editor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, if you have a Custom processor set up, after performing step 4 Lokalise will automatically send the parsed data to your processor. There you can perform any additional actions, such as analyzing the uploaded data, transforming your translations, adding special formatting, removing unnecessary special characters or certain banned words, restructuring translation keys, and so on. The only requirement is that the data returned by your processor must preserve the initial structure.&lt;/p&gt;

&lt;p&gt;Now let's briefly cover the download process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You choose file formats to download.&lt;/li&gt;
&lt;li&gt;You adjust any additional options as needed and click Build and download.&lt;/li&gt;
&lt;li&gt;Lokalise collects the chosen keys and translations.&lt;/li&gt;
&lt;li&gt;Lokalise generates translation files based on the download options and zips those into an archive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, a Custom processor will intercept the data collected in step 3 and once again it can perform any additional modifications before these data are zipped into an archive.&lt;/p&gt;

&lt;p&gt;As you can see, the idea is quite simple, however with this feature you can automate repetitive manual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why would you need a Custom processor?
&lt;/h2&gt;

&lt;p&gt;There are various use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Translate the uploaded content using your own or  third-party service.&lt;/li&gt;
&lt;li&gt;Perform text analysis for your localization workflow needs.&lt;/li&gt;
&lt;li&gt;Apply special formatting to the imported or exported translations.&lt;/li&gt;
&lt;li&gt;Clean up the imported translations by removing unnecessary special characters.&lt;/li&gt;
&lt;li&gt;Remove or replace banned or undesired words.&lt;/li&gt;
&lt;li&gt;Perform placeholder replacement.&lt;/li&gt;
&lt;li&gt;Restructure or rename the exported translation keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, as you can see, there are numerous potential use cases and you can think of very complex scenarios! Therefore, let's see how to code a custom processor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Custom processor
&lt;/h2&gt;

&lt;p&gt;In this section, you'll learn how to create a script performing pre-processing and post-processing of your translation data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-processing happens when you are uploading translation files. We are going to use a third-party API called &lt;a href="https://funtranslations.com" rel="noopener noreferrer"&gt;funtranslations.com&lt;/a&gt; to translate regular English texts into pirate speech.&lt;/li&gt;
&lt;li&gt;Post-processing is performed when you are downloading your translation files back. We'll create a script to remove a banned phrase from all our translations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preparing the app
&lt;/h3&gt;

&lt;p&gt;So, let’s get started by creating a new project directory (for example, &lt;code&gt;custom_processor&lt;/code&gt;) with a &lt;code&gt;package.json&lt;/code&gt; file inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"custom-processors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"server.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node server.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fastify start -w -l info -P app.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.3.7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fastify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.29.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fastify-autoload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.13.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fastify-cli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fastify-plugin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.0.1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nodemon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.0.16"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create a &lt;code&gt;server.js&lt;/code&gt; file with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Fastify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Fastify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pluginTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, add an &lt;code&gt;app.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AutoLoad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify-autoload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AutoLoad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AutoLoad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice!&lt;/p&gt;

&lt;h3&gt;
  
  
  Performing pre-processing
&lt;/h3&gt;

&lt;p&gt;Now let’s see how to perform pre-processing. I’d like to find all English translations and turn them into pirate speech using funtranslations.com. To achieve that, create a new file called &lt;code&gt;routes/preprocess.js&lt;/code&gt;. Inside it we’ll iterate over translation keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify-http-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/preprocess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid request: Missing body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we have to check the language ID; it should be equal to &lt;code&gt;640&lt;/code&gt; which is English. If the language ID is the proper one, we’ll send an API request to translate our texts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify-http-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/preprocess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid request: Missing body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;languageId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;curl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.funtranslations.com/translate/pirate.json&amp;amp;text=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;translation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;translated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translated&lt;/span&gt;
          &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;translation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translation&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, this is it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performing post-processing
&lt;/h3&gt;

&lt;p&gt;Post-processing is performed in a very similar way. Let’s create a new route which is going to remove a banned phrase, “Flying Dutchman”, from all the translations (after all, pirates are usually afraid of this mystical ship). Populate the &lt;code&gt;routes/postprocess.js&lt;/code&gt; file with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/postprocess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid request: Missing body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;translation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="sr"&gt;/flying&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;dutchman/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are simply removing the banned phrase from all translations.&lt;/p&gt;

&lt;p&gt;Great job!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up a Custom processor
&lt;/h2&gt;

&lt;p&gt;Once you have created a Custom processor, it’s time to enable it! Therefore, perform the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proceed to &lt;a href="https://app.lokalise.com/signup" rel="noopener noreferrer"&gt;lokalise.com&lt;/a&gt;, log in to the system, and open your translation project.&lt;/li&gt;
&lt;li&gt;Proceed to &lt;strong&gt;Apps&lt;/strong&gt;, find &lt;strong&gt;Custom processor&lt;/strong&gt; in the list, and click on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falrknlkmirpvzl4vveyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falrknlkmirpvzl4vveyi.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then click &lt;strong&gt;Install&lt;/strong&gt; and configure the app by entering a preprocess and a postprocess URL.&lt;/li&gt;
&lt;li&gt;If needed, choose a file format to run this processor for.&lt;/li&gt;
&lt;li&gt;When you are ready, click &lt;strong&gt;Save changes&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fdedxlz9iz7ir5qf50n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fdedxlz9iz7ir5qf50n.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it, great job!&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it out
&lt;/h2&gt;

&lt;p&gt;Now that everything is ready, you can test your new processor. To do that, proceed to the &lt;strong&gt;Upload&lt;/strong&gt; page, choose an English translation file, and click &lt;strong&gt;Upload&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxm94rkvl57ob1nli7liw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxm94rkvl57ob1nli7liw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Return to the project editor and observe the results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwur1dgivitldfq1qpcz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwur1dgivitldfq1qpcz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To test the postprocessing feature, simply add the "Flying Dutchman" phrase to any translation, and proceed to the &lt;strong&gt;Download&lt;/strong&gt; page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tv6uw09rf72j7ust0a1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tv6uw09rf72j7ust0a1.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Build and download&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojp4hu9pmx4nw2hwghq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojp4hu9pmx4nw2hwghq0.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should not see the banned phrase in the resulting translations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Say th' secret phrase and enter. It's  over there"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome, me bucko!"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And this concludes our tutorial. As you can see, the Custom processor is a very powerful feature that can be used to build custom workflows, so be sure to check it out. You can find even more &lt;a href="//developers.lokalise.com/"&gt;code samples in our DevHub&lt;/a&gt;. However, if you have any additional questions, please don’t hesitate to &lt;a href="https://forms.gle/u5CoQpbe3qdcTPL29" rel="noopener noreferrer"&gt;drop us a line&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for sticking with me today, and happy coding!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Introducing Developer Hub: A new resource for developers</title>
      <dc:creator>Ilya Krukowski</dc:creator>
      <pubDate>Mon, 08 Aug 2022 10:19:00 +0000</pubDate>
      <link>https://forem.com/lokalise/introducing-developer-hub-a-new-resource-for-developers-3j1f</link>
      <guid>https://forem.com/lokalise/introducing-developer-hub-a-new-resource-for-developers-3j1f</guid>
      <description>&lt;p&gt;We’d like to tell you about a new resource we’ve created, the  &lt;strong&gt;Lokalise Developer Hub&lt;/strong&gt;. It’s a single place to host all relevant information for developers including guides, sample apps written in different languages, API explorer where you can test endpoints in real time, tips, docs, API and SDK changelogs. So, let’s see what it has to offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  REST API docs and explorer
&lt;/h2&gt;

&lt;p&gt;We have a &lt;a href="https://developers.lokalise.com/reference/lokalise-rest-api?utm_source=devto&amp;amp;utm_medium=web"&gt;dedicated section providing comprehensive documentation for all the API endpoints that Lokalise supports&lt;/a&gt;. Here you can also find information on authentication (with API tokens and OAuth 2), pagination, rate limiting and branching support.&lt;/p&gt;

&lt;p&gt;And last but not the least is the ability to test our API by sending real requests using the &lt;a href="https://developers.lokalise.com/reference/lokalise-rest-api?utm_source=devto&amp;amp;utm_medium=web"&gt;API explorer&lt;/a&gt;. To get started, you’ll only require an &lt;a href="https://docs.lokalise.com/en/articles/1929556-api-tokens?utm_source=devto&amp;amp;utm_medium=web"&gt;API token&lt;/a&gt; and a project ID (for certain endpoints you might require a team ID instead):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log into your Lokalise account or &lt;a href="https://app.lokalise.com/signup?utm_source=devto&amp;amp;utm_medium=web"&gt;create a new one&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click on the avatar in the bottom left corner, choose &lt;strong&gt;Profile settings&lt;/strong&gt;, proceed to the &lt;strong&gt;API tokens&lt;/strong&gt; tab and generate a new token. Make sure to never publicly expose it and treat it as your password.&lt;/li&gt;
&lt;li&gt;Next, open your translation project, click &lt;strong&gt;More &amp;gt; Settings&lt;/strong&gt;, and under the &lt;strong&gt;General&lt;/strong&gt; tab find your project ID.&lt;/li&gt;
&lt;li&gt;Next, return to the API explorer, choose one of the endpoints from the left menu and, paste your API token into the &lt;code&gt;X-Api-Token&lt;/code&gt; header text field, fill in the other request params as needed, and press &lt;strong&gt;Try It&lt;/strong&gt;. Then you’ll be able to observe the response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more about the API explorer, you can watch the &lt;a href="https://developers.lokalise.com/reference/lokalise-rest-api?utm_source=devto&amp;amp;utm_medium=web"&gt;short video tutorial found on the “getting started” page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guides and tutorials
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lokalise apps and engines
&lt;/h3&gt;

&lt;p&gt;Apart from the API docs, we also provide additional guides and articles related to apps and engines. So, what's an app, you ask? In terms of Lokalise, we consider an app any piece of code that expands Lokalise's functionality. An app can come in handy when you want to automate your localization process and workflows, customize and extend Lokalise core capabilities, or connect Lokalise with other systems. For example, we already have apps to send notifications via Slack or Discord and to automatically create tasks on Asana or Jira. There are various types of apps available, and &lt;a href="https://developers.lokalise.com/docs/lokalise-apps?utm_source=devto&amp;amp;utm_medium=web"&gt;you can read about those in the corresponding Developer Hub article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An engine, in turn, facilitates the content exchange between Lokalise and ecommerce, PIM systems, or other content management systems, for example, Contentful, Storyblok, or Wordpress.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://developers.lokalise.com/docs/custom-processor?utm_source=devto&amp;amp;utm_medium=web"&gt;custom processor&lt;/a&gt; is one example of a Lokalise engine that we’ve introduced recently. To put it simply, a custom processor is a script that runs on a third-party service and manages translations that are uploaded and downloaded on Lokalise.&lt;/p&gt;

&lt;p&gt;To better understand the idea, let's see how a regular upload is performed on Lokalise:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You choose one or more translation files to upload.&lt;/li&gt;
&lt;li&gt;Lokalise detects the translation file languages automatically, but you can adjust those as needed.&lt;/li&gt;
&lt;li&gt;You adjust any additional uploading options and click Upload.&lt;/li&gt;
&lt;li&gt;Lokalise parses the uploaded data, and extracts keys and translation values.&lt;/li&gt;
&lt;li&gt;Extracted data is displayed in the translation editor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, if you have a custom processor set up, after performing step 4 Lokalise will automatically send the parsed data to your service. There you can perform any additional actions, such as analyzing the uploaded data, transforming your translations, adding special formatting, removing unneeded special characters or certain banned words, restructuring translation keys, and so on. The only requirement is that the data returned by your processor must preserve the initial structure. A custom processor can also intercept the downloaded data in the same way.&lt;/p&gt;

&lt;p&gt;This is very convenient, for example, when you would like to perform complex text analysis, remove banned words from your translations, add custom formatting, and so on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample apps
&lt;/h3&gt;

&lt;p&gt;Of course you might want to build a custom app to work with Lokalise, and to help you get started we’ve prepared a collection of tutorials with app samples. Specifically, you can learn &lt;a href="https://developers.lokalise.com/docs/lokalise-api-hello-world-examples?utm_source=devto&amp;amp;utm_medium=web"&gt;how to work with the Lokalise API, implement an OAuth 2 flow&lt;/a&gt; and act on the user's behalf, &lt;a href="https://developers.lokalise.com/docs/webhooks-guide?utm_source=devto&amp;amp;utm_medium=web"&gt;utilize webhooks to react to certain events happening in Lokalise&lt;/a&gt;. On top of that, we provide &lt;a href="https://developers.lokalise.com/docs/lokalise-custom-processors-sample-apps?utm_source=devto&amp;amp;utm_medium=web"&gt;boilerplate examples for custom processors&lt;/a&gt; showing how exactly you can process received data.&lt;/p&gt;

&lt;p&gt;Currently we provide code samples in the PHP, JS, Python, and Ruby languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  App publishing process
&lt;/h3&gt;

&lt;p&gt;After you’ve built an app, you might want to feature it on Lokalise so that other customers can take advantage of it (however, it’s not mandatory). You can &lt;a href="https://developers.lokalise.com/docs/guidelines-tips?utm_source=devto&amp;amp;utm_medium=web"&gt;refer to the app publishing section&lt;/a&gt; to learn about requirements regarding the apps, and what the overall publishing process looks like. Also, you can find an app review checklist and some useful tips on building successful apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  I18n tutorials
&lt;/h3&gt;

&lt;p&gt;If you are not looking to build a custom Lokalise app but rather want to know how to implement internationalization (i18n) in your project, &lt;a href="https://developers.lokalise.com/docs/general-information?utm_source=devto&amp;amp;utm_medium=web"&gt;this section is for you&lt;/a&gt;. We have a vast collection of I18n tutorials explaining how to build multilingual apps with different technologies. Our articles cover dozens of technologies including React, Angular, Django, Rails, Laravel, and many, many others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Changelogs
&lt;/h3&gt;

&lt;p&gt;Finally, changelogs for all our &lt;a href="https://developers.lokalise.com/docs/sdk-changelog?utm_source=devto&amp;amp;utm_medium=web"&gt;official API SDKs&lt;/a&gt; and the &lt;a href="https://developers.lokalise.com/docs/api-changelog?utm_source=devto&amp;amp;utm_medium=web"&gt;API itself&lt;/a&gt; can be found on the DevHub. You’ll never miss an update!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So, in this article we’ve briefly explored the Lokalise DevHub and checked out what it has to offer. We sincerely hope this new resource will help developers build something truly wonderful.&lt;/p&gt;

&lt;p&gt;If you have any feedback for us, please don’t hesitate to &lt;a href="https://forms.gle/u5CoQpbe3qdcTPL29"&gt;fill out this form&lt;/a&gt; – we would love to hear from you.&lt;/p&gt;

&lt;p&gt;That’s all for now. Stay tuned and happy coding!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Start building your own integrations with Lokalise Apps 🚀 </title>
      <dc:creator>Lokalise Dev</dc:creator>
      <pubDate>Thu, 18 Nov 2021 11:49:12 +0000</pubDate>
      <link>https://forem.com/lokalise/register-your-interest-today-for-lokalise-apps-482c</link>
      <guid>https://forem.com/lokalise/register-your-interest-today-for-lokalise-apps-482c</guid>
      <description>&lt;p&gt;📢 We’re excited to announce that Lokalise Apps is officially live!&lt;/p&gt;

&lt;p&gt;Our vision is to enable developers to build their own integrations with Lokalise and share them with our thousands of users, enhancing the visibility of creators and attracting opportunities to expand their businesses. 💫&lt;/p&gt;

&lt;p&gt;A key part of that is ensuring adoption among Lokalise users as we will promote apps built by our partners. 🤝&lt;/p&gt;

&lt;p&gt;Interested in building your first app with Lokalise and boosting your workflows? Sign up here: &lt;a href="https://bit.ly/3HvRkvb"&gt;https://bit.ly/3HvRkvb&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Localizing JavaScript apps with jQuery.i18n</title>
      <dc:creator>Lokalise Dev</dc:creator>
      <pubDate>Fri, 10 May 2019 12:25:42 +0000</pubDate>
      <link>https://forem.com/lokalise/localizing-javascript-apps-with-jquery-i18n-51ne</link>
      <guid>https://forem.com/lokalise/localizing-javascript-apps-with-jquery-i18n-51ne</guid>
      <description>&lt;p&gt;Recently we have done an &lt;a href="https://dev.to/lokalise/libraries-for-translating-javascript-apps-4mm-temp-slug-6576899"&gt;overview of the most popular internationalization libraries for JavaScript&lt;/a&gt; covering solutions like I18next, Globalize and others. In this article, however, we are going to focus on a single solution and talk about localizing JavaScript apps with &lt;a href="https://github.com/wikimedia/jquery.i18n"&gt;jQuery.I18n&lt;/a&gt;. This is a feature-rich library developed and supported by Wikimedia team. It is easy to get started and which is suitable for both simple and complex websites.&lt;/p&gt;

&lt;p&gt;We are going to cover the following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overview of the library&lt;/li&gt;
&lt;li&gt;Downloading and loading all the necessary modules&lt;/li&gt;
&lt;li&gt;Providing translations in JSON files&lt;/li&gt;
&lt;li&gt;Documenting your translations&lt;/li&gt;
&lt;li&gt;Performing translations and using placeholders&lt;/li&gt;
&lt;li&gt;Utilizing gender information and employing pluralization&lt;/li&gt;
&lt;li&gt;Using “magic words”&lt;/li&gt;
&lt;li&gt;Switching locale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that if you wish to follow this tutorial, you’ll require a web server like &lt;a href="https://www.mamp.info/en/"&gt;MAMP&lt;/a&gt;, &lt;a href="http://wamp.io/"&gt;WAMP&lt;/a&gt;, or &lt;a href="https://www.iis.net/"&gt;IIS&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;So, jQuery.I18n is an internationalization library developed by &lt;a href="https://www.mediawiki.org/wiki/Wikimedia_Language_engineering"&gt;Wikimedia language engineering team&lt;/a&gt;. Wikimedia, in turn, is a company behind &lt;a href="https://en.wikipedia.org/wiki/Main_Page"&gt;Wikipedia&lt;/a&gt;, a popular free online encyclopedia. What’s important, Wikimedia team utilizes jQuery.I18n internally for their resources which are accessed by people from all over the world. These projects, of course, require proper localization. It means that the library is being quite actively maintained and well-documented which is very important for us, developers.&lt;/p&gt;

&lt;p&gt;Here are the &lt;a href="https://github.com/wikimedia/jquery.i18n#features"&gt;main features of jQuery.I18n&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/wikimedia/jquery.i18n#plurals"&gt;Support for pluralization&lt;/a&gt; with the help of &lt;a href="http://cldr.unicode.org/"&gt;Unicode CLDR&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Support for &lt;a href="https://github.com/wikimedia/jquery.i18n#grammar"&gt;grammar forms&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ability to specify &lt;a href="https://github.com/wikimedia/jquery.i18n#gender"&gt;gender information&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ability to provide meta information inside translation files and &lt;a href="https://github.com/wikimedia/jquery.i18n#message-documentation"&gt;document your messages&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wikimedia/jquery.i18n#fallback"&gt;Fallback chains&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Customizable message parser that support so-called &lt;a href="https://github.com/wikimedia/jquery.i18n#magic-word-support"&gt;“magic words”&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Support for&lt;a href="https://github.com/wikimedia/jquery.i18n#data-api"&gt;HTML5 &lt;code&gt;data-*&lt;/code&gt; attributes&lt;/a&gt; that allow to provide translations right inside your markup&lt;/li&gt;
&lt;li&gt;Modular code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the other hand, jQuery.i18n, does have some downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As the name implies, it relies on the &lt;a href="https://jquery.com/"&gt;jQuery library&lt;/a&gt;. 8-10 years ago jQuery used to be a de-facto standard for web, and nearly everyone was using it. Now, however, things have changed and many devs tend to get rid of jQuery preferring pure JavaScript. Still, many sites rely on jQuery, therefore reliance of this library is probably not a big downside&lt;/li&gt;
&lt;li&gt;Translations are not updated dynamically once the current locale changes. This may or may not be a big problem for you, but sill, this is quite an annoying thing. Some other popular solutions (like &lt;a href="https://www.i18next.com/"&gt;I18next&lt;/a&gt;) can update your texts nearly instantly as soon as the user has switched the current language&lt;/li&gt;
&lt;li&gt;jQuery.I18n team is not actively participating in &lt;a href="https://github.com/wikimedia/jquery.i18n/issues"&gt;discussions on GitHub&lt;/a&gt;. There are a handful of open issues, and some of them are quite old. Still, jQuery.I18n does not have any serious bugs, and it is production-ready&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a Demo App
&lt;/h2&gt;

&lt;p&gt;For the purposes of this demo, I’ll create a single &lt;code&gt;index.html&lt;/code&gt; file inside the root folder of my local web server. Next, we have to download the latest version of jQuery.i18n, therefore &lt;a href="https://github.com/wikimedia/jquery.i18n#quick-start"&gt;run the following commands&lt;/a&gt; (you will need &lt;a href="https://git-scm.com/"&gt;Git&lt;/a&gt; installed on your PC):&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;These commands will clone the library’s code into the &lt;code&gt;jquery.i18n&lt;/code&gt; folder and also initialize a sub-module with CLDR rule parser. Copy the contents of the &lt;code&gt;jquery.i18n/src&lt;/code&gt; to the &lt;code&gt;js&lt;/code&gt; folder of your demo project. &lt;code&gt;src&lt;/code&gt; folder contains the library’s modules and you can choose which ones to load in your app. There’s also a nested &lt;code&gt;languages&lt;/code&gt; folder with basic support for some common locales.&lt;/p&gt;

&lt;p&gt;Also, navigate to &lt;em&gt;jquery.i18n\libs\CLDRPluralRuleParser\src&lt;/em&gt; and copy &lt;code&gt;CLDRPluralRuleParser.js&lt;/code&gt; into the &lt;code&gt;js&lt;/code&gt; folder. This file will enable support for pluralization.&lt;/p&gt;

&lt;p&gt;At this point we may define a skeleton for our HTML page inside the &lt;code&gt;index.html&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;First of all I’m loading jQuery 3 which our I18n library relies on, then CLDR rule parser, all the modules, and &lt;code&gt;ru.js&lt;/code&gt; file that enables support for the Russian language. Of course, you may further tweak this code and pick only the necessary modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translation Files
&lt;/h2&gt;

&lt;p&gt;Translations for jQuery.i18n are &lt;a href="https://github.com/wikimedia/jquery.i18n#message-file-format"&gt;stored inside simple JSON files&lt;/a&gt; or &lt;a href="https://github.com/wikimedia/jquery.i18n#dynamic-loading-using-load-method"&gt;can be loaded directly inside your code&lt;/a&gt;. I would recommend sticking to the first option (especially for larger sites), therefore create an &lt;code&gt;i18n&lt;/code&gt; folder inside your project.&lt;/p&gt;

&lt;p&gt;It possible to store translations for all languages in a single file or separate them into different files. If you prefer to utilize a single file, next your translation keys under the locale codes:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;In this example we have translations for English and Russian locale stored under the &lt;code&gt;welcome&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;Interestingly, it is possible to provide path to the language file instead of listing translations:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;As your website grows you will have more and more translation keys resulting in a very long JSON file. Therefore, let’s divide translations into separate files. I’ll create the following directory structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;i18n&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ru.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;en.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the contents for the &lt;code&gt;en.json&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Note that I am also providing metadata under the &lt;code&gt;@metadata&lt;/code&gt; key. Here you may list the authors of this translation, specify locale, and provide message documentation (which we’ll discuss in a moment).&lt;/p&gt;

&lt;p&gt;Here is the contents for the &lt;code&gt;ru.json&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Now we can load our translation files!&lt;/p&gt;

&lt;h3&gt;
  
  
  Message Documentation
&lt;/h3&gt;

&lt;p&gt;In the previous section we have specified a &lt;code&gt;message-documentation&lt;/code&gt; meta key with a bizarre-looking &lt;code&gt;qqq&lt;/code&gt; value. Basically, &lt;code&gt;qqq&lt;/code&gt; is a special locale which contains description for every translation key of your application. These descriptions may include information on where exactly this key is being used, what is the translation context (this info is especially important for translators), what tone the translator should use (formal, informal, friendly) etc. For example, &lt;a href="https://github.com/wikimedia/mediawiki/blob/master/languages/i18n/qqq.json"&gt;here is a &lt;code&gt;qqq.json&lt;/code&gt; file&lt;/a&gt; used in a real Wikimedia project.&lt;/p&gt;

&lt;p&gt;So, now let’s create our very own message documentation inside &lt;code&gt;i18n/qqq.json&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Now our future translators will have all the necessary info on the &lt;code&gt;welcome&lt;/code&gt; key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading Translations
&lt;/h2&gt;

&lt;p&gt;As long as our translation files are ready, we may &lt;a href="https://github.com/wikimedia/jquery.i18n#message-loading"&gt;load them&lt;/a&gt; inside the demo app. Create a new file &lt;code&gt;js/global.js&lt;/code&gt; with the following contents:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$.i18n().load()&lt;/code&gt; is a function that accepts either paths to your translation files or JSON with translation data. It means, that you may also load translations in the following way:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Now include &lt;code&gt;global.js&lt;/code&gt; file on the main page of the site:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;h2&gt;
  
  
  Performing Translations
&lt;/h2&gt;

&lt;p&gt;In order to fetch translation by its key, you may utilize &lt;code&gt;$.i18n()&lt;/code&gt; function. Remember, however, that we are loading our translation files asynchronously, and therefore have to wait until they are ready. Luckily, the &lt;code&gt;load()&lt;/code&gt; function returns a promise, so we may chain &lt;code&gt;done()&lt;/code&gt; function like this:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;&lt;code&gt;welcome&lt;/code&gt; here is a translation key.&lt;/p&gt;

&lt;p&gt;Now add a &lt;code&gt;#welcome&lt;/code&gt; element to the &lt;code&gt;index.html&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Now you may navigate to the main page of the site and make sure that the “Welcome!” phrase is being displayed in a &lt;code&gt;h1&lt;/code&gt; tag. It means that you have configured jQuery.i18n properly!&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Interpolation
&lt;/h3&gt;

&lt;p&gt;In some cases you may want to dynamically provide data for your translations. For example, our welcoming message may greet a currently logged in user by his or her name. Of course, we won’t code authentication system in this article, and rather store user’s information in a plain object:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Now pass the current user’s name to the &lt;code&gt;$.i18n()&lt;/code&gt; function as the second argument:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Now all you need to do is fetch this data inside your translations. Tweak &lt;code&gt;i18n/en.json&lt;/code&gt; file (I’m skipping metadata for brevity):&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$1&lt;/code&gt; is a placeholder that will be dynamically replaced with the second argument passed to the &lt;code&gt;$.i18n()&lt;/code&gt; function (which is “Alex” in our case).&lt;/p&gt;

&lt;p&gt;Now, &lt;code&gt;ru.json&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;You may define as many placeholders as needed in your translations, for example &lt;code&gt;$1 says "$2" to $3&lt;/code&gt; which will result in something like “Alex says “Hi” to Ann”. Note, however, that if a placeholder doesn’t have any value, it will be displayed as-is: &lt;code&gt;Welcome, $1!&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gender Information
&lt;/h3&gt;

&lt;p&gt;One very common task when performing internationalization is displaying slightly different messages based on the given gender. Suppose we would like to show some consulting information on the page, and the consultant may be either male or female. Based on the consultant’s gender we would like to display a message like “Your today’s consultant is SOME NAME. He (She) says: …”. How do we achieve that?&lt;/p&gt;

&lt;p&gt;First of all, add a new &lt;code&gt;consulting_info&lt;/code&gt; translation key to the &lt;code&gt;en.json&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wikimedia/jquery.i18n#gender"&gt;&lt;code&gt;GENDER&lt;/code&gt; is a special switch&lt;/a&gt; that displays one of the given options (“He” or “She”) based on the given argument represented as a placeholder.&lt;/p&gt;

&lt;p&gt;Now let’s add a Russian translation:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Now simply define a new &lt;code&gt;consultant&lt;/code&gt; object and pass interpolation data to the &lt;code&gt;$.i18n()&lt;/code&gt; function as we already did in the previous section:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Lastly add a new tag to the &lt;code&gt;index.html&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Reload the page and observe the result!&lt;/p&gt;

&lt;h3&gt;
  
  
  Pluralization
&lt;/h3&gt;

&lt;p&gt;Another common task is displaying pluralized messages based on the given count. For instance, we may want to say how many unread messages the current user has. To achieve that, &lt;a href="https://github.com/wikimedia/jquery.i18n#plurals"&gt;we will use a &lt;code&gt;PLURAL&lt;/code&gt; switch&lt;/a&gt;. Tweak &lt;code&gt;en.json&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;As you see, it is very similar to what we did with the &lt;code&gt;GENDER&lt;/code&gt; switch a minute ago. For Russian language, however, more options has to be provided:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Various languages have different pluralization rules but luckily you don’t need to bother about it thanks to &lt;a href="http://cldr.unicode.org/"&gt;Unicode CLDR&lt;/a&gt; pluralization info that jQuery.I18n relies on. All you need to do is &lt;a href="https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html"&gt;open the following page&lt;/a&gt; and use the given table to determine how many options should be provided for the language you wish to support.&lt;/p&gt;

&lt;p&gt;Now tweak your JS file again:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;And add yet another tag to the HTML page:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Reload the page once again and make sure the text has proper pluralization!&lt;/p&gt;

&lt;h3&gt;
  
  
  It’s Pure Magic!
&lt;/h3&gt;

&lt;p&gt;We have discussed two switches: &lt;code&gt;GENDER&lt;/code&gt; and &lt;code&gt;PLURAL&lt;/code&gt; but it appears they are actually &lt;a href="https://github.com/wikimedia/jquery.i18n#magic-word-support"&gt;so-called “magic words”&lt;/a&gt; that jQuery.I18n supports. A “magic word” is just a special construct that the parser processes and replaces with some content. You may further extend the parser and provide your own constructs as needed. For instance, let’s construct a magic word to display &lt;a href="https://www.w3schools.com/TAGS/tag_abbr.asp"&gt;abbreviations&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;&lt;code&gt;abbr&lt;/code&gt; is the name of our “magic word”. The anonymous function may optionally accept an array of &lt;code&gt;nodes&lt;/code&gt; which are simply arguments passed to the magic word. This function then returns the content that you wish to display.&lt;/p&gt;

&lt;p&gt;Use this magic word in the following way:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Note that magic words may be nested as &lt;a href="https://github.com/wikimedia/jquery.i18n#extending-the-parser"&gt;shown in this example&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Translating With HTML5 Data Attributes
&lt;/h3&gt;

&lt;p&gt;Another interesting feature that jQuery.I18n provides is the &lt;a href="https://github.com/wikimedia/jquery.i18n#data-api"&gt;support for HTML5 &lt;code&gt;data-*&lt;/code&gt; attributes&lt;/a&gt; that may be used to provide translations right inside your markup. To see it in action, add yet another tag into your markup with &lt;code&gt;data-i18n&lt;/code&gt; attribute:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;&lt;code&gt;glad_to_see&lt;/code&gt; is just a translation key. &lt;code&gt;We're glad to see you!&lt;/code&gt; basically acts as a fallback text that will be shown if translation for the specified key cannot be found.&lt;/p&gt;

&lt;p&gt;Next add English translation for this key:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Also add Russian translation:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;And lastly tweak your JS code:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;This will search for a translation under the key provided in the &lt;code&gt;data-i18n&lt;/code&gt; attribute. If the translation is found, it will be placed inside the &lt;code&gt;h2&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;What’s more, the &lt;code&gt;i18n()&lt;/code&gt; function may be applied to the whole HTML page:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;Now every tag containing the &lt;code&gt;data-i18n&lt;/code&gt; attribute will be properly translated!&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching Language
&lt;/h2&gt;

&lt;p&gt;Our next task is providing functionality to switch the currently set language. Let’s provide a language switcher component on our HTML page:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;When a user clicks on one of these links, we should change the language accordingly. Bind a click event and provide an event handler inside the &lt;code&gt;global.js&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;I’ve added this event handler inside the &lt;code&gt;done()&lt;/code&gt; function because, once again, we should wait until the translation files are loaded.&lt;/p&gt;

&lt;p&gt;There is one problem however: translation messages won’t update automatically once the user has changed locale. To fix that, I’ll extract all translation-related logic to a separate &lt;code&gt;do_translate()&lt;/code&gt; function, and call it once the &lt;code&gt;click&lt;/code&gt; event fires. Here is the complete version of the code:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;&lt;code&gt;do_translate()&lt;/code&gt; fires once translation files are loaded, as well as after the click event happens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Default Locale
&lt;/h3&gt;

&lt;p&gt;In order to provide a default locale, you may use two possible options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide it using the &lt;code&gt;lang&lt;/code&gt; attribute of the &lt;code&gt;html&lt;/code&gt; tag&lt;/li&gt;
&lt;li&gt;Set it using the &lt;code&gt;$.i18n()&lt;/code&gt; function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me demonstrate you these two approaches. &lt;code&gt;lang&lt;/code&gt; attribute is defined in the following way:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;This way we are saying that the language of this page is English. jQuery.I18n will read it and assign the default locale properly.&lt;/p&gt;

&lt;p&gt;To set a default locale programmatically, use the following code:&lt;/p&gt;

&lt;p&gt;.gist table { margin-bottom: 0; }&lt;/p&gt;

&lt;p&gt;These two options are equal, and you are free to choose any of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Your Life Easier With Lokalise
&lt;/h2&gt;

&lt;p&gt;Supporting multiple languages on a big website may become a serious pain. You must make sure that all the keys have translations for each and every locale. Luckily, there is a solution to this problem: the Lokalise platform that &lt;a href="https://lokalise.co/features"&gt;makes working with the localization files much simpler&lt;/a&gt;. Let me guide you through the initial setup which is nothing complex really.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To get started, &lt;a href="https://lokalise.co/signup"&gt;grab your free trial&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new project, give it some name, and set English as a base language&lt;/li&gt;
&lt;li&gt;Click “Upload Language Files”&lt;/li&gt;
&lt;li&gt;Upload translation files for all your languages&lt;/li&gt;
&lt;li&gt;Proceed to the project, and edit your translations as needed&lt;/li&gt;
&lt;li&gt;You may also contact a professional translator to do the job for you&lt;/li&gt;
&lt;li&gt;Next simply download your files back&lt;/li&gt;
&lt;li&gt;Profit!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you wish to upload QQQ files with message documentation, that’s possible as well. Perform the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your project and press the “Add Language” button on the top&lt;/li&gt;
&lt;li&gt;Choose any language from the dropdown&lt;/li&gt;
&lt;li&gt;Press on the new language’s flag and select “Language Settings”&lt;/li&gt;
&lt;li&gt;Set “Custom Language Code” to “On” and enter &lt;code&gt;qqq&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set “Custom Language Name” to “On” and enter something like “Message documentation”&lt;/li&gt;
&lt;li&gt;Save the changes and proceed to the “Upload” page&lt;/li&gt;
&lt;li&gt;Choose your &lt;code&gt;.qqq&lt;/code&gt; file. Your custom locale should be detected automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--teax6ypy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.lokalise.co/wp-content/uploads/2019/05/2019-05-09-13_55_55-JS-Demo-_-Lokalise.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--teax6ypy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.lokalise.co/wp-content/uploads/2019/05/2019-05-09-13_55_55-JS-Demo-_-Lokalise.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lokalise has many more features including support for dozens of platforms and formats, and even the possibility to upload screenshots in order to read texts from them. So, stick with Lokalise and make your life easier!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article we have discussed jQuery.I18n: an internationalization library for JavaScript applications created by Wikimedia. We have seen how to set it up, define and load language files, perform translations, use magic words, extend parser, and switch locale. Basically, we have covered all the main information, and you may start using this solution in real-world projects.&lt;/p&gt;

&lt;p&gt;Don’t forget to check &lt;a href="https://dev.to/lokalise/libraries-for-translating-javascript-apps-4mm-temp-slug-6576899"&gt;our article on other I18n libraries for JavaScript&lt;/a&gt; to learn about potential alternatives. Also, don’t hesitate to contact us if you have further questions, and stay with Lokalise!&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://blog.lokalise.co/localizing-apps-jquery/"&gt;Localizing JavaScript apps with jQuery.i18n&lt;/a&gt; appeared first on &lt;a href="https://blog.lokalise.co"&gt;Lokalise Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>localization</category>
      <category>i18n</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why app localization matters</title>
      <dc:creator>Lokalise Dev</dc:creator>
      <pubDate>Mon, 08 Apr 2019 14:23:52 +0000</pubDate>
      <link>https://forem.com/lokalise/why-localization-matters-for-your-app-4k3m</link>
      <guid>https://forem.com/lokalise/why-localization-matters-for-your-app-4k3m</guid>
      <description>&lt;p&gt;So, you think your business is ready for international growth. The product looks great, the website is up and running. Beautiful visuals and witty copy in English that will grab everyone’s attention no matter which corner of the world they reside in.&lt;/p&gt;

&lt;p&gt;And what else would you need, since English is the &lt;em&gt;lingua franca&lt;/em&gt; of the business world, spoken by 1.5 billion, or &lt;a href="https://www.babbel.com/en/magazine/how-many-people-speak-english-and-where-is-it-spoken/"&gt;20% of the Earth’s population&lt;/a&gt;, right?&lt;/p&gt;

&lt;p&gt;Think again.&lt;/p&gt;

&lt;p&gt;According to a 2014 report, &lt;a href="http://blog.languageline.com/what-is-localization"&gt;75% of consumers&lt;/a&gt; have said they’re likely to purchase goods and services if the product information is &lt;strong&gt;in their native language.&lt;/strong&gt; What’s more, &lt;a href="http://www.europeanbusinessreview.com/why-localisation-is-key-to-global-marketing/"&gt;87% of consumers&lt;/a&gt; who don’t speak and can’t read English won’t even consider buying from an English website.&lt;/p&gt;

&lt;p&gt;Look at it this way. If you’re traveling to a foreign country, you’re more likely to blend in and build a lasting relationship with the locals if you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;speak their language&lt;/li&gt;
&lt;li&gt;know their culture and customs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s basically what localization does for your product. But before we look into how localization could benefit your business, let’s start with the basics:&lt;/p&gt;

&lt;h2&gt;
  
  
  What is localization
&lt;/h2&gt;

&lt;p&gt;The term “&lt;a href="https://lokalise.co/"&gt;localization&lt;/a&gt;” stands for adapting a product or content to fit a specific market or country and adjusting its functional properties to accommodate the linguistic, cultural, political and legal differences.&lt;/p&gt;

&lt;p&gt;Localization is often confused with translation, the process of converting text from one language to another. While these terms might be somewhat similar, translation is actually just one aspect of localization with the latter being the more extensive of the two processes.&lt;/p&gt;

&lt;p&gt;So, in addition to translation, localization involves other elements that will be modified as a part of the process. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adapting visuals and graphics to the target market&lt;/li&gt;
&lt;li&gt;Modifying content to fit the tastes and consumption habits of the target market&lt;/li&gt;
&lt;li&gt;Changing the design and layout to properly display translated text&lt;/li&gt;
&lt;li&gt;Converting to local currencies and measure units&lt;/li&gt;
&lt;li&gt;Using proper local formats (dates, punctuation, symbols, phone numbers, addresses)&lt;/li&gt;
&lt;li&gt;Addressing local regulations and legal requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of the localization process, your product should look and feel as if it was created in the market or country you’re targeting.&lt;/p&gt;

&lt;p&gt;Besides, it’s not just the product or website that you can localize. If you’re really planning on expanding your audience, you should also localize &lt;strong&gt;marketing materials&lt;/strong&gt; (TV, radio, printed ads), product manuals, user interfaces, and product warranty materials.&lt;/p&gt;

&lt;p&gt;Still not sure if you should invest in localization? Here’s why it matters for your business:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Go global more rapidly&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It’s tempting to think that entering a new market will be as easy as launching a Facebook ad targeted to a specific location or country.&lt;/p&gt;

&lt;p&gt;However, it takes more than that. There are several &lt;strong&gt;barriers&lt;/strong&gt; that you will need to overcome, especially if you want to enter a market that’s geographically and culturally distant from your home country. Some of these barriers include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The political environment&lt;/li&gt;
&lt;li&gt;The socio-cultural/demographic environment&lt;/li&gt;
&lt;li&gt;The technological environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Through the process of localization, you’ll &lt;strong&gt;overcome these barriers&lt;/strong&gt; more rapidly, tailor your product or service to fit a specific culture, and allow your future customers to adapt to your product with less effort.&lt;/p&gt;

&lt;p&gt;In other words, localization will enable you to succeed in new markets and help your product or service &lt;strong&gt;adapt to your target location&lt;/strong&gt;. As a result, the customers will be more likely to spread the word about your brand and you will create a positive brand image. This in return will ensure better customer engagement with your products as opposed to your competitors who are not taking the localization path.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Boost sales&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Imagine you’re road tripping in a foreign country and you’re suddenly not feeling well, forcing you to search for a doctor. Which professional would you rather see – the one with lower prices but whom you’re having trouble communicating with? Or the one who’s a bit more pricey but, as it turns out, speaks your native language?&lt;/p&gt;

&lt;p&gt;It’s probably the second option as it’d be easier to explain your health issues. And in the end, you’d be healthy as a horse, and the doctor would earn a few more bucks than the first one.&lt;/p&gt;

&lt;p&gt;Research shows that it works the same way with localization. &lt;a href="http://vnlocalize.com/4-reasons-localization-important/"&gt;57% of consumers&lt;/a&gt; have said that the ability to obtain information in their own language is more important than price.&lt;/p&gt;

&lt;p&gt;When you communicate with your customers in their native language, you’re bonding with them and building trust. In the end, you’d have a mutual understanding which would make the customers &lt;strong&gt;more comfortable when placing purchases&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s some more cold evidence – &lt;a href="https://www.gala-global.org/industry/intro-language-industry/why-localize"&gt;74% of multinational enterprises&lt;/a&gt; believe that localization is either important or most important if you want to &lt;strong&gt;increase revenue from global operations&lt;/strong&gt;. So, if you want to boost those export sales and set a pace for your competitors, localization is the way to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Increase brand relevance&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In today’s world where trends change at unimaginable speeds, keeping your brand and product constantly relevant is a process that requires your ongoing attention.&lt;/p&gt;

&lt;p&gt;A relevant brand represents more than just a product or service – it creates and brings to the market products that &lt;strong&gt;meet important needs in people’s lives&lt;/strong&gt;. By doing so, these brands become an indispensable part of the consumer’s habits.  &lt;/p&gt;

&lt;p&gt;Just take a look at companies that perform well, like Apple. There have been many competitors that have tried to offer faster, different versions of iPads and iPhones. Nevertheless, the customers still prefer the forbidden fruit. It simply manages to stay one of &lt;a href="https://www.macobserver.com/tmo/article/why-apple-is-relevant-even-if-you-dont-like-the-company"&gt;most relevant products on the market&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Through the process of localization, your brand will &lt;strong&gt;become more relevant to more people in more markets&lt;/strong&gt;. To &lt;a href="https://www.prophet.com/2018/06/localization-is-the-key-to-unlocking-relevance-and-driving-growth/"&gt;quote Li Run&lt;/a&gt;, Senior brand director at TCL, the Chinese TV brand that constantly manages to stay at the very top of &lt;a href="https://www.amazon.com/Best-Sellers-Electronics-Televisions/zgbs/electronics/172659"&gt;Amazon’s best-selling charts&lt;/a&gt;, “only by understanding consumer needs and providing products and services that meet their local needs and values, can a brand achieve the deepest level of connection – getting beyond acceptance and towards being loved by local consumers.”&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Reduce risk&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Along with the many rewards that can be gained by entering a new market, there comes a fair share of potential risks, too. Aside from aspects out of your control – like shifts in currency value and &lt;em&gt;force majeure&lt;/em&gt;, you can undervalue the political climate, misunderstand consumer needs and cultural sensitivities.&lt;/p&gt;

&lt;p&gt;Localization acts as &lt;strong&gt;a safety measure&lt;/strong&gt; that mitigates these risks and ensures your business runs smoothly oversees. Remember that in some countries, certain expressions and even colors can bear a negative connotation. Through the process of localization, you can avoid falling into these traps that even some of the big giants have not managed to avert.&lt;/p&gt;

&lt;p&gt;For example, Nokia launched its Lumia phone in Spanish-speaking countries but failed miserably. They had missed the fact that the product name is slang for “prostitute” in Spanish, causing them to receive &lt;a href="https://www.cnet.com/news/nokias-lumia-means-um-prostitute-in-spanish/"&gt;negative publicity&lt;/a&gt;.  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The power of localization&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;No matter how good your product or service is, your brand will be able to expand globally only when you resonate with the customers in the target market – and the audience understands your message and offered solutions.&lt;/p&gt;

&lt;p&gt;That’s why it’s important to invest in localization right at the start of your internationalization journey, as it will help you reach more prospects, offer a better customer experience, and increase revenue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3OM-Rvp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wyb3bjwgveq1wxn7vwr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3OM-Rvp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wyb3bjwgveq1wxn7vwr7.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://blog.lokalise.co/localization-importance/"&gt;Why localization matters for your business&lt;/a&gt; appeared first on &lt;a href="https://blog.lokalise.co"&gt;Lokalise Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>i18n</category>
      <category>localization</category>
      <category>ios</category>
      <category>android</category>
    </item>
    <item>
      <title>Agile localization: The complete guide</title>
      <dc:creator>Lokalise Dev</dc:creator>
      <pubDate>Tue, 26 Mar 2019 08:52:45 +0000</pubDate>
      <link>https://forem.com/lokalise/agile-localization-the-complete-guide-3h7i</link>
      <guid>https://forem.com/lokalise/agile-localization-the-complete-guide-3h7i</guid>
      <description>&lt;p&gt;In the world of software development, localization used to be an afterthought. Product teams would focus on the design and development of the product. Only when the product is complete the need for localization arises.&lt;/p&gt;

&lt;p&gt;In today’s world, software development doesn’t work that way anymore. Companies went from annual and biannual releases to weekly and even daily releases. This has raised the need for agile localization to keep up with the fast-paced world of software development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this guide, we will discuss:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What agile localization is.&lt;/li&gt;
&lt;li&gt;The benefits of agile localization.&lt;/li&gt;
&lt;li&gt;The challenges that come with agile localization.&lt;/li&gt;
&lt;li&gt;Some tips to help you through your localization journey.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start by taking a look at what agile development is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is agile software development?
&lt;/h2&gt;

&lt;p&gt;Agile software development is a set of &lt;a href="https://plan.io/blog/ultimate-guide-to-implementing-agile-project-management-and-scrum/" rel="noopener noreferrer"&gt;software development methodologies&lt;/a&gt; which are based on iterative and cross-functional team collaboration approaches. It allows developers to produce high-quality code as well as ship products faster and more often.&lt;/p&gt;

&lt;p&gt;Before agile, development teams used to follow the waterfall methodology. This means that all the software requirements are defined beforehand. Making any changes during the process used to be almost impossible and very costly. Agile breaks this linear sequential approach. It splits the product development process into smaller parts or iterations. That way, the requirements change and evolve into a more flexible manner based on need.&lt;/p&gt;

&lt;p&gt;Agile Software Development spans many different frameworks and methodologies of software development, including Lean software development, Kanban, and Scrum.&lt;/p&gt;

&lt;p&gt;The term “&lt;em&gt;Agile&lt;/em&gt;” was popularized by the &lt;a href="https://agilemanifesto.org/" rel="noopener noreferrer"&gt;Manifesto for Agile Software Development&lt;/a&gt; which lists down four core values:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Manifesto-values-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Manifesto-values-1.png" alt="agile software development values"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But how does localization adapt to agile software development? Well, that’s where agile localization comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is agile localization?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Agile localization is a set of practices to incorporate localization and translation into an agile product development cycle.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Translations aren’t just done once and delivered at the end of the development cycle. Instead, they are translated as the product is being developed. This means that once new iterations are released, translators or localization teams can simultaneously work on the localization of the changes happening in the product.&lt;/p&gt;

&lt;p&gt;There are still the waterfall advocates who prefer it to agile. They believe that it offers a more firm and fixed structure that gives you an idea of how the project is going to unfold. That’s why there are companies that use the waterfall localization workflow to this day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Agile-Development.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Agile-Development.png" alt="Agile Localization Agile Development"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  So what is the waterfall localization workflow?
&lt;/h3&gt;

&lt;p&gt;Initially, there were only two main approaches that developers and localization teams followed. While they may not be ideal, some still adopt them:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Post-release
&lt;/h4&gt;

&lt;p&gt;Post-release is a common software localization approach where, as the name suggests, the localization takes place after the release of the product. In this approach, the localization teams can start working on the translations only when the product has been delivered.&lt;/p&gt;

&lt;p&gt;This approach causes a considerable delay. There is a distinctive gap between the completion of the development of the product and the completion of the translation. Latter option causes a waste of resources and makes companies incapable of releasing their software.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. String freeze
&lt;/h4&gt;

&lt;p&gt;The string freeze approach combats the inefficiencies of the first approach. There is a “string freeze” period during the software development process. During that time, strings that need to be localized are locked and can’t be changed, modified or removed. The string freeze duration can be anything from a few days to several weeks. It gives the localization teams time to work on translating the strings and then send them back to the developers in time for the software release. During the time of the freeze, developers can work on fixing bugs and defects, again without changing any of the strings.&lt;/p&gt;

&lt;p&gt;This approach can definitely be a lot more time-saving than the first one. However, developers would still need to manually identify the modified strings in the code which causes it to remain cumbersome.&lt;/p&gt;

&lt;p&gt;That’s why more and more companies eventually started adapting the agile localization workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is the agile localization workflow different?
&lt;/h2&gt;

&lt;p&gt;Agile localization syncs the localization process with the development process so that they are both operating simultaneously. This is accomplished by incorporating a continuous localization platform into your workflow which automatically facilitates and handles the process. The agile localization workflow then takes place as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The developer pushes new code to the project codebase.&lt;/li&gt;
&lt;li&gt;The localization platform automatically recognizes new or modified strings.&lt;/li&gt;
&lt;li&gt;It pushes them to the localization team or translators to work on.&lt;/li&gt;
&lt;li&gt;Once they’re done and the translations are approved, the platform automatically sends the translations back to the developers. It also syncs them back into the project so that they’re ready for release.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What are the benefits of agile localization?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Benefits.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Benefits.png" alt="Agile Localization Benefits"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Efficiency
&lt;/h3&gt;

&lt;p&gt;When agile localization is implemented correctly, the entire process becomes more efficient and effective with no resistance or bottlenecks. Any new updates to the code are recognized, and new or updated strings are automatically sent to the translators. The translators can now work on the strings, then push it back into the code, without needing any prior knowledge of the development process of the product. That way developers can be more focused on the development of the product without having to worry about other aspects of it.&lt;/p&gt;

&lt;p&gt;Agile localization also helps save a lot of resources, whether it’s time or money, as it closes the gap between the software development and localization processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ease of use
&lt;/h3&gt;

&lt;p&gt;One of the most significant advantages of continuous localization is how it makes the developers’ and especially the translators’ lives a whole lot easier. Before agile localization, developers would send translators a batch of strings in different formats (e.g., .xml files for Android apps, .strings files for iOS apps, .csv files, etc.). This forced translators to learn multiple different technologies to be able to work with them. The use of a continuous localization platform has eliminated that hassle. It allows you to import all your favorite file formats to the platform or even have it done automatically through the use of integration (e.g., GitHub). Moreover, the translators can work on the strings straight through the platform’s online intuitive dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adaptability
&lt;/h3&gt;

&lt;p&gt;Agile localization platforms, like &lt;a href="https://lokalise.co/" rel="noopener noreferrer"&gt;Lokalise&lt;/a&gt;, easily adapt to your workflow and open up a whole new world of translation possibilities for you. It doesn’t matter whether you’re a big company that has its own localization team or an indie developer who hires independent translators. Localization platforms also offer the possibility of crowdsourcing translations. Some even give you access to certified translators. In this case,  you can leverage professionals to start working on your project immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what challenges come with agile localization?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Challenges.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Challenges.png" alt="Agile Localization Challenges"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Short-term vision
&lt;/h3&gt;

&lt;p&gt;As opposed to the traditional waterfall model which had teams create a full plan from the beginning to an end with a set timeline, the agile approach is more flexible with shorter sprints. And while this has its benefits, you need to make sure that you plan your sprints effectively.&lt;/p&gt;

&lt;p&gt;That’s why you need to make sure that you have a localization team or enough translators to be able to handle those short sprints. If you don’t, you can leverage a translation management system which allows you to order professional translations. You can have your product translated to all popular languages, in Lokalise, for as low as $0.07 per word.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed vs. quality
&lt;/h3&gt;

&lt;p&gt;Working in a fast-paced agile workflow, their focus is more on speed than quality. For example, using machine translation tools like DeepL, Google Translate can impact the quality of the translations.&lt;/p&gt;

&lt;p&gt;However, the trade-off is usually acceptable, especially since they can be easily fixed with continuous localization as opposed to waterfall localization, where things have to be perfect by the time of the release.&lt;/p&gt;

&lt;p&gt;Now, let’s take a look at some tips to help you overcome these agile localization challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agile localization tips
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Tips.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Tips.png" alt="Agile Localization Tips"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Establish an internationalization (i18n) mindset
&lt;/h3&gt;

&lt;p&gt;Agile localization can have a drawback where developers only focus on the code. They would pay no attention to internationalization, which is a crucial process needed for localization. That is why it’s essential for developers to always work with an internationalization-centric mindset. They need to understand how important it is to always make sure to adapt the code and prepare it for localization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create clear translation guidelines
&lt;/h3&gt;

&lt;p&gt;To combat the translation inconsistencies continuous localization platforms offer a glossary feature. It helps maintain consistent translations across the project by providing definitions.&lt;/p&gt;

&lt;p&gt;The glossary can contain things like the names of products and services, brand-specific slogans, terminology, abbreviations, acronyms, etc. This ensures maintaining brand unity as well as speed up the translation process by providing clear guidelines to your translators.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conduct localization and linguistic testing
&lt;/h3&gt;

&lt;p&gt;It is critical to perform localization as well as linguistic testing once the translation is done. Linguistic testing ensures that all translations are complete and accurate without any grammatical or spelling mistakes. Localization testing ensures that the localized content abides by local customs and UI standards. This helps you locate and fix any translation errors, code issues and have them fixed ahead of release.&lt;/p&gt;

&lt;p&gt;It is also advised that the testing is performed on an audience from your target demographic. They can provide you with valuable insights that you wouldn’t know or have access to otherwise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pick the right Translation Management System
&lt;/h3&gt;

&lt;p&gt;There are multiple localization tools and platforms that you can choose from but there are a few things to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automation:&lt;/strong&gt; The platform needs to be able to blend flawlessly into your development workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supported Platforms:&lt;/strong&gt; The ability to support the platforms you’re working with. Also, when using multiple platforms, having the ability to store all platform keys in the same project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration Tools:&lt;/strong&gt; Allows you to invite team members and translators to work on the same project. They should also have the ability to assign different translation tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How can Lokalise help?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Lokalise.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.lokalise.co%2Fwp-content%2Fuploads%2F2019%2F03%2FAgile-Localization-Lokalise.png" alt="Agile Localization Lokalise"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://lokalise.co/" rel="noopener noreferrer"&gt;Lokalise&lt;/a&gt; is the leading localization and translation tool for web and apps. It integrates flawlessly with your existing development workflow through a plethora of one-click integrations, libraries, and plugins.&lt;/p&gt;

&lt;p&gt;It offers all the essential localization features like an online editor, translation memory, translation history, the use of a glossary, and more. In addition, Lokalise has more advanced features like inline machine translations, automatic screenshot linking, iOS and Android SDKs which allow you to preview the translations and app changes instantly.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://blog.lokalise.co/agile-localization/" rel="noopener noreferrer"&gt;Agile localization: The complete guide&lt;/a&gt; appeared first on &lt;a href="https://blog.lokalise.co" rel="noopener noreferrer"&gt;Lokalise Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>agile</category>
      <category>localization</category>
      <category>i18n</category>
    </item>
    <item>
      <title>Libraries for translating JavaScript apps</title>
      <dc:creator>Lokalise Dev</dc:creator>
      <pubDate>Thu, 31 Jan 2019 08:25:22 +0000</pubDate>
      <link>https://forem.com/lokalise/libraries-for-translating-javascript-apps-4l5c</link>
      <guid>https://forem.com/lokalise/libraries-for-translating-javascript-apps-4l5c</guid>
      <description>&lt;p&gt;Today, we are going to talk about libraries for translating JavaScript apps and briefly see them in action. It appears that there are quite a lot of available solutions, so you may ask: “Which one should I use?”. The most obvious (and perhaps the most sane) answer would be: “It depends”. Ideally, you should check each library and then decide which one you prefer.&lt;/p&gt;

&lt;p&gt;Therefore, in this article I will give you a general introduction to the following solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Globalize&lt;/li&gt;
&lt;li&gt;I18next&lt;/li&gt;
&lt;li&gt;jQuery.I18n&lt;/li&gt;
&lt;li&gt;Polyglot.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that we will be talking about localizing vanilla JS apps, not about some specific client-side framework. Also, we won’t dive deep into each library because the article would become much, much longer. I’ll only give you a gentle introduction to each tool and then we’ll try to compare them and come to some general conclusion.&lt;/p&gt;

&lt;p&gt;Shall we start?&lt;/p&gt;

&lt;h2&gt;
  
  
  Globalize
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/globalizejs/globalize"&gt;Globalize&lt;/a&gt; is a complex translation and localization JS library initially introduced by jQuery team. This library utilizes &lt;a href="http://cldr.unicode.org/"&gt;Unicode common locale data repository&lt;/a&gt; (CLDR) and has lots of features including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Message formatting&lt;/li&gt;
&lt;li&gt;Date/time parsing and ability to work with relative time&lt;/li&gt;
&lt;li&gt;Pluralization support&lt;/li&gt;
&lt;li&gt;Numbers parsing and currency formatting&lt;/li&gt;
&lt;li&gt;Ability to work with units (days, minutes, seconds, miles per hour etc)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Globalize works consistently in browser and NodeJS, has &lt;a href="https://github.com/globalizejs/globalize#pick-the-modules-you-need"&gt;modular code&lt;/a&gt; and allows to require as little modules as needed. While relying on CLDR data, it does not host or hardcode it directly, therefore developers may choose which data to load. This also means that you can update CLDR data yourself, without waiting for a new version of Globalize to be released. You may read a bit more about Globalize’s features &lt;a href="https://github.com/globalizejs/globalize#why-globalize"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now let’s see this library in action. There is a &lt;a href="https://github.com/globalizejs/globalize#getting-started"&gt;Getting started guide&lt;/a&gt; that explains how to install all the required modules on your machine using package manager. However, we’ll choose a more complex way of loading everything manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting CLDR Data
&lt;/h3&gt;

&lt;p&gt;CLDR is really huge and so there is no reason to download all its content. Luckily, Globalize documentation &lt;a href="https://github.com/globalizejs/globalize#2-cldr-content"&gt;summarizes what you have to load&lt;/a&gt; when using specific modules. Also there is an &lt;a href="https://johnnyreilly.github.io/globalize-so-what-cha-want/#/?currency=true&amp;amp;date=true&amp;amp;message=true&amp;amp;number=true&amp;amp;plural=true&amp;amp;relativeTime=true&amp;amp;unit=true"&gt;online tool&lt;/a&gt; where you just pick the modules that will be used and then see what JSON files you need to load. In this demo I’ll only use  “core”, “message”, and “plural” modules therefore we require the following files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/unicode-cldr/cldr-core/blob/master/supplemental/likelySubtags.json"&gt;cldr/supplemental/likelySubtags.json&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/unicode-cldr/cldr-core/blob/master/supplemental/plurals.json"&gt;cldr/supplemental/plurals.json&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/unicode-cldr/cldr-core/blob/master/supplemental/ordinals.json"&gt;cldr/supplemental/ordinals.json&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more how CLDR is organized, &lt;a href="https://github.com/unicode-cldr/cldr-json#package-organization"&gt;refer to this doc&lt;/a&gt;. It may seem complex at first but in reality things are quite simple: you just cherry-pick the required files, download them and use in your project.&lt;/p&gt;

&lt;p&gt;I’ve placed the files mentioned above to the &lt;code&gt;cldr/supplemental&lt;/code&gt; folder of my project but you may organize them differently of course.&lt;/p&gt;

&lt;p&gt;The next question is: how do we actually load this data? Well, &lt;a href="https://github.com/globalizejs/globalize/blob/master/doc/cldr.md#how-do-i-load-cldr-data-into-globalize"&gt;there are two alternatives&lt;/a&gt;: by embedding it inside the &lt;code&gt;Globalize.load&lt;/code&gt; function or by using an &lt;a href="https://api.jquery.com/jQuery.get/"&gt;asynchronous &lt;code&gt;$.get()&lt;/code&gt; method&lt;/a&gt;. The second option is much more robust, so let’s create a new JS file with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// i18n.js $.when( $.get("cldr/supplemental/likelySubtags.json"), $.get("cldr/supplemental/ordinals.json"), $.get("cldr/supplemental/plurals.json"), ).then(function() { // Normalize $.get results, we only need the JSON, not the request statuses. return [].slice.apply(arguments, [0]).map(function(result) { return result[0]; }); }).then(Globalize.load).then(function() { // your Globalize code here });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example we’re loading JSON data and feed it to Globalize. We’re using promises, so the custom code should be placed into the second &lt;code&gt;then&lt;/code&gt; and will be executed as soon as everything is loaded successfully. Feel free to refactor this code without using jQuery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading Other Files
&lt;/h3&gt;

&lt;p&gt;After loading CLDR JSON files, you require a &lt;a href="https://github.com/globalizejs/globalize#1-dependencies"&gt;bunch of other scripts&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;jQuery (note by the way that Globalize itself is not jQuery-based)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rxaviers/cldrjs"&gt;CLDR JS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Globalize JS core module&lt;/li&gt;
&lt;li&gt;Any other modules that you wish to use in your app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;jQuery and Cldr.js are external dependencies and you may load them from a CDN (for example, from &lt;a href="https://cdnjs.com/"&gt;cdnjs.com&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Then download Globalize from the &lt;a href="https://github.com/globalizejs/globalize/releases"&gt;Releases section&lt;/a&gt;, open the &lt;code&gt;dist&lt;/code&gt; folder, pick all the files that you require and place them under the &lt;code&gt;globalize&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;After that load all the scripts in the proper order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;!-- index.html --\&amp;gt; \&amp;lt;!DOCTYPE html\&amp;gt; \&amp;lt;html\&amp;gt; \&amp;lt;head\&amp;gt; \&amp;lt;meta charset="utf-8"\&amp;gt; \&amp;lt;/head\&amp;gt; \&amp;lt;body\&amp;gt; \&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/cldrjs/0.5.1/cldr.min.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/cldrjs/0.5.1/cldr/event.min.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/cldrjs/0.5.1/cldr/supplemental.min.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="globalize/globalize.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="globalize/plural.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="globalize/message.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="i18n.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;/body\&amp;gt; \&amp;lt;/html\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All in all, this is it. Now you may refer to the &lt;a href="https://github.com/globalizejs/globalize#api"&gt;API section&lt;/a&gt; of the Globalize docs and see what functions you may utilize.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using It
&lt;/h3&gt;

&lt;p&gt;You can provide translation messages with the help of &lt;a href="https://github.com/globalizejs/globalize/blob/master/doc/api/message/load-messages.md"&gt;&lt;code&gt;loadMessages&lt;/code&gt; function&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$.when( // ... }).then(Globalize.load).then(function() { Globalize.loadMessages({ "en": { 'welcome': 'Welcome, {name}!' } }); });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then instantiate Globalize with the desired locale and perform the actual translations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// loadMessages... var globalize = new Globalize("en"); console.log(globalize.messageFormatter('welcome')({name: 'Username'}));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/globalizejs/globalize/blob/master/doc/api/message/message-formatter.md"&gt;&lt;code&gt;messageFormatter&lt;/code&gt;&lt;/a&gt; returns a formatted translation. As you can see from this example, it supports interpolation, but there is more. Want to introduce pluralization? Simple!&lt;/p&gt;

&lt;p&gt;Add a new message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Globalize.loadMessages({ "en": { 'welcome': 'Welcome, {name}!', 'messages': ["You have {count, plural,", " one {one message}", " other {{count} messages}", "}"] } });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the message may span multiple lines but in this case it should be defined as an array. Here we are utilizing pluralization and providing two forms: singular and plural. &lt;code&gt;count&lt;/code&gt; is an interpolation.&lt;/p&gt;

&lt;p&gt;Now display this message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;taskFormatter = globalize.messageFormatter("messages"); console.log(taskFormatter({ count: 10 }));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may utilize other modules in pretty much the same way.&lt;/p&gt;

&lt;p&gt;To summarize, Globalize is a great powerful solution with good documentation and nice support. It may require some time to set it up but working with it is convenient and intuitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  I18next
&lt;/h2&gt;

&lt;p&gt;I18next is a JavaScript localization framework providing all the necessary tools to translate your applications. It has loads of various features including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.i18next.com/overview/supported-frameworks"&gt;Support for front-end frameworks&lt;/a&gt; including React, Angular, Vue etc&lt;/li&gt;
&lt;li&gt;Supports for various formats (including Polyglot which we’ll discuss later)&lt;/li&gt;
&lt;li&gt;Message formatting&lt;/li&gt;
&lt;li&gt;Pluralization&lt;/li&gt;
&lt;li&gt;Fallbacks&lt;/li&gt;
&lt;li&gt;Ability to load translation data from various resources&lt;/li&gt;
&lt;li&gt;…and many, many other &lt;a href="https://www.i18next.com/overview/plugins-and-utils"&gt;utilities and plugins&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Loading Required Files
&lt;/h3&gt;

&lt;p&gt;To get started with I18next you may simply require it from CDN, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;!DOCTYPE html\&amp;gt; \&amp;lt;html\&amp;gt; \&amp;lt;head\&amp;gt; \&amp;lt;meta charset="utf-8"\&amp;gt; \&amp;lt;/head\&amp;gt; \&amp;lt;body\&amp;gt; \&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/i18next/14.0.1/i18next.min.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;/body\&amp;gt; \&amp;lt;/html\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, it can be also installed with NPM or Yarn as explained &lt;a href="https://www.i18next.com/overview/getting-started"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;As I already mentioned above, I18next allows  you to load translations from the &lt;a href="https://www.i18next.com/overview/plugins-and-utils#backends"&gt;backend&lt;/a&gt;, but you may also provide them directly in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i18next.init({ lng: 'en', resources: { en: { translation: { "hi": "Welcome" } } } }).then(function(t) { // ready to go! });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that I am also setting English as a default locale.&lt;/p&gt;

&lt;p&gt;There are many other configuration options that are listed on the &lt;a href="https://www.i18next.com/overview/configuration-options"&gt;corresponding page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using It
&lt;/h3&gt;

&lt;p&gt;You may perform translations in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ... init .then(function(t) { // initialized and ready to go! console.log(i18next.t('hi')); });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.i18next.com/overview/api#t"&gt;&lt;code&gt;t&lt;/code&gt; is a function&lt;/a&gt; to look up translation  based on the provided key. It can also work with interpolation, for instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i18next.t('hi', {name: 'Username'});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.i18next.com/translation-function/plurals"&gt;Pluralization&lt;/a&gt; is supported too. To start using it, define singular and plural forms in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "msg": "one message", "msg\_plural": "{{count}} messages" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;_plural&lt;/code&gt; part that has to be provided for plural forms. Some languages require &lt;a href="https://www.i18next.com/translation-function/plurals#languages-with-multiple-plurals"&gt;multiple forms&lt;/a&gt;. In this case use &lt;code&gt;_0&lt;/code&gt;, &lt;code&gt;_1&lt;/code&gt;, and other post-fixes, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "key\_0": "zero", "key\_1": "singular", "key\_2": "two", "key\_3": "few", "key\_4": "many", "key\_5": "other" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then just use the &lt;code&gt;t&lt;/code&gt; function again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i18next.t('msg', {count: 10});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I18next allows you to provide &lt;a href="https://www.i18next.com/translation-function/context"&gt;context for the translation&lt;/a&gt;. This  is particularly important when working with gender information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "friend": "A friend", "friend\_male": "A boyfriend", "friend\_female": "A girlfriend" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;_male&lt;/code&gt; and &lt;code&gt;_female&lt;/code&gt; here are contexts that you can set in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i18next.t('friend'); // ==\&amp;gt; No context here, so return "A friend" i18next.t('friend', { context: 'male' }); // -\&amp;gt; A context is present, so return "A boyfriend"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t hesitate to browse other examples in the I18next’s docs on how to &lt;a href="https://www.i18next.com/translation-function/nesting"&gt;enable nesting in translations&lt;/a&gt;, &lt;a href="https://www.i18next.com/translation-function/objects-and-arrays"&gt;work with  objects&lt;/a&gt;, or &lt;a href="https://www.i18next.com/principles/fallback"&gt;setup fallbacks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To summarize, I18next is a great framework with an array of various plugins and utilities. This framework is quite large and heavy, but you receive all the necessary localization tools that can be extended as necessary. Moreover, setting this framework up is simple and requires very little time. So, I would say this is a great candidate for complex applications!&lt;/p&gt;

&lt;h2&gt;
  
  
  jQuery.I18n
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/wikimedia/jquery.i18n"&gt;jQuery.I18n&lt;/a&gt; is yet another popular solution presented to you by &lt;a href="https://www.mediawiki.org/wiki/Wikimedia_Language_engineering"&gt;Wikimedia Engineering team&lt;/a&gt; allowing to translate your JavaScript applications. Wikimedia, in turn, is a company behind &lt;a href="http://en.wikipedia.org/"&gt;Wikipedia project&lt;/a&gt;, one of  the most popular websites in the world. jQuery.I18n is used in Wikipedia internally, so you can be sure this library won’t be abandoned out of the blue. It utilizes JSON-based localization format and support the following &lt;a href="https://github.com/wikimedia/jquery.i18n#features"&gt;features&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ability to meta information and document your messages&lt;/li&gt;
&lt;li&gt;Supports pluralization with the help of CLDR&lt;/li&gt;
&lt;li&gt;Gender information&lt;/li&gt;
&lt;li&gt;Support for grammar forms&lt;/li&gt;
&lt;li&gt;Fallback chains&lt;/li&gt;
&lt;li&gt;Ability to customize message parser&lt;/li&gt;
&lt;li&gt;Has modular code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s see jQuery.I18n in action now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading Required Files
&lt;/h3&gt;

&lt;p&gt;First of all, download the library itself and initialize its dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone https://github.com/wikimedia/jquery.i18n.git $ cd jquery.i18n $ git submodule update --init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;jquery.i18n/src&lt;/code&gt; folder contains the library’s files. Pick the modules that you need (at the very least, you’ll require the core &lt;code&gt;jquery.i18n.js&lt;/code&gt;) and place them to your application. The idea here is similar to the one in Globalize. The &lt;code&gt;languages&lt;/code&gt; folder contains some helpers for various locales, so if you are supporting one of these, don’t forget to copy the corresponding file as well.&lt;/p&gt;

&lt;p&gt;If your application works with plural forms, then the &lt;code&gt;CLDRPluralRuleParser.js&lt;/code&gt; file is necessary too (it can be found under the &lt;code&gt;jquery.i18n\libs\CLDRPluralRuleParser\src&lt;/code&gt; path).&lt;/p&gt;

&lt;p&gt;After you are ready, load the files in the proper order, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;!DOCTYPE html\&amp;gt; \&amp;lt;html\&amp;gt; \&amp;lt;head\&amp;gt; \&amp;lt;meta charset="utf-8"\&amp;gt; \&amp;lt;/head\&amp;gt; \&amp;lt;body\&amp;gt; \&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="lib/CLDRPluralRuleParser.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="lib/jquery.i18n.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="lib/jquery.i18n.messagestore.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="lib/jquery.i18n.fallbacks.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="lib/jquery.i18n.language.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="lib/jquery.i18n.parser.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="lib/jquery.i18n.emitter.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;script src="lib/jquery.i18n.emitter.bidi.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;/body\&amp;gt; \&amp;lt;/html\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Providing Translations
&lt;/h3&gt;

&lt;p&gt;As mentioned above, &lt;a href="https://github.com/wikimedia/jquery.i18n#message-file-format"&gt;translations for the jQuery.I18n library&lt;/a&gt; are stored inside JSON files. You may separate translation data for different languages, or store everything in a single file. Create a &lt;code&gt;i18n/i18n.json&lt;/code&gt; file with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "@metadata": { "authors": ["Ilya"], "last-updated": "2019-01-29", "message-documentation": "qqq" }, "welcome": "Hi!" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/wikimedia/jquery.i18n#message-loading"&gt;To load this file&lt;/a&gt;, use the following code (note that I am also providing a default locale):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// main.js jQuery(document).ready(function() { $.i18n({locale: 'en'}).load({ en: 'i18n/i18n.json' }).done(function() { // success }) });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Include this script on your main page and you are good to go!&lt;/p&gt;

&lt;h3&gt;
  
  
  Using  It
&lt;/h3&gt;

&lt;p&gt;For instance, you may output a welcoming message in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log($.i18n('welcome', 'Username'));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/wikimedia/jquery.i18n#plurals"&gt;Pluralization&lt;/a&gt; is performed in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "msg": "You have $1 {{PLURAL:$1|message|messages}}" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, you have one key that lists all the available forms,  both  plural and singular. &lt;code&gt;$1&lt;/code&gt; is a &lt;a href="https://github.com/wikimedia/jquery.i18n#placeholders"&gt;placeholder&lt;/a&gt; for the interpolation. You may have as many placeholders as needed, and they should be named in a sequential manner: &lt;code&gt;$2&lt;/code&gt;, &lt;code&gt;$3&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;Then just utilize this new key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$.i18n('msg', 10); // $1 placeholder will have a value of 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The context of the translation is defined in pretty much the same way. For example, you can work with &lt;a href="https://github.com/wikimedia/jquery.i18n#gender"&gt;gender information&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"friend": "Some text... {{GENDER:$1|A boyfriend|A girlfriend}}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provide the context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$.i18n('friend', 'female');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One interesting feature is the support for the &lt;a href="https://github.com/wikimedia/jquery.i18n#data-api"&gt;&lt;code&gt;data-*&lt;/code&gt; HTML5 attributes&lt;/a&gt;. You just need to add a &lt;code&gt;data-i18n&lt;/code&gt; attribute to your tags,  provide the key as the value, and then apply &lt;code&gt;.i18n()&lt;/code&gt; function directly to those elements or their parent. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;body\&amp;gt; \&amp;lt;p data-i18n="translation-key"\&amp;gt;Fallback text goes here\&amp;lt;/p\&amp;gt; \&amp;lt;p data-i18n="another-key"\&amp;gt;Fallback text goes here\&amp;lt;/p\&amp;gt; \&amp;lt;/body\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now  inside your  code simply say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$('body').i18n();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script is going to traverse all elements inside &lt;code&gt;body&lt;/code&gt; and replace their contents with the messages under the provided translation keys. If the key cannot be found, the initial content will be displayed as a fallback.&lt;/p&gt;

&lt;p&gt;jQuery.I18n is a powerful and quite easy-to-use library. Basically, you may call it a direct competitor to Globalize as these two solutions have similar functionality. To some people Globalize may seem more favorable as it doesn’t rely on jQuery. On the other hand, many websites do requite jQuery, so that’s perhaps not a deal-breaker. If you’d like to mostly stay away from CLDR then jQuery.I18n is of course a better option. This library also allows to store metadata inside your translation files, supports &lt;a href="https://github.com/wikimedia/jquery.i18n#data-api"&gt;&lt;code&gt;data-*&lt;/code&gt; attributes API&lt;/a&gt;, supports so-called &lt;a href="https://github.com/wikimedia/jquery.i18n#magic-word-support"&gt;“magic words”&lt;/a&gt;, and more. So, as  you see, there is really a lot of features!&lt;/p&gt;

&lt;h2&gt;
  
  
  Polyglot
&lt;/h2&gt;

&lt;p&gt;The last solution we’ll talk about is &lt;a href="https://github.com/airbnb/polyglot.js"&gt;Polyglot.js&lt;/a&gt; created by Airbnb. As long as Airbnb service is worldwide, it’s essential for them to have proper localization. Polyglot, in contrast to the previously discussed libraries, is a very small solution really. It has only the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic translation features&lt;/li&gt;
&lt;li&gt;Interpolation&lt;/li&gt;
&lt;li&gt;Pluralization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It may become and excellent candidate for smaller and less intricate apps that do not require all the complexities of, say, Globalize. Now let’s see how to get started with Polyglot!&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading Files
&lt;/h3&gt;

&lt;p&gt;Polyglot has no external dependencies at all, so all you need to do is hook up the main file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;!DOCTYPE html\&amp;gt; \&amp;lt;html\&amp;gt; \&amp;lt;head\&amp;gt; \&amp;lt;meta charset="utf-8"\&amp;gt; \&amp;lt;/head\&amp;gt; \&amp;lt;body\&amp;gt; \&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/polyglot.js/2.2.2/polyglot.min.js"\&amp;gt;\&amp;lt;/script\&amp;gt; \&amp;lt;/body\&amp;gt; \&amp;lt;/html\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Providing Translations and Using It
&lt;/h3&gt;

&lt;p&gt;Now we can provide translations (aka “phrases”) and set the default locale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var polyglot = new Polyglot({ locale: 'en', phrases: { "message\_count": "%{smart\_count} message |||| %{smart\_count} messages" } });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example the default locale is English. Also there is a &lt;code&gt;message_count&lt;/code&gt; key that provides singular and plural forms separated with 4 pipelines (for  other languages there may be more forms). Oddly enough, &lt;a href="https://github.com/airbnb/polyglot.js#pluralization"&gt;pluralization relies on the &lt;code&gt;smart_count&lt;/code&gt; interpolated value&lt;/a&gt;, so you must provide it in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log(polyglot.t('message\_count', {smart\_count: 2}));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is it! There is not much else to say about the translation process, as it relies only on the &lt;code&gt;t&lt;/code&gt; function. You may find some more examples of using Polyglot in the &lt;a href="https://github.com/airbnb/polyglot.js#translation"&gt;official doc&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summing Everything Up
&lt;/h2&gt;

&lt;p&gt;Potentially, there is a lot of different features to compare (some may be more or less relevant for your setup), but here is a brief summary of the discussed solutions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aORn_JVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.lokalise.co/wp-content/uploads/2019/01/Untitled-1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aORn_JVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.lokalise.co/wp-content/uploads/2019/01/Untitled-1.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A couple of things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I18next &lt;a href="https://github.com/i18next/i18next-gitbook/blob/master/translation-function/formatting.md"&gt;does support various formatting&lt;/a&gt; but it requires external dependencies like &lt;a href="https://momentjs.com/"&gt;moment.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;jQuery.I18n requires CLDR Parser only for pluralization&lt;/li&gt;
&lt;li&gt;I18next provides lots of plugins to connect with client-side framework, but other solutions can play nicely with frameworks as well (you may just need to spend more time to integrate everything)&lt;/li&gt;
&lt;li&gt;You may work with gender information (and,  more broadly speaking, with  contexts) in any library — it just may be less convenient and present more complexeties&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From my experience, I18next is a very powerful and feature-rich tool that you can easily get started with. At the same time, Globalize’s modular approach and relation on CLDR might be convenient, especially for larger and more complex applications. I have not used jQuery.I18n that much but as long as the Wikimedia team utilizes it, one can conclude that this is also a feasible tool with vast functionality. And, Polyglot is a nice tiny helper for simpler apps that also plays very nicely with server-side frameworks like Rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Your Life Easier With Lokalise
&lt;/h2&gt;

&lt;p&gt;Supporting multiple languages on a big website may become a serious pain. You must make sure that all the keys are translated for each and every locale. Luckily, there is a solution to this problem: the Lokalise platform that &lt;a href="https://lokalise.co/features"&gt;makes working with the localization files much simpler&lt;/a&gt;. Let me guide you through the initial setup which is nothing complex really.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To get started, &lt;a href="https://lokalise.co/signup"&gt;grab your free trial&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new project, give it some name, and set English as a base language&lt;/li&gt;
&lt;li&gt;Click “Upload Language Files”&lt;/li&gt;
&lt;li&gt;Upload translation files for all your languages&lt;/li&gt;
&lt;li&gt;Proceed to the project, and edit your translations as needed&lt;/li&gt;
&lt;li&gt;You may also contact professional translator to do the job for you&lt;/li&gt;
&lt;li&gt;Next simply download your files back&lt;/li&gt;
&lt;li&gt;Profit!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lokalise has many more features including support for dozens of platforms and formats, and even the possibility to upload screenshots in order to read texts from them. So, stick with Lokalise and make your life easier!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article we were talking about the available tools used to translate JavaScript applications. We’ve covered Globalize, I18next and jQuery.I18n (larger and more complex solutions), as well as Polyglot which appeared to be a much simpler and smaller library. We’ve compared these libraries and came up with some conclusions about them. Hopefully, now you will be able to pick an I18n solution that fully suits you. Don’t be afraid to research, experiment, and ultimately pick the tool that works for you! After all,  it will be more complex to switch to another localization  library when your application is half-finished.&lt;/p&gt;

&lt;p&gt;I thank you for staying with me, and until the next time!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3OM-Rvp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wyb3bjwgveq1wxn7vwr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3OM-Rvp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wyb3bjwgveq1wxn7vwr7.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://blog.lokalise.co/comparing-libraries-translating-js-apps/"&gt;Libraries for translating JavaScript apps&lt;/a&gt; appeared first on &lt;a href="https://blog.lokalise.co"&gt;Lokalise Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>libraries</category>
      <category>localization</category>
      <category>i18n</category>
    </item>
  </channel>
</rss>
