<?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[ localization - 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[ localization - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 19 May 2026 04:43:19 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/localization/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ What is Unicode —The Secret Language Behind Every Text You See ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever sent a message with an emoji? Read a blog in another language? Or copied some strange symbol from the internet?  All of these are possible because of something called Unicode.  Unicode is a powerful system that lets computers understand... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-unicode-the-secret-language-behind-every-text-you-see/</link>
                <guid isPermaLink="false">688b74903e00617596a6f3ce</guid>
                
                    <category>
                        <![CDATA[ Computer Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ unicode ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Thu, 31 Jul 2025 13:50:08 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753969659647/1f49bf21-9be3-4e60-861f-50c714d7ae87.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever sent a message with an emoji? Read a blog in another language? Or copied some strange symbol from the internet? </p>
<p>All of these are possible because of something called <a target="_blank" href="https://en.wikipedia.org/wiki/Unicode"><strong>Unicode</strong></a>. </p>
<p>Unicode is a powerful system that lets computers understand and show text in nearly any language, including fun stuff like emojis. 😃</p>
<p>In this article, we’ll break down what Unicode is, why it matters, and how it powers global communication.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-problem-before-unicode">The Problem Before Unicode</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-unicode">What Is Unicode?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-does-unicode-work">How Does Unicode Work</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-unicode-encodings">What Are Unicode Encodings?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-code-points-characters-and-glyphs">Code Points, Characters, and Glyphs</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-unicode-in-programming">Unicode in Programming</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-unicode-matters">Why Unicode Matters</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-the-problem-before-unicode">The Problem Before Unicode</h2>
<p>Let’s rewind to the early days of computers when each country had its own way of showing text. These systems were called character encodings. </p>
<p>For example, English text used <a target="_blank" href="https://en.wikipedia.org/wiki/ASCII">ASCII</a>, while others used ISO-8859, Shift-JIS, and more.</p>
<p>But here’s the problem: the same number could mean different things in different systems. </p>
<p>For example, the number <code>0x41</code> meant the letter A in one system, but in another, it might mean something else entirely.</p>
<p>This caused chaos when sharing documents between systems. Special characters would turn into random symbols, and non-English languages were often unreadable. </p>
<p>It was clear that the world needed one universal system. Something that could handle all languages and symbols in a single, consistent way.</p>
<p>That’s where Unicode comes in.</p>
<h2 id="heading-what-is-unicode">What Is Unicode?</h2>
<p>Unicode is a standard system that assigns a unique number, called a code point, to every character. It includes letters, numbers, emojis, symbols, and even <a target="_blank" href="https://invisible-characters.com/">invisible control characters</a>.</p>
<p>Think of it like giving every character in every language its own ID number.</p>
<p>For example:</p>
<ul>
<li><p>The capital letter <strong>A</strong> is given the code <code>U+0041</code></p>
</li>
<li><p>The Greek letter <strong>Ω</strong> is <code>U+03A9</code></p>
</li>
<li><p>The emoji 😀 is <code>U+1F600</code></p>
</li>
</ul>
<p>This means no matter what device, app, or country you’re in, the same code will always mean the same character.</p>
<h2 id="heading-how-does-unicode-work">How Does Unicode Work?</h2>
<p>At its core, Unicode assigns a code point to each character. </p>
<p>Code points look like this: <code>U+XXXX</code>, where <code>XXXX</code> is a number written in hexadecimal (a base-16 system computers use).</p>
<p>But computers don’t store code points directly. They store bytes, the 1s and 0s under the hood. So Unicode needs a way to turn those code points into bytes. This is called encoding.</p>
<h3 id="heading-what-are-unicode-encodings">What Are Unicode Encodings?</h3>
<p>Unicode gives every character a unique code point, but computers don’t store “U+1F600” directly – they store bytes. To convert these code points into bytes that computers can save or transmit, we need encodings.</p>
<p>There are three main ways to turn Unicode code points into bytes:</p>
<p><strong>1. UTF-8 (Most common)</strong></p>
<ul>
<li><p>Uses 1 to 4 bytes.</p>
</li>
<li><p>Great for English and most symbols.</p>
</li>
<li><p>Saves space.</p>
</li>
<li><p>Works on the web and most systems.</p>
</li>
</ul>
<p><strong>2. UTF-16</strong></p>
<ul>
<li><p>Uses 2 or 4 bytes.</p>
</li>
<li><p>Used in Windows, Java, and some older systems.</p>
</li>
</ul>
<p><strong>3. UTF-32</strong></p>
<ul>
<li><p>Uses 4 bytes for everything.</p>
</li>
<li><p>Easy to work with, but uses more memory.</p>
</li>
</ul>
<p>If you’re storing or sending text, the encoding decides how many bytes are used. Choosing UTF‑8 can save space, especially for English-heavy data. When you see garbled text or � symbols, it’s usually a mismatch between encoding and decoding.</p>
<p>Web servers, databases, and APIs often require you to specify the encoding to ensure multilingual text displays correctly. In short, knowing the difference between UTF‑8, UTF‑16, and UTF‑32 helps you prevent bugs, save storage, and build apps that handle text from any language reliably.</p>
<p>So, UTF-8 is often the best choice. It’s efficient, and it works nearly everywhere.</p>
<h3 id="heading-code-points-characters-and-glyphs">Code Points, Characters, and Glyphs</h3>
<p>Let’s break down the main parts of Unicode:</p>
<p><strong>Code Point:</strong></p>
<p>This is the number assigned to a character. For example:</p>
<ul>
<li><p><code>U+0041</code> is the code point for <strong>A</strong></p>
</li>
<li><p><code>U+20AC</code> is for the Euro sign <strong>€</strong></p>
</li>
<li><p><code>U+1F600</code> is for the smiley face 😀</p>
</li>
</ul>
<p><strong>Character:</strong></p>
<p>The actual letter or symbol we see. For example, “A”, “Ω”, or “😎”.</p>
<p><strong>Glyph:</strong></p>
<p>This is the visual design of a character. For example, “A” in Arial looks different from “A” in Times New Roman, but the character is the same.</p>
<h2 id="heading-unicode-in-programming">Unicode in Programming</h2>
<p>Modern programming languages have embraced Unicode, making it easier than ever to build applications that support global audiences. </p>
<p>Whether you’re writing a command-line tool or building a web app, Unicode ensures your text renders correctly, no matter the language.</p>
<p>Take <a target="_blank" href="https://www.freecodecamp.org/news/an-animated-introduction-to-programming-with-python/">Python</a>, for instance. It natively supports Unicode strings:</p>
<pre><code class="lang-typescript">print(<span class="hljs-string">"Welcome 😊"</span>)  # This works because Python uses Unicode under the hood
</code></pre>
<p>You can even mix languages and emojis in the same output without a problem:</p>
<pre><code class="lang-typescript">print(<span class="hljs-string">"こんにちは, friend! 🚀"</span>)
</code></pre>
<p>In <a target="_blank" href="https://www.freecodecamp.org/news/what-is-javascript-definition-of-js/">JavaScript</a>, Unicode enables developers to use characters from virtually any script:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">"नमस्ते"</span>);  <span class="hljs-comment">// Prints “Namaste” in Hindi</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"مرحبا بالعالم"</span>);  <span class="hljs-comment">// Arabic: "Hello, world"</span>
</code></pre>
<p>Or even create multilingual UIs:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"greeting"</span>).textContent = <span class="hljs-string">"Bonjour, мир!"</span>;
</code></pre>
<p>Before Unicode, developers had to juggle different encodings like ASCII, which often led to corrupted text when files moved between systems. Now, thanks to Unicode, most languages, including Java, C#, Ruby, Go, and Rust, handle international text gracefully by default.</p>
<p>This shift means developers can write apps that support global users from day one. Whether you’re building a chat app, an international e-commerce site, or a multilingual blog – with Unicode, your code speaks every language.</p>
<h2 id="heading-why-unicode-matters">Why Unicode Matters</h2>
<p>Before Unicode, digital communication across languages was chaotic. </p>
<p>Different systems used different character sets, leading to garbled text, random boxes, or strings of question marks whenever someone typed in a non-Latin-based language. Unicode changed all of that.</p>
<p>With Unicode, you can now mix languages like Chinese and English in the same document without a problem. Whether you’re copying text between applications or transferring data across platforms, it just works. </p>
<p>This consistency has been a game-changer for building multilingual websites and applications. Developers no longer need to worry about separate encodings for different regions. A single, unified standard handles it all.</p>
<p>Unicode isn’t something most users think about, but it’s embedded in almost everything. </p>
<p>It powers the text you see on websites and in your email, your smartphone’s keyboard, and even the way you chat in online games. Social media posts, search queries, and programming languages, all rely on Unicode.</p>
<p>Behind the scenes, the <a target="_blank" href="https://www.unicode.org/consortium/consort.html">Unicode Consortium</a>, made up of industry giants like Google, Apple, and Microsoft, regularly updates the standard. They decide which new characters and emojis make it into our digital vocabulary. </p>
<p>That’s why your favourite facepalm emoji or regional script exists. Someone proposed it, and Unicode made it happen.</p>
<p>Unicode isn’t just a technical convenience. It plays a direct role in how people engage with content. </p>
<p>Pages with broken symbols or unreadable characters had significantly lower engagement rates compared to cleanly rendered ones. It was a clear signal that readability isn’t just about aesthetics – it affects how long people stay and interact with your content.</p>
<p>That’s why even small encoding errors can have a real impact, especially on multilingual platforms or international blogs. Unicode silently keeps everything running smoothly.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Unicode is one of the unsung heroes of our digital world. Without it, the internet would still be a confusing mix of broken characters and language barriers. Because of Unicode, we can type “Hello 😊”, mix multiple languages in a single message, or build global apps that just work.</p>
<p>So the next time you post an emoji, read a message in a different script, or switch languages on your keyboard, take a moment to appreciate the invisible infrastructure behind it all. That’s Unicode, working quietly to make sure we stay connected, no matter what language we speak.</p>
<p><a target="_blank" href="https://blog.manishshivanandhan.com/">Join my newsletter</a> for a summary of my articles every Friday. You can also <a target="_blank" href="https://linkedin.com/in/manishmshiva">connect with me on Linkedin</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Svelte i18n and Localization Made Easy ]]>
                </title>
                <description>
                    <![CDATA[ Apps are accessible worldwide. This means anyone from anywhere in the world can download your app.  So, if you want to cater to people everywhere, your app needs to support multiple languages.  Fortunately, Svelte is easy to work with, and it makes l... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/svelte-i18n-and-localization-made-easy/</link>
                <guid isPermaLink="false">6752020830d899211b5bda58</guid>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Svelte ]]>
                    </category>
                
                    <category>
                        <![CDATA[ i18n ]]>
                    </category>
                
                    <category>
                        <![CDATA[ app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ App Localization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Alex Tray ]]>
                </dc:creator>
                <pubDate>Thu, 05 Dec 2024 19:42:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733421094910/f2f91ab6-0717-4135-9f08-719f041471f6.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Apps are accessible worldwide. This means anyone from anywhere in the world can download your app. </p>
<p>So, if you want to cater to people everywhere, your app needs to support multiple languages. </p>
<p>Fortunately, Svelte is easy to work with, and it makes localization (l10n) and internationalization (i18n) quite straightforward. </p>
<p>But it lacks built-in i18n support—so you need to use a library like svelte-i18n or one of the others available. Let’s create a simple Svelte app to demonstrate how localization can be implemented. </p>
<p>We’ll create a welcome screen that can be used in both English and Spanish and then build some more advanced features.</p>
<dl>
<summary>Table of Contents</summary>
<ul>
<li>
  <a href="heading-what-makes-svelte-unique">What Makes Svelte Unique?</a></li>
  <li><a href="heading-how-to-localize-a-svelte-application">How to Localize a Svelte Application?</a></li>
  <li><a href="heading-adding-language-support">Adding Language Support</a></li>
  <li><a href="heading-updating-components-to-use-translations">Updating Components to Use Translations</a></li>
  <li><a href="heading-creating-a-language-switcher">Creating a Language Switcher</a></li>
  <li><a href="heading-adding-advanced-features">Adding Advanced Features</a></li>
<details>
  <li><a href="heading-formatting-numbers-and-currencies">Formatting Numbers and Currencies</a></li>
  <li><a href="heading-formatting-dates">Formatting Dates</a></li>
  <li><a href="heading-localizing-images">Localizing Images</a></li>
<details>
  <li><a href="heading-dynamic-image-paths">Dynamic Image Paths</a></li><li>
  </li><li><a href="heading-integrating-alternative-text-localization">Integrating Alternative Text Localization</a></li>
 <li><a href="heading-switching-svg-content-dynamically">Switching SVG Content Dynamically</a></li>
</details>
  <li><a href="heading-handling-multiple-forms-of-pluralization">Handling Multiple Forms of Pluralization</a></li>
  <li><a href="heading-handling-missing-translations">Handling Missing Translations</a></li>
</details>
  <li><a href="heading-making-your-app-production-ready">Making Your App Production-Ready</a></li>
 <li><a href="heading-best-practices-for-scaling-your-localization">Best Practices for Scaling Your Localization</a></li>
  <li><a href="heading-wrapping-up">Wrapping Up</a></li>
</ul>
</dl>

<h2 id="heading-what-makes-svelte-unique">What Makes Svelte Unique?</h2>
<p>Similar to <a target="_blank" href="https://centus.com/blog/vue-i18n">Vue i18n</a>, Svelte converts code to vanilla JS during the build process. This means your app ships with minimal code and offers excellent performance.</p>
<p>While other frameworks like React and Vue have longer startup times, Svelte’s compilation process changes that. It creates much smaller bundles, and apps run faster by default.</p>
<p>Svelte’s reactive syntax and lightweight nature make it an excellent choice for developers who want efficient, modern applications. </p>
<p>Its minimalism aligns well with the simplicity required in <a target="_blank" href="https://v2cloud.com/blog/best-programming-languages-for-cloud-computing"><strong>cloud development languages</strong></a>, ensuring that applications are lean, scalable, and optimized for cloud environments. </p>
<p>This minimalism also means you may not always have all the required built-in features. But pretty much all the limitations are resolved with the help of external libraries. </p>
<h2 id="heading-how-to-localize-a-svelte-application">How to Localize a Svelte Application</h2>
<p>Let’s jump right into creating a Svelte project now. I’ll create the project using the <code>npm create</code> command. Run the below commands one by one:</p>
<pre><code class="lang-javascript">npm create svelte@latest freecodecamp-localization-demo
cd freecodecamp-localization-demo
npm install
npm install svelte-i18n
</code></pre>
<p>The <code>npm create</code> command will prompt you with a few choices. Here’s what I’ve picked (but you can always adjust this based on your project requirements):</p>
<ul>
<li><p>Skeleton project: Select <strong>Yes</strong>.</p>
</li>
<li><p>Add TypeScript support: Select <strong>No</strong> (or Yes if you prefer TypeScript).</p>
</li>
<li><p>Add ESLint for code linting: Select <strong>Yes</strong>.</p>
</li>
<li><p>Add Prettier for code formatting: Select <strong>Yes</strong>.</p>
</li>
</ul>
<p>Now that we have our project set up, let's create a welcome component that we'll later enhance with translations. </p>
<p>Create a new file called Welcome.svelte in your src directory:</p>
<pre><code class="lang-javascript">&lt;!-- src/Welcome.svelte --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> username;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Welcome {username}!<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfcaW7fLzjbLa5ysYYu5FplhoGeVNmml9An_l0fcbEwQSxvg5mDbh3PY9b7etS6DESM7ZGdO_R5dqcYaDdgMaaQE2d7w1Q8eU4uAtIfKjHfm58buFwM-KLtJ_bv-x3XyqoYIOgfdQ?key=uXnvRGfJpBUiBb7CkgThtLro" alt="AD_4nXfcaW7fLzjbLa5ysYYu5FplhoGeVNmml9An_l0fcbEwQSxvg5mDbh3PY9b7etS6DESM7ZGdO_R5dqcYaDdgMaaQE2d7w1Q8eU4uAtIfKjHfm58buFwM-KLtJ_bv-x3XyqoYIOgfdQ?key=uXnvRGfJpBUiBb7CkgThtLro" width="1416" height="578" loading="lazy"></p>
<p>This component takes the username property (that I’ve passed from the App.svelte file) and displays a welcome message. </p>
<p>Simple enough for now, but what if your users speak different languages? Let’s add language support.</p>
<h2 id="heading-adding-language-support">Adding Language Support</h2>
<p>To do this, we need to create translation files for each language. Start by creating a new directory called <strong>locales</strong> in your <strong>src</strong> folder. Inside the <strong>locales</strong> folder, create two JSON files—<em>en.json</em> for English and <em>es.json</em> for Spanish.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/locales/en.json</span>
{
  <span class="hljs-string">"hello"</span>: <span class="hljs-string">"Hello {username}!"</span>,
  <span class="hljs-string">"buttons"</span>: {
    <span class="hljs-string">"save"</span>: <span class="hljs-string">"Save"</span>,
    <span class="hljs-string">"cancel"</span>: <span class="hljs-string">"Cancel"</span>
  }
}

<span class="hljs-comment">// src/locales/es.json  </span>
{
  <span class="hljs-string">"hello"</span>: <span class="hljs-string">"¡Hola {username}!"</span>,
  <span class="hljs-string">"buttons"</span>: {
    <span class="hljs-string">"save"</span>: <span class="hljs-string">"Guardar"</span>,
    <span class="hljs-string">"cancel"</span>: <span class="hljs-string">"Cancelar"</span>
  }
}
</code></pre>
<p>These files contain our translation strings.</p>
<p>Notice how we've organized them in a nested structure—this helps manage translations as your app grows. </p>
<p>The {username} and {count} placeholders will be replaced with actual values during runtime when we dynamically (or statically) pass the required values.</p>
<p>Next, we need to tell Svelte how to use these translations. For this, we need an <strong>i18n configuration</strong> file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/i18n.js</span>
<span class="hljs-keyword">import</span> { register, init } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte-i18n'</span>;

register(<span class="hljs-string">'en'</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./locales/en.json'</span>));
register(<span class="hljs-string">'es'</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./locales/es.json'</span>));

init({
  <span class="hljs-attr">fallbackLocale</span>: <span class="hljs-string">'en'</span>,
  <span class="hljs-attr">initialLocale</span>: <span class="hljs-string">'en'</span>,
});
</code></pre>
<p>We’ve registered our translation files and set English as both the initial language and fallback language. The fallback language is used when a translation is missing in the selected language.</p>
<h2 id="heading-updating-components-to-use-translations">Updating Components to Use Translations</h2>
<p>Now we can update our welcome component to use the text from the JSON file:</p>
<pre><code class="lang-javascript">&lt;!-- src/Welcome.svelte --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> {  } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte-i18n'</span>;
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> username;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{$('hello', { username })}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<p>The <code>$_</code> function is a special helper from svelte-i18n that retrieves translated strings. </p>
<p>When we pass { username } as the second argument, it replaces the placeholder in our translation strings with the actual username.</p>
<h2 id="heading-creating-a-language-switcher">Creating a Language Switcher</h2>
<p>How would someone change the language, though? Let's create a simple language selector component:</p>
<pre><code class="lang-javascript">&lt;!-- src/LanguageSelect.svelte --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> { locale } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte-i18n'</span>;

  <span class="hljs-keyword">const</span> languages = [
    { <span class="hljs-attr">code</span>: <span class="hljs-string">'en'</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'English'</span> },
    { <span class="hljs-attr">code</span>: <span class="hljs-string">'es'</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'Español'</span> }
  ];
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
  {#each languages as { code, name }}
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">on:click</span>=<span class="hljs-string">{()</span> =&gt;</span> locale.set(code)}&gt;{name}<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  {/each}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<p>The <strong>bind:value</strong> directive automatically updates the active language when users make a selection. </p>
<p>The locale store from svelte-i18n handles all the behind-the-scenes work of switching languages.</p>
<p>Now let's bring everything together in our main <strong>App</strong> component:</p>
<pre><code class="lang-javascript">&lt;!-- src/App.svelte --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> { waitLocale } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte-i18n'</span>;
  <span class="hljs-keyword">import</span> Welcome <span class="hljs-keyword">from</span> <span class="hljs-string">'./Welcome.svelte'</span>;
  <span class="hljs-keyword">import</span> LanguageSelect <span class="hljs-keyword">from</span> <span class="hljs-string">'./LanguageSelect.svelte'</span>;

  <span class="hljs-keyword">const</span>  username = <span class="hljs-string">'developer'</span>;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

{#<span class="hljs-keyword">await</span> waitLocale()}
  &lt;p&gt;Loading...&lt;/p&gt;
{:then}
  &lt;main&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">LanguageSelect</span> /&gt;</span></span>
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Welcome</span> {<span class="hljs-attr">username</span>} /&gt;</span></span>
  &lt;/main&gt;
{/<span class="hljs-keyword">await</span>}
</code></pre>
<p>The <code>waitLocale</code> function ensures translations are loaded before showing content. This prevents flickering or missing translations when the app first loads.</p>
<h2 id="heading-adding-advanced-features">Adding Advanced Features</h2>
<p>As your app grows, you'll need to handle more complex scenarios. Let's look at some common requirements.</p>
<h3 id="heading-formatting-numbers-and-currencies">Formatting Numbers and Currencies</h3>
<p>Different countries handle numbers and currencies quite differently. So for instance, if you show the figure of a hundred thousand to someone from France, and someone from the USA, you’ll need to show the same figure differently. </p>
<p>France → 100 000,00 $</p>
<p>USA → $100,000.00</p>
<p>You see how the thousands are separated by a space and the decimal by a comma in France? Using the US number format will make it quite confusing for someone from France. Here are a few other examples.</p>
<ul>
<li><p>US uses periods for decimals (1,234.56)</p>
</li>
<li><p>Many European countries use commas for decimals and periods for thousands (1.234,56)</p>
</li>
<li><p>Some countries group digits differently (like 1,23,456 in India)</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/lib/formatUtils.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatCurrency</span>(<span class="hljs-params">amount, locale, currency = <span class="hljs-string">'USD'</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.NumberFormat(locale, {
    <span class="hljs-attr">style</span>: <span class="hljs-string">'currency'</span>,
    currency
  }).format(amount);
}
</code></pre>
<p>You can now use these formatters in your components:</p>
<pre><code class="lang-javascript">&lt;!-- src/lib/PriceDisplay.svelte --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> { formatCurrency } <span class="hljs-keyword">from</span> <span class="hljs-string">'./lib/formatUtils'</span>;
  <span class="hljs-keyword">let</span> price = <span class="hljs-number">1234.56</span>;
  <span class="hljs-keyword">let</span> locale = <span class="hljs-string">'en'</span>;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Price: {formatCurrency(price, locale, 'USD')}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
</code></pre>
<p>With this setup, the app now adapts the currency formats to the different locales based on what outputs you ask from it:</p>
<ul>
<li><p>US: "$1,234.56", "1.2M", "15%"</p>
</li>
<li><p>German: "1.234,56 €", "1,2 Mio.", "15 %"</p>
</li>
</ul>
<h3 id="heading-formatting-dates">Formatting Dates</h3>
<p>Similar to currency and number formats, date are formatted differently across different locales. </p>
<p>So, while the US uses MM/DD/YYYY, many European countries use DD/MM/YYYY, and Japan often uses YYYY年MM月DD日. </p>
<p>Luckily, we don’t need to handle this manually. Similar to currency formatting, we have the <code>Intl.DateTimeFormat</code> function that accepts the locale and date in numeric format and returns the appropriately formatted date. </p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/lib/dateUtils.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatDate</span>(<span class="hljs-params">date, locale</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.DateTimeFormat(locale, {
    <span class="hljs-attr">year</span>: <span class="hljs-string">'numeric'</span>,
    <span class="hljs-attr">month</span>: <span class="hljs-string">'long'</span>,
    <span class="hljs-attr">day</span>: <span class="hljs-string">'numeric'</span>
  }).format(date);
}
</code></pre>
<p>You can now use this function in your Svelte app to display the dates correctly for each locale:</p>
<pre><code class="lang-javascript">&lt;!-- src/lib/DateDisplay.svelte --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> { formatDate } <span class="hljs-keyword">from</span> <span class="hljs-string">'./lib/dateUtils'</span>;
  <span class="hljs-keyword">let</span> locale = <span class="hljs-string">'en'</span>;
  <span class="hljs-keyword">let</span> today = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Today's date: {formatDate(today, locale)}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
</code></pre>
<p>Once implemented, you’ll see the dates get formatted automatically, like below:</p>
<ul>
<li><p>English (US): "September 23, 2024"</p>
</li>
<li><p>Spanish: "23 de septiembre de 2024"</p>
</li>
<li><p>German: "23. September 2024"</p>
</li>
<li><p>Japanese: "2024年9月23日"</p>
</li>
</ul>
<h3 id="heading-localizing-images">Localizing Images</h3>
<p>Don’t forget that localization isn’t just about translating text—your images need attention, too. Culturally relevant visuals, region-specific content like maps or symbols, and adaptable image formats can make all the difference.</p>
<h4 id="heading-dynamic-image-paths"><strong>Dynamic Image Paths</strong></h4>
<p>Use Svelte's reactivity to dynamically load images based on the current locale. For example, you can store localized image paths in a JSON file or directly in your i18n configuration:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-string">"en"</span>: { <span class="hljs-string">"logo"</span>: <span class="hljs-string">"/images/en/logo.png"</span> },
  <span class="hljs-string">"fr"</span>: { <span class="hljs-string">"logo"</span>: <span class="hljs-string">"/images/fr/logo.png"</span> }
}
</code></pre>
<p>Then, in your Svelte component:</p>
<pre><code class="lang-javascript">&lt;script&gt;
  <span class="hljs-keyword">import</span> { locale } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte-i18n'</span>;
  <span class="hljs-keyword">import</span> translations <span class="hljs-keyword">from</span> <span class="hljs-string">'./translations.json'</span>;

  $: imagePath = translations[$locale].logo;
&lt;/script&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{imagePath}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Localized logo"</span> /&gt;</span></span>
</code></pre>
<h4 id="heading-integrating-alternative-text-localization"><strong>Integrating Alternative Text Localization</strong></h4>
<p>Ensure your images' alt attributes are also localized. You can achieve this by adding an additional field in your translations:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-string">"en"</span>: { <span class="hljs-string">"logoAlt"</span>: <span class="hljs-string">"Company Logo"</span> },
  <span class="hljs-string">"fr"</span>: { <span class="hljs-string">"logoAlt"</span>: <span class="hljs-string">"Logo de l'entreprise"</span> }
}

Then bind the localized alt text dynamically
</code></pre>
<pre><code class="lang-javascript">
&lt;img src={imagePath} alt={translations[$locale].logoAlt} /&gt;
</code></pre>
<h4 id="heading-switching-svg-content-dynamically"><strong>Switching SVG Content Dynamically</strong></h4>
<p>If you need to localize content within SVGs, such as text or icons or want to <a target="_blank" href="https://www.adobe.com/express/feature/image/convert/svg">convert to SVG</a> format, consider using Svelte's templating for seamless integration.</p>
<pre><code class="lang-javascript">&lt;svg&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">text</span> <span class="hljs-attr">x</span>=<span class="hljs-string">"10"</span> <span class="hljs-attr">y</span>=<span class="hljs-string">"20"</span>&gt;</span>{$t('svgText')}<span class="hljs-tag">&lt;/<span class="hljs-name">text</span>&gt;</span></span>
&lt;/svg&gt;
</code></pre>
<p>This approach ensures your SVGs are directly rendered with localized text.</p>
<h3 id="heading-handling-multiple-forms-of-pluralization">Handling Multiple Forms of Pluralization</h3>
<p>While many languages have two forms of plurals (singular and plural), there are many languages with more than two forms, and some don’t pluralize. For example:</p>
<ul>
<li><p>Arabic has six forms</p>
</li>
<li><p>Japanese doesn't pluralize in the same way</p>
</li>
</ul>
<p>We can easily handle these differences using svelte-i18n's ICU message syntax. I’ve also added an example of how you can handle more than two forms when using Arabic.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// src/locales/en.json</span>
{
  <span class="hljs-string">"items"</span>: {
    <span class="hljs-string">"count"</span>: <span class="hljs-string">"{count, plural, =0 {No items} one {1 item} other {{count} items}}"</span>
  }
}

<span class="hljs-comment">// src/locales/es.json</span>

{
  <span class="hljs-string">"items"</span>: {
    <span class="hljs-string">"count"</span>: <span class="hljs-string">"{count, plural, =0 {Sin elementos} one {1 elemento} other {{count} elementos}}"</span>
  }
}

<span class="hljs-comment">// src/locales/ar.json</span>
{
  <span class="hljs-string">"items"</span>: {
    <span class="hljs-string">"count"</span>: <span class="hljs-string">"{count, plural, =0 {لا عناصر} one {عنصر واحد} two {عنصران} few {# عناصر} many {# عنصر} other {# عنصر}}"</span>,
  }
}
</code></pre>
<p>Now, let's create the pluralization component for our Svelte app where a button increments the count with every click.</p>
<pre><code class="lang-javascript">&lt;!-- src/lib/ItemCounter.svelte --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> {  } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte-i18n'</span>;
  <span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span>;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{$('items.count', { count })}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">on:click</span>=<span class="hljs-string">{()</span> =&gt;</span> count++}&gt;Add Item<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">on:click</span>=<span class="hljs-string">{()</span> =&gt;</span> count--} disabled={count === 0}&gt;Remove Item<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
</code></pre>
<p>With this implemented, your app is fully ready to handle pluralization dynamically (as you’ll notice by clicking on the <strong>Add item</strong> button). </p>
<h3 id="heading-handling-missing-translations">Handling Missing Translations</h3>
<p>Sometimes, translations for certain keys might be missing. This can happen due to oversight, dynamic content, or incomplete translation files. To handle such scenarios gracefully, set up error handling using the <strong>missingKeyHandler</strong> option in <strong>svelte-i18n</strong>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { register, init } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte-i18n'</span>;

register(<span class="hljs-string">'en'</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./locales/en.json'</span>));
register(<span class="hljs-string">'es'</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./locales/es.json'</span>));

init({
  <span class="hljs-attr">fallbackLocale</span>: <span class="hljs-string">'en'</span>,
  <span class="hljs-attr">initialLocale</span>: <span class="hljs-string">'en'</span>,
  <span class="hljs-attr">missingKeyHandler</span>: <span class="hljs-function">(<span class="hljs-params">locale, key</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">`Missing translation: <span class="hljs-subst">${key}</span> (<span class="hljs-subst">${locale}</span>)`</span>);
    <span class="hljs-keyword">return</span> key; <span class="hljs-comment">// Display the key itself when translation is missing</span>
  }
});
</code></pre>
<p>This code logs a warning when a translation is missing and displays the key instead of showing nothing.</p>
<h2 id="heading-making-your-app-production-ready">Making Your App Production-Ready</h2>
<p>When preparing your app for real-world users, optimize it for localization by automatically detecting the user’s language. </p>
<p>You can use the browser’s <strong>navigator.language</strong> property to set the initial locale:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { init, getLocaleFromNavigator } <span class="hljs-keyword">from</span> <span class="hljs-string">'svelte-i18n'</span>;

init({
  <span class="hljs-attr">fallbackLocale</span>: <span class="hljs-string">'en'</span>,
  <span class="hljs-attr">initialLocale</span>: getLocaleFromNavigator(),
});
</code></pre>
<h2 id="heading-best-practices-for-scaling-your-localization">Best Practices for Scaling Your Localization</h2>
<ul>
<li><p><strong>Organize translations</strong>: Group related translations logically (for example, buttons, menus, notifications) and use consistent naming patterns for keys.</p>
</li>
<li><p><strong>Use a translation management platform:</strong> As your app grows, handling translation files manually starts becoming cumbersome. <a target="_blank" href="https://centus.com/">Translation management platforms</a> are purpose-built to solve this exact issue and help save hours every day, make it easy to collaborate on projects, and keep track of progress.</p>
</li>
<li><p><strong>Thorough testing</strong>: Test your app with real users across all supported languages. You also need to keep in mind the text length changes to avoid issues with layouts, especially with languages like German or Arabic, which can expand text significantly.</p>
</li>
<li><p><strong>Cultural considerations</strong>: Adapt your app for cultural norms, reading directions (for example, RTL for Arabic), and regional preferences (for example, date and currency formats). </p>
</li>
</ul>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Localization might feel like a big task at first, but it’s totally worth it. It lets you reach more people and makes your app more inclusive.</p>
<p>Svelte and svelte-i18n streamline the process and keep it fun as you build your skills.</p>
<p>To keep it simple, start with the basics, such as adding translations and a language switcher. Advanced features like handling dates, currencies, and pluralization can follow as you gain confidence. </p>
<p>Take your time, test thoroughly, and build an app that feels natural for all the users you serve!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Multilingual Apps with i18n in React ]]>
                </title>
                <description>
                    <![CDATA[ I recently worked on an exciting project that involved creating a website capable of switching between languages to appeal to a broader audience. This made me understand the concept of "localization" better, which typically entails adapting content t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-multilingual-apps-with-i18n-in-react/</link>
                <guid isPermaLink="false">6750aec5bb4ed9c12f206d7d</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayantunji Timilehin ]]>
                </dc:creator>
                <pubDate>Wed, 04 Dec 2024 19:34:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733137904769/d29dd9ea-5794-4fbe-ac1c-066f6a216cb2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>I recently worked on an exciting project that involved creating a website capable of switching between languages to appeal to a broader audience. This made me understand the concept of "localization" better, which typically entails adapting content to make it relevant, accessible, and relatable for users in different languages and regions.</p>
<p>Localization isn’t just about translating words, it’s about creating an experience that makes users feel at home, no matter their language. For example, global platforms like Amazon make language switching so seamless that it feels almost magical. Beyond enhancing user experience, this feature plays a crucial role in boosting businesses by reaching a wider audience and fostering stronger connections with customers worldwide.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-i18n-and-why-use-it">What is i18n, and Why Use It?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-dive-right-in">Let’s dive right in</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-how-to-set-up-the-project">Step 1: How to Set Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-how-to-set-up-internationalization-with-i18next">Step 2: How to Set Up Internationalization with i18next</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-how-to-build-components">Step 3: How to Build Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-main-app-component">Step 4: Main App Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-what-is-i18n-and-why-use-it">What is i18n, and Why Use It?</h2>
<p>i18n, short for internationalization, means that an application supports multiple languages. "i18n" is derived from the fact that there are 18 letters between the first "i" and the last "n" in "internationalization." It’s all about making your app adaptable for global audiences by handling text translation, formatting dates and numbers, managing currencies, and accommodating regional conventions.</p>
<p>By enabling internationalization, your app becomes not just a tool but an inclusive platform that speaks directly to a user’s preference and culture.</p>
<h2 id="heading-lets-dive-right-in">Let’s dive right in</h2>
<p>We’ll create a very simple demo multilingual web application with a dark mode toggle feature to demonstrate how to achieve this concept.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ol>
<li><p>Basic Knowledge of React - You should understand how to create components, manage state, and use Hooks like <code>useState</code> and <code>useEffect</code>. If you’re new to React, I recommend starting with <a target="_blank" href="https://react.dev/">the official React docs</a> <a target="_blank" href="https://react.dev/">for a solid foundation</a>.</p>
</li>
<li><p>Familiarity with Internationalization Concepts - Knowing the basics of internationalization (i18n) and why it’s important will give you context for the project. This article's earlier sections cover the essentials.</p>
</li>
<li><p>Tailwind CSS - We'll use Tailwind CSS for styling. It's a utility-first CSS framework that helps you build modern, responsive designs without leaving your HTML. If you’re unfamiliar, check out <a target="_blank" href="https://tailwindcss.com/docs/installation">Tailwind's documentatio</a><a target="_blank" href="https://tailwindcss.com/docs/installation">n.</a></p>
</li>
<li><p>Node.js - Make sure Node.js is installed on your system to handle dependencies. You can download the latest version from <a target="_blank" href="https://nodejs.org/">Node.js</a>.</p>
</li>
<li><p>Package Manager - Either npm (included with Node.js) or yarn is needed to manage project dependencies</p>
</li>
</ol>
<h3 id="heading-tools-well-be-using">Tools we’ll be using</h3>
<ol>
<li><p>Code Editor</p>
</li>
<li><p>Localization Library: <a target="_blank" href="https://www.i18next.com/">react-i18next</a></p>
</li>
<li><p>Icons Library: <a target="_blank" href="https://www.npmjs.com/package/heroicons">hero-icons</a></p>
</li>
</ol>
<h2 id="heading-step-1-how-to-set-up-the-project">Step 1: How to Set Up the Project</h2>
<h3 id="heading-initialize-the-project">Initialize the Project</h3>
<p>Use Vite for fast setup:</p>
<pre><code class="lang-bash">npm create vite@latest multilingual-demo
</code></pre>
<p>Follow the instructions that show up in your terminal, selecting React and TypeScript for development as shown in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733093238523/2d3ee169-bc99-4498-9779-b07067d5e5ee.png" alt="Image of React and Typescript installation" class="image--center mx-auto" width="1112" height="326" loading="lazy"></p>
<h3 id="heading-install-dependencies">Install Dependencies</h3>
<p>Run the following commands in your terminal to install the dependencies required for this project:</p>
<pre><code class="lang-bash">npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend heroicons 
npm install tailwindcss postcss autoprefixer  
npx tailwindcss init
</code></pre>
<h3 id="heading-configure-tailwindcss"><strong>Configure TailwindCSS</strong></h3>
<p>Update the <code>tailwind.config.ts</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/** @type {import('tailwindcss').Config} */</span>
<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {
  content: [<span class="hljs-string">"./src/**/*.{js,jsx,ts,tsx}"</span>],
  darkMode: <span class="hljs-string">"class"</span>, <span class="hljs-comment">//For our dark mode functionality</span>
  theme: {
    container: {
      center: <span class="hljs-literal">true</span>,
      padding: <span class="hljs-string">"1.25rem"</span>,
      screens: {
        sm: <span class="hljs-string">"1200px"</span>,
      },
    },
    extend: {},
  },
  plugins: [],
};
</code></pre>
<p>Add TailwindCSS to <code>src/index.css</code>:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;  
<span class="hljs-keyword">@tailwind</span> components;  
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
<h2 id="heading-step-2-how-to-set-up-internationalization-with-i18next">Step 2: How to Set Up Internationalization with i18next</h2>
<h3 id="heading-initialize-i18next"><strong>Initialize i18next</strong></h3>
<p>Create an <code>i18n.tsx</code> file in the <code>src</code> folder and configure i18next:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> i18next <span class="hljs-keyword">from</span> <span class="hljs-string">"i18next"</span>;
<span class="hljs-keyword">import</span> LanguageDetector <span class="hljs-keyword">from</span> <span class="hljs-string">"i18next-browser-languagedetector"</span>;
<span class="hljs-keyword">import</span> { initReactI18next } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-i18next"</span>;
<span class="hljs-keyword">import</span> Backend <span class="hljs-keyword">from</span> <span class="hljs-string">"i18next-http-backend"</span>;

i18next.use(LanguageDetector).use(initReactI18next).use(Backend).init({
  returnObjects: <span class="hljs-literal">true</span>,
  fallbackLng: <span class="hljs-string">"en"</span>, <span class="hljs-comment">// Language to fallback to if the selected is not configured</span>
  debug: <span class="hljs-literal">true</span>, <span class="hljs-comment">//To enable us see errors</span>
  <span class="hljs-comment">//   lng: "en", //Default language as english</span>
});
</code></pre>
<p>Let’s take a quick look at the contents of this file, as it plays a key role in enabling the translation functionality. This file is responsible for setting up the core of the translation process and making sure that the language-switching feature works smoothly across your app.</p>
<ul>
<li><p><code>i18next</code>: The core internalization library we’re using for translation.</p>
</li>
<li><p><code>LanguageDetector</code>: Helps us detect the user's preferred language automatically, based on browser settings.</p>
</li>
<li><p><code>initReactI18next</code>: Is responsible for integrating the <code>i18next</code> plugin with React and provides Hooks like the <code>useTranslation</code> Hook and other utilities.</p>
</li>
<li><p><code>Backend</code>: Fetches translation data dynamically from an external source. In this case, we’ll be using JSON files.</p>
</li>
</ul>
<p>Import this file into the <code>main.tsx</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//main.tsx</span>

<span class="hljs-keyword">import</span> React, { StrictMode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { createRoot } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom/client"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App.tsx"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./i18n.tsx"</span>;  <span class="hljs-comment">//Import here</span>

createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>)!).render(
  &lt;StrictMode&gt;
    &lt;React.Suspense fallback=<span class="hljs-string">"loading"</span>&gt;
      &lt;App /&gt;
    &lt;/React.Suspense&gt;
  &lt;/StrictMode&gt;
);
</code></pre>
<h3 id="heading-create-translation-files"><strong>Create Translation Files</strong></h3>
<p>In the <code>public/locales</code> directory, create subfolders for each language (for example, <code>en</code>, <code>fr</code>) and include <code>translation.json</code> files:</p>
<p><code>en/translation.json</code></p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"greeting"</span>: <span class="hljs-string">"Welcome to the Language Playground"</span>,
    <span class="hljs-attr">"detail"</span>: {
        <span class="hljs-attr">"line1"</span>: <span class="hljs-string">"Did you know that over 7,000 languages are spoken worldwide?"</span>,
        <span class="hljs-attr">"line2"</span>: <span class="hljs-string">"This Playground demonstrates how web applications can support users in multiple languages, making them accessible and inclusive to people from different backgrounds."</span>
    }
}
</code></pre>
<p><code>fr/translation.json</code></p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"greeting"</span>: <span class="hljs-string">"Bienvenue sur le terrain de jeu linguistique"</span>,
    <span class="hljs-attr">"detail"</span>: {
        <span class="hljs-attr">"line1"</span>: <span class="hljs-string">"Saviez-vous que plus de 7 000 langues sont parlées dans le monde ?"</span>,
        <span class="hljs-attr">"line2"</span>: <span class="hljs-string">"Ce terrain de jeu démontre comment les applications web peuvent prendre en charge les utilisateurs dans plusieurs langues, les rendant accessibles et inclusives aux personnes de différents horizons."</span>
    }
}
</code></pre>
<p>Here, you can add as many languages with their translation files that will be supplied to <code>i18next</code>. Note that the keys in the JSON files are the same as these would be used as references when displaying them on the website.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733095771585/21cbb0f9-e767-426e-8fc6-2bb4e7abc5ef.png" alt="Image of folder structure of translation files" class="image--center mx-auto" width="376" height="522" loading="lazy"></p>
<h2 id="heading-step-3-how-to-build-components">Step 3: How to Build Components</h2>
<p>Create a <code>components</code> folder in the <code>src</code> directory and add the following components:</p>
<h3 id="heading-language-selector"><strong>Language Selector</strong></h3>
<p>Create the <code>LanguageSelector</code> component – contains a <code>select</code> element to help users switch languages dynamically:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> i18next <span class="hljs-keyword">from</span> <span class="hljs-string">"i18next"</span>;
<span class="hljs-keyword">import</span> { useTranslation } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-i18next"</span>;

<span class="hljs-keyword">type</span> languageOption = { language: <span class="hljs-built_in">string</span>; code: <span class="hljs-built_in">string</span> };

<span class="hljs-keyword">const</span> languageOptions: languageOption[] = [
  {
    language: <span class="hljs-string">"English"</span>,
    code: <span class="hljs-string">"en"</span>,
  },
  { language: <span class="hljs-string">"French"</span>, code: <span class="hljs-string">"fr"</span> },
  { language: <span class="hljs-string">"German"</span>, code: <span class="hljs-string">"de"</span> },
  { language: <span class="hljs-string">"Spanish"</span>, code: <span class="hljs-string">"es"</span> },
  { language: <span class="hljs-string">"Arabic"</span>, code: <span class="hljs-string">"ar"</span> },
  { language: <span class="hljs-string">"Yoruba"</span>, code: <span class="hljs-string">"yo"</span> },
];

<span class="hljs-keyword">const</span> LanguageSelector = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// Set the initial language from i18next's detected or default language</span>
  <span class="hljs-keyword">const</span> [language, setLanguage] = useState(i18next.language);

  <span class="hljs-keyword">const</span> { i18n } = useTranslation();

  <span class="hljs-keyword">const</span> handleLanguageChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLSelectElement&gt;</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> selectedLanguage = e.target.value;
    setLanguage(selectedLanguage);
    i18next.changeLanguage(selectedLanguage); <span class="hljs-comment">// Update language in i18next</span>
  };

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">document</span>.body.dir = i18n.dir(); <span class="hljs-comment">//sets the body to ltr or rtl</span>
  }, [i18n, i18n.language]);

  <span class="hljs-keyword">return</span> (
    &lt;select
      id=<span class="hljs-string">"language"</span>
      value={language}
      onChange={handleLanguageChange}
      className=<span class="hljs-string">"p-2 border border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
        dark:bg-gray-800 dark:border-gray-600 dark:text-gray-200 dark:focus:border-indigo-400 dark:focus:ring-indigo-700 dark:focus:ring-opacity-50"</span>
    &gt;
      {languageOptions.map(<span class="hljs-function">(<span class="hljs-params">{ language, code }, key</span>) =&gt;</span> (
        &lt;option value={code} key={key}&gt;
          {language}
        &lt;/option&gt;
      ))}
    &lt;/select&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> LanguageSelector;
</code></pre>
<ul>
<li><p>Initialize the language with the language detected by <code>i18next</code> or the default set language.</p>
</li>
<li><p>The <code>useTranslation</code> Hook exposes <code>i18n</code> instance from <code>i18next</code> to interact with the internationalization settings.</p>
</li>
<li><p>The <code>handleLanguageChange</code> function would be used to update the language selected by the user. It’s triggered when the user selects a new language from the dropdown menu.</p>
</li>
</ul>
<h3 id="heading-implementing-text-direction"><strong>Implementing Text Direction</strong></h3>
<p>The <code>dir</code> attribute in HTML is a critical feature for ensuring accessibility and inclusivity in web applications, especially when dealing with languages that differ in text direction. For example:</p>
<ul>
<li><p><strong>Left-to-Right (LTR)</strong>: Most languages, including English, French, and Spanish, follow this direction.</p>
<p>  <strong>Right-to-Left (RTL)</strong>: Languages like Arabic, and Hebrew require text alignment and layout to be flipped to maintain readability and cultural context.</p>
</li>
</ul>
<p>To achieve this in our app, we set the <code>document.body.dir</code> to the <code>dir</code> from <code>i18n</code> while listening for changes in language selection using the <code>useEffect</code> hook</p>
<h3 id="heading-dark-mode-toggle"><strong>Dark Mode Toggle</strong></h3>
<p>Create the <code>DarkModeToggle</code> component to switch between light and dark mode as preferred by the user.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { SunIcon, MoonIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@heroicons/react/solid"</span>;

<span class="hljs-keyword">const</span> DarkModeToggle = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [darkMode, setDarkMode] = useState(<span class="hljs-literal">false</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Check local storage or system preference on first load</span>
    <span class="hljs-keyword">const</span> isDark =
      <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">"theme"</span>) === <span class="hljs-string">"dark"</span> ||
      (!<span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">"theme"</span>) &amp;&amp;
        <span class="hljs-built_in">window</span>.matchMedia(<span class="hljs-string">"(prefers-color-scheme: dark)"</span>).matches);
    setDarkMode(isDark);
    <span class="hljs-built_in">document</span>.documentElement.classList.toggle(<span class="hljs-string">"dark"</span>, isDark);
  }, []);

  <span class="hljs-keyword">const</span> toggleDarkMode = <span class="hljs-function">() =&gt;</span> {
    setDarkMode(!darkMode);
    <span class="hljs-built_in">document</span>.documentElement.classList.toggle(<span class="hljs-string">"dark"</span>, !darkMode);
    <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">"theme"</span>, !darkMode ? <span class="hljs-string">"dark"</span> : <span class="hljs-string">"light"</span>);
  };

  <span class="hljs-keyword">return</span> (
    &lt;button
      aria-label=<span class="hljs-string">"Toggle dark mode"</span>
      onClick={toggleDarkMode}
      className=<span class="hljs-string">"p-1 rounded"</span>
    &gt;
      {darkMode ? (
        &lt;SunIcon
          className=<span class="hljs-string">"w-6 h-6 text-yellow-500 "</span>
          onClick={toggleDarkMode}
        /&gt;
      ) : (
        &lt;MoonIcon className=<span class="hljs-string">"w-6 h-6 text-gray-900 "</span> onClick={toggleDarkMode} /&gt;
      )}
    &lt;/button&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> DarkModeToggle;
</code></pre>
<h3 id="heading-header-component"><strong>Header Component</strong></h3>
<p>The <code>Header</code> component serves as a parent component to the <code>DarkModeToggle</code> and <code>languageSelector</code> components.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> DarkModeToggle <span class="hljs-keyword">from</span> <span class="hljs-string">"./DarkModeToggle"</span>;
<span class="hljs-keyword">import</span> LanguageSelector <span class="hljs-keyword">from</span> <span class="hljs-string">"./LanguageSelector"</span>;

<span class="hljs-keyword">const</span> Header = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;header className=<span class="hljs-string">"container flex justify-between"</span>&gt;
      &lt;DarkModeToggle /&gt;
      &lt;LanguageSelector /&gt;
    &lt;/header&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Header;
</code></pre>
<h2 id="heading-step-4-main-app-component">Step 4: Main App Component</h2>
<p>In the <code>src/app</code> file, include the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useTranslation } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-i18next"</span>;
<span class="hljs-keyword">import</span> Header <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Header"</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { t } = useTranslation();

  <span class="hljs-keyword">const</span> line1 = t(<span class="hljs-string">"detail.line1"</span>);
  <span class="hljs-keyword">const</span> line2 = t(<span class="hljs-string">"detail.line2"</span>);

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"h-[100vh] bg-white text-black dark:bg-gray-900 dark:text-white py-8"</span>&gt;
      &lt;Header /&gt;
      &lt;div className=<span class="hljs-string">"container text-center max-w-2xl mt-28"</span>&gt;
        &lt;h1 className=<span class="hljs-string">"text-4xl font-bold"</span>&gt;{t(<span class="hljs-string">"greeting"</span>)}&lt;/h1&gt;
        &lt;p className=<span class="hljs-string">"mt-8"</span>&gt;{line1}&lt;/p&gt;
        &lt;p className=<span class="hljs-string">"mt-2"</span>&gt;{line2}&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<ul>
<li><p>The <code>useTranslation</code> Hook from <code>react-i18next</code> exposes the <code>t</code> function, which is used to fetch translated text.</p>
</li>
<li><p>It fetches the translated string based on a key from your translation files (for example, <code>en.json</code>, <code>fr.json</code>).</p>
</li>
</ul>
<p>By following these steps, your app should now be fully functional with translations seamlessly integrated. This is what the final result of our app looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733099671232/67419cbe-ed58-4bb4-9e44-67de2ffa9be4.png" alt="Image of the app running on localhost" class="image--center mx-auto" width="2876" height="1280" loading="lazy"></p>
<p>Check out the <a target="_blank" href="https://multilingual-demo.vercel.app/">live demo</a> and the source code on <a target="_blank" href="https://github.com/timmy471/multilingual-demo">GitHub</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating websites that give users the flexibility to select their preferred language is not just a technical achievement but a step toward making the web more inclusive and welcoming.</p>
<p>By combining internationalization (i18n) with tools like React-i18next and styling with Tailwind CSS, you can build applications that are flexible, user-friendly, and accessible to a global audience.</p>
<p>In this project, we walked through setting up i18n, adding a language switcher, and including “dark mode” for better usability.</p>
<h2 id="heading-references">References</h2>
<p><a target="_blank" href="https://react.i18next.com/">https://react.i18next.com/</a></p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=dltHi9GWMIo">https://www.youtube.com/watch?v=dltHi9GWMIo</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Support Multiple Languages In Your Flutter Application ]]>
                </title>
                <description>
                    <![CDATA[ When building my own applications, I usually don’t stress about having multiple language support. All of my applications are pet projects of mine and I mostly use them to learn and advance my knowledge. Without any intention, some of the applications... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-support-multiple-languages-in-flutter/</link>
                <guid isPermaLink="false">673783bb1fb7eac514af465b</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Fri, 15 Nov 2024 17:24:11 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/YeO44yVTl20/upload/2ec70e1bfce727903fecba0c2f9b6b8b.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When building my own applications, I usually don’t stress about having multiple language support. All of my applications are pet projects of mine and I mostly use them to learn and advance my knowledge.</p>
<p>Without any intention, some of the applications that I have published to the Google Play Store are being used by a considerable amount of people (to my sheer astonishment).</p>
<p>After patting myself on the back, I started looking at the data of the users who are interacting (or just downloaded) with my application(s). One of the insights available in the Google Play console is the country of origin of users. There, I found out that some of my applications have a loyal audience in some non-English-speaking countries.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731601377555/a9cc451f-9e8a-4084-b58a-55af1428dd51.jpeg" alt="Screenshot showing popular countries application was downloaded from" class="image--center mx-auto" width="762" height="356" loading="lazy"></p>
<p>A people pleaser by heart, I figured the best course of action would be to add support to the spoken languages at the top 3 or 4 countries on that list. That is where I discovered the wonderful world of <a target="_blank" href="https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization">Internationalizing a Flutter application</a>.</p>
<p>And that leads us to the purpose of this article: helping you understand how to add multiple language support in your Flutter application.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-localization-in-flutter">How to Set Up Localization in Flutter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-localizations-with-dynamic-values">Localizations With Dynamic Values</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-with-localization">Testing With Localization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-how-to-set-up-localization-in-flutter">How to Set Up Localization in Flutter</h2>
<p>First and foremost, you need to include two packages in your <strong>pubspec.yaml</strong> file:</p>
<ol>
<li><p><a target="_blank" href="https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html">flutter_localizations</a></p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/intl">intl</a></p>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-attr">flutter_localizations:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-attr">intl:</span> <span class="hljs-string">any</span>
</code></pre>
<p>After doing this, head over to the bottom of your <strong>pubspec.yaml</strong> file and under the <code>flutter</code> section, make sure to have the <code>generate</code> attribute set to <code>true</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">flutter:</span>
  <span class="hljs-attr">generate:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>To support this, you will need to create another <strong>.yaml</strong> file called <strong>l10.yaml</strong> with these configurations:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">arb-dir:</span> <span class="hljs-string">lib/l10n</span>   <span class="hljs-string">///</span> <span class="hljs-string">This</span> <span class="hljs-string">is</span> <span class="hljs-string">where</span> <span class="hljs-string">our</span> <span class="hljs-string">translation</span> <span class="hljs-string">files</span> <span class="hljs-string">are</span> <span class="hljs-string">located</span> <span class="hljs-string">at</span>
<span class="hljs-attr">template-arb-file:</span> <span class="hljs-string">app_en.arb</span>       <span class="hljs-string">///</span> <span class="hljs-string">Sets</span> <span class="hljs-string">the</span> <span class="hljs-string">English</span> <span class="hljs-string">template</span>
<span class="hljs-attr">output-localization-file:</span> <span class="hljs-string">app_localizations.dart</span>  <span class="hljs-string">///</span> <span class="hljs-string">Output</span> <span class="hljs-string">file</span> <span class="hljs-string">where</span> <span class="hljs-string">the</span> <span class="hljs-string">generate</span> <span class="hljs-string">command</span> <span class="hljs-string">will</span> <span class="hljs-string">generate</span> <span class="hljs-string">localizations</span>
</code></pre>
<p>☝️ Head over <a target="_blank" href="https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization#configuring-the-l10n-yaml-file">here</a> to read about more configuration options in the <strong>l10.yaml</strong> file</p>
<p>To allow your application support multiple languages, add the following to your <code>MaterialApp</code> widget:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> MaterialApp(
  title: <span class="hljs-string">'My Application'</span>,
  localizationsDelegates: [                    <span class="hljs-comment">/// <span class="markdown">From here</span></span>
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [             
    Locale(<span class="hljs-string">'en'</span>), 
    Locale(<span class="hljs-string">'hi'</span>),
  ],                                           <span class="hljs-comment">/// <span class="markdown">To here</span></span>
  home: MainScreen(),
);
</code></pre>
<p>Having defined the languages we want to support, we need to create the files with the translations for these languages.</p>
<p>Create a folder called <strong>l10</strong> under your <strong>lib</strong> directory:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731601527005/01fd2950-9a09-486b-9f5b-a23595c7f607.jpeg" alt="Showing the folder structure of the application" class="image--center mx-auto" width="362" height="159" loading="lazy"></p>
<p>Inside the folder, you need to place files with an <strong>.arb</strong> extension that will hold key-value pairs of translations. So, for example, if your application needs to support English and Hindi, you will need to create two files:</p>
<ul>
<li><p><strong>app_en.arb</strong></p>
</li>
<li><p><strong>app_hi.arb</strong></p>
</li>
</ul>
<p>The contents of these files look like this:</p>
<pre><code class="lang-json">{
        <span class="hljs-attr">"appTitle"</span>: <span class="hljs-string">"Birthday Calendar"</span>,
        <span class="hljs-attr">"settings"</span>: <span class="hljs-string">"Settings"</span>,
        <span class="hljs-attr">"addBirthday"</span>: <span class="hljs-string">"Add Birthday"</span>,
        /...
}
</code></pre>
<pre><code class="lang-json">{
        <span class="hljs-attr">"appTitle"</span>: <span class="hljs-string">"जन्मदिन कैलेंडर"</span>,
        <span class="hljs-attr">"settings"</span>: <span class="hljs-string">"सेटिंग्स"</span>,
        <span class="hljs-attr">"addBirthday"</span>: <span class="hljs-string">"जन्मदिन जोड़ें"</span>,
        /...
}
</code></pre>
<p>Basically, you have a JSON object, with key value pairs, where the keys are the same across all JSON files, but the values are written in a different language.</p>
<p>The following command is used to generate the files associated with the contents of the <strong>.arb</strong> files:</p>
<pre><code class="lang-bash">flutter gen-l10n
</code></pre>
<p>In files where you intend to use translations, you need to add the following import:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_gen/gen_l10n/app_localizations.dart'</span>;
</code></pre>
<p>To access one of the keys from the <strong>.arb</strong> files, you need to use this code:</p>
<pre><code class="lang-dart">
AppLocalizations.of(context)!.appTitle <span class="hljs-comment">//Or another key name from the .arb file</span>
</code></pre>
<p>✋ Each time that you add more key-value pairs to your <strong>.arb</strong> files, you will need to run the command in the terminal to generate those translations. Otherwise, you won’t be able to access them through the code</p>
<h2 id="heading-localizations-with-dynamic-values">Localizations With Dynamic Values</h2>
<p>Seems straightforward up to this point, right? Well, what if you have places in your application that depend on data that is dynamic and not static? For example, in one of my applications, I have a string that includes an error and that error may change depending on the invocation of an API.</p>
<pre><code class="lang-dart">AlertDialog alertDialog = AlertDialog(
      title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">"Update Failed To Install ❌"</span>),
      content:
          Text(<span class="hljs-string">"Birthday Calendar has failed to update because: \n <span class="hljs-subst">$error</span>"</span>),
      actions: [alertDialogTryAgainButton, alertDialogCancelButton],
    );
</code></pre>
<p>In order to use localized strings here, we need to create a key-value pair in our <strong>.arb</strong> file that has a placeholder for the error:</p>
<pre><code class="lang-dart"><span class="hljs-string">"updateFailedToInstallDescription"</span>: <span class="hljs-string">"Birthday Calendar has failed to update because: {error}"</span>
</code></pre>
<p>And we can use it by doing this:</p>
<pre><code class="lang-dart">AlertDialog alertDialog = AlertDialog(
      title: Text(AppLocalizations.of(context)!.updateFailedToInstallTitle),
      content:
          Text(AppLocalizations.of(context)!.updateFailedToInstallDescription(error)),  <span class="hljs-comment">/// <span class="markdown"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">---</span> <span class="hljs-attr">HERE</span></span></span></span></span>
      actions: [alertDialogTryAgainButton, alertDialogCancelButton],
    );
</code></pre>
<h2 id="heading-testing-with-localization">Testing With Localization</h2>
<p>So you added localizations to your application, but now you realize that your unit tests need to be revamped in order to accommodate for this change. We’ll break this section down into two types of tests you may have:</p>
<ul>
<li><p>Unit tests</p>
</li>
<li><p>Integration tests</p>
</li>
</ul>
<p>For one of my applications, I had a utility class that was paired with a unit test class. So far, so good. When I added localization support to that application, one of the utility methods changed, since it now had to return a value based on the localization. To do that, I had to pass over the <code>AppLocalizations</code> object as an argument to the method. That argument relied on the BuildContext:</p>
<pre><code class="lang-dart"> <span class="hljs-keyword">static</span> <span class="hljs-built_in">String</span> convertAndTranslateMonthNumber(
      <span class="hljs-built_in">int</span> month, AppLocalizations appLocalizations) {
    <span class="hljs-keyword">switch</span> (month) {
      <span class="hljs-keyword">case</span> JANUARY_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.january;
      <span class="hljs-keyword">case</span> FEBRUARY_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.february;
      <span class="hljs-keyword">case</span> MARCH_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.march;
      <span class="hljs-keyword">case</span> APRIL_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.april;
      <span class="hljs-keyword">case</span> MAY_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.may;
      <span class="hljs-keyword">case</span> JUNE_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.june;
      <span class="hljs-keyword">case</span> JULY_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.july;
      <span class="hljs-keyword">case</span> AUGUST_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.august;
      <span class="hljs-keyword">case</span> SEPTEMBER_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.september;
      <span class="hljs-keyword">case</span> OCTOBER_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.october;
      <span class="hljs-keyword">case</span> NOVEMBER_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.november;
      <span class="hljs-keyword">case</span> DECEMBER_MONTH_NUMBER:
        <span class="hljs-keyword">return</span> appLocalizations.december;
      <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
    }
  }
</code></pre>
<p>This didn’t fare so well in my corresponding unit test class, since I had to create a <code>BuildContext</code>. Since doing that in a unit test class is problematic, there is a different way to get the <code>AppLocalizations</code> object without having to rely on a <code>BuildContext</code>.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> appLocalizations = lookupAppLocalizations(<span class="hljs-keyword">const</span> Locale(<span class="hljs-string">'en'</span>))
</code></pre>
<p>This way, we state the locale we want and then we can use it in any of our tests. My unit test looks like this after the revision:</p>
<pre><code class="lang-dart">test(<span class="hljs-string">"DateService convert month number 8 to August"</span>, () {
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> monthNumber = <span class="hljs-number">8</span>;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> monthName =
        BirthdayCalendarDateUtils.convertAndTranslateMonthNumber(
            monthNumber, appLocalizations);
    expect(monthName, <span class="hljs-string">"August"</span>);
  });
</code></pre>
<p>As for integration tests, you will need to wrap your widget inside of a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Localizations/Localizations.html">Localizations widget</a>. </p>
<pre><code class="lang-dart">testWidgets(<span class="hljs-string">"Your test description"</span>,
      (WidgetTester tester) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> tester.pumpWidget(
        Localizations(
          delegates: [
            <span class="hljs-comment">//localization delegates</span>
          ],
          locale: Locale(<span class="hljs-string">'en'</span>),
          child: Widget(),
        );
    );
    <span class="hljs-comment">//Your logic here</span>
  });
</code></pre>
<p>To see how all of this is implemented inside of an application, you can go <a target="_blank" href="https://github.com/TomerPacific/BirthdayCalendar">here</a>.</p>
<p>And if you want to download the application, you can head <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.birthday_calendar">here</a>.</p>
<p>If you would like to read other articles I have written, you can find them <a target="_blank" href="https://github.com/TomerPacific/MediumArticles">here</a>.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization">Official Flutter Documentation</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Localize Your Websites with Crowdin ]]>
                </title>
                <description>
                    <![CDATA[ We just posted a course on the freeCodeCamp Community YouTube channel that will teach you how to use Crowdin, a leading cloud-based localization platform. The course was created by Estefania Cassingena Navone. She's an experienced teacher and also ru... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/localize-websites-with-crowdin/</link>
                <guid isPermaLink="false">66b20586125aeccef6f65cf2</guid>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ translation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 13 Mar 2024 18:00:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/crowdin.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We just posted a course on the freeCodeCamp Community YouTube channel that will teach you how to use Crowdin, a leading cloud-based localization platform. The course was created by Estefania Cassingena Navone. She's an experienced teacher and also runs the freeCodeCamp Español channel.</p>
<h2 id="heading-whats-crowdin">What's Crowdin?</h2>
<p>Crowdin is a cloud-based solution designed to facilitate the translation and localization process for digital content, including websites and mobile apps. It stands out for its collaborative features, allowing multiple contributors to work on a project simultaneously, ensuring both consistency and efficiency in translating and localizing content for different regions and languages.</p>
<p>This course is designed to be a comprehensive guide for anyone looking to master Crowdin and enhance their localization strategies. Whether you're a developer, a content creator, or a project manager, this course will equip you with the knowledge and skills needed to utilize Crowdin effectively.</p>
<p>Here are some key highlights of the course:</p>
<ul>
<li><strong>Introduction to Localization</strong>: Understand the fundamentals of localization and its importance in today's globalized digital world.</li>
<li><strong>Crowdin Explained</strong>: Dive deep into what Crowdin is, its ecosystem, and how it can streamline your localization process.</li>
<li><strong>Hands-on Tutorials</strong>: From creating a Crowdin account to uploading files and starting translations, the course provides step-by-step tutorials on every aspect of the platform.</li>
<li><strong>Advanced Features</strong>: Learn about Crowdin's advanced features, including translation memory, glossary, quality assurance checks, and much more.</li>
<li><strong>Practical Applications</strong>: The course covers real-world applications, like integrating Crowdin with Google Drive, GitHub, and even localizing a React app using Crowdin's powerful tools.</li>
</ul>
<p>And here is the full list of sections:</p>
<ul>
<li>Introduction</li>
<li>freeCodeCamp's Localization Effort</li>
<li>What is Localization?</li>
<li>Importance of Localization</li>
<li>Translation vs. Localization</li>
<li>Localized Resources</li>
<li>Frequently Used File Formats</li>
<li>Localization Terminology</li>
<li>Introduction to Crowdin</li>
<li>Important Terminology for Crowdin</li>
<li>Create a Crowdin Account </li>
<li>Log in and Log out</li>
<li>Create a Crowdin Enterprise Account </li>
<li>Activate the Free Plan</li>
<li>Customize your Profile</li>
<li>Create a Project</li>
<li>Project Overview</li>
<li>Project Settings</li>
<li>Delete a Project</li>
<li>Upload Files</li>
<li>Start Translating</li>
<li>Translation Editor</li>
<li>Translation Editor Modes</li>
<li>Go to Another File </li>
<li>Show and Approve Strings</li>
<li>Translate RTL Languages</li>
<li>Translation Editor Settings </li>
<li>Keyboard Shortcuts</li>
<li>Download the Translated Files</li>
<li>Translation Memory</li>
<li>Glossary</li>
<li>Quality Assurance (QA) Checks</li>
<li>Upload Existing Translations</li>
<li>Pre-Translation</li>
<li>Offline Translation</li>
<li>Invite Project Members </li>
<li>Project Managers</li>
<li>Tasks </li>
<li>Project Reports</li>
<li>Conversations </li>
<li>Integrations (Google Drive as an example)</li>
<li>More Integrations</li>
<li>Crowdin Enterprise for Organizations</li>
<li>Groups in Crowdin Enterprise   </li>
<li>Projects in Crowdin Enterprise</li>
<li>Visual Studio Code Extension  </li>
<li>Translate a React App </li>
<li>Introduction to react-i18next  </li>
<li>Crowdin GitHub Integration </li>
<li>JS Proxy Translator Integration for Static Websites</li>
<li>Crowdin In-Context Localization for Web</li>
<li>Crowdin for Contributors</li>
<li>i18next and Crowdin documentation</li>
<li>Final Words</li>
</ul>
<p>Localization is not just about translating text – it's about adapting your content to resonate with different cultures and languages. This course goes beyond the basics of translation, teaching how to leverage Crowdin to its full potential. Watch the full course on <a target="_blank" href="https://www.youtube.com/watch?v=qTEag3J1ebY">the freeCodeCamp.org YouTube channel</a> (8-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/qTEag3J1ebY" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Localize Your Django App ]]>
                </title>
                <description>
                    <![CDATA[ By Jess Wilk Have you ever wondered how websites can offer their content in multiple languages, perfectly tailored to different cultures and regions?  Well, that magic is called localization, and it's a game-changer for web development – especially w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/localize-django-app/</link>
                <guid isPermaLink="false">66d45f69d14641365a0508fd</guid>
                
                    <category>
                        <![CDATA[ Django ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 16 Nov 2023 18:07:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/08/pexels-christian-heitz-285904-842711.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jess Wilk</p>
<p>Have you ever wondered how websites can offer their content in multiple languages, perfectly tailored to different cultures and regions? </p>
<p>Well, that magic is called localization, and it's a game-changer for web development – especially when using Django, a super versatile Python framework. </p>
<p>It's not just about translating text – it's also about tuning your app to fit into different cultures, with their unique customs and preferences.   </p>
<p>So, today I'm going to guide you through localizing a Django app. Let's dive in!</p>
<h3 id="heading-prerequisites">Prerequis<strong>i</strong>tes</h3>
<p>First of all, make sure you’ve got <strong>Python</strong> installed on your machine. For this tutorial, I assume you already possess basic <strong>Django knowledge</strong> – we will move through some parts quickly. </p>
<p>If you’re not yet familiar with the basics of Django, you might want to brush up on that first. Don’t worry, Hyperskill, where I work as an expert, has got your back with some awesome <a target="_blank" href="https://hyperskill.org/tracks/11?category=1&amp;utm_source=homepage">learning tracks</a>.</p>
<h3 id="heading-heres-what-well-cover">Here's what we'll cover:</h3>
<ol>
<li><a class="post-section-overview" href="#heading-how-to-install-django">How to Install Django</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-new-django-project-and-app">How to Create a New Django Project and App</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-locale-switcher">How to Create a Locale Switcher</a></li>
<li><a class="post-section-overview" href="#heading-how-to-localize-your-app">How to Localize Your App</a></li>
<li><a class="post-section-overview" href="#heading-how-to-mark-strings-in-templates-using-trans">How to Mark Strings in Templates Using <code>{% trans %}</code></a></li>
<li><a class="post-section-overview" href="#heading-pluralization">Pluralization</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ol>
<h2 id="heading-how-to-install-django">How to Install Django</h2>
<h3 id="heading-step-1-create-a-virtual-environment">Step 1: Create a virtual environment</h3>
<p>Usually, we create a virtual environment for Django projects. This helps isolate the project from other Python projects on your machine and keep project dependencies unique. Run the <code>python -m venv myenv</code> command to create a virtual environment.</p>
<h3 id="heading-step-2-activate-the-virtual-environment">Step 2: Activate the virtual environment</h3>
<p>Run <code>source myenv/bin/activate</code> on Unix/macOS or <code>myenv\\Scripts\\activate</code> on Windows to activate the virtual environment.</p>
<h3 id="heading-step-3-install-django">Step 3: Install Django</h3>
<p>With your virtual environment active, install Django using the Python package manager pip by executing <code>pip install django</code> command.</p>
<h3 id="heading-step-4-test-the-django-installation">Step 4: Test the Django installation</h3>
<p>After installation, verify that Django is installed correctly by running <code>django-admin --version</code>. It should output the version number without any errors.</p>
<p><img src="https://lh7-us.googleusercontent.com/Up1Ue0QkBKQkvZp6YggoT-2RkQSPOZ_h8EN46rl8Z_ZqZvM5EmRANoBURpN6oU8SP6OrObUHnHJ5HXnYeZEK5DyPADhfNHb4PNu98xcdIbui8gP18wHtmzTTshLQtEz1uXFk0j1l51c94wv5wCDLVcw" alt="Image" width="600" height="400" loading="lazy"></p>
<p><strong>django-admin</strong> is the command-line script that comes with Django. It performs administrative tasks like starting a new project and handling database migrations.</p>
<h2 id="heading-how-to-create-a-new-django-project-and-app">How to Create a New Django Project and App</h2>
<h3 id="heading-step-1-create-a-django-project">Step 1: Create a Django project</h3>
<p>Let’s create a new project named localization_project using the <code>django-admin startproject localization_project</code> command.</p>
<p>This command will create a new directory called <code>localization_project</code>, containing all the necessary files for our Django project, as shown below.</p>
<p><img src="https://lh7-us.googleusercontent.com/lasC5Lapd9onrbUJPL_kd51iMMV-n4e31amdbqUl8-452gqE9LPKvw4Tj5S5yGix44fhYAReSF5erlyip6FK4_vUil8pF7zE4hvgt0OZ_emW4QnZYNVUB3MCRse50PoFVeb1QkYgkHUF8grUbTGVc24" alt="Image" width="600" height="400" loading="lazy">
_A new directory called <code>localization_project</code>_</p>
<p>To start working on this new project, go to the newly created folder by executing the command <code>cd localization_project</code>.</p>
<h3 id="heading-step-2-create-a-django-app">Step 2: Create a Django app</h3>
<p>You need an app with some content to show how to translate content. I will use the <code>python manage.py startapp homepage</code> command to create a simple app.<br>Again, manage.py is another <strong>command-line utility</strong> that acts as a thin wrapper around django-admin, letting you interact with your Django project in various ways.   </p>
<p>Once you run that, you should get another folder called the homepage with many Python files.</p>
<h3 id="heading-step-3-define-the-view-for-your-app">Step 3: Define the view for your app</h3>
<p>Open the <code>views.py</code> file in the homepage app directory and define a view for the homepage. For the sale of simplicity, our homepage will display a greeting, a number in the thousands, and the current date.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> render
<span class="hljs-keyword">from</span> django.utils <span class="hljs-keyword">import</span> timezone


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home_view</span>(<span class="hljs-params">request</span>):</span>
    context = {
        <span class="hljs-string">'greeting'</span>: <span class="hljs-string">"Welcome to our Localization Project!"</span>,
        <span class="hljs-string">'large_number'</span>: <span class="hljs-number">12345.67</span>,
        <span class="hljs-string">'current_date'</span>: timezone.now()
    }
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'home.html'</span>, context)
</code></pre>
<h3 id="heading-step-4-configure-urls">Step 4: Configure URLs</h3>
<p>First, in the <code>localization_project</code> directory, edit the <code>urls.py</code> to include the homepage app's URLs.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path, include


urlpatterns = [
    path(<span class="hljs-string">'admin/'</span>, admin.site.urls),
    path(<span class="hljs-string">''</span>, include(<span class="hljs-string">'homepage.urls'</span>)),
]
</code></pre>
<p>Then, create a <code>urls.py</code> file in the <strong>homepage app directory</strong> and set the URL for your view.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
<span class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home_view


urlpatterns = [
    path(<span class="hljs-string">''</span>, home_view, name=<span class="hljs-string">'home'</span>),
]
</code></pre>
<h3 id="heading-step-5-create-the-homepage-template">Step 5: Create the homepage template</h3>
<p>In the homepage app directory, create a folder named templates. Inside it, create a file named <code>home.html</code>. This is where you'll design your homepage. Add the following HTML code:</p>
<pre><code class="lang-python">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Localization Project&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;{{ greeting }}&lt;/h1&gt;
    &lt;p&gt;Number: {{ large_number }}&lt;/p&gt;
    &lt;p&gt;Date: {{ current_date }}&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Add the homepage to the <strong>INSTALLED_APPS</strong> list in your <code>settings.py</code> file in the <code>localization_project</code> directory.</p>
<pre><code class="lang-python">INSTALLED_APPS = [
    <span class="hljs-comment"># 'other apps',</span>
    <span class="hljs-string">'homepage'</span>,
]
</code></pre>
<p>It should be your final <code>localization_project</code> directory:</p>
<p><img src="https://lh7-us.googleusercontent.com/OYqBkb5L6wtvrQENqa89O-7F9nJBGdBRA-vb-p-4saAxE1JLFfci3VTM2RENhuB-wdUmf6TLR-QpxrEeT_QdALrNbdTQdMpKNXTwaU2nITbcb6MQgystPD9DJZunddDvX5lGjj6Rc4wpZQIj3VEwnaM" alt="Image" width="600" height="400" loading="lazy">
_A final <code>localization_project</code> directory_</p>
<h3 id="heading-step-6-run-the-development-server">Step 6 - Run the development server</h3>
<p>Finally, you can run your development server to see the homepage using the <code>python manage.py runserver</code> command.   </p>
<p>Now, when you visit http://127.0.0.1:8000/ in your web browser, you should see your simple homepage displaying a welcome message, a number in thousands, and the current date.</p>
<p><img src="https://lh7-us.googleusercontent.com/FgKfmYPAti1Q74jKY5l393qNVjGh4GlRJIDEx2n5uxMG0SB3Ru5J19DX0fmaxSAHgllrCpC3Ky8nTH9HwC3rX_wAgSv-qUSFHjFop-HSsBOgcNYuNI635B4RdkzlxVP_ZO2dHGmE3EGZ2Kh2vRFb1Rg" alt="Image" width="600" height="400" loading="lazy">
<em>A welcome message, a number in thousands, and the current date</em></p>
<h2 id="heading-how-to-create-a-locale-switcher">How to Create a Locale Switcher</h2>
<p>Typically, most websites display content in English when you visit them for the first time. </p>
<p>If you have visited a website that supports localization, you might have noticed a dropdown menu allowing users to select from the languages the website supports. Once the user selects their preferred language, the website automatically sets this as the default language and updates the content accordingly.  </p>
<p>This dropdown is called a locale switcher, which you will create next. With it, you will have an option to allow users to change the language and see the localization functionality in action, which you will do after that.</p>
<h3 id="heading-step-1-add-language-selection-form">Step 1: Add language selection form</h3>
<p>First, modify your <code>home.html</code> template to include a form for language selection. This form will contain a drop-down menu with <strong>English</strong>, <strong>Spanish</strong>, and <strong>French</strong> options. The form will be submitted to Django's built-in <code>set_language view</code>, which will handle the language change.</p>
<pre><code class="lang-python">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Localization Project&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;{{ greeting }}&lt;/h1&gt;
    &lt;p&gt;Number: {{ large_number }}&lt;/p&gt;
    &lt;p&gt;Date: {{ current_date }}&lt;/p&gt;


    &lt;form action=<span class="hljs-string">"{% url 'set_language' %}"</span> method=<span class="hljs-string">"post"</span>&gt;
        {% csrf_token %}
        &lt;input name=<span class="hljs-string">"next"</span> type=<span class="hljs-string">"hidden"</span> value=<span class="hljs-string">"{{ redirect_to }}"</span> /&gt;
        &lt;select name=<span class="hljs-string">"language"</span>&gt;
            &lt;option value=<span class="hljs-string">"en"</span>&gt;English&lt;/option&gt;
            &lt;option value=<span class="hljs-string">"es"</span>&gt;Español&lt;/option&gt;
            &lt;option value=<span class="hljs-string">"fr"</span>&gt;Français&lt;/option&gt;
        &lt;/select&gt;
        &lt;input type=<span class="hljs-string">"submit"</span> value=<span class="hljs-string">"Change Language"</span>&gt;
    &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3 id="heading-step-2-update-your-view">Step 2: Update your view</h3>
<p>In your <code>home_view</code> function in <code>views.py</code>, include the current path in the context so that the form knows where to redirect after changing the language.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.utils.translation <span class="hljs-keyword">import</span> gettext <span class="hljs-keyword">as</span> _


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home_view</span>(<span class="hljs-params">request</span>):</span>
    context = {
        <span class="hljs-string">'greeting'</span>: _(<span class="hljs-string">"Welcome to our Localization Project!"</span>),
        <span class="hljs-string">'large_number'</span>: <span class="hljs-number">12345.67</span>,
        <span class="hljs-string">'current_date'</span>: timezone.now(),
        <span class="hljs-string">'redirect_to'</span>: request.path
    }
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'home.html'</span>, context)
</code></pre>
<h3 id="heading-step-3-configure-url-for-language-changing">Step 3: Configure URL for language changing</h3>
<p>Ensure that your <code>urls.py</code> in the <code>localization_project</code> directory is set up to handle the language change. Django provides a view for this, but you must hook it up in your URL configuration.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path, include
<span class="hljs-keyword">from</span> django.conf.urls.i18n <span class="hljs-keyword">import</span> i18n_patterns


urlpatterns = [
    path(<span class="hljs-string">'admin/'</span>, admin.site.urls),
    path(<span class="hljs-string">''</span>, include(<span class="hljs-string">'homepage.urls'</span>)),
    path(<span class="hljs-string">'i18n/'</span>, include(<span class="hljs-string">'django.conf.urls.i18n'</span>)),
]
</code></pre>
<p>The i18n URL pattern includes the <code>set_language</code> redirect view.</p>
<h3 id="heading-step-4-enable-middleware-for-locale">Step 4: Enable middleware for locale</h3>
<p>Make sure that <code>LocaleMiddleware</code> is enabled in your <code>settings.py</code>. This middleware allows Django to detect the user’s language preference from the request automatically.</p>
<pre><code class="lang-python">MIDDLEWARE = [
    <span class="hljs-comment"># 'other middleware',</span>
    <span class="hljs-string">'django.middleware.locale.LocaleMiddleware'</span>,
]
</code></pre>
<h3 id="heading-step-5-run-the-development-server-and-test-the-language-switcher">Step 5: Run the development server and test the language switcher</h3>
<p>Run your development server and visit your homepage. You should now see something similar to the image below, featuring the language selection dropdown with the three languages we have chosen.</p>
<p><img src="https://lh7-us.googleusercontent.com/9yNT9lQNvc6xCh_VvgwkOhygjTW0zogXJRdwhRYUlpEqJ3lngsynnXwbzHpwpI4MPLzeey4-HPJJEW0McsNSJSeKd0kBSDdpzliUUbcSaYvTCJzak-GznTRKLqWnV7W62Kf_aOz3Gi_kfzX8GwlVsVg" alt="Image" width="600" height="400" loading="lazy">
<em>The language selection dropdown with the three languages we have chosen</em></p>
<h2 id="heading-how-to-localize-your-app">How to Localize Your App</h2>
<p>This section will show you how to translate the text on our homepage according to the local user's selections.</p>
<h3 id="heading-step-1-enable-internationalization-in-django-settings">Step 1: Enable internationalization in Django settings</h3>
<p>Before starting, ensure your Django project is set up for internationalization. In your <code>settings.py</code> file, you'll need to check and update the following settings.   </p>
<p>The first four settings in the following code probably come by default. So, you need to specify the languages you want to support in your application – in this case, English, Spanish, and French.   </p>
<p>Next, define the path to your locale directory. This is where Django will store and look for translation files. You need to create this directory manually in your project (in the same directory as the <code>manage.py</code> file). Also, remember to import the OS module at the top of the file.</p>
<pre><code class="lang-python">LANGUAGE_CODE = <span class="hljs-string">'en-us'</span>
TIME_ZONE = <span class="hljs-string">'UTC'</span>
USE_I18N = <span class="hljs-literal">True</span>
USE_TZ = <span class="hljs-literal">True</span>
LANGUAGES = [
    (<span class="hljs-string">'en'</span>, <span class="hljs-string">'English'</span>),
    (<span class="hljs-string">'es'</span>, <span class="hljs-string">'Spanish'</span>),
    (<span class="hljs-string">'fr'</span>, <span class="hljs-string">'French'</span>),
]
LOCALE_PATHS = [os.path.join(BASE_DIR, <span class="hljs-string">'locale'</span>)]
</code></pre>
<h3 id="heading-step-2-mark-text-for-translation">Step 2: Mark text for translation</h3>
<p>When developing a web application using Django, it's essential to remember that text, numbers, and dates are localized differently depending on the language and culture. This is because their content and context can vary greatly. </p>
<p>For example, text strings need to be more inherently aware of their meaning and context when used in an application. So to make your application accessible to users from different cultures, you need to mark each string that needs to be translated explicitly.   </p>
<p>On the other hand, numbers and dates are data types that Django can automatically format according to the locale. You don't have to mark them.  </p>
<p>If you check our application, you'll notice that we passed three values from our <code>views.py</code> file to the HTML template: a string, a number, and a date. While there is no need to mark the number and date for localization, you must mark the string to enable its localization. For this purpose, Django provides the <code>gettext</code> function.  </p>
<p>When localizing your app, mark any string you pass from the view to the HTML template you want to localize. In our case, we will mark the greeting text with _().</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.utils.translation <span class="hljs-keyword">import</span> gettext <span class="hljs-keyword">as</span> _
<span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> render
<span class="hljs-keyword">from</span> django.utils <span class="hljs-keyword">import</span> timezone


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home_view</span>(<span class="hljs-params">request</span>):</span>
    context = {
        <span class="hljs-string">'greeting'</span>: _(<span class="hljs-string">"Welcome to our Localization Project!"</span>),
         <span class="hljs-comment">#other data</span>
    }
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'home.html'</span>, context)
</code></pre>
<h3 id="heading-step-3-create-message-files">Step 3: Create message files</h3>
<p>Use the <code>makemessages</code> command to create language files for each language. It scans your Django project files for translation strings marked for localization and generates <code>_._po</code> files, which store the translations. Run these commands in your command line tool or terminal tool. </p>
<ul>
<li>For Spanish: <code>django-admin makemessages -l es</code></li>
<li>For French: <code>django-admin makemessages -l fr</code></li>
</ul>
<p>This will create <code>.po</code> files in the <code>locale/es/LC_MESSAGES</code> and <code>locale/fr/LC_MESSAGES</code> directories.</p>
<h3 id="heading-step-4-translate-message-files">Step 4: Translate message files</h3>
<p>Open each <code>.po</code> file and add the translation for each string under its corresponding <code>msgstr</code>. For example, in <code>locale/es/LC_MESSAGES/django.po</code>, you would add the following:</p>
<pre><code class="lang-py">msgid <span class="hljs-string">"Welcome to our Localization Project!"</span>
msgstr <span class="hljs-string">"¡Bienvenido a nuestro Proyecto de Localización!"</span>
</code></pre>
<p>And you would add the following text in <code>locale/fr/LC_MESSAGES/django.po</code>:</p>
<pre><code class="lang-py">msgid <span class="hljs-string">"Welcome to our Localization Project!"</span>
msgstr <span class="hljs-string">"Bienvenue dans notre Projet de Localisation!"</span>
</code></pre>
<h3 id="heading-step-5-compile-message-files">Step 5: Compile message files</h3>
<p>After translating, compile these files into <code>.mo</code> files, machine-readable files that Django can use. Run the <code>django-admin compilemessages</code> command to process all your <code>_._po</code> files within the project and generate corresponding <code>.mo</code> files.</p>
<h3 id="heading-step-6-run-the-server-and-test-the-translations">Step 6: Run the server and test the translations</h3>
<p>Now, test your translations using the language switcher on your website. Refresh your homepage, and you should see the greeting message in the selected language:</p>
<p><img src="https://lh7-us.googleusercontent.com/PvcQ_nIqBHwpavzd-g9XWonKSAsCeZ_Cy80nCxYNZ3pBNIthVug_u-7CGr905Dug41pfXKBoZflcHkeAYHfQI54SutLQKZcU0jw6KlhjTl353pFOz49-I-SVR82gBOYkXiJ8VlzEze4PeLf7fC77YOo" alt="Image" width="600" height="400" loading="lazy">
<em>The greeting message in Spanish</em></p>
<p><img src="https://lh7-us.googleusercontent.com/WVeCHfGGlMqSEtGIBuKtFPmFYiWNEanZv66Btk92avC-rpaBQ1XrvSBwdlphmCqDBxd5JxZ5cnoLv2wiXvaobNKPOS21p6kfUe2FbxOkG7W54onAb6Jun5c2FOn1T74HWPoNIdNOCon2cc_kSC_GHeg" alt="Image" width="600" height="400" loading="lazy">
<em>The greeting message in French</em></p>
<p>As you can see, the project's heading, <em>Welcome to our Localization Project</em>, is translated according to the language we select. You will also notice that the number and date are automatically translated into the switched language.  </p>
<p>But you can see that the words <em>Number</em> and <em>Date</em> are not translated. We didn’t mark them for translation in the previous steps.  </p>
<p>Every time you update your web application with additional content and need to localize this new content, simply follow the steps mentioned above. </p>
<p>First, mark the strings that require localization. Then, run the <code>makemessages</code> command to update your <code>.po</code> files with these new strings. Next, provide the relevant translated texts in the <code>.po</code> files for these new strings. Finally, generate the <code>.mo</code> files using the <code>compilemessages</code> command.</p>
<h2 id="heading-how-to-mark-strings-in-templates-using-trans">How to Mark Strings in Templates Using <code>{% trans %}</code></h2>
<p>As you know, the words <em>Numbers</em> and <em>Date</em> were not localized in our previous steps. Interestingly, you can’t mark them using the <code>gettext</code> method as they are not passed from the <code>views.py</code> file. These are static words in the HTML template.   </p>
<p>To localize text in Django templates, you use the <code>{% trans %}</code> template tag provided by Django. This tag instructs Django to translate the specified text into the appropriate language based on the current user's language preference.   </p>
<p>Let’s localize the <em>Number</em> and <em>Date</em> text using the <code>{% trans %}</code> template tag.</p>
<h3 id="heading-step-1-update-your-template-with-trans-tags">Step 1: Update your template with <code>{% trans %}</code> tags</h3>
<p>Open your <code>home.html</code> template and modify it to include <code>{% trans %}</code> tags around the text you want to translate. Here's an example – remember to add <code>{% load i18n %}</code> at the top of the HTML file:</p>
<pre><code class="lang-python">{% load i18n %}


&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;{% trans <span class="hljs-string">"Localization Project"</span> %}&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;{{ greeting }}&lt;/h1&gt;
    &lt;p&gt;{% trans <span class="hljs-string">"Number"</span> %}: {{ large_number }}&lt;/p&gt;
    &lt;p&gt;{% trans <span class="hljs-string">"Date"</span> %}: {{ current_date }}&lt;/p&gt;


    &lt;!-- Language Switcher Form --&gt;
    &lt;!-- ... --&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>In this example, the words <em>Localization Project</em>, <em>Number</em>, and <em>Date</em> in the template are marked for translation.</p>
<h3 id="heading-step-2-createupdate-the-language-message-files">Step 2: Create/update the language message files</h3>
<p>Run the <code>makemessages</code> command to update the <code>.po</code> files for each language.</p>
<ul>
<li>For Spanish: <code>django-admin makemessages -l es</code></li>
<li>For French: <code>django-admin makemessages -l fr</code></li>
</ul>
<h3 id="heading-step-3-translate-the-new-strings-in-the-po-files">Step 3: Translate the new strings in the <code>.po</code> files</h3>
<p>In each <code>.po</code> file, you will find the newly added strings. Add their translations under <code>msgstr</code> for each language. For example, here is the related content for the Spanish .po file:</p>
<pre><code class="lang-py"><span class="hljs-comment">#: .\homepage\templates\home.html:10</span>
msgid <span class="hljs-string">"Number"</span>
msgstr <span class="hljs-string">"Número"</span>

<span class="hljs-comment">#: .\homepage\templates\home.html:11</span>
msgid <span class="hljs-string">"Date"</span>
msgstr <span class="hljs-string">"Fecha"</span>
</code></pre>
<p>Next, run the <code>django-admin compilemessages</code> command to compile the messages files and run the development server to test the updated web application. Now, you can see the following output:</p>
<p><img src="https://lh7-us.googleusercontent.com/36WBImqij72SZsdYIff9LbyEWz2NIiKQCy5Zqh0cGfhxfTwFHh7783qZ_cnyrQ4E7asEbbAg4GMdrwssghE38mMBgIgz52j4Y_6kCPy-YzJ2398j3_PSkZVjMYHK52oj8JXnZZS0h22wXYu4PZNeigc" alt="Image" width="600" height="400" loading="lazy">
<em>The greeting message completely in Spanish</em></p>
<p><img src="https://lh7-us.googleusercontent.com/x4Dt7zWoVjaFct9qlaHOIc4BVUQjLkufn-_Efl9hr8GcQIg52XDGilPykw-C3DA3arbny8CinIHaJzGPbT7xdNmGkB19CpjXlRieSwOH4wd9gwdf8WeNJJUblGvAf2UP8pLAZw4CKpuxXzGpv5vWNvg" alt="Image" width="600" height="400" loading="lazy">
<em>The greeting message completely in French</em></p>
<h2 id="heading-pluralization">Pluralization</h2>
<p>Pluralization in Django's framework is a way to handle different translations based on a numeric value. It is essential because, in many languages, the form of a word changes depending on the number describing it. </p>
<p>Django provides a way to handle this using the <code>{% blocktrans %}</code> template tag with a plural form.  </p>
<p>Let's demonstrate this using our Django app. Suppose you want to display a message about the number of visitors on your site, which changes dynamically.</p>
<h3 id="heading-step-1-update-your-view-to-pass-number-of-visitors">Step 1: Update your view to pass number of visitors</h3>
<p>First, modify your <code>home_view</code> in <code>views.py</code> to include a variable representing the number of visitors. For demonstration purposes, this can be a static number.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> render
<span class="hljs-keyword">from</span> django.utils <span class="hljs-keyword">import</span> timezone
<span class="hljs-keyword">from</span> django.utils.translation <span class="hljs-keyword">import</span> gettext <span class="hljs-keyword">as</span> _


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home_view</span>(<span class="hljs-params">request</span>):</span>
    num_visitors = <span class="hljs-number">5</span>
    context = {
        <span class="hljs-string">'greeting'</span>: _(<span class="hljs-string">"Welcome to our Localization Project!"</span>),
        <span class="hljs-string">'num_visitors'</span>: num_visitors,
    }
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'home.html'</span>, context)
</code></pre>
<h3 id="heading-step-2-update-the-template-with-pluralization">Step 2: Update the template with pluralization</h3>
<p>In your <code>home.html</code>, use the <code>{% blocktrans %}</code> tag with a plural form to handle the pluralization, like this:</p>
<pre><code class="lang-python">&lt;!-- Other template content --&gt;

    &lt;h1&gt;{{ greeting }}&lt;/h1&gt;
    &lt;p&gt;
    {% blocktrans count counter=num_visitors %}
        There <span class="hljs-keyword">is</span> {{ counter }} visitor.
    {% plural %}
        There are {{ counter }} visitors.
    {% endblocktrans %}
    &lt;/p&gt;


    &lt;!-- Remaining template content --&gt;
</code></pre>
<p>Here, <code>{% blocktrans count counter=num_visitors %}</code> is used to handle the singular case, and the <code>{% plural %}</code> section is for the plural case.</p>
<h3 id="heading-step-3-update-the-message-files">Step 3: Update the message files</h3>
<p>Run the <code>makemessages</code> command to update your <code>_._po</code> files for each language: <code>django-admin makemessages -l es</code> for Spanish, and <code>django-admin makemessages -l fr</code> for French.</p>
<h3 id="heading-step-4-translate-and-handle-plural-forms-in-po-files">Step 4: Translate and handle plural forms in <code>.po</code> files</h3>
<p>In each <code>.po</code> file, you will find entries for the singular and plural forms. </p>
<p>For example, the Spanish <em>.po</em> file should have the following content:</p>
<pre><code class="lang-py">msgid <span class="hljs-string">"There is %(counter)s visitor."</span>
msgid_plural <span class="hljs-string">"There are %(counter)s visitors."</span>
msgstr[<span class="hljs-number">0</span>] <span class="hljs-string">"Hay %(counter)s visitante."</span>
msgstr[<span class="hljs-number">1</span>] <span class="hljs-string">"Hay %(counter)s visitantes."</span>
</code></pre>
<p>Next, run the <code>django-admin compilemessages</code> command to compile the messages files. Run your server, and you should see the message in singular or plural form depending on the number of visitors. Change the number in <code>num_visitors</code> in your view and observe how the message changes.</p>
<p><img src="https://lh7-us.googleusercontent.com/0MLly1OjdLtGZ86I5wfGxcSOw36WoQFSIR-awU40fKB1xwrMOPV7M9GlT2hAD3YFmBeFkUeSxhG7eisZ7x_SCkjbMKZWI8Hox_4Z79ggwdR362xG3By6d4f3yoplWEiCRGDZWPd5eDaAAsQSiBoUZXU" alt="Image" width="600" height="400" loading="lazy">
<em>The message in singular or plural form depending on the number of visitors. English version</em></p>
<p><img src="https://lh7-us.googleusercontent.com/efgAT1-V3Eh_7QbiOLL0KHo2rEI2xh32A2y1oWXCf8lH1TNADoFF7H1PFT3tNbzt-_N5ss1D94pOK2m6b6Cx5dqIqTcvBjpxYadPUswCS4GCUky_Wj9ZgaBu1eCvDbcs9cYVyrr--aI-CferSD7j8FE" alt="Image" width="600" height="400" loading="lazy">
<em>The message in singular or plural form depending on the number of visitors. French version</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>And there you have it – a comprehensive guide to localizing a Django app. With Django's approachable design, you've got all the necessary tools to translate numbers and DateTime values at your fingertips. Plus, we've seen how the Django-admin command line can make managing translation files a breeze. </p>
<p>But it's key to remember that localizing a website goes beyond just translating words. To fully harness the power of Django's localization capabilities, immerse yourself in its documentation, deepen your understanding, and keep on practicing. </p>
<p>Thank you for reading! I'm Jess, and I'm an expert at Hyperskill. You can check out a <a target="_blank" href="https://hyperskill.org/tracks/11?category=1&amp;utm_source=homepage">Django track</a> on the platform. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Localization Guide – How to Translate Your Website Into Different World Languages [Full Book] ]]>
                </title>
                <description>
                    <![CDATA[ Welcome! In a global world where information is available to everyone in just a few clicks, adapting your website and resources to other languages and cultures is essential to succeed. This book will teach you the fundamentals of localization and how... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/localization-book-how-to-translate-your-website/</link>
                <guid isPermaLink="false">66b1f85841fdb67461b85342</guid>
                
                    <category>
                        <![CDATA[ book ]]>
                    </category>
                
                    <category>
                        <![CDATA[ i18n ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ translation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Estefania Cassingena Navone ]]>
                </dc:creator>
                <pubDate>Thu, 28 Sep 2023 18:17:44 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/Localization-Course-Handbook-Cover-Version-4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Welcome! In a global world where information is available to everyone in just a few clicks, adapting your website and resources to other languages and cultures is essential to succeed.</p>
<p>This book will teach you the fundamentals of localization and how to translate your website to reach a global community of users without any language barrier.</p>
<h2 id="heading-where-to-start">🔹 Where to Start</h2>
<p>But where do you start?</p>
<p>That is an important question that managers often ask themselves when they decide to adapt their products and go multilingual.</p>
<p>In this book, you will learn all the fundamentals of localization from a conceptual and practical point of view.</p>
<p>You will learn how to localize files, websites, games, and any other type of resource on <a target="_blank" href="https://crowdin.com/">Crowdin</a>, the cloud-based localization management platform that powers freeCodeCamp's localization effort.</p>
<p><strong>We will cover the following:</strong> </p>
<ul>
<li><a class="post-section-overview" href="#heading-freecodecamp-as-a-real-world-example">freeCodeCamp as a Real-World Example</a></li>
<li><a class="post-section-overview" href="#heading-a-localization-effort-by-humans-for-humans">A Localization Effort by Humans, for Humans</a></li>
<li><a class="post-section-overview" href="#heading-what-are-the-fundamentals-of-localization">What are the Fundamentals of Localization?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-localization">What is Localization?</a></li>
<li><a class="post-section-overview" href="#heading-translation-vs-localization">Translation vs Localization</a></li>
<li><a class="post-section-overview" href="#heading-importance-of-localization">Importance of Localization</a></li>
<li><a class="post-section-overview" href="#heading-localization-terminologies">Localization Terminologies</a></li>
<li><a class="post-section-overview" href="#heading-translation-vs-proofreading">Translating vs Proofreading</a></li>
<li><a class="post-section-overview" href="#heading-what-types-of-resources-can-be-localized">What Types of Resources Can Be Localized?</a></li>
<li><a class="post-section-overview" href="#heading-common-file-formats">Common File Formats</a></li>
<li><a class="post-section-overview" href="#heading-localization-phases-and-roles">Localization Phases and Roles</a></li>
<li><a class="post-section-overview" href="#heading-crowdin-fundamentals-for-localization-projects">Crowdin Fundamentals for Localization Projects</a></li>
<li><a class="post-section-overview" href="#important-terminologies-for-crowdin">Important Terminologies for Crowdin</a></li>
<li><a class="post-section-overview" href="#heading-getting-started-with-crowdin">Getting Started with Crowdin</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-crowdin-account">How to Create a Crowdin Account</a></li>
<li><a class="post-section-overview" href="#heading-how-to-customize-your-crowdin-profile">How to Customize your Crowdin Profile</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-project-on-crowdin">How to Create a Project on Crowdin</a></li>
<li><a class="post-section-overview" href="#heading-project-overview">Project Overview</a></li>
<li><a class="post-section-overview" href="#heading-how-to-customize-your-project-settings-in-crowdin">How to Customize your Project Settings in Crowdin</a></li>
<li><a class="post-section-overview" href="#heading-how-to-delete-a-project-in-crowdin">How to Delete a Project in Crowdin</a></li>
<li><a class="post-section-overview" href="#heading-how-to-upload-files-to-your-crowdin-project">How to Upload Files to your Crowdin Project</a></li>
<li><a class="post-section-overview" href="#heading-how-to-start-translating">How to Start Translating</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-the-translation-editor">How to Use the Translation Editor</a></li>
<li><a class="post-section-overview" href="#heading-translation-editor-modes">Translation Editor Modes</a></li>
<li><a class="post-section-overview" href="#heading-how-to-switch-to-another-file">How to Switch to Another File</a></li>
<li><a class="post-section-overview" href="#heading-how-to-view-all-strings">How to View All Strings</a></li>
<li><a class="post-section-overview" href="#heading-how-to-translate-rtl-languages">How to Translate RTL Languages</a></li>
<li><a class="post-section-overview" href="#heading-how-to-download-the-translated-files">How to Download the Translated File(s)</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-translation-memory-tm">How to Use Translation Memory (TM)</a></li>
<li><a class="post-section-overview" href="#heading-glossary">Glossary</a></li>
<li><a class="post-section-overview" href="#heading-quality-assurance-qa-checks-in-crowdin">Quality Assurance (QA) Checks in Crowdin</a></li>
<li><a class="post-section-overview" href="#heading-how-to-upload-existing-translations">How to Uploading Existing Translations</a></li>
<li><a class="post-section-overview" href="#heading-how-to-pre-translate-your-project">How to Pre-Translate your Project</a></li>
<li><a class="post-section-overview" href="#heading-offline-translation">Offline Translation</a></li>
<li><a class="post-section-overview" href="#heading-exploring-public-projects">Exploring Public Projects</a></li>
<li><a class="post-section-overview" href="#heading-crowdin-for-teams-and-organizations">Crowdin for Teams and Organizations</a></li>
<li><a class="post-section-overview" href="#heading-how-to-invite-project-members-and-contributors">How to Invite Project Members and Contributors</a></li>
<li><a class="post-section-overview" href="#heading-project-roles">Project Roles</a></li>
<li><a class="post-section-overview" href="#heading-how-to-assign-or-change-roles">How to Assign or Change Roles</a></li>
<li><a class="post-section-overview" href="#heading-project-managers">Project Managers</a></li>
<li><a class="post-section-overview" href="#heading-tasks">Tasks</a></li>
<li><a class="post-section-overview" href="#heading-project-reports">Project Reports</a></li>
<li><a class="post-section-overview" href="#heading-conversations-on-crowdin">Conversations on Crowdin</a></li>
<li><a class="post-section-overview" href="#heading-crowdin-integrations-and-productivity-tools">Crowdin Integrations and Productivity Tools</a></li>
<li><a class="post-section-overview" href="#heading-how-to-translate-a-website-on-crowdin">How to Translate a Website on Crowdin</a></li>
<li><a class="post-section-overview" href="#heading-freecodecamps-translation-effort">freeCodeCamp's Translation Effort</a></li>
</ul>
<p>Are you ready? Let's begin!</p>
<h2 id="heading-freecodecamp-as-a-real-world-example">🔹 freeCodeCamp as a Real-World Example</h2>
<p>freeCodeCamp.org is a real-world example of an organization and open-source project that has embraced the concept of localization for reaching a global community.</p>
<p>Our coding curriculum is available in many languages, including:</p>
<ul>
<li>English.</li>
<li>Spanish.</li>
<li>Chinese.</li>
<li>Italian.</li>
<li>Portuguese.</li>
<li>Ukrainian.</li>
<li>Japanese.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/freecodecamp-language-dropdown-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>How to Choose a Language on freeCodeCamp.</em></p>
<p>Our community is actively working on translating freeCodeCamp into many world languages, including:</p>
<ul>
<li>Arabic.</li>
<li>Azerbaijani.</li>
<li>Bengali.</li>
<li>Chinese Simplified.</li>
<li>Dutch.</li>
<li>French.</li>
<li>German.</li>
<li>Hebrew.</li>
<li>Hindi.</li>
<li>Indonesian.</li>
<li>Italian.</li>
<li>Japanese.</li>
<li>Korean.</li>
<li>Nepali.</li>
<li>Persian.</li>
<li>Portuguese.</li>
<li>Romanian.</li>
<li>Spanish.</li>
<li>Swahili.</li>
<li>Turkish.</li>
<li>Ukrainian.</li>
<li>Urdu, and more.</li>
</ul>
<p>We have many world languages available for localization and we also run localized publications, YouTube channels, forums, and so on.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/freecodecamp-crowdin-project.png" alt="Image" width="600" height="400" loading="lazy">
<em>Coding Curriculum localization project and available languages.</em></p>
<h2 id="heading-a-localization-effort-by-humans-for-humans">🔹 A Localization Effort by Humans, for Humans</h2>
<p>Our localization process is focused on what matters the most: our amazing community of learners who wake up every day excited about learning new skills.</p>
<p>We believe that language and culture should not be barriers to learning. Knowledge should be accessible worldwide.</p>
<p>This is why we started this process and why we'll continue our localization efforts until we reach our goal of guaranteeing access to knowledge around the world.</p>
<p>One of the key aspects of our localization process is that it is managed and run by humans, for humans. Translations are written and approved by members of our amazing community and staff.</p>
<p>Let's be honest, anyone can tell when a translation has been generated automatically. It's much more literal, it lacks the clarity, and it feels disconnected from the context and from the original tone of the text.</p>
<p>Human translators are much better at adapting languages, and translating sentences in a way that sounds more natural in different languages and cultures.</p>
<p>At freeCodeCamp, we have an amazing community of contributors who dedicate their time to translate our content and an amazing team of staff members who oversee the process with the ultimate goal of publishing high-quality translations for our learners.</p>
<p>Since we launched our localization effort, more than 2,000 translators and over 60 proofreaders have helped us accomplish our mission.</p>
<p>💡 <strong>Tip:</strong> If this sounds interesting to you and you would like to join freeCodeCamp's translation effort, please read our <a target="_blank" href="https://contribute.freecodecamp.org/#/index">contributing guidelines</a>. At the end of this article, you will find more information on our localization effort.</p>
<p>Managing such a large project with a worldwide community of contributors might seem complicated, right? How can we accomplish all of this as a non-profit organization?</p>
<p>You will get answers to those questions in this book.</p>
<p>We will cover all the fundamentals of localization, and the basic and advanced features of <a target="_blank" href="https://crowdin.com/">Crowdin</a>.</p>
<p>Are you ready? Let's begin.</p>
<h2 id="heading-what-are-the-fundamentals-of-localization">🔹 What are the <strong>Fundamentals</strong> of <strong>Localization?</strong></h2>
<p>We will start with an overview of the fundamentals of the localization process and the steps that you'll need to take to make sure that your product can be used without any language or cultural barriers.</p>
<h3 id="heading-what-is-localization">What is Localization?</h3>
<p>First of all, let's define <strong>localization</strong>.</p>
<p>According to the <a target="_blank" href="https://dictionary.cambridge.org/dictionary/english/localization">Cambridge Dictionary</a>, localization is defined as:</p>
<blockquote>
<p>The process of organizing a business or industry so that its main activities happen in local areas rather than nationally or internationally.</p>
</blockquote>
<p>In the context of products and services, localization basically means adapting them to the language and culture of a specific population. This also applies to software products because they need to be adapted to different cultures as well.</p>
<h3 id="heading-translation-vs-localization">Translation vs Localization</h3>
<p>You may be surprised to know that the concept of localization is different from the concept of translation. It's actually broader.</p>
<p>The <a target="_blank" href="https://dictionary.cambridge.org/dictionary/english/translation">Cambridge Dictionary</a> defines translation as:</p>
<blockquote>
<p>The activity or process of changing the words of one language into the words in another language that have the same meaning.</p>
</blockquote>
<p>Notice the key part of this definition: "changing the words to keep the same meaning."</p>
<p>Translation involves changing the words of a language into another language to keep their original meaning. It is very helpful but a bit limited because its goal is to say exactly the same thing in a different language.</p>
<p>However, localization can go beyond that to adapt the content better to another culture or country. </p>
<p>For example, localization is particularly helpful for marketing campaigns and ads that try to reach audiences and convince them to purchase their products. Certain cultures may accept certain colors better, or they may have local phrases or slangs that the local population is more familiar with.</p>
<p>In this case, localizing the campaign is better than writing a literal word-for-word translation.</p>
<p><strong>Continuous localization</strong> takes this concept one step further. It involves localizing a product continuously as it is updated or expanded in an agile product development cycle. It is often used to localize software products.</p>
<h2 id="heading-importance-of-localization">Importance of Localization</h2>
<p>Why should you localize your product or platform? Well, the world is very diverse, and different cultures have their distinctive customs and speak different languages. </p>
<p>Did you know that, according to the <a target="_blank" href="https://www.linguisticsociety.org/content/how-many-languages-are-there-world">Linguistic Society of America</a>, there are more than 6,000 languages in the world?</p>
<p>Among the top 20 most widely spoken languages in the world, we can find these:</p>
<ul>
<li>English.</li>
<li>Mandarin Chinese.</li>
<li>Hindi.</li>
<li>Spanish.</li>
<li>French.</li>
<li>Arabic.</li>
<li>Bengali.</li>
<li>Portuguese.</li>
<li>Urdu.</li>
<li>German.</li>
<li>Japanese.</li>
</ul>
<p>💡 <strong>Tip:</strong> You can find more information on languages by total number of speakers in <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_languages_by_total_number_of_speakers">this article</a>.</p>
<p>A large proportion of the world's population is not bilingual. Not everyone around the world has the opportunity to learn and master English as a second language but every single person around the world is a potential user of your product or platform. </p>
<p>That is why localization can be so important for you.</p>
<p>For example, if you are creating an educational platform, you will be able to reach people and accomplish your mission at a global scale by localizing your website and content.</p>
<p>If you are building a commercial product or platform, every single person around the world can be a potential user. </p>
<p>Don't let language become a barrier to reach your users. Localization can be your best ally. </p>
<h2 id="heading-localization-terminologies">Localization Terminologies</h2>
<p>Now that you know why localization is so important, let's dive into some important terminologies that you'll come across very often in the context of translation and localization.</p>
<h3 id="heading-internationalization">Internationalization</h3>
<blockquote>
<p>The action of becoming or making something become international.<br>— <a target="_blank" href="https://dictionary.cambridge.org/dictionary/english/internationalization">Cambridge Dictionary</a>.</p>
</blockquote>
<p>In the context of localization for a software product, it also involves adapting the user interface for working with other languages and making sure that it is ready to be translated.</p>
<h3 id="heading-culturalized">Culturalized</h3>
<blockquote>
<p>Deriving from or imposed or conditioned by culture.<br>— <a target="_blank" href="https://www.merriam-webster.com/dictionary/culturalized">Merriam-Webster Dictionary</a>.</p>
</blockquote>
<p>Every culture has different traditions and vocabulary. Culture can play a key role in how communities embrace products, campaigns, and platforms. Understanding how to adapt them is very important to succeed.</p>
<h3 id="heading-pseudolocalization">Pseudolocalization</h3>
<p>The <em>pseudo</em> prefix is defined as:</p>
<blockquote>
<p>Pretended and not real.<br>— <a target="_blank" href="https://dictionary.cambridge.org/dictionary/english/pseudo">Cambridge Dictionary</a>.</p>
</blockquote>
<p>That is exactly what pseudolocalization is all about. It is a process creating fake translations that act as placeholders for the real translations in a platform or product.</p>
<p>You may ask: "Why would we ever need to use fake translations?"</p>
<p>The answer is that we use them to check if our software is ready to handle multiple languages even before the translation process begins.</p>
<p>Checking if a language that tends to have longer or shorter words works well with our current user interface and checking if right-to-left languages are displayed correctly are common use cases.</p>
<p>This process is also helpful to find any strings that may still be hard-coded in the project source files. You may need to move them to the resources file where you keep all your project strings.</p>
<p>That is the main purpose of pseudolocalization: checking if everything is ready to start translating.</p>
<h3 id="heading-machine-translation-mt">Machine Translation (MT)</h3>
<blockquote>
<p>The process of using artificial intelligence to automatically translate text from one language to another without human involvement.<br>— <a target="_blank" href="https://aws.amazon.com/what-is/machine-translation/">Amazon Web Services</a>.</p>
</blockquote>
<p>We will talk about this term in more detail because the localization management platform that we will use to translate our resources has this feature, and it can save us a lot time. </p>
<h3 id="heading-translation-memory-tm">Translation Memory (TM)</h3>
<blockquote>
<p>A database that stores "segments", which can be sentences, paragraphs or sentence-like units (headings, titles or elements in a list) that have previously been translated, in order to aid human translators.<br>— <a target="_blank" href="https://en.wikipedia.org/wiki/Translation_memory">Wikipedia</a>.</p>
</blockquote>
<p>With this feature, you can save previous translations and "reuse" them to save time.</p>
<p>💡 <strong>Tip:</strong> Note that the acronyms (MT and TM) are very similar and but they are different. Please take a moment to understand the differences between these two concepts because you'll see them in this book frequently.</p>
<h3 id="heading-large-language-models-llms">Large Language Models (LLMs)</h3>
<blockquote>
<p>Deep learning algorithms that can recognize, summarize, translate, predict, and generate content using very large datasets.<br>— <a target="_blank" href="https://www.nvidia.com/en-us/glossary/data-science/large-language-models/">Nvidia</a>.</p>
</blockquote>
<p>These terms are fundamental if you are looking to dive into translation and localization.</p>
<p>You may also find words that use numbers to represent abbreviations. They are called numeronyms.</p>
<ul>
<li><strong>L10n</strong>: this numeronym stands for Localization. The number 10 stands for the 10 letters between the <strong>l</strong> at the start and the <strong>n</strong> at the end.</li>
<li><strong>i18n</strong>: this numeronym stands for Internationalization (yes, it's a very long word!). The number 18 stands for the 18 letters between the <strong>i</strong> at the start and the <strong>n</strong> at the end.</li>
</ul>
<p>💡 <strong>Tip:</strong> Sometimes, you may find L10n with the L capitalized or you may find it in lowercase, like this: l10n. Capitalizing the L is helpful to distinguish it from the i in the i18n numeronym (they can look very similar in certain types of fonts).</p>
<h2 id="heading-translation-vs-proofreading">Translation vs Proofreading</h2>
<p>Another important aspect that you should also know is the difference between translating and proofreading. </p>
<p><strong>Translation</strong> involves changing the words from one language to another with the goal of keeping the same meaning.</p>
<p>But after translators have completed their work, the localization team will also need to review, edit, and approve the translations to make sure that everything is accurate and correct. This process of checking the translations is called <strong>proofreading</strong>.</p>
<p>Translating and proofreading are different stages of the localization process. We'll look these processes in more detail, and you will learn the steps involved and the role of team members in making sure that the content is localized correctly.</p>
<h2 id="heading-what-types-of-resources-can-be-localized">What Types of Resources Can Be Localized?</h2>
<p>When we talk about translation and localization, the first thing that comes to mind is translating files with text, right?</p>
<p>But this is not the only type of resource that we can localize. We can localize documents, spreadsheets, websites, games, dialogues, scripts, audio, video, graphics, and so on.</p>
<p>Think about podcasts and videos. They can be localized with voiceovers. We just need to translate their transcripts, replace the original audio and synchronize the translated narration.</p>
<p>Captions and subtitles can also be localized. This is a form of text too but it comes from a video source. You can see how different types of files can be closely related in the localization process.</p>
<p>Finally, we can localize graphics such as image files, visual marketing campaigns, ads, and more. </p>
<p>The main point to highlight here is that localization and translation are not limited to written resources. There are wide variety of resources that we can localize to reach a wider audience. </p>
<h2 id="heading-common-file-formats">Common File Formats</h2>
<p>In the last section, we talked about different types of resources that can be translated. Now let's talk about the file formats that you'll usually find in the context of translation. You may also find them in the localization management platform that we will be working on. </p>
<p>💡 <strong>Tip:</strong> Even if you do not use these file formats right now, it's always helpful to understand what they do and what they represent. They may come in handy in the future, or in cases where you find them in the documentation of a localization tool you are using.</p>
<h3 id="heading-comma-separated-values-csv-files">Comma-Separated Values (CSV) Files</h3>
<ul>
<li>File extension: <code>**.csv**</code></li>
<li>This is a text file format in which the values are separated by commas.</li>
<li>Stores tabular data such as numbers and text.</li>
<li>Each line usually represents one record.</li>
<li>Commonly used for data exchange and can be processed using programming languages.</li>
</ul>
<h3 id="heading-html-files">HTML Files</h3>
<ul>
<li>File extension: <code>**.html**</code></li>
<li>HTML stands for HyperText Markup Language.</li>
<li>It is used to represent the structure and content of a website.</li>
<li>If you open this file in a browser, you will see the content of the website.</li>
</ul>
<h3 id="heading-json-files">JSON Files</h3>
<ul>
<li>File extension: <code>**.json**</code></li>
<li>JSON stands for JavaScript Object Notation. </li>
<li>Stores data in a simple plain text format based on key-value pairs.</li>
<li>Used for data exchange, especially across the web because it is lightweight. </li>
</ul>
<h3 id="heading-markdown-files">Markdown Files</h3>
<ul>
<li>File extension: <code>**.md**</code></li>
<li>Used to create formatted text. </li>
<li>It is a lightweight markup language with a specific syntax.</li>
<li>Common applications include writing software documentation, blog posts, and articles.</li>
</ul>
<h3 id="heading-po-portable-object-files">PO (Portable Object) Files</h3>
<ul>
<li>File extension: <code>**.po**</code>.</li>
<li>Used by the <code>**gettext**</code> system, which is commonly used for writing multilingual programs. It is also widely used in the implementation of <code>**GNU gettext**</code>.</li>
<li><code>**gettext**</code> is a standard in many game development engines, like the Unreal Engine. It is used in many programming languages, including C, C++, PHP, and Python.</li>
</ul>
<h3 id="heading-text-file">Text File</h3>
<ul>
<li>File extension: <code>**.txt**</code></li>
<li>Used to store plain text. </li>
<li>It does not contain images or non-text characters.</li>
</ul>
<h3 id="heading-extensible-markup-language-xml">Extensible Markup Language (XML)</h3>
<ul>
<li>File extension: <code>**.xml**</code></li>
<li>Used to store, share, and reconstruct arbitrary data.</li>
<li>Commonly used to exchange data over the internet. </li>
<li>Many localization frameworks use XML. For example, Android uses an XML-based file format to store translatable text.</li>
</ul>
<h3 id="heading-xliff-files">XLIFF Files</h3>
<ul>
<li>File extension: <code>**.xliff**</code></li>
<li>XLIFF stands for XML Localization Interchange File Format.</li>
<li>Uses XML-based format.</li>
<li>Used to standardize the way the localizable data can be passed between localization tools.</li>
</ul>
<h3 id="heading-xlsx-files">XLSX Files</h3>
<ul>
<li>File extension: <code>**.xlsx**</code></li>
<li>Used to store data in spreadsheets.</li>
<li>It is an abbreviation of "Microsoft Excel Spreadsheet".</li>
</ul>
<h3 id="heading-resx-files">RESX Files</h3>
<ul>
<li>File extension: <code>**.resx**</code></li>
<li>Used by .NET applications for storing resources that can be localized.</li>
<li>Uses an XML-based file format.</li>
</ul>
<p>These are the most widely used file formats that you may find in localization projects but there are over hundreds of file formats that you can use.</p>
<p>Crowdin, the localization management platform that we will use on this book, supports more than 100+ file formats.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/supported-formats.png" alt="Image" width="600" height="400" loading="lazy">
<em>Overview of the <a target="_blank" href="https://store.crowdin.com/categories/file-formats">supported formats</a> in Crowdin.</em></p>
<h2 id="heading-localization-phases-and-roles">Localization Phases and Roles</h2>
<p>Awesome. Now that you know more about the types of files that you might find in a localization project, let's take a step further and see this process from a project management perspective.</p>
<p>What steps are required for localizing a project? Where should you start? Here are steps to help you answer those questions:</p>
<h3 id="heading-step-1-define-the-project-scope-and-goals">Step #1 - Define the project scope and goals</h3>
<p>Before you start to localize any file, it's important to take a moment to analyze your project's scope and goals. </p>
<p>Ask yourself:</p>
<ul>
<li>What is your target audience?</li>
<li>What are you trying to achieve by localizing your resources?</li>
<li>How will you achieve those goals?</li>
<li>What parts of your website, game, video, or resource do you need to localize?</li>
<li>Do you need to translate it or localize it (adapt it)? Sometimes, translation can be good enough.</li>
<li>If you need to adapt it to other cultures, how will you achieve this? </li>
<li>Will you seek advice from people who understand these cultures? If yes, how will you contact them?</li>
<li>If you speak other languages, can you translate the resources yourself or do you need to find help based on the complexities of your project? </li>
<li>What is your available budget? </li>
<li>Are your goals realistic given your current budget?</li>
</ul>
<p>You should also determine who is going to translate your resources:</p>
<ul>
<li>Will you be translating the resources yourself?</li>
<li>Will you involve your user-base or community in the translation effort?</li>
<li>Will you hire a team or use the services of a translation vendor? </li>
<li>Will you use automated processes like machine translation to translate or pre-translate resources with artificial intelligence?</li>
</ul>
<p>💡 <strong>Tip:</strong> Crowdin has an option to hire their localization services and services from their partners in the Crowdin Marketplace.</p>
<p>Defining clear and realistic goals can be very helpful for avoiding any unexpected challenges during the process.</p>
<p>Write down your goals and make sure that you have an outline of the steps that you need to take to start, execute, and complete the localization process.</p>
<h3 id="heading-step-2-create-the-source-files-to-be-localized">Step #2 - Create the source files to be localized</h3>
<p>Now that you have clear goals and a clear project scope in mind, having the project source files is a must. These are the files that your localization team will localize.</p>
<p>You should make sure that you have all the necessary source files and resources before you start the localization process. </p>
<p>Of course, you can always add new resources and content later in the process, but having a clear initial idea of the complexities of a project will be helpful later on in terms of the time management and budget required.</p>
<h3 id="heading-step-3-prepare-your-software-for-internationalization">Step #3 - Prepare your Software for Internationalization</h3>
<p>Before the localization process begins, you need to prepare your product for internationalization, which is very technology-specific.</p>
<p>This is especially true for software products. The tools that you use to internationalize a React.js application may be very different from the tools that you use for an Android application, an iOS application, or a game. </p>
<p>However, the approaches and concepts that you will use are essentially the same.</p>
<p>You need to think about how you will adapt your user interface and services to other languages. For example, some languages may have longer or shorter words than the source language and this can change how elements are displayed. </p>
<p>Making sure that everything looks like you intend it to is very important, even before translators and proofreaders get involved in the process.</p>
<p>Another key step is making sure that you have all your translatable text separated from your code. When you translate software, all translatable text is extracted into a resource file that can be shared with translators or uploaded to a localization management platform.</p>
<p>The Crowdin team recommends storing larger files, such as HTML pages, and email templates, in a separate directory and keeping one directory per target locale. They suggest that "if you are translating your content into 5 target languages, you would have 5 copies of your resource files with "UI labels" and 5 directories with all other assets like HTML files."</p>
<p>If you are developing a web application, you will also need to implement multilingual routing. Your application should allow users to select their preferred language. </p>
<p>To do this, you have two options. You can:</p>
<ul>
<li>Add the language code as part of the domain name. For example: <code>**fr.example.com**</code>.</li>
<li>Add the language code to the URL. For example: <strong><code>example.com/fr</code></strong>. </li>
</ul>
<p>This is recommended for Search Engine Optimization (SEO) purposes.</p>
<p>Your software should also be able to handle and display adapted numbers, dates, and currencies since localization may also involve adapting them to different formats for different cultures.</p>
<p>Context will also be very important. Many internationalization tools create resource files with only one key-value pair for each piece of text. They associate each piece of text in the source language with its corresponding translation.</p>
<p>But it is very important to make sure that the resource files of your project also include some contextual information of the content or elements around them. This can be very helpful for translators because they can choose the best translations possible based on the context around the string.</p>
<p>Finally, your application should also be able to handle pluralization correctly because different languages may have different plural forms. </p>
<p>💡 <strong>Tip:</strong> Some of these features may be available with the software development kit (SDK) that you are using, but you may need to add some of them using third-party libraries. It's always important to consider and check this.</p>
<h3 id="heading-step-4-assemble-a-team">Step #4 - Assemble a Team</h3>
<p>If you analyze the scope of the project and decide that you cannot complete it by yourself, then it's time to assemble a team.</p>
<p>You may hire a team or, if you are a non-profit organization like freeCodeCamp, use crowdsourcing to ask your community for help with the translations. You may be surprised by the number of generous and kind members of your community who will be willing to help you achieve your goals.</p>
<p>Once you have your team, you can assign them roles:</p>
<ul>
<li><strong>Translators</strong> use the localization management platform you choose to translate the resources. </li>
<li><strong>Proofreaders</strong> review, edit, and approve the translations. It's always helpful to review the translations to fix any typos or inconsistencies.</li>
<li><strong>Developers</strong> work on integrating the tools you choose to automate the localization process.</li>
<li><strong>Project Managers</strong> coordinate the tasks of the project. They assign translators and proofreaders to specific tasks to make sure that the project moves forward as fast as possible. </li>
</ul>
<h3 id="heading-step-5-choose-the-localization-tools">Step #5 - Choose the Localization Tools</h3>
<p>Choosing the right localization tool can be essential for reaching your goals. In the world of localization, there is a tool called Translation Management System (TMS).</p>
<p>This type of system is designed to help you automate repetitive tasks with the goal of optimizing your team's workflow. Humans will still have a role to play in the localization process, but with the help of a translation management system, they can achieve their goals much faster. </p>
<p>Usually, these systems can be integrated with content management systems (CMS) to import content automatically from other platforms, such as blogging platforms. Once it is imported, you can localize it and export it in order to publish the translated versions.</p>
<p>With the proper integrations, translation management systems can also check if there have been changes in the source files and import the new content automatically to start localizing it. </p>
<p>A real-world example of this process is right next to us — freeCodeCamp translates its source files in Crowdin. When a file from freeCodeCamp's curriculum changes, the new content is updated automatically in the system, so contributors can translate it and publish it very quickly. </p>
<p>Automating this process can be very helpful, especially for large organizations with different projects and files, so you do not have to keep track of these changes manually. </p>
<h3 id="heading-step-6-translate-the-resources">Step #6 - Translate the Resources</h3>
<p>If you already chose a translation management system or another helpful tool, then it's time to start translating the resources.</p>
<p>Usually, these platforms will divide the source content into what they call "strings", which are parts of the original text that you can translate. Translators will translate the strings and save their translations. </p>
<p>The software will take care of storing and combining the strings to replace them in the correct place in your file. </p>
<h3 id="heading-step-7-proofread-the-translations">Step #7 - Proofread the Translations</h3>
<p>Proofreading is one of the most important parts of the process because it's like the last quality assurance step made by humans. </p>
<p>Proofreaders should check if the translations are accurate, and if there is a better way to adapt them to the culture or language. They can also check if there are typos or misspelled words, and if the correct format is used. They can edit and approve the translated strings.</p>
<p>Sometimes they may find an extra comma, a missing emoji, an extra space, or a missing letter and those small details really count for the user experience, so this step should be taken very seriously. </p>
<h3 id="heading-step-8-export-the-localized-resources">Step #8 - Export the Localized Resources</h3>
<p>After proofreading and approving all the translations, the next thing to do is to export the final localized resources.</p>
<p>If your project is small, you may choose to do this manually. But if your project is more complex, you may choose to automate this process with different integrations on your localization management system.</p>
<p>For example, Crowdin has integrations with different platforms, including GitHub, Google Drive, Google Sheets, Dropbox, MailChimp, and so on.</p>
<p>If your translations are ready and approved and you set up a GitHub integration, the translated files will be updated automatically in your project's repository. You can even configure where the translated files will be stored.</p>
<h3 id="heading-step-9-check-for-changes">Step #9 - Check for Changes</h3>
<p>Projects and platforms can evolve over time. Files can change as you add new features and content. This is especially true for freeCodeCamp since we add new content and update our existing content on a regular basis. </p>
<p>So how can we handle these changes and still keep our platform properly localized?</p>
<p>Thanks to Crowdin, we can use integrations to be notified of the changes made to files and we can know if we have new strings to be translated. </p>
<p>When this happens, our amazing team of contributors and staff members will start translating and proofreading the new strings, repeating this cycle every time we need to bring the translation percentage back to 100%.</p>
<h2 id="heading-crowdin-fundamentals-for-localization-projects">🔹 <strong>Crowdin Fundamentals for Localization Projects</strong></h2>
<p>Now that we covered the fundamental concepts of localization, we'll use them in the localization management platform that powers freeCodeCamp's localization effort.</p>
<h3 id="heading-what-is-a-localization-management-platform">What is a Localization Management Platform?</h3>
<p>This is a platform that helps you and your team to localize your resources, products, and platforms efficiently through automation, cloud-based services, and integrations with other platforms.</p>
<p>We talked about translation management platforms before, right?</p>
<p>Localization management platforms are very similar but they help you to localize your products, which is even broader than just translating the text word by word. </p>
<h3 id="heading-what-is-crowdin">What is Crowdin?</h3>
<p>Crowdin is a localization management platform that can be described as:</p>
<blockquote>
<p>A cloud-based solution that streamlines localization management for your team. (<a target="_blank" href="https://crowdin.com/">Source: Crowdin</a>)</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-landing-page.png" alt="Image" width="600" height="400" loading="lazy">
<em>Crowdin's landing page.</em></p>
<p>The team mentions that, "It's the perfect place to effectively manage all of your multilingual content. It allows you to streamline the localization process and keep your workflow agile."</p>
<p>This platform is also great for teams and organizations who are planning to localize their content into multiple languages.</p>
<p>This is <a target="_blank" href="https://crowdin.com/">Crowdin's official website</a> in case you would like to check it out:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://crowdin.com/">https://crowdin.com/</a></div>
<p>You will be applying your knowledge of localization on this platform, and you'll even learn how to localize a website in just a few minutes with Crowdin's services and integrations. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-workflow.png" alt="Image" width="600" height="400" loading="lazy">
<em>Crowdin Workflow. Image taken from Crowdin's <a target="_blank" href="https://crowdin.com/">official website</a>.</em></p>
<h3 id="heading-the-founder-of-crowdin">The Founder of Crowdin</h3>
<p><a target="_blank" href="https://crowdin.com/page/about-crowdin">Serhiy Dmytryshyn</a> is the founder and CEO of Crowdin. He launched the company in 2009 and it now has over 2 million registered users in over 160 countries.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Founder-and-CEO-of-Crowdin.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://crowdin.com/page/about-crowdin">Serhiy Dmytryshyn</a>, founder and CEO of Crowdin.</em></p>
<p>We had the opportunity to meet with him and ask him how he would describe Crowdin in five words. His answer was:</p>
<blockquote>
<p>Continuous Localization for Modern Companies.<br>— Serhiy Dmytryshyn</p>
</blockquote>
<p>His vision is for Crowdin to be the best platform for localizing products that are constantly evolving and for projects that may never have a final version because they will be continuously improved, expanded and updated, such as software products.</p>
<p>freeCodeCamp is an example of this. We are constantly adding and updating our content, which means that we also need an efficient and agile localization process to keep our platform accessible and updated for our global community.</p>
<p>The <a target="_blank" href="https://crowdin.com/page/about-crowdin">main goal</a> of Crowdin is to:</p>
<blockquote>
<p>Expand the potential of agile localization.</p>
</blockquote>
<p>But what is agile localization? Let's see.</p>
<h3 id="heading-what-is-agile-localization">What is Agile Localization?</h3>
<p>Agile localization is a process in which localization is incorporated into an agile product development cycle, with the goal of localizing the product as quickly as possible as it evolves.</p>
<p><strong>💡 Tip:</strong> An agile product development cycle is a cycle in which a product is constantly being updated in an iterative approach.</p>
<p>An agile localization process differs from the traditional localization process in that the translations are not only written once and then added to the final product. They are continuously added and updated as the product changes.</p>
<p>This sound great, right?</p>
<p>But constant updates also require constant management, team work, file uploads and downloads, platform deployments, and so on. </p>
<p>This process could become complicated very quickly if your team does not have the right tools, but with a localization management platform like Crowdin, you and your team can save time and accomplish your goals more efficiently.</p>
<h3 id="heading-advantages-of-crowdin">Advantages of Crowdin</h3>
<p>Let's see some of the reasons why you should use Crowdin:</p>
<ul>
<li>You can connect your project with external services through integrations to automate part of the localization process.</li>
<li>You can store your translations on their cloud-based services and grant access to team members and contributors from all around the world. </li>
<li>You can generate machine translations automatically when a resource is created and ask translators to check and edit them to save time.</li>
<li>Your team can check the quality and format of the translations with Crowdin's quality assurance, spellchecking, and proofreading features.</li>
<li>You can generate reports, communicate with team members internally, assign roles and permissions, invite new members, and much more.</li>
</ul>
<p>Basically, it's a platform that will make the localization process much easier for you and your team.</p>
<h3 id="heading-crowdins-free-plan">Crowdin's Free Plan</h3>
<p>One great thing about Crowdin is that they offer a completely free plan with all the essential features for translators to start localizing their content.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-free-plan.png" alt="Image" width="600" height="400" loading="lazy">
<em>Crowdin's free features.</em></p>
<p>Yes, it's free! You only need to create an account and you will be able to: </p>
<ul>
<li>Create unlimited public projects that everyone can see and contribute to. </li>
<li>Add unlimited translators to your public projects.</li>
<li>Create one private project.</li>
<li>Host up to 60,000 words in your translations. </li>
<li>Use helpful features for improving translators' efficiency. </li>
<li>Add one integration to your project (we will talk about integrations in just a moment). </li>
</ul>
<p>When you sign up and create your account, you can also start a 14-day free trial of their Team plan and they also have a 30-day trial period for their Business plan.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/free-trial.png" alt="Image" width="600" height="400" loading="lazy">
<em>Frequently Asked Question (FAQ).</em></p>
<p>Crowdin also has <a target="_blank" href="https://crowdin.com/pricing#annual">other plans</a> to fit your needs. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-pricing.png" alt="Image" width="600" height="400" loading="lazy">
<em>The plans that you can choose from.</em></p>
<h3 id="heading-crowdin-for-open-source-projects-and-educational-institutions">Crowdin for Open-Source Projects and Educational Institutions</h3>
<p>As a non-profit organization, freeCodeCamp has a special license that Crowdin grants to open-source projects and educational institutions to support their mission.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-for-nonprofits.png" alt="Image" width="600" height="400" loading="lazy">
<em>Frequently Asked Question (FAQ).</em></p>
<p>If you represent an open source project or an educational institution, you can contact Crowdin for an <a target="_blank" href="https://crowdin.com/page/open-source-project-setup-request">Open Source Request</a> or an <a target="_blank" href="https://crowdin.com/page/academic-license-project-setup-request">Academic License Request</a>. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-for-open-source.png" alt="Image" width="600" height="400" loading="lazy">
<em>Crowdin for Open Source.</em></p>
<p>The Crowdin team will assist you and your organization.</p>
<h2 id="heading-important-terminologies-for-using-crowdin">Important Terminologies for Using Crowdin</h2>
<p>Before we dive into practicing with the features of Crowdin, let's talk a little bit about important terminologies for working with localization management platforms.</p>
<p>You will find these terms very often in the sections that follow, so let's talk about them in detail. </p>
<h3 id="heading-strings">Strings</h3>
<p>When you upload a resource to Crowdin, the platform has to divide the text into smaller "segments" that can be translated and saved individually until all the translations are ready. These segments of the original text are what we call "strings". After they have been translated and approved, they cab be combined to generate the localized version of the resource. </p>
<p><strong>💡 Tip:</strong> You can think of strings as the smallest units of the translation process. We do not translate the text word by word. We translate them string by string.</p>
<h3 id="heading-source-language">Source language</h3>
<p>The source language is the original language of the resource. For example, freeCodeCamp's source language is English since the curriculum and documentation are created in English. </p>
<h3 id="heading-target-language">Target language</h3>
<p>This is the language that our resources are translated into. For example, freeCodeCamp's projects has different target languages because we translate our resources into different languages.</p>
<h3 id="heading-translation-memory-tm-1">Translation Memory (TM)</h3>
<p>This is like a database where we store all the previously translated "segments" of our project. We may store sentences, paragraphs, or other units of the text with their matching source segments. The goal is to reuse the same translations later on in projects when we find them. It is a feature that can save you a lot of time because it only takes a few seconds to choose a saved translation. We can adapt them if we need to but we'll still have a foundation to work on.</p>
<h3 id="heading-machine-translation-mt-1">Machine Translation (MT)</h3>
<p>This process involves a computer software translating the resources of your project automatically without any human intervention. Usually, artificial intelligence and machine learning are part of this process. Translators and proofreaders can then take the computer-generated translations and adapt them or fix them as needed.</p>
<p>💡 <strong>Tip:</strong> Please note that translation memory (TM) and machine translation (MT) are very different even thought their acronyms are very similar. This may be a bit confusing at first, but always remember that "memory" refers to a translation's database and "machine" refers to an automated translation process. </p>
<h3 id="heading-qa-checks">QA Checks</h3>
<p>QA means "Quality Assurance". This is the process of checking if the translations have the correct format and spelling. Crowdin has many QA features that can help your team find and fix any potential errors.</p>
<h3 id="heading-glossary">Glossary</h3>
<p>This is a database of important terms in your project with their corresponding meanings. The goal of creating and maintaining a glossary is to give your translators more context about the terms and help them choose the most accurate translations. </p>
<h3 id="heading-screenshot">Screenshot</h3>
<p>A picture of what you can see on your computer screen at a particular moment. This is stored as an image file.</p>
<h3 id="heading-crowdsourcing">Crowdsourcing</h3>
<p>This is a localization practice based on community cooperation. Basically, if you are an organization and your goal is to translate your resources into many languages, you can ask help from your community. freeCodeCamp's translation effort is an example of crowdsourcing.</p>
<h3 id="heading-pre-translation">Pre-translation</h3>
<p>This is an automated technique that you can use in Crowdin to pre-translate your project automatically using either Machine Translation (MT) or Translation Memory (TM). Then, your translators can check the computer-generated translations and adapt them as needed.</p>
<h3 id="heading-integrations">Integrations</h3>
<p>These are connections that you can make between Crowdin and other applications or services, such as GitHub, Google Drive, Google Sheets, and so on. This is how freeCodeCamp keeps its GitHub repository translated. When we add new strings, they are automatically uploaded to Crowdin and translators can start working on them.</p>
<h3 id="heading-webhooks">Webhooks</h3>
<p>These are automated "messages" that an application or platform will send to another application or platform when specific events happen. In Crowdin, you can send them when translations are completed, when files are proofread, and so on.</p>
<h3 id="heading-command-line-interface-cli">Command-line Interface (CLI)</h3>
<p>This is a text-based user interface that we can use to interact with a computer program by entering commands. Crowdin has a command-line interface (CLI) called the Crowdin Console Client that allows you to synchronize localization resources with your project.</p>
<h3 id="heading-application-programming-interface-api">Application Programming Interface (API)</h3>
<p>This is an intermediary that allows two applications to communicate with each other by sending information following specific protocols. Crowdin also has an API that can help you to integrate localization into your development process.</p>
<h3 id="heading-custom-variables">Custom Variables</h3>
<p>In Crowdin, you can specify variables that should not be translated. They will be highlighted in the source strings that translators can see. To enable this feature, you will need to contact the support team at Crowdin.</p>
<h2 id="heading-getting-started-with-crowdin">Getting Started with Crowdin</h2>
<p>After this detailed but super important introduction into the fundamentals of localization, now it's time to dive into practice and start working on Crowdin. </p>
<h3 id="heading-how-to-create-a-crowdin-account">How to Create a Crowdin Account</h3>
<p>If your goal is to create a project on Crowdin, you'll need to create an account if you don't have one already.</p>
<p>To do that, go to <a target="_blank" href="https://crowdin.com/">crowdin.com</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-landing-page-signed-out.png" alt="Image" width="600" height="400" loading="lazy">
<em>Crowdin Landing page.</em></p>
<p>Click on Sign up.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/sign-up.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sign up Button.</em></p>
<ol start="3">
<li>Create your account by filling and submitting the form. You will need to enter your email, username, and password. You will also have to agree to the terms by checking the checkbox.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/sign-up-form.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sign up form.</em></p>
<p><strong>💡 Tip:</strong> After signing up, you will need to confirm your email address. You will receive an email from Crowdin with a link that you can click on to go to your profile. You should see a confirmation message saying that your email was confirmed.</p>
<p>After signing up (or logging in if you already have an account), you will see your new Crowdin profile where you can manage your projects, team members, workflows, activity, and so on.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/new-profile.png" alt="Image" width="600" height="400" loading="lazy">
<em>New Crowdin Profile.</em></p>
<p>🎉 Congratulations! Now you have your Crowdin account and you are ready to start customizing your profile. </p>
<h3 id="heading-how-to-customize-your-crowdin-profile">How to Customize your Crowdin Profile</h3>
<p>To customize your profile:</p>
<p>Click on the small profile image at the top right, and choose "Settings" from the dropdown menu.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/settings-menu.png" alt="Image" width="600" height="400" loading="lazy">
<em>Customize your profile.</em></p>
<p>You will see your profile and the information that you can customize, such as your:</p>
<ul>
<li>Profile image.</li>
<li>Full name, username, and email.</li>
<li>Company and job title.</li>
<li>Gender.</li>
<li>A brief description of you.</li>
<li>Language, timezone, and time format.</li>
<li>Preferred languages.</li>
<li>Appearance (light, dark, or based on your local time).</li>
<li>Privacy. By default, your profile is public. Check this option if you would like to make your profile private.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/black-profile-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Account Settings &gt; Profile.</em></p>
<p>💡 <strong>Tip:</strong> From this page, you can also delete your account. You will see a red button at the bottom and a warning of the consequences of doing so. You should only click this button if you are absolutely sure that you need to delete all your projects and the data associated with them.</p>
<h3 id="heading-how-to-create-a-project-on-crowdin">How to Create a Project on Crowdin</h3>
<p>Now that you know how to customize your profile, let's create a project. You can create a project from your profile page. </p>
<p>If you are in a different part of the platform, you can go back to your profile by clicking on the your small profile image at the top right and click on "Profile", like you can see in the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/go-back-to-profile.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Profile page.</em></p>
<p>To create a project, click on the "Create Project" button (the green one or the gray one, they are both equivalent).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/create-a-profile.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create Project (green button).</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/create-a-profile-2-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create Project (gray button).</em></p>
<p>You will see a page where you can fill the basic information about your project, such as:</p>
<ul>
<li>Name.</li>
<li>Project address. This is the URL for your project. If your project address has multiple words, separate them with dashes (-).</li>
<li>Privacy settings (public or private).</li>
<li>The source language.</li>
<li>The target language(s).</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/create-a-project.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a Project.</em></p>
<p>💡 <strong>Tip:</strong> Your project address must be unique. It will be filled automatically when you write your project name. If it is already taken by another user, you will see a red warning and you will need to choose a different one.</p>
<p>You can choose as many target languages as you need. Just check their checkboxes and they will be added.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/target-languages.png" alt="Image" width="600" height="400" loading="lazy">
<em>Target languages.</em></p>
<p>This what you should see when you start choosing your target languages:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/target-languages-selected.png" alt="Image" width="600" height="400" loading="lazy">
<em>Choosing some target languages.</em></p>
<p>You also have an option to pre-fill the selection with the top 30 languages without selecting them manually:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/select-top-30-languages.png" alt="Image" width="600" height="400" loading="lazy">
<em>Select Top 30 languages.</em></p>
<p>💡 <strong>Tip:</strong> You can also create custom languages or copy the selection that you made for another project.</p>
<p>If you choose to add a custom language, you will see this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/adding-a-custom-language.png" alt="Image" width="600" height="400" loading="lazy">
<em>Adding custom languages.</em></p>
<p>Since this is a completely custom language, you will have to specify:</p>
<ul>
<li>The language name.</li>
<li>If it is a dialect of another language.</li>
<li>The code for that language on Crowdin.</li>
<li>Its three-letter code.</li>
<li>Its locale-code.</li>
<li>If the text will be written from left to right or from right to left. </li>
<li><p>The plural form.</p>
</li>
<li><p>After filling all these information, you are ready to create your project. Just click on the "Create Project" button at the bottom of the page. </p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/create-project.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>💡 <strong>Tip:</strong> You can also click "Cancel" and go back to this page if you want to start over.</p>
<p>Now you should able to see your project. Of course, it will be empty at first but don't worry. We will take care of that in just a moment. 😉</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-dashboard.png" alt="Image" width="600" height="400" loading="lazy">
<em>New Project.</em></p>
<h3 id="heading-project-overview">Project Overview</h3>
<p>Let's have a quick tour of the project.</p>
<p>First, you can see the name of the project with its current privacy settings. My demo project is private. You can create unlimited public projects or one private project with your free Crowdin account.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-dashboard-name.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Next to the project name, you will see two buttons: "Invite People" and "Buy Translations". </p>
<p>You can invite team members to join your project (we'll cover how to do that in this book) and you can also buy translations from Crowdin.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-dashboard-buttons.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can also find all the tabs you need to access the available tools for your project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-dashboard-tabs.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You will be in the "Sources" tab by default, where you can see the source files that you have uploaded and the strings of your project. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-dashboard-sources.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><strong>💡 Tip:</strong> You will also be able to create folders and add files.</p>
<p>Let's see the other tabs:</p>
<h4 id="heading-dashboard-tab">Dashboard Tab</h4>
<p>This is where you will see a list of the project's target languages.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/dashboard-target-languages.png" alt="Image" width="600" height="400" loading="lazy">
<em>Dashboard tab.</em></p>
<h4 id="heading-translations-tab">Translations Tab</h4>
<p>This is where you can upload existing translations, download your translations as a zip file, target file bundles, and set up over-the-air content delivery.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translations-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translations tab.</em></p>
<h4 id="heading-screenshots-tab">Screenshots Tab</h4>
<p>This is where you can upload screenshots of your project to help your translators with more context about the strings they are translating. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/screenshots-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshots tab.</em></p>
<h4 id="heading-tasks-tab">Tasks Tab</h4>
<p>This is where you can create and assign tasks to your team members.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tasks-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Tasks tab.</em></p>
<h4 id="heading-members-tab">Members Tab</h4>
<p>This is where you can see all the members of your project with their respective roles, when they joined the project, and the actions that you can take for each member.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/members-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Members tab.</em></p>
<h4 id="heading-integrations-tab">Integrations Tab</h4>
<p>This is where you can add integrations to your project and see all the integrations that your project currently has.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/integrations-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Integrations tab.</em></p>
<h4 id="heading-reports-tab">Reports Tab</h4>
<p>This is where you can see and generate reports on the activity of your project, including translation and proofreading.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/reports-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Reports tab.</em></p>
<h4 id="heading-activity-tab">Activity Tab</h4>
<p>This is where you can see the project activity.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/activity-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Activity tab.</em></p>
<h4 id="heading-discussions-tab">Discussions Tab</h4>
<p>This is where you can open discussion topics to discuss aspects of the project with your team. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/discussions-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Discussions tab.</em></p>
<h4 id="heading-tools-tab">Tools Tab</h4>
<p>This is where you can find more information on the Command Line Tool, API, Webhooks, and Crowdin in-context (a tool to translate web applications with a real-time preview).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tools-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Tools tab.</em></p>
<h4 id="heading-settings-tab">Settings Tab</h4>
<p>This is where you can customize the project settings. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/settings-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Settings tab.</em></p>
<p>Talking about project settings, let's dive into the settings that you can customize for your project.</p>
<h3 id="heading-how-to-customize-your-project-settings-in-crowdin">How to Customize your Project Settings in Crowdin</h3>
<p>You will find different categories of project settings.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/settings-tab-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Settings tab.</em></p>
<h4 id="heading-general-settings">General Settings</h4>
<p>The general settings include:</p>
<ul>
<li>Name.</li>
<li>Public description.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/general-settings-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>General Settings.</em></p>
<ul>
<li>Branding, including a custom domain and a project logo.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/branding-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Branding Settings.</em></p>
<ul>
<li>Badges to show the progress of the localization process.</li>
<li>An option to delete the project.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-18-at-8.25.48-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Badges and Deleting a Project.</em></p>
<h4 id="heading-privacy-and-collaboration">Privacy and Collaboration</h4>
<p>In this category, you will find settings to manage the privacy and notifications of your project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/privacy-and-collaboration.png" alt="Image" width="600" height="400" loading="lazy">
<em>Privacy and Collaboration.</em></p>
<p>You can manage your project's visibility in the project visibility settings. You can set your project to be public or private. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-visibility.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Next, we have the privacy settings. You can read a short description of each one of these settings below each corresponding item. To enable a setting, check its checkbox.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/privacy-settings.png" alt="Image" width="600" height="400" loading="lazy">
<em>Default privacy settings. You can customize these to fit your needs.</em></p>
<p>Finally, the notifications settings for translators, project managers, and developers can also be customized. Just check the notifications that you would like to enable.  </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-18-at-8.29.36-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Notifications settings.</em></p>
<h4 id="heading-languages">Languages</h4>
<p>In the languages category, you can change the source and target languages of your project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/languages-settings.png" alt="Image" width="600" height="400" loading="lazy">
<em>Languages settings.</em></p>
<h4 id="heading-quality-assurance-qa-checks">Quality Assurance (QA) Checks</h4>
<p>I promised you that this would be important, right? Here we are. In the quality assurance (QA) category, you can enable QA Checks and choose which specific QA checks you would like to have in your project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/qa-checks.png" alt="Image" width="600" height="400" loading="lazy">
<em>QA Checks category.</em></p>
<p>Crowdin <a target="_blank" href="https://support.crowdin.com/qa-checks/">mentions</a> that:</p>
<blockquote>
<p>The main aim of quality assurance (QA) checks is to help you efficiently handle different language-specific aspects in translations and ensure they are formatted the same way as the source strings and will fit the UI just as well. Some typical QA check issues include missed commas, extra spaces, or typos.  </p>
<p>It’s recommended to review and resolve all QA check issues before building your project and downloading translations.</p>
</blockquote>
<p>These quality assurance checks include:</p>
<ul>
<li>Empty translations.</li>
<li>Length issues.</li>
<li>Tags mismatch.</li>
<li>Spaces mismatch.</li>
<li>Variables mismatch.</li>
<li>Punctuation mismatch.</li>
<li>Character case mismatch.</li>
<li>Special characters mismatch.</li>
<li>"Incorrect translation" issues marked by project members.</li>
<li>Spelling mistakes.</li>
<li>ICU (International Components for Unicode) Syntax Errors.</li>
<li>Consistent terminology that follows the project glossary.</li>
<li>Duplicate translation.</li>
<li>FTL syntax.</li>
<li>Android syntax.</li>
</ul>
<p>After you select the QA Checks that you would like to enable, just click on the "Save" button and your changes will be saved.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/save-qa-checks.png" alt="Image" width="600" height="400" loading="lazy">
<em>Save Button.</em></p>
<h4 id="heading-translation-memories">Translation Memories</h4>
<p>You will also find a category for Translation Memories settings.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-memories.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translation Memories category.</em></p>
<p>We will talk about Translation Memories in detail later on, but know that this is where you can customize all the settings of this helpful feature.</p>
<h4 id="heading-glossaries">Glossaries</h4>
<p>You can also manage your glossaries from the Glossaries category. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/glossaries.png" alt="Image" width="600" height="400" loading="lazy">
<em>Glossaries category.</em></p>
<p>You can check your assigned glossaries and click on links to view the records of each glossary. At the top, you will also find a link to the <a target="_blank" href="https://crowdin.com/store/apps/glossary-translate-app">Translate Glossary App</a>.</p>
<h4 id="heading-import">Import</h4>
<p>In Crowdin, you can import source strings and you can customize settings such as how to handle duplicate strings and how to count the words. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/import-category.png" alt="Image" width="600" height="400" loading="lazy">
<em>Import settings.</em></p>
<h4 id="heading-export">Export</h4>
<p>The export category has helpful settings for exporting your translated files.</p>
<p>You can choose to:</p>
<ul>
<li>Save context information in the files.</li>
<li>Skip untranslated strings.</li>
<li>Skip untranslated files.</li>
<li>Export only approved translations.</li>
<li>Automatically fill in regional dialects.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/export.png" alt="Image" width="600" height="400" loading="lazy">
<em>Export settings.</em></p>
<h4 id="heading-labels">Labels</h4>
<p>Labels can be helpful for adding context to strings and organizing them by topics. They can be useful when you want to search for specific strings. </p>
<p>You can add labels from this category in the settings:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/labels.png" alt="Image" width="600" height="400" loading="lazy">
<em>Labels settings.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/creating-a-label.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a New Label.</em></p>
<h4 id="heading-parser-configuration">Parser Configuration</h4>
<p>With the Parser Configuration, you can configure how Crowdin imports and exports selected file types to fit your needs.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/parser-configuration.png" alt="Image" width="600" height="400" loading="lazy">
<em>Parser Configuration category.</em></p>
<h4 id="heading-file-processors">File Processors</h4>
<p>The last group in the settings is File Processors, which allows you to customize how to process supported file formats.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/file-processors.png" alt="Image" width="600" height="400" loading="lazy">
<em>File Processors category.</em></p>
<h2 id="heading-how-to-delete-a-project-in-crowdin">How to Delete a Project in Crowdin</h2>
<p>If you ever need to delete a project, remember that you can do so by going to the "Setting" tab and click on the "General" tab.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/settings-tab-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Settings &gt; General.</em></p>
<p>At the bottom, you will find a red "Delete Project" button. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/delete-a-project-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Delete project.</em></p>
<h2 id="heading-how-to-upload-files-to-your-crowdin-project">How to Upload Files to your Crowdin Project</h2>
<p>Now that you know how to customize your project settings, let's actually add a file to the project. You can either upload your project files manually or automate this process through integrations. </p>
<h3 id="heading-how-to-upload-files-manually">How to Upload Files Manually</h3>
<p>Let's upload a sample PDF file with text and images.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/demo-pdf-document-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Our Demo PDF File. We will translate this in Crowdin.</em></p>
<p>To upload files:</p>
<ol>
<li>Go to your project.</li>
<li>Go to the "Sources" tab. </li>
<li>Click on the green "Add File" button or on the gray "Upload Files" button (please see the screenshot below).</li>
<li>Choose the file that you need to upload from your file system. </li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/upload-files-sources.png" alt="Image" width="600" height="400" loading="lazy">
<em>Upload files or use sample files.</em></p>
<p>💡 <strong>Tip:</strong> To explore how Crowdin works, you can also add Crowdin sample files by clicking on the "Use Samples" button.</p>
<p>After uploading your file, you will see it listed:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/upload-files.png" alt="Image" width="600" height="400" loading="lazy">
<em>Uploading a file.</em></p>
<p>You may need to wait a few seconds before the file is processed. Then, you will see the total string count for your file.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/upload-finished.png" alt="Image" width="600" height="400" loading="lazy">
<em>Upload finished.</em></p>
<p>💡 <strong>Tip:</strong> You can also drag and drop your file into the files area in the "Sources" tab and your file will be uploaded automatically.</p>
<p>If you click on the three small dots on the right, you will see more options for that file, including:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/uploaded-file-options.png" alt="Image" width="600" height="400" loading="lazy">
<em>Additional Options.</em></p>
<ul>
<li>Settings.</li>
<li>Progress.</li>
<li>View strings.</li>
<li>Open in Editor.</li>
<li>Download source.</li>
<li>Rename.</li>
<li>Delete.</li>
</ul>
<h3 id="heading-how-to-upload-files-automatically">How to Upload Files Automatically</h3>
<p>One of the key aspects of Crowdin is how easy it is to connect it to other services through integrations, to automatically upload your files and synchronize your translations. </p>
<p>For example, freeCodeCamp has a GitHub integration set up, so we can automatically synchronize the files of our project when we add new strings that have to be translated on Crowdin.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/github-integration-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>GitHub integration.</em></p>
<p>You can also find hundreds of integrations on the <a target="_blank" href="https://store.crowdin.com/">Crowdin store</a> to connect your project to external services. </p>
<p>Crowdin also has an Application Programming Interface (API) for developers. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-api.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Crowdin API <a target="_blank" href="https://developer.crowdin.com/api/v2/">Documentation</a>.</em></p>
<p>The Crowdin team <a target="_blank" href="https://developer.crowdin.com/api/v2/">describes</a> it as:</p>
<blockquote>
<p>A full-featured RESTful API that helps you to integrate localization into your development process. The endpoints that we use allow you to easily make calls to retrieve information and to execute actions needed.</p>
</blockquote>
<p>With this API, you can:</p>
<ul>
<li>Create projects for translation.</li>
<li>Add and update files.</li>
<li>Download translations, and more.</li>
</ul>
<p>It is a great way to automate your localization process. You can learn more about the Crowdin API on the <a target="_blank" href="https://developer.crowdin.com/api/v2/">official documentation</a>.</p>
<p>And the third option to upload files automatically is to use the Command-Line Interface (CLI). </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-cli-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Crowdin Command-line Interface (CLI).</em></p>
<p>With this interface, you can:</p>
<ul>
<li>Automate the process of uploading files.</li>
<li>Download translations automatically and save them in the correct locations. </li>
<li>Upload existing translations.</li>
<li>Integrate Crowdin with other tools like Git.</li>
</ul>
<p>To learn more about the Crowdin CLI, check out <a target="_blank" href="https://www.youtube.com/watch?v=0duN4khpWjM">this tutorial</a> created by the Crowdin team.</p>
<p>Now that you know how to upload your files to Crowdin manually and automatically, let's see how you and your team can start translating.</p>
<h2 id="heading-how-to-start-translating">How to Start Translating</h2>
<p>Once your file is uploaded, it's time to start translating. You may start translating it yourself or you ask your team to start working on the translations. </p>
<p>💡 <strong>Tip:</strong> You can assign specific files to your translators and proofreaders with the tasks feature.</p>
<p>Let's assume that you are translating the files yourself. </p>
<p>To start, you need to go to the project's Dashboard tab and select the language that you will be translating your file into from the list of target languages that you chose when you created the project.</p>
<p>I will choose Spanish for this demo.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/list-of-target-languages.png" alt="Image" width="600" height="400" loading="lazy">
<em>Dashboard.</em></p>
<p>You will see all your project files for that specific target language. </p>
<p>On the language page, you can check the translation and proofreading progress of each file, translate and proofread, and upload or download your translations and source files. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-files-in-spanish.png" alt="Image" width="600" height="400" loading="lazy">
<em>List of files to be translated into Spanish.</em></p>
<p>You can click on the name of the file that you would like to translate. This will take you to the Translation Editor and you will see your file in the preview.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-editor.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Translation Editor.</em></p>
<p>When upload a file, Crowdin divides it into strings. This process may require certain format conversions based on the type of file that you are uploading.</p>
<p>According to the <a target="_blank" href="https://support.crowdin.com/supported-formats/#converted-file-formats">Crowdin documentation</a>:</p>
<blockquote>
<p>On import, some file formats are automatically converted into other formats to be further parsed and processed.   </p>
<p>You can see the list of the initial file formats and the file formats they’re being converted into in the table below.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/file-formats-table.png" alt="Image" width="600" height="400" loading="lazy">
<em>File formats conversion. Image taken from the <a target="_blank" href="https://support.crowdin.com/supported-formats/#converted-file-formats">Crowdin documentation</a>.</em></p>
<p>We can see that PDF files are converted into DOCX files, the type of file that we usually create in a text editor.</p>
<p>Then, to export the file, <a target="_blank" href="https://support.crowdin.com/supported-formats/#converted-file-formats">Crowdin also mentions</a> that:</p>
<blockquote>
<p>By default, we export the translations in the same format as the source files. For example, if you upload an XML file to Crowdin, you’ll have the XML file exported.</p>
</blockquote>
<h2 id="heading-how-to-use-the-translation-editor">How to Use The Translation Editor</h2>
<p>This is the layout of the translation editor that you will see by default when you click on a file. It is called the Comfortable Mode.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-editor.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Translation Editor.</em></p>
<p>It has four main sections:</p>
<ul>
<li>The left sidebar (in purple below).</li>
<li>Middle-top area (in yellow below).</li>
<li>Middle-bottom area (in orange below).</li>
<li>Right sidebar (in blue below).</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-editor-sections.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Translation Editor in Comfortable Mode.</em></p>
<p>Let's talk about each section.</p>
<h3 id="heading-left-sidebar">Left Sidebar</h3>
<ul>
<li>Highlighted in a purple box in the previous diagram.</li>
<li>Shows you all the strings in your document and a preview of your source file.</li>
<li>You will find helpful tools at the top such as (from left to right): searching strings in the file, changing the view to a list of all the strings, highlighting translated and untranslated strings, showing the translation preview, scale toggle, and adding a string.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/toolbar-options.png" alt="Image" width="600" height="400" loading="lazy">
<em>Left Sidebar - Toolbar at the top.</em></p>
<p>If you click on the first button (from left to right) after the search file field, you can change the view to see a list of all the strings instead of the file preview. </p>
<p>Now you will see a list of all the strings on the left:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/list-of-all-strings.png" alt="Image" width="600" height="400" loading="lazy">
<em>Basic list view.</em></p>
<p>You can always click on this button again to go back to the previous mode, where you can see the strings in the original context and layout of the source file, like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-editor.png" alt="Image" width="600" height="400" loading="lazy">
<em>WYSIWYG ("What You See Is What You Get") Translation Mode.</em></p>
<p>Here, you will see that strings are highlighted in different colors. </p>
<ul>
<li>Red means that the string has not been translated.</li>
<li>Yellow means that the string is partially translated.</li>
<li>Blue means that the string has been translated.</li>
<li>Gray means that the string is hidden and only visible to project managers and proofreaders.</li>
</ul>
<p>When you start proofreading the strings, you will also see:</p>
<ul>
<li>A yellow checkmark if the string is partially approved (if some plural forms are not approved).</li>
<li>A green checkmark if the string has been approved.</li>
</ul>
<p>You may also see a comment icon if a contributor has left a comment on a string or if it has marked the comment as an issue.</p>
<h3 id="heading-middle-top-area">Middle-Top Area</h3>
<ul>
<li>Highlighted in a yellow box in the diagram below. </li>
<li>This is where you can translate a string. You just need to select it from the left sidebar and it will appear in this area. </li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-editor-sections.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Translation Editor in Comfortable Mode.</em></p>
<p>Let's click on a string and see what happens:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/selecting-a-strings.png" alt="Image" width="600" height="400" loading="lazy">
<em>Selecting a string.</em></p>
<p>Awesome! The string is now selected as the "Source String" and we can start translating it:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/writing-translations.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translate a string.</em></p>
<p>The three tools that you can see at the bottom are (from left to right):</p>
<ul>
<li><strong>Copy Source:</strong> to keep the initial string structure. </li>
<li><strong>Clear:</strong> to erase the translation quickly.</li>
<li><strong>Text Selection Mode:</strong> to copy a part of the translation from Translation Memory (TM) or Machine Translations (MT).</li>
</ul>
<p>If you click on the three dots at the top, you will see additional options for the string, including:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/strings-translation-options.png" alt="Image" width="600" height="400" loading="lazy">
<em>String translation options.</em></p>
<ul>
<li>Hide String.</li>
<li>Copy String URL.</li>
<li>Copy Source Skeleton.</li>
<li>Translation History.</li>
<li>View String in Context.</li>
</ul>
<p>When you write your translation, you will see your translations in the preview. The string will be highlighted in yellow if it is the selected string. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/preview-with-partially-translated-string.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translation in the preview (sidebar). The translated string is in Spanish in the preview.</em></p>
<p>To save your translation, click on the green "Save" button. </p>
<p>After this, you can go to the next string and you will see the previously translated string highlighted in a different color (blue).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translated-saved-string.png" alt="Image" width="600" height="400" loading="lazy">
<em>Saved translation.</em></p>
<p>To go back to the previous string, just click on it or click on the left arrow next to "Source String".</p>
<p>If you go back to the string, you will see something new in the middle-bottom area.</p>
<p>Let's talk about the middle-bottom area.</p>
<h3 id="heading-middle-bottom-area">Middle-Bottom Area</h3>
<ul>
<li>Highlighted in an orange box in the diagram below.</li>
<li>This section has translations made by other contributors (if applicable), translation memory (TM) suggestions, machine translation (MT) suggestions, and translations to other languages.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-editor-sections.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Translation Editor in Comfortable Mode.</em></p>
<p><strong>💡 Tip:</strong> If you click on a suggestion, it will automatically appear in the translation input field.</p>
<p>This is the current state of our project. We have this translated string and you can see the Spanish Translation in the middle-bottom section (orange box below).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/back-to-translated-string.png" alt="Image" width="600" height="400" loading="lazy">
<em>Middle-Bottom Section.</em></p>
<p>For each translation, you will see:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/approve-string-translation.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translation.</em></p>
<ul>
<li>The translation in the target language.</li>
<li>The Crowdin user who saved the translation. </li>
<li>When it was saved.</li>
<li>Its current rating (other project members can upvote or downvote a translation).</li>
<li>A checkmark button to approve the translation (like a proofreader).</li>
<li>A trash button to delete the translation.</li>
</ul>
<p>If you are the project owner or you have proofreading permissions, you can approve the string translation yourself. However, it is always recommended to have another team member check your string to avoid any common issues.</p>
<p>To approve the translation, just click on the checkmark button next to the translation. </p>
<p>Now you will see the approved string highlighted in green in the preview:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/approved-string.png" alt="Image" width="600" height="400" loading="lazy">
<em>Approved string highlighted in green.</em></p>
<p>The translated string will now show who approved it and when it was approved:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/back-to-approved-string-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-right-sidebar">Right Sidebar</h3>
<p>And last (but not least!) we have the right sidebar. </p>
<ul>
<li>Highlighted in blue in the diagram below.</li>
<li>This is where you can write comments, search the TM, search for terms on your glossary, add new apps, and find the apps you added through the <a target="_blank" href="https://crowdin.com/store/apps">Crowdin Store</a>.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-editor-sections.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Translation Editor in Comfortable Mode.</em></p>
<p>To write a comment, just go to the comments on the sidebar and write your comment on the text input field at the bottom. You can mark the comment as an issue if you need to.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/writing-a-comment-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Writing a comment.</em></p>
<p>💡 <strong>Tip:</strong> You should write your comment using the source language of the project, so other team members and project managers can understand it.</p>
<p>If you click on the second option on this sidebar, you will be able to search your translation memory for previous translations.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-memory.png" alt="Image" width="600" height="400" loading="lazy">
<em>Search Translation Memory (TM).</em></p>
<p>In the third option, you can search for terms related to the currently selected string in your glossary.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/glossary.png" alt="Image" width="600" height="400" loading="lazy">
<em>Search Terms.</em></p>
<p>What is really great about this search term feature is that, <a target="_blank" href="https://support.crowdin.com/online-editor/#section-4">according to Crowdin</a>:</p>
<blockquote>
<p>If the specific term is not available in the project’s glossary, the system will show you Wikipedia explanations.</p>
</blockquote>
<p>This can be very helpful to understand more about the context of a term when you are translating a resource.</p>
<p>And the last option is a plus icon that will take you to the Crowdin Store, where you can find apps and integrations for your project and you will be able to access them on the sidebar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/add-apps.png" alt="Image" width="600" height="400" loading="lazy">
<em>Go to the Crowdin Store.</em></p>
<h2 id="heading-translation-editor-modes">Translation Editor Modes</h2>
<p>The translation editor has three modes to customize the layout in a way that fits your needs — Comfortable Mode, Side-by-Side Mode, and Multilingual Mode.</p>
<h3 id="heading-comfortable-mode">Comfortable Mode</h3>
<ul>
<li>Primarily used for translations. </li>
<li>It has the four main sections that we saw in the previous section.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/comfortable-mode.png" alt="Image" width="600" height="400" loading="lazy">
<em>Layout in the Comfortable Mode.</em></p>
<h3 id="heading-how-to-switch-modes">How to Switch Modes</h3>
<p>To switch to another mode, you need to click on the menu icon at the top left of the translation editor:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/change-mode-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Click on this menu icon.</em></p>
<p>Then, click on "View" and choose the mode that you would like to see:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/view-change-mode.png" alt="Image" width="600" height="400" loading="lazy">
<em>Change Editor View.</em></p>
<h3 id="heading-side-by-side-mode">Side-by-Side Mode</h3>
<ul>
<li>Primarily used by managers and proofreaders to approve the best translations and by translators to vote translations in a row.</li>
</ul>
<p>The first time you switch to this view, you will see some helpful tips:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tip-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Review or make translations.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tip-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Approve multiple translations at once.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tip-3.png" alt="Image" width="600" height="400" loading="lazy">
<em>Switch to Comfortable view to make new translations.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tip-4.png" alt="Image" width="600" height="400" loading="lazy">
<em>That's all friends!</em></p>
<p>This is what you will see when you enter side-by-side mode. Please take a moment to see it in detail and explore the changes:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/side-by-side-mode.png" alt="Image" width="600" height="400" loading="lazy">
<em>Side-by-Side Mode</em></p>
<p>To see all the possible string status in the side-by-side mode, let's go back to the Comfortable Mode to translate and save another string (but we will not approve the string this time).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/string-translation-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translating another string.</em></p>
<p>In the side-by-side view, we now have a translated string, an approved string, and strings that we still need to translate.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/side-by-side-with-multiple-strings.png" alt="Image" width="600" height="400" loading="lazy">
<em>Side-by-Side Mode.</em></p>
<p>Let's start with a quick tour. You have four areas that you can resize to fit your needs:</p>
<ul>
<li>At the top left, we find the list of strings. You can select multiple string for bulk operations such as approving multiple strings with just one click.</li>
<li>At the bottom left, we find the preview of the source file. </li>
<li>At the top right, we find the string details.</li>
<li>At the bottom right, we find the current translations and suggestions. This is very similar to what you see in the Comfortable mode.</li>
</ul>
<h4 id="heading-toolbar">Toolbar</h4>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/toolbar.png" alt="Image" width="600" height="400" loading="lazy">
<em>Toolbar.</em></p>
<p>At the top of the list of string, you will see multiple tools, including (from left to right):</p>
<ul>
<li>Search for a string.</li>
<li>Change sorting criteria.</li>
<li>String length in the source file and in the translated version. </li>
<li>Save button. </li>
<li>Cancel button. </li>
<li>Copy source.</li>
<li>Approve string.</li>
<li>Add string.</li>
<li>Edit string.</li>
<li>More options.</li>
</ul>
<p>If you open the "More options" menu by clicking on the three dots, you will see more helpful options for the selected string(s).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/more-options-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>More options.</em></p>
<h4 id="heading-how-to-sort-the-strings">How to Sort the Strings</h4>
<p>You will notice that, by default, the strings will be sorted by their status. </p>
<p><strong>💡 Tip:</strong> Untranslated strings will be displayed first, so you will not see the strings in the order in which they appear in the source file. </p>
<p>You can change the sorting criteria by clicking on the filter icon next to the search field. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/filtering-strings.png" alt="Image" width="600" height="400" loading="lazy">
<em>Filtering the Strings.</em></p>
<p>In this mode, you will also see different visual references of the status of each string. </p>
<p>Here, we can see:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/types-of-strings.png" alt="Image" width="600" height="400" loading="lazy">
<em>String status.</em></p>
<ul>
<li>An untranslated string in red.</li>
<li>A translated string in blue. </li>
<li>An approved string with a green checkmark. </li>
<li>A hidden string in gray. </li>
</ul>
<h3 id="heading-multilingual-mode">Multilingual Mode</h3>
<p>Awesome. Now let's go to the multilingual mode. This mode is primarily used by translators and proofreaders to work with multiple languages at the same time.</p>
<p>💡 <strong>Tip:</strong> You can work on up to ten languages simultaneously.</p>
<p>To switch to this mode, click on the main menu icon at the top. Then select "View" and choose "Multilingual".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/multilingual-view.png" alt="Image" width="600" height="400" loading="lazy">
<em>Go to the Multilingual Mode.</em></p>
<p>When you choose "Multilingual", you will need to choose the languages that you are planning to work with.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/languages.png" alt="Image" width="600" height="400" loading="lazy">
<em>Languages for the Multilingual Mode.</em></p>
<p>Let's choose Spanish and Japanese just to see how this mode works. Click on them and then click on the "Apply" button.</p>
<p>You should see this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/multilingual-mode.png" alt="Image" width="600" height="400" loading="lazy">
<em>Multilingual Mode.</em></p>
<p>Each string will have a text field where you can write the translation for each of the target languages selected. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/string-in-multilingual.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>When you are working on the multilingual mode, you can switch between two possible views:</p>
<ul>
<li>List View.</li>
<li>Grid View.</li>
</ul>
<p>This is an example of the list view:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/multilingual-mode.png" alt="Image" width="600" height="400" loading="lazy">
<em>List view in Multilingual Mode.</em></p>
<p>To switch to Grid View, you need to click on the button in the toolbar at the top:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/multilingual-mode-copy.png" alt="Image" width="600" height="400" loading="lazy">
<em>Switch between list and grid view.</em></p>
<p>The other parts of the editor and the tools in this mode are very similar to the Side-by-Side view that you are already familiar with.</p>
<h2 id="heading-how-to-switch-to-another-file">How to Switch to Another File</h2>
<p>You may want to go to another file after translating all the strings in a different file. This is very easy to do.</p>
<p>To do that:</p>
<p>Click on the main menu at the top left.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/main-menu-button.png" alt="Image" width="600" height="400" loading="lazy">
<em>Main Menu.</em></p>
<p>Go to File and then choose "Open...".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/open-another-file-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Open a File.</em></p>
<p>Choose the file that you would like to open and click on "Open" (or double-click on the file name).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/open-file-menu.png" alt="Image" width="600" height="400" loading="lazy">
<em>List of files.</em></p>
<p>💡 <strong>Tip:</strong> You can also get to the list of files much faster by clicking on the file name directly. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/file-click-on-file.png" alt="Image" width="600" height="400" loading="lazy">
<em>Click on the file name.</em></p>
<p>You will be taken to the file you've chosen.</p>
<h3 id="heading-how-to-view-all-strings">How to View All Strings</h3>
<p>If you ever need to see a list of all the strings in a project, you just need to:</p>
<p>Go to the main menu at the top left.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/main-menu-button.png" alt="Image" width="600" height="400" loading="lazy">
<em>Main Menu.</em></p>
<p>Go to "File", and then select "All Strings".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/all-strings-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>See All Strings.</em></p>
<p>You will see a list of all the strings in the project, their status, and translations. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/all-strings-list.png" alt="Image" width="600" height="400" loading="lazy">
<em>All Strings.</em></p>
<h2 id="heading-how-to-translate-rtl-languages">How to Translate RTL Languages</h2>
<p>While some languages like Spanish and Italian are written from left to right (LTR), other languages like Arabic and Urdu are written from right to left (RTL). </p>
<p><a target="_blank" href="https://support.crowdin.com/online-editor/#translating-rtl-languages">Crowdin mentions</a> that:</p>
<blockquote>
<p>When translating between LTR and RTL languages, some elements in the translation field in the Editor might not be displayed the same way as they will be once exported.</p>
</blockquote>
<p>To make sure that the translations will be displayed correctly in the exported file, Crowdin recommends:</p>
<ol>
<li>Clicking the "Copy Source" button when writing the translations. This is the first button on the toolbar.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/toolbar-copy-source-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>The "Copy Source" Button.</em></p>
<ol start="2">
<li><p>Translating the text into your target language.</p>
</li>
<li><p>Leaving any variables or tags exactly the same, even if they do not look the same. They will be in the right position when you export a file.</p>
</li>
</ol>
<p>💡 <strong>Tip:</strong> Crowdin <a target="_blank" href="https://support.crowdin.com/online-editor/#translating-rtl-languages">suggests</a> using the <a target="_blank" href="https://store.crowdin.com/unicode">Unicode Table app</a> to copy and paste right-to-left and left-to-right marks, changing the direction of the text where needed.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-77.png" alt="Image" width="600" height="400" loading="lazy">
<em>Example of translating a RTL language from the <a target="_blank" href="https://support.crowdin.com/online-editor/#translating-rtl-languages">Crowdin documentation</a>.</em></p>
<h3 id="heading-translation-editor-settings">Translation Editor Settings</h3>
<p>You can also customize the settings of the translation editor. </p>
<p>To access these settings, click on the gear icon at the top right of the translation editor.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/editor-settings.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translation Editor Settings.</em></p>
<p>You will see a list of the settings that you can customize.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/editor-settings-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Editor Settings (Part 1)</em></p>
<p>The first part of these settings includes:</p>
<ul>
<li>The minimum match percentage for showing Translation Memory suggestions. </li>
<li>If translations should be checked for quality assurance (QA) issues.</li>
<li>If the editor should auto-complete what you are writing and show you a list of predictions. </li>
<li>If you would like to approve the translations automatically. This can be helpful if you are translating and proofreading the project yourself.</li>
<li>If you would like to move to the next string automatically. </li>
<li>The color theme for the translation editor (light, dark, or automatic).</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/editor-settings-2-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Editor Settings (Part 2).</em></p>
<p>If you scroll down, you will find more settings, such as:</p>
<ul>
<li>If the editor should show only the beginning of the source string in a compact view.</li>
<li>If you would like to show the translation preview for translated strings.</li>
<li>How HTML tags should be displayed. You can show them or hide them. </li>
<li>If non-printable characters should be displayed or not. </li>
<li>If the translation field should be highlighted.</li>
<li>If you would like to enable real-time spellchecking.</li>
<li>The language of the user interface of your translation editor.</li>
</ul>
<p>You can customize these settings to fit your needs.</p>
<h3 id="heading-keyboard-shortcuts">Keyboard Shortcuts</h3>
<p>Another key productivity feature for translators and proofreaders on Crowdin is that they can use keyboard shortcuts.</p>
<p>To see all the keyboard shortcuts available for your operating system, just click on the keyboard icon at the top right.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/keyboard-shortcuts-button-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Open Keyboard Shortcuts.</em></p>
<p>These are the keyboard shortcuts for Windows:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-78.png" alt="Image" width="600" height="400" loading="lazy">
<em>Keyboard Shortcuts (Windows). Screenshot from the <a target="_blank" href="https://support.crowdin.com/online-editor/#helpful-tips">Crowdin Documentation</a>.</em></p>
<p>And these are the keyboard shortcuts for macOS:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/keyboard-shortcuts-macos-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Keyboard Shortcuts (macOS).</em></p>
<p>If you scroll down, you will find more keyboard shortcuts for macOS.</p>
<h2 id="heading-how-to-download-the-translated-files">How to Download the Translated File(s)</h2>
<p>Once your project is translated and approved, you will need to download it. </p>
<p>In Crowdin, you have three different options for downloading files. You can download the entire project, download all the project files in a specific language, or download a specific file in a specific language.</p>
<p>Let's see these options in more detail. </p>
<h3 id="heading-how-to-download-the-entire-project">How to Download the Entire Project</h3>
<p>If you need to download the entire project:</p>
<ol>
<li>Go to your project.</li>
<li>Go to the "Translations" tab. </li>
<li>On "Download as ZIP" section, click on the "Build &amp; Download" button.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/download-project-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Download as ZIP.</em></p>
<p>You will see a progress bar while Crowdin builds the project and then your download will start.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/progress-bar.png" alt="Image" width="600" height="400" loading="lazy">
<em>Build &amp; Download.</em></p>
<p>The ZIP file will have folders for each language. The names of the folders will be their corresponding language codes.</p>
<h3 id="heading-how-to-download-all-files-in-a-target-language">How to Download All Files in a Target Language</h3>
<p>This option is helpful if you need to download all the translated files in a specific target language.</p>
<ol>
<li>Go to your project. </li>
<li>Go to the "Translations" tab.</li>
<li>On "Download as ZIP" section, select the language.</li>
<li>Click on the "Build &amp; Download" button.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/download-language-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Download as ZIP.</em></p>
<h3 id="heading-how-to-download-a-file-in-a-target-language">How to Download a File in a Target Language</h3>
<p>The third option is to download just one file in a specific target language. </p>
<p>To do this:</p>
<ol>
<li>Go to your project.</li>
<li>Go to the Dashboard tab.</li>
<li>Choose the target language. </li>
<li>Click on the file.</li>
<li>Click on the three dots to the right to see the additional options (see the screenshot below).</li>
<li>Click on "Download".</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/download-one-file.png" alt="Image" width="600" height="400" loading="lazy">
<em>Download a File in a Target Language.</em></p>
<p>The download should start and you should have your translated file ready in just a few seconds or minutes.</p>
<p>You can also automate the process of exporting or downloading your translated files with Crowdin integrations. </p>
<h2 id="heading-how-to-use-translation-memory-tm"><strong>How to Use Translation Memory (TM)</strong></h2>
<p>Great work so far. Now that you know the basic features of the translation editor, let's talk about more advanced features such as the Translation Memory. </p>
<p>We will see how you can:</p>
<ul>
<li>Create translation memory.</li>
<li>Manage translation memory.</li>
<li>Download and upload translation memory.</li>
<li>Assign translation memory to a project.</li>
</ul>
<p>💡 <strong>Tip:</strong> Remember that TM is like a database of strings that we have translated previously in our project and their corresponding translations. Reusing previous translations can save us a lot of time. </p>
<p>Are you ready? Let's begin. </p>
<h3 id="heading-how-to-create-a-translation-memory-tm">How to Create a Translation Memory (TM)</h3>
<p>To create a TM for your project, you need to:</p>
<ol>
<li>Go to your profile. You can do this by clicking on "Profile" in the dropdown menu that is displayed when you click on your profile image at the top right.</li>
<li>Go to the "Resources" tab.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/resources-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Resource tab in Profile.</em></p>
<ol start="3">
<li>If you already have a project created, you will see a TM for that project. You can also create a new TM, independently from a project (you can assign it to a project later on).</li>
</ol>
<p><a target="_blank" href="https://support.crowdin.com/translation-memory/#creating-tm">Crowdin mentions</a> that:</p>
<blockquote>
<p>Besides the project TMs that are automatically created along the respective projects, you can also create separate TMs, fill them with the appropriate content by uploading your existing TMs in TMX, XLSX, or CSV format, and then assign these TMs to the needed projects.</p>
</blockquote>
<ol start="4">
<li>To create a new TM, click on the green "Create TM" button.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/create-TM-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create TM.</em></p>
<ol start="5">
<li>Complete the information for your new TM, such as its name, language, and if you would like to assign it to an existing project.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/create-translation-memory.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create Translation Memory.</em></p>
<p>I'm going to create a new TM called "Demo TM" in English. For now, I will not assign it to any project.</p>
<p>Let's see what happens:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/demo-translation-memory.png" alt="Image" width="600" height="400" loading="lazy">
<em>New TM.</em></p>
<p>🎉 Awesome. Now we see the new TM in the list.</p>
<h3 id="heading-how-to-manage-translation-memory-tm">How to Manage Translation Memory (TM)</h3>
<p>If you click on the three dots to the right of the TM to show additional options, you will see the following options:</p>
<ul>
<li>Upload.</li>
<li>Download.</li>
<li>Edit.</li>
<li>View Records.</li>
<li>Delete.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/additional-options-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Additional Options for a Translation Memory.</em></p>
<p>If you select a TM by checking its checkbox, you can delete it, edit it, or view its records. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/manage-tm-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Since our new TM is new (and therefore, empty), let's see the records of the TM for our freeCodeCamp Course Project.</p>
<p>If you click on it, you'll see a list of all the records stored in the TM:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-memory-strings-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translation Memory (TM).</em></p>
<p>From here, you can:</p>
<ul>
<li>Edit the records.</li>
<li>Delete the records.</li>
<li>Search for specific records matching case, whole phrase, and finding an exact match.</li>
<li>Hide or show specific columns.</li>
</ul>
<h3 id="heading-how-to-upload-and-download-translation-memory-tm">How to Upload and Download Translation Memory (TM)</h3>
<p>In the "Resources" tab of your project, you can also upload and download TM.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tm-records-download-upload.png" alt="Image" width="600" height="400" loading="lazy">
<em>Resources toolbar.</em></p>
<p>The file must be in one of the following formats: </p>
<ul>
<li>TMX</li>
<li>XLSX</li>
<li>CSV</li>
</ul>
<p>To upload a TM:</p>
<ul>
<li>Click on the "Upload" button. </li>
<li>Choose your file in your file system. </li>
<li>Match each column to the corresponding language.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tm-records-download-upload.png" alt="Image" width="600" height="400" loading="lazy">
<em>The "Upload" button.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-79.png" alt="Image" width="600" height="400" loading="lazy">
<em>Matching columns to their corresponding languages. Image taken from the <a target="_blank" href="https://support.crowdin.com/translation-memory/#downloading-and-uploading-tm">Crowdin Documentation</a>.</em></p>
<p>To download translation memory:</p>
<ul>
<li>Click on the "Download" button. </li>
<li>Select the file format (either TMX, XLSX, or CSV).</li>
<li>Choose if you would like to download all languages or only a specific language pair.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tm-records-download-upload.png" alt="Image" width="600" height="400" loading="lazy">
<em>The "Download" button.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/download-tm.png" alt="Image" width="600" height="400" loading="lazy">
<em>Download TM.</em></p>
<p>The download should start after you click on the green "Download" button. </p>
<p>This is the CSV file that we get when we export the Translation Memory of our freeCodeCamp Course Project:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-19-at-5.19.49-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Exported CSV File.</em></p>
<h3 id="heading-how-to-assign-translation-memory-to-a-project">How to Assign Translation Memory to a Project</h3>
<p>To assign an existing TM to a project:</p>
<ol>
<li>Go to your project.</li>
<li>Go to the "Settings" tab.</li>
<li>Go to "Translation Memories".</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/assign-tm.png" alt="Image" width="600" height="400" loading="lazy">
<em>Settings tab.</em></p>
<ol start="4">
<li>Scroll down to find "Assigned Translation Memories".</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/assigned-tm.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="5">
<li>Select the Translation Memories that you would like to assign to your project. </li>
</ol>
<p><a target="_blank" href="https://support.crowdin.com/translation-memory/#prioritizing-tm">Crowdin mentions</a> that:</p>
<blockquote>
<p>When you assign a few TMs to the project, you can set the needed priority for each of them. As a result, TM suggestions from the TM with the higher priority will be displayed in the first place.</p>
</blockquote>
<p>💡 <strong>Tip:</strong> A higher number represents a higher priority. If a TM has a priority of 5, that would be higher than a priority of 1.</p>
<p>You can also change the default TM by clicking on the corresponding star icon.</p>
<p><strong>Important:</strong> Please note that Crowdin recently eliminated the need to use custom workflows to automatically apply Translation Memory matches. Now you can enable Translation Memory on the project settings, like you can see below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image-13.png" alt="Image" width="600" height="400" loading="lazy">
<em>How to Enable Translation Memory Pre-Translation for New Content in the Project Settings. Screenshot provided by the Crowdin team.</em></p>
<h2 id="heading-glossary-1">Glossary</h2>
<p>Awesome! Now you know the most important aspects of TM on Crowdin, so let's check out the glossaries.</p>
<p>💡 <strong>Tip:</strong> Remember that a glossary allows you to store and manage your project's terminology to help your translators with more context and definitions.</p>
<h3 id="heading-how-to-create-a-glossary">How to Create a Glossary</h3>
<p>When you create a project, a glossary is automatically created but you can also create new ones that are independent from any project.</p>
<p>To create a glossary:</p>
<ul>
<li>Go to your profile.</li>
<li>Go to "Resources".</li>
<li>Click on "Glossaries".</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/glossaries-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Glossaries</em></p>
<p>You will see a glossary for each project that you have created on Crowdin. </p>
<ul>
<li>Click on the green "Create Glossary" button.</li>
<li>Choose a name and a language for your glossary. </li>
<li>If you need to, assign it to a project.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/create-a-glossary.png" alt="Image" width="600" height="400" loading="lazy">
<em>Assign to a Glossary.</em></p>
<p>After this, you will see your new glossary in the list of glossaries. I created a new glossary called "Demo Glossary".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/demo-glossary.png" alt="Image" width="600" height="400" loading="lazy">
<em>List of Glossaries.</em></p>
<h3 id="heading-how-to-manage-glossary-terms">How to Manage Glossary Terms</h3>
<p>You can add, edit, and delete concepts and terms from your glossaries. Let's see how you can do this step by step.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/empty-glossary.png" alt="Image" width="600" height="400" loading="lazy">
<em>New Empty Glossary.</em></p>
<p><strong>💡 Tip:</strong> On Crowdin, concepts is not the same as a term. A concept is broader than a term. This is what <a target="_blank" href="https://support.crowdin.com/glossary/#managing-glossary-concepts-and-terms">the documentation</a> says about their difference:</p>
<blockquote>
<p>A concept incorporates glossary terms and their variations with multiple translations and other relevant information.</p>
</blockquote>
<p>To add a concept, click on the green "Add concept" button:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/add-concept-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Add concept.</em></p>
<p>You will need to write information about the new concept:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-19-at-6.08.32-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Add concept dialog.</em></p>
<p>The <a target="_blank" href="https://support.crowdin.com/glossary/#managing-glossary-concepts-and-terms">Crowdin documentation</a> describes the following concept details:</p>
<blockquote>
<p><strong>Definition:</strong> concept definition.<br><strong>Subject:</strong> a branch of knowledge the concept is related to.<br><strong>Note:</strong> short notes about a concept that might be helpful to translators.<br><strong>URL:</strong> URL to the web page with relevant information about a concept.<br><strong>Figure:</strong> URL to the relevant image.</p>
</blockquote>
<p>It also mentions the following <strong>term</strong> details:</p>
<blockquote>
<p><strong>Part of speech:</strong> e.g., noun, verb, adjective, etc.<br><strong>Type:</strong> e.g., full form, acronym, abbreviation, etc.<br><strong>Status:</strong> preferred, admitted, not recommended, obsolete.<br><strong>Gender:</strong> term gender.<br><strong>Description:</strong> term description.<br><strong>Note:</strong> short notes about a term that might be helpful to translators.<br><strong>URL:</strong> URL to the web page with relevant information about a term.</p>
</blockquote>
<p>After you create the term, you will see it in the list of terms for the glossary:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/glossary-term-demo.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To edit or delete a glossary term, click on the three dots to the right to see additional options and choose an option. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/glosssary-term-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Glossary Term.</em></p>
<p>You can download or upload a glossary in the following formats: </p>
<ul>
<li>TBX (v2)</li>
<li>TBX (v3)</li>
<li>CSV</li>
<li>XLSX</li>
</ul>
<p>Just click on the corresponding button and you will be able to choose a file to upload or the format of the file to download.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/download-upload.png" alt="Image" width="600" height="400" loading="lazy">
<em>Upload and Download a Glossary.</em></p>
<p><a target="_blank" href="https://support.crowdin.com/glossary/#downloading-and-uploading-glossary">Crowdin suggests</a> that:</p>
<blockquote>
<p>If you upload a glossary in CSV or XLS/XLSX file formats, select the language for each column and the column value (term, description, or part of speech) in the configuration dialog.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-80.png" alt="Image" width="600" height="400" loading="lazy">
<em>Selecting the columns. Image taken from the <a target="_blank" href="https://support.crowdin.com/glossary/#downloading-and-uploading-glossary">Crowdin documentation</a>.</em></p>
<h3 id="heading-how-to-assign-a-glossary-to-a-project">How to Assign a Glossary to a Project</h3>
<p>It is very easy to assign a glossary to a project on Crowdin. </p>
<p>You just need to:</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Settings" tab.</li>
<li>Go to "Glossaries".</li>
<li>Select the glossary (or glossaries) that you would like to assign to your project.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/glossaries-demo.png" alt="Image" width="600" height="400" loading="lazy">
<em>Project Glossaries.</em></p>
<p><strong>💡 Tip:</strong> To change the default glossary of a project, just click on the star icon of the corresponding glossary. The dark gray star marks the current default glossary.</p>
<p>You can also share glossaries across all your projects by checking the option "Share Glossaries" in your profile:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/share-glossaries.png" alt="Image" width="600" height="400" loading="lazy">
<em>Share Glossaries.</em></p>
<h3 id="heading-how-to-translate-the-glossary">How to Translate the Glossary</h3>
<p>Translating your glossary can be very helpful to use terms consistently in your target languages.</p>
<p>To translate your glossary, Crowdin recommends using the free <a target="_blank" href="https://crowdin.com/store/apps/glossary-translate-app">Translate Glossary</a> app.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translate-glossary.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translate Glossary app.</em></p>
<p>If you install the app, you will be able to translate your glossary directly on Crowdin and choose which projects should have access to this app.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/install-translate-glossary.png" alt="Image" width="600" height="400" loading="lazy">
<em>Install the app.</em></p>
<h2 id="heading-quality-assurance-qa-checks-in-crowdin">Quality Assurance (QA) Checks in Crowdin</h2>
<p>Quality assurance is essential for every type of project and this is especially true for localization projects. One small typo or spelling mistake can make all the difference to your users. </p>
<p>This is why Crowdin implemented efficient quality assurance features to help you deliver the high-quality translations that your users deserve.</p>
<p>According to <a target="_blank" href="https://support.crowdin.com/qa-checks/">Crowdin</a>:</p>
<blockquote>
<p>QA checks help to detect some common mistakes easily and quickly. It’s recommended to review and resolve all QA check issues before building your project and downloading translations.</p>
</blockquote>
<p>The issues detected by the QA Checks include:</p>
<ul>
<li>Typos.</li>
<li>Missing commas.</li>
<li>Extra spaces.</li>
<li>Other common mistakes.</li>
</ul>
<h3 id="heading-how-to-configure-quality-assurance-qa-checks">How to Configure Quality Assurance (QA) Checks</h3>
<p>To configure QA checks and choose what you would like to check for in the translated strings, you just need to:</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Settings" tab.</li>
<li>Make sure that "Enable QA Checks" is selected.</li>
<li>Select what you would like to check for.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/qa-checks-settings.png" alt="Image" width="600" height="400" loading="lazy">
<em>The QA Checks category.</em></p>
<p>You can check:</p>
<ul>
<li>If a translation is empty.</li>
<li>If the translation is longer than the predefined length limit (if it exists).</li>
<li>Tags mismatch.</li>
<li>Spaces mismatch.</li>
<li>Variables mismatch.</li>
<li>Punctuation mismatch.</li>
<li>Character case mismatch.</li>
<li>Special characters mismatch.</li>
<li>"Incorrect translation" issues.</li>
<li>Spelling issues.</li>
<li>ICU Syntax errors.</li>
<li>Consistency with the glossary terms.</li>
<li>Duplicate translation.</li>
<li>FTL syntax errors.</li>
<li>Android syntax errors.</li>
</ul>
<p><strong>💡 Tip:</strong> For each of these QA checks, you can choose if you would like to show a warning to the user when the string is being saved or if you would like to go one step further and show an error.</p>
<p>To save your changes, click on the green "Save" button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/qa-save-button.png" alt="Image" width="600" height="400" loading="lazy">
<em>Save QA Checks.</em></p>
<h3 id="heading-how-to-check-quality-assurance-status">How to Check Quality Assurance Status</h3>
<p>Proofreaders and project managers can see the issues found by the quality assurance checks. </p>
<p>To see the current status, you just need to:</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Dashboard" tab. </li>
<li>Find the "QA Checks" status. </li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/qa-checks-status-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>QA Checks with no issues.</em></p>
<p>You will see one of the following status:</p>
<ul>
<li><strong>Off</strong>: When the QA checks are not enabled.</li>
<li><strong>In Progress</strong>: When the QA checks are working.</li>
<li><strong>No Issues</strong>: When the QA checks did not find any issues.</li>
<li><strong>Issues Found</strong>: When the QA checks has found issues.</li>
</ul>
<p>If you try to save a string with QA check issue, you will see a warning or an error. You can save it anyway, but you should always try to fix these issues first. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/qa-warning.png" alt="Image" width="600" height="400" loading="lazy">
<em>A warning from adding extra spaces.</em></p>
<p>💡 <strong>Tip:</strong> Clicking the "Autofix" button on the warning will try to fix the issue automatically. You can also save it anyway or cancel.</p>
<p>This is a sample image from the Crowdin documentation showing what you should see if quality assurance issues are found in your project:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-81.png" alt="Image" width="600" height="400" loading="lazy">
<em>QA checks issues found. Image taken from the <a target="_blank" href="https://support.crowdin.com/qa-checks/">Crowdin documentation</a>.</em></p>
<p>Quality assurance is a very important step that you should definitely take very seriously during your localization process.</p>
<h2 id="heading-how-to-upload-existing-translations">How to Upload Existing Translations</h2>
<p>Great. If you have existing translations, you can also upload them to your project. </p>
<p>You have three options:</p>
<ul>
<li>Upload them via the Translations Tab. </li>
<li>Upload them via the Language Page.</li>
<li>Upload them via the Translation Editor.</li>
</ul>
<h3 id="heading-how-to-upload-via-the-translations-tab">How to Upload Via the Translations Tab</h3>
<p>To upload them via the Translations Tab:</p>
<ul>
<li>Go to your project.</li>
<li>Go to "Upload existing translations".</li>
<li>Drag and drop your existing translations or select the files from your file system. </li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translations-tab-upload.png" alt="Image" width="600" height="400" loading="lazy">
<em>Upload translations via the Translations Tab.</em></p>
<p>According to Crowdin, the <a target="_blank" href="https://support.crowdin.com/uploading-translations/#key-value-formats">supported formats</a> for uploading translations include those that have a key-value structure, such as:</p>
<blockquote>
<p>Android XML, macOS/iOS Strings, Stringsdict, JSON, Chrome JSON, GO JSON, i18next JSON, FBT JSON, XLIFF, XLIFF 2.0, Java Properties, Play Properties, Java Properties XML, RESX, RESW, RES JSON, YAML, INI, Joomla INI, JS, FJS, PO, TS, QT TS, Blackberry, Symbian, Flex, BADA, TOML, Coffee, DKLANG, XAML, SRT, VTT, VTT2, SBV, SVG, DTD, CSV, RC, WXL, Maxthon, Haml, XLSX, PLIST, PHP, ARB, VDF.</p>
</blockquote>
<p>Crowdin also <a target="_blank" href="https://support.crowdin.com/uploading-translations/#text-and-html-based-formats">mentions</a> that:</p>
<blockquote>
<p>For files that do not have a defined structure, translation upload is handled by an experimental machine learning technology.  </p>
<p>This includes the following file formats: HTML, Front Matter HTML, Markdown, Front Matter Markdown, TXT, Generic XML, Web XML, DOCX, HAML, IDML, DITA, Wiki, FLSNP, MIF, and ADOC.</p>
</blockquote>
<h3 id="heading-how-to-upload-via-the-language-page">How to Upload Via the Language Page</h3>
<p>To upload them via the language page:</p>
<ul>
<li>Go to your project.</li>
<li>Go to "Dashboard".</li>
<li>Choose a target language.</li>
<li>Choose a file but instead of clicking on it, click on the three dots to its right to show more options. </li>
<li>Click on "Upload Translations".</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/upload-translations-language-page-2-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Upload translations from the language page.</em></p>
<p>When you click on this option, you will see some settings for the upload and for assigning the translations. Check the options that you need for your project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/upload-options.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-upload-via-the-translation-editor">How to Upload Via the Translation Editor</h3>
<p>Finally, you can also upload translations directly from the translation editor.</p>
<p>To do this:</p>
<ul>
<li>Click on the main menu. </li>
<li>In File, select "Upload Translations..."</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/upload-from-editor.png" alt="Image" width="600" height="400" loading="lazy">
<em>Upload Translations from the Translation Editor.</em></p>
<h3 id="heading-supported-file-formats-on-crowdin">Supported File Formats on Crowdin</h3>
<p>So far, we have mentioned different file formats that you can work with on Crowdin. But Crowdin supports more than 100+ file formats. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/100-file-formats.png" alt="Image" width="600" height="400" loading="lazy">
<em>Supports more than 100+ file formats.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/supported-file-formats.png" alt="Image" width="600" height="400" loading="lazy">
<em>Supported file formats in the Crowdin Store.</em></p>
<p>You can check the supported file formats in the following resources:</p>
<ul>
<li><a target="_blank" href="https://support.crowdin.com/supported-formats/">Supported Formats</a>.</li>
<li><a target="_blank" href="https://store.crowdin.com/categories/file-formats">File Formats in the Crowdin Store</a>.</li>
</ul>
<h2 id="heading-how-to-pre-translate-your-project">How to Pre-Translate your Project</h2>
<p>If your goal is to save time and improve your productivity, pre-translation through Machine Translation (MT) can be exactly what you need. </p>
<p>This is an automated process that applies computer-generated translations to your project when you upload a file. On Crowdin, you can configure machine translation engines to use this feature.</p>
<p>You have two alternatives to implement this process:</p>
<ul>
<li>Manual.</li>
<li>Automated.</li>
</ul>
<h3 id="heading-manual-pre-translation">Manual Pre-Translation</h3>
<p>To pre-translate your content manually via Machine Translation (MT):</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Dashboard" tab.</li>
<li>Click "Pre-translation".</li>
<li>Select via TM (Translation Memory) or via MT (Machine Translation).</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/pretranslation-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Pre-translation.</em></p>
<p>If you choose "via MT" (Machine Translation), you will have to choose your translation engine, target languages, and the files that you would like to pre-translate. You can also add labels.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-engine.png" alt="Image" width="600" height="400" loading="lazy">
<em>Pre-translate your project via MT.</em></p>
<h3 id="heading-automated-pre-translation">Automated Pre-Translation</h3>
<p>If you choose to automate the process, the system will pre-translate your new content automatically.</p>
<p>Recently, Crowdin added the possibility to enable Machine Translation Pre-Translation for new content in the project settings. If you enable this and you upload new content to your project, the system will translate automatically.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image--1-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Enable Machine Translation Pre-translate for new content. Screenshot provided by the Crowdin team.</em></p>
<h2 id="heading-offline-translation">Offline Translation</h2>
<p>Sometimes, you may need to work offline. Crowdin also has a great feature for this in situations where you or your team members would like to work on the translations offline.</p>
<p>To enable or disable this feature:</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Settings" tab.</li>
<li>Go to "Privacy and Collaboration".</li>
<li>Check (or uncheck) the "Allow offline translation" option.</li>
</ul>
<p><strong>💡Tip:</strong> This feature can be enabled or disabled by project managers.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/settings-tab-offline-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Allow offline translation.</em></p>
<p>Each file can be downloaded via the Editor or via the Language page.</p>
<h3 id="heading-how-to-download-a-file-via-the-editor">How to Download a File via the Editor</h3>
<p>To download a file via the Translation Editor:</p>
<ul>
<li>Click on the main menu at the top left.</li>
<li>Go to File</li>
<li>Click on "Download" or "Export in XLIFF".  </li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/download-files.png" alt="Image" width="600" height="400" loading="lazy">
<em>Download file via the editor.</em></p>
<h3 id="heading-how-to-download-a-file-via-the-language-page">How to Download a File Via the Language Page</h3>
<p>To download a file via the language page:</p>
<ul>
<li>Go to your project.</li>
<li>Go to "Dashboard".</li>
<li>Select a language.</li>
<li>In the list of files, click on the three dots next to the file that you would like to download to show more options.</li>
<li>Select "Download" or "Export in XLIFF".</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/language-download-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Download file via the language page.</em></p>
<h3 id="heading-how-to-download-all-files-for-a-target-language">How to Download All Files for a Target Language</h3>
<p>If you need to download all the files for a target language:</p>
<ul>
<li>Go to your project.</li>
<li>Go to "Dashboard".</li>
<li>Select a language.</li>
<li>Click on the up and down arrow icon (see the screenshot below). </li>
<li>Select "Download translations" or "Export in XLIFF".</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/download-files-from-language-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Download all translations.</em></p>
<h2 id="heading-exploring-public-projects">Exploring Public Projects</h2>
<p>Now that you know some of the most important features of Crowdin, you may also be asking yourself how you can explore other projects that are currently being translated on Crowdin. </p>
<p>If you click on "Projects" at the top and then you select "Explore Public Projects", you will see a page where you can explore public projects per topic and find popular projects on Crowdin.</p>
<p>You can also filter projects by searching on the search bar. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/explore-crowdin.png" alt="Image" width="600" height="400" loading="lazy">
<em>Explore Crowdin.</em></p>
<p>🎉 Congratulations. We just reached the end of the second part of the book, which was focused on Crowdin fundamentals. </p>
<p>Now we will dive into how teams and organizations can use Crowdin to manage their projects effectively. </p>
<h2 id="heading-crowdin-for-teams-and-organizations">🔹 <strong>Crowdin for Teams and Organizations</strong></h2>
<p>Awesome! Now let's see how Crowdin can help you if you are working with a team on a localization project or if you are the founder or manager of an organization that is interested in localizing a product or platform. </p>
<p>We will talk about how you can invite members, assign roles and tasks, and generate reports.</p>
<h3 id="heading-how-to-invite-project-members-and-contributors">How to Invite Project Members and Contributors</h3>
<p>To invite members to your project:</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Members" tab.</li>
<li>Click on the green "Invite" button.</li>
</ul>
<p>💡 <strong>Tip:</strong> You can also click on the gray "Invite People" button at the top left to reach the same options.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/members-tab-2-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Members tab.</em></p>
<p>You will need to enter information for these options before sending the invitation(s):</p>
<ul>
<li>Role.</li>
<li>Email or Crowdin username.</li>
<li>Message.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/invite-members.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sending an invitation.</em></p>
<p>If you click on "Select role", you should see this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/select-role.png" alt="Image" width="600" height="400" loading="lazy">
<em>Selecting a role.</em></p>
<p>By default, the "Translator" role will be selected.</p>
<p>You will also see three fields where you can choose which languages you would like to assign that role. For example, a contributor could be a translator for Japanese and a translator and proofreader for Spanish.</p>
<p><strong>💡 Tip:</strong> If you leave it blank, the role will apply to all languages but you can also choose specific languages. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/select-role-copy.png" alt="Image" width="600" height="400" loading="lazy">
<em>Choosing specific languages.</em></p>
<p>We will talk more about member roles in just a moment. </p>
<p>💡 <strong>Tip:</strong> You can also change the role(s) of a member in the Profile section.</p>
<p>Once you are ready, click "Save" and you will go back to the main options. Click "Done" when you are ready to send the invitations.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/invite-members.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sending an invitation.</em></p>
<h3 id="heading-how-to-send-invitation-links">How to Send Invitation Links</h3>
<p>If you would like to invite your team members through a link instead of sending them a direct invitation, you can do so too. </p>
<p>Just click on the "Get Link" button at the bottom to copy the link and send it to the person that you would like to invite to your project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/invite-members-link.png" alt="Image" width="600" height="400" loading="lazy">
<em>Get an invitation link.</em></p>
<p>You will see a confirmation message at the top.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/link-copied-to-clipboard.png" alt="Image" width="600" height="400" loading="lazy">
<em>Confirmation Message.</em></p>
<p>Then, you can paste the link wherever you need to, like an email.</p>
<h3 id="heading-how-to-manage-invitation-links">How to Manage Invitation Links</h3>
<p>To manage your invitation links, click on the "Manage Links" option:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/manage-links.png" alt="Image" width="600" height="400" loading="lazy">
<em>Manage Links.</em></p>
<p>You will see a list of all the invitation links you have generated, when you generated them, and the role(s) you granted to the invitee.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/invitation-links.png" alt="Image" width="600" height="400" loading="lazy">
<em>Invitation Links that have been generated.</em></p>
<p><strong>💡 Tip:</strong> You can copy them again by clicking on the link icon to their right or revoke them by clicking on "Revoke link".</p>
<p>If you click on "Done", you will go back to the previous screen and you can click the "X" button at the top to close it.</p>
<h2 id="heading-project-roles">Project Roles</h2>
<p>Now let's talk about the different roles that you can assign to your team members. Let's read the role descriptions provided by Crowdin in the <a target="_blank" href="https://support.crowdin.com/modifying-project-participants-roles/">documentation</a>:</p>
<h3 id="heading-owner">Owner</h3>
<blockquote>
<p>A person who created a project and has complete control over it. The owner can invite and manage project members, create projects, upload source and translation files to the project, set up integrations, etc.</p>
</blockquote>
<h3 id="heading-manager">Manager</h3>
<blockquote>
<p>Has similar rights as a project owner except the ability to manage some of the owner’s Resources (e.g., configuring MT engines, custom workflows, etc.) and delete projects.</p>
</blockquote>
<h3 id="heading-language-coordinator">Language Coordinator</h3>
<blockquote>
<p>Can manage certain features of a project only within languages they have access to.   </p>
<p>Language coordinators can translate and approve strings, manage project members and join requests, generate project reports, create tasks, and pre-translate the project content.   </p>
<p>Unlike managers, they do not have access to other project settings (e.g., project files, integrations, etc.).</p>
</blockquote>
<h3 id="heading-developer">Developer</h3>
<blockquote>
<p>Can upload files, edit translatable text, connect integrations, and use the API. Cannot manage project tasks, members and reports.</p>
</blockquote>
<h3 id="heading-proofreader">Proofreader</h3>
<blockquote>
<p>Can translate and approve strings. Doesn’t have access to project settings.</p>
</blockquote>
<h3 id="heading-translator">Translator</h3>
<blockquote>
<p>Can translate strings and vote for translations added by other members.</p>
</blockquote>
<h3 id="heading-blocked">Blocked</h3>
<blockquote>
<p>Doesn’t have access to the project.</p>
</blockquote>
<h3 id="heading-how-to-assign-or-change-roles">How to Assign or Change Roles</h3>
<p>You have two options to assign or change the role of a project member. </p>
<p>You can either:</p>
<ul>
<li>Set the role in the invitation.</li>
<li>Set the role on the member's profile page.</li>
</ul>
<h4 id="heading-how-to-set-the-role-in-the-invitation">How to Set the Role in the Invitation</h4>
<p>You can choose the role when you send an invitation to a new member.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/select-role.png" alt="Image" width="600" height="400" loading="lazy">
<em>Setting the role in the invitation.</em></p>
<h4 id="heading-how-to-setting-the-role-in-the-profile-page">How to Setting the Role in the Profile Page</h4>
<p>You can also go to the "Permissions" tab on the member's profile page to choose the roles that you would like to assign to that member and to choose the languages you would like to assign to them.</p>
<p>This is an example from the Crowdin <a target="_blank" href="https://support.crowdin.com/modifying-project-participants-roles/#languages-permissions">documentation</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-82.png" alt="Image" width="600" height="400" loading="lazy">
<em>Setting roles in the "Permissions" tab. Image taken from the Crowdin <a target="_blank" href="https://support.crowdin.com/modifying-project-participants-roles/#languages-permissions">documentation</a>.</em></p>
<p>Click on the "Save" button to save your changes.</p>
<h2 id="heading-project-managers">Project Managers</h2>
<p>Project managers play a key role for your team and for your project. They can have unlimited control over the entire project and they can help you coordinate and assign tasks to team members.</p>
<h3 id="heading-how-to-add-a-project-manager">How to Add a Project Manager</h3>
<p>Let's see how you can add a project manager to your project. You can assign the Manager role to a member when you send them an invitation:</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Members" tab. </li>
<li>Click on "Invite".</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/invite-button-4.png" alt="Image" width="600" height="400" loading="lazy">
<em>Members tab.</em></p>
<p>Click on "Translator". You will see all the possible roles. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/select-role-manager-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Select a role.</em></p>
<p>Choose "Manager". This will grant the member unlimited control over the entire project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/manager-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Manager role.</em></p>
<p>Alternatively, you can invite a Manager from your profile page:</p>
<ul>
<li>Go to your profile.</li>
<li>Go to the "Managers" tab.</li>
<li>Click on "Add Manager".</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/managers-tab-profile-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Add Manager.</em></p>
<p>Now you will need to enter the name or username of the manager, the message, the permissions that you would like to grant, and the projects that will be managed by this manager.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/invite-manager-from-profile.png" alt="Image" width="600" height="400" loading="lazy">
<em>Invite Manager.</em></p>
<h3 id="heading-editing-manager-permissions">Editing Manager Permissions</h3>
<p>You can also edit manager permissions on Crowdin. </p>
<p>To do this:</p>
<ol>
<li>Go to your profile.</li>
<li>Go to the "Managers" tab. You will see a list of the managers that you've added to your projects.</li>
<li>Double-click on the manager you would like to edit.</li>
</ol>
<p>This is an example from the <a target="_blank" href="https://support.crowdin.com/manager-permissions/#editing-manager-permissions">Crowdin documentation</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-83.png" alt="Image" width="600" height="400" loading="lazy">
<em>Managers tab example. Image taken from the <a target="_blank" href="https://support.crowdin.com/manager-permissions/#editing-manager-permissions">Crowdin documentation</a>.</em></p>
<ol start="4">
<li><p>Update the permissions for that manager.</p>
</li>
<li><p>Click on the "Save" button.</p>
</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/edit-permissions.png" alt="Image" width="600" height="400" loading="lazy">
<em>Imagen taken from the <a target="_blank" href="https://support.crowdin.com/manager-permissions/#editing-manager-permissions">Crowdin documentation</a>.</em></p>
<h3 id="heading-how-to-remove-a-manager">How to Remove a Manager</h3>
<p>To remove a manager:</p>
<ul>
<li>Go to your profile. </li>
<li>Go to the "Managers" tab.</li>
<li>Select the manager that you would like to remove.</li>
<li>Click "Remove".</li>
</ul>
<h3 id="heading-manager-vs-proofreader">Manager vs Proofreader</h3>
<p>On Crowdin, Managers and proofreaders have certain differences in their roles and permissions.</p>
<p>To explain this in more detail, the Crowdin team highlights this important question in the <a target="_blank" href="https://support.crowdin.com/modifying-project-participants-roles/#qa">documentation</a>:</p>
<blockquote>
<p>Q: <em>I’m a project owner. Do I need to invite a manager or a proofreader?</em>  </p>
<p>A: The main difference between a manager and a proofreader is the following: in addition to approving translations added by translators, managers can also invite and remove project members, upload source and translation files to the project, set up integrations, etc  </p>
<p>If you want to have a project member who should have access to the features mentioned above, you need to invite a project manager. Alternatively, if you plan to manage the project yourself, it will be enough to invite a proofreader.</p>
</blockquote>
<h2 id="heading-tasks">Tasks</h2>
<p>Organization is the key to the success of any project and Crowdin definitely knows this. This is why they incorporated very helpful tools called tasks on their platform to help you organize your project and coordinate tasks among your team members.</p>
<p>With tasks, you can assign specific files to your translators and proofreaders, set due dates, receive notifications, discuss tasks with other team members, and even split the words from the same file between different team members. </p>
<p>You can also track the status of each task in a visual board where you can drag and drop your tasks from one status to the next. </p>
<p>This sounds great, right? Let's see tasks in more detail.</p>
<h3 id="heading-how-to-create-a-new-task">How to Create a New Task</h3>
<p>To create a new task:</p>
<ul>
<li>Go to your project.</li>
<li>Click on the "Create Task" button.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tasks-tab-section-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Tasks on Crowdin.</em></p>
<p>💡 <strong>Tip:</strong> A task can only be assigned to one project.</p>
<p>After clicking on the button, you will see a form where you can enter all the details of your new task.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/new-task-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a new task.</em></p>
<p>The details that you can enter for a task include:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/task-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Task information (Part 1).</em></p>
<ul>
<li>Name.</li>
<li>Description.</li>
<li>Type (translate or proofread by own translators or by vendors).</li>
<li>Due date (optional).</li>
<li>Strings assigned to the task (all the strings of the selected file(s) or only strings modified during a specific time period).</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/task-3.png" alt="Image" width="600" height="400" loading="lazy">
<em>Task information (Part 2)</em></p>
<ul>
<li>Filter by labels (the labels that the strings of the task should have).</li>
<li>Exclude labels (the labels that the strings of the task should not have).</li>
<li>You can choose if you would like to skip strings that are already included in other tasks to avoid duplicate work by your team members.</li>
<li>Files that should be translated or proofread.</li>
<li>The target language(s). If the task has more than one target language, the system will create a task for each target language.</li>
<li>To assign team members to the task for each target language, click on the "Assign" option next to each language.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/assign-task-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Assign button.</em></p>
<p>When you click on "Assign", you will see the following options:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/assign-members-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Assign team members.</em></p>
<ul>
<li>Select the user(s) that you would like to assign to the task. You can search for users using the filter tool.</li>
<li>If you need to remove a user, just click on it on the list to the right. You can empty the list by clicking on the trash icon.</li>
</ul>
<p>💡 <strong>Tip:</strong> You can also choose if you would like to split the files to assign multiple users to the same file. </p>
<ul>
<li>Once you have all the users selected, click on the "Apply" button.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/select-all-users.png" alt="Image" width="600" height="400" loading="lazy">
<em>Users selected for the task.</em></p>
<p>You will see the profile image of the assigned team members in their corresponding language(s).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/assigned-members-list-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Assigned team member.</em></p>
<p>Click on "Create Task" to add the new task. Now you will see the task dashboard with a new task in each target language you selected. </p>
<p>In this case, I selected Spanish and assigned myself to the task:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/task-board-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Tasks dashboard.</em></p>
<h3 id="heading-the-tasks-board">The Tasks Board</h3>
<p>Now it's time to use the tasks board. This board is very helpful to see all the tasks of your project and track their status.</p>
<p>You will immediately notice that there are three possible statuses for a task: </p>
<ul>
<li>To Do</li>
<li>In Progress</li>
<li>Done</li>
</ul>
<p><strong>💡 Tip:</strong> Project managers can move a task from one status to another by dragging and dropping it.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/task-board-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Tasks dashboard.</em></p>
<p>To see all the tasks in a specific target language, just click on that language and you will see them as "cards" with their corresponding information.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/task-board-2-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Tasks board.</em></p>
<p><strong>💡 Tip:</strong> If a task is assigned to you, you will see a gray star next to the name of the task. You can also search for tasks and filter them. </p>
<h3 id="heading-how-to-filter-tasks-by-members">How to Filter Tasks by Members</h3>
<p>You can see all the tasks assigned to a specific team member by using filters.</p>
<ul>
<li>Click on "Filters".</li>
<li>Click on "All users" (next to "Filter by").</li>
<li>Search for the username of the team member.</li>
<li>Click on the username.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/filter-by-members-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Filter tasks by members.</em></p>
<p>💡 <strong>Tip:</strong> To clear the filter, click on the "Clear filter" option to the right.</p>
<h3 id="heading-task-details">Task Details</h3>
<p>The task details is the basic information and structure of a task. With the details, you can see:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-20-at-10.17.37-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>A Task.</em></p>
<ul>
<li>If the task is assigned to you (a gray star).</li>
<li>Task number.</li>
<li>Task name.</li>
<li>When the task was created.</li>
<li>If it will be translated and proofread by your own team or by vendors.</li>
<li>The profile pictures of the team members assigned to the task.</li>
<li>Word count.</li>
<li>Number of comments on the task.</li>
</ul>
<p>If you click on the task, you will see a more detailed overview:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/detailed-task-view.png" alt="Image" width="600" height="400" loading="lazy">
<em>Task details.</em></p>
<h3 id="heading-how-to-add-comments-to-a-task">How to Add Comments to a Task</h3>
<p>If you scroll down, you will also have the option to add comments to the task.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/detailed-task-view-2-copy-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Add comments to a task.</em></p>
<h3 id="heading-how-to-change-the-status-of-a-task">How to Change the Status of a Task</h3>
<p>From this detailed view, you will also be able to change the status of the task by just clicking on a button:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/change-status-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Task options.</em></p>
<p>If you change the status of a task using these buttons, you will also see the change reflected on the main task board, which would be equivalent to dragging and dropping it into a new status.</p>
<h3 id="heading-how-to-manage-tasks">How to Manage Tasks</h3>
<p>If you click on a task to check its details, and then you click on the three dots at the top right to display more options, you will be able to edit, close, and delete the task.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/edit-a-task-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>More options for managing tasks.</em></p>
<h3 id="heading-how-to-edit-a-task">How to Edit a Task</h3>
<p>This is what you will see if you try to edit a task:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/editing-a-task.png" alt="Image" width="600" height="400" loading="lazy">
<em>Edit task view.</em></p>
<p>It's very similar to what we saw when we created the task. You can change the strings assigned, the files, dates, and you can even reset the task scope and the progress for the assigned files.</p>
<h3 id="heading-how-to-close-a-task">How to Close a Task</h3>
<p>To close a task, you can click on "Close":</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/edit-a-task-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>More options for managing tasks.</em></p>
<h3 id="heading-how-to-see-all-closed-tasks">How to See All Closed Tasks</h3>
<p>To see all your closed tasks, you will need to:</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Tasks" tab.</li>
<li>Select "Closed" (next to "All").</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/closed-tasks.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>💡 <strong>Tip:</strong> "Done" and "Closed" are a bit different. A task can have the "Done" status but not closed. When a task is has the "Done" status, you will see a button on the card to close it, just like in the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/close-task-when-done-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Close button on a task marked as "Done".</em></p>
<h3 id="heading-how-to-reopen-a-task">How to Reopen a Task</h3>
<p>If you close a task and then realize that you need to reopen it, you just need to:</p>
<ul>
<li>Open the details of the closed task.</li>
<li>Click on the three dots to the right to see additional options.</li>
<li>Choose "Reopen".</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/reopen-task.png" alt="Image" width="600" height="400" loading="lazy">
<em>Reopen a task.</em></p>
<p>Now you will see the reopened task on the "To do" column of the tasks board.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/todo-task-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Reopened task.</em></p>
<h3 id="heading-how-to-delete-a-task">How to Delete a Task</h3>
<p>To delete a task:</p>
<ul>
<li>Open the task details.</li>
<li>Click on the three dots. </li>
<li>Select "Delete".</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/delete-a-task.png" alt="Image" width="600" height="400" loading="lazy">
<em>Delete a task.</em></p>
<h3 id="heading-how-to-see-all-tasks-assigned-to-you">How to See All Tasks Assigned to You</h3>
<p>If you are contributing to a project, you can see all the tasks assigned to you by following these steps:</p>
<ul>
<li>Go to your profile.</li>
<li>Go to the "To do" tab.</li>
<li>You will see all the tasks assigned to you and your archived tasks. </li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/see-all-my-tasks-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>The "To Do" tab on Profile.</em></p>
<p>💡 <strong>Tip:</strong> At the top, you will also find a search field to filter your tasks and an option to filter tasks by project.</p>
<p>This is the basic information that you will see for each task in this view:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/todo-tasks-profile.png" alt="Image" width="600" height="400" loading="lazy">
<em>Task in the "To Do" tab on the profile.</em></p>
<ul>
<li>Task number.</li>
<li>Task status.</li>
<li>Project.</li>
<li>Target language.</li>
<li>Word count.</li>
</ul>
<p>You will also see these two options to:</p>
<ul>
<li>Open the translation editor and start working on the task.</li>
<li>Archive the task.</li>
</ul>
<h3 id="heading-important-question-about-tasks">Important Question about Tasks</h3>
<p>Crowdin mentions this <a target="_blank" href="https://support.crowdin.com/enterprise/tasks/#qa">important question in the documentation</a>:</p>
<blockquote>
<p><strong>Q: <em>How the source file updates affect the existing translation and proofreading tasks?</em></strong>  </p>
<p>A: After the source file update, the list of source strings included in the task will be updated the following way:  </p>
<ul>
<li>The strings removed from the source file during the update will be removed from the task.  </li>
<li>The modified strings marked with the <em>Keep Translations</em> option will appear in the task with the new modified text.  </li>
<li>The newly added strings won’t affect the existing task in any way.  </li>
</ul>
<p>If the source file is restored to the revision, containing the removed strings, they will reappear in the task.</p>
</blockquote>
<h2 id="heading-project-reports">Project Reports</h2>
<p>As a project owner or project manager, reports can very helpful to understand your team's current progress and activity.</p>
<p>Crowdin has a reports feature where you can check your project status. This is a report of the demo project that we have been working with so far: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-reports.png" alt="Image" width="600" height="400" loading="lazy">
<em>Reports tab.</em></p>
<p>To access your project report:</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Reports" tab.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-20-at-11.31.20-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Report Part 1.</em></p>
<p>You will see the project status by default and you can switch to cost reports if you need to.</p>
<p>Here you can see some screenshots with the type of data that you can analyze and visualize with these reports:</p>
<ul>
<li>Total project size and the number of translatable strings.</li>
<li>Source language and target language.</li>
<li>Number of members.</li>
<li>Number of managers.</li>
<li>Number of translatable words.</li>
<li>Number of hidden words.</li>
<li>Number of duplicate words.</li>
<li>Number of translated words.</li>
<li>Number of approved words.</li>
<li>Number of new members.</li>
<li>Number of active members.</li>
</ul>
<p>You can see these reports with their corresponding increase or decrease percentage compared to the time period you selected.</p>
<p>You will also find charts of the translation activity with their corresponding legends.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-20-at-11.34.02-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Report (Part 2).</em></p>
<p>You will find charts of the "Proofreading Activity".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-20-at-11.34.11-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And charts of the "Source Strings Activity", and so on.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-20-at-11.34.13-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can also change the report unit by clicking on this option:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/report-unit.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can choose: </p>
<ul>
<li>Strings.</li>
<li>Words.</li>
<li>Characters without spaces.</li>
<li>Characters with spaces.</li>
</ul>
<p>On the <a target="_blank" href="https://support.crowdin.com/project-reports/?q=reports">Crowdin documentation</a>, you can find examples of the Translation Activity chart, the Proofreading Activity chart, and the Source Strings Activity chart:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translation-activity-chart-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translation Activity. Image taken from the <a target="_blank" href="https://support.crowdin.com/project-reports/?q=reports">Crowdin documentation</a>.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-20-at-11.41.30-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Proofreading Activity. Image taken from the <a target="_blank" href="https://support.crowdin.com/project-reports/?q=reports">Crowdin documentation</a>.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Screenshot-2023-09-20-at-11.41.46-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Source Strings Activity. Image taken from the <a target="_blank" href="https://support.crowdin.com/project-reports/?q=reports">Crowdin documentation</a>.</em></p>
<h3 id="heading-top-members-report">Top Members Report</h3>
<p>Your team members are fundamental for your localization process. Having a detailed report of your most dedicated and productive team members is always helpful. </p>
<p>To generate a report of your top members:</p>
<ul>
<li>Go to your project.</li>
<li>Go to the "Reports" tab.</li>
<li>Click on "Top Members".</li>
<li>Select a date range from the calendar.</li>
<li>Click on "Generate".</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/top-members-report.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You will see a list of your top members with:</p>
<ul>
<li>Their target languages</li>
<li>How many strings they translated</li>
<li>Target words</li>
<li>How many strings they approved</li>
<li>How many votes they submitted.</li>
</ul>
<p>You can also export your report in XLSX, CSV or JSON format and display or hide specific columns.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/export-report-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Export options.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/show-or-hide-columns.png" alt="Image" width="600" height="400" loading="lazy">
<em>Display or hide columns.</em></p>
<h2 id="heading-conversations-on-crowdin">Conversations on Crowdin</h2>
<p>Communication is essential for any successful project. You and your team members can communicate directly on Crowdin with the conversations feature.</p>
<p>With this feature, you can communicate with one or more team member in a private chat. Each conversation has a subject, so you can easily find them.</p>
<h3 id="heading-how-to-access-conversations">How to Access Conversations</h3>
<p>To access your conversations:</p>
<p>Click on the conversations icon at the top right, next to your profile picture.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/conversation-button-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Conversations button.</em></p>
<p>Go to "Create Conversation".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/create-conversations-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create a Conversation.</em></p>
<p>You can search for users by their name or username and you can also filter users by project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/filter-users.png" alt="Image" width="600" height="400" loading="lazy">
<em>Filter users.</em></p>
<p>This is an example from the <a target="_blank" href="https://support.crowdin.com/conversations/">Crowdin Documentation</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-86.png" alt="Image" width="600" height="400" loading="lazy">
<em>Conversation. Image taken from the <a target="_blank" href="https://support.crowdin.com/conversations/">Crowdin documentation</a>.</em></p>
<h3 id="heading-how-to-manage-conversations-and-messages">How to Manage Conversations and Messages</h3>
<p>For conversations, you can:</p>
<ul>
<li>Change the subject for all the users in the conversation.</li>
<li>Mute the conversation to stop receiving notifications.</li>
<li>Add users.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-87.png" alt="Image" width="600" height="400" loading="lazy">
<em>Example conversation. Image taken from the <a target="_blank" href="https://support.crowdin.com/conversations/">Crowdin documentation</a>.</em></p>
<p>For individual messages within a conversation, you can:</p>
<ul>
<li>Share the message in the same conversation or in another conversation. </li>
<li>Mark the message as unread.</li>
<li>Edit your messages.</li>
<li>Delete your messages.</li>
<li>Report spam.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-88.png" alt="Image" width="600" height="400" loading="lazy">
<em>Example of managing messages. Image taken from the <a target="_blank" href="https://support.crowdin.com/conversations/#messages">Crowdin documentation</a>.</em></p>
<p>🎉 Awesome. Now you know the most important features of Crowdin for teams and organizations.</p>
<p>Let's dive into how you can integrate Crowdin with other services and how you can install apps to improve your productivity.</p>
<h2 id="heading-crowdin-integrations-and-productivity-tools">🔹 Crowdin Integrations and Productivity Tools</h2>
<p>One of the key characteristics of Crowdin is its connectivity. You can connect your project to different external services to import and export your files and translations as needed.</p>
<h2 id="heading-what-is-an-integration">What is an Integration?</h2>
<p>On Crowdin, an integration is a "connection" that you can make between your project and an external service to synchronize your files between these platforms automatically.</p>
<p>On the <a target="_blank" href="https://store.crowdin.com/">Crowdin store</a>, you can find over 600 apps and integrations that you can add to your project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Crowdin-store.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Crowdin Store.</em></p>
<p>You can filter them by:</p>
<ul>
<li>Collections.</li>
<li>Categories.</li>
<li>Partners.</li>
<li>QA Checks.</li>
</ul>
<p>To give you an idea of the types of apps and integrations that you can find, these are some of the featured apps and integrations:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/featured-integrations.png" alt="Image" width="600" height="400" loading="lazy">
<em>Featured apps and integrations.</em></p>
<p>Let's talk about a few of them in more detail. </p>
<h3 id="heading-github-integration">GitHub Integration</h3>
<p>If your project is hosted on a GitHub repository, the <a target="_blank" href="https://store.crowdin.com/github">GitHub integration</a> could be exactly what you need.</p>
<blockquote>
<p>Crowdin’s integration with GitHub synchronizes source and translation files between your GitHub repository and translation project in Crowdin. All translated and approved files will be automatically pushed as a pull request to the l10n branch in GitHub repository.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/GitHub-integration.png" alt="Image" width="600" height="400" loading="lazy">
<em>GitHub Integration.</em></p>
<p>To learn more about this integration, you can check out <a target="_blank" href="https://www.youtube.com/watch?v=8baL6VWnnZg">this tutorial</a> created by Crowdin.</p>
<h3 id="heading-github-crowdin-action">GitHub Crowdin Action</h3>
<p>If you need to upload and download files from your GitHub repository to your Crowdin project automatically, this action can be very helpful for you.</p>
<p>The <a target="_blank" href="https://store.crowdin.com/github-action">GitHub Crowdin Action</a> can:</p>
<ul>
<li>Upload source files to Crowdin.</li>
<li>Upload translations to Crowdin.</li>
<li>Downloads translations from Crowdin.</li>
<li>Create a PR with the translations.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/github-crowdin-action.png" alt="Image" width="600" height="400" loading="lazy">
<em>GitHub Crowdin Action.</em></p>
<p>To learn more about the GitHub Crowdin Action, check out <a target="_blank" href="https://www.youtube.com/watch?v=5b7BMuCoKGg">this tutorial</a> created by the Crowdin team.</p>
<h3 id="heading-google-drive-integration">Google Drive Integration</h3>
<p>Google Drive is very helpful to create, host, and share documents. With the Google Drive integration, you can translate your files very easily on Crowdin.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/google-drive-integration.png" alt="Image" width="600" height="400" loading="lazy">
<em>Google Drive Integration.</em></p>
<p>To learn more about this integration, you can check out <a target="_blank" href="https://www.youtube.com/watch?v=M_WbdDQmEP8">this tutorial</a> created by the Crowdin team.</p>
<h3 id="heading-ai-assistant">AI Assistant</h3>
<p>With the rise in popularity of Large Language Models (LLMs), artificial intelligence has become a very important tool for translation and localization. </p>
<p>Crowdin has a very helpful <a target="_blank" href="https://store.crowdin.com/localization-ai">AI Assistant</a> that you can install to help you and your team. It is described as:</p>
<blockquote>
<p>An AI chatbot for translators built on OpenAI’s ChatGPT API. The first version of this app works as a co-pilot for translators. (<a target="_blank" href="https://store.crowdin.com/localization-ai">Source: Crowdin</a>)</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/ai-assistant.png" alt="Image" width="600" height="400" loading="lazy">
<em>AI Assistant.</em></p>
<p>Crowdin mentions that the AI Assistant has these interesting features:</p>
<blockquote>
<p>Our AI Assistant has a prompt engineering feature, empowering translators to customize prompts before commencing a translation project. As a result, they can conveniently modify the context, enhancing precision and meaningful translations.</p>
</blockquote>
<p>To learn more about this integration, check out <a target="_blank" href="https://www.youtube.com/watch?v=DSEu0iQanc4">this tutorial</a> created by the Crowdin team.</p>
<h3 id="heading-visual-studio-code-integration">Visual Studio Code Integration</h3>
<p>If you are a developer who works with <a target="_blank" href="https://store.crowdin.com/visual-studio-code">Visual Studio Code</a>, then Crowdin also has you covered because the team developed an extension to help you to translate your project.</p>
<blockquote>
<p>Integrate your Visual Studio Code projects with Crowdin to optimize the localization process. <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=Crowdin.vscode-crowdin">IDE Plugin</a> allows uploading new source strings instantly to your Crowdin project and downloading translations. (<a target="_blank" href="https://store.crowdin.com/visual-studio-code">Source: Crowdin</a>)</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/visual-studio-code.png" alt="Image" width="600" height="400" loading="lazy">
<em>Visual Studio Code Integration.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/vs-code-extension.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Extensions Marketplace.</em></p>
<p>You can learn more about this extension on its <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=Crowdin.vscode-crowdin">official documentation</a> in the Visual Studio Code Extensions Marketplace.</p>
<h3 id="heading-video-captions-translator">Video Captions Translator</h3>
<p>If you or your organization need to translate video captions, Crowdin has an integration for that too. It is called <a target="_blank" href="https://store.crowdin.com/video-captions-translator">Video Captions Translator</a>.</p>
<blockquote>
<p>Professional translations for video subtitles. Setup integration once, define your localization workflow and spend less time managing translations.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/video-captions-translator.png" alt="Image" width="600" height="400" loading="lazy">
<em>Video Captions Translator.</em></p>
<p>To learn more about YouTube Captions Translation with Crowdin, you can check out <a target="_blank" href="https://store.crowdin.com/video-captions-translator">this tutorial</a> created by the Crowdin team.</p>
<h3 id="heading-google-sheets-integration">Google Sheets Integration</h3>
<p>If you use <a target="_blank" href="https://store.crowdin.com/spreadsheet-crowdin">Google Sheets</a> to manage your localization keys, you can add this integration to your project to map your columns to their corresponding fields on Crowdin, including:</p>
<ul>
<li>Key.</li>
<li>Source Text.</li>
<li>Target Languages (all project languages).</li>
<li>Labels.</li>
<li>Context.</li>
<li>Translation Maximum Length.</li>
</ul>
<p>Please note that the Crowdin team mentions that:</p>
<blockquote>
<p>It's worth noting that this integration will only sync the first sheet from your Google Sheet document.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/google-sheets.png" alt="Image" width="600" height="400" loading="lazy">
<em>Google Sheets Integration.</em></p>
<p>To learn more about the Google Sheets Integration, check out <a target="_blank" href="https://www.youtube.com/watch?v=7tOanqDiIJ8">this tutorial</a> created by the Crowdin team. </p>
<h3 id="heading-suggestions-diff-checker">Suggestions Diff Checker</h3>
<p>If you have ever asked for a tool that could help your translators and proofreaders compare translations very easily with visual cues, this app is for you. </p>
<p>Crowdin describes <a target="_blank" href="https://store.crowdin.com/diff-checker">Suggestions Diff Checker</a> as:</p>
<blockquote>
<p>A great helper for proofreaders and translators that compares two translations and shows the difference between them.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/suggestions-diff-checker.png" alt="Image" width="600" height="400" loading="lazy">
<em>Suggestions Diff Checker.</em></p>
<h3 id="heading-proofreading-diff">Proofreading Diff</h3>
<p>Proofreading is a very important task for the localization process. Proofreaders can edit the translations and make sure that they are as accurate as possible. </p>
<p>With the <a target="_blank" href="https://store.crowdin.com/proofreading-diff">Proofreading Diff</a> report app, you can:</p>
<blockquote>
<p>Track and analyze changes made during the proofreading process. This tool can help you provide a thorough feedback to translators about their work.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/proofreading-diff.png" alt="Image" width="600" height="400" loading="lazy">
<em>Proofreading Diff.</em></p>
<h3 id="heading-video-preview">Video Preview</h3>
<p>Translating the subtitles of a video without actually watching the video simultaneously can be quite challenging because having the full context of a string is very helpful to translate it accurately. </p>
<p>This is why Crowdin created a translator productivity app called <a target="_blank" href="https://store.crowdin.com/preview-video">Video Preview</a>. They describe it as:</p>
<blockquote>
<p>A handy tool that can be useful when you have video subtitles to translate. It allows you to specify the video URL for each file with subtitles, and enables translators to preview the video while working on the translation.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/video-preview.png" alt="Image" width="600" height="400" loading="lazy">
<em>Video Preview.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-96.png" alt="Image" width="600" height="400" loading="lazy">
<em>Video Preview in action. Image taken from the <a target="_blank" href="https://store.crowdin.com/preview-video">Crowdin Documentation</a>.</em></p>
<h3 id="heading-glossary-editor">Glossary Editor</h3>
<p>This is another helpful Crowdin application for managing your project glossaries.</p>
<p>Crowdin mentions that the <a target="_blank" href="https://store.crowdin.com/glossary-edit-app">Glossary Editor</a>:</p>
<blockquote>
<p>Allows you to add and change the terms from your glossaries directly in Crowdin Editor.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/glossary-editor.png" alt="Image" width="600" height="400" loading="lazy">
<em>Glossary Editor.</em></p>
<h3 id="heading-project-duplicator">Project Duplicator</h3>
<p>Have you ever wished that you could use a project as a template for another project and save yourself all the initial set up time?</p>
<p>If you have, then <a target="_blank" href="https://store.crowdin.com/create-project-app">Project Duplicator</a> is exactly what you need:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-duplicator.png" alt="Image" width="600" height="400" loading="lazy">
<em>Project Duplicator.</em></p>
<p>With Project Duplicator, you can copy the following settings from a project:</p>
<ul>
<li>Source language.</li>
<li>Quality assurance checks.</li>
<li>Source strings.</li>
<li>Translations.</li>
<li>Translation Memory.</li>
<li>Notifications.</li>
<li>Language Mapping.</li>
</ul>
<h3 id="heading-unity-integration">Unity Integration</h3>
<p>If you are a game developer and you work with Unity, this integration is exactly what you need.</p>
<p>With the <a target="_blank" href="https://store.crowdin.com/unity">Unity Integration</a>, you can:</p>
<blockquote>
<p>Translate the content within your tables (strings and assets) and download translations into Unity.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/unity.png" alt="Image" width="600" height="400" loading="lazy">
<em>Unity Integration.</em></p>
<p>This is an official screenshot of the Crowdin Plugin for Unity:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-101.png" alt="Image" width="600" height="400" loading="lazy">
<em>Imagen taken from the <a target="_blank" href="https://store.crowdin.com/unity">Crowdin Documentation</a>.</em></p>
<h3 id="heading-is-crowdin-slow-for-everyone-or-just-me">Is Crowdin slow for everyone or just me?</h3>
<p>Yes, this is the official name of a translator productivity tool in Crowdin! 🙂 It is a performance widget for measuring your internet connection. </p>
<p>Crowdin <a target="_blank" href="https://store.crowdin.com/internet-speed">mentions</a> that it can be helpful to:</p>
<blockquote>
<p>Know immediately if the slowness you experience is caused by your internet connection or it's a Crowdin performance issue.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/is-crowdin-slow.png" alt="Image" width="600" height="400" loading="lazy">
<em>Is Crowdin slow for everyone or just me?</em></p>
<p>This is an official screenshot of the widget:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-97.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image taken from the <a target="_blank" href="https://store.crowdin.com/internet-speed">Crowdin Documentation</a>.</em></p>
<h3 id="heading-units-converter">Units Converter</h3>
<p>You learned that localization is broader than translation. Units are a great example of this. </p>
<p>When you localize a product that includes units of length, area, mass, volume, temperature, speed, and more, you need to have a tool at hand to convert them quickly while you localize your content. </p>
<p>Crowdin mentions that:</p>
<blockquote>
<p>The app is especially convenient when localizing for cultures that use different measurement systems.</p>
</blockquote>
<p>This is where the <a target="_blank" href="https://store.crowdin.com/units_converter">Units Converter app</a> can save you a lot of time because you can have it on your Translation Editor and quickly convert units without pausing your localization process to go to another tool.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/units-converter.png" alt="Image" width="600" height="400" loading="lazy">
<em>Units Converter.</em></p>
<p>This is an official screenshot of the app:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-98.png" alt="Image" width="600" height="400" loading="lazy">
_Units Converter app. Image taken from the <a target="_blank" href="https://store.crowdin.com/units_converter">Crowdin Documentation</a>._</p>
<h3 id="heading-screenshots-uploader">Screenshots Uploader</h3>
<p>Visual context is essential for writing high-quality translations. </p>
<p>The <a target="_blank" href="https://store.crowdin.com/screenshots-uploader">Screenshots Uploader</a> app:</p>
<blockquote>
<p>Makes it easier for your team to receive visual context.</p>
</blockquote>
<p>You can:</p>
<ul>
<li>Allow translators to upload screenshots. </li>
<li>Paste screenshots from your clipboard history without saving them on your device.</li>
<li>Edit screenshots before uploading them with helpful tools like cropping, zooming in, and so on.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/screenshots-uploader.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshots Uploader.</em></p>
<h3 id="heading-directory-notifications">Directory Notifications</h3>
<p>The <a target="_blank" href="https://store.crowdin.com/directory-notification">Directory Notifications</a> app is helpful for project owners and managers because Crowdin will:</p>
<blockquote>
<p>Send an email notification whenever a directory of files in your Crowdin project gets translated or proofread.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/directory-notifications.png" alt="Image" width="600" height="400" loading="lazy">
<em>Directory Notifications.</em></p>
<p>This is an official screenshot of the app:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-100.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image taken from the <a target="_blank" href="https://store.crowdin.com/directory-notification">Crowdin Documentation</a>.</em></p>
<h3 id="heading-emoji-input-for-editor">Emoji Input for Editor</h3>
<p>Emojis are awesome, right? I think we can all agree on that. 😁 </p>
<p>Luckily for us, Crowdin has an <a target="_blank" href="https://store.crowdin.com/emoji">Emoji Input for the Translation Editor</a>. They describe it as:</p>
<blockquote>
<p>A Crowdin Editor emoji list app for easy access with an extensive search functionality.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/emoji-input.png" alt="Image" width="600" height="400" loading="lazy">
<em>Emoji Input for Editor.</em></p>
<p>This is an official screenshot of the app:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-99.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image taken from the <a target="_blank" href="https://store.crowdin.com/emoji">Crowdin Documentation</a>.</em></p>
<h3 id="heading-emoji-mismatch">Emoji Mismatch</h3>
<p>And now let's dive into helpful automated Quality Assurance (QA) checks for our project that are available for Enterprise Crowdin accounts.</p>
<p>The first one is <a target="_blank" href="https://store.crowdin.com/emoji-mismatch-custom">Emoji Mismatch</a>, which can help us to find:</p>
<blockquote>
<p>Missed, extra or mismatched emoji in the translation.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/Emoji-Mismatch.png" alt="Image" width="600" height="400" loading="lazy">
<em>Emoji Mismatch.</em></p>
<p>Please note that this works with Crowdin Enterprise.</p>
<h3 id="heading-space-after-punctuation">Space After Punctuation</h3>
<p>This is a very helpful quality assurance tool that you can add to your project.</p>
<p>Crowdin mentions that <a target="_blank" href="https://store.crowdin.com/space-after-punctuation-custom">Space After Punctuation</a>:</p>
<blockquote>
<p>Checks whether the translation contains the spaces after the punctuation symbols.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/space-after-punctuation.png" alt="Image" width="600" height="400" loading="lazy">
<em>Space After Punctuation.</em></p>
<p>Please note that this works with Crowdin Enterprise. </p>
<h3 id="heading-url-localization">URL Localization</h3>
<p>This is a quality assurance tool that you can also install for your project. With <a target="_blank" href="https://store.crowdin.com/url-localization-custom">URL localization</a>, you can check:</p>
<blockquote>
<p>Mistakes in URLs in the translation according to QA check configuration.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/url-localization.png" alt="Image" width="600" height="400" loading="lazy">
<em>URL Localization.</em></p>
<p>Please note that this works with Crowdin Enterprise. </p>
<h3 id="heading-duplicated-words">Duplicated Words</h3>
<p>This is a great tool for the proofreading phase of the localization project. Crowdin mentions that <a target="_blank" href="https://store.crowdin.com/duplicated-words-custom">Duplicated Words</a>:</p>
<blockquote>
<p>Removes every second repeated word in translation.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/duplicated-words.png" alt="Image" width="600" height="400" loading="lazy">
<em>Duplicated words.</em></p>
<p>Please note that this works with Crowdin Enterprise. </p>
<h3 id="heading-time-format-consistency">Time Format Consistency</h3>
<p>Writing time in a consistent format is also very important when you are localizing a product.</p>
<p>Crowdin <a target="_blank" href="https://store.crowdin.com/time-format-consistency">mentions</a> that:</p>
<blockquote>
<p>This QA check verifies that time formats in the source and translated text match. It checks for time formats in the 24-hour format (HH:MM).</p>
<p>The QA check will give a positive result if the number and format of time instances are consistent between the source and translated text, and a negative result if there is a mismatch.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/time-format-consistency.png" alt="Image" width="600" height="400" loading="lazy">
<em>Time Format Consistency.</em></p>
<p>Please note that this works with Crowdin Enterprise. </p>
<h3 id="heading-camelcase-consistency-check">camelCase Consistency Check</h3>
<p>This quality assurance check helps you ensure that product names, brand names, or other names that should be written in camelCase follow the same format in the translation.</p>
<p>Crowdin <a target="_blank" href="https://store.crowdin.com/camelcase-check">mentions</a> that it:</p>
<blockquote>
<p>Validates that all camelCase words present in the source text are accurately retained in the translated text.</p>
<p>If a camelCase word in the source text is not found in the translated text, the check will fail and notify the translator with a detailed message.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/camelcase-consistency-check.png" alt="Image" width="600" height="400" loading="lazy">
<em>camelCase Consistency Check.</em></p>
<h3 id="heading-more-apps-and-integrations">More Apps and Integrations</h3>
<p>We have seen many helpful apps and integrations for your localization process. They can optimize your team's productivity and help you to provide high-quality translations.</p>
<p>But this is just the start. Crowdin has more than 600 apps and integrations for your project. You can find a full list on the <a target="_blank" href="https://store.crowdin.com/categories/all">Crowdin Store</a>. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-store-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Crowdin Store.</em></p>
<p>If you click on them, you will find more details about what they do, how they do it, and you may even find the source code that implements the functionality.</p>
<p>You can also sort them by relevance, name, or date. </p>
<p>The tools that you need to be more productive and deliver high-quality translations to your users are just one click away. </p>
<h2 id="heading-how-to-translate-a-website-on-crowdin">🔹 How to Translate a Website on Crowdin</h2>
<p>Great work! We've reached a very important part of the book, how to translate a website on Crowdin.</p>
<h3 id="heading-how-to-translate-a-website-on-crowdin-1">How to Translate a Website on Crowdin</h3>
<p>There are three main approaches for translating a website on Crowdin:</p>
<ul>
<li>Integrations.</li>
<li>JS Proxy Translator.</li>
<li>Crowdin In-context.</li>
</ul>
<p>Since there are many technologies, frameworks, and libraries for web development and the internationalization process is very technology-specific, it is recommended to analyze all the options available and find the one that works best for your particular use case.</p>
<h3 id="heading-integrations-1">Integrations</h3>
<p>The web development world is incredibly diverse. We can create and host websites on a variety of services and develop them using different tools.</p>
<p>To support this variety of tools and services, Crowdin has developed many integrations with external platforms to import your text and send your translations to the Content Management System (CMS) of your choice.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translate-website-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Translate a Website on Crowdin.</em></p>
<p>For example, you can translate a website from WordPress, Webflow, Joomla, and other similar tools. </p>
<p>According to <a target="_blank" href="https://crowdin.com/blog/2020/12/17/website-translation-with-crowdin">Crowdin</a>:</p>
<blockquote>
<p>With Crowdin, you are not confined to any specific website building and hosting services.</p>
<p>We do not only have 15 apps, including <a target="_blank" href="https://store.crowdin.com/wix-proxy-translator">Wix</a>, <a target="_blank" href="https://store.crowdin.com/ghost-org-proxy-translator">Ghost</a>, <a target="_blank" href="https://store.crowdin.com/squarespace-proxy-translator">Squarespace</a> and <a target="_blank" href="https://store.crowdin.com/webflow-proxy-translator">Webflow</a>, that provide you with the best way to translate a website, but a separate <a target="_blank" href="https://store.crowdin.com/js-proxy-translator">JS Proxy</a> technology that will help you with the localization of any other website.</p>
</blockquote>
<p>Essentially, these technologies:</p>
<ul>
<li>Scan your web pages.</li>
<li>Detect the content that can be translated (strings).</li>
<li>Extract them in a format that can be localized.</li>
<li>Synchronize the translations with your original project.</li>
</ul>
<p>You just need to add a JavaScript snippet to your code and you will be ready to start translating.</p>
<p>These are some of the apps that you can install to start translating your website on various external services:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/apps-for-translation.png" alt="Image" width="600" height="400" loading="lazy">
<em>A list of Crowdin apps that you can install to <a target="_blank" href="https://crowdin.com/blog/2020/12/17/website-translation-with-crowdin">translate your website</a>.</em></p>
<p>If host your repository on GitHub or you use GitHub pages, you can also use Crowdin's <a target="_blank" href="https://store.crowdin.com/github">GitHub Integration</a> and the <a target="_blank" href="https://store.crowdin.com/github-action">GitHub Crowdin Action</a> to synchronize your website files and translations automatically.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/github-integrations.png" alt="Image" width="600" height="400" loading="lazy">
<em>GitHub Integrations.</em></p>
<h3 id="heading-js-proxy-translator">JS Proxy Translator</h3>
<p>Integrations are very helpful, but Crowdin also has an option that can help you with the localization of any website, irrespective of the service where it is hosted. </p>
<p>This approach to website localization on Crowdin is called <a target="_blank" href="https://store.crowdin.com/js-proxy-translator">JS Proxy Translator</a>. </p>
<p>Crowdin mentions that with this proxy, you can:</p>
<ul>
<li>Synchronize your sources and translated content.</li>
<li>Localize your website with minimum effort.</li>
<li>Extract source text without any coding.</li>
<li>Translate meta titles and description to be SEO-friendly.</li>
<li>Schedule when to synchronize your translations.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/js-proxy.png" alt="Image" width="600" height="400" loading="lazy">
<em>JS Proxy Translator.</em></p>
<p>Here we have two official screenshots provided by the Crowdin team that shows the steps required to configure the JS Proxy Translator. </p>
<p>The first step is to import your website content. You just need to enter your site URL and specify if you would like to sync the source files manually or daily by importing them automatically once a day.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-104.png" alt="Image" width="600" height="400" loading="lazy">
<em>JS Proxy Translator (Part 1). Image taken from the <a target="_blank" href="https://store.crowdin.com/js-proxy-translator">Crowdin documentation</a>.</em></p>
<p>Then, after your source text is translated, you can publish the translations to your website.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-105.png" alt="Image" width="600" height="400" loading="lazy">
<em>JS Proxy Translator (Part 2). Image taken from the <a target="_blank" href="https://store.crowdin.com/js-proxy-translator">Crowdin documentation</a>.</em></p>
<p>The Crowdin team does <a target="_blank" href="https://store.crowdin.com/js-proxy-translator">mention</a> that:</p>
<blockquote>
<p>You need to publish all content on your website before synchronizing it to your project for translation.</p>
</blockquote>
<p>To learn more about how to translate a website with a JS Proxy on Crowdin, check out <a target="_blank" href="https://www.youtube.com/watch?v=q_0byyBDRGI">this tutorial</a> created by the Crowdin team.</p>
<h3 id="heading-in-context-for-web">In-Context for Web</h3>
<p>The third approach to website localization on Crowdin is to translate the text directly within the website or web application using Crowdin In-Context.</p>
<p>The Crowdin team <a target="_blank" href="https://store.crowdin.com/in-context">mentions</a> that:</p>
<blockquote>
<p>Crowdin In-Context tool allows to translate texts directly within the actual web application. In such a way, the best translation quality is maintained.  </p>
<p>In-Context localization is tied up with the actual project created in Crowdin, under which translatable files are stored.  </p>
<p>This tool makes all the texts in the web app editable. Moreover, the translation process is real-time visible. Even the dynamic part of the application and strings that contain placeholders can be translated this way.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/crowdin-in-context.png" alt="Image" width="600" height="400" loading="lazy">
<em>Crowdin In-Context.</em></p>
<p>You can see it in action <a target="_blank" href="https://demo.crowdin.com/">here</a>, in the official Crowdin demo:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/demo-website.png" alt="Image" width="600" height="400" loading="lazy">
<em>Crowdin In-Context Demo.</em></p>
<p>If you log in to your Crowdin account, you will see this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/demo-website-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Crowdin In-Context Demo.</em></p>
<p>You will be able to click on each string on the website to translate it and when you save the translations, they will be synchronized with your Crowdin project.</p>
<p><strong>💡 Tip:</strong> To learn more about Crowdin In-Context, check out <a target="_blank" href="https://www.youtube.com/watch?v=ktfw7UsW3qw">this tutorial</a> created by the Crowdin team.</p>
<p>These are three different approaches for translating a website on Crowdin. Choosing the right one can make all the difference for your localization team. </p>
<p>Now that you know more about the most important Crowdin features, let's dive into freeCodeCamp's translation effort as a real-world example of a worldwide localization project powered by contributions from an amazing community of volunteers.</p>
<h2 id="heading-freecodecamps-translation-effort">🔹 freeCodeCamp's Translation Effort</h2>
<p>🎉 Awesome. Congratulations on reaching this part of the book. This proves that you are very interested in learning these skills.</p>
<p>Now that you know how to translate your project on Crowdin, let's see how freeCodeCamp's amazing community is translating our content into many world languages.</p>
<p>We'll see this from a potential contributor's point of view, emphasizing how to translate our content on Crowdin.</p>
<h3 id="heading-freecodecamps-contributing-guidelines">freeCodeCamp's Contributing Guidelines</h3>
<p>If you are a contributor who is interested in joining the translation effort, where should you start? </p>
<p>You should start by reading our <a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-translate-files">Contributing Guidelines</a>. These are a set of articles that you can always refer to if you have any questions on how to join or how to start translating and proofreading. </p>
<p>There, you will find:</p>
<ul>
<li>A written overview of Crowdin.</li>
<li>How to get started.</li>
<li>How to select a project and a file.</li>
<li>How to translate the projects that we have on Crowdin. </li>
<li>How to proofread translations.</li>
<li>Translation best practices, and more!</li>
</ul>
<p>In <a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-translate-files">this article</a>, you will find information on how to prepare yourself for contributions. </p>
<p>We recommend:</p>
<ul>
<li>Reading <a target="_blank" href="https://www.freecodecamp.org/news/help-translate-freecodecamp-language/">this announcement</a> written by Quincy Larson, the founder of freeCodeCamp. </li>
<li>Joining the <a target="_blank" href="https://forum.freecodecamp.org/c/contributors/3">community forum</a>. </li>
<li>Joining our <a target="_blank" href="https://discord.gg/PRyKn3Vbay">Discord chat server</a>.</li>
</ul>
<p>Our <a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-translate-files">documentation</a> also mentions that working with a small team can be super helpful to stay motivated:</p>
<blockquote>
<p>Crowdin and other tools make it easy to contribute translations, but it's still a lot of work.  </p>
<p>We want you to enjoy contributing and not burn out or lose interest.  </p>
<p>A small group of 4-5 individuals is a good size to start your niche for your world language. You can then recruit even more friends to join the team.</p>
</blockquote>
<p>Currently, we have over 30 of the most widely spoken languages enabled on our Crowdin project. </p>
<p>Some of them are deployed on the live version of freeCodeCamp. You just need to select them from the dropdown menu to see a new language automatically.</p>
<p>If you do not see your languages listed, the documentation also mentions that:</p>
<blockquote>
<p>If you would like us to include a new world language, we recommend getting your friends excited about this.  </p>
<p>Once you have a small group of people (at least 4-5) interested and committed, we can hop on a call. We will explain all the details and walk you through some of the tools and processes.</p>
</blockquote>
<p>Once you have finished reading this part of the contributing guidelines, you can start to contribute. </p>
<p>First, let's take a look at the different roles that you can have as a freeCodeCamp contributor.</p>
<h3 id="heading-roles-for-the-freecodecamp-localization-process">Roles for the freeCodeCamp Localization Process</h3>
<p>You can contribute to freeCodeCamp's translation effort as a translator or proofreader.</p>
<p>Translators help us to translate curriculum files, documentation, and elements of freeCodeCamp's user interface like buttons and labels.</p>
<p>Proofreaders make sure that the translations are consistent, uniform in tone, and free from common issues such as typos.</p>
<h3 id="heading-language-leads">Language Leads</h3>
<p>Our language leads will be very happy to welcome you to our translation effort:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/author/farhanhasin/">Farhan Hasin Chowdhury</a> (<a target="_blank" href="https://twitter.com/frhnhsin">@frhnhsin</a>) is leading the Bengali community.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/chinese/news/author/miyaliu/">Miya Liu</a> (<a target="_blank" href="https://twitter.com/miyaliu666">@miyaliu666</a>) is leading the Chinese community.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/italian/news/author/dario/">Dario Di Cillo</a> (<a target="_blank" href="https://twitter.com/_dariodc">@_DarioDC</a>) is leading the Italian community.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/japanese/news/author/yoko/">Yoko Matsuda</a> (<a target="_blank" href="https://twitter.com/_sidemt">@_sidemt</a>) is leading the Japanese community.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/korean/news/author/alison-yoon/">Alison Yoon</a> (<a target="_blank" href="https://twitter.com/aliyooncreative">@aliyooncreative</a>) is leading the Korean community.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/portuguese/news/author/daniel/">Daniel Rosa</a> (<a target="_blank" href="https://twitter.com/Daniel__Rosa">@Daniel__Rosa</a>) is leading the Portuguese community. </li>
<li><a target="_blank" href="https://www.freecodecamp.org/portuguese/news/author/nielda/">Nielda Karla Gonçalves de Melo</a> (<a target="_blank" href="https://twitter.com/NieldaKarla">@NieldaKarla</a>) is leading the Portuguese community.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/espanol/news/author/rafael/">Rafael Hernandez</a> (<a target="_blank" href="https://twitter.com/rafaeldavish">@RafaelDavisH</a>) is leading the Spanish community and the localization process.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/espanol/news/author/estefaniacn">Estefania Cassingena Navone</a> (<a target="_blank" href="https://twitter.com/EstefaniaCassN">@EstefaniaCassN</a>) is leading the Spanish YouTube channel.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/author/larymak/">Hillary Nyakundi</a> (<a target="_blank" href="https://twitter.com/larymak1">@larymak1</a>) is leading the Swahili community.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/ukrainian/news/author/anastasiia/">Anastasiia Buievych</a> (<a target="_blank" href="https://twitter.com/anisiangel?s=21&amp;t=3yJxu9lXXPDyxB6WYhRsWg">@anisiangel</a>) is leading the Ukrainian community.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/author/zaira/">Zaira Hira</a> (<a target="_blank" href="https://twitter.com/hira_zaira">@hira_zaira</a>) is leading the Urdu community.</li>
</ul>
<h3 id="heading-freecodecamp-on-crowdin">freeCodeCamp on Crowdin</h3>
<p>Just like I mentioned before, Crowdin is the translation platform we use. It is a localization management platform where individuals, teams, and organizations can localize their resources efficiently.</p>
<p>To access freeCodeCamp's projects on Crowdin:</p>
<p>Go to <a target="_blank" href="https://translate.freecodecamp.org/">translate.freecodecamp.org</a> and you will see the dashboard with the projects that we are currently focused on:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/freecodecamp.png" alt="Image" width="600" height="400" loading="lazy">
<em>freeCodeCamp.org on Crowdin.</em></p>
<p>We have three main projects:</p>
<ul>
<li><a target="_blank" href="https://translate.freecodecamp.org/curriculum">Coding Curriculum</a></li>
<li><a target="_blank" href="https://translate.freecodecamp.org/learn-ui">Learn User Interface</a></li>
<li><a target="_blank" href="https://translate.freecodecamp.org/contributing-docs">Contributing Documentation</a></li>
</ul>
<p>We also have other projects on Crowdin such as News UI, Other Courses, and Subtitles (Chinese).</p>
<h3 id="heading-how-to-choose-a-project-and-a-language">How to Choose a Project and a Language</h3>
<p>Once you're on our translation platform, you will need to choose a project. Let's say that you choose to translate the coding curriculum. </p>
<p>You just need to click on the project:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/freecodecamp-dashboard-crowdin.png" alt="Image" width="600" height="400" loading="lazy">
<em>Click on the project you would like to contribute work.</em></p>
<p>Once you click on the project, you will be taken to a list of available languages for translation.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-in-crowdin.png" alt="Image" width="600" height="400" loading="lazy">
<em>List of available languages for the Coding Curriculum project.</em></p>
<p>For each language, you will see:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/language.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Its name.</li>
<li>Its language code.</li>
<li>A bar with two possible colors. Blue represents the translation progress and green represents the proofreading progress. </li>
<li>You can also see their corresponding percentages to the right.</li>
<li>The last number to the right represents the number of words to translate.</li>
</ul>
<p>You will also find an information panel on the right with:</p>
<ul>
<li>A brief description of the project.</li>
<li>The source language (English).</li>
<li>The number of contributors for that project.</li>
<li>How many source words exist in the project.</li>
<li>When the project was created and when it was last active.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/project-in-crowdin.png" alt="Image" width="600" height="400" loading="lazy">
<em>Check out the information on the right (the gray panel).</em></p>
<p>Now it's time to choose a language. </p>
<p>If you click on the language, you will be taken to the project structure with all the files and folder available for translation. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/spanish-modern-files-crowdin.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Coding Curriculum project structure for Spanish (Modern).</em></p>
<h4 id="heading-how-to-select-a-file">How to Select a File</h4>
<p>To enter the Translation Editor and start translating, just click on the name of a file that needs translation. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/selecting-a-file.png" alt="Image" width="600" height="400" loading="lazy">
<em>Select a file that needs translation. A file needs translation if the bar is not completely blue (translated) or green (proofread).</em></p>
<p>Please note that we usually prioritize translating the first three certifications.</p>
<p>💡 <strong>Tip:</strong> if you are not signed in to your Crowdin account or if you have not created your Crowdin account yet, you will be prompted to do so when you click on a file. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/creating-an-account.png" alt="Image" width="600" height="400" loading="lazy">
<em>This is the screen where you can log in to freeCodeCamp's translation platform. You can also sign up to create your Crowdin account.</em></p>
<p>If you're signing up for a new account, you will need to enter your email, choose your username, and your password. You will also receive an email from Crowdin asking you to click on a link to verify your email address. </p>
<h3 id="heading-the-translation-editor">The Translation Editor</h3>
<p>Congratulations. Now you have your Crowdin account and you are ready to start translating the file you selected. </p>
<p>Let's say that you clicked on the <code>**build-a-drum-machine.md**</code> file and you want to translate it into Spanish (Modern).</p>
<p>You will see a screen similar to this one:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/the-translator-ui.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Translation Editor. This is what you will see when you click on a file and sign in to your Crowdin account.</em></p>
<p>If you would like to learn more about how the editor works, click on "Next" to see more tips on the UI but if you would like to close this short tutorial, just click on the X at the top. </p>
<p>These are the seven steps of the short tutorial provided by Crowdin in case you would like to keep them as a reference:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tutorial-step-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Collaborate on translations in real-time.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tutorial-step-3.png" alt="Image" width="600" height="400" loading="lazy">
<em>Use context to make relevant translations.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tutorial-step-4.png" alt="Image" width="600" height="400" loading="lazy">
<em>Preview files.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tutorial-step-5.png" alt="Image" width="600" height="400" loading="lazy">
<em>Make translations from any device.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tutorial-step-6.png" alt="Image" width="600" height="400" loading="lazy">
<em>Switch to Side-by-side view to review translations faster.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/tutorial-step-7.png" alt="Image" width="600" height="400" loading="lazy">
<em>That's all friends! This is the seventh and last step of the tutorial.</em></p>
<p>After you reach the final step, click "Close" and you will be in the Translation Editor. </p>
<p>This is where the magic happens. You can start translating, save your translations, use suggested translations and adapt them, and even upvote or downvote proposed translations. </p>
<p>To translate a string, just click on it or save your current translation to go to the next string.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translations-editor.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Translations Editor.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/translations-editor-copy.png" alt="Image" width="600" height="400" loading="lazy">
<em>This is where you can write and save your translation.</em></p>
<p>💡 <strong>Tip:</strong> You can also write comments and mark them as issues to notify freeCodeCamp's staff and other contributors.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/comments-sample.png" alt="Image" width="600" height="400" loading="lazy">
<em>You can write comments for individual strings and mark them as issues.</em></p>
<h3 id="heading-how-to-translate-the-learn-interface">How to Translate the Learn Interface</h3>
<p>We also have specific guidelines for translating the Learn interface.</p>
<p>Our <a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-translate-files?id=translate-the-learn-interface">documentation</a> mentions that:</p>
<blockquote>
<p>Our <code>/learn</code> interface relies on JSON files loaded into an i18n plugin to generate translated text. This translation effort is split across both Crowdin and GitHub.</p>
</blockquote>
<p>We translate the <code>intro.json</code> and <code>translations.json</code> files on Crowdin. If you are planning to translate strings from these files, please know that the Context information provided in Crowdin can be very helpful.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-68.png" alt="Image" width="600" height="400" loading="lazy">
<em>Example of the context information provided by Crowdin (<a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-translate-files?id=on-crowdin">source</a>).</em></p>
<p>There are certain files that we cannot upload to Crowdin, such as <code>links.json</code>, <code>meta-tags.json</code>, <code>motivation.json</code>, and <code>trending.json</code>. These files are usually maintained by language leads but if you would like to help with these, please refer to <a target="_blank" href="https://contribute.freecodecamp.org/#/language-lead-handbook">this article</a>.</p>
<h3 id="heading-how-to-translate-the-documentation">How to Translate the Documentation</h3>
<p>Documentation is another essential resource for freeCodeCamp's mission because we can share important information, steps, and guidelines with potential contributors. </p>
<p>We do have certain guidelines for translating our documentation. You can <a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-translate-files?id=translate-documentation">find them</a> in this article. It covers how to translate internal links in the translated documentation.</p>
<h3 id="heading-best-practices">Best Practices</h3>
<p>For any project, our goal should always be to follow the best practices, right? These are some of the <a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-translate-files?id=translation-best-practices">best practices</a> that you should follow to translate freeCodeCamp's resources:</p>
<ul>
<li>Do not translate the content within <code>&lt;code&gt;</code> tags. These tags indicate text that is found in code and should be left in English.</li>
<li>Do not add additional content. If you feel a challenge requires changes in the text content or additional information, you should propose the changes through a GitHub issue or a pull request that modifies the English file.</li>
<li>Do not change the order of content.</li>
</ul>
<h3 id="heading-how-to-become-a-proofreader">How to Become a Proofreader</h3>
<p>If you join freeCodeCamp's localization effort, you can also <a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-proofread-files?id=becoming-a-proofreader">become a proofreader</a>.</p>
<p>We will typically grant you proofreading access if you have been contributing to freeCodeCamp for a while.</p>
<p>If you would like to apply to become a proofreader, please reach out to us in our <a target="_blank" href="https://discord.gg/PRyKn3Vbay">contributors chat room</a>.</p>
<p>💡 <strong>Tip:</strong> Proofreaders can approve their own translations. However, we advise you to allow another proofreader to review your proposed translations to make sure that there are no errors or typos.</p>
<h3 id="heading-how-to-proofread-the-translations">How to Proofread the Translations</h3>
<p>When you become a proofreader, you will have special permissions in the Translations Editor. You will be able to see the current translations, edit them, and approve them.</p>
<p>You should consider the community scores determined by the upvotes and the downvotes when deciding which translations to approve.</p>
<p>When you approve a string, the automated process that we configured with the <a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-proofread-files?id=proofread-translations">GitHub integration</a> on Crowdin will add it to our live platform:</p>
<blockquote>
<p>Approving a string in the proofreading view will mark it as complete and it will be downloaded in our next pull from Crowdin to GitHub.</p>
</blockquote>
<h3 id="heading-discord-chat-server-for-translators">Discord Chat Server for Translators</h3>
<p>If you have any questions or you would like to join our translation effort, you are welcome to join our <a target="_blank" href="https://contribute.freecodecamp.org/#/how-to-translate-files?id=prepare-yourself-for-contributions">Discord chat server</a> for translators.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/discord-chat-room.png" alt="Image" width="600" height="400" loading="lazy">
<em>Our Discord chat server.</em></p>
<p>Once you create your account and join the server, you will see a welcome message with the <code>**#start-here**</code> localization channel.</p>
<p>Excellent work. Now you are ready to start translating and join freeCodeCamp's localization effort.</p>
<h2 id="heading-summary">Summary</h2>
<p>Congratulations! We covered many topics related to localization and now you know more about how to localize your resources and platforms to reach users worldwide.</p>
<p>These are some key takeaways:</p>
<ul>
<li>In a globalized world where information is available with just a few clicks, localizing products, services, and platforms is essential if your goal is to reach users worldwide. Adapting them to different cultures will open doors for your team, your organization, and users around the world.</li>
<li>Crowdin is a powerful localization management platform focused on giving you and your team the tools you need to localize products and platforms that are constantly evolving. </li>
<li>freeCodeCamp's localization effort is a real-world example of a global community brought together by a common goal: providing free access to education around the world without language barriers. You can join too.</li>
</ul>
<p>I hope you liked this book on localization fundamentals. You are ready to start localizing your platform. Now is the right time to start. Localization can be exactly what you need to reach a global community of users.</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[ World Translation Month – Help Us Translate freeCodeCamp into Your Native Language ]]>
                </title>
                <description>
                    <![CDATA[ We believe that everyone deserves to have free programming education in their own native language. So we are expanding International Translation Day (September 30th) into a full month-long effort. Over the past few years, hundreds of volunteer contri... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/world-translation-month-event/</link>
                <guid isPermaLink="false">66bae5f92c1f85b4545c8bc7</guid>
                
                    <category>
                        <![CDATA[ community ]]>
                    </category>
                
                    <category>
                        <![CDATA[ language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ translation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rafael D. Hernandez ]]>
                </dc:creator>
                <pubDate>Tue, 07 Sep 2021 17:37:37 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/09/WTM_BG-yt-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We believe that everyone deserves to have free programming education in their own native language. So we are expanding International Translation Day (September 30th) into a full month-long effort.</p>
<p>Over the past few years, hundreds of volunteer contributors have worked to translate freeCodeCamp into major world languages. In February, <a target="_blank" href="https://www.freecodecamp.org/news/world-language-translation-effort/">Quincy announced</a> that freeCodeCamp had successfully launched both Spanish and Chinese editions. And Portuguese and Italian soon followed.</p>
<p>During World Translation Month, we will celebrate what we have achieved collectively – and what we will accomplish for everybody around the world<em>.</em> Watch this one-minute announcement video:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/i2XjBz_U28E" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p><em>If you are interested in participating in translating freeCodeCamp into any world language, you can <a target="_blank" href="https://chat.freecodecamp.org/channel/contributors">visit our contributors channel and learn more</a>.</em></p>
<h2 id="heading-latest-translation-accomplishments">Latest Translation Accomplishments</h2>
<p>Today, we have successfully launched the Italian and Brazilian Portuguese curriculum. We are eager to see what is to come for these active communities.</p>
<p>And the Ukrainian community has really taken off in their translation efforts. They have eight certifications fully translated and two close to 100%, almost all completed within a month. It is remarkable to witness the dedication and perseverance every language community has shown.</p>
<p>We have multiple other communities and individuals who are translating the curriculum to their native languages. You can take a look at the current statuses of all the active translations and their progress here.</p>
<h2 id="heading-current-translation-progress">Current Translation Progress</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>World Language</td><td>Curriculum %</td><td>Docs %</td></tr>
</thead>
<tbody>
<tr>
<td>Afrikaans</td><td>1%</td><td>59%</td></tr>
<tr>
<td>Arabic</td><td>11%</td><td>29%</td></tr>
<tr>
<td>Azerbaijani</td><td>2%</td><td>1%</td></tr>
<tr>
<td>Bengali</td><td>27%</td><td>1%</td></tr>
<tr>
<td>Catalan</td><td>3%</td><td>1%</td></tr>
<tr>
<td>Chinese</td><td>65%</td><td>56%</td></tr>
<tr>
<td>Czech</td><td>1%</td><td>17%</td></tr>
<tr>
<td>Danish</td><td>1%</td><td>18%</td></tr>
<tr>
<td>Dutch</td><td>4%</td><td>23%</td></tr>
<tr>
<td>Finissh</td><td>1%</td><td>17%</td></tr>
<tr>
<td>French</td><td>20%</td><td>45%</td></tr>
<tr>
<td>German</td><td>31%</td><td>98%</td></tr>
<tr>
<td>Greek</td><td>1%</td><td>1%</td></tr>
<tr>
<td>Hebrew</td><td>1%</td><td>1%</td></tr>
<tr>
<td>Hindi</td><td>1%</td><td>11%</td></tr>
<tr>
<td>Hungarian</td><td>1%</td><td>1%</td></tr>
<tr>
<td>Indonesian</td><td>7%</td><td>30%</td></tr>
<tr>
<td>Italian</td><td>80%</td><td>100%</td></tr>
<tr>
<td>Japanese</td><td>79%</td><td>99%</td></tr>
<tr>
<td>Korean</td><td>1%</td><td>1%</td></tr>
<tr>
<td>Norwegian</td><td>1%</td><td>17%</td></tr>
<tr>
<td>Persian</td><td>12%</td><td>5%</td></tr>
<tr>
<td>Polish</td><td>1%</td><td>19%</td></tr>
<tr>
<td>Portuguese-BR</td><td>79%</td><td>100%</td></tr>
<tr>
<td>Romainian</td><td>48%</td><td>31%</td></tr>
<tr>
<td>Russian</td><td>15%</td><td>95%</td></tr>
<tr>
<td>Spanish</td><td>44%</td><td>84%</td></tr>
<tr>
<td>Swahili</td><td>1%</td><td>1%</td></tr>
<tr>
<td>Swedish</td><td>1%</td><td>19%</td></tr>
<tr>
<td>Tamil</td><td>!%</td><td>34%</td></tr>
<tr>
<td>Telugu</td><td>1%</td><td>4%</td></tr>
<tr>
<td>Turkish</td><td>3%</td><td>4%</td></tr>
<tr>
<td>Ukrainian</td><td>77%</td><td>74%</td></tr>
<tr>
<td>Vietnamese</td><td>1%</td><td>4%</td></tr>
</tbody>
</table>
</div><p>Currently, volunteers around the world are in the process of translating freeCodeCamp's curriculum into 28 world languages.</p>
<p>And many of these world languages are showing significant progress, especially the French, Romanian, Japanese, and German-speaking communities. Keep up the great work!</p>
<h2 id="heading-what-is-the-goal-of-the-world-translation-month-event">What is the Goal of the World Translation Month Event?</h2>
<p>freeCodeCamp's goal is to give every individual the tools to translate the curriculum into their native language. Also, we will dedicate all our efforts and time to this event to reach significant progress for every world language. </p>
<p>We have witnessed what a handful of contributors can do together with a singular purpose. And we hope that World Translation Month will become a yearly event when all world languages can celebrate having access to free programming education translated by their community for their community.</p>
<h2 id="heading-our-reward-system">Our Reward System 🥇</h2>
<p>freeCodeCamp wants to make this a memorable event for every contributor who participates.</p>
<p>During the WTM event, we will give all participants a World Translation Month Badge. Also, at the end of the event, we will present special badges to the Top Translators and Top Proofreaders who have made the most contributions. </p>
<p>You can display these badges on your freeCodeCamp curriculum and forum profiles if you want to do so.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/WTM_Badges.png" alt="Image" width="600" height="400" loading="lazy">
<em>2021 World Translation Month badges</em></p>
<h2 id="heading-the-freecodecamp-localization-roadmap">The freeCodeCamp Localization Roadmap</h2>
<p>The freeCodeCamp curriculum is already available in Chinese and Spanish. And beyond this, these communities have many other resources at their disposal, thanks to the efforts of hundreds of contributors.</p>
<p>For example, the Chinese-speaking community has translated nearly all the certifications into Chinese. They also have close to 600 articles translated, there are 10 of freeCodeCamp's video tutorials with subtitles on <a target="_blank" href="https://space.bilibili.com/335505768/video">Bilibili</a>, and they've organized hundreds of meetups, hackathons, conferences, live coding events, and coding workshops in universities and high schools.</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2021/09/image-1.png" alt="Image" width="913" height="557" loading="lazy">
<em>freeCodeCamp bilibili 频道</em></p>
<p>The Spanish-speaking community has the first two certifications available in Spanish, and they've translated around 250 articles. Community members have also written about 20 original articles, created 3 original full-length video tutorials, and <a target="_blank" href="https://www.freecodecamp.org/news/p/fc63f178-c627-414a-8ae6-877eb3458261/@EstefaniaCassN">Estefania</a> has created 24 short videos for the Spanish YouTube channel. </p>
<p>The response from the Spanish-speaking community has been remarkable on <a target="_blank" href="https://www.youtube.com/c/freeCodeCampEspa%C3%B1ol/videos">YouTube</a> and <a target="_blank" href="https://twitter.com/freecodecampES">Twitter</a>. Thanks to the fantastic job Estefania has been doing by engaging with the community with posts and short videos on YouTube, the channel now has over 12,000 followers. And freeCodeCamp's Twitter account en Español has reached 7,300 followers while continuing to grow.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screen-Shot-2021-09-06-at-2.23.41-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>YouTube Channel freeCodeCamp en Español</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screen-Shot-2021-09-06-at-2.29.17-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Twitter freeCodeCamp en Español</em></p>
<h2 id="heading-the-translation-blueprint">The Translation Blueprint</h2>
<p>As Quincy mentioned in the <a target="_blank" href="https://www.freecodecamp.org/news/world-language-translation-effort/">World Language Translation Effort</a> article, freeCodeCamp's "<em>...long-term goal is for each of these language communities to come into its own.</em>" </p>
<p>And we'll take the same steps as we took with the thriving Spanish-speaking and Chinese-speaking communities with other language communities around the world. </p>
<p>The plan is to launch a localized publication, YouTube Channel, sub-forum, and Twitter account for these language communities, each with their own contributors and staff.</p>
<h2 id="heading-thank-you-to-all-the-translators-contributors-and-proofreaders">Thank You to All the Translators, Contributors, and Proofreaders!</h2>
<p>I want to finish up by thanking the proofreaders and translators who have dedicated hours upon hours to translating and proofreading. </p>
<h3 id="heading-active-translators">Active Translators</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>World Language</td><td>Translated Strings</td></tr>
</thead>
<tbody>
<tr>
<td>Aziz Meknassi</td><td>Arabic</td><td>4</td></tr>
<tr>
<td>Damani Salah Eddin</td><td>Arabic</td><td>18</td></tr>
<tr>
<td>elmadhdi1962</td><td>Arabic</td><td>4</td></tr>
<tr>
<td>fatima</td><td>Arabic</td><td>2</td></tr>
<tr>
<td>Gehad Salem</td><td>Arabic</td><td>11</td></tr>
<tr>
<td>Khalid Benjelloun</td><td>Arabic</td><td>3</td></tr>
<tr>
<td>Rundi Wadi</td><td>Arabic</td><td>38</td></tr>
<tr>
<td>Modasser Billah</td><td>Bengali</td><td>5</td></tr>
<tr>
<td>nr072</td><td>Bengali</td><td>77</td></tr>
<tr>
<td>Pabitra Jana</td><td>Bengali</td><td>1</td></tr>
<tr>
<td>Raihan Mahmud</td><td>Bengali</td><td>70</td></tr>
<tr>
<td>Salman Shuvo</td><td>Bengali</td><td>47</td></tr>
<tr>
<td>Chengjun.L</td><td>Chinese</td><td>49</td></tr>
<tr>
<td>cocoder</td><td>Chinese</td><td>26</td></tr>
<tr>
<td>Simon Yang</td><td>Chinese</td><td>1</td></tr>
<tr>
<td>xhksun</td><td>Chinese</td><td>166</td></tr>
<tr>
<td>ztftrue</td><td>Chinese</td><td>1</td></tr>
<tr>
<td>Hana Klingová</td><td>Czech</td><td>28</td></tr>
<tr>
<td>johmar</td><td>Dutch</td><td>339</td></tr>
<tr>
<td>cbnrd</td><td>French</td><td>43</td></tr>
<tr>
<td>David Heusler</td><td>French</td><td>109</td></tr>
<tr>
<td>Flo</td><td>French</td><td>1</td></tr>
<tr>
<td>Julien Li</td><td>French</td><td>449</td></tr>
<tr>
<td>Mariefay</td><td>French</td><td>14</td></tr>
<tr>
<td>MNE</td><td>French</td><td>29</td></tr>
<tr>
<td>Rémy Beumier</td><td>French</td><td>1</td></tr>
<tr>
<td>srasay2</td><td>French</td><td>1</td></tr>
<tr>
<td>Andrew Russell</td><td>German</td><td>28</td></tr>
<tr>
<td>AW</td><td>German</td><td>84</td></tr>
<tr>
<td>Julia Wegmayr</td><td>German</td><td>248</td></tr>
<tr>
<td>Martin Seibert</td><td>German</td><td>1</td></tr>
<tr>
<td>Philipp R. Proksch</td><td>German</td><td>3</td></tr>
<tr>
<td>Philipp S</td><td>German</td><td>312</td></tr>
<tr>
<td>Vairus</td><td>Greek</td><td>10</td></tr>
<tr>
<td>Jod Louis</td><td>Haitian Creole</td><td>1</td></tr>
<tr>
<td>Ami</td><td>Hebrew</td><td>28</td></tr>
<tr>
<td>bachdor24</td><td>Hebrew</td><td>4</td></tr>
<tr>
<td>Curiosity_17</td><td>Hindi</td><td>1</td></tr>
<tr>
<td>Japmohan kumar</td><td>Hindi</td><td>1</td></tr>
<tr>
<td>adh</td><td>Indonesian</td><td>299</td></tr>
<tr>
<td>Arief Darmawan</td><td>Indonesian</td><td>503</td></tr>
<tr>
<td>Danang Aprias Noor Fadilla</td><td>Indonesian</td><td>46</td></tr>
<tr>
<td>Dicky Giancini</td><td>Indonesian</td><td>94</td></tr>
<tr>
<td>Dyah Achwatiningrum</td><td>Indonesian</td><td>368</td></tr>
<tr>
<td>Fahmi Aditia</td><td>Indonesian</td><td>50</td></tr>
<tr>
<td>Hendra Bangun Dwi R</td><td>Indonesian</td><td>8</td></tr>
<tr>
<td>Rifqi</td><td>Indonesian</td><td>1</td></tr>
<tr>
<td>Tjandra Darmo</td><td>Indonesian</td><td>6</td></tr>
<tr>
<td>wardhen</td><td>Indonesian</td><td>115</td></tr>
<tr>
<td>iuri86</td><td>Italian</td><td>10</td></tr>
<tr>
<td>TOMMASO DI COSTANZO</td><td>Italian</td><td>35</td></tr>
<tr>
<td>Emi</td><td>Japanese</td><td>7</td></tr>
<tr>
<td>Julie Park</td><td>Korean</td><td>19</td></tr>
<tr>
<td>San</td><td>Korean</td><td>9</td></tr>
<tr>
<td>Sewook Han</td><td>Korean</td><td>6</td></tr>
<tr>
<td>Zergro</td><td>Norwegian</td><td>3</td></tr>
<tr>
<td>m_golzar</td><td>Persian</td><td>8</td></tr>
<tr>
<td>najme</td><td>Persian</td><td>571</td></tr>
<tr>
<td>Jakub Siwik</td><td>Polish</td><td>27</td></tr>
<tr>
<td>BrainMath</td><td>Portuguese, Brazilian</td><td>10</td></tr>
<tr>
<td>Clovis Goulart</td><td>Portuguese, Brazilian</td><td>340</td></tr>
<tr>
<td>Cássio Barth</td><td>Portuguese, Brazilian</td><td>1</td></tr>
<tr>
<td>fcc_javascript_4linux</td><td>Portuguese, Brazilian</td><td>1</td></tr>
<tr>
<td>Felipe Santos</td><td>Portuguese, Brazilian</td><td>8</td></tr>
<tr>
<td>Isabella Lima</td><td>Portuguese, Brazilian</td><td>3</td></tr>
<tr>
<td>Jualianaluzia</td><td>Portuguese, Brazilian</td><td>36</td></tr>
<tr>
<td>Lais Golin</td><td>Portuguese, Brazilian</td><td>1</td></tr>
<tr>
<td>luforain</td><td>Portuguese, Brazilian</td><td>1</td></tr>
<tr>
<td>Rick</td><td>Portuguese, Brazilian</td><td>2</td></tr>
<tr>
<td>Silvano RM</td><td>Portuguese, Brazilian</td><td>17</td></tr>
<tr>
<td>Clovis Goulart</td><td>Portuguese, Portugal</td><td>37</td></tr>
<tr>
<td>Pedro Goncalves</td><td>Portuguese, Portugal</td><td>65</td></tr>
<tr>
<td>Ghenadie Tofan</td><td>Romanian</td><td>422</td></tr>
<tr>
<td>Alexander Filëv</td><td>Russian</td><td>114</td></tr>
<tr>
<td>Beloze</td><td>Russian</td><td>26</td></tr>
<tr>
<td>Cherepnin</td><td>Russian</td><td>97</td></tr>
<tr>
<td>Corrector</td><td>Russian</td><td>87</td></tr>
<tr>
<td>Farid</td><td>Russian</td><td>2</td></tr>
<tr>
<td>Helge Kim</td><td>Russian</td><td>5</td></tr>
<tr>
<td>Knopentiya</td><td>Russian</td><td>3</td></tr>
<tr>
<td>Pavel Ryazantsev</td><td>Russian</td><td>7</td></tr>
<tr>
<td>Rita Shinger</td><td>Russian</td><td>25</td></tr>
<tr>
<td>vekotov</td><td>Russian</td><td>28</td></tr>
<tr>
<td>Виктор Сударинен</td><td>Russian</td><td>1</td></tr>
<tr>
<td>Тимофей Рассолов</td><td>Russian</td><td>198</td></tr>
<tr>
<td>daca</td><td>Serbian</td><td>6</td></tr>
<tr>
<td>Abel Campos</td><td>Spanish</td><td>13</td></tr>
<tr>
<td>Aldair Avalos</td><td>Spanish</td><td>86</td></tr>
<tr>
<td>Alvaro Agamez</td><td>Spanish</td><td>159</td></tr>
<tr>
<td>Andres</td><td>Spanish</td><td>2</td></tr>
<tr>
<td>Atilio Garcia Pezo</td><td>Spanish</td><td>4</td></tr>
<tr>
<td>Christian Caracach</td><td>Spanish</td><td>13</td></tr>
<tr>
<td>DavitBoo</td><td>Spanish</td><td>1</td></tr>
<tr>
<td>devsiderio</td><td>Spanish</td><td>23</td></tr>
<tr>
<td>Drifting Live</td><td>Spanish</td><td>1</td></tr>
<tr>
<td>Erik</td><td>Spanish</td><td>4</td></tr>
<tr>
<td>Hernan David Cuy Salcedo</td><td>Spanish</td><td>20</td></tr>
<tr>
<td>Javier Coronado Velasquez</td><td>Spanish</td><td>377</td></tr>
<tr>
<td>JorgeT</td><td>Spanish</td><td>2</td></tr>
<tr>
<td>Karlos Arroyo Fernandez</td><td>Spanish</td><td>8</td></tr>
<tr>
<td>Laura</td><td>Spanish</td><td>1</td></tr>
<tr>
<td>Luis Carlos Figueroa Veliz</td><td>Spanish</td><td>10</td></tr>
<tr>
<td>Marco Segura</td><td>Spanish</td><td>5</td></tr>
<tr>
<td>Mariano Esquivel</td><td>Spanish</td><td>20</td></tr>
<tr>
<td>Martin Diaz</td><td>Spanish</td><td>1</td></tr>
<tr>
<td>Maximiliano Romero</td><td>Spanish</td><td>4</td></tr>
<tr>
<td>Nestor Plasencia</td><td>Spanish</td><td>1</td></tr>
<tr>
<td>Renzo-Chong</td><td>Spanish</td><td>2</td></tr>
<tr>
<td>Rumen Zaechki</td><td>Spanish</td><td>6</td></tr>
<tr>
<td>Santiago Correa Alvarez</td><td>Spanish</td><td>18</td></tr>
<tr>
<td>Sergio Fdez</td><td>Spanish</td><td>21</td></tr>
<tr>
<td>Sofiapicco</td><td>Spanish</td><td>2</td></tr>
<tr>
<td>Ulises Lopez</td><td>Spanish</td><td>12</td></tr>
<tr>
<td>Chaandha Raghav</td><td>Tamil</td><td>128</td></tr>
<tr>
<td>Raviraj Subramanian</td><td>Tamil</td><td>7</td></tr>
<tr>
<td>Eren Byxlarge</td><td>Turkish</td><td>8</td></tr>
<tr>
<td>Kubilaycitak</td><td>Turkish</td><td>18</td></tr>
<tr>
<td>Mehmet Emin Eraslan</td><td>Turkish</td><td>2</td></tr>
<tr>
<td>Alina Bovsunivska</td><td>Ukrainian</td><td>217</td></tr>
<tr>
<td>alina_farafonova</td><td>Ukrainian</td><td>15</td></tr>
<tr>
<td>alx<em>man</em></td><td>Ukrainian</td><td>122</td></tr>
<tr>
<td>Anastasia Trius</td><td>Ukrainian</td><td>304</td></tr>
<tr>
<td>Anastasia_k</td><td>Ukrainian</td><td>131</td></tr>
<tr>
<td>andrii.bodnar</td><td>Ukrainian</td><td>2</td></tr>
<tr>
<td>Angelina Yaremchuk</td><td>Ukrainian</td><td>242</td></tr>
<tr>
<td>Anna Verbytska</td><td>Ukrainian</td><td>183</td></tr>
<tr>
<td>anna.linevych</td><td>Ukrainian</td><td>106</td></tr>
<tr>
<td>anya_filipchuk</td><td>Ukrainian</td><td>1</td></tr>
<tr>
<td>coolakova</td><td>Ukrainian</td><td>185</td></tr>
<tr>
<td>Cофія Назарчук</td><td>Ukrainian</td><td>199</td></tr>
<tr>
<td>Daria Deinekina</td><td>Ukrainian</td><td>156</td></tr>
<tr>
<td>Darina Gorichenko</td><td>Ukrainian</td><td>373</td></tr>
<tr>
<td>DarinaMilova</td><td>Ukrainian</td><td>168</td></tr>
<tr>
<td>Daryna</td><td>Ukrainian</td><td>270</td></tr>
<tr>
<td>Diana</td><td>Ukrainian</td><td>169</td></tr>
<tr>
<td>Dmytro Zubenko</td><td>Ukrainian</td><td>534</td></tr>
<tr>
<td>Doctorplague</td><td>Ukrainian</td><td>110</td></tr>
<tr>
<td>Inga Usenko</td><td>Ukrainian</td><td>1</td></tr>
<tr>
<td>Iryna Lobko</td><td>Ukrainian</td><td>212</td></tr>
<tr>
<td>Ivan Adamchuk</td><td>Ukrainian</td><td>366</td></tr>
<tr>
<td>Ivanka_Kvasna</td><td>Ukrainian</td><td>202</td></tr>
<tr>
<td>Julia Serbinenko</td><td>Ukrainian</td><td>283</td></tr>
<tr>
<td>K_Katrina_A</td><td>Ukrainian</td><td>36</td></tr>
<tr>
<td>Kateryna_sk</td><td>Ukrainian</td><td>410</td></tr>
<tr>
<td>Katya Belikova</td><td>Ukrainian</td><td>190</td></tr>
<tr>
<td>Khristina Konvaliuk</td><td>Ukrainian</td><td>91</td></tr>
<tr>
<td>Liubov Kot</td><td>Ukrainian</td><td>9</td></tr>
<tr>
<td>Maria Gaidarzhy</td><td>Ukrainian</td><td>162</td></tr>
<tr>
<td>Mariana Minko</td><td>Ukrainian</td><td>319</td></tr>
<tr>
<td>Maryna Moroz</td><td>Ukrainian</td><td>124</td></tr>
<tr>
<td>minkaffe</td><td>Ukrainian</td><td>262</td></tr>
<tr>
<td>Nata</td><td>Ukrainian</td><td>143</td></tr>
<tr>
<td>Nataliia Mykolyshyn4</td><td>Ukrainian</td><td>314</td></tr>
<tr>
<td>nathuzovata</td><td>Ukrainian</td><td>257</td></tr>
<tr>
<td>ol_kvasna</td><td>Ukrainian</td><td>198</td></tr>
<tr>
<td>Olena Tyshkevych</td><td>Ukrainian</td><td>321</td></tr>
<tr>
<td>olena.karpina</td><td>Ukrainian</td><td>209</td></tr>
<tr>
<td>olia-k</td><td>Ukrainian</td><td>228</td></tr>
<tr>
<td>Pavlo Tiupa</td><td>Ukrainian</td><td>320</td></tr>
<tr>
<td>Roksolana Khanas</td><td>Ukrainian</td><td>313</td></tr>
<tr>
<td>Solomia2108 Kotiai</td><td>Ukrainian</td><td>330</td></tr>
<tr>
<td>Solomiia Stupak</td><td>Ukrainian</td><td>228</td></tr>
<tr>
<td>SOPHIE_20861</td><td>Ukrainian</td><td>17</td></tr>
<tr>
<td>Sophiya</td><td>Ukrainian</td><td>349</td></tr>
<tr>
<td>tetiana_kinashchuk</td><td>Ukrainian</td><td>272</td></tr>
<tr>
<td>vdenyssko</td><td>Ukrainian</td><td>164</td></tr>
<tr>
<td>Viktoriia Farenyk</td><td>Ukrainian</td><td>353</td></tr>
<tr>
<td>Viktoriia_Shoptenko</td><td>Ukrainian</td><td>79</td></tr>
<tr>
<td>vitka-kvitka</td><td>Ukrainian</td><td>199</td></tr>
<tr>
<td>Yaroslav Stryhun</td><td>Ukrainian</td><td>291</td></tr>
<tr>
<td>yulialutsenko</td><td>Ukrainian</td><td>2</td></tr>
<tr>
<td>Zhandm</td><td>Ukrainian</td><td>26</td></tr>
<tr>
<td>Аліна Соловій</td><td>Ukrainian</td><td>334</td></tr>
<tr>
<td>Андрій Андрійович Сукнацький</td><td>Ukrainian</td><td>271</td></tr>
<tr>
<td>Андрій Біленко</td><td>Ukrainian</td><td>275</td></tr>
<tr>
<td>Богдана Вознюк</td><td>Ukrainian</td><td>6</td></tr>
<tr>
<td>Вікторія Мельник</td><td>Ukrainian</td><td>145</td></tr>
<tr>
<td>Дарія Фадєєва</td><td>Ukrainian</td><td>210</td></tr>
<tr>
<td>Кристина Єщенко</td><td>Ukrainian</td><td>59</td></tr>
<tr>
<td>Таня Щадило</td><td>Ukrainian</td><td>339</td></tr>
<tr>
<td>Тимофей Рассолов</td><td>Ukrainian</td><td>1</td></tr>
<tr>
<td>Тоня Маркиш</td><td>Ukrainian</td><td>98</td></tr>
<tr>
<td>Ілона Єщенко</td><td>Ukrainian</td><td>67</td></tr>
<tr>
<td>Anushah Nadir</td><td>Urdu</td><td>17</td></tr>
<tr>
<td>Muhammad Ali</td><td>Urdu</td><td>54</td></tr>
<tr>
<td>Sharaf A</td><td>Urdu</td><td>12</td></tr>
</tbody>
</table>
</div><h3 id="heading-active-proofreaders">Active Proofreaders</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>World Language</td><td>Translated Strings</td><td>Approved Strings</td></tr>
</thead>
<tbody>
<tr>
<td>Miya Liu</td><td>Chinese</td><td>18</td><td>2547</td></tr>
<tr>
<td>Alan Luo</td><td>Chinese</td><td>567</td><td>480</td></tr>
<tr>
<td>Christophe Thomas</td><td>French</td><td>537</td><td>372</td></tr>
<tr>
<td>Gaelle Tjat</td><td>French</td><td>572</td><td>1492</td></tr>
<tr>
<td>Stephan Düsterhöft</td><td>German</td><td>1070</td><td>1144</td></tr>
<tr>
<td>Michaelsndr</td><td>German</td><td>586</td><td>809</td></tr>
<tr>
<td>Dicky Giancini</td><td>Indonesian</td><td>83</td><td>492</td></tr>
<tr>
<td>Andrea Ros</td><td>Italian</td><td>875</td><td>52</td></tr>
<tr>
<td>Ilenia Magoni</td><td>Italian</td><td>405</td><td>18</td></tr>
<tr>
<td>alevanni19</td><td>Italian</td><td>11</td><td></td></tr>
<tr>
<td>sidemt</td><td>Japanese</td><td>162</td><td>175</td></tr>
<tr>
<td>Daniel Rosa</td><td>Portuguese, Brazil</td><td>1427</td><td>1839</td></tr>
<tr>
<td>Ricardo Passos</td><td>Portuguese, Brazil</td><td>77</td><td></td></tr>
<tr>
<td>fcc_javascript_4linux</td><td>Portuguese, Brazil</td><td>1</td><td></td></tr>
<tr>
<td>Sam_3877</td><td>Romanian</td><td>1696</td><td></td></tr>
<tr>
<td>Juan Carrillo</td><td>Spanish</td><td>241</td><td>1951</td></tr>
<tr>
<td>choidavid4</td><td>Spanish</td><td>357</td><td>349</td></tr>
<tr>
<td>Bohdana Vozniuk</td><td>Ukrainian</td><td>103</td><td>137</td></tr>
</tbody>
</table>
</div><p>Also, thank you to all the proofreaders and participants in the World Translation Month video:</p>
<ul>
<li>Andrea Ros - Italian Proofreader</li>
<li><a target="_blank" href="https://twitter.com/Daniel__Rosa">Daniel Rosa</a> - Portuguese-BR Proofreader</li>
<li><a target="_blank" href="https://twitter.com/stephandue">Stephan Düsterhöft</a> and Michael Franz - German Proofreaders</li>
<li><a target="_blank" href="https://twitter.com/sideyoks">Sidemt (Yoko)</a> - Japanese Proofreader</li>
<li>Adina Solomon - Romanian Proofreader</li>
<li>Khalid - Arabic Proofreader</li>
<li><a target="_blank" href="https://twitter.com/juancarrillofl">Juan Carrillo</a> - Spanish Proofreader</li>
<li><a target="_blank" href="https://github.com/S1ngS1ng">S1ng S1ng</a>，<a target="_blank" href="https://github.com/iLtc">Alan Luo</a>，<a target="_blank" href="https://github.com/ZhichengChen">Zhicheng Chen</a>，<a target="_blank" href="https://github.com/zhannicholas">Nicholas Zhan</a>，<a target="_blank" href="https://twitter.com/miyaliu666">Miya Liu</a> - Translators/Proofreaders</li>
</ul>
<p><em>If you are interested in participating in translating freeCodeCamp's curriculum into any world language, you can <a target="_blank" href="https://chat.freecodecamp.org/channel/contributors">visit our contributors channel and learn more</a>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Add Localization (l10n) to Your React App with react-i18next ]]>
                </title>
                <description>
                    <![CDATA[ When you add localization to a website you're making it available in multiple languages. This tutorial aims to teach you how to do that to a small react app using react-i18next. The way it works is simple. Instead of putting the text you want to disp... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-localization-to-your-react-app/</link>
                <guid isPermaLink="false">66b0aa4372aed240a79b249d</guid>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tom Mondloch ]]>
                </dc:creator>
                <pubDate>Mon, 29 Mar 2021 22:40:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/605df42a9618b008528a7ca8.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you add localization to a website you're making it available in multiple languages. This tutorial aims to teach you how to do that to a small react app using <code>react-i18next</code>.</p>
<p>The way it works is simple. Instead of putting the text you want to display in the components, it all goes into a JSON file. </p>
<p>You then use the keys from the file in the components to get the text. You can add additional JSON files with the same keys and translated values for each desired language. Whatever language is set will determine which JSON file is used as the text for the app.</p>
<p>Follow the steps below to learn how to add this functionality to a React app.</p>
<h2 id="heading-clone-the-boilerplate">Clone the boilerplate</h2>
<p>If you want to code along, you can <a target="_blank" href="https://github.com/moT01/react-i18next-demo">clone the boilerplate from GitHub</a> or <a target="_blank" href="https://repl.it/github/moT01/react-i18next-demo">open the boilerplate on replit</a>. It's a small React app with two pages and a few components:</p>
<p><img src="https://raw.githubusercontent.com/moT01/moT01/master/i18n-demo.png" alt="the two app pages" width="2255" height="878" loading="lazy"></p>
<p>The goal is to make the app available in English and Spanish. </p>
<p>If you are following along, I recommend taking a quick look at the components and file structure to get familiar with the code. The JS files in the <code>src</code> folder are the important ones you will be changing. The <code>Nav</code>, <code>Home</code>, and <code>Page2</code> components contain all the text that needs to be translated.</p>
<h2 id="heading-add-the-dependencies">Add the dependencies</h2>
<p><code>react-i18next</code> is based on <code>i18next</code>, so you will need both packages as dependencies to translate the app. You can add them with:</p>
<pre><code class="lang-sh">npm install --save react-i18next i18next
</code></pre>
<p>If you are following along, start the app with <code>npm start</code> so you can give the two pages a look.</p>
<h2 id="heading-create-the-file-structure">Create the file structure</h2>
<p>In the <code>src</code> folder, create an <code>i18n</code> folder to hold all the information related to translating the app. Here’s the structure I used that is pretty common:</p>
<pre><code>src
+-- i18n
    +-- locales
    |    +-- en
    |        +-- translations.json
    |    +-- es
    |        +-- translations.json
    +-- config.js
</code></pre><p>The <code>config.js</code> file contains the set up for the <code>i18n</code> instance. The two JSON files will hold the text that goes in the app. <code>en</code> for English and <code>es</code> for Spanish. If you are following along, create the file structure in the boilerplate.</p>
<h2 id="heading-configure-the-i18n-instance">Configure the i18n instance</h2>
<p>The <code>i18n</code> instance will hold all of your translations, the current language, and other info and methods needed. You can configure it in the <code>config.js</code> file like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> i18n <span class="hljs-keyword">from</span> <span class="hljs-string">'i18next'</span>;
<span class="hljs-keyword">import</span> { initReactI18next } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-i18next'</span>;

i18n.use(initReactI18next).init({
  <span class="hljs-attr">fallbackLng</span>: <span class="hljs-string">'en'</span>,
  <span class="hljs-attr">lng</span>: <span class="hljs-string">'en'</span>,
  <span class="hljs-attr">resources</span>: {
    <span class="hljs-attr">en</span>: {
      <span class="hljs-attr">translations</span>: <span class="hljs-built_in">require</span>(<span class="hljs-string">'./locales/en/translations.json'</span>)
    },
    <span class="hljs-attr">es</span>: {
      <span class="hljs-attr">translations</span>: <span class="hljs-built_in">require</span>(<span class="hljs-string">'./locales/es/translations.json'</span>)
    }
  },
  <span class="hljs-attr">ns</span>: [<span class="hljs-string">'translations'</span>],
  <span class="hljs-attr">defaultNS</span>: <span class="hljs-string">'translations'</span>
});

i18n.languages = [<span class="hljs-string">'en'</span>, <span class="hljs-string">'es'</span>];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> i18n;
</code></pre>
<p>At the top, import the necessary dependencies. Next, the <code>use(initReactI18next)</code> will bind <code>react-i18next</code> to the <code>i18n</code> instance. </p>
<p>The first two properties of the <code>init</code> object parameter are a fallback language (<code>fallbackLng</code>) and default language (<code>lng</code>). I set both of these to English (<code>en</code>). </p>
<p>The <code>resources</code> are the JSON files with the translations that you created in the last step. The namespaces (<code>ns</code>) and default namespace (<code>defaultNS</code>) are the keys from the <code>resources</code> object. </p>
<p>You can split your translations into multiple files (namespaces) if you have a large app to simplify the files. In which case, you would add more than just the one file in the <code>resources</code> object and add it to the namespaces array. This app is small, so all the translations can go in one file.</p>
<h2 id="heading-add-the-i18n-instance-to-your-app">Add the i18n instance to your app</h2>
<p>In the <code>index.js</code> file of the boilerplate, which contains the entire app, import the <code>i18n</code> instance you created like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> <span class="hljs-string">'./i18n/config'</span>;
</code></pre>
<p>Next, add an empty object to your two <code>translations.json</code> files so you don’t get any errors. You will fill in the keys later.</p>
<h2 id="heading-translate-a-functional-component-with-the-usetranslation-hook">Translate a functional component with the useTranslation hook</h2>
<p>The <code>Nav.js</code> file is the first component to translate. It’s a functional component, so you can use the <code>useTranslation</code> hook. Import it at the top with <code>import { useTranslation } from 'react-i18next'</code>. </p>
<p>Next, instead of using the implicit return with the component, add curly brackets and the return statement so you can declare a variable. Get the <code>t</code> function from the hook with <code>const { t } = useTranslation();</code>. </p>
<p>All together, it should look like something this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useTranslation } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-i18next'</span>;

<span class="hljs-keyword">const</span> Nav = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { t } = useTranslation();

  <span class="hljs-keyword">return</span> (
    …
  );
}
</code></pre>
<p>The text you want to translate in this component is <code>Home</code> and <code>Page 2</code>. In your English <code>translations.json</code> file, add two properties to the object:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"home"</span>: <span class="hljs-string">"Home"</span>,
  <span class="hljs-attr">"page2"</span>: <span class="hljs-string">"Page 2"</span>
}
</code></pre>
<p>Now, you can pass those keys to the <code>t</code> function to get the text. In the <code>Nav.js</code> component, use the <code>t</code> function to translate <code>Home</code> and <code>Page 2</code> to the English text like this: <code>t('key-from-json-file')</code>. </p>
<p>Here’s how those lines should look:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useTranslation } <span class="hljs-keyword">from</span> ‘react-i18next;

<span class="hljs-keyword">const</span> Nav = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { t } = useTranslation();

  <span class="hljs-keyword">return</span> (
    …

    &lt;Link to=<span class="hljs-string">"/"</span>&gt;{t(<span class="hljs-string">'home'</span>)}&lt;/Link&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/page2"</span>&gt;</span>{t('page2')}<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-preview-in-spanish">Preview in Spanish</h2>
<p>The two nav buttons should be working for English. Add the same two keys to the Spanish JSON file so you can see what it will look like in Spanish. Here’s the translations:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"home"</span>: <span class="hljs-string">"Casa"</span>,
  <span class="hljs-attr">"page2"</span>: <span class="hljs-string">"Página 2"</span>
}
</code></pre>
<p>After that, go to the <code>config.js</code> file, set the <code>lng</code> (default language) to <code>es</code> and reload the page. The nav links should show up in Spanish. When you are done, you can set the default language back to <code>en</code>.</p>
<h2 id="heading-translate-a-class-component-using-withtranslation">Translate a class component using withTranslation</h2>
<p>In the <code>Home.js</code> file, there are three sentences and a button to translate. You can use the <code>t</code> function again for most of them, but this one is a Class component so you need to use the <code>withTranslation</code> higher order component (HOC). Import it at the top with <code>import { withTranslation } from 'react-i18next';</code>. </p>
<p>Then, at the bottom of the file, export it with <code>export default withTranslation()(Home);</code>. Now the <code>t</code> function will be passed into the component’s props for use. </p>
<p>In the render method, get the function with <code>const { t } = this.props</code>. The code should look something like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { withTranslation } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-i18next'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Home</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Component</span> </span>{
  …

  render() {
    <span class="hljs-keyword">const</span> { t } = <span class="hljs-built_in">this</span>.props;

    …
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> withTranslation()(Home);
</code></pre>
<p>The first line to translate, <code>Welcome, {username}</code>, has a variable. You can pass an object to the <code>t</code> function with your variable like this: <code>t('key-from-json-file', { variable: value })</code>. It would look like this in the component:</p>
<pre><code class="lang-js">&lt;p&gt;{t(<span class="hljs-string">'welcome'</span>, { <span class="hljs-attr">username</span>: username })}&lt;/p&gt;
</code></pre>
<p>Add that in place of of the "welcome" line. Then, in the English JSON file, add the new key: <code>"welcome": "Welcome, {{username}}"</code>. The <code>username</code> variable passed to the <code>t</code> function will be placed in the string for you.</p>
<p>Also, add the property to the Spanish file: <code>"welcome": "Bienvenido {{username}}"</code>. Follow the instructions from before if you want to preview it in Spanish.</p>
<h3 id="heading-challenge-yourself">Challenge yourself</h3>
<p>You should be able to translate a few things on your own now. Try to do the next two lines, <code>Change your username:</code> and <code>Submit</code>, like the others.</p>
<p>Here are the properties for the English JSON file:</p>
<pre><code class="lang-json"><span class="hljs-string">"change-username"</span>: <span class="hljs-string">"Change your username:"</span>,
<span class="hljs-string">"submit"</span>: <span class="hljs-string">"Submit"</span>
</code></pre>
<p>And the Spanish translations:</p>
<pre><code class="lang-json"><span class="hljs-string">"change-username"</span>: <span class="hljs-string">"Cambia tu nombre de usuario:"</span>,
<span class="hljs-string">"submit"</span>: <span class="hljs-string">"Enviar"</span>
</code></pre>
<p>Add the above in the two JSON files. Then, translate the <code>label</code> and <code>button</code> text with the <code>t</code> function.</p>
<p>When you are done, the render part of <code>Home.js</code> should look like this:</p>
<pre><code class="lang-js">render() {
  <span class="hljs-keyword">const</span> { username } = <span class="hljs-built_in">this</span>.state;
  <span class="hljs-keyword">const</span> { t } = <span class="hljs-built_in">this</span>.props;

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'body'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{t('welcome', {username: username})}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>{t('change-username')}<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">'text'</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{this.updateUsername.bind(this)}</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{this.setUsername.bind(this)}</span>&gt;</span>
          {t('submit')}
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Click <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">'/page2'</span>&gt;</span>here<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span> to go to page 2, {username}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Change the <code>lng</code> in <code>config.js</code> to <code>en</code> or <code>es</code> and reload the app to test the languages.</p>
<h2 id="heading-translate-basic-nested-elements-with-the-trans-component">Translate basic nested elements with the Trans Component</h2>
<p>Leave the last line on the home page for now and head over to the <code>Page2.js</code> file. The two sentences to translate contain other elements – a <code>strong</code> tag and a <code>Link</code> component. </p>
<p>The general rule is to use the <code>t</code> function from the previous examples when you can, but if you have elements or other components in the text, you will need to use the <code>Trans</code> component. </p>
<p>First, import <code>Trans</code> and <code>withTranslation</code> from <code>react-i18next</code>. Then, export the component using <code>withTranslation</code> again. It should look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Trans, withTranslation } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-i18next'</span>;

<span class="hljs-keyword">const</span> Page2 = <span class="hljs-function">() =&gt;</span> (
  …
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> withTranslation()(Page2);
</code></pre>
<p>If you have <em>basic</em> HTML tags in your text, you can wrap the <code>Trans</code> component around the key from your JSON file, and the tags will be added to the text.</p>
<p>Change the first <code>&lt;p&gt;</code> in the component to this:</p>
<pre><code class="lang-js">&lt;p&gt;<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Trans</span>&gt;</span>this-is-page2<span class="hljs-tag">&lt;/<span class="hljs-name">Trans</span>&gt;</span></span>&lt;/p&gt;
</code></pre>
<p>Then, add the key to the two <code>translation.json</code> files. For English:</p>
<pre><code class="lang-json"><span class="hljs-string">"this-is-page2"</span>: <span class="hljs-string">"This is &lt;strong&gt;page 2&lt;/strong&gt;"</span>
</code></pre>
<p>And the Spanish JSON:</p>
<pre><code class="lang-json"><span class="hljs-string">"this-is-page2"</span>: <span class="hljs-string">"Esta es la &lt;strong&gt;página 2&lt;/strong&gt;"</span>
</code></pre>
<p>The above will translate the first line of <code>Page2.js</code>. Default basic tags you can use with this method are <code>br</code>, <code>strong</code>, <code>i</code>, and <code>p</code>, but that list can be expanded in the config. Set the language in the config to Spanish to make sure it's working.</p>
<h2 id="heading-translate-complex-nested-elements-with-trans">Translate complex nested elements with Trans</h2>
<p>The next line has a nested <code>Link</code> component, so it's a little trickier. You need to use <code>Trans</code> again, but pass the key in its props as <code>i18nKey</code> and keep the <code>Link</code> as its child. The <code>Link</code> doesn’t need to have any text. Here’s how that looks:</p>
<pre><code class="lang-js">&lt;Trans i18nKey=<span class="hljs-string">'go-to-home'</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">'/'</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span></span>
&lt;/Trans&gt;
</code></pre>
<p>Be sure to keep the <code>&lt;p&gt;</code> tags. Add the associated property to the English JSON:</p>
<pre><code class="lang-json"><span class="hljs-string">"go-to-home"</span>: <span class="hljs-string">"Go to &lt;0&gt;the home page&lt;/0&gt;"</span>
</code></pre>
<p>And the Spanish version:</p>
<pre><code class="lang-json"><span class="hljs-string">"go-to-home"</span>: <span class="hljs-string">"Ir a la &lt;0&gt;pagina principal&lt;/0&gt;"</span>
</code></pre>
<p>The <code>&lt;0&gt;</code> tags represent the first child of the <code>Trans</code> component, in this case, <code>Link</code>. If there was another child, it would use <code>&lt;1&gt;</code> tags, and so on.</p>
<p>Change the language in the config if you want to see the two different locales.</p>
<h2 id="heading-translate-complex-nested-elements-with-a-variable">Translate complex nested elements with a variable</h2>
<p>One more line to translate. The last line in the <code>Home.js</code> file contains a nested <code>Link</code> element and a variable. You need to use <code>Trans</code> again so add it to the existing import at the top.</p>
<p>If you need to use a variable with <code>Trans</code>, it can be placed inside the element. So the last line:</p>
<pre><code class="lang-js">&lt;p&gt;Click &lt;Link to=<span class="hljs-string">'/page2'</span>&gt;here&lt;<span class="hljs-regexp">/Link&gt; to go to page 2, {username}&lt;/</span>p&gt;
</code></pre>
<p>Can be translated like this:</p>
<pre><code class="lang-js">&lt;Trans i18nKey=<span class="hljs-string">'go-to-page2'</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">'/page2'</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span></span>
  {{username}}
&lt;/Trans&gt;
</code></pre>
<p>The key is passed to <code>Trans</code> as a prop like before. The nested <code>Link</code> element and the <code>{{ username }}</code> variable need to be somewhere in the component, it doesn't matter where.</p>
<p>Be sure to add the values to the JSON files. For English:</p>
<pre><code class="lang-json"><span class="hljs-string">"go-to-page2"</span>: <span class="hljs-string">"Click &lt;0&gt;here&lt;/0&gt; to go to page 2, {{username}}"</span>
</code></pre>
<p>And Spanish:</p>
<pre><code class="lang-json"><span class="hljs-string">"go-to-page2"</span>: <span class="hljs-string">"Haga clic &lt;0&gt;aquí&lt;/0&gt; para ir a la página 2, {{username}}"</span>
</code></pre>
<h2 id="heading-how-to-change-languages">How to change languages</h2>
<p>All the desired text should be available in both English and Spanish now. The last thing to do is to make it easy to switch between languages. </p>
<p>The <code>Footer.js</code> file has a few buttons and a <code>changeLanguage</code> function. Import the <code>useTranslation</code> hook from <code>react-i18next</code> at the top of the file. At the top of the <code>Footer</code> component, get the <code>i18n</code> instance from it like this: <code>const { i18n } = useTranslation();</code>. </p>
<p>Finally, in the <code>changeLanguage</code> function, use the <code>i18n.changeLanguage()</code> method, passing <code>e.target.value</code> to it, to change the langauge.</p>
<p>All together, it looks like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useTranslation } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-i18next'</span>;

<span class="hljs-keyword">const</span> Footer = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { i18n } = useTranslation();

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">changeLanguage</span>(<span class="hljs-params">e</span>) </span>{
    i18n.changeLanguage(e.target.value);
  }

  <span class="hljs-keyword">return</span>(
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'footer'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{changeLanguage}</span> <span class="hljs-attr">value</span>=<span class="hljs-string">'en'</span>&gt;</span>English<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{changeLanguage}</span> <span class="hljs-attr">value</span>=<span class="hljs-string">'es'</span>&gt;</span>Español<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
</code></pre>
<p>Now, you should be able to switch languages by clicking the buttons on each page.</p>
<h2 id="heading-how-to-add-languages">How to add languages</h2>
<p>From here, the app is ready to be translated into any language quite easily. You just need to add another language to the config, and the JSON file with the translated text to go with it.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Hopefully, this gave you some insight into how to rapidly expand a React application into many world languages. </p>
<p>There are many more features and details with these tools. See the links below for more info. </p>
<h3 id="heading-helpful-links">Helpful Links</h3>
<ul>
<li>View a <a target="_blank" href="https://react-i18next-demo.mot01.repl.co/">demo of the finished project</a></li>
<li>View the <a target="_blank" href="https://github.com/moT01/react-i18next-demo/tree/solution">source code for the finished project</a></li>
<li>The <a target="_blank" href="https://react.i18next.com">react-i18next documentation</a> goes into more detail on some of the translation methods, server side rendering, testing, and additional uses with React.</li>
<li>The <a target="_blank" href="https://www.i18next.com/">i18next documentation</a> has helpful information on additional configuration, the API, translating complex text, and additional features.</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to perform localization in Phoenix applications with Gettext ]]>
                </title>
                <description>
                    <![CDATA[ By Anastasia In my previous tutorial, we discussed how to introduce support for I18n into Rails apps. Today we will continue covering back-end frameworks and talk about localization of Phoenix applications with the help of Gettext. You might not have... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-perform-localization-in-phoenix-applications-with-gettext-c38bb0f01bef/</link>
                <guid isPermaLink="false">66c353e3d58e4fdd567d51c1</guid>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ startup ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                    <category>
                        <![CDATA[ translation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 27 Sep 2018 06:46:25 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*9FLnNCwPgLPZBvQ4pfwnzg.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Anastasia</p>
<p>In my previous tutorial, we discussed <a target="_blank" href="https://blog.lokalise.co/rails-i18n/">how to introduce support for I18n into Rails apps</a>. Today we will continue covering back-end frameworks and talk about <em>localization of Phoenix applications</em> with the help of <em>Gettext</em>.</p>
<p>You might not have heard about <a target="_blank" href="http://phoenixframework.org/">Phoenix</a> before, so let me say a couple of words about it. This is a server-side MVC framework written in <a target="_blank" href="https://elixir-lang.org/">Elixir</a>, which is a functional programming language working on <a target="_blank" href="https://en.wikipedia.org/wiki/BEAM_(Erlang_virtual_machine)">Erlang virtual machine</a>. The framework itself is quite young but still it is very promising thanks to Erlang’s and Elixir’s features. It is very fast, scalable, and concurrency-oriented which is really important for heavily loaded applications.</p>
<p><a target="_blank" href="https://www.gnu.org/software/gettext/manual/gettext.html#Why">Gettext</a>, in turn, is an I18n tool maintained by GNU which may be used for web, desktop applications, and even in operating systems.</p>
<p>Throughout this article we will be localizing a Phoenix demo project and will see Gettext in action. Also, we will discuss how to introduce support for locale switching and persisting user preferences throughout the requests. Before proceeding to the main part of the tutorial, you also might want to learn common recommendations <a target="_blank" href="https://blog.lokalise.co/localization-5-focus-points/">which are listed in our recent article</a>.</p>
<h3 id="heading-hello-gettext">Hello, Gettext!</h3>
<p>Alright, let’s dive straight into the code and observe the localization of Phoenix applications in practice. Create a new project without a default DBMS and change directory into the project:</p>
<pre><code>mix phx.new lokalise_demo --no-ecto cd lokalise_demo
</code></pre><p>It appears that Phoenix has support for Gettext out of the box: you don’t need to install any third-party libraries. Moreover, if you navigate to the <code>demo/lib/demo_web/templates/page/index.html.eex</code> file, you’ll notice the following line of code:</p>
<pre><code>&lt;h2&gt;&lt;%= gettext <span class="hljs-string">"Welcome to %{name}!"</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">"Phoenix"</span> %&gt;&lt;/h2&gt;
</code></pre><p>What is going on here? Well, <code>gettext</code> <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html#gettext/2">is a function</a> that tries to load translation for the string <code>"Welcome to %{name}!"</code>. <code>%{name}</code> here is a <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html#module-interpolation">placeholder that will be replaced</a> with a <code>"Phoenix"</code> string as dictated by the second argument <code>name: "Phoenix"</code> (this argument contains so-called <em>bindings</em>).</p>
<p>By default, Phoenix applications have English as the default locale set, and no other locales are supported. However, you may easily change that by adding a new line to the <code>config/config.exs</code> file:</p>
<pre><code>config :lokalise_demo, LokaliseDemoWeb.Gettext, <span class="hljs-attr">locales</span>: ~w(en ru)
</code></pre><p>Now we are supporting both English and Russian locales.</p>
<p>The next step is to provide translations for the string passed to the <code>gettext</code> function inside the <code>index.html.eex</code> file. The simplest way to do that is by extracting all translation strings into separate files automatically:</p>
<pre><code>mix gettext.extract mix gettext.merge priv/gettext mix gettext.merge priv/gettext --locale ru
</code></pre><p>These commands are going to create three new files inside the <code>priv/gettext</code> folder. Therefore, let’s stop for a second and talk a bit more about these files.</p>
<h3 id="heading-gettext-file-types">Gettext File Types</h3>
<p>The first command above, <code>mix gettext.extract</code>, searches for all Gettext messages that require translation and places them into the <code>priv/gettext/default.pot</code> file. POT means “portable object template”, and such files serve as templates for language-specific translations. Our <code>default.pot</code> has the following contents:</p>
<pre><code>## This file is a PO Template file. ## ## msgid here are often extracted <span class="hljs-keyword">from</span> source code. ## Add <span class="hljs-keyword">new</span> translations manually only <span class="hljs-keyword">if</span> they<span class="hljs-string">'re dynamic ## translations that can'</span>t be statically extracted. ## ## Run mix gettext.extract to bring <span class="hljs-built_in">this</span> file up to ## date. Leave msgstr empty <span class="hljs-keyword">as</span> changing them here <span class="hljs-keyword">as</span> no ## effect: edit them <span class="hljs-keyword">in</span> PO (.po) files instead. msgid <span class="hljs-string">""</span> msgstr <span class="hljs-string">""</span> #, elixir-format #: lib/lokalise_demo_web/templates/page/index.html.eex:<span class="hljs-number">2</span> msgid <span class="hljs-string">"Welcome to %{name}!"</span> msgstr <span class="hljs-string">""</span>
</code></pre><p>The template conveniently shows the lines where the extracted messages are located. <code>msgid</code> is the string to translate (some developers may refer to it as a “key”). <code>msgstr</code> is, of course, the actual translation.</p>
<p>The name of the POT file — <code>default</code> — is also a <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html#module-domains"><em>domain name</em></a> which serves as a namespace. Initially, there is only one namespace, but for larger sites with hundreds of translations it may be a good idea to create multiple domains and, consequently, separate translations into different files.</p>
<p>The <code>mix gettext.merge priv/gettext --locale LOCALE_CODE_HERE</code> command creates <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html#module-translations">translation files</a> for the given language based on the template. These translation files have <code>.po</code> extension (“portable object”) and live inside the <code>priv/gettext/LOCALE_CODE_HERE/LC_MESSAGES</code> folder. Remember that in order to provide translations for the messages, you should edit these PO files, not the templates directly!</p>
<h3 id="heading-gettext-domains">Gettext Domains</h3>
<p>As already mentioned above, Gettext supports multiple domains or namespaces. When you are utilizing the <code>gettext/4</code> function, you always assume a <code>default</code> domain. If you would like to employ a different namespace, use the <code>[dgettext/6](https://hexdocs.pm/gettext/Gettext.html#dngettext/6)</code> <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html#dngettext/6">function</a> instead which accepts the domain, the message, optional bindings, and some other arguments:</p>
<pre><code>&lt;%= dgettext <span class="hljs-string">"custom_domain"</span>, <span class="hljs-string">"message is ${placeholder}"</span>, <span class="hljs-attr">placeholder</span>: <span class="hljs-string">"my binding"</span> %&gt;
</code></pre><p>Now the <code>mix gettext.extract</code> command is going to create a new <code>custom_domain.pot</code> file. Similarly, running <code>mix gettext.merge</code> creates a <code>custom_domain.po</code> file based on the template.</p>
<p>Note once again that for smaller sites, using multiple domains is usually an overkill. Still, their usage for large resources is very much recommended because this way you don’t end up with hundreds of translations in a single file. Another reason is the ability to have the same translation keys under different namespaces.</p>
<h3 id="heading-providing-translations">Providing Translations</h3>
<p>So, having discussed some Gettext internals, we can now translate the <code>Welcome to %{name}!</code> string into Russian (this message is already in English, so of course no translation is needed for this language). Modify the <code>priv/gettext/ru/LC_MESSAGES/default.po</code> file like this:</p>
<pre><code># ... some other stuff goes here ... #, elixir-format #: lib/lokalise_demo_web/templates/page/index.html.eex:<span class="hljs-number">2</span> msgid <span class="hljs-string">"Welcome to %{name}!"</span> msgstr <span class="hljs-string">"Вас приветствует %{name}"</span>
</code></pre><p>This is it! Currently we do not have any mechanism to switch the language, so set Russian as a <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html#module-default-locale">default locale</a>:</p>
<pre><code># config/config.exs config :lokalise_demo, LokaliseDemoWeb.Gettext, <span class="hljs-attr">locales</span>: ~w(en ru), <span class="hljs-attr">default_locale</span>: <span class="hljs-string">"ru"</span> # &lt;== modify <span class="hljs-built_in">this</span> line
</code></pre><p>Now start the server by running:</p>
<pre><code>mix phx.server
</code></pre><p>Open <code>http://localhost:4000</code> page in your browser and make sure that the translated message is shown!</p>
<h3 id="heading-gettext-pluralization">Gettext Pluralization</h3>
<p>Another important feature that I would like to cover is the <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html#module-pluralization">pluralization</a>. Different languages have different pluralization rules, and Gettext supports many of them out of the box. Still, it is our job to provide proper translations for all potential cases.</p>
<p>As a very simple example, let’s say how many apples the user has. Suppose we don’t know the exact amount, which means that the sentence may read as “1 apple” or “X apples”. To support pluralization, we have to stick with the <code>[ngettext/5](https://hexdocs.pm/gettext/Gettext.html#ngettext/5)</code> <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html#ngettext/5">function</a>:</p>
<pre><code>ngettext <span class="hljs-string">"You have 1 apple"</span>, <span class="hljs-string">"You have %{count} apples"</span>, <span class="hljs-number">2</span>
</code></pre><p>This function accepts both singular and plural forms of the sentence, as well as the <code>count</code>. Under the hood, Gettext takes this count and chooses the proper translation based on the pluralization rules.</p>
<p>Next you may update the POT and PO files with the following commands:</p>
<pre><code>mix gettext.extract --merge priv/gettext mix gettext.extract --merge priv/gettext --locale=ru
</code></pre><p>You’ll find a couple of new lines inside the Gettext files:</p>
<pre><code>msgid <span class="hljs-string">"You have 1 apple"</span> msgid_plural <span class="hljs-string">"You have %{count} apples"</span> msgstr[<span class="hljs-number">0</span>] <span class="hljs-string">""</span> msgstr[<span class="hljs-number">1</span>] <span class="hljs-string">""</span>
</code></pre><p><code>msgstr[0]</code> and <code>msgstr[1]</code> contain translations for singular and plural forms respectively. For English we don’t need to do anything else, but the Russian language requires some extra steps:</p>
<pre><code>msgid <span class="hljs-string">"You have one message"</span> msgid_plural <span class="hljs-string">"You have %{count} messages"</span> msgstr[<span class="hljs-number">0</span>] <span class="hljs-string">"У вас одно яблоко"</span> msgstr[<span class="hljs-number">1</span>] <span class="hljs-string">"У вас %{count} яблока"</span> msgstr[<span class="hljs-number">2</span>] <span class="hljs-string">"У вас %{count} яблок"</span>
</code></pre><p>The pluralization rules in this case are a bit more complex, therefore we must provide not two, but three possible options. You may find more information on the topic <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.Plural.html">in the official docs</a>.</p>
<h3 id="heading-choosing-the-apps-locale">Choosing The App’s Locale</h3>
<p>As I already mentioned earlier, currently there is no way to actually switch between locales when browsing the app. This is an important feature, so let’s add it now!</p>
<p>All in all, we have two potential solutions:</p>
<ul>
<li>Utilize a third-party solution, for example the <a target="_blank" href="https://github.com/smeevil/set_locale">set_locale</a> plug (the easy way)</li>
<li>Write everything from scratch (the warrior’s way)</li>
</ul>
<p>If you choose to stick with the third-party plug, things will be very simple indeed. You need to perform <a target="_blank" href="https://github.com/smeevil/set_locale#setup">only three quick steps</a>:</p>
<ol>
<li>Install the package</li>
<li>Add a new plug to the <code>router.ex</code> file</li>
<li>Add a new <code>:locale</code> routing scope</li>
</ol>
<p>After that the <a target="_blank" href="https://github.com/smeevil/set_locale#fallback-chain-and-precedence">locale will be inferred</a> from the URL, cookies, or the <code>accept-language</code> request header. Simple.</p>
<p>However, in this tutorial I propose choosing a more complex way and writing this feature from scratch.</p>
<h3 id="heading-reading-locale-from-the-url">Reading Locale From the URL</h3>
<p>The most common way of specifying the desired locale is via the URL. The language’s code may be a part of the domain name, or a part of the path:</p>
<ul>
<li><code>[http://en.example.com/some/path](http://en.example.com/some/path)</code></li>
<li><code>[http://example.com/en/some/path](http://example.com/en/some/path)</code></li>
<li><code>[http://example.com/some/path?locale=en](http://example.com/some/path?locale=en)</code></li>
</ul>
<p>Let’s stick with the latter option and provide the locale as a GET parameter. To read the locale’s value and do something about it, we need a <a target="_blank" href="https://hexdocs.pm/phoenix/plug.html#module-plugs">custom plug</a>. Create a new <code>lib/lokalise_demo_web/plugs/set_locale_plug.ex</code> file with the following contents:</p>
<pre><code>defmodule LokaliseDemoWeb.Plugs.SetLocale <span class="hljs-keyword">do</span> <span class="hljs-keyword">import</span> Plug.Conn # <span class="hljs-number">1</span> @supported_locales Gettext.known_locales(LokaliseDemoWeb.Gettext) # <span class="hljs-number">2</span> def init(_options), <span class="hljs-attr">do</span>: nil # <span class="hljs-number">3</span> def call(%Plug.Conn{<span class="hljs-attr">params</span>: %{<span class="hljs-string">"locale"</span> =&gt; locale}} = conn, _options) when locale <span class="hljs-keyword">in</span> @supported_locales <span class="hljs-keyword">do</span> # <span class="hljs-number">4</span> end def call(conn, _options), <span class="hljs-attr">do</span>: conn # <span class="hljs-number">5</span> end
</code></pre><p>Let’s discuss this code snippet:</p>
<ol>
<li>On this line we are importing a behavior. It requires us to fulfill a certain contract (see below).</li>
<li>This is the module attribute with a list of supported locales</li>
<li>This is the actual fulfillment of the contract: a callback that gets invoked automatically. It may return options passed to the <code>call/2</code> function, or just <code>nil</code></li>
<li>The <code>call/2</code> is initialized with all the GET parameters of the request. We are only interested in the <code>locale</code> part and fetch it using the pattern matching mechanism. Also on this line we have a guard clause that ensures the chosen language is actually supported</li>
<li>This is the fallback clause that gets invoked when the passed locale is unsupported. In this case we just return the connection without any modifications.</li>
</ol>
<p>The last thing we need to do is flesh out the first clause of the <code>call/2</code> function. It simply has to set the chosen locale as the current one:</p>
<pre><code>def call(%Plug.Conn{<span class="hljs-attr">params</span>: %{<span class="hljs-string">"locale"</span> =&gt; locale}} = conn, _options) when locale <span class="hljs-keyword">in</span> @supported_locales <span class="hljs-keyword">do</span> LokaliseDemoWeb.Gettext |&gt; Gettext.put_locale(locale) conn end
</code></pre><p>Note that the <code>conn</code> must be returned by the <code>call/2</code> function!</p>
<p>The plug is ready, and you may place it inside the <code>:browser</code> pipeline:</p>
<pre><code># lib/router.ex # ... pipeline :browser <span class="hljs-keyword">do</span> plug :accepts, [<span class="hljs-string">"html"</span>] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers plug LokaliseDemoWeb.Plugs.SetLocale end
</code></pre><p>Now reload the server and navigate to <code>http://localhost:4000/?locale=en</code>. The welcoming message should be in English which means that the custom plug is working as expected!</p>
<h3 id="heading-storing-locale-into-a-cookie">Storing Locale Into a Cookie</h3>
<p>Our next task is persisting the chosen locale among requests so that the user does not need to provide it every time. The perfect candidate for such persistence would be cookies: small text files stored on the user’s PC. Phoenix indeed has support for cookies out of the box, so just utilize a <code>[put_resp_cookies/4](https://hexdocs.pm/plug/Plug.Conn.html#put_resp_cookie/4)</code> <a target="_blank" href="https://hexdocs.pm/plug/Plug.Conn.html#put_resp_cookie/4">function</a> inside your plug:</p>
<pre><code>def call(%Plug.Conn{<span class="hljs-attr">params</span>: %{<span class="hljs-string">"locale"</span> =&gt; locale}} = conn, _options) when locale <span class="hljs-keyword">in</span> @supported_locales <span class="hljs-keyword">do</span> LokaliseDemoWeb.Gettext |&gt; Gettext.put_locale(locale) conn |&gt; put_resp_cookie <span class="hljs-string">"locale"</span>, locale, <span class="hljs-attr">max_age</span>: <span class="hljs-number">365</span>*<span class="hljs-number">24</span>*<span class="hljs-number">60</span>*<span class="hljs-number">60</span> end
</code></pre><p>We modify the connection by storing a cookie named <code>"locale"</code>. It has a lifetime of 1 year which effectively means eternity in terms of the web.</p>
<p>The last step here is reading the chosen locale from the cookie. Unfortunately, we cannot use a guard clause for this task anymore, so let’s replace two clauses of the <code>call/2</code> function with only one:</p>
<pre><code>def call(conn, _options) <span class="hljs-keyword">do</span> <span class="hljs-keyword">case</span> fetch_locale_from(conn) <span class="hljs-keyword">do</span> nil -&gt; conn locale -&gt; LokaliseDemoWeb.Gettext |&gt; Gettext.put_locale(locale) conn |&gt; put_resp_cookie <span class="hljs-string">"locale"</span>, locale, <span class="hljs-attr">max_age</span>: <span class="hljs-number">365</span>*<span class="hljs-number">24</span>*<span class="hljs-number">60</span>*<span class="hljs-number">60</span> end end
</code></pre><p>All in all, the logic remains the same: we fetch the locale, check it, and then either do nothing or store it as the current one.</p>
<p>Add two private functions to finalize this feature:</p>
<pre><code>defp fetch_locale_from(conn) <span class="hljs-keyword">do</span> (conn.params[<span class="hljs-string">"locale"</span>] || conn.cookies[<span class="hljs-string">"locale"</span>]) |&gt; check_locale end defp check_locale(locale) when locale <span class="hljs-keyword">in</span> @supported_locales, <span class="hljs-attr">do</span>: locale defp check_locale(_), <span class="hljs-attr">do</span>: nil
</code></pre><p>Here we are reading the locale from the either the GET param or cookie, and then checking if the desired language is supported. Then either return this language’s code, or just <code>nil</code>. Great job!</p>
<p>Another pretty common way of setting the locale is by using the <code>Accept-Language</code> HTTP header. If you would like to implement this mechanism, try utilizing <a target="_blank" href="https://github.com/smeevil/set_locale/blob/fd35624e25d79d61e70742e42ade955e5ff857b8/lib/headers.ex">the code from the set_locale plug</a> that already provides all the necessary RegExs and other fancy stuff.</p>
<h3 id="heading-locale-switcher-control">Locale Switcher Control</h3>
<p>So, the <code>SetLocale</code> plug is ready, but we still have not provided any controls to choose the website’s language. Therefore, let’s render two links at the top of the page. Define a new helper inside the <code>lib/views/layout_view.ex</code> file:</p>
<pre><code>defmodule LokaliseDemoWeb.LayoutView <span class="hljs-keyword">do</span> use LokaliseDemoWeb, :view def new_locale(conn, locale, language_title) <span class="hljs-keyword">do</span> <span class="hljs-string">"&lt;a href=\"#{page_path(conn, :index, locale: locale)}\"&gt;#{language_title}&lt;/a&gt;"</span> |&gt; raw end end
</code></pre><p>Call this helper from the <code>templates/layout/app.html.eex</code> template:</p>
<pre><code>&lt;body&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"header"</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">new_locale</span> @<span class="hljs-attr">conn</span>, <span class="hljs-attr">:en</span>, "<span class="hljs-attr">English</span>" %&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">new_locale</span> @<span class="hljs-attr">conn</span>, <span class="hljs-attr">:ru</span>, "<span class="hljs-attr">Russian</span>" %&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span> <span class="hljs-comment">&lt;!-- other stuff --&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span> &lt;/body&gt;
</code></pre><p>Reload the page and try switching between locales. Everything should be working just fine, which means that the task is completed!</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 with the help of domains. But still you must make sure that all the keys are translated for each and every locale. Luckily, there is a solution to this problem: the Lokalise platform that <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>Create a new project, give it some name, and set English as a base language</li>
<li>Click “Upload Language Files”</li>
<li>Upload PO files for all your languages</li>
<li>Proceed to the project, and edit your translations as needed</li>
<li>You may also contact professional translator to do the job for you</li>
<li>Next simply download your PO files back and replace them inside the <code>priv/gettext</code> folder</li>
<li>Profit!</li>
</ul>
<p>Lokalise has many more features including support for dozens of platforms and formats, and even the possibility to upload screenshots in order to read texts from them. So, stick with Lokalise and make your life easier!</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In today’s tutorial we have seen how to perform localization of Phoenix applications with the help of Gettext. We have discussed what Gettext is and what goodies it has to offer. We have seen how to extract translations, generate templates, and create PO files based on these templates. You have also learned what domains are, and how to introduce support for pluralization. On top of that, we have successfully created our custom plug to fetch and persist the chosen locale based on the user’s preferences. Not bad for one article!</p>
<p>To learn more about Phoenix I18n, I encourage you to check out <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html">the official guide</a> that provides both general explanations, as well as documentation for individual functions. To learn about the Gettext and its features in more detail, refer to the <a target="_blank" href="https://www.gnu.org/software/gettext/manual/gettext.html">GNU’s documentation</a>. And, of course, if you have any questions feel free to post them in the comments!</p>
<p><em>Originally published at <a target="_blank" href="https://blog.lokalise.co/localization-of-phoenix-applications/">blog.lokalise.co</a> on September 27, 2018.</em></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>
