<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ internationalization - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ internationalization - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 19 May 2026 04:43:42 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/internationalization/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Handle Processing Multilingual Names in Your Applications ]]>
                </title>
                <description>
                    <![CDATA[ By Apoorv Tyagi Earlier this year, my team at work and I were looking at the errors occurring in one of our signup APIs. We saw that nearly 5% of our requests were getting failed, all due to 400 BAD REQUEST errors. And the root cause was traced back ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/handling-multilingual-names-in-applications/</link>
                <guid isPermaLink="false">66d45d988812486a37369c5f</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ internationalization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 13 Nov 2023 17:03:24 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/11/photo-1581544291234-31340be4b1b8.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Apoorv Tyagi</p>
<p>Earlier this year, my team at work and I were looking at the errors occurring in one of our signup APIs. We saw that nearly 5% of our requests were getting failed, all due to <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400">400 BAD REQUEST</a> errors. And the root cause was traced back to a regex check.</p>
<p>This regex was a constraint, where our system only allows people to use English characters to input their first and last names. The issue was, many individuals opted to enter their names in their native languages. </p>
<p>These customers were people who were interested in purchasing health policies from our platform, making them a crucial segment of our user base.</p>
<p>In response to this, we decided to address these 5% of our users by enabling them to enter their names in any language they preferred. But this brought up a lot of challenges that we needed to solve – and I'm going to explain how we did that here.</p>
<h2 id="heading-challenges-with-processing-multilingual-names">Challenges with Processing Multilingual Names</h2>
<h3 id="heading-1-data-storage-strategy">1. Data Storage Strategy</h3>
<p>We rely on MongoDB for storing and retrieving user names. While MongoDB allows storage for all UTF-8 compatible characters, the problem comes when dealing with search. </p>
<p>For English names, our search operations utilize the <a target="_blank" href="https://www.mongodb.com/docs/manual/reference/collation/">simple collation</a> method. The corresponding fields are appropriately indexed to optimize query performance.</p>
<p>While the option to implement a <a target="_blank" href="https://www.mongodb.com/docs/manual/reference/collation/">collation index</a> for other languages also exists in MongoDB, this approach means that you have to inform the DB about the specific language for which you intend to search. The challenge here is that our user base spans many languages, with India alone having more than 20 diverse languages.</p>
<p>Our objective was to extend support to at least all Indian languages. But this meant that implementing collation indexes for every supported language would lead to an increased number of indexes – and an increase in index size over time as well.</p>
<p>This approach would also place the responsibility on developers to remember to add an index for each new language as our language support expands, which is far from an efficient solution.</p>
<h3 id="heading-2-api-gateway-constraint">2. API Gateway Constraint</h3>
<p>All our APIs are exposed behind an API gateway. Just before the gateway forwards a request to the respective API service, an inbound policy verifies the user's authentication status. Once the user is authenticated, it retrieves basic user details such as name, mobile number, and other metadata, and appends it to a request header of that API.</p>
<p>Many APIs rely on this user-specific data in headers for their further processing.</p>
<p>But there's a restriction imposed by the gateway – it allows only <a target="_blank" href="https://www.cs.cmu.edu/~pattis/15-1XX/common/handouts/ascii.html">ASCII</a> characters for processing and inclusion in the headers. So we had to make sure that even though the name may be in any other language, the response we shared had to be exclusively in English.</p>
<p>Also, this process needed to remain fast, as any delay in authentication could lead to sluggish API performance.</p>
<h3 id="heading-3-external-partners-challenge-with-vernacular-names">3. External Partners' Challenge with Vernacular Names</h3>
<p>Even if we started to accept names in multiple languages, there were our partners who had to accept those names from us. If they don't support multilingual names, the user journey breaks. </p>
<p>One such example was our payment partner. We had to make sure our payments team always received the English name, even when users provided names in other languages.</p>
<p>Also, we wanted to avoid those annoying pop-ups prompting users to enter their names in English whenever we were able. So keeping these problems in mind, we had to build a viable solution.</p>
<h2 id="heading-how-we-solved-these-challenges">How We Solved These Challenges</h2>
<p>While utilizing a third-party transliteration service might have been the easiest route, we opted to develop an in-house solution to control costs and maintain full control.</p>
<p>Considering the API gateway and the requirements of payment partners, it became clear that we needed to transform non-English names into English equivalents. But presenting this English name to the user was counterintuitive – for example entering a name in Hindi, only to see it transformed into English upon logging in seemed contradictory.</p>
<p>To handle this, we developed a dual-naming strategy. The original fields, <code>"firstName"</code> and <code>"lastName"</code> would retain the user-entered names in their entered language. Then we introduced two additional fields, <code>"englishFirstName"</code> and <code>"englishLastName"</code> that were dedicated to storing the English counterparts of these names. These English names could then be shared with the API gateway and our payment partners.</p>
<p>Coming back to the challenge of storing these names efficiently, we anticipated that managing collation indexes as the number of supported languages grew would become unmanageable. Searching would also require specifying the collation for each query, creating an added layer of complexity. So we decided to pivot away from this approach.</p>
<p>Our second approach involved using Unicode. As we aimed to support multiple languages without constraints, we recognized that Unicode could effectively represent characters in nearly every language. Because of this, we decided to store Unicode representations for first and last names in their respective MongoDB fields.</p>
<p>We just added another layer between our DB and the application. It converts these Unicode strings to the original values in the local language while retrieving the names from the DB and converting the local names to their respective English names. Then it stores them in <code>englishFirstName</code> and <code>englishLastName</code> at the time of any insert or update.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699777686379/79ebd2a2-aeda-4f06-8bef-25ca6a7f0a6c.png" alt="Image" width="817" height="277" loading="lazy"></p>
<p>This strategy provided the flexibility we needed to manage multilingual names seamlessly.</p>
<h3 id="heading-key-design-considerations">Key Design Considerations</h3>
<h4 id="heading-1-unicode-optimization">1. Unicode Optimization</h4>
<p>Unicode representation typically comprises a 6 character string, with 'a' represented as 'U+0061' and 'P' as 'U+0050,' commonly commencing with 'U+00.' To conserve space in our database storage, we opted to omit the 'U+' prefix and leading zeros, optimizing our data storage.</p>
<h4 id="heading-2-transliteration-vs-translation">2. Transliteration vs. Translation</h4>
<p>Initially, our aim was transliteration, which requires converting names from one script to another while retaining their phonetic sound.   </p>
<p>For example, the Hindi word <code>"प्रतीक्षा"</code> should be transformed to <code>"Partiksha"</code> and not translated to its English equivalent, <code>"Wait"</code>. </p>
<p>But we recognized that Google Translate primarily focuses on translation, not transliteration. Again we didn't want to go directly for the paid Google transliteration service in our first iteration, so we developed our transliteration service using the free version of Google translate.</p>
<h4 id="heading-3-contextual-enhancements">3. Contextual Enhancements</h4>
<p>Another and the most crucial observation that we had was providing context to the Google Translate API that influenced its responses. </p>
<p>To leverage this, we experimented with adding statement prefixes to non-English names to establish context. After a few hits and trials, we realized that for shorter names (less than 5 characters), a more extensive prefix statement didn't yield desirable results, and Google often returned the same Hindi word. For longer names, we employed lengthier statements, determining the optimal balance through trial and error.</p>
<p>Translating names normally led to their literal translation. For example "प्रतीक्षा" to "Wait" instead of "Pratiksha":</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699726163389/b8b39f05-928b-4801-a2e8-7fc6db3279b1.png" alt="Image" width="715" height="325" loading="lazy"></p>
<p>Adding a prefix statement corrected it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699726207309/3e1dd55c-dfa1-4106-8f7f-abef22b4e880.png" alt="Image" width="746" height="365" loading="lazy"></p>
<p>Alright, now let's see how we actually implemented all this.</p>
<h2 id="heading-initial-code">Initial Code</h2>
<p>After our first iteration, we developed the below code for transliteration. Here we are using the <code>@iamtraction/google-translate</code> library which is a wrapper written over the free Google translate API.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> translate = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@iamtraction/google-translate'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getGoogleTranslateText</span>(<span class="hljs-params">localName</span>) </span>{
  <span class="hljs-comment">/*
    Adding an English sentence before the name so that
    it doesn't get translated to its literal meaning.
    For eg परीक्षा to Exam instead of Pariksha.
  */</span>
  <span class="hljs-keyword">if</span> (localName.length &lt;= <span class="hljs-number">5</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-string">`name: <span class="hljs-subst">${localName}</span>`</span>;
  }
  <span class="hljs-keyword">return</span> <span class="hljs-string">`your name is: <span class="hljs-subst">${localName}</span>`</span>;
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">translateNameToEnglish</span>(<span class="hljs-params">localName</span>) </span>{
  <span class="hljs-keyword">if</span> (localName.match(<span class="hljs-regexp">/^[a-zA-Z ]+$/i</span>)) {
    <span class="hljs-comment">// If the name is already in English just return</span>
    <span class="hljs-keyword">return</span> localName;
  }
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> translate(getGoogleTranslateText(localName), {
      <span class="hljs-attr">to</span>: <span class="hljs-string">'en'</span>,
    });
    <span class="hljs-keyword">const</span> translatedName = res.text.split(<span class="hljs-string">':'</span>)[<span class="hljs-number">1</span>].trim();
    <span class="hljs-keyword">return</span> translatedName;
  } <span class="hljs-keyword">catch</span> (err) {}
  <span class="hljs-comment">// In case of error, Return the Unicode string</span>
  <span class="hljs-keyword">return</span> localName;
}
</code></pre>
<h2 id="heading-beta-release-and-production-challenges">Beta Release and Production Challenges</h2>
<p>Once we built this, we released the feature in beta, and approximately 250 users signed up with non-English names within the first few days.</p>
<p>After simply eyeballing some translated texts, we found that the process of converting the name from its local language to Unicode was working perfectly fine and users were able to view their names properly in the application in the language they preferred.</p>
<p>Still, we identified two issues as far as the process of transliteration to English was concerned:</p>
<ol>
<li><strong>Some names were incorrectly transliterated</strong>. This occurrence could be attributed to our dependence on Google Translate, a general translation service, rather than a specialized transliteration service.</li>
<li><strong>Some names remained unaltered and were not transliterated</strong>. These names were returned in the same language as the original one. This meant adding context with prefix sentences before the translation was causing problems for specific names.</li>
</ol>
<p>This prompted a further investigation which led us to another npm package called "unidecode," which converts Unicode to the original string. While initial tests with unidecode showed accuracy, they also revealed minor spelling discrepancies. In contrast, Google consistently delivered translations with correct spellings. We just needed to find a way of using the best of both worlds.</p>
<p>So we incorporated unidecode into our algorithm as part of our solution.</p>
<h2 id="heading-improved-solution">Improved Solution</h2>
<p>Here's what we came up with:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> translate = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@iamtraction/google-translate'</span>);
<span class="hljs-keyword">const</span> unidecode = <span class="hljs-built_in">require</span>(<span class="hljs-string">'unidecode'</span>);
<span class="hljs-keyword">const</span> { isAlmostEqualStrings } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./levenshtein'</span>);
<span class="hljs-comment">/**
 *
 * @param {String} localName
 * @description Generates text for Google (shorter statement context for short names) based on localName length
 * @returns {String} returns text to translate
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getGoogleTranslateText</span>(<span class="hljs-params">localName</span>) </span>{
  <span class="hljs-comment">/*
    Adding an English sentence before name so that
    it doesn't get translated to its literal meaning.
    For eg परीक्षा to Exam instead of Pariksha.
  */</span>
  <span class="hljs-keyword">if</span> (localName.length &lt;= <span class="hljs-number">5</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-string">`name: <span class="hljs-subst">${localName}</span>`</span>;
  }
  <span class="hljs-keyword">return</span> <span class="hljs-string">`your name is: <span class="hljs-subst">${localName}</span>`</span>;
}

