<?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: Žan Horvat</title>
    <description>The latest articles on Forem by Žan Horvat (@an_horvat_ac8f7d2703e719).</description>
    <link>https://forem.com/an_horvat_ac8f7d2703e719</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%2Fuser%2Fprofile_image%2F1855188%2Fe000139c-7a9c-4669-871c-c55341ad50eb.jpg</url>
      <title>Forem: Žan Horvat</title>
      <link>https://forem.com/an_horvat_ac8f7d2703e719</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/an_horvat_ac8f7d2703e719"/>
    <language>en</language>
    <item>
      <title>How to Translate Your Next.js App in 5 Minutes With Crowdin</title>
      <dc:creator>Žan Horvat</dc:creator>
      <pubDate>Wed, 14 Aug 2024 11:00:48 +0000</pubDate>
      <link>https://forem.com/zerodays/how-to-translate-your-nextjs-app-in-5-minutes-with-crowdin-1pe0</link>
      <guid>https://forem.com/zerodays/how-to-translate-your-nextjs-app-in-5-minutes-with-crowdin-1pe0</guid>
      <description>&lt;p&gt;For the majority of websites using content management systems such as Webflow or WordPress, localization is not an issue, as it can be relatively easily done through the system. &lt;/p&gt;

&lt;p&gt;But for &lt;strong&gt;custom solutions&lt;/strong&gt;, such as custom web applications using Next.js or just plain React, this can be quite a pain.&lt;/p&gt;

&lt;p&gt;When the client wants to have the product in another language, one of the developers has to open the code and manually translate the strings, as the process is often too technical to be outsourced and fully done by the translators. &lt;/p&gt;

&lt;p&gt;Now, imagine having to translate the entire web app into more than one language - simply a buttload of manual work developers don’t have time for.&lt;/p&gt;

&lt;p&gt;We decided to see if there are any solutions to this issue on the market, did a bit of research, and decided to try out &lt;a href="https://crowdin.com/" rel="noopener noreferrer"&gt;Crowdin&lt;/a&gt; - and we think it’s awesome! It offers: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Support for almost all languages&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with 600+ apps&lt;/strong&gt; (including GitHub integration)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to use UI for translating strings&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So for our needs we developed a localization pipeline around Crowdin and wanted to share it with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localization pipeline
&lt;/h2&gt;

&lt;p&gt;In our applications, translatable strings are stored in specific &lt;strong&gt;translation files&lt;/strong&gt; (usually in &lt;strong&gt;JSON format&lt;/strong&gt;). In addition to storing translatable strings in translation files, we usually use some sort of &lt;strong&gt;internationalization framework&lt;/strong&gt; (for example &lt;a href="https://github.com/i18next/next-i18next" rel="noopener noreferrer"&gt;next-i18next&lt;/a&gt; for our Next.js applications). &lt;/p&gt;

&lt;p&gt;Example of our &lt;strong&gt;folder structure&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/project-root 
  /public 
    /locales 
      /en-INTL 
        common.json
      /sl-SI 
        common.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example &lt;strong&gt;translation file&lt;/strong&gt; &lt;code&gt;common.json&lt;/code&gt;:&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;"welcome_message"&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 to our app!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"login_button"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"logout_button"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Logout"&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;To keep the translation files updated, we use &lt;a href="https://crowdin.github.io/crowdin-cli/" rel="noopener noreferrer"&gt;Crowdin CLI&lt;/a&gt; to &lt;strong&gt;upload&lt;/strong&gt; the source files and &lt;strong&gt;download&lt;/strong&gt; translations. &lt;/p&gt;

&lt;p&gt;Once the strings are available in Crowdin, our clients or dedicated translators work on translating the strings into the target languages within the Crowdin web platform.&lt;/p&gt;

&lt;p&gt;After translations are completed, Crowdin's &lt;strong&gt;integration with GitHub&lt;/strong&gt; automates the next step. Crowdin automatically generates a &lt;strong&gt;pull request&lt;/strong&gt; in the project's GitHub repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code example
&lt;/h2&gt;

&lt;p&gt;In this section, we’ll take a look at a step-by-step guide on how to implement the pipeline from the previous section.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create account and project on &lt;a href="https://crowdin.com/" rel="noopener noreferrer"&gt;Crowdin website
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Download &lt;a href="https://crowdin.github.io/crowdin-cli/" rel="noopener noreferrer"&gt;Crowdin CLI&lt;/a&gt;. If you use macOS, you can easily use &lt;strong&gt;Homebrew&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;crowdin@4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;After installation, you need to &lt;strong&gt;configure the CLI&lt;/strong&gt; to work with your Crowdin project. &lt;/p&gt;

