<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Crowdin</title>
    <description>The latest articles on DEV Community by Crowdin (@crowdin).</description>
    <link>https://dev.to/crowdin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1625%2Fe83fa168-c1a4-4ead-87ce-ae6ed648b75f.png</url>
      <title>DEV Community: Crowdin</title>
      <link>https://dev.to/crowdin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/crowdin"/>
    <language>en</language>
    <item>
      <title>Astro Websites Localization and Internalization</title>
      <dc:creator>yanafeshchuk</dc:creator>
      <pubDate>Wed, 12 Jul 2023 09:21:21 +0000</pubDate>
      <link>https://dev.to/crowdin/astro-websites-localization-and-internalization-3cl0</link>
      <guid>https://dev.to/crowdin/astro-websites-localization-and-internalization-3cl0</guid>
      <description>&lt;p&gt;In this article, you will learn the basics of Astro localization, so you can make your website multilingual to reach new markets. We’ll discuss i18n and localization best practices that will ensure that you can adapt an application to meet the cultural, language, and other requirements of a country or region you’re targeting.&lt;/p&gt;

&lt;p&gt;Integrating your localization software with an Astro-based website is an important step to automating the localization process.&lt;/p&gt;

&lt;p&gt;Let’s start with a walk-through on how to set up language sub-paths and connect &lt;a href="https://crowdin.com/"&gt;Crowdin&lt;/a&gt; to your Astro project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Astro localization
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we will use Crowdin to translate an English Astro website into French and Spanish.&lt;/p&gt;

&lt;p&gt;Let’s see how we can initiate our project in Crowdin. Typically, you would follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up for a Crowdin or Crowdin Enterprise account and create a project.&lt;/li&gt;
&lt;li&gt;Add the project name, source language, visibility and target languages. In our case, we’ll have English as the source language and French and Spanish as the target languages.&lt;/li&gt;
&lt;li&gt;Upload the content you want to translate, such as documents, strings, or multimedia files.&lt;/li&gt;
&lt;li&gt;Define your target languages and invite translators to join your project.&lt;/li&gt;
&lt;li&gt;Translators can access and translate the content directly in the Crowdin Editor, using built-in translation tools such as translation memory and glossary management.&lt;/li&gt;
&lt;li&gt;Proofreaders can review the translated content and provide feedback or approve it.&lt;/li&gt;
&lt;li&gt;Once translations are completed, you can download the translated files and use them in your website.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Astro Site Preparation
&lt;/h2&gt;

&lt;p&gt;Astro is a comprehensive web development tool for building fast websites focusing on content.&lt;br&gt;
Let’s initialize a new Astro site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# create a new project with npm&lt;/span&gt;
npm create astro@latest

&lt;span class="c"&gt;# create a new project with pnpm&lt;/span&gt;
pnpm create astro@latest

&lt;span class="c"&gt;# create a new project with yarn&lt;/span&gt;
yarn create astro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will prompt you to enter a project name and select a template. For this tutorial, we’ll use the &lt;code&gt;blog&lt;/code&gt; template and TypeScript as the language.&lt;/p&gt;

&lt;p&gt;Then, organize your translations within the &lt;a href="https://docs.astro.build/en/reference/configuration-reference/#publicdir"&gt;Astro’s publicDir&lt;/a&gt;, in the &lt;code&gt;locales&lt;/code&gt; directory. In the example below, we show a simple use case of implementing language sub-paths in Astro using a JSON files to store translations. We will work with three languages, en, fr, and es. In this case, you might have the following locale structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public
└── locales
    ├── en
    |   └── translation.json
    └── fr
    |   └── translation.json
    └── es
        └── translation.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The translation files will have the following structure:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;public/locales/en/translation.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav.home"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Home"&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav.blog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Blog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav.about"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"About"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;public/locales/fr/translation.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav.home"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Page d'Accueil"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav.blog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Blogue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav.about"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"À propos de"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;public/locales/es/translation.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav.home"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Página Principal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav.blog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Blog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nav.about"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acerca de"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can be either a plain or nested JSON file depending on your needs. The filename should be the same for all languages. The only difference is the language code in the path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crowdin CLI Configuration
&lt;/h2&gt;

&lt;p&gt;In Astro, you can incorporate &lt;a href="https://crowdin.github.io/crowdin-cli/"&gt;Crowdin CLI&lt;/a&gt; in your project to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate the process of updating your source files in your Crowdin project&lt;/li&gt;
&lt;li&gt;Download translations from Crowdin and automatically save them in the correct locations&lt;/li&gt;
&lt;li&gt;Upload all your existing translations to Crowdin in minutes
The easiest way to install the Crowdin CLI is to use npm:
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;p&gt;Visit the &lt;a href="https://crowdin.github.io/crowdin-cli/installation"&gt;official CLI documentation&lt;/a&gt; to explore other installation options.&lt;/p&gt;

&lt;p&gt;To configure Crowdin CLI in your project, run the following command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You’ll be prompted to enter your Crowdin Project ID and your Personal Access Token. It will generate the &lt;code&gt;crowdin.yml&lt;/code&gt; file in the root directory of your project.&lt;/p&gt;

&lt;p&gt;To set up the Crowdin CLI for your Astro project, you need to add the following configuration to the &lt;code&gt;crowdin.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project_id"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project-id"&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;personal-access-token"&lt;/span&gt;

&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_path"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;preserve_hierarchy"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_url"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.crowdin.com"&lt;/span&gt; &lt;span class="c1"&gt;# https://{organization-name}.api.crowdin.com for Crowdin Enterprise&lt;/span&gt;

&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;files"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
  &lt;span class="pi"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/public/locales/en/translation.json"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Source language file pattern&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;translation"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/public/locales/%two_letters_code%/translation.json"&lt;/span&gt;  &lt;span class="c1"&gt;# Translation files pattern&lt;/span&gt;
  &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s recommended to use &lt;a href="https://developer.crowdin.com/configuration-file/#api-credentials-from-environment-variables"&gt;environment variables&lt;/a&gt; for referencing the token in your &lt;code&gt;crowdin.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project_id_env"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_PROJECT_ID"&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token_env"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CROWDIN_PERSONAL_TOKEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  VCS (Git) Integrations
&lt;/h2&gt;

&lt;p&gt;Crowdin provides VCS (version control systems) integrations for different platforms including GitHub, GitLab, Bitbucket, Azure Repos and other platforms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: supports various GitHub workflows, such as pull request and push notifications, branch updates, and automatic commits.&lt;/li&gt;
&lt;li&gt;GitLab: allows you to synchronize your source and translation files with your GitLab repository, as well as manage your project languages and track the translation progress.&lt;/li&gt;
&lt;li&gt;Bitbucket: allow you to automate updating your source and translation files with your Bitbucket repository.&lt;/li&gt;
&lt;li&gt;Azure Repos: allows you to synchronize your source and translation files with your Azure Repos repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use the &lt;a href="https://github.com/marketplace/actions/crowdin-action"&gt;Crowdin GitHub Action&lt;/a&gt; to automatically manage and synchronize localization resources with your Crowdin project using GitHub workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Language Sub-Paths in Astro
&lt;/h2&gt;

&lt;p&gt;Organizing translations for your website or application to language sub-paths in Astro is easy. It simplifies the management process and guarantees a consistent user experience across your application.&lt;/p&gt;

&lt;p&gt;Setting up these sub-paths involves including the “lang” parameter in your Astro routes, making the process straightforward. The sub-paths create distinct paths for each section of your site, each with its translations.&lt;/p&gt;

&lt;p&gt;Let’s connect the previously created JSON files with the Astro project using the &lt;a href="https://github.com/yassinedoghri/astro-i18next"&gt;astro-i18next&lt;/a&gt; package.&lt;/p&gt;

&lt;p&gt;To use the &lt;code&gt;astro-i18next&lt;/code&gt; package, you need to install it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# npm&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;astro-i18next

&lt;span class="c"&gt;# yarn&lt;/span&gt;
yarn add astro-i18next

&lt;span class="c"&gt;# pnpm&lt;/span&gt;
pnpm add astro-i18next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing the package, you can start using it to internationalize your project. To do this, you need to add &lt;code&gt;astro-i18next&lt;/code&gt; to your &lt;code&gt;astro.config.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mdx&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@astrojs/mdx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;sitemap&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@astrojs/sitemap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;astroI18next&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro-i18next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mdx&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;astroI18next&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure &lt;code&gt;astro-i18next&lt;/code&gt; in your &lt;code&gt;astro-i18next.config.mjs&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type {import('astro-i18next').AstroI18nextConfig} */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;defaultLocale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to use the &lt;code&gt;t()&lt;/code&gt; function available in the &lt;code&gt;astro-i18next&lt;/code&gt; package to translate the header link titles, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
import HeaderLink from './HeaderLink.astro';
import { SITE_TITLE } from '../consts';
import i18next, { t } from 'i18next';

const lang = i18next.language;
const base = lang === 'en' ? '' : `/${lang}`;
---

&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;
    {SITE_TITLE}
  &lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;HeaderLink&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;{`${base}/`}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{ t('nav.home') }&lt;span class="nt"&gt;&amp;lt;/HeaderLink&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;HeaderLink&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;{`${base}/blog`}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{ t('nav.blog') }&lt;span class="nt"&gt;&amp;lt;/HeaderLink&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;HeaderLink&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;{`${base}/about`}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{ t('nav.about') }&lt;span class="nt"&gt;&amp;lt;/HeaderLink&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;HeaderLink&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://twitter.com/astrodotbuild"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Twitter&lt;span class="nt"&gt;&amp;lt;/HeaderLink&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;HeaderLink&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://github.com/withastro/astro"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;GitHub&lt;span class="nt"&gt;&amp;lt;/HeaderLink&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we use the &lt;code&gt;i18next.language&lt;/code&gt; variable to get the current language in order to build the base path for header links. The t() function is used to translate the text.&lt;/p&gt;

&lt;p&gt;The package also provides a language switcher component that you can use to switch between languages. To use it, you need to import the &lt;code&gt;LanguageSelector&lt;/code&gt; component from the &lt;code&gt;astro-i18next&lt;/code&gt; package. Let’s add it to the &lt;code&gt;src/components/Footer.astro&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
import { LanguageSelector } from 'astro-i18next/components';
---

&lt;span class="nt"&gt;&amp;lt;footer&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;LanguageSelector&lt;/span&gt; &lt;span class="na"&gt;showFlag=&lt;/span&gt;&lt;span class="s"&gt;{true}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create localized pages using the generate command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx astro-i18next generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're all set! Now we can run the project and see the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’re all set! Now we can run the project and see the results:&lt;br&gt;
Go to the project in a web browser (&lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;) and try to change the language 🚀&lt;/p&gt;

&lt;p&gt;The localized pages will be available at the following URLs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; - English&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://localhost:3000/es"&gt;http://localhost:3000/es&lt;/a&gt; - Spanish&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://localhost:3000/fr"&gt;http://localhost:3000/fr&lt;/a&gt; - French
Check out a minimalistic Astro web application on &lt;a href="https://github.com/yassinedoghri/astro-i18next/tree/beta/examples"&gt;GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connecting Crowdin in an Astro Context
&lt;/h2&gt;

&lt;p&gt;Connecting Crowdin to Astro is easy using APIs provided by Crowdin. To integrate Crowdin with Astro, follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a project in Crowdin and set up your translation workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect your repository to Crowdin to sync your source files and translations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s an example of using Crowdin to manage translations in an Astro website:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In Crowdin, create a new project and upload the source files you need to translate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up the translation workflows, translation memory, and terminology management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect your repository to Crowdin to sync your source files and translations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To switch between languages, use the &lt;code&gt;lang&lt;/code&gt; parameter in your Astro routes. For example, when a user visits &lt;code&gt;/es/blog&lt;/code&gt;, the API will retrieve the Spanish translations. If the user visits &lt;code&gt;/fr/blog&lt;/code&gt;, the API will retrieve the French translations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  i18n Implementation in Astro
&lt;/h2&gt;

&lt;p&gt;Internationalization, or i18n, in Astro, refers to changing a web application to support multiple languages, date and time styles, and number styles. Visit the &lt;a href="https://docs.astro.build/en/recipes/i18n/"&gt;Astro documentation&lt;/a&gt; to learn more about i18n in Astro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations for Crowdin as a Translation Management System using Astro
&lt;/h2&gt;

&lt;p&gt;There are aspects to consider when working with Crowdin as a TMS with the Astro web framework. These factors may need paying attention to before you can get started with your application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Integration: Ensure that Crowdin integrates seamlessly with the Astro framework.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Translation process: Make sure that you have a simple process for handling translation requests, revisions, and approvals.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terminology management: Use Crowdin’s terminology management feature to ensure you use consistent terminology throughout the translation process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Translation memory: Take advantage of Crowdin’s translation memory feature to store and reuse translations for frequently used phrases and sentences.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Project management: Use Crowdin’s project management tools to keep track of the translation process, deadlines, and progress.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User management: Consider setting up user roles and permissions within Crowdin. This allows you to manage access to the translation project and ensure that the right people have access to the right information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performance: Test the performance of the integration between Crowdin and Astro to ensure that it performs well in production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Error handling: Handle errors gracefully and log them meaningfully, so you can debug and resolve issues when they arise.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;By utilizing Astro and Crowdin, you can streamline the localization process, and the result will be a web application accessible to a diverse global audience.&lt;br&gt;
&lt;a href="https://accounts.crowdin.com/register"&gt;Start a free 14-day trial&lt;/a&gt; today to make your company multilingual with Crowdin.&lt;/p&gt;

</description>
      <category>engineeringteams</category>
      <category>framework</category>
    </item>
    <item>
      <title>Internationalization (i18n) in Next.js 13 with React Server Components</title>
      <dc:creator>Jan Amann</dc:creator>
      <pubDate>Wed, 12 Apr 2023 08:32:00 +0000</pubDate>
      <link>https://dev.to/crowdin/internationalization-i18n-in-nextjs-13-with-react-server-components-2jod</link>
      <guid>https://dev.to/crowdin/internationalization-i18n-in-nextjs-13-with-react-server-components-2jod</guid>
      <description>&lt;p&gt;With the introduction of &lt;a href="https://beta.nextjs.org/docs/getting-started" rel="noopener noreferrer"&gt;Next.js 13&lt;/a&gt; and the beta release of the App Router, React Server Components became publicly available. This new paradigm allows components that don’t require React’s interactive features such as &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; to remain server-side only.&lt;/p&gt;