<span class="hljs-comment">/**
 *
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> <span class="hljs-variable">localName</span></span>
 * <span class="hljs-doctag">@description </span>Give an ALMOST transliterated name
 * <span class="hljs-doctag">@returns <span class="hljs-type">{String}</span> </span>returns a converted transliterated name from the local language
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transliterate</span>(<span class="hljs-params">localName, googleTranslatedName</span>) </span>{
  <span class="hljs-keyword">const</span> decodedName = unidecode(localName);
  <span class="hljs-keyword">if</span> (
    decodedName &amp;&amp;
    <span class="hljs-built_in">Array</span>.from(decodedName)[<span class="hljs-number">0</span>]?.toLowerCase() !==
      <span class="hljs-built_in">Array</span>.from(googleTranslatedName)[<span class="hljs-number">0</span>]?.toLowerCase() &amp;&amp;
    !isAlmostEqualStrings(decodedName, googleTranslatedName)
  ) {
    <span class="hljs-keyword">return</span> decodedName;
  }
  <span class="hljs-keyword">return</span> googleTranslatedName;
}

<span class="hljs-comment">/**
 *
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>Input non English string
 * <span class="hljs-doctag">@description </span>translates non-english string to English
 * <span class="hljs-doctag">@returns <span class="hljs-type">{String}</span> </span>returns translated string
 */</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">translateNameToEnglish</span>(<span class="hljs-params">localName</span>) </span>{
  <span class="hljs-keyword">if</span> (!localName || localName.match(<span class="hljs-regexp">/^[a-zA-Z ]+$/i</span>)) {
    <span class="hljs-comment">// If name is already in English just return</span>
    <span class="hljs-keyword">return</span> localName;
  }
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> translate(getGoogleTranslateText(localName), {
      <span class="hljs-attr">to</span>: <span class="hljs-string">'en'</span>,
    });
    <span class="hljs-keyword">const</span> translatedName = res.text.split(<span class="hljs-string">':'</span>)[<span class="hljs-number">1</span>].trim();
    <span class="hljs-keyword">return</span> transliterate(localName, translatedName);
  } <span class="hljs-keyword">catch</span> (err) {}
  <span class="hljs-comment">// In case of error, Return original string</span>
  <span class="hljs-keyword">return</span> localName;
}
</code></pre>
<p>After obtaining the translated name, we feed it into the recently introduced <code>transliterate</code> function. Inside this function, our initial step involves extracting the decoded string using the <code>Unidecode</code> library. But then the crux of the matter arises: wow do we determine which result to prioritize – the decoded string or the translated string?</p>
<p>To tackle this, we implemented <a target="_blank" href="https://en.wikipedia.org/wiki/Levenshtein_distance"><strong>Levenshtein Distance</strong></a>, an algorithm that calculates the similarity between two strings.</p>
<p>Initially, we check if the first character of the decoded name matches the first character of the translated name. If it doesn't match, then for sure the translated name was incorrect, so we return the decoded name, even though it might contain minor spelling discrepancies, it's better to use that than the incorrect translation.</p>
<p>If it does match then we go for the Levenshtein Distance algorithm.</p>
<blockquote>
<p>The Levenshtein distance is a number that tells you how similar two strings are. The higher the number, the more dissimilar the two strings are.</p>
</blockquote>
<p>In the implementation, we have a function <code>isAlmostEqualStrings</code> that generates a value from 0 to 1 and returns true if the value is above a certain threshold. In our case, we set the threshold to 0.8</p>
<p>If the Levenshtein distance indicates a match exceeding 80%, we return the translated name. Otherwise, we return the decoded name. This approach ensures that we prioritize accuracy, offering a reliable result based on the established similarity threshold.</p>
<p>This updated algorithm substantially reduced the above mentioned issues. Even though it's not 100% accurate, it solved our 5% cases very well.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The algorithm we developed was entirely in-house and incurred no costs. While investing in a paid solution might have potentially offered better results, wise engineering decisions taken iteratively and a handful of clever hacks played a vital role in both reducing costs and efficiently resolving the specific problem we had.</p>
<p>The complete code for the above implementation along with the Levenshtein Distance algorithm can be found on <a target="_blank" href="https://github.com/ApoorvTyagi/english-transliterate">GitHub</a> (contributions/corrections are welcome).</p>
<p>With this, we come to the end of the article. My DMs are always open if you want to discuss further on any tech topic or if you've got any questions, suggestions, or feedback in general: </p>
<ul>
<li><a target="_blank" href="https://twitter.com/apoorv__tyagi">Twitter</a></li>
<li><a target="_blank" href="https://www.linkedin.com/in/apoorvtyagi/">LinkedIn</a></li>
<li><a target="_blank" href="https://github.com/apoorvtyagi">GitHub</a></li>
<li><a target="_blank" href="https://apoorvtyagi.tech/">Blog</a></li>
</ul>
<p>Happy learning!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Format Compact Numbers with the JavaScript Internationalization API ]]>
                </title>
                <description>
                    <![CDATA[ Sometimes it can be difficult to fit large numbers into your site or app's layout, especially if you have to display several of them together. As a result, a lot of modern sites and apps use the same format to display large numbers in a compact way. ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/format-compact-numbers-with-javascript/</link>
                <guid isPermaLink="false">66d45edda3a4f04fb2dd2e49</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ internationalization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gerard Hynes ]]>
                </dc:creator>
                <pubDate>Wed, 04 Jan 2023 15:39:17 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/Format-Compact-Numbers.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Sometimes it can be difficult to fit large numbers into your site or app's layout, especially if you have to display several of them together.</p>
<p>As a result, a lot of modern sites and apps use the same format to display large numbers in a compact way. For example, displaying 123,000 as 123K.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/freecodecamp_socials.png" alt="freeCodeCamp's YouTube and Instagram profiles using compact number format." width="600" height="400" loading="lazy"></p>
<p><em>freeCodeCamp's YouTube and Instagram profiles using compact number format.</em></p>
<p>You can do this by writing a custom format function, using a third-party library, or, best of all, using a built-in JavaScript API.</p>
<p>You can of course write your own formatter function (and there are several available on Stack Overflow) but you will end up having to check for a lot of conditions.</p>
<p>As an aside, since ES2021, JavaScript supports using underscores as numeric separators to make large numbers easier to read in your code.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatCompactNumber</span>(<span class="hljs-params">number</span>) </span>{
  <span class="hljs-keyword">if</span> (number &lt; <span class="hljs-number">1000</span>) {
    <span class="hljs-keyword">return</span> number;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1000</span> &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1000</span>).toFixed(<span class="hljs-number">1</span>) + <span class="hljs-string">"K"</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1</span>_000_000 &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1</span>_000_000).toFixed(<span class="hljs-number">1</span>) + <span class="hljs-string">"M"</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1</span>_000_000_000 &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1</span>_000_000_000).toFixed(<span class="hljs-number">1</span>) + <span class="hljs-string">"B"</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1</span>_000_000_000_000 &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000_000_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1</span>_000_000_000_000).toFixed(<span class="hljs-number">1</span>) + <span class="hljs-string">"T"</span>;
  }
}

