{"id":16401,"date":"2022-07-05T10:00:32","date_gmt":"2022-07-05T08:00:32","guid":{"rendered":"https:\/\/phraseapp.com\/blog\/?p=3090"},"modified":"2024-11-04T11:14:38","modified_gmt":"2024-11-04T10:14:38","slug":"angular-localization-i18n","status":"publish","type":"post","link":"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/","title":{"rendered":"The Ultimate Guide to Angular Localization"},"content":{"rendered":"<p>Among the big 3 web UI libraries (React, Vue, Angular), Google&#8217;s Angular stands out as a mature, batteries-included framework. With its CLI, TypeScript support, modules, dependency injectors, reactive programming, and string conventions, Angular is built for projects that can scale while maintaining structure.<br \/>\nOf course, you\u2019re here for Angular internationalization (i18n), and we\u2019ve got you covered. It&#8217;s no surprise that Angular has robust built-in i18n support. In this step-by-step tutorial on Angular localization and internationalization, we\u2019ll walk you through how to install, configure, and localize your Angular applications using the first-party @angular\/localize package.<br \/>\nThis is a hands-on guide, so we\u2019ll hit the ground running with a quick demo app and localize it. Let\u2019s get started \ud83d\ude42<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_69_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Overview<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#library-versions-used\" title=\"Library versions used\">Library versions used<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#the-demo-app\" title=\"The demo app\">The demo app<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#attributions\" title=\"Attributions\">Attributions<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#demo-components\" title=\"Demo components\">Demo components<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-localize-my-angular-app-with-the-first-party-localize-package\" title=\"How do I localize my Angular app with the first-party localize package?\">How do I localize my Angular app with the first-party localize package?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-install-the-angular-localize-package\" title=\"How do I install the Angular localize package?\">How do I install the Angular localize package?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-configure-the-supported-locales-in-my-app\" title=\"How do I configure the supported locales in my app?\">How do I configure the supported locales in my app?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#what-is-the-end-to-end-translation-workflow\" title=\"What is the end-to-end translation workflow?\">What is the end-to-end translation workflow?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-configure-my-production-server-for-a-multilingual-app\" title=\"How do I configure my production server for a multilingual app?\">How do I configure my production server for a multilingual app?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-speed-up-my-localization-development-workflow\" title=\"How do I speed up my localization development workflow?\">How do I speed up my localization development workflow?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-can-i-detect-a-visitors-locale\" title=\"How can I detect a visitor\u2019s locale?\">How can I detect a visitor\u2019s locale?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-build-a-language-switcher-for-my-app\" title=\"How do I build a language switcher for my app?\">How do I build a language switcher for my app?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-translate-strings-in-my-angular-app\" title=\"How do I translate strings in my Angular app?\">How do I translate strings in my Angular app?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-work-with-dynamic-values-in-my-translated-strings\" title=\"How do I work with dynamic values in my translated strings?\">How do I work with dynamic values in my translated strings?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-work-with-plurals-in-my-translations\" title=\"How do I work with plurals in my translations?\">How do I work with plurals in my translations?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-localize-numbers-in-my-angular-app\" title=\"How do I localize numbers in my Angular app?\">How do I localize numbers in my Angular app?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-localize-dates-in-my-angular-app\" title=\"How do I localize dates in my Angular app?\">How do I localize dates in my Angular app?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#how-do-i-use-phrase-to-localize-my-angular-app\" title=\"How do I use Phrase to localize my Angular app?\">How do I use Phrase to localize my Angular app?<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#what-alternative-localization-libraries-are-available-for-angular\" title=\"What alternative localization libraries are available for Angular?\">What alternative localization libraries are available for Angular?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#ngx-translate\" title=\"ngx-translate\">ngx-translate<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#transloco\" title=\"Transloco\">Transloco<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#i18next\" title=\"i18next\">i18next<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/#wrapping-up-our-angular-l10n-tutorial\" title=\"Wrapping up our Angular l10n tutorial\">Wrapping up our Angular l10n tutorial<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"library-versions-used\"><\/span>Library versions used<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>In this article, we\u2019re using the following NPM packages (versions in parentheses):<\/p>\n<ul>\n<li>Angular (13.3)\u2014our UI framework<\/li>\n<li>@angular\/localize (13.3)\u2014Angular\u2019s first-party i18n library<\/li>\n<li>Tailwind CSS (3.0)\u2014used for styling and optional here (but just FYI)<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"the-demo-app\"><\/span>The demo app<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Meet <em>azcadea<\/em>, a fictional e-commerce store that specializes in retro arcade cabinets, desperate for localization.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-17301 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/before-localization-1.png\" alt=\"Our little demo app before localization\" width=\"1170\" height=\"1314\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/before-localization-1.png 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/before-localization-1-267x300.png 267w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/before-localization-1-912x1024.png 912w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/before-localization-1-768x863.png 768w\" sizes=\"(max-width: 1170px) 100vw, 1170px\" \/><\/p>\n<p>\ud83d\udd17 <em>Resource \u00bb<\/em> Get our starter demo app code from <a href=\"https:\/\/github.com\/PhraseApp-Blog\/angular-i18n-2022\/tree\/start\">GitHub<\/a>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"attributions\"><\/span>Attributions<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Shoutouts to the following people for making the assets we\u2019ve used in this demo available for free.<\/p>\n<ul>\n<li>The brand icon is <a href=\"https:\/\/thenounproject.com\/icon\/arcade-2298442\/\"><em>arcade<\/em> by Flatart on thenounproject.com<\/a>.<\/li>\n<li>The arcade cabinet photos are from Unsplash:<\/li>\n<li style=\"list-style-type: none;\">\n<ul>\n<li><a href=\"https:\/\/unsplash.com\/photos\/0X5Hl2Xbj6s\">Mappy cabinet by Daniela Scherenberg Diaz<\/a><\/li>\n<li><a href=\"https:\/\/unsplash.com\/photos\/K-osgBT0WLE\">Guitar hero cabinet by Senad Palic<\/a><\/li>\n<li><a href=\"https:\/\/unsplash.com\/photos\/EfqQLK5T_lE\">Super Mario Bros. cabinet by Rafael Hoyos Weht<\/a><\/li>\n<li><a href=\"https:\/\/unsplash.com\/photos\/mdZA_AWrAjA\">\u00a0Pac-man cabinet by Darya Tryfanava<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h3><span class=\"ez-toc-section\" id=\"demo-components\"><\/span>Demo components<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Our demo is largely presentational to allow us to focus on the i18n. Its hierarchy looks like the following.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">.\n\u2514\u2500\u2500 src\/\n    \u2514\u2500\u2500 app\/\n        \u2502\n        \u2502   # Defines routes to \/ and \/about\n        \u251c\u2500\u2500 app-routing.module.ts\n        \u2502\n        \u2502   # Root component, with header, footer,\n        \u2502   # and &lt;router-outlet&gt;\n        \u251c\u2500\u2500 app.component.html\n        \u251c\u2500\u2500 app.component.ts\n        \u2502\n        \u2502   # Top and bottom bars\n        \u251c\u2500\u2500 layout\/\n        \u2502   \u251c\u2500\u2500 navbar\/\n        \u2502   \u2502   \u2514\u2500\u2500 (html\/ts)\n        \u2502   \u2514\u2500\u2500 footer\/\n        \u2502       \u2514\u2500\u2500 (html\/ts)\n        \u2502\n        \u2514\u2500\u2500 pages\/\n            \u2502\n            \u2502   # Renders list of arcade cabinets\n            \u251c\u2500\u2500 home\/\n            \u2502   \u2514\u2500\u2500 (html\/ts)\n            \u2502\n            \u2502   # Simple about page with text\n            \u2514\u2500\u2500 about\/\n                \u2514\u2500\u2500 (html\/ts)\n<\/pre>\n<p>We\u2019re using Angular\u2019s router to provide two \u201cpages\u201d, Home and About.<\/p>\n<div><strong style=\"color: #ff6600;\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17302\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/routing.gif\" alt=\"Switching between our home and about pages\" width=\"600\" height=\"737\" \/><\/strong><\/div>\n<p>Let\u2019s take a look at the Home component in a bit more detail.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\" data-enlighter-group=\"6837c630-ba09-4a73-bb1b-e2354b1e0b6d\" data-enlighter-title=\"src\/app\/home\/home.component.ts\" data-enlighter-linenumbers=\"false\">import { Component } from '@angular\/core';\nimport { Cabinet } from '..\/..\/cabinet.model';\n@Component({\n  selector: 'app-home',\n  templateUrl: '.\/home.component.html',\n})\nexport class HomeComponent {\n  \/\/ Mock data that we would probably fetch from an API\n  cabinets: Cabinet[] = [\n    {\n      imageUrl: '\/assets\/cabinets\/mappy.jpg',\n      name: 'Mappy Mini-cabinet',\n      description: 'The original cat and mouse game.',\n      addedAt: new Date(2021, 12, 22),\n      storeCount: 2,\n      price: 27.99,\n    },\n    \/\/ \u2026\n  ];\n  altFor(cabinet: Cabinet): string {\n    \/\/ Hard-coded in English\n    return `Image of ${cabinet.name}`;\n  }\n}\n<\/pre>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> In <a href=\"https:\/\/github.com\/PhraseApp-Blog\/angular-i18n-2022\/blob\/start\/src\/app\/pages\/home\/home.component.ts\">our actual code on GitHub<\/a> we\u2019re using a little injected service to provide the cabinet data. We\u2019re omitting that code here for brevity.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-group=\"1c96e912-ff2a-4741-bc5b-a62d68fe57e7\" data-enlighter-title=\"src\/app\/home\/home.component.html\" data-enlighter-linenumbers=\"false\">&lt;!-- Tailwind CSS classes omitted for brevity--&gt;\n&lt;div&gt;\n  &lt;div *ngFor=\"let cabinet of cabinets\"&gt;\n    &lt;div&gt;\n      &lt;img\n        [alt]=\"altFor(cabinet)\"\n        [src]=\"cabinet.imageUrl\"\n      \/&gt;\n    &lt;\/div&gt;\n    &lt;div&gt;\n      &lt;div&gt;\n        &lt;h3&gt;{{ cabinet.name }}&lt;\/h3&gt;\n        &lt;!-- Currency symbol is hard-coded --&gt;\n        &lt;p&gt;${{ cabinet.price }}&lt;\/p&gt;\n      &lt;\/div&gt;\n      &lt;p&gt;{{ cabinet.description }}&lt;\/p&gt;\n      &lt;div&gt;\n        &lt;span&gt;\n          &lt;!-- Hard-coded in English --&gt;\n          Available at {{ cabinet.storeCount }} outlets\n        &lt;\/span&gt;\n        &lt;button&gt;\n          &lt;!-- Hard-coded in English --&gt;\n          Add to cart\n        &lt;\/button&gt;\n      &lt;\/div&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n&lt;\/div&gt;\n<\/pre>\n<p>This should all be bread-and-butter Angular for you. Note, however, that all of our UI strings are hard-coded in English. We want our app to be available in multiple languages, so we need to internationalize and localize it.<\/p>\n<p>\ud83d\udd17 <em>Resource \u00bb<\/em> Get all of our starter demo app code from <a href=\"https:\/\/github.com\/PhraseApp-Blog\/angular-i18n-2022\/tree\/start\">GitHub<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-my-angular-app-with-the-first-party-localize-package\"><\/span>How do I localize my Angular app with the first-party localize package?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The basic recipe is:<\/p>\n<ol>\n<li>Install and configure the @angular\/localize package<\/li>\n<li>Mark strings as localizable in your components<\/li>\n<li>Extract localized strings for translation<\/li>\n<li>Translate strings to the locales you want to support<\/li>\n<li>Ensure your dates and numbers are localized<\/li>\n<li>Build your app to merge in your supported locales, and deploy<\/li>\n<\/ol>\n<p>We\u2019ll go over all these steps in detail in the following sections.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-install-the-angular-localize-package\"><\/span>How do I install the Angular localize package?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>All we have to do is use the Angular CLI to add the @angular\/localize library. From the project root, let\u2019s run the following from the command line.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">$ ng add @angular\/localize\n<\/pre>\n<p>This will install the @angular\/localize NPM package for us, adding it to our <code>project.json<\/code>.<\/p>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> The command will also import the <a href=\"https:\/\/v17.angular.io\/api\/localize\/init\/$localize\">$localize template tag<\/a> in <code>polyfill.ts<\/code>. We\u2019ll use <code>$localize<\/code> directly a bit later. For now, just know that Angular needs it to handle strings marked for translation in our templates.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-configure-the-supported-locales-in-my-app\"><\/span>How do I configure the supported locales in my app?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A special <code>i18n<\/code> section in our project\u2019s <code>angular.json<\/code> file is reserved for configuring the @angular\/localize library. We can use this section to define our app\u2019s supported locales.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"aef9e54c-6c71-450b-81e6-0c1b9591e31b\" data-enlighter-title=\"angular.json\" data-enlighter-highlight=\"12,13,14,15,16,17,18,19,20,21\">{\n  \"$schema\": \".\/node_modules\/@angular\/cli\/lib\/config\/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"azcadea\": {\n        \/\/ \u2026\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"i18n\": {\n        \/\/ The locale we use in development, defaults to en-US\n        \"sourceLocale\": \"en-CA\",\n        \/\/ All other locales our app supports\n        \"locales\": {\n          \"ar\": {\n            \"translation\": \"src\/locale\/messages.ar.xlf\"\n          }\n        }\n      },\n      \"architect\": {\n        \/\/ \u2026\n      }\n    }\n  },\n  \"defaultProject\": \"azcadea\"\n}\n<\/pre>\n<p>By default, Angular will assume that we use the United States English locale (<code>en-US<\/code>) when developing. This is the <code>sourceLocale<\/code>, where we translate messages <em>from<\/em>. Each locale we want to translate <em>to<\/em> goes under the <code>locales<\/code> key.<br \/>\nI changed the <code>sourceLocale<\/code> to Canadian English (<code>en-CA<\/code>) above and added Arabic (<code>ar<\/code>) as a supported locale. Feel free to use any locales you want here. We need to include the path to a <code>translation<\/code> file for each locale other than the source (more on translation files a bit later).<\/p>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> To identify a locale in Angular, use a <a href=\"https:\/\/en.wikipedia.org\/wiki\/IETF_language_tag\">BCP 47 language tag<\/a> (like <code>en<\/code> for English) for the language, followed by an optional country code (like <code>-US<\/code> for the United States). <a href=\"https:\/\/github.com\/unicode-org\/cldr-json\/blob\/main\/cldr-json\/cldr-core\/availableLocales.json\">This CLDR list of locales<\/a> might help.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"what-is-the-end-to-end-translation-workflow\"><\/span>What is the end-to-end translation workflow?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Now that we\u2019ve installed and configured the @angular\/localize package, let\u2019s run through an example of how we would localize a string of text in our app.<br \/>\nLet\u2019s start with our app name in the navbar.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"75ec8514-f8dc-463b-97ff-fd7920a3590d\" data-enlighter-title=\"src\/app\/layout\/navbar\/navbar.component.html\" data-enlighter-highlight=\"5\">&lt;nav&gt;\n  &lt;div&gt;\n    &lt;img alt=\"Logo\" src=\"assets\/logo.svg\" \/&gt;\n    &lt;h1&gt;azcadea&lt;\/h1&gt;\n  &lt;\/div&gt;\n  &lt;!-- \u2026 --&gt;\n&lt;\/nav&gt;\n<\/pre>\n<p>We&#8217;ll add a special <code>i18n<\/code> attribute to the <code>&lt;h1&gt;<\/code> element, which marks its text for translation.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"fa06c924-0b83-4043-a62c-2a86c0e6a363\" data-enlighter-title=\"src\/app\/layout\/navbar\/navbar.component.html\" data-enlighter-highlight=\"5\">&lt;nav&gt;\n  &lt;div&gt;\n    &lt;img alt=\"Logo\" src=\"assets\/logo.svg\" \/&gt;\n    &lt;h1 i18n&gt;azcadea&lt;\/h1&gt;\n  &lt;\/div&gt;\n  &lt;!-- \u2026 --&gt;\n&lt;\/nav&gt;\n<\/pre>\n<p>Next, let\u2019s run the <code>extract-i18n<\/code> CLI command from our project root to pull this marked string into a translation file.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">$ ng extract-i18n --output-path src\/locale\n<\/pre>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> For better organization, we add the optional <code>--output-path<\/code> argument to the command, ensuring that all of our translation files go under the <code>src\/locale<\/code> directory.<\/p>\n<p>A new file should have appeared in our project:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"43e48982-81c8-464e-a5da-c666175187c2\" data-enlighter-title=\"src\/locale\/messages.xlf\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?&gt;\n&lt;xliff version=\"1.2\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;\n  &lt;file source-language=\"en-CA\" datatype=\"plaintext\" original=\"ng2.template\"&gt;\n    &lt;body&gt;\n      &lt;trans-unit id=\"7285274704628585281\" datatype=\"html\"&gt;\n        &lt;source&gt;azcadea&lt;\/source&gt;\n        &lt;context-group purpose=\"location\"&gt;\n          &lt;context context-type=\"sourcefile\"&gt;src\/app\/layout\/navbar\/navbar.component.html&lt;\/context&gt;\n          &lt;context context-type=\"linenumber\"&gt;11&lt;\/context&gt;\n        &lt;\/context-group&gt;\n      &lt;\/trans-unit&gt;\n    &lt;\/body&gt;\n  &lt;\/file&gt;\n&lt;\/xliff&gt;\n<\/pre>\n<p>The <code>ng extract-i18n<\/code> command combed through our app, pulled out the string we marked with <code>i18n<\/code>, and put it in a <code>&lt;trans-unit&gt;<\/code> (translation unit) in the new <code>messages.xlf<\/code> file.<\/p>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> XLIFF stands for XML Localization Interchange File Format and is a common standard for translation files. <a href=\"https:\/\/en.wikipedia.org\/wiki\/XLIFF\">Read more about the XLIFF format on Wikipedia<\/a>.<\/p>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> You can <a href=\"https:\/\/v17.angular.io\/guide\/i18n-common-translation-files#change-the-source-language-file-format\">use formats other than XLIFF<\/a> if you want.<\/p>\n<p>To translate our string into Arabic, let\u2019s copy the generated <code>messages.xlf<\/code> file to a new file in the same directory and name it <code>messages.ar.xlf<\/code>. We can name the copy anything we want as long as it matches the file path we specified in <code>angular.json<\/code> above. Now to translate:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"e18cf521-e0b8-4b9e-9911-96bf19ef8c7d\" data-enlighter-title=\"src\/locale\/messages.ar.xlf\" data-enlighter-highlight=\"7,8\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?&gt;\n&lt;xliff version=\"1.2\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;\n  &lt;file source-language=\"en-CA\" datatype=\"plaintext\" original=\"ng2.template\"&gt;\n    &lt;body&gt;\n      &lt;trans-unit id=\"7285274704628585281\" datatype=\"html\"&gt;\n        &lt;source&gt;azcadea&lt;\/source&gt;\n        &lt;!-- Add translation within &lt;target&gt; tags --&gt;\n        &lt;target&gt;\u0623\u0632\u0643\u064a\u062f\u064a\u0627&lt;\/target&gt;\n        &lt;!-- \/\/\u2026 --&gt;\n      &lt;\/trans-unit&gt;\n    &lt;\/body&gt;\n  &lt;\/file&gt;\n&lt;\/xliff&gt;\n<\/pre>\n<p>Translation in place, the final step is to merge our new translation into a build. Providing a <code>--localize<\/code> argument to the <code>ng build<\/code> CLI command should do just the trick.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">$ ng build --localize\n<\/pre>\n<p>Each of our configured locales will generate a unique copy of our app with that locale baked in. Take a look under the <code>dist<\/code> directory and you should see something like the following.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">.\n\u2514\u2500\u2500 dist\/\n    \u2514\u2500\u2500 azcadea\/\n        \u251c\u2500\u2500 ar\/\n        \u2502   \u251c\u2500\u2500 assets\/\n        \u2502   \u251c\u2500\u2500 index.html\n        \u2502   \u2514\u2500\u2500 \u2026\n        \u2514\u2500\u2500 en-CA\/\n            \u251c\u2500\u2500 assets\/\n            \u251c\u2500\u2500 index.html\n            \u2514\u2500\u2500 \u2026\n<\/pre>\n<p>We can use a simple HTTP server to test these builds. I\u2019ll use the popular <code>http-server<\/code> here.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\"># From the project root\n$ npx http-server dist\/azcadea\n<\/pre>\n<p>Now if we open <code>http:\/\/localhost:8080\/en-CA<\/code> in our browser, we\u2019ll see our app exactly as it was in development. And if we open <code>http:\/\/localhost:8080\/ar<\/code> we\u2019ll see that our brand name has been translated into Arabic.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17303\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/brand-ar.png\" alt=\"Our app's name in Arabic\" width=\"516\" height=\"130\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/brand-ar.png 516w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/brand-ar-300x76.png 300w\" sizes=\"(max-width: 516px) 100vw, 516px\" \/><\/p>\n<p>\u270b <em>Heads up \u00bb<\/em> If you\u2019re working along with us from the starter demo, you&#8217;ll notice that images aren\u2019t showing up in the build. Localized builds will have each have their own <code>assets<\/code> subdirectory, e.g. <code>\/en-CA\/assets<\/code>, <code>\/ar\/assets<\/code>. So any URLs in our app that having a leading forward slash, like <code>\/assets\/foo.jpg<\/code>, will break in built versions. A quick fix is to remove the leading forward slash in such URLs, so <code>\/assets\/foo.jpg<\/code> becomes <code>assets\/foo.jpg<\/code>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-configure-my-production-server-for-a-multilingual-app\"><\/span>How do I configure my production server for a multilingual app?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>There are two main considerations when configuring a production server for our localized Angular app.<\/p>\n<ol>\n<li>Serve the correct locale to the visitor. We could redirect the root route <code>\/<\/code> to our default locale, <code>\/en-CA<\/code> in our case. Alternatively, we could try to detect the browser\u2019s preferred language and redirect to the closest locale (more on that a bit later).<\/li>\n<li>Serve the <code>index.html<\/code> whenever <em>any<\/em> localized URI is requested. So if we get a request for <code>\/ar\/about<\/code>, we need to send back the <code>\/ar\/about\/index.html<\/code> file. Our <code>index.html<\/code> file contains our single-page Angular app, which will handle the <code>\/about<\/code> route itself.<\/li>\n<\/ol>\n<p>\ud83d\uddd2 <em>Note \u00bb <\/em>Angular builds an entire copy of our app for each locale we support. So switching locales essentially means that when a user wants the Arabic version of our app, we serve them the entire Arabic copy. In our case this means that we redirect them to <code>\/ar<\/code>, which is the subdirectory (and URI) where our Arabic version lives.<\/p>\n<p>Of course, the implementation details will depend both on your production server and the needs of your app. Here\u2019s a quick and dirty implementation in <a href=\"http:\/\/expressjs.com\/\">Express<\/a> that we can use for testing our production builds in development.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"9ee42394-623e-4037-b34a-5af26e2ff7b8\" data-enlighter-title=\"serve-prod-test.js\">\/*******************************************************\n * \u26a0\ufe0f This server is for testing production builds in a\n * development environment. It has not been checked for\n * security. Please do not use in production!\n *****************************************************\/\nconst path = require('path');\nconst express = require('express');\nconst port = 8080;\nconst rootDir = path.join(__dirname, 'dist\/azcadea');\nconst locales = ['en-CA', 'ar'];\nconst defaultLocale = 'en-CA';\nconst server = express();\n\/\/ Serve static files (HTML, CSS, etc.)\nserver.use(express.static(rootDir));\n\/\/ Always serve the index.html file in a locale\n\/\/ build's directory e.g. \/en-CA\/foo will return\n\/\/ \/en-CA\/index.html. This allows Angular to handle\n\/\/ the \/foo route itself.\nlocales.forEach((locale) =&gt; {\n  server.get(`\/${locale}\/*`, (req, res) =&gt; {\n    res.sendFile(\n      path.resolve(rootDir, locale, 'index.html')\n    );\n  });\n});\n\/\/ Redirect \/ to \/en-CA\nserver.get('\/', (req, res) =&gt;\n  res.redirect(`\/${defaultLocale}`)\n);\nserver.listen(port, () =&gt;\n  console.log(`App running at port ${port}\u2026`)\n);\n<\/pre>\n<p>Now can build our app and run our production test server.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">$ ng build --localize --configuration production\n$ node serve-prod-test.js\n<\/pre>\n<p>If we visit <code>http:\/\/localhost:8080<\/code> in our browser, we should be redirected to <code>http:\/\/localhost:8080\/en-CA<\/code> and see the English version of our app. And if we enter <code>localhost\/ar\/about<\/code> directly in our browser\u2019s address bar, we should see the Arabic version of our about page. All routes accounted for \ud83d\ude42<\/p>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> Depending on your configuration, you might want to adjust each locale\u2019s <code>&lt;base href&gt;<\/code> to control relative links. Angular has a handy <code>baseHref<\/code> option when configuring a locale that takes care of this. <a href=\"https:\/\/v17.angular.io\/guide\/i18n-common-deploy\">Read about it in the official docs<\/a>.<\/p>\n<p>\ud83d\udd17 <em>Resource \u00bb<\/em> The Angular documentation has <a href=\"https:\/\/angular.io\/guide\/i18n-common-deploy#configure-a-server\">listings for Nginx and Apache server configurations<\/a> for production Angular apps.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-speed-up-my-localization-development-workflow\"><\/span>How do I speed up my localization development workflow?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>It can be a pain to build our app every time we want to test a change to one of our localizations. One way to alleviate this is to add a development server configuration for each of our locales. This allows us to run our dev server, with hot reloading as usual, while serving a locale-specific version of our app. Let\u2019s add an Arabic version by modifying our <code>angular.json<\/code> file.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"7f8e3d99-9e6d-4efa-9e0f-8c8110fc0fc1\" data-enlighter-title=\"angular.json\" data-enlighter-highlight=\"27,28,30,31,32,33,34,35,36,37,38,39,40,41,42,55,56,57,58,59\">{\n  \"$schema\": \".\/node_modules\/@angular\/cli\/lib\/config\/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"azcadea\": {\n      \/\/ \u2026\n      \"architect\": {\n        \"build\": {\n           \/\/ \u2026\n          },\n          \"configurations\": {\n            \"production\": {\n              \/\/ \u2026\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true,\n              \/\/ Localizes default dev build to Canadian English\n              \"localize\": [\"en-CA\"]\n            },\n            \/\/ New build configuration with Arabic localization\n            \"ar\": {\n              \/\/ Options optimize for dev environment\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true,\n              \/\/ This is the real magic\n              \"localize\": [\"ar\"]\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit\/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"azcadea:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"azcadea:build:development\"\n            },\n            \/\/ New dev server configuration that targets\n            \/\/ Arabic build we added above\n            \"ar\": {\n              \"browserTarget\": \"azcadea:build:ar\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \/\/ \u2026\n        },\n        \"test\": {\n          \/\/ \u2026\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"azcadea\"\n}\n<\/pre>\n<p>Now we can run the following command line at the project root to work with an Arabic development version of our app.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">$ ng serve --configuration ar\n<\/pre>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> Running <code>ng serve<\/code> without the <code>--configuration<\/code> argument will start the dev server with the English version of our app.<\/p>\n<p>We can shorten this a bit by adding it as an NPM script to our <code>package.json<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"2e70d4dc-c329-43b4-8d10-a1f2bc1e2354\" data-enlighter-title=\"package.json\" data-enlighter-highlight=\"7,8\">{\n  \"name\": \"azcadea\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \/\/ Our new script\n    \"start:ar\": \"ng serve --configuration ar\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n  },\n  \"private\": true,\n  \/\/ \u2026\n}\n<\/pre>\n<p>Now we can run <code>npm run start:ar<\/code> from the command line to run our app in development with Arabic localization.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-can-i-detect-a-visitors-locale\"><\/span>How can I detect a visitor\u2019s locale?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>People can set their preferred locales in their browsers, and these locales are sent as an <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Accept-Language\">Accept-Language HTTP header<\/a> to servers along with normal <code>GET<\/code> requests. We can use the <code>Accept-Language<\/code> header to resolve the initial locale on the server for our Angular apps. Let\u2019s update our production test Express server to add locale detection and serve a version of our app matching the closest supported locale.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-group=\"d8650f13-9da6-461c-898a-3cec95ba7986\" data-enlighter-title=\"serve-prod-test.js\" data-enlighter-highlight=\"8,20,21,22,23,24,26\" data-enlighter-linenumbers=\"false\">\/*******************************************************\n * \u26a0\ufe0f This server is for testing production builds in a\n * development environment. It has not been checked for\n * security. Please do not use in production!\n *****************************************************\/\nconst path = require('path');\nconst express = require('express');\nconst matchSupportedLocales = require('.\/match-supported-locales');\n\/\/ \u2026\nconst locales = ['en-CA', 'ar'];\nconst defaultLocale = 'en-CA';\nconst server = express();\n\/\/ \u2026\nserver.get('\/', (req, res) =&gt; {\n  const closestSupportedLocale = matchSupportedLocales(\n    req.acceptsLanguages(),\n    locales,\n    defaultLocale\n  );\n  return res.redirect(`\/${closestSupportedLocale}`);\n});\n\/\/ \u2026\n<\/pre>\n<p>When a visitor hits our root route (<code>\/<\/code>), we try to match one of our supported locales to one of their accepted locales, retrieved from Express\u2019 helpful <a href=\"https:\/\/expressjs.com\/en\/api.html#req.acceptsLanguages\">req.acceptsLanguages()<\/a>. The function returns the locales in the incoming <code>Accept-Language<\/code> header as an array. Our new <code>matchSupportedLocales()<\/code> function uses this array, along with our supported locales and our default locale, to make the best match.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-group=\"23bbcfbb-c23e-4a62-8a63-b88e326c0cb2\" data-enlighter-title=\"match-supported-locale.js\" data-enlighter-linenumbers=\"false\">function matchSupportedLocale(\n  acceptsLocales,\n  supportedLocales,\n  defaultLocale\n) {\n  return (\n    firstExactMatch(acceptsLocales, supportedLocales) ||\n    firstLanguageMatch(acceptsLocales, supportedLocales) ||\n    defaultLocale\n  );\n}\nfunction firstExactMatch(acceptsLocales, supportedLocales) {\n  return acceptsLocales.find((al) =&gt;\n    supportedLocales.includes(al)\n  );\n}\nfunction firstLanguageMatch(\n  acceptsLocales,\n  supportedLocales\n) {\n  for (acceptedLang of languagesFor(acceptsLocales)) {\n    const match = supportedLocales.find(\n      (sl) =&gt; languageFor(sl) === acceptedLang\n    );\n    if (match) {\n      return match;\n    }\n  }\n}\nfunction languagesFor(locales) {\n  return locales.map((loc) =&gt; languageFor(loc));\n}\nfunction languageFor(locale) {\n  return locale.split('-')[0];\n}\nmodule.exports = matchSupportedLocale;\n<\/pre>\n<p>Here\u2019s the basic algorithm:<\/p>\n<ol>\n<li>Find the first <em>exact match<\/em> between the visitor\u2019s accepted locales and our app\u2019s supported locales. If a visitor to our app has <code>en-CA<\/code> in her accepted locales, we will serve the <code>en-CA<\/code> version of our site.<\/li>\n<li>If no exact match is found, find the first <em>language match<\/em> between the visitor\u2019s accepted locales and our app\u2019s supported locales. If a visitor to our app has <code>ar-SA<\/code> (Arabic, Saudi Arabia) in his accepted locales, we will serve the <code>ar<\/code> version of our site.<\/li>\n<li>If no language match is found, we will serve the default locale version of our site (<code>en-CA<\/code> in our case).<\/li>\n<\/ol>\n<p>And with that, we can serve the best possible experience to match our visitors\u2019 preferred locales.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-build-a-language-switcher-for-my-app\"><\/span>How do I build a language switcher for my app?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Even with browser locale detection we often need to provide our visitors a way to manually select their locale of choice. Luckily, we can cook up a little locale switcher without too much effort.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"33bb5e5d-46b5-4533-9f71-45afdd133b5a\" data-enlighter-title=\"src\/app\/locale-switcher\/locale-switcher.component.ts\">import {\n  Inject,\n  Component,\n  LOCALE_ID,\n} from '@angular\/core';\n@Component({\n  selector: 'app-locale-switcher',\n  templateUrl: '.\/locale-switcher.component.html',\n})\nexport class LocaleSwitcherComponent {\n  locales = [\n    { code: 'en-CA', name: 'English' },\n    { code: 'ar', name: '\u0639\u0631\u0628\u064a (Arabic)' },\n  ];\n  constructor(\n    @Inject(LOCALE_ID) public activeLocale: string\n  ) {}\n  onChange() {\n    \/\/ When the visitor selects Arabic, we redirect\n    \/\/ to `\/ar`\n    window.location.href = `\/${this.activeLocale}`;\n  }\n}\n<\/pre>\n<p>The special injection token, <a href=\"https:\/\/angular.io\/api\/core\/LOCALE_ID\">LOCALE_ID<\/a>, is used to retrieve the active locale from Angular. When the English version of our site is loaded <code>LOCALE_ID<\/code> will be <code>en-CA<\/code>, for example. We store this value in an <code>activeLocale<\/code> field, which we bind to the switcher\u2019s value in our template.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"cfc06a92-8580-4e4e-b6ac-b0652056b16d\" data-enlighter-title=\"src\/app\/locale-switcher\/locale-switcher.component.html\">&lt;select\n  (change)=\"onChange()\"\n  [(ngModel)]=\"activeLocale\"\n&gt;\n  &lt;option\n    *ngFor=\"let locale of locales\"\n    [value]=\"locale.code\"\n  &gt;\n    {{ locale.name }}\n  &lt;\/option&gt;\n&lt;\/select&gt;\n<\/pre>\n<p>So with very little code we&#8217;ve given our visitors a nice UI to switch locales.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17304\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/locale-switcher.gif\" alt=\"Our language switcher in action\" width=\"600\" height=\"108\" \/><\/div>\n<p>\u270b <em>Heads up \u00bb<\/em> When we run <code>ng serve<\/code> or <code>npm start<\/code> our app will run in development mode with our source locale, and our locale switcher won\u2019t work. If we <em>build<\/em> our app and run the production test server we wrote above, however, the switcher should switch with no hitch.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-translate-strings-in-my-angular-app\"><\/span>How do I translate strings in my Angular app?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Now that we have our i18n infrastructure set up, let\u2019s go deeper into Angular string translation.<\/p>\n<h4>How do I translate strings in my component templates?<\/h4>\n<p>We\u2019ve gone through this one, but it bears repeating: we just use the handy <code>i18n<\/code> attribute to mark a string for translation.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"ae452c14-bf65-4185-9034-e3369ec171a0\" data-enlighter-title=\"src\/app\/layout\/navbar\/navbar.component.html\">&lt;h1 i18n&gt;azcadea&lt;\/h1&gt;\n<\/pre>\n<p>When we run <code>ng extract-i18n<\/code>, our marked string will be pulled into <code>messages.xlf<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"685f46b0-339c-4093-a64b-2f49be852099\" data-enlighter-title=\"src\/locale\/messages.xlf\" data-enlighter-highlight=\"6\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?&gt;\n&lt;xliff version=\"1.2\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;\n  &lt;file source-language=\"en-CA\" datatype=\"plaintext\" original=\"ng2.template\"&gt;\n    &lt;body&gt;\n      &lt;trans-unit id=\"7285274704628585281\" datatype=\"html\"&gt;\n        &lt;source&gt;azcadea&lt;\/source&gt;\n        &lt;context-group purpose=\"location\"&gt;\n          &lt;context context-type=\"sourcefile\"&gt;src\/app\/navbar\/navbar.component.html&lt;\/context&gt;\n          &lt;context context-type=\"linenumber\"&gt;14&lt;\/context&gt;\n        &lt;\/context-group&gt;\n      &lt;\/trans-unit&gt;\n      &lt;!-- ... --&gt;\n    &lt;\/body&gt;\n  &lt;\/file&gt;\n&lt;\/xliff&gt;\n<\/pre>\n<p>We can copy <code>messages.xlf<\/code> to <code>messages.ar.xlf<\/code> to translate our string to Arabic (and again for each of our app\u2019s supported locales).<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"0bd7e784-5d36-4419-b835-72db96ab7087\" data-enlighter-title=\"src\/locale\/messages.ar.xlf\" data-enlighter-highlight=\"7\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?&gt;\n&lt;xliff version=\"1.2\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"&gt;\n  &lt;file source-language=\"en-CA\" datatype=\"plaintext\" original=\"ng2.template\"&gt;\n    &lt;body&gt;\n      &lt;trans-unit id=\"7285274704628585281\" datatype=\"html\"&gt;\n        &lt;source&gt;azcadea&lt;\/source&gt;\n        &lt;target&gt;\u0623\u0632\u0643\u064a\u062f\u064a\u0627&lt;\/target&gt;\n      &lt;\/trans-unit&gt;\n      &lt;!-- ... --&gt;\n    &lt;\/body&gt;\n  &lt;\/file&gt;\n&lt;\/xliff&gt;\n<\/pre>\n<p>Now we can build our app for production with <code>ng build --localize<\/code> to bake our translation into the Arabic version of our app.<\/p>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> By default, Angular will mark translated strings using an auto-generated ID in the translation files <code>&lt;trans-unit&gt;<\/code>s. You can <a href=\"https:\/\/angular.io\/guide\/i18n-optional-manage-marked-text\">set custom IDs for your translation strings<\/a> if you want, however.<\/p>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> You can use an <code>&lt;ng-container&gt;<\/code> element if you want to <a href=\"https:\/\/angular.io\/guide\/i18n-common-prepare#translate-inline-text-without-html-element\">mark an inline string for translation<\/a> without wrapping it in an otherwise superfluous HTML element. <code>&lt;ng-container&gt;<\/code> renders its contents without adding a wrapper.<\/p>\n<h4>How do I translate strings in HTML attributes?<\/h4>\n<p>Let\u2019s mark our logo\u2019s <code>&lt;img alt=\"\u2026\"&gt;<\/code> attribute for translation: We simply use <code>i18n-{attribute}<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"79f549ed-055c-44cc-b78f-53529efd24f5\" data-enlighter-title=\"src\/app\/layout\/navbar\/navbar.component.html\" data-enlighter-highlight=\"5\">&lt;nav&gt;\n  &lt;div&gt;\n    &lt;div&gt;\n      &lt;img\n        i18n-alt\n        alt=\"azcadea logo\"\n        src=\"assets\/logo.svg\"\n      \/&gt;\n      &lt;h1 i18n&gt;azcadea&lt;\/h1&gt;\n    &lt;\/div&gt;\n    &lt;!-- \u2026 --&gt;\n  &lt;\/div&gt;\n  &lt;!-- \u2026 --&gt;\n&lt;\/nav&gt;\n<\/pre>\n<p>Just like <code>i18n<\/code> for inner text, <code>i18n-{attribute}<\/code> will be caught by <code>ng extract-i18n<\/code> and cause the string to be added as a <code>&lt;trans-unit&gt;<\/code> to our <code>messages.xlf<\/code>.<\/p>\n<h4>How do I translate strings in my component TypeScript code?<\/h4>\n<p>When we mark our HTML tags with <code>i18n<\/code>, the Angular compiler generates code that calls the <code>$localize<\/code> template tag underneath the hood. Like any other <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Template_literals#tagged_templates\">JavaScript template tag<\/a>, the function can be called like <code>$localize`Text in source locale`<\/code>. Let\u2019s use <code>$localize<\/code> to mark our navbar link strings for translation.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"f1418e01-cef6-4bf2-a038-cf2838e3da1c\" data-enlighter-title=\"src\/app\/layout\/navbar\/navbar.component.ts\" data-enlighter-highlight=\"8,9\">import { Component } from '@angular\/core';\n@Component({\n  selector: 'app-navbar',\n  templateUrl: '.\/navbar.component.html',\n})\nexport class NavbarComponent {\n  home: string = $localize`Home`;\n  about: string = $localize`About`;\n}\n<\/pre>\n<p>Strings in our component code marked with <code>$localize<\/code> will be pulled by <code>ng extract-i18n<\/code> into <code>messages.xlf<\/code> as usual.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"1d0a55c8-b917-4943-b357-9416cd2a1b79\" data-enlighter-title=\"src\/locale\/messages.xlf\">&lt;!-- \u2026 --&gt;\n      &lt;trans-unit id=\"2821179408673282599\" datatype=\"html\"&gt;\n        &lt;source&gt;Home&lt;\/source&gt;\n        &lt;context-group purpose=\"location\"&gt;\n          &lt;context context-type=\"sourcefile\"&gt;src\/app\/navbar\/navbar.component.ts&lt;\/context&gt;\n          &lt;context context-type=\"linenumber\"&gt;8&lt;\/context&gt;\n        &lt;\/context-group&gt;\n      &lt;\/trans-unit&gt;\n&lt;!-- \u2026 --&gt;\n<\/pre>\n<p>To complete the translation we of course copy the <code>&lt;trans-unit&gt;<\/code> to <code>messages.ar.xlf<\/code>, add a <code>&lt;target&gt;<\/code> with our Arabic translation, and run <code>ng build --localize<\/code>.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17305\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/navbar-localized.png\" alt=\"English and Arabic versions of our app's navbar\" width=\"1374\" height=\"326\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/navbar-localized.png 1374w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/navbar-localized-300x71.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/navbar-localized-1024x243.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/navbar-localized-768x182.png 768w\" sizes=\"(max-width: 1374px) 100vw, 1374px\" \/><\/div>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-work-with-dynamic-values-in-my-translated-strings\"><\/span>How do I work with dynamic values in my translated strings?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Injecting strings that can change at runtime into translations is quite common, and works seamlessly with <code>$localize<\/code>. Our arcade cabinet images currently have English-only <code>alt<\/code> text that can serve to demonstrate.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"5f1e5a3c-50db-422b-abf7-d896473815b3\" data-enlighter-title=\"src\/app\/pages\/home\/home.component.ts\" data-enlighter-highlight=\"11\">\/\/ \u2026\n@Component({\n  selector: 'app-home',\n  templateUrl: '.\/home.component.html',\n})\nexport class HomeComponent implements OnInit {\n  \/\/ \u2026\n  altFor(cabinet: Cabinet): string {\n    return `Image of ${cabinet.name}`;\n  }\n}\n<\/pre>\n<p>All we need to do is stick <code>$localize<\/code> in front of our template string, and interpolate <code>cabinet.name<\/code> as usual.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"1526ccca-e0ec-4a03-bb5a-5a83c898c3f3\" data-enlighter-title=\"src\/app\/pages\/home\/home.component.ts\" data-enlighter-highlight=\"11\">\/\/ \u2026\n@Component({\n  selector: 'app-home',\n  templateUrl: '.\/home.component.html',\n})\nexport class HomeComponent implements OnInit {\n  \/\/ \u2026\n  altFor(cabinet: Cabinet): string {\n    return $localize`Image of ${cabinet.name}`;\n  }\n}\n<\/pre>\n<p>Angular is smart enough to know that interpolated values need special placeholders in translation strings, and it adds them in for us when we run <code>ng extract-i18n<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"98c17479-5032-4b59-98de-e87fb28bb8dc\" data-enlighter-title=\"src\/locale\/messages.xlf\" data-enlighter-highlight=\"3\">&lt;!-- \u2026 --&gt;\n      &lt;trans-unit id=\"6223458566712133696\" datatype=\"html\"&gt;\n        &lt;source&gt;Image of &lt;x id=\"PH\" equiv-text=\"cabinet.name\"\/&gt;&lt;\/source&gt;\n        &lt;context-group purpose=\"location\"&gt;\n          &lt;context context-type=\"sourcefile\"&gt;src\/app\/home\/home.component.ts&lt;\/context&gt;\n          &lt;context context-type=\"linenumber\"&gt;29&lt;\/context&gt;\n        &lt;\/context-group&gt;\n      &lt;\/trans-unit&gt;\n&lt;!-- \u2026 --&gt;\n<\/pre>\n<p>Note the <code>&lt;x \u2026&gt;<\/code> placeholder tag added above. We need to use this same tag in our translations to let Angular know where we want to inject the runtime value.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"2f69911e-b298-4c2c-aee4-2e260c55983f\" data-enlighter-title=\"src\/locale\/messages.ar.xlf\" data-enlighter-highlight=\"4\">&lt;!-- \u2026 --&gt;\n      &lt;trans-unit id=\"6223458566712133696\"&gt;\n        &lt;source&gt;Image of &lt;x id=\"PH\" equiv-text=\"cabinet.name\"\/&gt;&lt;\/source&gt;\n        &lt;target&gt;\u0635\u0648\u0631\u0629 &lt;x id=\"PH\" equiv-text=\"cabinet.name\"\/&gt;&lt;\/target&gt;\n      &lt;\/trans-unit&gt;\n&lt;!-- \u2026 --&gt;\n<\/pre>\n<p>That\u2019s all it takes. Now we can <code>ng build --localize<\/code> and run our test production server. When we do, opening our browser\u2019s dev tools reveals that our <code>alt<\/code> tags are localized with proper interpolation in our builds.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-17307 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/alt-tags.png\" alt=\"Localized interpolation displayed in our English and Arabic image alt tags\" width=\"1439\" height=\"524\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/alt-tags.png 1439w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/alt-tags-300x109.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/alt-tags-1024x373.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/alt-tags-768x280.png 768w\" sizes=\"(max-width: 1439px) 100vw, 1439px\" \/><\/div>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-work-with-plurals-in-my-translations\"><\/span>How do I work with plurals in my translations?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Angular implements the ICU standard for its localized plurals. ICU is a bit outside the scope of this guide, but you can read more about it in <a href=\"https:\/\/phrase.com\/blog\/posts\/guide-to-the-icu-message-format\/\">The Missing Guide to the ICU Message Format<\/a>. Still, the ICU syntax is intuitive enough that we don\u2019t need a comprehensive understanding of the standard to use it. An ICU plural message looks like the following.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-linenumbers=\"false\">{todoCount, plural,\n  one { You have one to-do remaining! }\n  other { You have {{todoCount}} to-dos left. Keep going \ud83d\ude42 }\n}\n<\/pre>\n<p>The example above is an English translation, of course, so we provide the language\u2019s two plural forms, <code>one<\/code> and <code>other<\/code>. <code>todoCount<\/code> (which can be named anything) is an interpolated integer counter that determines which form is rendered at runtime. We can inject the value of <code>todoCount<\/code> into our plural form messages using Angular\u2019s usual <code>{{variable}}<\/code> syntax.<br \/>\nLet\u2019s use this syntax to localize the string that indicates how many store outlets provide a given cabinet in <em>azcadea<\/em>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"eec2c5e3-2d35-4a31-a162-f483ae5c7a05\" data-enlighter-title=\"src\/app\/pages\/home\/home.component.html\" data-enlighter-highlight=\"9,10,11,12,13,14\">&lt;div&gt;\n  &lt;div *ngFor=\"let cabinet of cabinets\"&gt;\n    &lt;!-- \u2026 --&gt;\n    &lt;div&gt;\n      &lt;!-- \u2026 --&gt;\n      &lt;div&gt;\n        &lt;span i18n&gt;\n          {cabinet.storeCount, plural,\n             =0 {Sold out}\n             =1 {Available at 1 outlet}\n             other {Available at {{ cabinet.storeCount }} outlets} }\n        &lt;\/span&gt;\n        &lt;!-- \u2026 --&gt;\n      &lt;\/div&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n&lt;\/div&gt;\n<\/pre>\n<p>Note that we\u2019ve used <code>=0<\/code> and <code>=1<\/code>, plural form selectors that override the language\u2019s formal selectors (<code>one<\/code>, <code>few<\/code>, <code>other<\/code>, etc.). This allows us to have a \u201czero\u201d case in our English translation when English normally uses its <code>other<\/code> form for zero.<\/p>\n<p>\u270b <em>Heads up \u00bb<\/em> In general, the <code>other<\/code> case is always required.<\/p>\n<p>You may have guessed what\u2019s next at this point: We run <code>ng extract-i18n<\/code> to get the message into <code>messages.xlf<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"8b8aa9d8-dd4d-459d-9897-93050ad1a469\" data-enlighter-title=\"src\/locale\/messages.xlf\">&lt;!-- \u2026 --&gt;\n      &lt;trans-unit id=\"8856905278208146821\" datatype=\"html\"&gt;\n        &lt;source&gt; &lt;x id=\"ICU\" equiv-text=\"{cabinet.storeCount, plural, =0 {Sold out} =1\n          {Available at 1 outlet} other {Available at\n          {{ cabinet.storeCount }} outlets} }\" xid=\"4465788077291796430\"\/&gt; &lt;\/source&gt;\n        &lt;!-- \u2026 --&gt;\n      &lt;\/trans-unit&gt;\n      &lt;trans-unit id=\"7185053985224017078\" datatype=\"html\"&gt;\n        &lt;source&gt;{VAR_PLURAL, plural, =0 {Sold out} =1 {Available at 1 outlet} other {Available at\n          &lt;x id=\"INTERPOLATION\"\/&gt; outlets}}&lt;\/source&gt;\n        &lt;!-- \u2026 --&gt;\n      &lt;\/trans-unit&gt;\n&lt;!-- \u2026 --&gt;\n<\/pre>\n<p>We get two <code>&lt;trans-unit&gt;<\/code>s this time, which can seem a bit weird. Angular pulls the ICU <a href=\"https:\/\/angular.io\/guide\/i18n-common-translation-files#translate-nested-expressions\">nested expression<\/a> bit out into its own separate <code>&lt;trans-unit&gt;<\/code>. This is because sometimes the ICU expression is only <em>part<\/em> of a bigger translation string. In this case, the ICU expression takes up the entire string, so both <code>&lt;trans-units&gt;<\/code> are virtually identical. Let\u2019s copy both <code>&lt;trans-units&gt;<\/code> into our Arabic translation file and translate them.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"dace338d-bf4e-4ce0-a411-c1899bcc27b3\" data-enlighter-title=\"src\/locale\/messages.ar.xlf\">&lt;!-- \u2026 --&gt;\n      &lt;trans-unit id=\"8856905278208146821\"&gt;\n        &lt;source&gt; &lt;x id=\"ICU\" equiv-text=\"{cabinet.storeCount, plural, =0 {Sold out} =1 {Available at 1 outlet} other {Available at {{ cabinet.storeCount }} outlets} }\" xid=\"4465788077291796430\"\/&gt; &lt;\/source&gt;\n        &lt;target&gt; &lt;x id=\"ICU\" equiv-text=\"{cabinet.storeCount, plural, =0 {Sold out} =1 {Available at 1 outlet} other {Available at           {{ cabinet.storeCount }} outlets} }\" xid=\"4465788077291796430\"\/&gt; &lt;\/target&gt;\n      &lt;\/trans-unit&gt;\n      &lt;trans-unit id=\"7185053985224017078\"&gt;\n        &lt;source&gt;{VAR_PLURAL, plural, =0 {Sold out} =1 {Available at 1 outlet} other {Available at\n          &lt;x id=\"INTERPOLATION\"\/&gt; outlets}}&lt;\/source&gt;\n        &lt;target&gt;{VAR_PLURAL, plural,\n          =0 {\u063a\u064a\u0631 \u0645\u062a\u0648\u0641\u0631}\n          =1 {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f \u0641\u0631\u0639 &lt;x id=\"INTERPOLATION\"\/&gt;}\n          two {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f \u0641\u0631\u0639\u064a\u0646}\n          few {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f &lt;x id=\"INTERPOLATION\"\/&gt; \u0623\u0641\u0631\u0639}\n          many {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f &lt;x id=\"INTERPOLATION\"\/&gt; \u0641\u0631\u0639}\n          other {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f &lt;x id=\"INTERPOLATION\"\/&gt; \u0641\u0631\u0639}\n        }&lt;\/target&gt;\n      &lt;\/trans-unit&gt;\n&lt;!-- \u2026 --&gt;\n<\/pre>\n<p>The outer string is copied as-is in translation and is only included in this case to avoid a warning that the Angular build tools will output if we don\u2019t include it. The important <code>&lt;trans-unit&gt;<\/code> for us is the second, nested expression. Note that, unlike English, Arabic has six plural forms that need to be added for proper translation.<\/p>\n<p>\ud83d\udd17 <em>Resource \u00bb<\/em> <a href=\"https:\/\/unicode-org.github.io\/cldr-staging\/charts\/latest\/supplemental\/language_plural_rules.html\">The CLDR Language Plural Rules<\/a> document lists plural forms for all languages.<\/p>\n<p>If we build our app for production and test it, we see that our plural forms are showing correctly for our supported locales.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-17308 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/plurals.png\" alt=\"Our demo app showing plural forms for Arabic and English translations\" width=\"1870\" height=\"1721\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/plurals.png 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/plurals-300x276.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/plurals-1024x943.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/plurals-768x707.png 768w\" sizes=\"(max-width: 1870px) 100vw, 1870px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-localize-numbers-in-my-angular-app\"><\/span>How do I localize numbers in my Angular app?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The simplest way to localize numbers is to use Angular\u2019s built-in formatting pipes in our component templates. These pipes will use the active locale for formatting by default.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\" data-enlighter-linenumbers=\"false\">import { Component } from '@angular\/core';\n@Component({\n  selector: 'app-number-pipes',\n  template: `\n    &lt;p&gt;number: {{ myNumber | number }}&lt;\/p&gt;\n    &lt;p&gt;currency: {{ myNumber | currency: 'CAD' }}&lt;\/p&gt;\n    &lt;p&gt;percent: {{ myPercent | percent }}&lt;\/p&gt;\n  `,\n})\nexport class NumberPipesComponent {\n  myNumber: number = 9999.99;\n  myPercent: number = 0.21;\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\">&lt;!-- When active locale is en-CA, renders: --&gt;\n&lt;p&gt;number: 9,999.99&lt;\/p&gt;\n&lt;p&gt;currency: $9,999.99&lt;\/p&gt;\n&lt;p&gt;percent: 21%&lt;\/p&gt;\n&lt;!-- When active locale is ar, renders: --&gt;\n&lt;p&gt;number: 9,999.99&lt;\/p&gt;\n&lt;p&gt;currency: CA$ 9,999.99&lt;\/p&gt;\n&lt;p&gt;percent: 21\u200e%\u200e&lt;\/p&gt;\n<\/pre>\n<p>You can notice a difference in the currency formatting above. Unfortunately, Angular always seems to use Western Arabic (1, 2, 3) digits regardless of the active locale. While not ideal, this is often adequate, since most people are familiar enough with Western Arabic digits.<\/p>\n<p>\ud83e\udd3f <em>Go deeper \u00bb<\/em> The <a href=\"https:\/\/angular.io\/api\/common\/DecimalPipe\">decimal<\/a>, <a href=\"https:\/\/angular.io\/api\/common\/CurrencyPipe\">currency<\/a>, and <a href=\"https:\/\/angular.io\/api\/common\/PercentPipe\">percent<\/a> pipes have a lot of formatting options through arguments. They also have function equivalents we can use in our TypeScript: <a href=\"https:\/\/angular.io\/api\/common\/formatNumber\">formatNumber()<\/a>,<a href=\"https:\/\/angular.io\/api\/common\/formatCurrency\">\u00a0formatCurrency()<\/a>, and <a href=\"https:\/\/angular.io\/api\/common\/formatPercent\">formatPercent()<\/a>.<\/p>\n<p>Let\u2019s update our Home component to localize our arcade cabinet prices.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"b2787717-0c48-4ee5-a072-e99445122e26\" data-enlighter-title=\"src\/app\/pages\/home\/home.component.html\" data-enlighter-highlight=\"7\">&lt;div&gt;\n  &lt;div *ngFor=\"let cabinet of cabinets\"&gt;\n      &lt;!-- \u2026 --&gt;\n        &lt;p&gt;\n          {{ cabinet.price | currency: 'USD' }}\n        &lt;\/p&gt;\n\t  &lt;!-- \u2026 --&gt;\n  &lt;\/div&gt;\n&lt;\/div&gt;\n<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-localize-dates-in-my-angular-app\"><\/span>How do I localize dates in my Angular app?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Similar to numbers, the simplest way to localize a date is to use the date pipe in our component templates.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"a1ebb202-d115-4669-a0d8-c712a0022ba4\" data-enlighter-title=\"src\/app\/pages\/home\/home.component.html\" data-enlighter-highlight=\"8\">&lt;div&gt;\n  &lt;div *ngFor=\"let cabinet of cabinets\"&gt;\n      &lt;!-- \u2026 --&gt;\n        &lt;p&gt;\n          &lt;!-- cabinet.addedAt is a Date object --&gt;\n          {{ cabinet.addedAt | date }}\n        &lt;\/p&gt;\n\t  &lt;!-- \u2026 --&gt;\n  &lt;\/div&gt;\n&lt;\/div&gt;\n<\/pre>\n<p>Angular will automatically use the active locale when rendering the date.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\">&lt;!-- When active locale is en-CA, and cabinet.addedAt is\n     2022-01-22, the above will render: --&gt;\n&lt;p&gt;Jan. 22, 2022&lt;\/p&gt;\n&lt;!-- When active locale is ar, and cabinet.addedAt is\n     2022-01-22, the above will render: --&gt;\n&lt;p&gt;2022\/01\/22&lt;\/p&gt;\n<\/pre>\n<p>We can specify a date format for the pipe to control its output.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"cae60fb5-9c45-4c91-b845-8863b1e96c7f\" data-enlighter-title=\"src\/app\/pages\/home\/home.component.html\" data-enlighter-highlight=\"7\">&lt;div&gt;\n  &lt;div *ngFor=\"let cabinet of cabinets\"&gt;\n      &lt;!-- \u2026 --&gt;\n        &lt;p&gt;\n          {{ cabinet.addedAt | date: 'd MMM YYYY' }}\n        &lt;\/p&gt;\n\t  &lt;!-- \u2026 --&gt;\n  &lt;\/div&gt;\n&lt;\/div&gt;\n<\/pre>\n<p>\ud83e\udd3f <em>Go deeper \u00bb<\/em> Check out of <a href=\"https:\/\/angular.io\/api\/common\/DatePipe\">all the formatting options for the date pipe<\/a>, as well as its function equivalent, <a href=\"https:\/\/angular.io\/api\/common\/formatDate\">formatDate()<\/a>.<\/p>\n<p>And with that, our demo app is pretty well localized \ud83d\ude0a.<\/p>\n<div><strong style=\"color: #ff6600;\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17309\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/en-complete.png\" alt=\"Complete English version of our demo app\" width=\"1170\" height=\"1212\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/en-complete.png 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/en-complete-290x300.png 290w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/en-complete-989x1024.png 989w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/en-complete-768x796.png 768w\" sizes=\"(max-width: 1170px) 100vw, 1170px\" \/><\/strong><\/div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17310\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ar-complete.png\" alt=\"Complete Arabic version of our demo app\" width=\"1170\" height=\"1213\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ar-complete.png 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ar-complete-289x300.png 289w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ar-complete-988x1024.png 988w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ar-complete-768x796.png 768w\" sizes=\"(max-width: 1170px) 100vw, 1170px\" \/><\/div>\n<p>\ud83d\udd17 <em>Resource \u00bb<\/em> Get the\u00a0entire code of our fully localized app from <a href=\"https:\/\/github.com\/PhraseApp-Blog\/angular-i18n-2022\/tree\/main\">GitHub<\/a>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-use-phrase-to-localize-my-angular-app\"><\/span>How do I use Phrase to localize my Angular app?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>You may have noticed that managing translation files with Angular\u2019s localization library is a bit clunky. We have to copy the source locale for each of our other supported locales, and we have to keep all these files in sync as we make changes within our app.<br \/>\nThere\u2019s a better way: the <a href=\"https:\/\/phrase.com\/platform\/\">Phrase Localization Platform<\/a> can take care of all this juggling for us and keep us focused on the code we love. Let me show you how to use Phrase to take all that file syncing headache out of your localization workflow.<\/p>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> To follow along here, you\u2019ll need to sign up for Phrase.<\/p>\n<p>With the <a href=\"https:\/\/phrase.com\/download\/\">Phrase CLI<\/a> installed, we can run the following from the command line in our Angular project root.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">$ phrase init\n<\/pre>\n<p>We\u2019ll be asked for an access token at this point, and we can get one by logging into Phrase, clicking our name near the top-right of the page, then clicking <em>Profile Settings<\/em> \u279e <em>Access tokens<\/em>.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-17334 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/account-phrase-1.png\" alt=\"The account dropdown on the main Phrase account page\" width=\"2948\" height=\"782\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/account-phrase-1.png 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/account-phrase-1-300x79.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/account-phrase-1-1024x271.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/account-phrase-1-768x203.png 768w\" sizes=\"(max-width: 2948px) 100vw, 2948px\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-17333 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/generate-token-phrase-1.png\" alt=\"The token tab in profile settings\" width=\"1170\" height=\"228\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/generate-token-phrase-1.png 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/generate-token-phrase-1-300x58.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/generate-token-phrase-1-1024x200.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/generate-token-phrase-1-768x150.png 768w\" sizes=\"(max-width: 1170px) 100vw, 1170px\" \/><br \/>\nFrom there we can click the <em>Generate Token<\/em> button, enter the required note, and click <em>Save<\/em>. Next, let\u2019s copy the API token we\u2019re given and paste it where the CLI command is waiting for it.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17313\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/cli-token.png\" alt=\"Entering API token into Phrase CLI\" width=\"1898\" height=\"160\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/cli-token.png 1898w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/cli-token-300x25.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/cli-token-1024x86.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/cli-token-768x65.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/cli-token-1536x129.png 1536w\" sizes=\"(max-width: 1898px) 100vw, 1898px\" \/><br \/>\nAt this point, we\u2019ll be given a few prompts to complete initialization:<\/p>\n<ul>\n<li>Select an existing Phrase project or create a new one. We can create a new one here and name it <code>azcadea<\/code>.<\/li>\n<li>Select the translation file format. We can select the XLIFF (not XLIFF 2) format since that\u2019s what we\u2019re using here.<\/li>\n<li>Enter the path to the file we want to upload. This is equivalent to our source locale, so we can enter <code>.\/src\/locale\/messages.xlf<\/code>.<\/li>\n<li>Enter the path to the files we want to download. We can use the special <code>&lt;locale_name&gt;<\/code> placeholder here and enter <code>.\/src\/locale\/messages.&lt;locale_name&gt;.xlf<\/code>. This will allow us to add any number of locales to our Phrase project.<\/li>\n<\/ul>\n<p>At this point, we\u2019ll be asked if we want to upload our locales for the first time. We can enter <code>y<\/code> to start the upload.<br \/>\nBelieve it or not, that\u2019s it from a developer\u2019s end. At this point, our English source translations are available in our Phrase project. Our translators can add Arabic (<code>ar<\/code>) as a project language, then use the Phrase web console to get their translation work done.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-17314\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ng-phrase.png\" alt=\"The Phrase translation console\" width=\"1170\" height=\"992\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ng-phrase.png 1170w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ng-phrase-300x254.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ng-phrase-1024x868.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2022\/06\/ng-phrase-768x651.png 768w\" sizes=\"(max-width: 1170px) 100vw, 1170px\" \/><\/p>\n<p>\ud83d\uddd2 <em>Note \u00bb<\/em> It\u2019s probably a good idea to go to <em>Project Settings<\/em> \u279e <em>Placeholders<\/em> and selecting the <em>OASIS XLIFF placeholders<\/em> to match the <code>&lt;x \u2026&gt;<\/code> placeholders Angular uses in our translation files. This will make the placeholders easy to spot and use for our translators.<\/p>\n<p>Once translations are ready, we just need to run the following from the command line at the root of our project.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">$ phrase pull\n<\/pre>\n<p>This downloads the latest Arabic messages to our codebase.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"29d1c261-f129-4b8b-827b-70c1efa4f800\" data-enlighter-title=\"src\/locale\/messages.ar.xlf\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n&lt;xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\"&gt;\n  &lt;file original=\"ng2.template\" datatype=\"plaintext\" source-language=\"en-CA\" target-language=\"ar\"&gt;\n    &lt;body&gt;\n      &lt;trans-unit id=\"1726363342938046830\"&gt;\n        &lt;source xml:lang=\"en-CA\"&gt;About&lt;\/source&gt;\n        &lt;target xml:lang=\"ar\"&gt;\u0646\u0628\u0630\u0629 \u0639\u0646\u0627&lt;\/target&gt;\n      &lt;\/trans-unit&gt;\n      &lt;trans-unit id=\"7185053985224017078\"&gt;\n        &lt;source xml:lang=\"en-CA\"&gt;{VAR_PLURAL, plural, =0 {Sold out} =1 {Available at 1 outlet} other {Available at\n          &lt;x id=\"INTERPOLATION\"\/&gt; outlets}}&lt;\/source&gt;\n        &lt;target xml:lang=\"ar\"&gt;{VAR_PLURAL, plural,\n          =0 {\u063a\u064a\u0631 \u0645\u062a\u0648\u0641\u0631}\n          =1 {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f \u0641\u0631\u0639 &lt;x id=\"INTERPOLATION\"\/&gt;}\n          two {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f \u0641\u0631\u0639\u064a\u0646}\n          few {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f &lt;x id=\"INTERPOLATION\"\/&gt; \u0623\u0641\u0631\u0639}\n          many {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f &lt;x id=\"INTERPOLATION\"\/&gt; \u0641\u0631\u0639}\n          other {\u0645\u062a\u0648\u0641\u0631 \u0639\u0646\u062f &lt;x id=\"INTERPOLATION\"\/&gt; \u0641\u0631\u0639}\n        }&lt;\/target&gt;\n      &lt;\/trans-unit&gt;\n      &lt;!-- \u2026 --&gt;\n    &lt;\/body&gt;\n  &lt;\/file&gt;\n&lt;\/xliff&gt;\n<\/pre>\n<p>We didn\u2019t have to sync translation files ourselves. Imagine how much easier our whole workflow would be if we had ten locales we had to support, or fifty!<\/p>\n<h2><span class=\"ez-toc-section\" id=\"what-alternative-localization-libraries-are-available-for-angular\"><\/span>What alternative localization libraries are available for Angular?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The first-party @angular\/localize package is a robust i18n solution for our Angular apps. It is missing some features, however, like support for localized numeral systems. And, if you find Angular creating a production version of your app for each locale a little excessive, you might want to try some third-party <a href=\"https:\/\/phrase.com\/blog\/posts\/best-libraries-for-angular-i18n\/\">Angular libraries for internationalization<\/a>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"ngx-translate\"><\/span>ngx-translate<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>At the time of writing no third-party i18n libraries for Angular are <a href=\"https:\/\/www.npmtrends.com\/@ngneat\/transloco-vs-@ngx-translate\/core-vs-angular-i18n-vs-ng2-translate-vs-angular-l10n\">anywhere near as popular<\/a> as <a href=\"https:\/\/github.com\/ngx-translate\/core\">ngx-translate<\/a>. A small library that does things a bit differently from @angular\/localize, ngx-translate has a few things on offer.<\/p>\n<h4>Features of ngx-translate<\/h4>\n<ul>\n<li>Uses simple JSON files for translations.<\/li>\n<li>Allows locale switching at runtime (one app build for all locales).<\/li>\n<li>Supports lazy-loading of translation files, which means only the translation our visitor needs is delivered to their browser, reducing load times.<\/li>\n<\/ul>\n<p>\u270b <em>Heads up \u00bb<\/em> ngx-translate does not have any support for date and time formatting, however, so you\u2019ll need additional libraries to handle that if you go with ngx-translate.<\/p>\n<p>Here\u2019s an example of a translated string in an Angular app using ngx-translate:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"27823b39-cea7-440e-89cd-19ae98e523b4\" data-enlighter-title=\"src\/assets\/i18n\/en.json\">{\n  \"welcome\": \"Hello there!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"c2aae802-fad1-427c-83eb-e1c8e523f511\" data-enlighter-title=\"src\/assets\/i18n\/ar.json\">{\n  \"welcome\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\">&lt;!-- In a template --&gt;\n&lt;p&gt;{{ \"welcome\" | translate }}&lt;\/p&gt;\n&lt;!-- When runtime locale is en, renders: --&gt;\n&lt;p&gt;Hello there!&lt;\/p&gt;\n&lt;!-- When runtime locale is ar, renders: --&gt;\n&lt;p&gt;\u0623\u0647\u0644\u0627\u064b \u0628\u0643!&lt;\/p&gt;\n<\/pre>\n<p>\ud83e\udd3f <em>Go deeper \u00bb<\/em> With ngx-translate we can translate strings using directives or in TypeScript, handle plurals using a plugin, and much more. Read <a href=\"https:\/\/phrase.com\/blog\/posts\/angular-i18n-ngx-translate\/\">A Deep Dive on Angular I18n with ngx-translate<\/a> to get all the details.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"transloco\"><\/span>Transloco<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>While not as popular as ngx-translate, the Transloco i18n library for Angular shares the former\u2019s basic design philosophy of loading JSON translation files into your Angular app\u2014no need to rebuild the app for different locales. Well-documented and fully featured via first-party plugins, Transloco is definitely worth a look.<\/p>\n<h4>Features of Transloco<\/h4>\n<p>Much like ngx-translate, Transloco:<\/p>\n<ul>\n<li>Uses simple JSON files for translations.<\/li>\n<li>Allows locale switching at runtime.<\/li>\n<li>Supports lazy loading of translation files.<\/li>\n<\/ul>\n<p>However, Transloco also:<\/p>\n<ul>\n<li>Has excellent documentation.<\/li>\n<li>Supports robust fallback options for missing translations.<\/li>\n<li>Supports native numeral systems for localized date and number formatting using the first-party Locale L10N plugin.<\/li>\n<\/ul>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7479\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2019\/08\/ncl-bengail-numeral-system.png\" alt=\"A shopping cart line item shown in different locales with their native numeral systems\" width=\"490\" height=\"291\" \/><\/div>\n<div style=\"text-align: center;\"><small><em>A shopping cart line item shown in different locales with their native numeral systems<\/em><\/small><\/div>\n<p>\ud83d\udd17 <em>Resource \u00bb<\/em> Read our <a href=\"https:\/\/phrase.com\/blog\/posts\/number-localization\/\">Concise Guide to Number Localization<\/a> if you\u2019re interested in how different locales use different numeral systems.<\/p>\n<p>Here\u2019s what a translated string looks like when working with Transloco:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"e2c9fe34-eeda-4b27-a7c2-57de153772fb\" data-enlighter-title=\"src\/assets\/i18n\/en.json\">{\n  \"greeting\": \"Hello there!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"8070bd94-7085-403c-8803-b202b8003210\" data-enlighter-title=\"src\/assets\/i18n\/ar.json\">{\n  \"greeting\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\">&lt;!-- In a template --&gt;\n&lt;ng-container *transloco=\"let t\"&gt;\n  &lt;p&gt;{{ t('greeting') }}&lt;\/p&gt;\n&lt;\/ng-container&gt;\n&lt;!-- When runtime locale is en, renders: --&gt;\n&lt;p&gt;Hello there!&lt;\/p&gt;\n&lt;!-- When runtime locale is ar, renders: --&gt;\n&lt;p&gt;\u0623\u0647\u0644\u0627\u064b \u0628\u0643!&lt;\/p&gt;\n<\/pre>\n<p>\ud83e\udd3f <em>Go deeper \u00bb<\/em> The structural <code>transloco<\/code> directive is just one way to translate strings in templates: we can also use a translation pipe or attribute directive. Translating within TypeScript is also available, and much more. Read the <a href=\"https:\/\/phrase.com\/blog\/posts\/angular-10-tutorial-localization-transloco\/\">Angular Tutorial on Localizing with Transloco<\/a> for a more detailed look.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"i18next\"><\/span>i18next<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>At the time of writing <a href=\"https:\/\/www.i18next.com\/\">i18next<\/a> is one of the most popular i18n libraries in the world outside of the Angular ecosystem. If you\u2019re coming to Angular from another UI library or framework, you might already be familiar with i18next. After all, i18next seems to support every library and framework under the sun. Angular support for i18next comes in via the third-party <a href=\"https:\/\/github.com\/Romanchuk\/angular-i18next\">angular-i18next<\/a> package.<\/p>\n<h4>Features of i18next<\/h4>\n<p>Just like ngx-translate and Transloco, i18next:<\/p>\n<ul>\n<li>Uses simple JSON files for translation by default.<\/li>\n<li>Allows locale-switching at runtime.<\/li>\n<li>Supports lazy-loading of translation files.<\/li>\n<\/ul>\n<p>In addition, i18next boasts unique features:<\/p>\n<ul>\n<li>Supports a plethora of libraries and frameworks both client- and server-side.<\/li>\n<li>Almost any i18n feature is either built-in or supported via a plugin.<\/li>\n<li>A flexible architecture means you can swap in your own solutions for any major part of the library.<\/li>\n<\/ul>\n<p>Here\u2019s what a string translated with i18next looks like in an Angular app:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"c6ef30a3-349d-4253-a871-e68782387160\" data-enlighter-title=\"src\/assets\/locales\/en.translation.json\">{\n  \"hello\": \"Hello there!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"13e21888-d1b4-4eab-a03c-b60b255be606\" data-enlighter-title=\"src\/assets\/locales\/ar.translation.json\">{\n  \"hello\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-linenumbers=\"false\">&lt;!-- In a template --&gt;\n&lt;p&gt;{{ 'hello' | i18next }}&lt;\/p&gt;\n&lt;!-- When runtime locale is en, renders: --&gt;\n&lt;p&gt;Hello there!&lt;\/p&gt;\n&lt;!-- When runtime locale is ar, renders: --&gt;\n&lt;p&gt;\u0623\u0647\u0644\u0627\u064b \u0628\u0643!&lt;\/p&gt;\n<\/pre>\n<p>\ud83e\udd3f <em>Go deeper \u00bb<\/em> We\u2019ve just scratched the surface of i18next here. Check out <a href=\"https:\/\/phrase.com\/blog\/posts\/angular-l10n-with-i18next\/\">Angular L10n with I18next<\/a> to get more details on setting up and using i18next in your Angular apps.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"wrapping-up-our-angular-l10n-tutorial\"><\/span>Wrapping up our Angular l10n tutorial<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We hope that our Angular i18n guide has served you well. No matter which i18n library you choose to go with, Phrase can take your localization process to the next level.<\/p>\n<p>The Phrase Localization Platform supports all the translation file formats we\u2019ve covered here and many more. With its CLI and Bitbucket, GitHub, and GitLab sync, your i18n can be on autopilot. The fully-featured Phrase web console, with machine learning and smart suggestions, is a joy for translators to use. Once translations are ready, they can sync back to your project automatically. You set it and forget it, leaving you to focus on the code you love.<\/p>\n<p>Check out all <a href=\"https:\/\/phrase.com\/roles\/developers\/\">Phrase features for developers<\/a>\u00a0and see for yourself how it can help you take your apps global.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn end-to-end Angular localization with the built-in i18n library and explore third-party internationalization alternatives along the way.<\/p>\n","protected":false},"author":41,"featured_media":2612,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_stopmodifiedupdate":false,"_modified_date":"","_searchwp_excluded":"","footnotes":""},"categories":[40],"class_list":["post-16401","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-localization"],"acf":[],"_links":{"self":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/16401"}],"collection":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/comments?post=16401"}],"version-history":[{"count":6,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/16401\/revisions"}],"predecessor-version":[{"id":94253,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/16401\/revisions\/94253"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media\/2612"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media?parent=16401"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/categories?post=16401"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}