&lt;p&gt;One area that benefits from this new capability is &lt;strong&gt;internationalization&lt;/strong&gt;.&lt;br&gt;
Traditionally, internationalization requires a tradeoff in performance, as loading translations results in larger client-side bundles and using message parsers impacts the client runtime performance of your app.&lt;/p&gt;

&lt;p&gt;The promise of &lt;strong&gt;React Server Components&lt;/strong&gt; is that we can have our cake and eat it too. If internationalization is implemented entirely on the server side, we can achieve new levels of performance for our apps, leaving the client side for interactive features. But how can we work with this paradigm when we need interactively-controlled states that should be reflected in internationalized messages?&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore a multilingual app that displays street photography images from Unsplash. We’ll use &lt;a href="https://next-intl-docs.vercel.app/" rel="noopener noreferrer"&gt;&lt;code&gt;next-intl&lt;/code&gt;&lt;/a&gt; to implement all our internationalization needs in React Server Components and we’ll look at a technique for introducing interactivity with a minimalistic client-side footprint.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://street-photography-viewer.vercel.app" rel="noopener noreferrer"&gt;Interactive demo&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Fetching photos from Unsplash
&lt;/h2&gt;

&lt;p&gt;A key benefit of Server Components is the ability to fetch data directly from inside components via &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;. We can use this to fetch the photos from Unsplash in our page component.&lt;/p&gt;

&lt;p&gt;But first, we need to create our API client based on the official Unsplash SDK.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createApi&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unsplash-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;createApi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;accessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNSPLASH_ACCESS_KEY&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have our Unsplash API client, we can use it in our page component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unsplash-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;UnsplashApiClient&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./UnsplashApiClient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topicSlug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;street-photography&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;topicRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;photosRequest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;UnsplashApiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;topicIdOrSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;topicSlug&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nx"&gt;UnsplashApiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPhotos&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;topicIdOrSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;topicSlug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;perPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoViewer&lt;/span&gt;
      &lt;span class="na"&gt;coverPhoto&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;topicRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cover_photo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photosRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We use &lt;code&gt;Promise.all&lt;/code&gt; to invoke both requests that we need to make in parallel. This way we avoid a request waterfall.&lt;/p&gt;

&lt;p&gt;At this point, our app renders a simple photo grid.&lt;/p&gt;

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

&lt;p&gt;The app currently uses hard-coded English labels and the dates of the photos are displayed as timestamps—not very user-friendly (yet).&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding internationalization with &lt;a href="https://next-intl-docs.vercel.app/" rel="noopener noreferrer"&gt;&lt;code&gt;next-intl&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In addition to English, we’d like our app to be available in Spanish. Support for Server Components is currently in beta for &lt;code&gt;next-intl&lt;/code&gt;, so we can use &lt;a href="https://next-intl-docs.vercel.app/docs/next-13/server-components" rel="noopener noreferrer"&gt;the installation instructions for the latest beta&lt;/a&gt; to set up our app for internationalization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Formatting dates
&lt;/h3&gt;

&lt;p&gt;Aside from adding a second language, we’ve already found that the app doesn’t adapt well to English users because the dates should be formatted. To achieve a good user experience, we’d like to tell the user the relative time when the photo was uploaded (e.g. “8 days ago”).&lt;/p&gt;

&lt;p&gt;Once  &lt;code&gt;next-intl&lt;/code&gt; is set up, we can fix the formatting by using the &lt;code&gt;format.relativeTime&lt;/code&gt; function in the component that renders each photo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useFormatter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-intl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PhotoGridItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFormatter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relativeTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the date when a photo has been updated is easier to read.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Hint:&lt;/strong&gt; In a traditional React app that renders on both the server and client side, it can be quite a challenge to ensure that the displayed relative date is in sync across the server and client. Since these are different environments and may be in different time zones, you need to configure a mechanism to transfer the server time to the client side. By performing the formatting only on the server side, we don’t have to worry about this problem in the first place.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¡Hola! 👋 Translating our app to Spanish
&lt;/h3&gt;

&lt;p&gt;Next, we can replace the static labels in the header with localized messages. These labels are passed as props from the &lt;code&gt;PhotoViewer&lt;/code&gt; component, so this is our chance to introduce dynamic labels via the &lt;code&gt;useTranslations&lt;/code&gt; hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-intl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PhotoViewer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PhotoViewer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Header&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each internationalized label we add, we need to make sure that there is an appropriate entry set up for all languages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// en.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PhotoViewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Street photography&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Street photography captures real-life moments and human interactions in public places. It is a way to tell visual stories and freeze fleeting moments of time, turning the ordinary into the extraordinary.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// es.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PhotoViewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Street photography&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;La fotografía callejera capta momentos de la vida real y interacciones humanas en lugares públicos. Es una forma de contar historias visuales y congelar momentos fugaces del tiempo, convirtiendo lo ordinario en lo extraordinario.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;a href="https://next-intl-docs.vercel.app/docs/usage/typescript" rel="noopener noreferrer"&gt;&lt;code&gt;next-intl&lt;/code&gt; provides a TypeScript integration&lt;/a&gt; that helps you ensure that you’re only referencing valid message keys.&lt;/p&gt;

&lt;p&gt;Once this is done, we can visit the Spanish version of the app at &lt;code&gt;/es&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;So far, so good!&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding interactivity: Dynamic ordering of photos
&lt;/h2&gt;

&lt;p&gt;By default, the Unsplash API returns the most popular photos. We want the user to be able to change the order to show the most recent photos first.&lt;/p&gt;

&lt;p&gt;Here, the question arises whether we should resort to client-side data fetching so that we can implement this feature with &lt;code&gt;useState&lt;/code&gt;. However, that would require us to move all of our components to the client side, resulting in an increased bundle size.&lt;/p&gt;

&lt;p&gt;Do we have an alternative? Yes. And it’s a capability that has been around on the web for ages: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams" rel="noopener noreferrer"&gt;search parameters&lt;/a&gt; (sometimes referred to as &lt;em&gt;query parameters&lt;/em&gt;). What makes search parameters a great option for our use case is that they can be read on the server side.&lt;/p&gt;

&lt;p&gt;So let’s modify our page component to receive &lt;code&gt;searchParams&lt;/code&gt; via props.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;POPULAR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;photosRequest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;UnsplashApiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPhotos&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this change, the user can navigate to &lt;code&gt;/?orderBy=latest&lt;/code&gt; to change the order of the displayed photos.&lt;/p&gt;

&lt;p&gt;To make it easy for the user to change the value of the search parameter, we’d like to render an interactive &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select" rel="noopener noreferrer"&gt;&lt;code&gt;select&lt;/code&gt; element&lt;/a&gt; from within a component.&lt;/p&gt;

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

&lt;p&gt;We can mark the component with &lt;code&gt;'use client';&lt;/code&gt; to attach an event handler and process change events from the &lt;code&gt;select&lt;/code&gt; element. Nevertheless, we would like to keep the internationalization concerns on the server side to reduce the size of the client bundle.&lt;/p&gt;

&lt;p&gt;Let’s have a look at the required markup for our &lt;code&gt;select&lt;/code&gt; element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;select&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;“popular”&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Popular&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"latest"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Latest&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can split this markup into two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Render the &lt;code&gt;select&lt;/code&gt; element with an interactive Client Component.&lt;/li&gt;
&lt;li&gt;Render the internationalized &lt;code&gt;option&lt;/code&gt; elements with a Server Component and pass them as &lt;code&gt;children&lt;/code&gt; to the &lt;code&gt;select&lt;/code&gt; element.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s implement the &lt;code&gt;select&lt;/code&gt; element for the client side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-intl/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;OrderBySelect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The `useRouter` hook from `next-intl` automatically&lt;/span&gt;
    &lt;span class="c1"&gt;// considers a potential locale prefix of the pathname.&lt;/span&gt;
    &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/?orderBy=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let’s use our component in &lt;code&gt;PhotoViewer&lt;/code&gt; and provide the localized &lt;code&gt;option&lt;/code&gt; elements as  &lt;code&gt;children&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-intl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OrderBySelect&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./OrderBySelect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PhotoViewer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PhotoViewer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderBySelect&lt;/span&gt; &lt;span class="na"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"popular"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orderBy.popular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"latest"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orderBy.latest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;OrderBySelect&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this pattern, the markup for the  &lt;code&gt;option&lt;/code&gt; elements is now generated on the server side and passed to the &lt;code&gt;OrderBySelect&lt;/code&gt;, which handles the change event on the client side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Since we have to wait for the updated markup to be generated on the server side when the order is changed, we may want to show the user a loading state. React 18 introduced &lt;a href="https://beta.reactjs.org/reference/react/useTransition" rel="noopener noreferrer"&gt;the &lt;code&gt;useTransition&lt;/code&gt; hook&lt;/a&gt;  which is integrated with Server Components. This allows us to disable the &lt;code&gt;select&lt;/code&gt; element while waiting for a response from the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-intl/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useTransition&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;OrderBySelect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isTransitioning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startTransition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTransition&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;startTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/?orderBy=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isTransitioning&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding more interactivity: Page controls
&lt;/h2&gt;

&lt;p&gt;The same pattern that we’ve explored for changing the order can be applied to page controls by introducing a &lt;code&gt;page&lt;/code&gt; search parameter.&lt;/p&gt;

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

&lt;p&gt;Note that languages have different rules for handling decimal and thousand separators. Furthermore, languages have different forms of pluralization: While English only makes a grammatical distinction between one and zero/many elements, for example Croatian has a separate form for “few” elements.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;next-intl&lt;/code&gt; uses the &lt;a href="https://next-intl-docs.vercel.app/docs/usage/messages#rendering-of-messages" rel="noopener noreferrer"&gt;ICU syntax&lt;/a&gt; which makes it possible to express these language subtleties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// en.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pagination&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page {page, number} of {totalPages, number} ({totalElements, plural, =1 {one result} other {# results}} in total)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time we don’t need to mark a component with &lt;code&gt;'use client';&lt;/code&gt;. Instead, we can implement this with regular anchor tags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ArrowLeftIcon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ArrowRightIcon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@heroicons/react/24/solid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-intl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Pagination&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;pageInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pagination&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalElements&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;pageInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Since we're using `Link` from next-intl, a potential locale&lt;/span&gt;
      &lt;span class="c1"&gt;// prefix of the pathname is automatically considered.&lt;/span&gt;
      &lt;span class="na"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Keep a potentially existing `orderBy` parameter. &lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pageInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ArrowLeftIcon&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;pageInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pageInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ArrowRightIcon&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;h3&gt;
  
  
  Server Components are a great match for internationalization
&lt;/h3&gt;

&lt;p&gt;Internationalization is an important part of the user experience, whether you support multiple languages or you want to get the subtleties of a single language right. A library like &lt;a href="https://next-intl-docs.vercel.app/" rel="noopener noreferrer"&gt;&lt;code&gt;next-intl&lt;/code&gt;&lt;/a&gt; can help with both cases.&lt;/p&gt;

&lt;p&gt;Implementing internationalization in Next.js apps has historically come with a performance tradeoff, but with Server Components this is no longer the case. However, it might take some time to explore and learn patterns that will help you keep your internationalization concerns on the server side.&lt;/p&gt;

&lt;p&gt;In our street photography viewer app, we only needed to move a single component to the client side: &lt;code&gt;OrderBySelect&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Another aspect to consider is that you might want to consider implementing loading states since the network latency introduces a delay before your users see the result of their actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search parameters are a great alternative to &lt;code&gt;useState&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Search parameters are a great way to implement interactive features in Next.js apps, as they help to reduce the bundle size of the client side.&lt;/p&gt;

&lt;p&gt;Apart from performance, there are other &lt;strong&gt;benefits of using search parameters&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;URLs with search parameters can be shared while preserving the application state.&lt;/li&gt;
&lt;li&gt;Bookmarks preserve the state as well.&lt;/li&gt;
&lt;li&gt;You can optionally integrate with the browser history, enabling undoing state changes via the back button.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note however that there are also &lt;strong&gt;tradeoffs to consider&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search parameter values are strings, so you may need to serialize and deserialize data types.&lt;/li&gt;
&lt;li&gt;The URL is part of the user interface, so using many search parameters may affect readability.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;You can have a look at the complete &lt;a href="https://github.com/amannn/street-photography-viewer" rel="noopener noreferrer"&gt;code of the example on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Many thanks to &lt;a href="https://twitter.com/delba_oliveira" rel="noopener noreferrer"&gt;Delba de Oliveira&lt;/a&gt; from Vercel for providing feedback for this article!&lt;/p&gt;

&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/de/@nasa?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;NASA&lt;/a&gt; on &lt;a href="https://unsplash.com/de/fotos/Q1p7bh3SHj8?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Webinar: From multilingual UI to multilingual company</title>
      <dc:creator>Diana Voroniak</dc:creator>
      <pubDate>Thu, 09 Mar 2023 11:52:42 +0000</pubDate>
      <link>https://dev.to/crowdin/webinar-from-multilingual-ui-to-multilingual-company-35ai</link>
      <guid>https://dev.to/crowdin/webinar-from-multilingual-ui-to-multilingual-company-35ai</guid>
      <description>&lt;p&gt;We’ll have a live discussion with Sarah Rosales-Hunt, who’s currently a Head of Product Content and Localization at Humu (ex. Sr. Localization Project Manager, Slack). After working both at the agency and client side, Sarah has vast experience to share. We'll be discussing what are the basic elements for a successful localization initiative, how to wok with content, and how to get ready for expanding to new markets.&lt;/p&gt;

&lt;p&gt;In this free webinar, you’ll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where does localization start?&lt;/li&gt;
&lt;li&gt;What kind of content should be localized first?&lt;/li&gt;
&lt;li&gt;What are the main elements of a successful localization project?&lt;/li&gt;
&lt;li&gt;A one-man band is also a team.&lt;/li&gt;
&lt;li&gt;What are the types of localization teams?&lt;/li&gt;
&lt;li&gt;What to expect from your localization software?&lt;/li&gt;
&lt;li&gt;Automating localization regardless of where your content lives&lt;/li&gt;
&lt;li&gt;Hosted by&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Register now:&lt;/strong&gt; &lt;a href="http://bit.ly/3F5TMsD" rel="noopener noreferrer"&gt;http://bit.ly/3F5TMsD&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About Crowdin&lt;/strong&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://crowdin.com/" rel="noopener noreferrer"&gt;Crowdin&lt;/a&gt; is a cloud-based localization management software. With over 2M registered users and 100K+ localization projects from all over the world. Crowdin helps companies streamline their growth by reaching people who speak different languages. Trusted by Humu, Calendly, Mojang Studios, GitLab, and other companies creating multilingual products.&lt;/p&gt;