formatCompactNumber(<span class="hljs-number">12</span>_000);        <span class="hljs-comment">// 12.0K</span>
formatCompactNumber(<span class="hljs-number">2</span>_000_000);     <span class="hljs-comment">// 2.0M</span>
formatCompactNumber(<span class="hljs-number">2</span>_500_000);     <span class="hljs-comment">// 2.5M</span>
formatCompactNumber(<span class="hljs-number">6</span>_000_000_000); <span class="hljs-comment">// 6.0B</span>
formatCompactNumber(<span class="hljs-number">6</span>_900_000_000); <span class="hljs-comment">// 6.9B</span>
</code></pre>
<p>This implementation still leaves a <code>.0</code> after an even thousand, million, billion, or trillion. You could fix this using the <code>replace</code> method and a regular expression.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatCompactNumber</span>(<span class="hljs-params">number</span>) </span>{
  <span class="hljs-keyword">if</span> (number &lt; <span class="hljs-number">1000</span>) {
    <span class="hljs-keyword">return</span> number;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1000</span> &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1000</span>).toFixed(<span class="hljs-number">1</span>).replace(<span class="hljs-regexp">/\.0$/</span>, <span class="hljs-string">""</span>) + <span class="hljs-string">"K"</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1</span>_000_000 &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1</span>_000_000).toFixed(<span class="hljs-number">1</span>).replace(<span class="hljs-regexp">/\.0$/</span>, <span class="hljs-string">""</span>) + <span class="hljs-string">"M"</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1</span>_000_000_000 &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1</span>_000_000_000).toFixed(<span class="hljs-number">1</span>).replace(<span class="hljs-regexp">/\.0$/</span>, <span class="hljs-string">""</span>) + <span class="hljs-string">"B"</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1</span>_000_000_000_000 &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000_000_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1</span>_000_000_000_000).toFixed(<span class="hljs-number">1</span>).replace(<span class="hljs-regexp">/\.0$/</span>, <span class="hljs-string">""</span>) + <span class="hljs-string">"T"</span>;
  }
}
</code></pre>
<p>But what if you need to handle negative numbers? You could add another conditional.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatCompactNumber</span>(<span class="hljs-params">number</span>) </span>{
  <span class="hljs-keyword">if</span> (number &lt; <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"-"</span> + formatCompactNumber(<span class="hljs-number">-1</span> * number);
  }
  <span class="hljs-keyword">if</span> (number &lt; <span class="hljs-number">1000</span>) {
    <span class="hljs-keyword">return</span> number;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1000</span> &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1000</span>).toFixed(<span class="hljs-number">1</span>).replace(<span class="hljs-regexp">/\.0$/</span>, <span class="hljs-string">""</span>) + <span class="hljs-string">"K"</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1</span>_000_000 &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1</span>_000_000).toFixed(<span class="hljs-number">1</span>).replace(<span class="hljs-regexp">/\.0$/</span>, <span class="hljs-string">""</span>) + <span class="hljs-string">"M"</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1</span>_000_000_000 &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1</span>_000_000_000).toFixed(<span class="hljs-number">1</span>).replace(<span class="hljs-regexp">/\.0$/</span>, <span class="hljs-string">""</span>) + <span class="hljs-string">"B"</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (number &gt;= <span class="hljs-number">1</span>_000_000_000_000 &amp;&amp; number &lt; <span class="hljs-number">1</span>_000_000_000_000_000) {
    <span class="hljs-keyword">return</span> (number / <span class="hljs-number">1</span>_000_000_000_000).toFixed(<span class="hljs-number">1</span>).replace(<span class="hljs-regexp">/\.0$/</span>, <span class="hljs-string">""</span>) + <span class="hljs-string">"T"</span>;
  }
}
</code></pre>
<p>As you can probably see by now, this only scratches the surface of things you would need to consider when writing your own function for displaying compact numbers.</p>
<p>There are a <a target="_blank" href="https://www.npmjs.com/search?q=compact%20number">handful of npm packages</a> for formatting numbers compactly. For example, you could install <a target="_blank" href="https://github.com/snewcomer/cldr-compact-number"><code>cldr-compact-number</code></a>, but this would also add 3 kilobytes (or 1.2 kilobytes gzipped) to your JavaScript bundle, adding slightly to your pageload time.</p>
<p>Thankfully, you don't need to use any third-party libraries to format compact numbers, since there is a relatively simple solution that is natively supported in JavaScript.</p>
<h2 id="heading-how-to-use-the-javascript-internationalization-api">How to Use the JavaScript Internationalization API</h2>
<p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl">JavaScript Internationalization API</a> helps you to support different languages and formatting conventions when working in JavaScript. This could be anything from formatting dates and times to knowing whether <em>a</em> or <em>ä</em> comes first when comparing strings.</p>
<p>The API has excellent browser support, with <a target="_blank" href="https://caniuse.com/?search=Internationalization%20API">98% support worldwide</a>. It works by using the <code>Intl</code> object to create a namespace for language-sensitive string comparison, number formatting, and date and time formatting.</p>
<p>As well as <code>Intl.DateTimeFormat</code> for formatting dates and times, and <code>Intl.Collator</code> for doing language-sensitive string comparison, there is <code>Intl.NumberFormat</code>. This lets you format numbers in a language-specific manner.</p>
<p>To get started, create a formatter using the <code>Intl.NumberFormat</code> constructor. Optionally, you can pass it one or more locales and an <code>options</code> object.</p>
<pre><code class="lang-js"><span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.NumberFormat(locales, options);
</code></pre>
<p>A locale is a parameter that defines the user's language, region, or other localization preference. In this case it's an <a target="_blank" href="https://en.wikipedia.org/wiki/IETF_language_tag">IETF language tag</a>, such as "en-US" for US English, "zh-CH" for Mandarin, or "uk" for Ukrainian.</p>
<p>In the browser, you can access the user's locale using <code>navigator.language</code>. If you don't provide a locale, the API will attempt to use the user's locale from their browser.</p>
<p>The <code>options</code> object can contain values to control things like how to format currency, whether to use <code>l</code> or <code>liters</code>, or whether to display <code>+</code> or <code>-</code> for positive or negative values.</p>
<p>Once you have created a formatter, you can call its <code>format</code> method and pass it the number you want to format. It will return the number formatted according to whatever configuration you provided.</p>
<p>For example, you could use the Internationalization API to format financial values according to different currencies:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> number = <span class="hljs-number">12345678.99</span>

<span class="hljs-keyword">const</span> germanCurrencyFormatter = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.NumberFormat(<span class="hljs-string">"de-DE"</span>, { <span class="hljs-attr">style</span>: <span class="hljs-string">"currency"</span>, <span class="hljs-attr">currency</span>: <span class="hljs-string">"EUR"</span> });

<span class="hljs-keyword">const</span> chineseCurrencyFormatter = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.NumberFormat(<span class="hljs-string">"zh-CH"</span>, { <span class="hljs-attr">style</span>: <span class="hljs-string">"currency"</span>, <span class="hljs-attr">currency</span>: <span class="hljs-string">"CNY"</span> });

germanCurrencyFormatter.format(number); <span class="hljs-comment">// 12.345.678,99 €</span>
chineseCurrencyFormatter.format(number); <span class="hljs-comment">// ¥12,345,678.99</span>
</code></pre>
<p>One of the options that is available is <code>notation</code>. This controls the formatting when displaying numbers. The possible values for <code>notation</code> are:</p>
<ul>
<li><p><code>"standard"</code> – plain number formatting according to the conventions of the locale (the default)</p>
</li>
<li><p><code>"scientific"</code> – returns the order of magnitude for a number</p>
</li>
<li><p><code>"engineering"</code> – returns the exponent of ten when the number is divisible by three</p>
</li>
<li><p><code>"compact"</code> – returns a string representing the exponent, such as K for thousands</p>
</li>
</ul>
<p>So, for example, if you format the number 123456789 with the locale set to "en", you will get:</p>
<ul>
<li><p>standard: 123,456,789</p>
</li>
<li><p>scientific: 1.235E8</p>
</li>
<li><p>engineering: 123.457E6</p>
</li>
<li><p>compact: 123M</p>
</li>
</ul>
<p>If you want a formatter to display large numbers in a compact manner, set the locale to your desired locale and set <code>notation</code> to <code>"compact"</code> .</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> formatter = <span class="hljs-built_in">Intl</span>.NumberFormat(<span class="hljs-string">"en"</span>, { <span class="hljs-attr">notation</span>: <span class="hljs-string">"compact"</span> });
</code></pre>
<p>This can be used to create a much shorter formatting function:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatCompactNumber</span>(<span class="hljs-params">number</span>) </span>{
  <span class="hljs-keyword">const</span> formatter = <span class="hljs-built_in">Intl</span>.NumberFormat(<span class="hljs-string">"en"</span>, { <span class="hljs-attr">notation</span>: <span class="hljs-string">"compact"</span> });
  <span class="hljs-keyword">return</span> formatter.format(number);
}