&lt;p&gt;You can do this by creating a &lt;strong&gt;configuration file&lt;/strong&gt; (&lt;code&gt;crowdin.yml&lt;/code&gt;) in your project's root directory. Configuration file can be created with following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crowdin init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After creating the configuration file, set &lt;strong&gt;Crowdin credentials&lt;/strong&gt;. You can find them in the Crowdin Web UI.&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project_id_env'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_PROJECT_ID'&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;api_token_env'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_PERSONAL_TOKEN'&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;base_path_env'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_BASE_PATH'&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;base_url_env'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_BASE_URL'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;preserve_hierarchy&lt;/code&gt; property should be set to &lt;strong&gt;true&lt;/strong&gt;. This ensures that the directory structure of the source files is maintained in Crowdin. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;files&lt;/code&gt; section in the configuration file specifies which files should be synchronized with Crowdin. It includes the paths to the source files and their corresponding locations for translated files. If your source language is &lt;strong&gt;en-US&lt;/strong&gt; and translation files are located in &lt;code&gt;/public/locales/&amp;lt;locale&amp;gt;/&lt;/code&gt;, the &lt;code&gt;files&lt;/code&gt; section should look like this:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/public/locales/en-US/*.json'&lt;/span&gt;
  &lt;span class="na"&gt;translation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/public/locales/%locale%/%original_file_name%'&lt;/span&gt;
  &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/%original_file_name%'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's explain this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;source&lt;/code&gt; specifies the &lt;strong&gt;pattern&lt;/strong&gt; for files to be uploaded for translation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;translation&lt;/code&gt; specifies where the translated files should be &lt;strong&gt;placed locally&lt;/strong&gt;. &lt;code&gt;%locale%&lt;/code&gt; is a placeholder for the target language code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;dest&lt;/code&gt; indicates &lt;strong&gt;file name&lt;/strong&gt; of translations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Uploading/downloading translations from Crowdin
&lt;/h3&gt;

&lt;p&gt;To streamline the process of managing translation files with Crowdin, two &lt;strong&gt;custom commands&lt;/strong&gt; are used: &lt;code&gt;push-i18n&lt;/code&gt; and &lt;code&gt;pull-i18n&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We’ll define these commands in the project's &lt;code&gt;package.json&lt;/code&gt; file and automate the process of uploading and downloading translation files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"push-i18n"&lt;/span&gt;: &lt;span class="s2"&gt;"crowdin upload sources &amp;amp;&amp;amp; crowdin upload translations"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;push-i18n&lt;/code&gt; command is responsible for &lt;strong&gt;pushing&lt;/strong&gt; both &lt;strong&gt;source files&lt;/strong&gt; and any &lt;strong&gt;existing translations&lt;/strong&gt; to Crowdin. This command is typically run when you want to update Crowdin with the latest version of your project’s localization files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"pull-i18n"&lt;/span&gt;: &lt;span class="s2"&gt;"crowdin download sources &amp;amp;&amp;amp; crowdin download"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pull-i18n&lt;/code&gt; command handles the process of &lt;strong&gt;downloading files&lt;/strong&gt; from Crowdin to your local project. This ensures that you have the most recent translations available in your project.&lt;/p&gt;

&lt;p&gt;To use these commands conveniently add them to the &lt;code&gt;scripts&lt;/code&gt; section of your &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;To &lt;strong&gt;upload&lt;/strong&gt; your newly updated strings just use &lt;code&gt;push-i18n&lt;/code&gt;. To &lt;strong&gt;update&lt;/strong&gt; your local strings with changes from other translators just run &lt;code&gt;pull-i18n&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Crowdin offers a great solution for a painful problem of web app localization. Instead of keeping the work on the developer side, the entire translation process can be done by non-technical team members, through an intuitive dashboard. Numerous integrations and language support are also a plus!&lt;/p&gt;

&lt;p&gt;Do you recommend any other localization tools? Let us know in the comments!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This blog and its underlying research were made by the awesome team at zerodays.dev.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>crowdin</category>
      <category>localization</category>
    </item>
  </channel>
</rss>