</description>
      <category>multilingual</category>
      <category>globalization</category>
      <category>localization</category>
      <category>webinar</category>
    </item>
    <item>
      <title>Java i18n (Internationalization) and Localization Tutorial</title>
      <dc:creator>Julia Herasymchuk</dc:creator>
      <pubDate>Fri, 02 Sep 2022 08:46:04 +0000</pubDate>
      <link>https://dev.to/crowdin/java-i18n-internationalization-and-localization-tutorial-17bi</link>
      <guid>https://dev.to/crowdin/java-i18n-internationalization-and-localization-tutorial-17bi</guid>
      <description>&lt;p&gt;As an aspiring developer, finding a quick and reliable way to offer your app to the world can be challenging. It is the reason why you have to learn Java i18n and l10n (&lt;a href="https://blog.crowdin.com/2022/07/14/internationalization-vs-localization/"&gt;internationalization and localization&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Using Java, you already have the tools to detect the user’s locale and translate your app accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Java Internationalization Is an Important Part of the Development
&lt;/h2&gt;

&lt;p&gt;Internationalization happens at the development stage, and the sooner it’s implemented – the fewer code changes would be required. However, there are some myths about i18n that we’d like to debunk:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Internalization is only about translating the text.&lt;/li&gt;
&lt;li&gt; It is challenging to implement it, and the process is burdensome. &lt;/li&gt;
&lt;li&gt; Only the project architect has to think about it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you might have guessed - these are all false statements. While localization is relatively simple to code even for a junior developer, many bundles and libraries provide ways to translate text from your app and make it culturally correct – this may include date formats, standards of measurement, or even the whole UI design.&lt;/p&gt;

&lt;p&gt;But is it demanding to create a new structure for your application? Nobody likes to go back to their code and rewrite everything repeatedly. An agile approach to software development teaches us to implement everything step-by-step, so the best case scenario is to build your project with internalization in mind.&lt;/p&gt;

&lt;p&gt;Let’s imagine the process like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You create an application. &lt;/li&gt;
&lt;li&gt;  The app reads the user’s locale and formats, dates and numbers.&lt;/li&gt;
&lt;li&gt;  It then takes text data from the pre-defined resource and outputs the correct language to the user of our app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So there must be ways to store all your important messages in one place - you could translate them yourself or automate content updates using a convenient service such as Crowdin.&lt;/p&gt;

&lt;p&gt;With Crowdin, you can localize any git branches you want, or you can use CLI/API integrations. Read &lt;a href="https://blog.crowdin.com/2021/02/11/smart-ways-to-approach-mobile-app-localization/"&gt;more life hacks on app translation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Implement Internalization in Java
&lt;/h2&gt;

&lt;p&gt;Java is a powerful programming language that provides convenient ways to work with multi-lingual resources. The best way to do it is what’s called a ResourceBundle.&lt;/p&gt;

&lt;h3&gt;
  
  
  ResourceBundle Class
&lt;/h3&gt;

&lt;p&gt;ResourceBundle, together with Locale, are the fundamentals of internalization in Java. ResourceBundle class is used to read strings from text files (.properties).&lt;/p&gt;

&lt;p&gt;Imagine you could store all your Strings in one file, and the translators do the rest. That’s the job of the .properties file. Every single .properties file serves strings for each locale that your application supports.&lt;/p&gt;

&lt;p&gt;Properties usually have a standard naming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ResouceBundleName&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;language_code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;country_code&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for example, MessageBundle_uk_UA for new ResouceBundle&lt;/p&gt;

&lt;p&gt;MessageBundle.properties is the source language file&lt;/p&gt;

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

&lt;p&gt;And the strings inside are organized like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;visited&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chooseAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Choose&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeCity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Remove&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changeLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Change&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;localization&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Exit&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editCity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Edit&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should organize all the strings into categories and be clear with their names. When we want to get the string - on the right - in our Java app, we call it using its name - on the left.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Locales List in Java
&lt;/h3&gt;

&lt;p&gt;You may wonder - “I have already created a Resource Bundle, can I assign it to a Locale?” - Yes! That’s where we use the Locale class.&lt;/p&gt;

&lt;p&gt;It provides ways to interact with the user’s system, such as using these methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
-&amp;gt; returns user’s default locale.

Using the system’s default localization on the app’s startup is a good practice. This method provides the user’s default locale, which you can use to load strings from ResourceBundle without the user even thinking about it.



```(String language_tag)```

 -&amp;gt; returns Locale for the language.

As you have already noticed, each Resource Bundle can be created for specific language tags. Sometimes you may need to differentiate between Australian English (en_AU) and American English (en_US) - so you can specify it using this method.



 ```(Locale.getAvailableLocales()```

-&amp;gt; returns an array of all installed locales.

We use this method when we want to know which locales are installed on the user’s JVM. You parse the list - you know what to serve to the user.

### Dates and Numbers

As I mentioned earlier, different countries may have different ways of presenting Dates and Numbers. Let's assume we have 1 million. In English, it should be formatted as 1,000,000; however, in German, it should be 1.000.000.

To format numbers, Locale-aware creates an instance of NumberFormat class:



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;NumberFormat nf_en = NumberFormat.getInstance(Locale.ENGLISH);&lt;/p&gt;

&lt;p&gt;String number_en = nf_en.format(1000000);&lt;/p&gt;

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


-&amp;gt; 1,000,000



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;NumberFormat nf_ge = NumberFormat.getInstance(Locale.GERMAN);&lt;/p&gt;

&lt;p&gt;String number_ge = nf_ge.format(1000000);&lt;/p&gt;

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


-&amp;gt; 1.000.000

Same thing for dates. To format a date in Java, all you need to do is create an instance of DateFormat class:



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;DateFormat df_en = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.ENGLISH);&lt;/p&gt;

&lt;p&gt;String date_en = df_en.format(new Date());&lt;/p&gt;

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


-&amp;gt; Aug 7, 2022



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;DateFormat df_ge = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.GERMAN);&lt;/p&gt;

&lt;p&gt;String date_ge = df_ge.format(new Date());&lt;/p&gt;

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


-&amp;gt; 07.08.2022

The best part is instead of hard-coding the locale, we can use the user’s locale to format everything according to their culture!


## Context

So, you build the app and have all strings in English. Here’s the time you invite translators to your project. A lot of different people will look at the strings you wrote. How to build our resources so they will be translatable?

The concept of context plays a huge role in localization. Without it, some text may be translated poorly, but not because of the translator. Complex resources must have context attached!

If you name a string “string1,” - the translator will have no idea what this string’s for. Here’s a &amp;lt;a href="https://blog.crowdin.com/2019/11/05/no-context-no-quality-give-translators-context-to-get-better-translations/" target="_blank"&amp;gt;little reading material&amp;lt;/a&amp;gt;
 on why it’s crucial.

Long story short: be sure to correctly name all your strings in the .properties files so that people can know the context of it.


## Java i18n Application Example

We have talked about internalization only in theory. Let’s get to the point with our tutorial, where we will use all our new knowledge.

This tutorial will help to create a light Java project and implement internalization. All the project files will be available to fork &amp;lt;a href="https://github.com/danielsutts/i18n-with-Crowdin" target="_blank"&amp;gt;here&amp;lt;/a&amp;gt;.

### App Overview:

Our application is a small city visit tracker. I’ll call it simple - City Visit Tracker. The user enters their name and is presented with a few actions:

1.  Add a new city to the list.   
2.  Remove a city from the list.
3.  Edit an already added city (we all had this problem remembering the dates, didn’t we?).    
4.  Change the localization of the app.
5.  And, finally, exit the app.

I will use &amp;lt;a href="https://crowdin.com/on-demand-demo" target="_blank"&amp;gt;Crowdin&amp;lt;/a&amp;gt; for the translation of my resources. Using Crowdin - later in the tutorial.

First things first, let’s define the Client class.