formatCompactNumber(<span class="hljs-number">-57</span>);               <span class="hljs-comment">// -57</span>
formatCompactNumber(<span class="hljs-number">999</span>);               <span class="hljs-comment">// 999</span>
formatCompactNumber(<span class="hljs-number">8</span>_554);             <span class="hljs-comment">// 8.5K</span>
formatCompactNumber(<span class="hljs-number">150</span>_000);           <span class="hljs-comment">// 150K</span>
formatCompactNumber(<span class="hljs-number">3</span>_237_512);         <span class="hljs-comment">// 3.2M</span>
formatCompactNumber(<span class="hljs-number">9</span>_782_716_897);     <span class="hljs-comment">// 9.8B</span>
formatCompactNumber(<span class="hljs-number">7</span>_899_693_036_970); <span class="hljs-comment">// 7.9T</span>
</code></pre>
<p>Now your site or app can display even the largest numbers compactly, making your layout that little bit neater and tidier.</p>
<h3 id="heading-thanks-for-reading">Thanks for reading!</h3>
<p>I hope this quick guide helps you when you're working with large numbers in JavaScript and perhaps encourages you to explore the Internationalization API further.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn to Code in Japanese – The Japanese freeCodeCamp Curriculum is Now Live ]]>
                </title>
                <description>
                    <![CDATA[ The Japanese freeCodeCamp curriculum is now live. In Japan, I have seen many comments, tweets, and blog posts saying "freeCodeCamp looks awesome, but unfortunately, I can't use it because I can't read English." Currently, most information and learnin... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-programming-freecodecamp-japanese-released/</link>
                <guid isPermaLink="false">66b0a97a5e73cf343a5cc04e</guid>
                
                    <category>
                        <![CDATA[ freeCodeCamp.org ]]>
                    </category>
                
                    <category>
                        <![CDATA[ internationalization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ translation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Yoko Matsuda ]]>
                </dc:creator>
                <pubDate>Thu, 10 Feb 2022 21:06:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/02/japanese_certs.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a target="_blank" href="https://www.freecodecamp.org/japanese/learn">The Japanese freeCodeCamp curriculum</a> is now live.</p>
<p>In Japan, I have seen many comments, tweets, and blog posts saying "freeCodeCamp looks awesome, but unfortunately, I can't use it because I can't read English."</p>
<p>Currently, most information and learning resources related to programming are in English. But we want freeCodeCamp to be a place where everyone can learn to code in their native language.</p>
<p>I hope the Japanese freeCodeCamp curriculum will be a new option for Japanese speakers who wish to learn to code.</p>
<h2 id="heading-japanese-translation-contributorssupporters">Japanese translation contributors/supporters</h2>
<p>The translation effort at freeCodeCamp is supported by many contributors.</p>
<p>Also, the Japanese translation of the entire curriculum, in particular, was made possible thanks to the support of Mr. Kimio Momose and Mr. Thomas Telandro of the nonprofit organization Unbiased Learning and the translation company TMJ Japan Ltd.</p>
<p>I appreciate all the help from everyone who contributed to this translation effort.</p>
<p>Below is a list of contributors who have participated in the Japanese translation so far.</p>
<h3 id="heading-japanese-translation-contributors">Japanese translation contributors</h3>
<p>yukari-n-erb<br>mmatsumoto1026<br>miya-start<br>tetz-akaneya<br>furidosu<br>Besshy<br>Lulkafe<br>Emi<br>Riokeh<br>King Tony<br>YAMADA Nobuko<br>徳輝方<br>ariadaioh</p>
<p>TMJ Japan Ltd.<br>tomig7<br>Hiroko.Nagayama<br>and others</p>
<p>Supported by: Unbiased Learning</p>
<p>Also, thanks to everyone who gave us kind encouragement in the contributor chat, forum, Twitter, and so on.</p>
<h2 id="heading-frequently-asked-questions">Frequently Asked Questions</h2>
<p>Below are answers to frequently asked questions about the Japanese curriculum.</p>
<h3 id="heading-im-halfway-through-the-english-curriculum-what-should-i-do">I'm halfway through the English curriculum. What should I do?</h3>
<p>Your progress and certificates are available in all languages. You don't have to go over it in Japanese again. You can just switch to the Japanese curriculum (making sure you're signed in) and continue with it.</p>
<p>You can switch languages at any time from the menu button at the top right corner of the <a target="_blank" href="https://www.freecodecamp.org/japanese/learn">learning platform</a>.</p>
<p>Also, certificates you already earned in the past can be displayed in Japanese if you would like.</p>
<h3 id="heading-i-need-help-with-the-curriculum-where-should-i-ask-questions">I need help with the curriculum. Where should I ask questions?</h3>
<p>The <a target="_blank" href="https://forum.freecodecamp.org/">freeCodeCamp forum</a> is the best place to ask questions about the freeCodeCamp curriculum and projects.</p>
<p>In addition, there is now <a target="_blank" href="https://forum.freecodecamp.org/c/japanese/552">Japanese sub-forum</a> too so you can post questions there. Asking questions in Japanese will help other Japanese campers who face the same problem in the future. </p>
<p>Of course, you can also help the Japanese community grow even more by answering questions from other campers.</p>
<h3 id="heading-i-found-a-translation-error-or-i-want-to-propose-improvements-to-a-translation">I found a translation error or I want to propose improvements to a translation.</h3>
<p>We appreciate it if you could let us know in our <a target="_blank" href="https://discord.gg/PRyKn3Vbay">contributor chat</a> or in the comments on Crowdin, our translation platform.</p>
<p>See <a target="_blank" href="https://forum.freecodecamp.org/t/freecodecamp/484083">this forum post</a> for more details.</p>
<p>It's OK even if you are not 100% sure about your suggestion. Please reach out to us, and we can discuss how we can improve it together.</p>
<h3 id="heading-i-want-to-participate-in-the-translation-effort">I want to participate in the translation effort.</h3>
<p>We welcome new contributors! Feel free to join the <a target="_blank" href="https://discord.gg/PRyKn3Vbay">contributor chat</a> and say "Hi".</p>
<p>freeCodeCamp is an open source project, and we are constantly improving and updating our curriculum. New courses will be added from time to time.</p>
<p>Because of this, there will always be more translation work to do each time we add or change something, so we would appreciate your continued contributions.</p>
<p>See below for the information regarding Japanese translation contribution:</p>
<ul>
<li><a target="_blank" href="https://forum.freecodecamp.org/t/freecodecamp/484083">Forum post: freeCodeCamp contribution information in Japanese</a></li>
<li><a target="_blank" href="https://contribute.freecodecamp.org/#/i18n/japanese/index">Contribution guidelines (Japanese)</a></li>
</ul>
<p>You can also contribute to the source code on GitHub.</p>
<h3 id="heading-i-cant-participate-in-translation-but-i-want-to-help-freecodecamp-and-the-japanese-community">I can't participate in translation, but I want to help freeCodeCamp and the Japanese community.</h3>
<p>There will be more and more things we need help with as the Japanese community grows.</p>
<p>Please reach out to me in the <a target="_blank" href="https://discord.gg/PRyKn3Vbay">contributors chat</a>. Let's talk about how we can help the Japanese freeCodeCamp community together.</p>
<p>If you want to learn more about freeCodeCamp, you can read <a target="_blank" href="https://www.freecodecamp.org/japanese/news/about/">other frequently asked questions here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Hindi freeCodeCamp YouTube Channel is Now Live ]]>
                </title>
                <description>
                    <![CDATA[ Now you can get the same free and ad-free freeCodeCamp learning experience in Hindi. Today I'm excited to announce that we've launched a Hindi-language YouTube channel. The freeCodeCamp Hindi YouTube Channel So far, we already have two full courses o... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/hindi-freecodecamp-youtube-discord/</link>
                <guid isPermaLink="false">66b8d3a02755c964523f053b</guid>
                
                    <category>
                        <![CDATA[ community ]]>
                    </category>
                
                    <category>
                        <![CDATA[ india ]]>
                    </category>
                
                    <category>
                        <![CDATA[ internationalization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Quincy Larson ]]>
                </dc:creator>
                <pubDate>Fri, 14 Aug 2020 19:32:38 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/08/resized-india.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Now you can get the same free and ad-free freeCodeCamp learning experience in Hindi.</p>
<p>Today I'm excited to announce that we've launched a Hindi-language YouTube channel.</p>
<h2 id="heading-the-freecodecamp-hindi-youtube-channel">The freeCodeCamp Hindi YouTube Channel</h2>
<p>So far, we already have two full courses on the channel: Flutter and Python.</p>
<p>And Saturday we will live-stream the first segment of our 5-week-long Data Science with Python course.</p>
<p>You can <a target="_blank" href="https://www.youtube.com/channel/UClHVZQqHLfbcbwFDwPfdB-A">subscribe to the freeCodeCamp Hindi channel here</a>.</p>
<p>This channel is run by prolific YouTuber Pawan Kumar.</p>
<p>Pawan is a Google Developer Expert for technologies like Flutter, Dart, and Firebase. He also runs a tech youtube channel called MTECHVIRAL.</p>
<p>Pawan works as a head of Mobile Engineering at Frontier Wallet. He started his career as an Android dev when he was in college. He likes to experiment with new technologies, and talk about them at events and <a target="_blank" href="https://twitter.com/imthepk">on his Twitter</a>.</p>
<p>Pawan has helped many developers until now around the world in learning new technologies and getting tech jobs.</p>
<blockquote>
<p>"When I was a child, I didn’t have many resources to learn new things. So I decided to help others in learning new awesome things." – Pawan Kumar</p>
</blockquote>
<p>And this lead to him creating the official Hindi-language freeCodeCamp YouTube channel.</p>
<h2 id="heading-the-future-of-freecodecamps-internationalization-efforts">The future of freeCodeCamp's Internationalization Efforts</h2>
<p>Our community is making steady progress in our internationalization efforts. Look for some other big announcements in the coming months.</p>
<p>Our goal is to eventually have a translated curriculum for every major world language, launch YouTube channels in more major languages.</p>
<p>Again, you can <a target="_blank" href="https://www.youtube.com/channel/UClHVZQqHLfbcbwFDwPfdB-A">subscribe to the Hindi YouTube channel here</a>.</p>
<p>Happy coding.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Handle Timezones and Synchronize Your Software with International Customers ]]>
                </title>
                <description>
                    <![CDATA[ By Jérémy Bardon When you develop some software you may not think about timezones at first. Unless you live in a country which has to deal with multiple time zones, such as the United States or Russia. I recently came across an issue involving timezo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/synchronize-your-software-with-international-customers/</link>
                <guid isPermaLink="false">66d45f3cf855545810e9346c</guid>
                
                    <category>
                        <![CDATA[ i18n ]]>
                    </category>
                
                    <category>
                        <![CDATA[ internationalization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ programing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 07 Apr 2020 16:51:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/04/steven-hille-VP25o26erko-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jérémy Bardon</p>
<p>When you develop some software you may not think about timezones at first. Unless you live in a country which has to deal with multiple time zones, such as the United States or Russia.</p>
<p>I recently came across an issue involving timezones. There were some unit tests making assertions about dates that used to work at my office in France but weren't working in Morocco for new members on our team.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/1-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Here is the unit test working in France but not in Morocco</em></p>
<p>‌This was an opportunity for me to learn how to correctly handle dates and times for international software. In this article, I’ll introduce time zone issues and share some rules to follow.</p>
<h2 id="heading-quick-introduction-to-time-zones">Quick introduction to time zones</h2>
<p>As the earth is kind of a sphere, the sun is rising in Japan while it's setting in America. If everyone used global time, let’s say <code>09:00</code> would be sunrise in Japan, but for Americans it would be sunset. Not very handy.</p>
<p>To make sure the time is coordinated with the sun for everyone, it’s necessary to shift from global time according to your location. As a result, the globe gets split into <strong>time zones</strong> and each gets an <strong>offset</strong>. This offset is a number of minutes to add to the global time to get your time zone time. It can be either positive or negative.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/2-2.png" alt="Image" width="600" height="400" loading="lazy">
_Standard world time zones — Illustration by [Wikimedia Commons](https://commons.wikimedia.org/wiki/User:Hellerick" rel="noopener"&gt;Hellerick from &lt;a href="https://en.wikipedia.org/wiki/File:Standard_World_Time<em>Zones.png" rel="noopener)</em></p>
<p>Global time is called <a target="_blank" href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time"><strong>UTC</strong></a><strong>,</strong> it stands for Coordinated Universal Time. You may also heard about <a target="_blank" href="https://en.wikipedia.org/wiki/Greenwich_Mean_Time"><strong>GMT</strong></a> which is a time zone without any offset.</p>
<p>For instance, when it’s <code>10:50</code> at UTC, it’s also  <code>03:50</code> in San Francisco with a <code>-0700</code> offset and <code>18:50</code> in Beijing with a <code>+0800</code> offset. Yet, the shift isn’t only in whole hours: Nepal's offset is <code>+0545</code>. You can check it out on <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">Wikipedia</a>.</p>
<p>In addition of this offset, which comes with the time zone, some countries also shift clocks twice a year. <a target="_blank" href="https://en.wikipedia.org/wiki/Daylight_saving_time"><strong>DST or summer time</strong></a> adds one hour to the time zone offset before summer. Then, the clock is reset to the time zone time in winter. The goal is to make the daytime longer.</p>
<p>The most common way to figure out a time zone is by using the <a target="_blank" href="https://www.iana.org/time-zones">IANA Time Zone Database</a>. You end up with a string such as <code>Europe/Paris</code> following the Area/City pattern. Besides, Microsoft maintains its own <a target="_blank" href="https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values">Microsoft Time Zone Database</a> used on its operating systems. But this can <a target="_blank" href="https://devblogs.microsoft.com/dotnet/cross-platform-time-zones-with-net-core/">cause issues</a> when running cross-platform .NET Core apps. </p>
<p>IANA is still the go-to. The Microsoft database isn't updated often, it contains less history, fairly curious time zone names (eg: <code>Romantic Standard Time</code>) and is error prone. For example, try to not mix up <code>Arab</code> , <code>Arabic</code> and <code>Arabian Standard Time</code>. For more details on each database and their differences, <a target="_blank" href="https://codeofmatt.com/what-is-a-time-zone/">check out this article</a>.</p>
<p>One last thing: there are plenty of ways to write a date. Fortunately, the <a target="_blank" href="https://en.wikipedia.org/wiki/ISO_8601"><strong>ISO 8601 specification</strong></a> sets a common rule for date formatting.</p>
<pre><code>November <span class="hljs-number">11</span>, <span class="hljs-number">2018</span> at <span class="hljs-number">12</span>:<span class="hljs-number">51</span>:<span class="hljs-number">43</span> AM (<span class="hljs-keyword">in</span> a time zone at UTC+<span class="hljs-number">00</span>:<span class="hljs-number">00</span>)
<span class="hljs-number">2018</span><span class="hljs-number">-11</span><span class="hljs-number">-05</span>T12:<span class="hljs-number">51</span>:<span class="hljs-number">43</span>Z &lt;- Z stands <span class="hljs-keyword">for</span> UTC

November <span class="hljs-number">11</span>, <span class="hljs-number">2018</span> at <span class="hljs-number">12</span>:<span class="hljs-number">51</span>:<span class="hljs-number">43</span> AM (<span class="hljs-keyword">in</span> a time zone at UTC +<span class="hljs-number">07</span>:<span class="hljs-number">30</span>)
<span class="hljs-number">2018</span><span class="hljs-number">-11</span><span class="hljs-number">-05</span>T12:<span class="hljs-number">51</span>:<span class="hljs-number">43</span>+<span class="hljs-number">0730</span>
</code></pre><h2 id="heading-how-computers-handle-dates">How computers handle dates</h2>
<p>Computers are only able to perform operations using numbers. This means that <code>2020-08-01 +1</code>  is not equal to <code>2020-08-02</code> and can’t be handled.</p>
<p>In order to work with dates more easily, we can represent dates as numbers. This is what <strong>timestamps</strong> are all about. It’s the number of milliseconds elapsed from a pre-defined date (or <strong>epoch</strong>) to the specified date.</p>
<p>Great, let’s choose an epoch then! Actually, the common epoch has already been set and its value is <strong>January 1, 1970 (midnight UTC)</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/3-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To make sure you understood, run the previous snippet in your browser. What? You didn’t get the same result?</p>
<p>Ok, I cheated a bit to get this result… I should get <code>Thu Jan 01 1970 01:00 GMT+0100</code> because my computer time zone is set to Europe/Paris.</p>
<p>Actually, this moment with a zero timestamp is midnight in Greenwich, but also <code>05:45</code> in Mumbai and even <code>1969-12-31T16:30</code> in San Francisco when you consider their time zone’s offset.</p>
<blockquote>
<p>Rule #1 : Timestamps are only for saving, not for displaying. It's considered on UTC because it doesn’t include any offset or time zone.</p>
</blockquote>
<p>You didn’t get the “right” date before because JavaScript uses your local time zone to show the most accurate date/time to you.</p>
<p>Now, try the following snippet. I’m sure you’ll get the same result as I did:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/4-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Yes, the zero timestamp is <code>1970-01-01T00:00:00</code> <strong>at UTC</strong> for everyone around the globe. Still, it’s not true if you choose another time zone.</p>
<p>To sum up, <code>toString</code> shows the date using your local time zone while <code>toUTCString</code> is based on UTC. Also don’t be fooled by <code>toISOString</code> which is the same as <code>toUTCString</code> but outputs the ISO 8601 format (its name should be <code>toUTCISOString</code>).</p>
<p>I recommend the <a target="_blank" href="http://man7.org/linux/man-pages/man1/date.1.html">date command</a> to convert a second timestamp (not milliseconds) into a readable string. Using this command with the UTC option makes sure it doesn't take your computer/browser's time zone into account. </p>
<pre><code class="lang-bash"><span class="hljs-comment"># Linux</span>
$ date -d @1586159897 -u 
Mon Apr  6 07:58:17 UTC 2020

<span class="hljs-comment"># For Osx users</span>
$ date -r 1586159897 -u
</code></pre>
<h2 id="heading-lets-fix-our-unit-test">Let’s fix our unit test</h2>
<p>The problem I encountered with time zones was in my unit tests. Take the time to read it and understand what it’s supposed to assert:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/6-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In this test, the goal is to check that <code>setHours</code> sets the date’s hours and minutes to zero (midnight). I first choose a random timestamp which isn’t at midnight. Then compare the result with the timestamp for the same day at midnight.</p>
<p>Actually it’s working – but only if your time zone offset is <code>+0200</code> (including DST) at this moment. For instance, it’s not working for Africa/Casablanca ( <code>+0100</code> including DST). Let’s see how those timestamps are printed:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/7-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>That’s it, the UTC date for both results isn't the same. It also means the resulting timestamps aren’t the same either.</p>
<p>As you can see, the offset for Paris is <code>+0200</code> and <code>+0100</code> for Casablanca. But both display midnight with <code>toString</code>. This means that the <code>setHours</code> function uses your computer time zone to perform the operation. And <code>toString</code> displays the date using your time zone.</p>
<p>This is not the only issue with this test: what if you run this test in San Fransisco? Right, the day would be <code>2020-07-31</code> for both dates because of the <code>-0700</code> offset.</p>
<p>The safest way to make this test reliable and work all around the world is to use a date in your local time zone. You’ll not use timestamps to set initial dates anymore.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/8-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We can enhance the previous rule about timestamps:</p>
<blockquote>
<p>Rule #2 : String dates are suitable for display using the user's time zone and computations. They aren’t on UTC but generally include an offset.</p>
</blockquote>
<h2 id="heading-keep-it-on-date-on-the-server-side">Keep it on date on the server side</h2>
<p>The rule about timestamps still applies on the server side. However the second rule about using string dates can’t be used.</p>
<p>Indeed, in some case with technologies such as PHP, Java, and Rails the pages are rendered on server side (<a target="_blank" href="https://www.quora.com/What-is-the-difference-between-client-side-and-server-side-rendering-Why-is-server-side-rendering-required-for-React-and-Redux">SSR</a>). This means all the HTML is generated by the server and it has no idea about the client's time zone. Think about the server – it’s nothing more than a computer on the globe. It also has its own time zone but it’s not necessarily the same as the client's time zone.</p>
<blockquote>
<p>Rule #3 : Servers might either know the client's time zone or send a date on UTC. The server's time zone doesn’t matter.</p>
</blockquote>
<p>The new Java 8 Date/Time is considered one of the most understandable and clear APIs that helps you deal with date. I’m not going to explain how it works here but let’s review some interesting points.</p>
<p><code>LocalDateTime</code>, <code>OffsetDateTime</code> and <code>ZonedDateTime</code> are the 3 classes provided to compute and display date and time. No more <code>Date</code> or <code>DateTime</code> which mix up displaying the local date and UTC date.</p>
<p>The following examples are extracted from <a target="_blank" href="https://yawk.at/java.time/">this awesome article</a> (written by Jonas Konrad) which describes the Java 8 Date/Time API with a bunch of examples. By the way, many thanks to him, he kindly let me quote its pieces of code!</p>
<p>Let’s look at the differences between the 3 classes:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/9-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>There is a small but important difference between <code>OffsetDateTime</code> and <code>ZonedDateTime</code>, did you notice it?</p>
<p>As its name says, <code>OffsetDateTime</code> is only aware of an offset between the local date and UTC. This means that it handles DST differently from a date which is attached to a time zone.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/10-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The example with a time zone seems to be the right behavior. Actually, both are correct because adding 1 day can either mean:</p>
<ul>
<li>Add 1 day and keep the same hour (handles DST with <code>ZonedDateTime</code>)</li>
<li>Add 24 hours to the current date (with <code>OffsetDateTime</code>).</li>
</ul>
<p>Remember Rule #1 about timestamps? You should only use a UTC timestamp for saving. The Java API provides an <code>Instant</code> class which is a timestamp you can get from any of the three classes used for displaying the date.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/11-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>In this article, you've learned that timestamps are for saving (Rule #1) and string dates are for displaying (Rule #2). Did you notice that the number of seconds from the epoch is quite a big number?</p>
<p>That’s why after the <a target="_blank" href="https://en.wikipedia.org/wiki/Year_2000_problem">Unix Millennium Bug (Y2K) problem</a> comes the <a target="_blank" href="https://en.wikipedia.org/wiki/Year_2038_problem">Y2K38 problem</a> which stands for the year 2038. At <code>2038-01-19T03:14:07Z</code> the timestamp (in seconds) will reach its maximum for 32-bit signed integers <code>2,147,483,647</code> . It will then turn into a negative number after adding one more second.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/5-2.jpg" alt="Image" width="600" height="400" loading="lazy">
_This sign indicates January 1900 instead of January 2000 — Picture from [Wikipedia Commons](https://en.wikipedia.org/wiki/File:Bug_de_l%27an<em>2000.jpg" rel="noopener)</em></p>
<p>‌On forums, people say they don’t care because their software won't be used for 20 years without re-writing. Well, that might be true but let’s still think about some solutions (with MySQL):</p>
<ul>
<li>Update <code>TIMESTAMP</code> type to 64-bit signed integers</li>
<li>Save UTC dates in <code>DATETIME</code> columns instead of <code>TIMESTAMP</code></li>
</ul>
<p>Both solutions have their advantages and drawbacks. The first one seems like a hack which reports the issue later. Yet, it fixes the issue for an almost infinite amount of time (billions of years). Your software will be deprecated and not used anymore when the problem occurs again. </p>
<p>The second solution also works for a very long time (up to <code>9999-12-31T23:59:59Z</code>).</p>
<p>Using <code>TIMESTAMP</code> is recommended for logs, while <code>DATETIME</code> is better for other needs. Remember a timestamp can’t store a date prior to <code>1970-01-01T00:00:00Z</code> and not after <code>2038-01-19T03:14:07Z</code>. This means you should use <code>DATETIME</code> to save dates far in the past and future.</p>
<p>Besides, in MySQL <code>TIMESTAMP</code>s are stored at UTC but displayed according to a specified time zone (and converted to UTC before saving). This mechanism comes in handy when you need to get a local date and doesn’t exist with <code>DATETIME</code>.</p>
<p>A last word about <a target="_blank" href="https://momentjs.com/">moment.js</a>, a popular library to deal with dates. I first experimented an issue and wanted to warn you about it:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/12sq-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Both <code>console.log</code>s will output <code>2020-08-02 00:00</code>. If you’re used to functional programming, you expect <code>hours</code> and <code>minutes</code> to return a new moment object because they are <a target="_blank" href="https://en.wikipedia.org/wiki/Pure_function">pure functions</a>. It’s not the case – they modify the input date and return it for easy chaining.</p>
<p>Thanks for reading up to the end. I hope this experience of mine has been useful to you. By the way, I’m not very confident about the choice between <code>TIMESTAMP</code> and <code>DATETIME</code>, so don’t hesitate to share your experience!</p>
<p><strong>If you found this article useful, please share it on social media to help others find it and to show your support!</strong> ?</p>
<p><strong>Don’t forget to check my <a target="_blank" href="https://www.freecodecamp.org/news/author/jbardon/">author page</a> for upcoming articles</strong> ?</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Complete Guide to Rails Internationalization (i18n) ]]>
                </title>
                <description>
                    <![CDATA[ By Anastasia In this article you are going to learn how to translate your Rails application into multiple languages, work with translations, localize datetime, and switch locales. We are going to see all these aspects in action by creating a sample a... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/lokalise-co-blog-bf840492f34f/</link>
                <guid isPermaLink="false">66c35aaee9895571912a0cdb</guid>
                
                    <category>
                        <![CDATA[ internationalization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Ruby on Rails ]]>
                    </category>
                
                    <category>
                        <![CDATA[ startup ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 23 Aug 2018 11:17:13 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*oNjw5BDpdjHzMGKwIyWmQA.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Anastasia</p>
<p>In this article you are going to learn how to translate your <a target="_blank" href="https://rubyonrails.org/">Rails application</a> into multiple languages, work with translations, localize datetime, and switch locales. We are going to see all these aspects in action by creating a sample application and enhancing it step by step. By the end of the article you will have all the necessary knowledge to start implementing these concepts in real projects.</p>
<h3 id="heading-preparing-your-rails-app">Preparing your Rails App</h3>
<p>So, as I already said, we are going to see all the concepts in action, therefore let’s create a new Rails application by running:</p>
<pre><code>rails <span class="hljs-keyword">new</span> SampleApp
</code></pre><p>For this tutorial I am using <em>Rails 5.2.1</em>, but most of the described concepts apply to older versions as well.</p>
<p>Now let’s generate a <code>StaticPagesController</code> which is going to have an <code>index</code> action (our main page):</p>
<pre><code>rails g controller StaticPages index
</code></pre><p>Tweak the <code>views/static_pages/index.html.erb</code> view by adding some sample content:</p>
<pre><code>&lt;h1&gt;Welcome!&lt;<span class="hljs-regexp">/h1&gt; &lt;p&gt;We provide some fancy services to &lt;em&gt;good people&lt;/</span>em&gt;.&lt;/p&gt;
</code></pre><p>Also I would like to add a Feedback page where our users will be able to share their opinion (hopefully, a positive one) about the company. Each feedback will have an author’s name and the actual message:</p>
<pre><code>rails g scaffold Feedback author message
</code></pre><p>We will be interested only in two actions: <code>new</code> (which is going to render the form to post a review and also list all the existing reviews) and <code>create</code> (to actually validate and persist the reviews). Of course, ideally the reviews should be pre-moderated but we won’t bother with this today.</p>
<p>Tweak the <code>new</code> action to fetch all the reviews from the database and order them by creation date:</p>
<pre><code># feedbacks_controller.rb # ... def <span class="hljs-keyword">new</span> @feedback = Feedback.new @feedbacks = Feedback.order created_at: :desc end
</code></pre><p>Also I would like to redirect the user to the Feedback page when the form is processed and the new record is persisted:</p>
<pre><code># feedbacks_controller.rb # ... def create @feedback = Feedback.new(feedback_params) <span class="hljs-keyword">if</span> @feedback.save redirect_to new_feedback_path <span class="hljs-keyword">else</span> @feedbacks = Feedback.order created_at: :desc render :<span class="hljs-keyword">new</span> end end
</code></pre><p>Render the feedbacks collection on the <code>new</code> page:</p>
<pre><code>&lt;!-- views/feedbacks/<span class="hljs-keyword">new</span>.html.erb --&gt; &lt;!-- other code goes here... --&gt; &lt;%= render @feedbacks %&gt;
</code></pre><p>Lastly, create a partial for an individual feedback:</p>
<pre><code>&lt;!-- views/feedbacks/_feedback.html.erb --&gt; &lt;article&gt; &lt;em&gt; &lt;%= tag.time feedback.created_at, datetime: feedback.created_at %&gt;&lt;br&gt; Posted by &lt;%= feedback.author %&gt; &lt;/em&gt; &lt;p&gt; &lt;%= feedback.message %&gt; &lt;/p&gt; &lt;hr&gt; &lt;/article&gt;
</code></pre><p>Take care of the routes:</p>
<pre><code># config/routes.rb Rails.application.routes.draw <span class="hljs-keyword">do</span> resources :feedbacks root <span class="hljs-string">'static_pages#index'</span> end
</code></pre><p>Lastly add a global menu to the layout:</p>
<pre><code>&lt;!-- views/layouts/application.html.erb --&gt; &lt;!-- other code goes here... --&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">link_to</span> '<span class="hljs-attr">Home</span>', <span class="hljs-attr">root_path</span> %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">link_to</span> '<span class="hljs-attr">Feedback</span>', <span class="hljs-attr">new_feedback_path</span> %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
</code></pre><p>Now run migrations and boot up the server:</p>
<pre><code>rails db:migrate rails s
</code></pre><p>Navigate to the <code>http://locahost:3000</code> and make sure that everything is fine. Now that we have something to work with, let’s proceed to the main part and localize our application.</p>
<h3 id="heading-a-bit-of-configuration">A Bit of Configuration</h3>
<p>Before performing translations, we need to decide which languages will be supported. You can choose any, but I will stick with Russian and English, with the latter set as a default. Reflect this inside the <code>config/application.rb</code> file:</p>
<pre><code># ... config.i18n.available_locales = [:en, :ru] config.i18n.default_locale = :en
</code></pre><p>Also hook up a <a target="_blank" href="https://github.com/svenfuchs/rails-i18n">rails-i18n gem</a> that has locale data for <a target="_blank" href="https://github.com/svenfuchs/rails-i18n#available-locales">different languages</a>. For example, it has translated names of the months, pluralization rules, and other useful stuff.</p>
<pre><code># Gemfile # ... gem <span class="hljs-string">'rails-i18n'</span>
</code></pre><p>Just install this gem and you are good to go:</p>
<pre><code>bundle install
</code></pre><h3 id="heading-storing-translations">Storing Translations</h3>
<p>Now that everything is configured, let’s take care of the home page and translate the text there.</p>
<p>The simplest way to do this is by utilizing <a target="_blank" href="https://guides.rubyonrails.org/i18n.html#localized-views">localized views</a>. All you need to do is create views named <code>index.LANG_CODE.html.erb</code>, where the <code>LANG_CODE</code> corresponds to one of the supported languages. So, in this demo we should created two views: <code>index.en.html.erb</code> and <code>index.ru.html.erb</code>. Inside just place content for English and Russian version of the site, and Rails will automatically pick the proper view based on the currently set locale. Convenient, eh?</p>
<p>This approach, however, is not always feasible. Another way would be to store your translated strings in a separate file, and render a proper version of the string based on the chosen language. By default, Rails employs <a target="_blank" href="https://en.wikipedia.org/wiki/YAML">YAML files</a> that has to be stored under the <code>config/locales</code> directory. Translations for different languages are stored in separate files, and each file is named after this language.</p>
<p>Open the <code>config/locales</code> folder and note that there is already an <code>en.yml</code> file inside which has some sample data:</p>
<pre><code>en: hello: <span class="hljs-string">"Hello world"</span>
</code></pre><p>So, <code>en</code> is a top-level key representing the language that these translations are for. Next, there is a nested key-value pair, where <code>hello</code> is the <em>translation key</em>, and <code>Hello world</code> is the actual translated string. Let’s replace this pair with the following content:</p>
<pre><code>en: welcome: <span class="hljs-string">"Welcome!"</span>
</code></pre><p>This is just a welcoming message from our homepage. Now create a <code>ru.yml</code> file in the <code>config/locales</code> folder and provide translated welcoming message there as well:</p>
<pre><code>ru: welcome: <span class="hljs-string">"Добро пожаловать!"</span>
</code></pre><p>We have just created translation for our first string, which is really great.</p>
<h3 id="heading-performing-simple-translations">Performing Simple Translations</h3>
<p>Now that we have populated the YAML files with some data, let’s see how to employ the translated strings in the views. Actually, it is as simple as utilizing the <code>translate</code> method which is aliased as <code>t</code>. This method has one required argument: the name of the translation key:</p>
<pre><code>&lt;!-- views/static_pages/index.html.erb --&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">t</span> '<span class="hljs-attr">welcome</span>' %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre><p>When the page is requested, Rails looks up the string that corresponds to the provided key, and renders it. If the requested translation cannot be found, Rails will just render the key on the screen (and turn it to a more human-readable form).</p>
<p>Translation keys can be named anything you like (well, nearly anything) but of course it is advised to give them some meaningful names so that you can understand what text they correspond to.</p>
<p>Let’s take care of the second message:</p>
<pre><code>en: welcome: <span class="hljs-string">"Welcome!"</span> services_html: <span class="hljs-string">"We provide some fancy services to &lt;em&gt;good people&lt;/em&gt;."</span>
</code></pre><pre><code>ru: welcome: <span class="hljs-string">"Добро пожаловать!"</span> services_html: <span class="hljs-string">"Мы предоставляем различные услуги для &lt;em&gt;хороших людей&lt;/em&gt;."</span>
</code></pre><p>Why do we need this <code>_html</code> postfix? Well, as you can see our string has some HTML markup, and by default Rails will render the <code>em</code> tag as plain text. As long as we don’t want this to happen, we mark the string as a “safe HTML”.</p>
<p>Now just use the <code>t</code> method again:</p>
<pre><code>&lt;!-- views/static_pages/index.html.erb --&gt; &lt;!-- ... ---&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">t</span> '<span class="hljs-attr">services_html</span>' %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
</code></pre><h3 id="heading-more-on-translation-keys">More On Translation Keys</h3>
<p>Our homepage is now localized, but let’s stop for a moment and think about what we have done. All in all, our translation keys have meaningful names, but what happens if we are going to have, say, 500 messages in the app? This number is actually not that big, and large websites may have thousands of translations.</p>
<p>If all our key-values pairs are stored right under the <code>en</code> (or <code>ru</code>) key without any further grouping, this leads to two main problems:</p>
<ul>
<li>We need to make sure that all the keys have unique names. This becomes increasingly complex as your application grows.</li>
<li>It is hard to locate all related translations (for example, translations for a single page or feature).</li>
</ul>
<p>Therefore, it would be a good idea to further group your translations under arbitrary keys. For example, you may do something like this:</p>
<pre><code>en: main_page: header: welcome: <span class="hljs-string">"Welcoming message goes here"</span>
</code></pre><p>The level of nesting is not limited (but you should be reasonable about it), and the keys in different groups may have identical names.</p>
<p>It is beneficial, however, to follow the folder structure of your views (in a moment we will see why). Therefore, tweak the YAML files in the following way:</p>
<pre><code>en: static_pages: index: welcome: <span class="hljs-string">"Welcome!"</span> services_html: <span class="hljs-string">"We provide some fancy services to &lt;em&gt;good people&lt;/em&gt;."</span>
</code></pre><pre><code>ru: static_pages: index: welcome: <span class="hljs-string">"Добро пожаловать!"</span> services_html: <span class="hljs-string">"Мы предоставляем различные услуги для &lt;em&gt;хороших людей&lt;/em&gt;."</span>
</code></pre><p>Generally, you need to provide full path to the translation key when referencing it in the <code>t</code> method:</p>
<pre><code>&lt;!-- views/static_pages/index.html.erb --&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">t</span> '<span class="hljs-attr">static_pages.index.welcome</span>' %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">t</span> '<span class="hljs-attr">static_pages.index.services_html</span>' %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
</code></pre><p>However, there is also a “lazy” lookup available. If you perform translation in a view or controller, and the translation keys are namespaced properly following the folder structure, you may omit the namespaces all together. This way, the above code turns to:</p>
<pre><code>&lt;!-- views/static_pages/index.html.erb --&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">t</span> '<span class="hljs-attr">.welcome</span>' %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">t</span> '<span class="hljs-attr">.services_html</span>' %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
</code></pre><p>Note that the leading dot is required here.</p>
<p>Let’s also translate our global menu and namespace the translations properly:</p>
<pre><code>en: <span class="hljs-built_in">global</span>: menu: home: <span class="hljs-string">"Home"</span> feedback: <span class="hljs-string">"Feedback"</span>
</code></pre><pre><code>ru: <span class="hljs-built_in">global</span>: menu: home: <span class="hljs-string">"Главная"</span> feedback: <span class="hljs-string">"Отзывы"</span>
</code></pre><p>In this case we can’t take advantage of the lazy lookup, so provide the full path:</p>
<pre><code>&lt;!-- views/layouts/application.html.erb --&gt; &lt;!-- ... ---&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">link_to</span> <span class="hljs-attr">t</span>('<span class="hljs-attr">global.menu.home</span>'), <span class="hljs-attr">root_path</span> %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">link_to</span> <span class="hljs-attr">t</span>('<span class="hljs-attr">global.menu.feedback</span>'), <span class="hljs-attr">new_feedback_path</span> %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
</code></pre><h3 id="heading-translating-models">Translating Models</h3>
<p>Now let’s proceed to the Feedback page and take care of the form. The first thing we need to translate is the labels for the inputs. It appears that Rails allows us to provide translations for the model attributes, and they will be automatically utilized as needed. All you need to do is namespace these translations properly:</p>
<pre><code>en: activerecord: attributes: feedback: author: <span class="hljs-string">"Your name"</span> message: <span class="hljs-string">"Message"</span>
</code></pre><pre><code>ru: activerecord: attributes: feedback: author: <span class="hljs-string">"Ваше имя"</span> message: <span class="hljs-string">"Сообщение"</span>
</code></pre><p>The labels will now be translated automatically. As for the “submit” button, you can provide translation for model itself by saying:</p>
<pre><code>en: activerecord: models: feedback: <span class="hljs-string">"Feedback"</span>
</code></pre><p>But honestly I don’t like the “Create Feedback” text on this button, so let’s stick with a generic “Submit” word:</p>
<pre><code>en: <span class="hljs-built_in">global</span>: forms: submit: Submit
</code></pre><pre><code>ru: <span class="hljs-built_in">global</span>: forms: submit: Отправить
</code></pre><p>Now utilize this translation:</p>
<pre><code>&lt;!-- views/feedbacks/_form.html.erb --&gt; &lt;!-- ... ---&gt; &lt;%= form.submit t(<span class="hljs-string">'global.forms.submit'</span>) %&gt;
</code></pre><h3 id="heading-error-messages">Error Messages</h3>
<p>Probably we do not want the visitors to post empty feedback messages, therefore provide some simple validation rules:</p>
<pre><code># models/feedback.rb # ... validates :author, <span class="hljs-attr">presence</span>: <span class="hljs-literal">true</span> validates :message, <span class="hljs-attr">presence</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">length</span>: {<span class="hljs-attr">minimum</span>: <span class="hljs-number">5</span>}
</code></pre><p>But what about the corresponding error messages? How do we translate them? It appears that we don’t need to do anything at all as rails-i18n gem already knows how to localize common errors. For example, <a target="_blank" href="https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ru.yml#L133">this file</a> contains error messages for the Russian locale. If you actually <em>do</em> want to tweak the default error messages, then <a target="_blank" href="https://guides.rubyonrails.org/i18n.html#error-message-scopes">check the official doc</a> that explains how to achieve that.</p>
<p>One problem with the form, however, is that the error messages subtitle (the one that says “<em>N</em> errors prohibited this feedback from being saved:”) is not translated. Let’s fix it now and also talk about pluralization.</p>
<h3 id="heading-pluralization-rules">Pluralization Rules</h3>
<p>As long as potentially there can be one or more error messages, the “error” word in the subtitle should be pluralized accordingly. In English words are usually pluralized by adding an “s” postfix, but for Russian the rules are a bit more complex.</p>
<p>I already mentioned that the rails-i18n gem contains pluralization rules for all the supported languages, so we don’t need to bother writing them from scratch. All you need to do is provide the proper key for each possible case. So, for English there are only two possible cases: one error or many errors (of course, there can be no errors, but in this case the message won’t be displayed at all).</p>
<pre><code>en: <span class="hljs-built_in">global</span>: forms: submit: Submit messages: errors: one: <span class="hljs-string">"One error prohibited this feedback from being saved"</span> other: <span class="hljs-string">"%{count} errors prohibited this feedback from being saved"</span>
</code></pre><p>The <code>%{count}</code> here is interpolation – we take the passed value and place it right into the string.</p>
<p>Now take care of the Russian locale which has more possible cases:</p>
<pre><code>ru: <span class="hljs-built_in">global</span>: forms: submit: Отправить messages: errors: one: <span class="hljs-string">"Не удалось сохранить отзыв! Найдена одна ошибка:"</span> few: <span class="hljs-string">"Не удалось сохранить отзыв! Найдены %{count} ошибки:"</span> many: <span class="hljs-string">"Не удалось сохранить отзыв! Найдено %{count} ошибок:"</span> other: <span class="hljs-string">"Не удалось сохранить отзыв! Найдена %{count} ошибка:"</span>
</code></pre><p>Having this in place, just utilize these translation:</p>
<pre><code>&lt;!-- views/feedbacks/_form.html.erb --&gt; &lt;!-- ... ---&gt; &lt;%= form_with(model: feedback, <span class="hljs-attr">local</span>: <span class="hljs-literal">true</span>) <span class="hljs-keyword">do</span> |form| %&gt; &lt;% <span class="hljs-keyword">if</span> feedback.errors.any? %&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"error_explanation"</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">t</span> '<span class="hljs-attr">global.forms.messages.errors</span>', <span class="hljs-attr">count:</span> <span class="hljs-attr">feedback.errors.count</span> %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span> <span class="hljs-comment">&lt;!-- errors... --&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span> &lt;/div&gt; &lt;% end %&gt; &lt;!-- form fields --&gt; &lt;% end %&gt;
</code></pre><p>Note that in this case we pass the translation key as well as the value for the <code>count</code> variable. Rails will take the proper translation variant based on this number. Also the value of the <code>count</code> will be interpolated into each <code>%{count}</code> placeholder.</p>
<p>Our next stop is the <code>_feedback.html.erb</code> partial. Here we need to localize two strings: “Posted by…” and datetime (<code>created_at</code> field). As for “Posted by…”, let’s just utilize the interpolation again:</p>
<pre><code>en: <span class="hljs-built_in">global</span>: feedback: posted_by: <span class="hljs-string">"Posted by %{author}"</span>
</code></pre><pre><code>ru: <span class="hljs-built_in">global</span>: feedback: posted_by: <span class="hljs-string">"Автор: %{author}"</span>
</code></pre><pre><code>&lt;!-- views/feedbacks/_feedback.html.erb --&gt; &lt;article&gt; &lt;em&gt; &lt;%= tag.time feedback.created_at, datetime: feedback.created_at %&gt;&lt;br&gt; &lt;%= t 'global.feedback.posted_by', author: feedback.author %&gt; &lt;/em&gt; &lt;p&gt; &lt;%= feedback.message %&gt; &lt;/p&gt; &lt;hr&gt; &lt;/article&gt;
</code></pre><p>But what about the <code>created_at</code>? To take care of it, we can take advantage of the <code>localize</code> method aliased as just <code>l</code>. It is very similar to the Ruby’s <code>strftime</code>, but produces a translated version of the date (specifically, the months’ names are translated properly). Let’s use a <a target="_blank" href="https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ru.yml#L265">predefined format</a> called <code>:long</code>:</p>
<pre><code>&lt;!-- views/feedbacks/_feedback.html.erb --&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">em</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">tag.time</span> <span class="hljs-attr">l</span>(<span class="hljs-attr">feedback.created_at</span>, <span class="hljs-attr">format:</span> <span class="hljs-attr">:long</span>), <span class="hljs-attr">datetime:</span> <span class="hljs-attr">feedback.created_at</span> %&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">br</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">t</span> '<span class="hljs-attr">global.feedback.posted_by</span>', <span class="hljs-attr">author:</span> <span class="hljs-attr">feedback.author</span> %&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</span> <span class="hljs-comment">&lt;!--... --&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span></span>
</code></pre><p>If you would like to add your very own format, it is possible too <a target="_blank" href="https://guides.rubyonrails.org/i18n.html#adding-date-time-formats">as explained here</a>.</p>
<h3 id="heading-switching-between-locales">Switching Between Locales</h3>
<p>So, our app is now fully translated… but there is a very minor thing: we cannot change the locale! Come to think of it, this is quite a major issue really, so let’s fix it now.</p>
<p>There are a <a target="_blank" href="https://guides.rubyonrails.org/i18n.html#managing-the-locale-across-requests">handful of possible ways</a> of setting and persisting the chosen locale across the requests. We are going to stick with the following approach:</p>
<ul>
<li>Our URLs will have an optional <code>:locale</code> parameter, and so they’ll look like <code>[http://localhost:3000/en/some_page](http://localhost:3000/en/some_page)</code></li>
<li>If this parameter is set and the specified locale is supported, we translate the app into the corresponding language</li>
<li>If this parameter is not set or the locale is not supported, set a default locale</li>
</ul>
<p>Sounds straightforward? Then let’s dive into the code!</p>
<p>First of all, tweak the <code>routes.rb</code> by including a <code>scope</code>:</p>
<pre><code># config/routes.rb scope <span class="hljs-string">"(:locale)"</span>, <span class="hljs-attr">locale</span>: <span class="hljs-regexp">/#{I18n.available_locales.join("|")}/</span> <span class="hljs-keyword">do</span> # your routes here... end
</code></pre><p>Here we are validating the specified parameter using a RegEx to make sure that the locale is supported (note that the anchor characters like <code>\A</code> are not permitted here).</p>
<p>Next, set a <code>before_action</code> in the <code>ApplicationController</code> to check and set the locale on each request:</p>
<pre><code># application_controller.rb # ... before_action :set_locale private def set_locale I18n.locale = extract_locale || I18n.default_locale end def extract_locale parsed_locale = params[:locale] I18n.available_locales.map(&amp;:to_s).include?(parsed_locale) ? parsed_locale : nil end
</code></pre><p>Also, in order to persist the chosen locale across the requests, set the <code>default_url_options</code>:</p>
<pre><code># application_controller.rb # ... private def default_url_options { <span class="hljs-attr">locale</span>: I18n.locale } end
</code></pre><p>The is going to include the <code>locale</code> parameter into every link generated with Rails helpers.</p>
<p>The last step is to present two links to switch between locales:</p>
<pre><code>&lt;!-- views/layouts/application.html.erb --&gt; &lt;!-- ... --&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">link_to</span> <span class="hljs-attr">t</span>('<span class="hljs-attr">global.menu.home</span>'), <span class="hljs-attr">root_path</span> %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">link_to</span> <span class="hljs-attr">t</span>('<span class="hljs-attr">global.menu.feedback</span>'), <span class="hljs-attr">new_feedback_path</span> %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">link_to</span> '<span class="hljs-attr">English</span>', <span class="hljs-attr">root_path</span>(<span class="hljs-attr">locale:</span> <span class="hljs-attr">:en</span>) %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">link_to</span> 'Русский', <span class="hljs-attr">root_path</span>(<span class="hljs-attr">locale:</span> <span class="hljs-attr">:ru</span>) %&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
</code></pre><p>As an exercise, you may make these links more fancy and, for instance, redirect the user back to the page that he was browsing.</p>
<h3 id="heading-simplify-your-life-with-lokalise">Simplify Your Life With Lokalise</h3>
<p>By now you are probably thinking that supporting multiple languages on a big website is probably a pain. And, honestly, you are right. Of course, the translations can be namespaced, and <a target="_blank" href="https://guides.rubyonrails.org/i18n.html#organization-of-locale-files">even split into multiple YAML files</a> if needed, but still you must make sure that all the keys are translated for each and every locale.</p>
<p>Luckily, there is a solution to this problem: the Lokalise platform that <a target="_blank" href="https://lokalise.co/features">makes working with the localization files much simpler</a>. Let me guide you through the initial setup which is nothing complex really.</p>
<ul>
<li>To get started, <a target="_blank" href="https://lokalise.co/signup">grab your free trial</a></li>
<li><a target="_blank" href="https://docs.lokalise.co/api-and-cli/lokalise-cli-tool">Install Lokalise CLI</a> that will be used to upload and download translation files</li>
<li>Open your <a target="_blank" href="https://lokalise.co/profile">personal profile page</a>, navigate to the “API tokens” section, and generate a read/write token</li>
<li>Create a new project, give it some name, and set English as a base language</li>
<li>On the project page click the “More” button and choose “Settings”. On this page you should see the project ID</li>
<li>Now from the command line simply run <code>lokalise --token &lt;token&gt; import &lt;project_id&gt; --lang_iso en --file config/lo</code>cales/en.yml while providing your generated token and project ID (on Windows you may also need to provide the full path to the file). This should upload English translation to Lokalise. Run the same command for the Russian locale.</li>
<li>Navigate back to the project overview page. You should see all your translation keys and values there. Of course, it is possible to edit, delete them, as well as add new ones. Here you may also filter the keys and, for example, find the untraslated ones which is really convenient.</li>
<li>After you are done editing the translations, download them back by running <code>lokalise --token &lt;token&gt; export &lt;project_id&gt; --type yaml --bundle_structure %LANG_ISO%.yml --unzip_to E:/Supreme/docs/work/lokalise/rails/SampleApp/con</code>fig/locales/. Great!</li>
</ul>
<p>Lokalise has many more features including support for dozens of platforms and formats, ability to order translations from professionals, and even the possibility to upload screenshots in order to read texts from them. So, stick with Lokalise and make your life easier!</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In this article we have thoroughly discussed how to introduce internationalization support in Rails applications and implemented it ourselves. You have learned how and where to store translations, how to look them up, what are localized views, how to translate error messages and ActiveRecord-related stuff, as well as how to switch between locales and persist the chosen locale among the request. Not bad for today, eh?</p>
<p>Of course, it is impossible to cover all ins and outs of Rails I18n in one article, and so I recommend checking out <a target="_blank" href="https://guides.rubyonrails.org/i18n.html">the official guide</a> that gives some more detailed information on the topic and provides useful examples.</p>
<p><em>Originally published at <a target="_blank" href="https://blog.lokalise.co/rails-i18n/">blog.lokalise.co</a> on August 23, 2018.</em></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