```javascript

public class Client {
   private Locale userLocale;
   private final String username;
   private Map&amp;lt;City, Date&amp;gt; visitedCities;
   public Client(Locale userLocale, String username) {
       this.userLocale = userLocale;
       this.username = username;
   }
   public Locale getUserLocale() {
       return userLocale;
   }
   public void setUserLocale(Locale userLocale) {
       this.userLocale = userLocale;
   }
   public String getUsername() {
       return username;
   }  
   public void addCity(City city, Date dateVisited) {
       this.visitedCities.put(city, dateVisited);
   }
   public void removeCity(City city, Date dateVisited) {
       this.visitedCities.remove(city, dateVisited);
   }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want our user class to store all the cities visited, the user’s name and, most importantly, the user’s preferred locale. I’ve also added Getters and Setters so we can change the info to our needs.&lt;/p&gt;

&lt;p&gt;Next, let’s define the City class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;City&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="nx"&gt;Locale&lt;/span&gt; &lt;span class="nx"&gt;cityLocale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;cityName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;cityCountry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;City&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Locale&lt;/span&gt; &lt;span class="nx"&gt;cityLocale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;cityName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;cityCountry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cityLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cityLocale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cityName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cityName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cityCountry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cityCountry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we’re allowing the user to put their cities to the list, let’s add all the cityLocale properties to the class, so we could even add the feature to translate city names in the future if we need to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;Let’s create a new folder with the Message Bundle name and add a few .properties files. I want my app to have English, Polish, and Ukrainian. Therefore, I’m adding four files to the folder (the fourth one is the default).&lt;/p&gt;

&lt;p&gt;MessageBundle/&lt;/p&gt;

&lt;p&gt;/MessageBundle.properties&lt;/p&gt;

&lt;p&gt;/MessageBundle_en_US.properties&lt;/p&gt;

&lt;p&gt;/MessageBundle_pl_PL.properties&lt;/p&gt;

&lt;p&gt;/MessageBundle_uk_UA.properties&lt;/p&gt;

&lt;p&gt;For the sake of simplicity, we will not use the default file now, and I will start populating the resources in the en_US bundle.&lt;/p&gt;

&lt;p&gt;So, what do we want in our Resource Bundle?&lt;/p&gt;

&lt;p&gt;We need some menu messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;printAll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Print&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;visited&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chooseAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Choose&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeCity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Remove&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changeLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Change&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;localization&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Exit&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editCity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Edit&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want some basic words that we might use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Yes&lt;/span&gt;

&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt;

&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;City&lt;/span&gt;

&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;

&lt;span class="nx"&gt;And&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="nx"&gt;we&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;going&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;our&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;welcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Welcome&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;City&lt;/span&gt; &lt;span class="nx"&gt;Visit&lt;/span&gt; &lt;span class="nx"&gt;Tracker&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enterYourName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Enter&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCityName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Enter&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCityDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Enter&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="nx"&gt;visited&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Successfully&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cityDeleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Successfully&lt;/span&gt; &lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cityEdited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Successfully&lt;/span&gt; &lt;span class="nx"&gt;edited&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forExample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noChanges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;applied&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chooseLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Choose&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;available&lt;/span&gt; &lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tryAgain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Try&lt;/span&gt; &lt;span class="nx"&gt;again&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noCities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;cities&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="nx"&gt;yet&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chooseDelete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Choose&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chooseEdit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Choose&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newCityName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Enter&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;

&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newCityDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Enter&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="nx"&gt;visited&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Crowdin and Resources
&lt;/h3&gt;

&lt;p&gt;I added all the messages we needed. However, when developing an application, you want it to be Agile. &lt;a href="https://blog.crowdin.com/2020/12/08/development-and-localization-running-in-parallel-tips-for-developers/"&gt;Crowdin’s Github integration&lt;/a&gt; is available. I will go through integration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Go to your  &lt;em&gt;Crowdin account&lt;/em&gt;  and  &lt;a href="https://accounts.crowdin.com/register"&gt;sign in&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Open &lt;em&gt;a new project&lt;/em&gt;.
&lt;/li&gt;
&lt;li&gt; Enter the info about your project.
&lt;/li&gt;
&lt;li&gt; Choose the &lt;strong&gt;Source language&lt;/strong&gt; and &lt;strong&gt;Target languages&lt;/strong&gt; (in our case, the source is English, and the target is Ukrainian and Polish.
&lt;/li&gt;
&lt;li&gt; Select “&lt;strong&gt;Choose integration&lt;/strong&gt;” in your &lt;em&gt;main menu&lt;/em&gt; -&amp;gt; &lt;em&gt;GitHub Personal&lt;/em&gt; -&amp;gt; &lt;em&gt;Authorize it with GitHub&lt;/em&gt;.
&lt;/li&gt;
&lt;li&gt; Choose &lt;em&gt;a repository&lt;/em&gt; you want to work with and &lt;strong&gt;select the branch&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt; You want to create a configuration file in your repository, where you need to specify the &lt;em&gt;/src/resources folder&lt;/em&gt; for source files.
&lt;/li&gt;
&lt;li&gt; You also want to specify the translated files folder. That way, your translators can give out their work piece-by-piece without needing to touch Git. Isn't it cool?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next step is to hire professionals to translate your text. But one more excellent feature of Crowdin allows us to translate our source files manually! Let’s do just that.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Go to &lt;strong&gt;Home&lt;/strong&gt; - &lt;em&gt;choose target language&lt;/em&gt; - press &lt;strong&gt;Translate&lt;/strong&gt;. Now you should see &lt;em&gt;Crowdin’s UI&lt;/em&gt; that allows you to choose the most appropriate translation of the Strings in our Source file.&lt;/li&gt;
&lt;li&gt;  Go through them and choose &lt;strong&gt;Suggestions below&lt;/strong&gt; (by the way, they are pretty good!).
&lt;/li&gt;
&lt;li&gt;  Once you’ve reached the end of both Ukrainian and Polish files, &lt;em&gt;go back&lt;/em&gt;, and press &lt;strong&gt;Proofread&lt;/strong&gt; to approve the translations. (this is usually a task of the localization director, so don’t be afraid to do it now - it’s only for practice :)
&lt;/li&gt;
&lt;li&gt;  In your &lt;em&gt;Home folder&lt;/em&gt;, press “&lt;strong&gt;Build &amp;amp; Download&lt;/strong&gt;” to download the translations you've just created. Now you can copy them into your folder and use them as custom internationalization of your application!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Let’s Go On with Our Application
&lt;/h3&gt;

&lt;p&gt;We want our application to write, edit and remove cities. We also want it to list all the cities we’ve already added.&lt;/p&gt;

&lt;p&gt;With the use of DateFormat, ResourceBundle and Locale Java classes, let’s add these methods to our main class:&lt;/p&gt;

&lt;p&gt;To change the app’s locale, we simply print the user list of locales and try loading it from our ResourceBundle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;changeLocale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.chooseLocale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Arrays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;Scanner&lt;/span&gt; &lt;span class="nx"&gt;scanner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Scanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;newLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resources/MessageBundle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newLocale&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MissingResourceException&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.tryAgain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
       &lt;span class="nx"&gt;changeLocale&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we provide the user with the menu and wait for the input. Here you see only a few methods. Visit the app’s &lt;a href="https://github.com/danielsutts/i18n-with-Crowdin"&gt;GitHub page&lt;/a&gt; for more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;Scanner&lt;/span&gt; &lt;span class="nx"&gt;scanner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Scanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="c1"&gt;// Print out the menu&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.chooseAction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 - &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.printAll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 - &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.addCity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2 - &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.removeCity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3 - &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.editCity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4 - &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.changeLocale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5 - &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu.exit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;
   &lt;span class="c1"&gt;// If Menu Action is "Add City"&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nx"&gt;addCity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Exception&lt;/span&gt; &lt;span class="nx"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.noChanges&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;       &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.tryAgain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
       &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="err"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we define the addCity() method that adds city to our user’s repo. As you can see here, we use all the power of ResourceBundle, DateFormats and Locales to give the user the most authentic and internationalized experience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;addCity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;throws&lt;/span&gt; &lt;span class="nx"&gt;ParseException&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// Take user's input&lt;/span&gt;
   &lt;span class="c1"&gt;// Create a new City instance to fill it in&lt;/span&gt;
   &lt;span class="nx"&gt;Scanner&lt;/span&gt; &lt;span class="nx"&gt;scanner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Scanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nx"&gt;City&lt;/span&gt; &lt;span class="nx"&gt;newCity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;City&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="c1"&gt;// Add the user's locale to the City instance&lt;/span&gt;
   &lt;span class="nx"&gt;newCity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setCityLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getLocale&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
   &lt;span class="c1"&gt;// Print out the city name prompt and set the new name&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.addCity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.addCityName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;newCity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setCityName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextLine&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
   &lt;span class="c1"&gt;// Print out the date prompt and set the new date&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.addCityDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="c1"&gt;// Here, we transform the input data according to the user's locale&lt;/span&gt;
   &lt;span class="c1"&gt;// and save it as usual Date class&lt;/span&gt;
   &lt;span class="nx"&gt;DateFormat&lt;/span&gt; &lt;span class="nx"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DateFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDateInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DateFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getLocale&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.forExample&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="nx"&gt;newDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextLine&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
   &lt;span class="nx"&gt;newCity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setVisitedDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newCity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="c1"&gt;// Tell user we saved a new city!&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.saved&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s define our pinnacle - the primary method of City Visit Tracker!&lt;/p&gt;

&lt;p&gt;We’ll have to define three static variables in the CityApplication.java file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;ResourceBundle&lt;/span&gt; &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nx"&gt;locales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;[]{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en_US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pl_PL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uk_UA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="nx"&gt;CityRepository&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CityRepository&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ResourceBundle helps us switch between different l10n bundles. Locales tell us which languages are supported by our app. Repo is initialized to store the cities.&lt;/p&gt;

&lt;p&gt;From now on, all I need to do on the app’s startup is get the user’s default locale, print some welcome messages and start the menu() method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// Get user's default locale&lt;/span&gt;
   &lt;span class="c1"&gt;// Load ResourceBundle for the locale&lt;/span&gt;
   &lt;span class="nx"&gt;Locale&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="nx"&gt;userResourceBundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resources/MessageBundle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="nx"&gt;Scanner&lt;/span&gt; &lt;span class="nx"&gt;scanner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Scanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.welcome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.enterYourName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userResourceBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message.hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUsername&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we’re done. Now is your turn! This app is a small example of what you can do with Java’s i18n and l10n bundles.&lt;/p&gt;

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

&lt;p&gt;In this tutorial, we discovered what Java offers us in terms of localization and internationalization of our applications. I18n and l10n are extremely important when you want to provide the absolute best experience to the user.&lt;/p&gt;

&lt;p&gt;ResourceBundle class gives us an easy way to work with resources.&lt;/p&gt;

&lt;p&gt;Locale class gives us ways to interact with the user’s locale.&lt;/p&gt;

&lt;p&gt;DateFormat and NumberFormat allow us to format dates and numbers for the user’s locale.&lt;/p&gt;

&lt;p&gt;Don’t forget to make your translations agile and clean. Create a default package and let the translators and proofreaders do the work. We’ve also explored a way to automate the translation process with Crowdin – as you can see, it’s simple and fast.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>java</category>
    </item>
    <item>
      <title>Android App Localization: Key Steps</title>
      <dc:creator>Julia Herasymchuk</dc:creator>
      <pubDate>Thu, 11 Aug 2022 13:33:00 +0000</pubDate>
      <link>https://dev.to/crowdin/android-app-localization-key-steps-2k99</link>
      <guid>https://dev.to/crowdin/android-app-localization-key-steps-2k99</guid>
      <description>&lt;p&gt;You might be a beginner developer making your first Android app or an experienced programmer adding to your dozen or so other Android apps. But the main question is: who are you making this app for? This article will help you learn the key steps for the &lt;a href="https://crowdin.com/"&gt;localization of Android applications&lt;/a&gt; and reaching new markets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reasons to Localize Your Android App
&lt;/h2&gt;

&lt;p&gt;A leading media and information brand for the app industry, Business of Apps, says that Android is the most popular operating system in the world, &lt;a href="https://www.businessofapps.com/data/android-statistics/"&gt;with more than 2.5 billion active users&lt;/a&gt; in more than 190 countries. That is why Android apps are used by many people worldwide, so getting Android localization right has become an essential part of creating mobile apps. Localization of Android apps is the process of adapting a mobile app for people who speak different languages or live in different countries.&lt;/p&gt;

&lt;p&gt;You probably want your app to grow and be used by more people than just those in your local area. You want it to reach people on other continents and eventually go global. The problem is that not everyone speaks English. You will need to support more than one language sooner or later. It is an excellent reason to think about adding localization support to your Android app from the beginning.&lt;/p&gt;

&lt;p&gt;Localization can also be a part of app store optimization processes as a separate step for improving the visibility of your app. App localization is a lot about translation, but it's not the only thing. Localization includes a full review of how an app works to ensure it works well in each locale. Localizing app descriptions in the &lt;a href="https://play.google.com/store/games"&gt;Google Play app stores&lt;/a&gt; is also essential for worldwide app store optimization (&lt;a href="https://blog.crowdin.com/2021/02/11/smart-ways-to-approach-mobile-app-localization/#whats-next-aso-and-app-localization"&gt;ASO&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Even though localization takes time and work, it pays off in terms of downloads, new users, and money. A study by Distomo found that localized apps had &lt;a href="https://www.demandgenreport.com/features/demanding-views/there-s-a-language-for-that-translating-mobile-apps-and-content/"&gt;128% more downloads&lt;/a&gt; from the app store than apps that were not localized. Also, localized apps brought in 26 percent more money in each region. These numbers show that localizing your Android apps for a global audience is a great idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Main Steps for Android App Localization
&lt;/h2&gt;

&lt;p&gt;By following best practices, you can make the process of localizing cheaper and less time-consuming while also making it easy to scale and maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prioritize locales and languages you’ll begin with
&lt;/h3&gt;

&lt;p&gt;When localizing Android apps, the most crucial choice is which markets to focus on. There is a cost to making your app work in a particular place. So, you shouldn't just try to make your Android app work in every language because you can.&lt;/p&gt;

&lt;p&gt;Also, the cost of making your app work in a different language is not the same in every case. It would help to consider how much money you will get back.&lt;/p&gt;

&lt;p&gt;Focus on the most common languages on the internet to get started. There are the five most-used languages on the internet. In order, the most common languages are English (25%), Chinese (19.8%), Spanish (8%), Arabic (4%) and Portuguese (4%).&lt;/p&gt;

&lt;p&gt;The three languages – English, Chinese, and Spanish – make up 55% of all the content on the internet, so they should be the first ones to be localized. When you are done localizing for these languages, you can move on to the next one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make layouts with localization in mind
&lt;/h3&gt;

&lt;p&gt;Different languages need different lengths for texts. When designing your app’s UI, you should consider how text grows and shrinks in different languages. It's typically recommended that UI elements have 30% more space in other languages.&lt;/p&gt;

&lt;p&gt;There are three main rules of design you should stick to&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; You shouldn't give UI elements fixed widths and heights (buttons, text fields, menus, labels, images). If not, when texts are translated into other languages, they might be cut off or have too many spaces.
&lt;/li&gt;
&lt;li&gt; You should put elements together in a way that makes sense. And put them in the same place every time. They need to be rearranged to make sense in the new languages. For example, if you have some numbered instructions on an Android app with buttons or other UI elements.&lt;/li&gt;
&lt;li&gt; Containers and wrappers should be able to change their sizes to fit the things they hold. A container can contain, for example, 3 buttons (you can find “ViewGroup” in the Android documentation). If the text is longer on another locale, then this container must correctly display the same 3 buttons without covering anything else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you need to rearrange your layout to fit a specific language, you can create an alternative layout for that language (for example, res/layout-de/main.xml). However, doing this can make your app harder to maintain. It is better to create a single layout that is more flexible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Think about languages written and read from right to left
&lt;/h3&gt;

&lt;p&gt;Some languages are written and read from right to left. Hebrew and Arabic are good examples. You need to make layouts that can be turned around if necessary. It could be an expensive process that must go through many steps. That's why it's essential to think carefully about which languages you want to support before you start developing. Most of the time, modular design is thought to be the best way to go in this situation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Word wrappers and line breakers
&lt;/h3&gt;

&lt;p&gt;How words are split up is another crucial difference between languages. Even though space is used to separate words in many western languages, East Asian languages like Chinese and Japanese don't use spaces to do this.&lt;/p&gt;

&lt;p&gt;Consider the last example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;English&lt;/strong&gt;: You should localize your Android app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chinese&lt;/strong&gt;: 您應該本地化您的 Android 應用程序 (Disclaimer: it's text from Google Translate, I can't guarantee the accuracy of this translation)&lt;/p&gt;

&lt;p&gt;In Chinese, there are no spaces between words. The Chinese language is based on the separation of syllables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use fonts that can be applied to more than one language
&lt;/h3&gt;

&lt;p&gt;When choosing fonts for your texts, you need to be careful so that they look and feel the same in different languages. The font choice can significantly affect how easily the text reads. A few things should be kept in mind.&lt;/p&gt;

&lt;p&gt;But the most crucial thing for translation is whether or not fonts are Unicode or not (such as ASCII). The Universal Coded Character Set (UCS), a complete set of characters and glyphs from many languages, is used to make Unicode fonts. These characters are encoded to ensure they look the same on all platforms and systems. ASCII, on the other hand, is only used for one language. ASCII may look nice in English, but it can't be translated well. So, you should be careful to use a font that works with Unicode. Be careful because some fonts say they are Unicode, but they only work with some code points. It could cause trouble with languages.&lt;/p&gt;

&lt;p&gt;In different languages, the same font size may be hard to read differently. For example, 12px is easy to read in English, but the same size in Japanese could be hard to understand.&lt;/p&gt;

&lt;p&gt;For each language, you must change the font sizes on the fly. For that, you need to keep separate style sheets for each language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not hardcode text strings
&lt;/h3&gt;

&lt;p&gt;You shouldn't use hardcoded strings in layouts. For a beginner, this is very tempting. But this could be a big problem when it's time to localize your Android application into a different language.&lt;/p&gt;

&lt;p&gt;The good news is that Android Studio tells us about this. If you have already added hardcoded strings, Android Studio has an excellent way to add them to the string.xml file:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Right-click on the text and select &lt;strong&gt;Show Context Actions&lt;/strong&gt;, and then &lt;strong&gt;Extract resource string&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt; Press &lt;strong&gt;Alt&lt;/strong&gt;+&lt;strong&gt;Enter&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;Extract resource string&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt; Then you'll see a dialog box. &lt;/li&gt;
&lt;li&gt; Give that string value a preferred name.&lt;/li&gt;
&lt;li&gt; Now, look at the &lt;code&gt;string.xml&lt;/code&gt; file and the layout page.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Put all the strings in strings.xml
&lt;/h3&gt;

&lt;p&gt;Don't hardcore any code strings when you develop your apps. Instead, put all your strings in a default &lt;code&gt;strings.xml file&lt;/code&gt; and mark them as resources. The strings in the &lt;code&gt;strings.xml&lt;/code&gt; file can then be quickly taken out, translated, and put back into your app (with the proper qualifiers) without having to change the code that was compiled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't make more text strings and resource files than you need
&lt;/h3&gt;

&lt;p&gt;You probably don't need to make a different version of every resource in your app for each locale. For example, the layout in the &lt;code&gt;res/layout/main.xml&lt;/code&gt; file might work in any locale, so you wouldn't need to make any other layout files.&lt;/p&gt;

&lt;p&gt;The default Android language for your app is American English. In &lt;code&gt;res/values/strings.xml&lt;/code&gt;, all of the American English spellings for the strings used by the app are listed.&lt;/p&gt;

&lt;p&gt;You should use British English spelling for a few crucial phrases. You want these alternative strings to be used when your app runs on a device in the United Kingdom. To do this, you could make a tiny file called &lt;code&gt;res/values-en-rGB/strings.xml&lt;/code&gt;with only the strings that should be different when the app runs in the U.K. The app uses the defaults for the rest of the strings, which are set in &lt;code&gt;res/values/strings.xml&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing String Localization with Localization Software Strings
&lt;/h2&gt;

&lt;p&gt;Strings are the most important objects In a localization process. Let's look at what we need to do to make strings work in different locales.&lt;/p&gt;

&lt;h3&gt;
  
  
  Follow the rules for UI strings in Android
&lt;/h3&gt;

&lt;p&gt;Make sure you pay close attention to how you talk to your users as you design and build your UIs. In general, use a style that is short, friendly, and to the point, and use the same style throughout your UIs.&lt;/p&gt;

&lt;p&gt;Read the &lt;a href="https://material.io/design/communication/writing.html#principles"&gt;Material Design&lt;/a&gt; suggestions for writing style and word choice and follow them.&lt;/p&gt;

&lt;p&gt;Also, always use the standard terms for Android, like Action Bar, Options Menu, System Bar, Notifications, and so on, whenever possible. Consistently using the correct Android terms make localization easier and the end product better for users. Additionally, you can use the &lt;a href="https://blog.crowdin.com/2018/05/18/using-glossary-to-keep-translations-swift-and-consistent/"&gt;glossary&lt;/a&gt;, which lets you create, store, and manage your project's terms in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Give declared strings enough context
&lt;/h3&gt;

&lt;p&gt;When you declare strings in your strings.xml file, explain how they are used. This information is beneficial to the translator and translates better. It also helps you keep track of your strings better.&lt;/p&gt;

&lt;p&gt;Here's what we mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;string name="countdown"&amp;gt;

&amp;lt;xliff:g id="time" example="5 days"&amp;gt;%1$s&amp;lt;/xliff:g&amp;gt; until holiday

&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consider giving additional information, such as: When does it show up on the user's screen? What is the location of this text? Is it a button or a title? Should it be translated as a verb or as a noun?&lt;/p&gt;

&lt;p&gt;To stay flexible during localization, put in some time and provide context before the translation starts. In the long run, this will pay off. For example, the previous article summarizes all the different ways to &lt;a href="https://blog.crowdin.com/2019/11/05/no-context-no-quality-give-translators-context-to-get-better-translations/"&gt;give context during localization&lt;/a&gt;. So that you can be sure your translators always have a great place to work.&lt;/p&gt;

&lt;p&gt;For each language, you usually need at least one translator. Each of them might ask a dozen questions, and most of those questions are likely to be very similar. It would take you a long time to answer each one. &lt;a href="https://blog.crowdin.com/2020/01/27/a-closer-look-at-crowdin-mobile-sdk-extras-real-time-preview-and-screenshots/"&gt;Attaching screenshots&lt;/a&gt; is a very different thing.&lt;/p&gt;

&lt;p&gt;Screenshots will help all translators understand many things at once, as each translator would see a screenshot with the current string's location marked.&lt;/p&gt;

&lt;p&gt;A good idea would be to run the app version with Real-Time Preview set up on the Android Emulators you prefer to use. If you use &lt;a href="https://store.crowdin.com/appetize-app"&gt;Appetize.io&lt;/a&gt;, for example, give your team of translators the link to the app installed on the emulator so they can run it in their browsers. Or, give your translators the version they need, and they will put it on their devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mark parts of the message that don't need to be translated
&lt;/h3&gt;

&lt;p&gt;Often, strings have text that shouldn't be translated into other languages. Typical examples are a piece of code, a placeholder for a value, a special symbol, or a name. As you get your strings ready to be translated, look for text that shouldn't be changed and mark it, so the translator doesn't change it. Translators do not have to translate such strings because they can break the code.&lt;/p&gt;

&lt;p&gt;Use an &lt;code&gt;&amp;lt;xliff:g&amp;gt;&lt;/code&gt;placeholder tag to mark text that shouldn't be translated. Here's an example of a tag that makes sure the text "percent 1$s" doesn't get changed during translation, which could make the message wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;string name="countdown"&amp;gt;

&amp;lt;xliff:g id="time" example="5 days"&amp;gt;%1$s&amp;lt;/xliff:g&amp;gt; until holiday

&amp;lt;/string&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you declare a placeholder tag, you should always add an id attribute that says what the placeholder is for. If your apps later change the value of the placeholder, make sure to add an example attribute to show how it should be used.&lt;/p&gt;

&lt;p&gt;You can see in Crowdin the highlighted text that translators should not translate. Additionally, they will receive a notification “ do not translate.”&lt;/p&gt;

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

&lt;h3&gt;
  
  
  How to Automate the Sync of Source Content and Translations?
&lt;/h3&gt;

&lt;p&gt;You may already know there's more to localization than downloading resource files, sending them to translators, and putting the translations into the codebase:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; It's a waste of time to do everything by hand.&lt;/li&gt;
&lt;li&gt; You must start downloading, sending, and integrating again if the source texts change or you get a new copy.&lt;/li&gt;
&lt;li&gt; If you send translators a spreadsheet with strings but no context, you can expect to get a lot of questions about the lack of context, and the number of target languages will multiply those questions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can set up several &lt;a href="https://store.crowdin.com/"&gt;integrations with Git tools &lt;/a&gt; in Crowdin. They all do a great job of keeping your systems' content in sync. For example, use integrations with &lt;a href="https://store.crowdin.com/github"&gt;GitHub&lt;/a&gt;, &lt;a href="https://store.crowdin.com/gitlab"&gt;GitLab&lt;/a&gt;, &lt;a href="https://store.crowdin.com/bitbucket"&gt;Bitbucket&lt;/a&gt;, and &lt;a href="https://store.crowdin.com/azure-repos"&gt;Azure Repos&lt;/a&gt;. It is also possible to &lt;a href="https://blog.crowdin.com/2020/04/30/add-localization-to-your-github-workflows-with-crowdin-action/"&gt;integrate with GitHub Actions&lt;/a&gt;. You can also use the &lt;a href="https://developer.crowdin.com/cli-tool/"&gt;Crowdin CLI&lt;/a&gt; or the &lt;a href="https://developer.crowdin.com/android-studio/#setup"&gt;Android Studio plugin&lt;/a&gt; to automatically upload source files to Crowdin and easily add translations to the codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hide duplicate strings to translate less
&lt;/h3&gt;

&lt;p&gt;Sometimes, the same strings will appear in different places on your app. For example, you may have two "Next" buttons that are represented by the button next and next button translation keys. These keys, on the other hand, will have the same translation Next. It means that these keys are essentially duplicates.&lt;/p&gt;

&lt;p&gt;If there are duplicate strings in your project, you can choose how the system should handle them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Show – translators will translate each instance (string) separately. Duplicated strings won’t be hidden.&lt;/li&gt;
&lt;li&gt;  Show, but auto-translate them – Duplicated strings will be automatically translated but will stay visible to translators. Once the string is translated, its translation is automatically shared between the duplicates. Translators may review and re-translate those strings if necessary.&lt;/li&gt;
&lt;li&gt;  Show within a version branch (regular detection) – duplicates will be hidden only between &lt;a href="https://support.crowdin.com/versions-management/"&gt;version branches&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  Show within a version branch (strict detection) – duplicates will be hidden only between version branches. &lt;/li&gt;
&lt;li&gt;  Hide (regular detection) – all duplicates will share the same translation.&lt;/li&gt;
&lt;li&gt;  Hide (strict detection) – all duplicates will share the same translation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regular duplicates detection: Crowdin only compares source texts when comparing strings.&lt;/p&gt;

&lt;p&gt;Strict duplicates detection: When Crowdin compares strings, it looks at both the keys and the source texts.&lt;/p&gt;

&lt;p&gt;Additionally, Crowdin offers a feature – &lt;a href="https://support.crowdin.com/tm-auto-substitution/"&gt;Auto-substitution&lt;/a&gt;. It is meant to make the Translation Memory (TM) more useful by suggesting translations that are a better match. With this feature, tags, HTML entities, placeholders, numbers, and other things that can't be translated are changed to the ones used in the source strings.&lt;/p&gt;

&lt;p&gt;Once the project owner or manager turns on the feature in the project settings, everyone who is part of the project can use it. If you are not the owner or manager of the project, ask someone who is to help you. To turn on Auto-substitution, go to Project Settings, click on the General tab, scroll down to the Translation Memory section, and check the Enable Auto-Substitution box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localization of a Mobile App without Resource Files
&lt;/h2&gt;

&lt;p&gt;Usually, the localization of an app starts when the resource files are sent to translators, either by email (please don’t)) or automatically. The resource file comes first, and you can move on to localization.&lt;/p&gt;

&lt;p&gt;Let's switch this up and send strings directly from your design tool (like Figma, Sketch, or Adobe XD) to be translated. Crowdin's design and development teams can work on UI localization without using resource files. It’s a new way to do localization.&lt;/p&gt;

&lt;p&gt;Designers working on prototypes in Adobe XD, Figma, or Sketch can send strings to be translated by using keys and rules for dividing up text.&lt;/p&gt;

&lt;p&gt;Because each localization string has its unique identifier, it's easy for development teams to add the original and translated content to the codebase.&lt;/p&gt;

&lt;p&gt;Also, if the source files in Crowdin are in a format other than Android XML and Strings, engineers can run the custom exporters and download files in the correct format. They can use either the &lt;a href="https://blog.crowdin.com/2020/08/05/announcing-the-new-API-2.0/"&gt;Crowdin API&lt;/a&gt; or the Crowdin download targets command in &lt;a href="https://blog.crowdin.com/2020/12/03/what-is-new-at-crowdin-november-2020-roundup/"&gt;CLI versions 3.4.0&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://support.crowdin.com/advanced-project-setup/#bundles"&gt;Crowdin Bundles&lt;/a&gt; lets you upload a single source file (like Android XML) to your project, translate it, and then use bundles to export translations for multiple platforms by creating different file formats (Android XML for the Android app). You can add bundles and set them up so that you can export groups of strings in one of the file formats.&lt;/p&gt;

&lt;p&gt;Crowdin's integration with design tools (&lt;a href="https://store.crowdin.com/figma"&gt;Figma&lt;/a&gt;, &lt;a href="https://store.crowdin.com/sketch"&gt;Sketch&lt;/a&gt;, or &lt;a href="https://store.crowdin.com/adobe-xd-plugin"&gt;Adobe XD&lt;/a&gt;) is an excellent way for designers to preview translated mockups, change them if needed, or let engineers know about the parts that should be scaled.&lt;/p&gt;

&lt;p&gt;Find out more about the Crowdin &lt;a href="https://crowdin.com/teams/designers"&gt;plugins for design teams&lt;/a&gt; and how they make it easier to &lt;a href="https://blog.crowdin.com/2021/01/27/best-practices-for-ui-localization/"&gt;localize user interfaces&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crowdin SDK for Android apps
&lt;/h2&gt;

&lt;p&gt;You don't have to put out a new version of the app on Google Play every time you want to add a new language. Install &lt;a href="https://developer.crowdin.com/sdk-android/"&gt;Crowdin SDK&lt;/a&gt; on your app, and your &lt;a href="https://crowdin.com/"&gt;Crowdin&lt;/a&gt; project will send you ready-to-go translations immediately. Three new features will make getting the latest translations directly from your Crowdin project's distribution, automatically upload screenshots, and see what translations look like in the app in real-time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-The-Air Content Delivery&lt;/strong&gt;&lt;br&gt;
Crowdin can send new translations to your app over the air, so you don't have to update your app on Google Play. The Crowdin SDK for Android has two extra parts that you can connect: a real-time preview of the translation and an easy way to upload and tag screenshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-Time Preview&lt;/strong&gt;&lt;br&gt;
Crowdin lets translators see an instant preview of the translations they make on their app. LQA (linguistic quality assurance) should be made easier so your team can check the translated content in context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshots&lt;/strong&gt;&lt;br&gt;
Project owners and managers can take screenshots in the app version where this component is turned on. Screenshots will automatically be added to the project if the source strings are marked.&lt;/p&gt;

&lt;h2&gt;
  
  
  You Know How to Do Localization Now
&lt;/h2&gt;

&lt;p&gt;We've gone over everything you need to think about when you start tailoring your app for your target market. There are two common myths. The first is to think everyone will use your app in English. The second would be that you can handle localization without technology.&lt;/p&gt;

&lt;p&gt;Crowdin knows that the &lt;a href="https://blog.crowdin.com/2021/02/11/smart-ways-to-approach-mobile-app-localization/"&gt;localization of your mobile app&lt;/a&gt; is a vital part of growing your market share and sales in other countries. We make it easy for you to localize your Android app, so you don't have to deal with strings and spreadsheets.&lt;/p&gt;

&lt;p&gt;Sign up for a &lt;a href="https://accounts.crowdin.com/register"&gt;Crowdin&lt;/a&gt; or &lt;a href="https://accounts.crowdin.com/workspace/create"&gt;Crowdin Enterprise&lt;/a&gt; account to get started. Book a &lt;a href="https://crowdin.com/demo-request"&gt;personal demo&lt;/a&gt; with our tech manager if you'd like to learn more best practices and talk about the unique ways you work.&lt;/p&gt;

</description>
      <category>android</category>
      <category>localization</category>
      <category>mobile</category>
      <category>design</category>
    </item>
    <item>
      <title>Node.js i18n: Agile Localization for Developers</title>
      <dc:creator>Diana Voroniak</dc:creator>
      <pubDate>Fri, 25 Mar 2022 10:23:26 +0000</pubDate>
      <link>https://dev.to/crowdin/nodejs-i18n-agile-localization-for-developers-3o63</link>
      <guid>https://dev.to/crowdin/nodejs-i18n-agile-localization-for-developers-3o63</guid>
      <description>&lt;p&gt;In today’s market, people seek personalization. They expect your app to speak their native language. In this article, we will cover how you can translate your Node.js app using a developer-friendly localization platform. Learn about the i18n framework and how to integrate your repository with a localization system to automate Node.js localization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Your App Multilingual and Grow Your Revenue
&lt;/h2&gt;

&lt;p&gt;Language connects people. The same principle is applied to your app development. Having localized versions of your app can grow and generate great revenue for your app by opening new markets.  &lt;/p&gt;

&lt;p&gt;The internet is somewhat similar to the real world. When you visit a place where no one speaks your mother language, you feel isolated and lost. Making your app multilingual help your customers use it more effectively and opens up opportunities to reach new audiences.  &lt;/p&gt;

&lt;p&gt;According to a recent &lt;a href="https://preply.com/en/blog/language-inclusive-apps/" rel="noopener noreferrer"&gt;study by Preply&lt;/a&gt;, most of the top apps in each tech category are also the most language-inclusive ones.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Node.js Web Application Framework
&lt;/h2&gt;

&lt;p&gt;Node.js is a server-side platform built on Google Chrome's JavaScript Engine. The official &lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;documentation of Node.js&lt;/a&gt; states the following – ‘’Node.js is a platform built on Chrome's JavaScript runtime for easily building fast and scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.’’  &lt;/p&gt;

&lt;p&gt;If you choose Node.js as a development environment and are curious about how you can implement localization as a part of your workflow – you’re in the right place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Node.js i18n: Localization Starting Point
&lt;/h2&gt;

&lt;p&gt;If you plan to create a multilingual version of your app, you should use one of the Node.js internationalization libraries available, for example on &lt;a href="https://openbase.com/" rel="noopener noreferrer"&gt;Openbase&lt;/a&gt;. This article will cover the &lt;a href="https://github.com/mashpie/i18n-node" rel="noopener noreferrer"&gt;i18n-node&lt;/a&gt; as an example. You may choose another library according to your preferences. They do implement localization functionality pretty much in the same way. &lt;/p&gt;

&lt;p&gt;Here are a few first steps you need to take to begin the localization of your Node.js app with &lt;a href="https://github.com/mashpie/i18n-node" rel="noopener noreferrer"&gt;i18n-node&lt;/a&gt; library.&lt;/p&gt;

&lt;h3&gt;
  
  
  The first step would be to install the package
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;$ npm install i18n&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Then, create an i18n config object
&lt;/h3&gt;

&lt;p&gt;Before we use the library, we need to configure it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ touch app/i18n.config.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: app/i18n.config.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { I18n } = require('i18n');
const path = require('path');

const i18n = new I18n({
  locales: ['en', 'uk'],
  defaultLocale: 'en',
  directory: path.join('./', 'locales')
});

module.exports = i18n;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added support for 2 languages, with the default one being en (English) and the second one being uk (Ukrainian). View the whole list of all &lt;a href="https://github.com/mashpie/i18n-node#list-of-all-configuration-options" rel="noopener noreferrer"&gt;configuration&lt;/a&gt; options for the library to learn more. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create an i18n object
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;$ touch index.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: index.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const i18n = require('./app/i18n.config');

console.log(i18n.getLocales()); // ['en', 'uk']
console.log(i18n.getLocale()); // 'en'
console.log(i18n.__('Hello')); // 'Hello'
console.log(i18n.__n('You have %s message', 5)); // 'You have 5 messages'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in the command line, run the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ node index.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will automatically generate a locales' directory on the root folder. It will contain the relevant translation strings for the current language:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ tree locales &lt;br&gt;
locales&lt;br&gt;
├── uk.json&lt;br&gt;
└── en.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: locales/en.json&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
   "Hello": "Hello",&lt;br&gt;
   "You have %s message": {&lt;br&gt;
      "one": "You have %s message",&lt;br&gt;
      "other": "You have %s messages"&lt;br&gt;
   }&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add the following line in the index.js to test the generation of translatable strings for the other language:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;i18n.setLocale('uk')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: locales/uk.json&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
   "Hello": "Привіт",&lt;br&gt;
   "You have %s message": {&lt;br&gt;
      "one": "Ви маєте %s повідомлення",&lt;br&gt;
      "other": "Ви маєте %s повідомлень"&lt;br&gt;
   }&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Rerun the app to verify translations
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;$ node index.js&lt;br&gt;
Привіт&lt;br&gt;
Ви маєте 5 повідомлень&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate Node.js Localization with Crowdin
&lt;/h2&gt;

&lt;p&gt;Node.js localization library was created to help and save your time as a developer. However, as you can assume from a small example above, doing the actual translations into multiple languages can still be very challenging, especially for complex apps.  &lt;/p&gt;

&lt;p&gt;You will face several challenges like making sure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your translators do not edit the same file simultaneously or break the code&lt;/li&gt;
&lt;li&gt; translation values are present for each language&lt;/li&gt;
&lt;li&gt;you don’t have to spend time copy-pasting translations
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t have to deal with all the extra work. And why would you if there is a solution? Make localization part of your workflow and forget about those issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrate Your Repo and Localization Project
&lt;/h3&gt;

&lt;p&gt;Set up an integration with your repo. Crowdin integrates with &lt;a href="https://store.crowdin.com/collections/frontpage/products/github" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://store.crowdin.com/collections/frontpage/products/gitlab" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt;, &lt;a href="https://store.crowdin.com/products/bitbucket?_pos=1&amp;amp;_sid=499a27e7c&amp;amp;_ss=r" rel="noopener noreferrer"&gt;Bitbucket&lt;/a&gt;, and &lt;a href="https://store.crowdin.com/collections/frontpage/products/azure-repos" rel="noopener noreferrer"&gt;Azure Repos&lt;/a&gt;. You can also install &lt;a href="https://support.crowdin.com/cli-tool/" rel="noopener noreferrer"&gt;Crowdin Console Client&lt;/a&gt; (CLI) which allows you to integrate with GIT, SVN, Mercurial, and more. &lt;/p&gt;

&lt;p&gt;Crowdin will get the localization files from your repo and upload them to the &lt;a href="https://support.crowdin.com/online-editor/" rel="noopener noreferrer"&gt;Editor&lt;/a&gt;, where all strings from your app will look user-friendly and include the comments you provided. Once the translations are done, the system compiles them into a file and syncs them with your code as a merge request. Translations do not get to the master branch before you merge them. You can also integrate multiple branches with Crowdin at the same time, so translators can work with the texts from your feature branch before it’s rolled out, and you can release them into multiple languages at the same time.  &lt;/p&gt;

&lt;p&gt;In case you have more than one app or several products that share similar features and texts, you can share their translations and avoid translating the same content twice, saving time and money. Usually, iOS and Android app versions have much of the same content. In Crowdin, you can easily detect duplicate strings to reuse their translations. &lt;/p&gt;

&lt;h3&gt;
  
  
  Use Machine Translation and Translation Memory Pre-Translation
&lt;/h3&gt;

&lt;p&gt;Both machine translation (MT) and translation memory (TM) will help you translate content faster and reduce translation costs. With their help, you can reduce the duration of the project even before the translators begin their work. To do this, configure pre-translation via TM or MT. You can also leverage MT to get translations of non-critical content.  &lt;/p&gt;

&lt;p&gt;On Crowdin, you can localize your product with the help of more than 40 machine engines like Microsoft Translator, Google Translate, DeepL Translator. View the list of &lt;a href="https://crowdin.com/page/integrations#nav-mt-engines" rel="noopener noreferrer"&gt;machine engines&lt;/a&gt; you can use with Crowdin today.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ensure Context for Translators
&lt;/h3&gt;

&lt;p&gt;Simply put, context ensures the quality of the localized version of your app. Crowdin has created a few ways to help you provide context for translators and reduce manual work. &lt;/p&gt;

&lt;p&gt;You can provide context using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WYSIWYG &lt;a href="https://support.crowdin.com/online-editor/" rel="noopener noreferrer"&gt;editor&lt;/a&gt; view&lt;/li&gt;
&lt;li&gt;Context for strings. Add text descriptions or labels, use Glossary terms to get relevant translations faster.&lt;/li&gt;
&lt;li&gt;Screenshots. You can upload your game or app screenshots manually or using different integrations or add-ons, like Crowdin SDK for example. This way, translators know the location and context for each string.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.crowdin.com/enterprise/in-context/" rel="noopener noreferrer"&gt;In-Context&lt;/a&gt; Localization tool. Translators can work as if in the real app interface and preview the translations they make right there. Translations are stored within your Crowdin project, and you can decide when to pull the strings to your application. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can learn more about the Crowdin platform by watching a short &lt;a href="https://crowdin.com/on-demand-demo" rel="noopener noreferrer"&gt;on-demand demo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localize Your Product with Crowdin
&lt;/h2&gt;

&lt;p&gt;Automate localization by internationalizing your texts and integrating your repo with Crowdin New source strings are automatically sent to translators, and you receive translations as a merge after the work is done. Let your customers access your product in several languages. Get started and register a &lt;a href="https://accounts.crowdin.com/register" rel="noopener noreferrer"&gt;Crowdin account&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>tutorial</category>
      <category>localization</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Crowdin Language Services: The Fastest Way to Make Your Product Multilingual</title>
      <dc:creator>Diana Voroniak</dc:creator>
      <pubDate>Fri, 04 Mar 2022 08:41:32 +0000</pubDate>
      <link>https://dev.to/crowdin/crowdin-language-services-the-fastest-way-to-make-your-product-multilingual-20c9</link>
      <guid>https://dev.to/crowdin/crowdin-language-services-the-fastest-way-to-make-your-product-multilingual-20c9</guid>
      <description>&lt;p&gt;Crowdin Language Services is the fastest way to prepare content for your multilingual audience. Along with the localization management platform, we now deliver professional translation services.&lt;/p&gt;

&lt;p&gt;Want to reach a multilingual market? Now you have a quick and easy solution. Upload your files or connect your VCS to automate content sync and order translations from a new vendor – &lt;a href="https://solutions.crowdin.com/crowdin-language-services" rel="noopener noreferrer"&gt;Crowdin Language Services&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;If you need help with managing the localization process and tasks like: deciding on the target market, languages, and budget, uploading the files, ensuring translators have all the resources to provide high-quality translations, and more, Crowdin also offers to hire a &lt;a href="https://solutions.crowdin.com/virtual-localization-manager" rel="noopener noreferrer"&gt;virtual localization manager&lt;/a&gt; to handle the role of your localization lead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Save Time on Finding the Best Translation Agency
&lt;/h2&gt;

&lt;p&gt;It can be challenging to find a good translation service, especially considering the number of freelancers and agencies available on the internet today. The important things to consider when choosing your LSP can include translation expertise, delivery time, and many other factors. We wanted to give you an option where you can skip making this choice and simply select the files and order translations right away.&lt;/p&gt;

&lt;p&gt;That’s why we added &lt;a href="https://solutions.crowdin.com/crowdin-language-services" rel="noopener noreferrer"&gt;Crowdin Language Services&lt;/a&gt; to the list of vendors in Crowdin. You can still bring your in-house team, freelancers, or any agency of your choice. For quick translations and projects with limited time or budget, Crowdin Language Services can be the right choice. All you need to quickly translate your content is to add your files to Crowdin (manually or via an integration) and hire professionals to do translations while you focus on developing new features or creating content.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Main Reasons to Hire Crowdin Language Services
&lt;/h2&gt;

&lt;p&gt;If you hire professional translation services, you know you’re working with trained linguists. With us, you will receive high-quality translations and a fast, clear, and convenient process from start to finish. Here are a few key reasons to hire Crowdin Language Services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Types and Sync with 100+ Tools
&lt;/h3&gt;

&lt;p&gt;Crowdin Language Services will deal with any type of content. With us, you can localize your web or desktop software, games, apps, websites, landing pages, and more. &lt;/p&gt;

&lt;p&gt;To avoid any manual file transfer, you can connect your Crowdin projects with repositories on GitHub, GitLab, Bitbucket, and Azure Repos.&lt;/p&gt;

&lt;p&gt;You can also export and translate data from more than 100 applications like content management systems, file directories, marketing and customer support tools. And yes, you can do it without manual file copy-pasting. All you need is to install a respective app from the &lt;a href="https://store.crowdin.com/" rel="noopener noreferrer"&gt;Crowdin Store&lt;/a&gt; and sync source files and translations between Crowdin and the tool of your choice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finf85r4vkfwrcwmzugja.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finf85r4vkfwrcwmzugja.png" alt="Crowdin Store:Integrations and Apps" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Languages
&lt;/h3&gt;

&lt;p&gt;Crowdin Language Services team can get started within a few days and specializes in translations into more than &lt;strong&gt;100&lt;/strong&gt; languages, including German, English, French, Spanish, Italian, Portuguese, Dutch, Russian, Polish, Czech, Chinese, Arabic, and more. &lt;/p&gt;

&lt;h3&gt;
  
  
  Quality
&lt;/h3&gt;

&lt;p&gt;Go with Crowdin translation services that put quality first. With us, you’ll know for sure that your content will be polished before it reaches you. We ensure continuous quality control with both automatic (QA checks, glossaries, translation memory) and professional review (proofreading).&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer Service
&lt;/h3&gt;

&lt;p&gt;The translation process is straightforward and easy to start. Crowdin Language Services complete the project within the deadline and can manage different localization projects simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Certifications and Security
&lt;/h3&gt;

&lt;p&gt;All information and content you provide to us are stored on secure servers. View our &lt;a href="https://crowdin.com/page/security" rel="noopener noreferrer"&gt;Security at Crowdin&lt;/a&gt; page to see our security policies, data on sub-processors, and what Crowdin does to ensure internal and application security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Order Professional Translations From Crowdin Language Services
&lt;/h3&gt;

&lt;p&gt;Start localizing your content in a matter of minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa79v8aqa6o7jcy7vrj2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa79v8aqa6o7jcy7vrj2d.png" alt="5 steps to make your product multilingual with Crowdin Language Services" width="800" height="1038"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To order translations, please follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create an account or log into &lt;a href="https://crowdin.com/" rel="noopener noreferrer"&gt;Crowdin&lt;/a&gt; and create a localization project. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automate your content synchronization. Connect your project with &lt;a href="https://support.crowdin.com/github-integration/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://support.crowdin.com/gitlab-integration/" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt;, &lt;a href="https://support.crowdin.com/bitbucket-integration/" rel="noopener noreferrer"&gt;Bitbucket&lt;/a&gt;, or &lt;a href="https://support.crowdin.com/azure-repos-integration/" rel="noopener noreferrer"&gt;Azure Repos&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the main page &amp;gt; &lt;em&gt;Resources&lt;/em&gt; &amp;gt; &lt;em&gt;Vendors&lt;/em&gt; &amp;gt; Crowdin Language Services &amp;gt; Choose a project and &lt;strong&gt;Create a Task&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pick files, target languages, and buy translations.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Localize Your Product with Crowdin
&lt;/h2&gt;

&lt;p&gt;Automate localization process, release your app in several languages simultaneously, and provide an enhanced experience for your global customers with Crowdin.&lt;/p&gt;

&lt;p&gt;Get started and register a &lt;a href="https://crowdin.com/" rel="noopener noreferrer"&gt;Crowdin&lt;/a&gt; account.&lt;/p&gt;

</description>
      <category>multilingual</category>
      <category>localization</category>
      <category>translatio</category>
      <category>devs</category>
    </item>
    <item>
      <title>CakePHP Translation: Agile Localization for Devs</title>
      <dc:creator>Diana Voroniak</dc:creator>
      <pubDate>Fri, 10 Dec 2021 15:11:53 +0000</pubDate>
      <link>https://dev.to/crowdin/cakephp-translation-agile-localization-for-devs-32eh</link>
      <guid>https://dev.to/crowdin/cakephp-translation-agile-localization-for-devs-32eh</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/cakephp/cakephp" rel="noopener noreferrer"&gt;CakePHP&lt;/a&gt; is an open-source web framework for PHP which uses commonly known design patterns. Based on model-view-controller, it is generally used for handling web applications, the informative &lt;a href="https://bakery.cakephp.org/" rel="noopener noreferrer"&gt;CakePHP library&lt;/a&gt; makes the development clear.&lt;/p&gt;

&lt;p&gt;Our article will teach you how CakePHP Translation basics can help you automate your localization and translate regular app updates with ease.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate Localization with VCS Integrations
&lt;/h2&gt;

&lt;p&gt;Localization is an important step that opens your product to a global market. In order to reap the benefits of localization and enjoy both the result and process, it should be organized in a highly agile manner.&lt;/p&gt;

&lt;p&gt;The best way to do it is to integrate the localization process into your development workflow. For this, use a localization management platform that integrates with your software repository.  &lt;/p&gt;

&lt;p&gt;On Crowdin, you can connect your localization projects with &lt;a href="https://store.crowdin.com/collections/frontpage/products/github" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://store.crowdin.com/collections/frontpage/products/gitlab" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt;, &lt;a href="https://store.crowdin.com/products/bitbucket?_pos=1&amp;amp;_sid=499a27e7c&amp;amp;_ss=r" rel="noopener noreferrer"&gt;Bitbucket&lt;/a&gt;, &lt;a href="https://store.crowdin.com/collections/frontpage/products/azure-repos" rel="noopener noreferrer"&gt;Azure Repos&lt;/a&gt; or install &lt;a href="https://support.crowdin.com/cli-tool/" rel="noopener noreferrer"&gt;Crowdin Console Client (CLI)&lt;/a&gt; that allows you to integrate with GIT, SVN, Mercurial, and more.  &lt;/p&gt;

&lt;p&gt;Integration with your repository ensures that the new texts from the selected branches are sent to your localization project right away, and all the translations are automatically added to your repo as a pull request.  &lt;/p&gt;

&lt;p&gt;Make all of these steps automated to save your time for other tasks.  &lt;/p&gt;

&lt;h2&gt;
  
  
  CakePHP Localization Basics
&lt;/h2&gt;

&lt;p&gt;Developers often underestimate the complexity of localization. An application aiming for a multilingual market should contain more than just translated copy. You should also consider date and time formats, currency symbols, and pluralization.  &lt;/p&gt;

&lt;p&gt;To make it easier for you to understand the basics, let’s talk about CakePHP localization and gettext.&lt;/p&gt;

&lt;h3&gt;
  
  
  CakePHP – Gettext
&lt;/h3&gt;

&lt;p&gt;Since CakePHP follows standard gettext strategies, CakePHP localization requires storing translatable text in &lt;a href="https://support.crowdin.com/file-formats/po/#custom-attributes" rel="noopener noreferrer"&gt;.po files&lt;/a&gt;. It is one of the most common human/machine-readable file formats.&lt;/p&gt;

&lt;p&gt;So that's why the first step towards the multilingual CakePHP app is to install &lt;a href="https://www.gnu.org/software/gettext/" rel="noopener noreferrer"&gt;gettext&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Up CakePHP Translations
&lt;/h3&gt;

&lt;p&gt;The simplest way to “transform” a single-language application into a multilingual application is to use the &lt;code&gt;__()&lt;/code&gt; function in your code. &lt;/p&gt;

&lt;p&gt;Here is an example of some code for a single-language application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h2&amp;gt;Home page&amp;lt;/h2&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To internationalize your code, all you need to do is to wrap all strings you need to localize in __() like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h2&amp;gt;&amp;lt;?= __('Home page') ?&amp;gt;&amp;lt;/h2&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two code examples are identical in terms of functionality – they will both send the content to the browser. The &lt;code&gt;__()&lt;/code&gt; function will translate the passed string if a translation is available. If there are no translations, it returns the texts without any changes. That means, the original string will appear on the web page.&lt;/p&gt;

&lt;p&gt;Note: You do not need to instantiate the L10n class. It happens automatically the first time the &lt;code&gt;__()&lt;/code&gt; function is called.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Language Files
&lt;/h3&gt;

&lt;p&gt;Create one or more .po files, depending on how the file is and how many languages you want. Place files under &lt;em&gt;src/Locale/&lt;/em&gt; and within this directory. It's important to create a subfolder for each language the application needs to support.&lt;/p&gt;

&lt;p&gt;If you choose to use just one .po file, you'll wrap your strings with the &lt;code&gt;__()&lt;/code&gt; helper. If you choose to have multiple .po files in order to avoid one massive file, you can use the &lt;code&gt;__d()&lt;/code&gt; helper so that you could specify the domain (domain = name of the .po file without the .po extension).&lt;/p&gt;

&lt;p&gt;Translation folders can be the two or three-letter ISO code of the language or the full locale name such as &lt;code&gt;fr_FR&lt;/code&gt;, &lt;code&gt;es_ES&lt;/code&gt;, &lt;code&gt;es_DO&lt;/code&gt;, which contains both the language and the country where it is spoken.&lt;/p&gt;

&lt;p&gt;View Crowdin &lt;a href="https://support.crowdin.com/api/language-codes/" rel="noopener noreferrer"&gt;languages codes&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/src
        /Locale
            /fr
                mywebapp.po
            /es
                mywebapp.po

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source strings will be marked with “&lt;strong&gt;msgid&lt;/strong&gt;” and shouldn’t be changed. Strings marked with “&lt;strong&gt;msgstr&lt;/strong&gt;” are where you put the translation. The translation starts blank and needs to be filled in. Don't forget to keep quotes around your translation.  &lt;/p&gt;

&lt;p&gt;If “&lt;strong&gt;msgctxt&lt;/strong&gt;” is given, it will be used as part of the translation title, which you can use to provide the context. In the next section, we will cover more details about context.&lt;/p&gt;

&lt;p&gt;An example translation file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;msgid "Settings and privacy"
msgstr "Paramètres et confidentialité"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you install the integration with your VSC system, you can customize content synchronization between your repository and Crowdin. For this, change the configuration in the Crowdin UI, or edit the configuration file based on your preferences and upload it.  &lt;/p&gt;

&lt;p&gt;For example, you can define the path for files, map your own languages to be recognizable in your localization projects, rename translation files, select files and directories that you don’t need to translate, and more.  &lt;/p&gt;

&lt;p&gt;Read more about &lt;a href="https://support.crowdin.com/vcs-integrations-configuring-online/?q=language%20codes#creating-a-configuration" rel="noopener noreferrer"&gt;VCS Integrations&lt;/a&gt; and a &lt;a href="https://support.crowdin.com/configuration-file/" rel="noopener noreferrer"&gt;configuration file&lt;/a&gt;.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Add String Context
&lt;/h3&gt;

&lt;p&gt;Providing translators with context improves translation quality, makes the translation process easier, and streamlines the QA process by reducing the possibility of user error.  &lt;/p&gt;

&lt;p&gt;You can add a “comment” to your string to give translators hints about a translatable string.&lt;/p&gt;

&lt;p&gt;Let's look at the word "email". In English, it refers to a noun and a verb. To specify which one is used here, you can use the &lt;code&gt;__x()&lt;/code&gt; function. The context will appear on the &lt;strong&gt;msgctxt&lt;/strong&gt; line in the resulting .po file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo __x('noun', 'email');

echo __x('verb', 'email');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first argument is the context of the message, and the second is the message to be translated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;msgctxt "noun"
msgid "email"
msgstr ""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;You can set the default locale in your &lt;em&gt;config/app.php&lt;/em&gt; file by setting &lt;code&gt;App.defaultLocale&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The default locale controls several aspects of your application, including the default language, the date and time formats. It can also control the number or currency format whenever any of those is displayed using CakePHP’s localization libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'App' =&amp;gt; [
    ...
    'defaultLocale' =&amp;gt; env('APP_DEFAULT_LOCALE', 'en_US'),
    ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the official &lt;a href="https://book.cakephp.org/3/en/core-libraries/internationalization-and-localization.html" rel="noopener noreferrer"&gt;CakePHP documentation&lt;/a&gt; to discover further information about using plurals, variables, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provide Context – Improve the Translation Quality
&lt;/h2&gt;

&lt;p&gt;Context ensures the quality of your localized product and makes the translation process easier. There are a lot of ways to provide context. It can be screenshots, videos, a link to the entire file, or even better – a visual preview right in the Editor where translators work.  &lt;/p&gt;

&lt;p&gt;The original context is particularly important when sentences are short or consist of few words, as often happens in our case with web applications.  &lt;/p&gt;

&lt;p&gt;To make it easier for you to provide context for translators and reduce manual work, Crowdin has created an &lt;a href="https://support.crowdin.com/enterprise/in-context/" rel="noopener noreferrer"&gt;In-Context Localization tool&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;You can use the tool to create an overlay for your app. Translators can translate and receive context in real-time. In-context localization is connected with the actual project created in Crowdin, which contains the translatable files. The translators can preview their translations as if in the real app interface. Translation files are stored within your Crowdin project, and you can decide when to pull them to your application (for example, only pull them after reviewing by a proofreader, so you can be confident in the quality of the final product).&lt;/p&gt;

&lt;p&gt;Read our article about the &lt;a href="https://blog.crowdin.com/2019/11/05/no-context-no-quality-give-translators-context-to-get-better-translations/" rel="noopener noreferrer"&gt;In-Context tool&lt;/a&gt; and other ways to provide context for translators  on Crowdin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Localize Your App with Crowdin
&lt;/h3&gt;

&lt;p&gt;Automate localization process, release your app in several languages simultaneously, and provide an enhanced experience for your global customers with Crowdin. &lt;/p&gt;

&lt;p&gt;Get started and register a &lt;a href="https://accounts.crowdin.com/register?utm_source=dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=cakephp-localization&amp;amp;utm_content=article"&gt;Crowdin&lt;/a&gt; account.&lt;/p&gt;

</description>
      <category>cakephp</category>
      <category>webdev</category>
      <category>localization</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Webinar: How to localize your mobile app and drive your app store optimization (ASO) strategy</title>
      <dc:creator>Diana Voroniak</dc:creator>
      <pubDate>Thu, 09 Dec 2021 15:44:29 +0000</pubDate>
      <link>https://dev.to/crowdin/webinar-how-to-localize-your-mobile-app-and-drive-your-app-store-optimization-aso-strategy-3kp1</link>
      <guid>https://dev.to/crowdin/webinar-how-to-localize-your-mobile-app-and-drive-your-app-store-optimization-aso-strategy-3kp1</guid>
      <description>&lt;p&gt;Did you know that including #applocalization in your app store optimization strategy could boost your app downloads?&lt;br&gt;
You can learn more about this topic at our online webinar.&lt;/p&gt;

&lt;p&gt;Join Crowdin on December 22 at 11 AM EST / 5 PM CET for a webinar on mobile localization.&lt;/p&gt;

&lt;p&gt;Attend this free webinar to learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to get started with app localization&lt;/li&gt;
&lt;li&gt;Why context is essential when translating short copy&lt;/li&gt;
&lt;li&gt;Publishing new translations over-the-air with no need to update the app&lt;/li&gt;
&lt;li&gt;Driving your app store optimization strategy (ASO)&lt;/li&gt;
&lt;li&gt;Agency VS community translations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our Partnerships Manager Bohdan Sitarskyi and a special guest, Localization Project Manager Guillermo Umpiérrez, will discuss how you can make localization a part of your app store optimization strategy and drive more downloads.&lt;/p&gt;

&lt;p&gt;Localize your mobile apps with Crowdin and get relevant translations, provide translators with context, and update texts faster using automation features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Register now&lt;/strong&gt;: &lt;a href="https://bit.ly/3dx47zU" rel="noopener noreferrer"&gt;https://bit.ly/3dx47zU&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  About Crowdin:
&lt;/h2&gt;

&lt;p&gt;Crowdin is a cloud-based localization management software. With 2M registered users and 100K+ localization projects from all over the world. Efficiently create and manage all your multilingual content in one place. Localize your apps, websites, games, help documentation, designs, and all that create a native experience for your customers around the globe.&lt;/p&gt;

&lt;p&gt;Trusted by GitHub, Avast, GitLab, Buffer, Calendly, and Wrike, and other companies creating multilingual products.&lt;/p&gt;

&lt;p&gt;Crowdin integrates with GitHub, GitLab, Bitbucket, Azure Repos, and 100+tools. You can use our API, CLI, webhooks, apps and customize your experience depending on your needs.&lt;/p&gt;

</description>
      <category>android</category>
      <category>ios</category>
      <category>localization</category>
      <category>webinar</category>
    </item>
    <item>
      <title>Mobile Apps Localization</title>
      <dc:creator>Diana Voroniak</dc:creator>
      <pubDate>Wed, 10 Nov 2021 08:16:07 +0000</pubDate>
      <link>https://dev.to/crowdin/mobile-apps-localization-2093</link>
      <guid>https://dev.to/crowdin/mobile-apps-localization-2093</guid>
      <description>&lt;p&gt;Whenever you are new to localization, the best thing to keep in mind is that localization is an integral part of product development, the same as prototyping or testing. Thus, localization will require some extra setups at the beginning in order for you to create a continuous workflow later.&lt;/p&gt;

&lt;p&gt;In case you’re afraid that localization can slow down your team and the results you make, we can change your mind. Downloading resource files, sending them to translators, and integrating translations into the codebase manually is really time-consuming, but luckily, it’s not the only option.&lt;/p&gt;

&lt;p&gt;Learn how Crowdin helps you automate the localization of your Android or iOS mobile apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Recap: One Project or Two for Each App?
&lt;/h2&gt;

&lt;p&gt;The content of iOS and Android apps typically overlaps. In Crowdin, you can easily identify duplicate strings and ensure that the same content is not translated twice.&lt;/p&gt;

&lt;p&gt;You can hide duplicates. Translators will see and translate only one string rather than five identical ones, while the translation will be automatically applied to duplicates. Or choose to show duplicates - if the content is precisely different and requires different translations.&lt;/p&gt;

&lt;p&gt;You can enable &lt;em&gt;Hide duplicates&lt;/em&gt; option in the project settings.&lt;/p&gt;

&lt;p&gt;Unify placeholders for iOS and Android strings. This will help to treat more strings as duplicates. For example, &lt;em&gt;Hello, %s!&lt;/em&gt; (Android) and &lt;em&gt;Hello, %@!&lt;/em&gt; (iOS) will be matched as duplicates and will be shown to translators as &lt;em&gt;Hello, [%s]!&lt;/em&gt; On export, you’ll get files with the placeholders in their original formatting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihxc1390bo1w7s0qs9k4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihxc1390bo1w7s0qs9k4.png" alt="Crowdin settings, unify placeholders" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read more about how to &lt;a href="https://support.crowdin.com/advanced-project-setup/?utm_source=dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=mobile-apps-localization&amp;amp;utm_content=article#unify-placeholders"&gt;unify placeholders&lt;/a&gt; in Android and iOS localization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send Strings Directly from Your Design Tool
&lt;/h2&gt;

&lt;p&gt;Did you know that designers could send strings for translation with the keys and text segmentation rules while working on the prototypes?&lt;/p&gt;

&lt;p&gt;Your team can send strings for translation directly from a design tool like Figma, Sketch, or Adobe XD. Don’t worry about integrating texts into the codebase. Each string has its unique identifier, so development teams can integrate both sources and translated content in the codebase without any problems. &lt;/p&gt;

&lt;p&gt;If you set up automatic synchronization with your repo - these updates can be added automatically. In the same way, you can edit or create sources - to synchronize them back, for example, in GitHub integration - choose &lt;em&gt;sync sources&lt;/em&gt; in the integration settings.&lt;/p&gt;

&lt;p&gt;In addition, if the source files in Crowdin are of a different format than Android XML and Strings, engineers will be able to launch custom exporters and download the files in the appropriate format. They can use either Crowdin API or &lt;em&gt;crowdin download targets&lt;/em&gt; command of the CLI 3.4.0 version and higher.&lt;/p&gt;

&lt;p&gt;You can read more about custom file exporters and discover more smart ways of &lt;a href="https://blog.crowdin.com/2021/02/11/smart-ways-to-approach-mobile-app-localization/?utm_source=dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=mobile-apps-localization&amp;amp;utm_content=article"&gt;mobile app localization&lt;/a&gt; on our blog.&lt;/p&gt;

&lt;p&gt;Crowdin's integration with their design tools allows designers to preview translated mockups, customize them as needed, or notify engineers that elements must be scaled.&lt;/p&gt;

&lt;p&gt;Learn more about Crowdin plugins for design teams and how they help streamline &lt;a href="https://blog.crowdin.com/2021/01/27/best-practices-for-ui-localization/?utm_source=dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=mobile-apps-localization&amp;amp;utm_content=article"&gt;user interface localization&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update Localized Content with Over-the-Air Technology
&lt;/h2&gt;

&lt;p&gt;Sometimes rolling out new versions on the App Store or Google Play each time new translations arrive is not an option. When updates happen occasionally, waiting for app verification does not cause much trouble, but if you want to deliver translations to users much faster, you should consider using &lt;a href="https://blog.crowdin.com/2019/12/12/over-the-air-content-delivery-for-android-and-ios-apps-available-in-open-beta/?utm_source=dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=mobile-apps-localization&amp;amp;utm_content=article"&gt;Over-the-Air technology&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To use it, you need to create a distribution in your Crowdin project and install Crowdin SDK on the mobile app (&lt;a href="https://github.com/crowdin/mobile-sdk-android/?utm_source=dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=mobile-apps-localization&amp;amp;utm_content=article"&gt;Android&lt;/a&gt; or &lt;a href="https://github.com/crowdin/mobile-sdk-ios/?utm_source=dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=mobile-apps-localization&amp;amp;utm_content=article"&gt;iOS&lt;/a&gt;). Distribution is a CDN vault that mirrors the translated content of your project. You can create separate distributions for different files, or one distribution per project.&lt;/p&gt;

&lt;p&gt;Once you're done with the settings, you can create releases within the required distribution each time you'd like to update translations in your app and deliver translations to consumers immediately. You can discover even more ways to &lt;a href="https://blog.crowdin.com/2021/02/11/smart-ways-to-approach-mobile-app-localization/?utm_source=dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=mobile-apps-localization&amp;amp;utm_content=article"&gt; localize your mobile app&lt;/a&gt; on our blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localize Your Product with Crowdin
&lt;/h2&gt;

&lt;p&gt;Keep developing new features and improvements while translators receive new texts in real-time. Release multilingual versions for customers around the globe simultaneously. Register in &lt;a href="https://accounts.crowdin.com/register/?utm_source=dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=mobile-apps-localization&amp;amp;utm_content=article"&gt;Crowdin&lt;/a&gt; to try it out first hand, and if you’ve got any questions, &lt;a href="https://crowdin.com/contacts" rel="noopener noreferrer"&gt;contact&lt;/a&gt; the support team. &lt;/p&gt;

</description>
      <category>localization</category>
      <category>translation</category>
      <category>multilingual</category>
      <category>app</category>
    </item>
    <item>
      <title>Development and Localization Running in Parallel: Tips for Developers</title>
      <dc:creator>yuliyahrsh</dc:creator>
      <pubDate>Wed, 13 Jan 2021 14:51:03 +0000</pubDate>
      <link>https://dev.to/crowdin/development-and-localization-running-in-parallel-tips-for-developers-1nnh</link>
      <guid>https://dev.to/crowdin/development-and-localization-running-in-parallel-tips-for-developers-1nnh</guid>
      <description>&lt;p&gt;To make your mobile or web app, website, or other product-related content multilingual you first add localization to your workflow. The next step is to automate this process, so it won’t delay your next release. &lt;/p&gt;

&lt;p&gt;Localization should go in parallel with the development process. Meaning that each new text string created or modified by you is delivered to translators right away. Once translations are done, they are pushed to your repository as a merge/pull request automatically. This way you’ll have translations in time to deploy your next update.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LRpVrYhX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ihznpfce1rj461pe34pp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LRpVrYhX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/ihznpfce1rj461pe34pp.gif" alt="Localization in parallel with development" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read also: &lt;a href="https://blog.crowdin.com/2018/09/04/5-things-every-developer-should-know-about-localization/?utm_source=dev.to&amp;amp;utm_medium=Referral&amp;amp;utm_campaign=tips-for-developers"&gt;5 things developers should know about localization&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to start with automating localization?
&lt;/h2&gt;

&lt;p&gt;What are the first steps? How to set up the automated workflow? &lt;/p&gt;

&lt;p&gt;We’ve prepared a small plan for you:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a project in Crowdin
&lt;/h3&gt;

&lt;p&gt;Select the source language (the one you’re translating from) and target languages (the ones you’ll be translating your content into).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Choose your automation approach
&lt;/h3&gt;

&lt;p&gt;Manage your files using in-built integrations with tools like GitLab, GitHub, Bitbucket, or Azure Repos. You can also set up a more custom workflow using Crowdin’s CLI, API, or webhooks. All the content updates can be performed automatically on the configured schedule. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Translate your files in Crowdin
&lt;/h3&gt;

&lt;p&gt;Invite your translators and proofreaders to the project or select a translation agency for the translation tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Merge translations into your main branch
&lt;/h3&gt;

&lt;p&gt;The system will automatically add translations to the defined folder on your side. Check the translated files and merge them into the main branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported File Formats
&lt;/h2&gt;

&lt;p&gt;Crowdin supports the most common localization formats (like JSON, XML, XLIFF, INI, and so on) as well as custom file formats. Feel free to upload files into your project and start the localization. View all &lt;a href="https://crowdin.com/page/integrations#nav-formats"&gt;supported file formats in Crowdin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our team can create pre/post processors to customize the file import and export settings and add the support of the custom placeholders for Organization Plans. So, for example, you’ll be able to export translations in Android XML and iOS Strings formats for one source file from your Crowdin project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versions Management in Localization
&lt;/h2&gt;

&lt;p&gt;Integrate localization into your development process. Release new features and product versions for customers speaking different languages simultaneously.&lt;/p&gt;

&lt;p&gt;You can create branches for each version of your product in your Crowdin project. Translate only the new text in the version branch and hide the duplicates that you have already translated on the main branch.&lt;/p&gt;

&lt;p&gt;We have a special option for duplicates – Show (recommended for versions). This option allows hiding duplicated strings only between versions – so translators will see only the difference compared to the main branch.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GezIu8Zt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/jl9ilgw4scb1xkygbdnj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GezIu8Zt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/jl9ilgw4scb1xkygbdnj.png" alt="Hide duplicate strings" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrate content from your repository on GitLab, GitHub, Bitbucket, or Azure Repos
&lt;/h3&gt;

&lt;p&gt;Once you integrate your Crowdin projects with your repository, the translation team will be able to work on the localization in parallel with your development process.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the branches and define the path for the source files and translations – the same branches will be created in your Crowdin project immediately.&lt;/li&gt;
&lt;li&gt;Set the synchronization for the new branches. Once you create the new branch on your repository, it will be added to your project automatically – there is no need to define each new branch manually.&lt;/li&gt;
&lt;li&gt;The integration is already configured? Great! The system will create the service branch on your repository to send the translations there. Check the new pull/merge request before merging to the main branch.&lt;/li&gt;
&lt;li&gt;Remove the service branch after merging to the main branch – integration will create the new service branch during the next synchronization.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Crowdin action for GitHub
&lt;/h3&gt;

&lt;p&gt;Integrate localization into your workflow with the help of &lt;a href="https://blog.crowdin.com/2020/04/30/add-localization-to-your-github-workflows-with-crowdin-action/"&gt;Crowdin  Action for GitHub&lt;/a&gt;. View the main settings, specify which action you need to include in your GitHub workflow, and define the preferred branch for the translations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crowdin API
&lt;/h3&gt;

&lt;p&gt;Define your localization workflow and integrate it into the development process with the selected API requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new content for translation to your Crowdin project.&lt;/li&gt;
&lt;li&gt;Create tasks for translation or proofreading.&lt;/li&gt;
&lt;li&gt;Check the translation status for each language or each file.&lt;/li&gt;
&lt;li&gt;Export the translated files from your Crowdin project.&lt;/li&gt;
&lt;li&gt;Explore the API for Crowdin and Crowdin Enterprise to select the requests beneficial for your workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read more about &lt;a href="https://blog.crowdin.com/2020/08/05/announcing-the-new-API-2.0/"&gt;API for your localization project&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Console client (CLI)
&lt;/h3&gt;

&lt;p&gt;Connect cross-platform Crowdin CLI directly to your repository and never deal with localization files manually again.&lt;/p&gt;

&lt;p&gt;Use the configuration file to run the commands – it contains a description of all resources: files for localization and the locations of the corresponding translations. Sure, the first setup can take a bit more time, but all the next actions would be done just in a few clicks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhooks
&lt;/h3&gt;

&lt;p&gt;Webhooks will notify you about the main events from your Crowdin project, like completed translations or proofreading.&lt;/p&gt;

&lt;p&gt;Create webhooks in the Project Settings &amp;gt; API tab &amp;gt; Webhooks or use the API tool for this, and select the actions that you would like to get the notifications about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localize your software and other content on Crowdin
&lt;/h2&gt;

&lt;p&gt;Create your &lt;a href="https://crowdin.com/?utm_source=dev.to&amp;amp;utm_medium=Referral&amp;amp;utm_campaign=tips-for-developers"&gt;localization project on Crowdin&lt;/a&gt;, invite translators, and automate updates for source and translated content between Crowdin and your repo. Start receiving translations right in time for the next release.&lt;/p&gt;




&lt;p&gt;How do you handle localization in your company? Would love to see your stories in the comments below 👇&lt;/p&gt;

</description>
      <category>localization</category>
      <category>translation</category>
    </item>
    <item>
      <title>Webinar: continuous localization means no more content copy-pasting for devs</title>
      <dc:creator>Khrystyna Humenna</dc:creator>
      <pubDate>Tue, 24 Nov 2020 14:42:25 +0000</pubDate>
      <link>https://dev.to/crowdin/webinar-continuous-localization-means-no-more-content-copy-pasting-5bo9</link>
      <guid>https://dev.to/crowdin/webinar-continuous-localization-means-no-more-content-copy-pasting-5bo9</guid>
      <description>&lt;p&gt;Join Crowdin this Thursday at 11 AM EST / 5 PM CET for a live webinar on continuous localization. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This free webinar is for you, if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your product is already multilingual, but it takes too long to copy-paste sources and translations&lt;/li&gt;
&lt;li&gt;You keep translations in a spreadsheet&lt;/li&gt;
&lt;li&gt;It's hard to keep up with content updates, so sometimes you end up with untranslated strings in your app&lt;/li&gt;
&lt;li&gt;Your task is to add multilingual support to your product&lt;/li&gt;
&lt;li&gt;Release is often delayed due to localization&lt;/li&gt;
&lt;li&gt;You team first creates a new feature and only after sends new texts to translators&lt;/li&gt;
&lt;li&gt;You like webinars for developers :)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save your spot to learn how to release localized versions faster, translate several branches simultaneously, automate content updates, both for sources and translations, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Register now:&lt;/strong&gt; &lt;a href="https://bit.ly/35YZuvf"&gt;https://bit.ly/35YZuvf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get 1 hour of expert training and get answers to your questions on localization. Submit your questions via the registration form or ask them during the webinar on November 26.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can’t make the webinar?&lt;/strong&gt; No worries. Register now for a link to the recording: &lt;a href="https://bit.ly/35YZuvf"&gt;https://bit.ly/35YZuvf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About Crowdin:&lt;/strong&gt;&lt;br&gt;
Crowdin is a cloud-based localization management software. With 1.7M registered users and 100K+ localization projects from all over the world. Crowdin helps companies streamline their growth by reaching people who speak different languages. &lt;/p&gt;

&lt;p&gt;Trusted by GitHub, Avast, GitLab, Buffer, Calendly, and Wrike, and other companies creating multilingual products.&lt;/p&gt;

&lt;p&gt;Crowdin integrates with GitHub, GitLab, Bitbucket, Azure Repos, and 40+tools. You can use our API, CLI, webhooks, apps to customize your experience to your needs.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>localization</category>
      <category>webinar</category>
    </item>
  </channel>
</rss>
