{"id":89840,"date":"2024-08-05T18:01:39","date_gmt":"2024-08-05T16:01:39","guid":{"rendered":"https:\/\/phrase.com\/?p=89840"},"modified":"2024-10-31T12:56:42","modified_gmt":"2024-10-31T11:56:42","slug":"qwik-localization","status":"publish","type":"post","link":"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/","title":{"rendered":"Mastering Qwik Localization: A Comprehensive Guide"},"content":{"rendered":"\n<div id=\"acf\/text-block_59ba0d1d9fda3242794626502fe594f1\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>As applications become more complex, loading more JavaScript upfront increases client bundle sizes and slows initial load times. Frameworks like Next.js tackle this with Server Components, which render on the server to reduce the client&#8217;s payload and browser workload. <a href=\"https:\/\/qwik.dev\/\">Qwik<\/a> takes a radically different approach, serializing the application&#8217;s state on the server and <a href=\"https:\/\/qwik.dev\/docs\/concepts\/resumable\/\">resuming<\/a> it on the client, which avoids the need for re-rendering and hydration in the browser.<\/p>\n<p>While Qwik\u2019s instantly interactive \u201cLive HTML\u201d and aggressive lazy-loading can speed up our apps, they make things like <a href=\"https:\/\/qwik.dev\/docs\/integrations\/i18n\/\">internationalization<\/a> (i18n) a bit more tricky. Thankfully, the <a href=\"https:\/\/github.com\/robisim74\/qwik-speak\">Qwik Speak<\/a> library by <a href=\"https:\/\/github.com\/robisim74\">Roberto Simonetti<\/a> simplifies Qwik i18n while adhering to Qwik\u2019s resumability and lazy-loading principles. In this guide, we\u2019ll walk through a Qwik demo app and internationalize it using Qwik Speak. Let\u2019s get started.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong> Internationalization (i18n) and localization (l10n) allow us to make our apps available in different languages and regions, often for more profit. If you\u2019re new to i18n and l10n, check out our guide to <a href=\"https:\/\/phrase.com\/blog\/posts\/i18n-a-simple-definition\/\">internationalization<\/a>.<\/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\/qwik-localization\/#our-demo\" title=\"Our demo\">Our demo<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#packages-used\" title=\"Packages used\">Packages used<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#building-the-starter-app\" title=\"Building the starter app\">Building the starter app<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-localize-my-app-with-qwik-speak\" title=\"How do I localize my app with Qwik Speak?\">How do I localize my app with Qwik Speak?<\/a><\/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\/qwik-localization\/#how-do-i-install-and-configure-qwik-speak\" title=\"How do I install and configure Qwik Speak?\">How do I install and configure Qwik Speak?<\/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\/qwik-localization\/#a-note-on-locales\" title=\"A note on locales\">A note on locales<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-configure-localized-routing\" title=\"How do I configure localized routing?\">How do I configure localized routing?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#forcing-a-locale-prefix\" title=\"Forcing a locale prefix\">Forcing a locale prefix<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-localize-my-links\" title=\"How do I localize my links?\">How do I localize my links?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-build-a-language-switcher\" title=\"How do I build a language switcher?\">How do I build a language switcher?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-extract-translations-from-code\" title=\"How do I extract translations from code?\">How do I extract translations from code?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#some-notes-on-translation-files\" title=\"Some notes on translation files\">Some notes on translation files<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#runtime-translations\" title=\"Runtime translations\">Runtime translations<\/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\/qwik-localization\/#lazy-loaded-translations\" title=\"Lazy-loaded translations\">Lazy-loaded translations<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-deal-with-right-to-left-languages\" title=\"How do I deal with right-to-left languages?\">How do I deal with right-to-left languages?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-localize-on-the-server\" title=\"How do I localize on the server?\">How do I localize on the server?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-localize-page-metadata\" title=\"How do I localize page metadata?\">How do I localize page metadata?<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-work-with-basic-translation-messages\" title=\"How do I work with basic translation messages?\">How do I work with basic translation messages?<\/a><\/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\/qwik-localization\/#how-do-i-work-with-dynamic-values-in-translation-messages\" title=\"How do I work with dynamic values in translation messages?\">How do I work with dynamic values in translation messages?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-work-with-localized-plurals\" title=\"How do I work with localized plurals?\">How do I work with localized plurals?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-add-html-to-my-translation-messages\" title=\"How do I add HTML to my translation messages?\">How do I add HTML to my translation messages?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-format-localized-numbers\" title=\"How do I format localized numbers?\">How do I format localized numbers?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#a-note-on-regional-formatting\" title=\"A note on regional formatting\">A note on regional formatting<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#how-do-i-format-localized-dates\" title=\"How do I format localized dates?\">How do I format localized dates?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/phrase.com\/blog\/posts\/qwik-localization\/#take-your-qwik-localization-to-the-next-level\" title=\"Take your Qwik localization to the next level\">Take your Qwik localization to the next level<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"our-demo\"><\/span>Our demo<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The fictitious <strong>Etttro<\/strong> is a mock second-hand marketplace for retro hardware.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-89860\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/demo-before-i18n-1024x856.png\" alt=\"Screenshot of the Etttro demo app's home page showing three latest products: a Commodore 64 for $149.99 dated 2024-06-01, a Nintendo Virtual Boy for $229 dated 2024-06-05, and a Sony Walkman for $199 dated 2024-06-10. The app has a dark green background and includes navigation links for &quot;Latest products&quot; and &quot;About us.&quot;\" width=\"1024\" height=\"856\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/demo-before-i18n-1024x856.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/demo-before-i18n-300x251.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/demo-before-i18n-768x642.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/demo-before-i18n.png 1148w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>We won\u2019t cover any e-commerce or CRUD in this guide, keeping the app nice and lean to focus on the i18n.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"packages-used\"><\/span>Packages used<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We\u2019ll use the following NPM packages and walk through their installation when needed.<\/p>\n<ul>\n<li><code>typescript@5.5<\/code> \u2014 our programming language<\/li>\n<li><code>@builder.io\/qwik@1.7<\/code> \u2014 the core Qwik library<\/li>\n<li><code>@builder.io\/qwik-city@1.7<\/code> \u2014 Qwik\u2019s SSR framework<\/li>\n<li><code>qwik-speak@0.23<\/code> \u2014\u00a0the i18n library<\/li>\n<li><code>rtl-detect@1.1<\/code> \u2014\u00a0detects the layout direction of a language<\/li>\n<li><code>tailwindcss@3.4<\/code> \u2014\u00a0for styling, optional for our purposes<\/li>\n<\/ul>\n<h3><span class=\"ez-toc-section\" id=\"building-the-starter-app\"><\/span>Building the starter app<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Let\u2019s spin up a new Qwik app from the command line.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"> npm create qwik@latest<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_bf3d1c42722388eba8b2ce07a4524a8c\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>When prompted to select a starter, we can pick the <strong>Basic App (Qwik City + Qwik)<\/strong> option. After installing the npm packages, we can install Tailwind CSS by running the following. (Again, this is optional, and we don\u2019t focus much on styling in this guide).<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"> npm run qwik add tailwind<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c5365f2ce45bbaf8c6713abb1bfa837c\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>If you are coding along, note that we removed all non-Tailwind boilerplate styles in our demo. So our <code>global.css<\/code> looks like the following.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-comment\">\/* src\/global.css *\/<\/span>\n\n<span class=\"hljs-keyword\">@tailwind<\/span> base;\n<span class=\"hljs-keyword\">@tailwind<\/span> components;\n<span class=\"hljs-keyword\">@tailwind<\/span> utilities;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_ba58c4245d6be8f21a0c9d68e4337f06\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can remove most of the boilerplate code that some with the Qwik starter: everything in <code>src\/components<\/code>, <code>src\/media<\/code> can be deleted, and so can <code>src\/routes\/demo<\/code> and <code>public\/fonts<\/code>. <\/p>\n<p>Our root layout file can be simplified to look like the following.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/routes\/layout.tsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { component$, Slot } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> type { RequestHandler } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik-city\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { routeLoader$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik-city\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Header <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"~\/components\/layout\/header\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> onGet: RequestHandler = <span class=\"hljs-keyword\">async<\/span> ({\n  cacheControl,\n}) =&gt; {\n  cacheControl({\n    <span class=\"hljs-attr\">staleWhileRevalidate<\/span>: <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">24<\/span> * <span class=\"hljs-number\">7<\/span>,\n    <span class=\"hljs-attr\">maxAge<\/span>: <span class=\"hljs-number\">5<\/span>,\n  });\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> useServerTimeLoader = routeLoader$(() =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">date<\/span>: <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Date<\/span>().toISOString(),\n  };\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> component$(() =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Header<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Slot<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n  );\n});\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_0b3c891338b171d7b1e5896301391106\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0We omit styles for brevity. You can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/Qwik\/tree\/start\">get all the starter code from our GitHub repo<\/a>, including styles.<\/p>\n<p>Let\u2019s write the <code>Header<\/code> component.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/layout\/header.tsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { component$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Link } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik-city\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> component$(() =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">nav<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\ud83d\udc7e Etttro<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>Latest products<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/about\"<\/span>&gt;<\/span>About us<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">nav<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span><\/span>\n  );\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_008cdf408875825d45fa7d75656beb27\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Our app will have three pages:<\/p>\n<ul>\n<li>A home page listing the latest products on offer.<\/li>\n<li>A single product page that shows product details.<\/li>\n<li>A simple about page.<\/li>\n<\/ul>\n<p>We can create hard-coded product data to simulate data retrieval on the server.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/data\/retro-hardware.ts<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> retroHardware = &#91;\n  {\n    <span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-number\">1<\/span>,\n    <span class=\"hljs-attr\">title<\/span>: <span class=\"hljs-string\">\"Commodore 64\"<\/span>,\n    <span class=\"hljs-attr\">priceInCents<\/span>: <span class=\"hljs-number\">14999<\/span>,\n    <span class=\"hljs-attr\">imageUrl<\/span>: <span class=\"hljs-string\">\"commodore-64.jpg\"<\/span>,\n    <span class=\"hljs-attr\">publishedAt<\/span>: <span class=\"hljs-string\">\"2024-06-01T10:00:00Z\"<\/span>,\n    <span class=\"hljs-attr\">description<\/span>: <span class=\"hljs-string\">\"Classic Commodore 64 in working condition...\"<\/span>,\n  },\n  {\n    <span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-number\">2<\/span>,\n    <span class=\"hljs-attr\">title<\/span>: <span class=\"hljs-string\">\"Virtual Boy\"<\/span>,\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n  },\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n] <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-keyword\">const<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> retroHardware;\n\n<span class=\"hljs-keyword\">export<\/span> type Product = (<span class=\"hljs-keyword\">typeof<\/span> retroHardware)&#91;number];\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_abbcefc212cd809a566fea771170070f\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0<a href=\"https:\/\/github.com\/PhraseApp-Blog\/Qwik\/blob\/start\/src\/data\/retro-hardware.ts\">Get the entire file from GitHub<\/a>.<\/p>\n<p>Our home page can now \u201cpull in\u201d this data and display it.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/routes\/index.tsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { component$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> {\n  Link,\n  routeLoader$,\n  type DocumentHead,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik-city\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> retroHardware, {\n  type Product,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"~\/data\/retro-hardware\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> useProducts = routeLoader$&lt;\n  Readonly&lt;Product&#91;]&gt;\n&gt;<span class=\"hljs-function\">(<span class=\"hljs-params\">(<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> retroHardware;\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> component$(() =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> productsS = useProducts();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>Latest products<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        {productsS.value.map((product) =&gt; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span>\n            <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{<\/span>`<span class=\"hljs-attr\">products<\/span>\/${<span class=\"hljs-attr\">product.id<\/span>}`}\n            <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{product.id}<\/span>\n          &gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">article<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h3<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>{product.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h3<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n                <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>\n                <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{600}<\/span>\n                <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{600}<\/span>\n                <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{product.title}<\/span>\n                <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{<\/span>`\/<span class=\"hljs-attr\">product-img<\/span>\/${<span class=\"hljs-attr\">product.imageUrl<\/span>}`}\n              \/&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>${product.priceInCents \/ 100.0}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n                  {new Date(\n                    product.publishedAt,\n                  ).toLocaleDateString(\"en-US\")}\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n                {product.description.slice(0, 65)}...\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">article<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n        ))}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n  );\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> head: DocumentHead = {\n  <span class=\"hljs-attr\">title<\/span>: <span class=\"hljs-string\">\"Etttro | Retro Hardware Marketplace\"<\/span>,\n  <span class=\"hljs-attr\">meta<\/span>: &#91;\n    {\n      <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"description\"<\/span>,\n      <span class=\"hljs-attr\">content<\/span>: <span class=\"hljs-string\">\"Etttro is your community second-hand...\"<\/span>,\n    },\n  ],\n};\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_4c4d6585ecb412dcc8f519052914086b\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>To get to the i18n as quickly as possible, we\u2019ll skip the code for the single product and about pages, but you can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/Qwik\/tree\/start\">get all the starter code from GitHub<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-my-app-with-qwik-speak\"><\/span>How do I localize my app with Qwik Speak?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Localizing a Qwik app with Qwik Speak involves the following steps.<\/p>\n<p>1. Install and configure the Qwik Speak library.<br \/>\n2. Move hard-coded strings to translation files.<br \/>\n3. Use Qwik Speak\u2019s <code>t()<\/code> function to display the localized strings.<br \/>\n4. Set up localized routing.<br \/>\n5. Build a language switcher UI.<br \/>\n6. Automatically extract strings out of our components for translation.<br \/>\n7. Handle dynamic values in translations.<br \/>\n8. Work with plurals in translations.<br \/>\n9. Format localized numbers and dates.<\/p>\n<p>We\u2019ll go through these steps in detail. Let\u2019s start with installing the i18n library.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-install-and-configure-qwik-speak\"><\/span>How do I install and configure Qwik Speak?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The usual <code>npm install<\/code> will do us here, except notice that Qwik Speak is installed as a <strong>development<\/strong> <strong><\/strong>dependency.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npm install qwik-speak --save-dev\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_0fea40d53f44acc3af97a35502566e98\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Next, let\u2019s set up the <code>qwikSpeakInline<\/code> Vite plugin in <code>vite.config.ts<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ vite.config.ts\n\n  import { defineConfig, type UserConfig } from \"vite\";\n  import { qwikVite } from \"@builder.io\/qwik\/optimizer\";\n  import { qwikCity } from \"@builder.io\/qwik-city\/vite\";\n<span class=\"hljs-addition\">+ import { qwikSpeakInline } from 'qwik-speak\/inline';<\/span>\n  import tsconfigPaths from \"vite-tsconfig-paths\";\n  import pkg from \".\/package.json\";\n\n  \/\/...\n\n  export default defineConfig(({ command, mode }): UserConfig =&gt; {\n    return {\n      plugins: &#91;\n        qwikCity(),\n        qwikVite(),\n<span class=\"hljs-addition\">+       qwikSpeakInline({<\/span>\n<span class=\"hljs-addition\">+         supportedLangs: &#91;'en-US', 'ar-EG'],<\/span>\n<span class=\"hljs-addition\">+         defaultLang: 'en-US',<\/span>\n<span class=\"hljs-addition\">+         assetsPath: 'i18n'<\/span>\n<span class=\"hljs-addition\">+       }),<\/span>\n        tsconfigPaths(),\n      ],\n      optimizeDeps: {\n        exclude: &#91;],\n      },\n      \/\/ ...\n    };\n  });\n\n\/\/ ...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_6cb5a4fe53f08a6661bb0c2f7cf60cd0\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Qwik Speak resolves its translations during SSR (server-side rendering) and <strong>inlines<\/strong> them into pre-compiled chunks of our app for the browser, keeping the client-side performant.<\/p>\n<p>A Vite plugin is used to generate these translation chunks at compile time. This is the plugin we configured above. Here\u2019s what we set:<\/p>\n<ul>\n<li><code>supportedLangs<\/code> \u2014 the locales that we guarantee translations for, set as language tags; here we\u2019re supporting <code>en-US<\/code> (English as used in the United States) and <code>ar-EG<\/code> (Arabic as used in Egypt). Feel free to support any locales you want.<\/li>\n<li><code>defaultLang<\/code> \u2014 this needs to be one of our <code>supportedLangs<\/code> and is used as a fallback when we can\u2019t resolve a locale for the user.<\/li>\n<li><code>assetsPath<\/code> \u2014\u00a0the directory, relative to the project root, where translation files will be stored; we set ours to <code>i18n<\/code>.<\/li>\n<\/ul>\n<h3><span class=\"ez-toc-section\" id=\"a-note-on-locales\"><\/span>A note on locales<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A locale defines a language, a region, and sometimes more. Locales typically use <a href=\"https:\/\/en.wikipedia.org\/wiki\/IETF_language_tag\">IETF BCP 47 language tags<\/a>, like <code>en<\/code> for English, <code>fr<\/code> for French, and <code>es<\/code> for Spanish. Adding a region with the ISO Alpha-2 code (e.g., <code>BH<\/code> for Bahrain, <code>CN<\/code> for China, <code>US<\/code> for the United States) is recommended for accurate date and number localization. So a complete locale might look like <code>en-US<\/code> for American English or <code>zh-CN<\/code> for Chinese as used in China.<\/p>\n<p>\ud83d\udd17 Explore more language tags on <a href=\"https:\/\/en.wikipedia.org\/wiki\/List_of_ISO_639-1_codes\">Wikipedia<\/a> and find country codes through the ISO&#8217;s <a href=\"https:\/\/www.iso.org\/obp\/ui\/#search\">search tool<\/a>.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Learn more about the <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/tools\/inline\">Qwik Speak Inline Vite plugin<\/a> from the official docs.<\/p>\n<p>Next, we need to add two configuration files, <code>speak-config.ts<\/code> and <code>speak-functions.ts<\/code>. These go directly into the <code>src<\/code> directory.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/speak-config.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> type { SpeakConfig } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"qwik-speak\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> config: SpeakConfig = {\n  <span class=\"hljs-comment\">\/\/ Note that we expand on the locale config<\/span>\n  <span class=\"hljs-comment\">\/\/ here, setting a currency and time zone.<\/span>\n  <span class=\"hljs-attr\">defaultLocale<\/span>: {\n    <span class=\"hljs-attr\">lang<\/span>: <span class=\"hljs-string\">\"en-US\"<\/span>,\n    <span class=\"hljs-attr\">currency<\/span>: <span class=\"hljs-string\">\"USD\"<\/span>,\n    <span class=\"hljs-attr\">timeZone<\/span>: <span class=\"hljs-string\">\"America\/Los_Angeles\"<\/span>,\n  },\n  <span class=\"hljs-attr\">supportedLocales<\/span>: &#91;\n    {\n      <span class=\"hljs-attr\">lang<\/span>: <span class=\"hljs-string\">\"ar-EG\"<\/span>,\n      <span class=\"hljs-attr\">currency<\/span>: <span class=\"hljs-string\">\"USD\"<\/span>,\n      <span class=\"hljs-attr\">timeZone<\/span>: <span class=\"hljs-string\">\"Africa\/Cairo\"<\/span>,\n    },\n    {\n      <span class=\"hljs-attr\">lang<\/span>: <span class=\"hljs-string\">\"en-US\"<\/span>,\n      <span class=\"hljs-attr\">currency<\/span>: <span class=\"hljs-string\">\"USD\"<\/span>,\n      <span class=\"hljs-attr\">timeZone<\/span>: <span class=\"hljs-string\">\"America\/Los_Angeles\"<\/span>,\n    },\n  ],\n\n  <span class=\"hljs-comment\">\/\/ Translations available in the whole<\/span>\n  <span class=\"hljs-comment\">\/\/ app. These map to files under our<\/span>\n  <span class=\"hljs-comment\">\/\/ `i18n\/{lang}` directories.<\/span>\n  <span class=\"hljs-attr\">assets<\/span>: &#91;<span class=\"hljs-string\">\"app\"<\/span>],\n\n  <span class=\"hljs-comment\">\/\/ Translations that require dynamic keys,<\/span>\n  <span class=\"hljs-comment\">\/\/ and cannot be set at compile-time.<\/span>\n  <span class=\"hljs-attr\">runtimeAssets<\/span>: &#91;],\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b293007168055be24dab215e5225ea87\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/speak-functions.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { server$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik-city\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> type {\n  LoadTranslationFn,\n  Translation,\n  TranslationFn,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"qwik-speak\"<\/span>;\n\n<span class=\"hljs-comment\">\/**\n * Translation files are lazy-loaded via dynamic import\n * and will be split into separate chunks during build.\n * Assets names and keys must be valid variable names.\n *\/<\/span>\n<span class=\"hljs-keyword\">const<\/span> translationData = <span class=\"hljs-keyword\">import<\/span>.meta.glob&lt;Translation&gt;(\n  <span class=\"hljs-string\">\"\/i18n\/**\/*.json\"<\/span>,\n);\n\n<span class=\"hljs-comment\">\/**\n * Using server$, translation data is always accessed\n * on the server.\n *\/<\/span>\n<span class=\"hljs-keyword\">const<\/span> loadTranslation$: LoadTranslationFn = server$(\n  <span class=\"hljs-keyword\">async<\/span> (lang: string, <span class=\"hljs-attr\">asset<\/span>: string) =&gt;\n    <span class=\"hljs-keyword\">await<\/span> translationData&#91;<span class=\"hljs-string\">`\/i18n\/<span class=\"hljs-subst\">${lang}<\/span>\/<span class=\"hljs-subst\">${asset}<\/span>.json`<\/span>](),\n);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> translationFn: TranslationFn = {\n  <span class=\"hljs-attr\">loadTranslation$<\/span>: loadTranslation$,\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_7dcebdadc05a27d1d21044645fb0d86c\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The translation functions determine how our translation files are loaded on the server. Qwik Speak lets us write our translation functions any way we want. We\u2019ll stick to the defaults for this guide.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0The translation files are loaded from the same directory set when configuring the Vite plugin, <code>\/i18n<\/code>. Each locale will have a subdirectory e.g. <code>\/i18n\/en-US<\/code> .<\/p>\n<p>Let\u2019s pull this config into the root of our app to provide it with the Qwik Speak context.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/root.tsx\n\n  import { component$ } from \"@builder.io\/qwik\";\n  import {\n    QwikCityProvider,\n    RouterOutlet,\n    ServiceWorkerRegister,\n  } from \"@builder.io\/qwik-city\";\n  import { RouterHead } from \".\/components\/router-head\/router-head\";\n<span class=\"hljs-addition\">+ import { useQwikSpeak } from \"qwik-speak\";<\/span>\n<span class=\"hljs-addition\">+ import { config } from \".\/speak-config\";<\/span>\n<span class=\"hljs-addition\">+ import { translationFn } from \".\/speak-functions\";<\/span>\n\n  import \".\/global.css\";\n\n  export default component$(() =&gt; {\n<span class=\"hljs-addition\">+   useQwikSpeak({ config, translationFn });<\/span>\n\n    return (\n      &lt;QwikCityProvider&gt;\n        &lt;head&gt;\n          &lt;meta charset=\"utf-8\" \/&gt;\n          &lt;RouterHead \/&gt;\n          &lt;ServiceWorkerRegister \/&gt;\n        &lt;\/head&gt;\n        &lt;body lang=\"en\" class=\"...\"&gt;\n          &lt;RouterOutlet \/&gt;\n        &lt;\/body&gt;\n      &lt;\/QwikCityProvider&gt;\n    );\n  });\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_d7c7f7d0bf4e42561b1c4fa2d2ae939e\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>One last piece of setup: We need to set the base URL of our app explicitly since Qwik Speak uses the base URL to load its translation chunks. This is configured in our app\u2019s SSR entry point.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/entry.ssr.tsx\n\n<span class=\"hljs-addition\">+ import { isDev } from \"@builder.io\/qwik\/build\";<\/span>\n  import {\n    renderToStream,\n<span class=\"hljs-addition\">+   type RenderOptions,<\/span>\n    type RenderToStreamOptions,\n  } from \"@builder.io\/qwik\/server\";\n  import { manifest } from \"@qwik-client-manifest\";\n  import Root from \".\/root\";\n<span class=\"hljs-addition\">+ import { config } from \".\/speak-config\";<\/span>\n\n<span class=\"hljs-addition\">+ export function extractBase({<\/span>\n<span class=\"hljs-addition\">+   serverData,<\/span>\n<span class=\"hljs-addition\">+ }: RenderOptions): string {<\/span>\n<span class=\"hljs-addition\">+   if (!isDev &amp;&amp; serverData?.locale) {<\/span>\n<span class=\"hljs-addition\">+     return \"\/build\/\" + serverData.locale;<\/span>\n<span class=\"hljs-addition\">+   } else {<\/span>\n<span class=\"hljs-addition\">+     return \"\/build\";<\/span>\n<span class=\"hljs-addition\">+   }<\/span>\n<span class=\"hljs-addition\">+ }<\/span>\n\n  export default function (opts: RenderToStreamOptions) {\n<span class=\"hljs-addition\">+   \/\/ Let's set the lang attribute on the<\/span>\n<span class=\"hljs-addition\">+   \/\/ &lt;html&gt; tag while we're at it.<\/span>\n<span class=\"hljs-addition\">+   const lang =<\/span>\n<span class=\"hljs-addition\">+     opts.serverData?.locale || config.defaultLocale.lang;<\/span>\n\n    return renderToStream(&lt;Root \/&gt;, {\n      manifest,\n      ...opts,\n<span class=\"hljs-addition\">+     base: extractBase,<\/span>\n      containerAttributes: {\n<span class=\"hljs-addition\">+       lang,<\/span>\n        ...opts.containerAttributes,\n      },\n      serverData: {\n        ...opts.serverData,\n      },\n    });\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_cd3280cb4ecdc00490b9c918f4e5297a\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><code>serverData.locale<\/code> will be set to the resolved locale by Qwik Speak. Right now it will always be the default locale, <code>en-US<\/code>. We\u2019ll look at how we can change this in the following sections.<\/p>\n<p>Alright, that\u2019s about it for setup. Let\u2019s see if this all works, shall we? We\u2019ll add our first translations, starting with the app title. Let\u2019s add two translation files, <code>i18n\/en-US\/app.json<\/code> and <code>i18n\/ar-EG\/app.json<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ i18n\/en-US\/app.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"appTitle\"<\/span>: <span class=\"hljs-string\">\"Ettro\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b293007168055be24dab215e5225ea87\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ i18n\/ar-EG\/app.json<\/span>\n\n{\n  <span class=\"hljs-attr\">\"appTitle\"<\/span>: <span class=\"hljs-string\">\"\u0625\u062a\u0631\u0648\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c4d9dc0215ff8ab2ade38d30becc2171\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>As per our config, Qwik Speak expects our translation files to be at <code>i18n\/{locale}\/{asset}.json<\/code>.  The <code>{asset}.json<\/code> part is just the namespace file; <code>app<\/code> is the default namespace file. Now let\u2019s use this translation in our header component.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/components\/layout\/header.tsx\n\n  import { component$ } from \"@builder.io\/qwik\";\n  import { Link } from \"@builder.io\/qwik-city\";\n<span class=\"hljs-addition\">+ import { inlineTranslate } from \"qwik-speak\";<\/span>\n\n  export default component$(() =&gt; {\n<span class=\"hljs-addition\">+   const t = inlineTranslate();<\/span>\n\n    return (\n      &lt;header class=\"...\"&gt;\n        &lt;nav class=\"...\"&gt;\n          &lt;Link href=\"\/\"&gt;\n            &lt;span class=\"...\"&gt;\n<span class=\"hljs-deletion\">-             \ud83d\udc7e Etttro<\/span>\n<span class=\"hljs-addition\">+             \ud83d\udc7e {t(\"appTitle\")}<\/span>\n            &lt;\/span&gt;\n          &lt;\/Link&gt;\n\n          &lt;ul class=\"...\"&gt;\n            {\/* ... *\/}\n          &lt;\/ul&gt;\n        &lt;\/nav&gt;\n      &lt;\/header&gt;\n    );\n  });\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_08947a933251b28e5b1f54370c7430dd\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The <code>t()<\/code> function tells Qwik Speak to swap in a translation at compile time. Again, <code>app<\/code> is the default namespace\/asset file, so Qwik Speak knows to look in the <code>app.json<\/code> file under the resolved locale for the <code>appTitle<\/code> key. The resolved locale is currently the default, <code>en-US<\/code>, so if we reload our app it should look exactly as before.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-89848\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/app-title-en.png\" alt=\"Screenshot of the Etttro demo app's header showing the app title &quot;Etttro&quot; with a pixelated alien icon, and navigation links for &quot;Latest products&quot; and &quot;About us&quot; on a dark green background.\" width=\"622\" height=\"104\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/app-title-en.png 622w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/app-title-en-300x50.png 300w\" sizes=\"(max-width: 622px) 100vw, 622px\" \/><\/p>\n<p>However, if we change the <code>defaultLocale<\/code> to Arabic in our <code>speak-config.ts<\/code> file, we should see our app title translated to Arabic.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/speak-config.ts\n\nimport type { SpeakConfig } from \"qwik-speak\";\n\nexport const config: SpeakConfig = {\n  defaultLocale: {\n<span class=\"hljs-deletion\">-   lang: \"en-US\",<\/span>\n<span class=\"hljs-addition\">+   lang: \"ar-EG\",<\/span>\n    currency: \"USD\",\n    timeZone: \"America\/Los_Angeles\",\n  },\n  \/\/ ...\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2fe4901367dfd0f2a69be5c77ea70c4b\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-89842\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/app-title-ar.png\" alt=\"Screenshot of the Etttro demo app's header showing the app title in Arabic with a pixelated alien icon, and navigation links for &quot;Latest products&quot; and &quot;About us&quot; in English on a dark green background.\" width=\"570\" height=\"98\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/app-title-ar.png 570w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/app-title-ar-300x52.png 300w\" sizes=\"(max-width: 570px) 100vw, 570px\" \/><\/p>\n<p>Alright! We got Qwik Speak working. Let\u2019s set up localized routing next.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0You can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/Qwik\/\">get the completed app code from our GitHub repo<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-configure-localized-routing\"><\/span>How do I configure localized routing?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We often want localized apps with routes like <code>\/foo<\/code> for English resources and <code>\/ar-EG\/foo<\/code> for Arabic. The locale segment of each route would resolve to the active locale for the request, and we would serve content for that locale.<\/p>\n<p>We can accomplish this by setting up some middleware and moving our routes to handle the dynamic locale segment.<\/p>\n<p>First, let\u2019s move our routes. We\u2019ll create a directory called <code>[...lang]<\/code> under <code>\/src\/routes<\/code> and move all our app routes there.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\"># BEFORE\n.\n\u2514\u2500\u2500 src\/\n    \u2514\u2500\u2500 routes\/\n        \u251c\u2500\u2500 about\/\n        \u2502   \u2514\u2500\u2500 index.tsx\n        \u251c\u2500\u2500 products\/\n        \u2502   \u2514\u2500\u2500 &#91;id]\/\n        \u2502       \u2514\u2500\u2500 index.ts\n        \u2514\u2500\u2500 index.tsx\n\n\n# AFTER\n.\n\u2514\u2500\u2500 src\/\n    \u2514\u2500\u2500 routes\/\n        \u2514\u2500\u2500 &#91;...lang]\/\n            \u251c\u2500\u2500 about\/\n            \u2502   \u2514\u2500\u2500 index.tsx\n            \u251c\u2500\u2500 products\/\n            \u2502   \u2514\u2500\u2500 &#91;id]\/\n            \u2502       \u2514\u2500\u2500 index.ts\n            \u2514\u2500\u2500 index.tsx\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">plaintext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_dff7cee1464fce218369720f98a24f75\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p><code>[...lang]<\/code> is a catch-all <a href=\"https:\/\/qwik.dev\/docs\/routing\/#dynamic-route-segments\">dynamic route segment<\/a> corresponding to the <code>ar-EG<\/code> part of <code>\/ar-EG\/foo<\/code>. We can now use this <code>lang<\/code> route param to resolve the active locale in our new plugin middleware:<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/routes\/plugin.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> type { RequestHandler } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik-city\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> {\n  setSpeakContext,\n  validateLocale,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"qwik-speak\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { config } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/speak-config\"<\/span>;\n\n<span class=\"hljs-comment\">\/**\n * This middleware function must only contain the logic to\n * set the locale, because it is invoked on every request\n * to the server.\n * Avoid redirecting or throwing errors here, and prefer\n * layouts or pages.\n *\/<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> onRequest: RequestHandler = ({\n  params,\n  locale,\n}) =&gt; {\n  <span class=\"hljs-keyword\">let<\/span> lang: string | <span class=\"hljs-literal\">undefined<\/span> = <span class=\"hljs-literal\">undefined<\/span>;\n\n  <span class=\"hljs-keyword\">if<\/span> (params.lang &amp;&amp; validateLocale(params.lang)) {\n    lang = config.supportedLocales.find(\n      <span class=\"hljs-function\">(<span class=\"hljs-params\">value<\/span>) =&gt;<\/span> value.lang === params.lang,\n    )?.lang;\n  } <span class=\"hljs-keyword\">else<\/span> {\n    lang = config.defaultLocale.lang;\n  }\n\n  <span class=\"hljs-comment\">\/\/ Set Qwik locale.<\/span>\n  locale(lang);\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_9c82778c858dc85592e8a65d30261dd6\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We check for a <code>lang<\/code> route param and use Qwik Speak\u2019s <code>validateLocale<\/code> function to ensure it\u2019s in the correct format (e.g. <code>en-US<\/code>, <code>ar-EG<\/code>, <code>it-IT<\/code>). If invalid, <code>lang<\/code> will be set to the configured default locale, <code>en-US<\/code>. If all is well, we further check that <code>lang<\/code> corresponds to a locale our app supports. <\/p>\n<p>Whatever <code>lang<\/code> ends up being, we set it as the app\u2019s locale using Qwik\u2019s <code>locale()<\/code> function. This function is used internally by Qwik Speak to determine the active locale.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0If <code>lang<\/code> is <code>undefined<\/code> when we call <code>locale()<\/code>, Qwik Speak will resolve the active locale to the default locale, <code>en-US<\/code>.<\/p>\n<p>We should see our app title in English if we visit our default routes e.g. <code>\/<\/code> or <code>\/about<\/code>. However, if we visit <code>\/ar-EG<\/code> or <code>\/ar-EG\/about<\/code>, the app title should be in Arabic. We\u2019ve successfully set the app locale using the route prefix.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Qwik Speak also supports <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/tutorial-routing#domain-based-routing\">domain-based routing<\/a>, where our locales are determined by the top-level domain, e.g. <code>example.ar<\/code> or <code>example.it<\/code>. Also available is <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/tutorial-routing-rewrite\">route rewriting<\/a>, where sub-routes are translated, e.g. <code>example.com\/about<\/code> is translated to <code>example.com\/ar-EG\/\u0639\u0646\u0627<\/code>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"forcing-a-locale-prefix\"><\/span>Forcing a locale prefix<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Qwik Speak insists on leaving the default locale out of routes. We can, however, force a route prefix for the default locale. This will cause our English routes to always be <code>\/en-US\/foo<\/code>. <\/p>\n<p>First, let\u2019s handle the case where the <code>lang<\/code> route param doesn\u2019t correspond to a locale supported by our app; we want to send the client a 404 error response.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/routes\/layout.tsx\n\n  import { component$, Slot } from \"@builder.io\/qwik\";\n  import type { RequestHandler } from \"@builder.io\/qwik-city\";\n  import { routeLoader$ } from \"@builder.io\/qwik-city\";\n  import Footer from \"~\/components\/layout\/footer\";\n  import Header from \"~\/components\/layout\/header\";\n<span class=\"hljs-addition\">+ import { config } from \"~\/speak-config\";<\/span>\n\n  export const onGet: RequestHandler = async ({\n    cacheControl,\n<span class=\"hljs-addition\">+   params,<\/span>\n<span class=\"hljs-addition\">+   send,<\/span>\n  }) =&gt; {\n    cacheControl({\n      staleWhileRevalidate: 60 * 60 * 24 * 7,\n      maxAge: 5,\n    });\n\n<span class=\"hljs-addition\">+   if (<\/span>\n<span class=\"hljs-addition\">+     !config.supportedLocales.find(<\/span>\n<span class=\"hljs-addition\">+       (loc) =&gt; loc.lang === params.lang,<\/span>\n<span class=\"hljs-addition\">+     )<\/span>\n<span class=\"hljs-addition\">+   ) {<\/span>\n<span class=\"hljs-addition\">+     send(404, \"Not Found\");<\/span>\n<span class=\"hljs-addition\">+   }<\/span>\n<span class=\"hljs-addition\">+ };<\/span>\n\n  export const useServerTimeLoader = routeLoader$(() =&gt; {\n    return {\n      date: new Date().toISOString(),\n    };\n  });\n\n  export default component$(() =&gt; {\n    return (\n      &lt;&gt;\n        &lt;Header \/&gt;\n        &lt;main class=\"...\"&gt;\n          &lt;Slot \/&gt;\n        &lt;\/main&gt;\n        &lt;Footer \/&gt;\n      &lt;\/&gt;\n    );\n  });\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_63bf9a6d6fb9c14f8795c6f034964177\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now let\u2019s force a locale prefix from the home route. If we land on <code>\/<\/code> let\u2019s redirect to <code>\/en-US<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/routes\/&#91;...lang]\/index.tsx\n\n  import { component$ } from \"@builder.io\/qwik\";\n  import {\n    routeLoader$,\n    type DocumentHead,\n<span class=\"hljs-addition\">+   type RequestHandler,<\/span>\n  } from \"@builder.io\/qwik-city\";\n<span class=\"hljs-addition\">+ import { config } from \"~\/speak-config\";<\/span>\n\/\/ ...\n\n<span class=\"hljs-addition\">+ export const onGet: RequestHandler = async ({<\/span>\n<span class=\"hljs-addition\">+   params,<\/span>\n<span class=\"hljs-addition\">+   redirect,<\/span>\n<span class=\"hljs-addition\">+ }) =&gt; {<\/span>\n<span class=\"hljs-addition\">+   if (!params.lang) {<\/span>\n<span class=\"hljs-addition\">+     throw redirect(301, `\/${config.defaultLocale.lang}\/`);<\/span>\n<span class=\"hljs-addition\">+   }<\/span>\n<span class=\"hljs-addition\">+ };<\/span>\n\n\/\/ ...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_127c3974d1ff74584c2c6eecee381514\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now if a visitor lands on the <code>\/<\/code> route, we redirect to the <code>\/en-US<\/code> route. The rest of our locale-forcing logic will be covered in the next section.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-my-links\"><\/span>How do I localize my links?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We can roll our own localized link component to ensure our links keep the active locale prefix. Qwik Speak has a handy <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak#routing\">localizePath function<\/a> that can help you here if you didn\u2019t force the locale prefix like we did above. We <strong>are<\/strong> forcing the locale prefix in this guide, so let\u2019s write our own function to localize any path.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/  src\/util\/i18n\/loc-path.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { config } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"~\/speak-config\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">locPath$<\/span>(<span class=\"hljs-params\">\n  path: string,\n  lang: string,\n<\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (path === <span class=\"hljs-string\">\"\/\"<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`\/<span class=\"hljs-subst\">${lang}<\/span>`<\/span>;\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> pathParts = path\n    .split(<span class=\"hljs-string\">\"\/\"<\/span>)\n    .filter(<span class=\"hljs-function\">(<span class=\"hljs-params\">segment<\/span>) =&gt;<\/span> segment);\n\n  <span class=\"hljs-keyword\">if<\/span> (\n    config.supportedLocales.find(\n      <span class=\"hljs-function\">(<span class=\"hljs-params\">locale<\/span>) =&gt;<\/span> locale.lang === pathParts&#91;<span class=\"hljs-number\">0<\/span>],\n    )\n  ) {\n    pathParts&#91;<span class=\"hljs-number\">0<\/span>] = lang;\n  } <span class=\"hljs-keyword\">else<\/span> {\n    pathParts.unshift(lang);\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`\/<span class=\"hljs-subst\">${pathParts.join(<span class=\"hljs-string\">\"\/\"<\/span>)}<\/span>`<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_51e8dc082f997bbfcc084822e38569bf\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Given a <code>path<\/code> and a locale code, <code>lang<\/code>, <code>locPath$<\/code> will convert that path to one prefixed with <code>lang<\/code>. Some examples:<\/p>\n<ul>\n<li><code>locPath$(&quot;\/&quot;, &quot;ar-EG&quot;)<\/code> \u2192 <code>&quot;\/ar-EG&quot;<\/code><\/li>\n<li><code>locPath$(&quot;\/en-US\/foo&quot;, &quot;ar-EG&quot;)<\/code> \u2192 <code>&quot;\/ar-EG\/foo&quot;<\/code><\/li>\n<li><code>locPath$(&quot;\/ar-EG\/foo&quot;, &quot;en-US&quot;)<\/code> \u2192 <code>&quot;\/en-US\/foo&quot;<\/code><\/li>\n<\/ul>\n<p>We can now use <code>locPath$<\/code> in a new <code>LocLink<\/code> component for localized links.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/i18n\/loc-link.tsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Slot, component$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> {\n  Link,\n  useLocation,\n  type LinkProps,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik-city\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> locPath$ <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"~\/util\/i18n\/loc-path\"<\/span>;\n\ntype LocLinkProps = LinkProps &amp; { <span class=\"hljs-attr\">href<\/span>: string };\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> component$(\n  ({ href, ...props }: LocLinkProps) =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> {\n      <span class=\"hljs-attr\">params<\/span>: { lang },\n    } = useLocation();\n    <span class=\"hljs-keyword\">const<\/span> localizedHref = locPath$(href, lang);\n\n    <span class=\"hljs-keyword\">return<\/span> (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{localizedHref}<\/span> {<span class=\"hljs-attr\">...props<\/span>}&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Slot<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span><\/span>\n    );\n  },\n);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_4ccee61b4b0b5ebc14a21937aa8a7fcf\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We\u2019re effectively extending Qwik\u2019s built-in <code>Link<\/code> component, and localizing the given <code>href<\/code> prop. For example, if we\u2019re given <code>\/foo<\/code> as the <code>href<\/code> value, and the active locale is Arabic, we set the <code>href<\/code> on the <code>Link<\/code> to be <code>\/ar-EG\/foo<\/code>.<\/p>\n<p><code>LocLink<\/code> can be used as a drop-in replacement for <code>Link<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/components\/layout\/header.tsx\n\n  import { component$ } from \"@builder.io\/qwik\";\n<span class=\"hljs-deletion\">- import { Link } from \"@builder.io\/qwik-city\";<\/span>\n<span class=\"hljs-addition\">+ import LocLink from \"..\/i18n\/loc-link\";<\/span>\n  import { inlineTranslate } from \"qwik-speak\";\n\n  export default component$(() =&gt; {\n    const t = inlineTranslate();\n\n    return (\n      &lt;header class=\"...\"&gt;\n        &lt;div class=\"...\"&gt;\n          &lt;nav class=\"...\"&gt;\n<span class=\"hljs-deletion\">-           &lt;Link href=\"\/\"&gt;<\/span>\n<span class=\"hljs-addition\">+           &lt;LocLink href=\"\/\"&gt;<\/span>\n              &lt;span class=\"text-2xl font-thin\"&gt;\n                \ud83d\udc7e {t(\"appTitle\")}\n              &lt;\/span&gt;\n<span class=\"hljs-deletion\">-           &lt;\/Link&gt;<\/span>\n<span class=\"hljs-addition\">+           &lt;\/LocLink&gt;<\/span>\n            &lt;ul class=\"...\"&gt;\n              &lt;li&gt;\n<span class=\"hljs-deletion\">-               &lt;Link href=\"\/\"&gt;<\/span>\n<span class=\"hljs-addition\">+               &lt;LocLink href=\"\/\"&gt;<\/span>\n                  {t(\"nav.latestProducts\")}\n<span class=\"hljs-deletion\">-               &lt;\/Link&gt;<\/span>\n<span class=\"hljs-addition\">+               &lt;\/LocLink&gt;<\/span>\n              &lt;\/li&gt;\n              &lt;li&gt;\n<span class=\"hljs-deletion\">-               &lt;Link href=\"\/about\"&gt;<\/span>\n<span class=\"hljs-addition\">+               &lt;LocLink href=\"\/about\"&gt;<\/span>\n                  {t(\"nav.aboutUs\")}\n<span class=\"hljs-deletion\">-               &lt;\/Link&gt;<\/span>\n<span class=\"hljs-addition\">+               &lt;\/LocLink&gt;<\/span>\n              &lt;\/li&gt;\n            &lt;\/ul&gt;\n          &lt;\/nav&gt;\n        &lt;\/div&gt;\n      &lt;\/header&gt;\n    );\n  });\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e5f2fffb3e36b59e6ae638f3f71bb094\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now our links will always include the locale prefix for the active locale.<\/p>\n<figure id=\"attachment_89866\" aria-describedby=\"caption-attachment-89866\" style=\"width: 600px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-89866 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/forced-locale-localized-links.gif\" alt=\"An animation showing various links in the app being clicked. The browser address bar always shows the en-US\/ locale route prefix whenever a link is clicked.\" width=\"600\" height=\"502\" \/><figcaption id=\"caption-attachment-89866\" class=\"wp-caption-text\">Note that the en-US prefix is always present in the browser address bar.<\/figcaption><\/figure>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-build-a-language-switcher\"><\/span>How do I build a language switcher?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We often want to allow our visitors to select their preferred locale. Qwik Speak doesn\u2019t have a function to switch locales, so we need to reload the app when a new locale is selected. Let\u2019s build a locale switcher <code>&lt;select&gt;<\/code> component to achieve this.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/i18n\/locale-switcher.tsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { $, component$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useLocation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik-city\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> {\n  useSpeakLocale,\n  type SpeakLocale,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"qwik-speak\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { config } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"~\/speak-config\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> locPath$ <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"~\/util\/i18n\/loc-path\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> langNames: Record&lt;SpeakLocale&#91;<span class=\"hljs-string\">\"lang\"<\/span>], string&gt; = {\n  <span class=\"hljs-string\">\"en-US\"<\/span>: <span class=\"hljs-string\">\"English\"<\/span>,\n  <span class=\"hljs-string\">\"ar-EG\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u0639\u0631\u0628\u064a\u0629 (Arabic)\"<\/span>,\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> component$(() =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">lang<\/span>: activeLang } = useSpeakLocale();\n  <span class=\"hljs-keyword\">const<\/span> loc = useLocation();\n\n  <span class=\"hljs-keyword\">const<\/span> changeLocale$ = $((evt: Event) =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> selectedLang = (evt.target <span class=\"hljs-keyword\">as<\/span> HTMLSelectElement)\n      .value;\n\n    <span class=\"hljs-comment\">\/\/ Reload the whole app\/page with the newly<\/span>\n    <span class=\"hljs-comment\">\/\/ selected locale.<\/span>\n    <span class=\"hljs-built_in\">window<\/span>.location.href = locPath$(\n      loc.url.pathname,\n      selectedLang,\n    );\n  });\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">select<\/span>\n      <span class=\"hljs-attr\">onChange<\/span>$=<span class=\"hljs-string\">{changeLocale$}<\/span>\n      <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>\n    &gt;<\/span>\n      {config.supportedLocales.map(({ lang }) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span>\n          <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{lang}<\/span>\n          <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{lang}<\/span>\n          <span class=\"hljs-attr\">selected<\/span>=<span class=\"hljs-string\">{lang<\/span> === <span class=\"hljs-string\">activeLang}<\/span>\n        &gt;<\/span>\n          {langNames&#91;lang]}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n      ))}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">select<\/span>&gt;<\/span><\/span>\n  );\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_803287b75d954165684be59e22505f28\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak#context\">useSpeakLocale()<\/a> function returns an object representing the active locale; a <code>lang<\/code> string on the object has the locale code (<code>\u201den-US\" | \"ar-EG\"<\/code>). We use this to set the selected language option in the <code>&lt;select&gt;<\/code> dropdown.<\/p>\n<p>When the visitor chooses a new language from the dropdown, we reload the app, swapping the newly selected language into the current URL. For example, let\u2019s say the visitor is on a product details page in English and they switch to Arabic: We take the current URL, <code>\/en-US\/products\/1<\/code>, use our <code>locPath$<\/code> function to convert it to <code>\/ar-EG\/products\/1<\/code>, and reload the app with this new URL.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-89890 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/locale-switcher.gif\" alt=\"An animation showing the locale switcher dropdown in action. The dropdown is clicked, revealing options for English and Arabic. When Arabic is selected, the current page is shown with Arabic translations. When English is selected, the page is shown with English translations.\" width=\"600\" height=\"530\" \/><\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0If you\u2019re coding along, remember to place the new <code>LocaleSwitcher<\/code> component in your <code>Header<\/code> component.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0You can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/Qwik\">get all the code we cover in this guide from our GitHub repo<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-extract-translations-from-code\"><\/span>How do I extract translations from code?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Qwik Speak features a handy CLI that can save us time by extracting translations from our code files. Let\u2019s see it in action.<\/p>\n<p>First, we\u2019ll add a new NPM script to our <code>package.json<\/code> file.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ package.json\n\n{\n  \"name\": \"my-qwik-basic-starter\",\n  \"description\": \"Demo App with Routing built-in (recommended)\",\n  \/\/ ...\n  \"scripts\": {\n    \/\/...\n    \"start\": \"vite --open --mode ssr\",\n    \"qwik\": \"qwik\",\n<span class=\"hljs-addition\">+   \"i18n:extract\": \"qwik-speak-extract --supportedLangs=en-US,ar-EG --assetsPath=i18n --unusedKeys=true\"<\/span>\n  },\n  \"devDependencies\": {\n    \/\/...\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_2aab02d9671f23a267d0433c41f962e7\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The <code>qwik-speak-extract<\/code> command will search our code for calls to <code>t(key)<\/code>. The command will extract all the <code>key<\/code>s found and place them in translation files under the <code>--assetsPath<\/code> directory. It will create one file for each entry we provide in <code>--supportedLangs<\/code>.<\/p>\n<p>Let\u2019s run the command to see what it does. Recall the translations in our <code>Header<\/code> component.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/layout\/header.tsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { component$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { inlineTranslate } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"qwik-speak\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> LocLink <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/i18n\/loc-link\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> LocaleSwitcher <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/i18n\/locale-switcher\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> component$(() =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> t = inlineTranslate();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">nav<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">LocLink<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n              {\/* \ud83d\udc47 The extraction script will look\n                     for `t(key)` calls. This will be\n                     extracted into `app.json`. *\/}\n              \ud83d\udc7e {t(\"appTitle\")}\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">LocLink<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">LocLink<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n                {\/* \ud83d\udc47 This will be extracted into\n                       `nav.json`.  *\/}\n                {t(\"nav.latestProducts\")}\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">LocLink<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">LocLink<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/about\"<\/span>&gt;<\/span>\n                {t(\"nav.aboutUs\")}\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">LocLink<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">nav<\/span>&gt;<\/span>\n\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">LocaleSwitcher<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span><\/span>\n  );\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_8ec8ea68fe8bc04dc39af49ee01172b4\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Let\u2019s remove all the translation JSON files we created earlier under the <code>i18n<\/code> directory, then run <code>npm run i18n:extract<\/code> from the command line.<\/p>\n<p>If all goes well, we should see the files re-created by the extraction script.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">.\n\u2514\u2500\u2500 i18n\/\n    \u251c\u2500\u2500 ar-EG\/\n    \u2502   \u251c\u2500\u2500 app.json\n    \u2502   \u2514\u2500\u2500 nav.json\n    \u2514\u2500\u2500 en-US\/\n        \u251c\u2500\u2500 app.json\n        \u2514\u2500\u2500 nav.json\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">plaintext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1fa732a620530ddfddf4cb672f674926\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>All keys without namespaces, like <code>appTitle<\/code>, go into the default <code>app.json<\/code> file.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ i18n\/en-US\/app.json<\/span>\n{\n  <span class=\"hljs-attr\">\"appTitle\"<\/span>: <span class=\"hljs-string\">\"\"<\/span>\n}\n\n<span class=\"hljs-comment\">\/\/ i18n\/ar-EG\/app.json<\/span>\n{\n  <span class=\"hljs-attr\">\"appTitle\"<\/span>: <span class=\"hljs-string\">\"\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_a0ef1b4a981208f641732c8a752692a6\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Keys with namespaces, like <code>nav.latestProducts<\/code>, go into files named after the given namespace. <\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ i18n\/en-US\/nav.json<\/span>\n{\n  <span class=\"hljs-attr\">\"nav\"<\/span>: {\n    <span class=\"hljs-attr\">\"aboutUs\"<\/span>: <span class=\"hljs-string\">\"\"<\/span>,\n    <span class=\"hljs-attr\">\"latestProducts\"<\/span>: <span class=\"hljs-string\">\"\"<\/span>\n  }\n}\n\n<span class=\"hljs-comment\">\/\/ i18n\/ar-EG\/nav.json<\/span>\n{\n  <span class=\"hljs-attr\">\"nav\"<\/span>: {\n    <span class=\"hljs-attr\">\"aboutUs\"<\/span>: <span class=\"hljs-string\">\"\"<\/span>,\n    <span class=\"hljs-attr\">\"latestProducts\"<\/span>: <span class=\"hljs-string\">\"\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_7d21f9c255a60eecf2f8a6c7cd4ec309\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The translation files work the same as they did before.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0 <code>qwik-speak-extract<\/code> is smart enough not to override existing key\/value pairs.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Heads up<\/strong> <strong><\/strong>\u00bb<strong> <\/strong>Every new file\/namespace must be added to the <code>src\/speak-config.ts<\/code> file\u2019s <code>assets<\/code> array.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/speak-config.ts\n\nimport type { SpeakConfig } from \"qwik-speak\";\n\nexport const config: SpeakConfig = {\n  \/\/ ...\n  supportedLocales: &#91;\n    \/\/...\n  ],\n  \/\/ Translations available in the whole app\n<span class=\"hljs-deletion\">- assets: &#91;\"app\"],<\/span>\n<span class=\"hljs-addition\">+ assets: &#91;\"app\", \"nav\"],<\/span>\n  runtimeAssets: &#91;],\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_a8e66cfa22b5b611dce1cc057584e499\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>If we want to prune keys no longer in our codebase, we can use the <code>--unusedKeys=true<\/code> CLI flag as we have above. This will cause the command to remove the keys we\u2019re no longer using.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0If you don\u2019t like your extracted translations to have blank values, you can provide default translations with your keys that will end up as values in your extracted files. Read about default values and all the extraction CLI options on the <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/tools\/extract\">Qwik Speak Extract<\/a> page of the official docs.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0These files can be uploaded to a string management platform like <a href=\"https:\/\/phrase.com\/platform\/strings\/\">Phrase Strings<\/a>, where translators can work on various language files; an automated process can pull the translation files back into the project when ready. <\/p>\n<h2><span class=\"ez-toc-section\" id=\"some-notes-on-translation-files\"><\/span>Some notes on translation files<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We won\u2019t cover the following in detail here, but we found the following aspects of Qwik Speak translation files noteworthy.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"runtime-translations\"><\/span>Runtime translations<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Qwik Speak translations are <strong>compiled<\/strong> on the server and baked into utilizing components as hard-coded strings. This makes our app very performant, as it avoids any translation logic running on the client. However, this means that Qwik Speak needs to statically analyze translation keys at compile time. What if we needed to calculate a translation key at runtime? This is where <strong>runtime translations<\/strong> come in. <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/translate#runtime-translation\">Read about runtime translations in the official docs<\/a>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"lazy-loaded-translations\"><\/span>Lazy-loaded translations<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In large applications, we may need to delay loading specific translations until they are required. Qwik Speak allows this via lazy loading. The idea is to exclude specific translation files from the <code>speak-config.js<\/code> <code>assets<\/code> array and load them manually with <code>useSpeak()<\/code>. <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/lazy-loading\">Read about lazy-loading in the official docs<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-deal-with-right-to-left-languages\"><\/span>How do I deal with right-to-left languages?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Arabic, Hebrew, Persian (Farsi), Urdu, and others are laid out right to left. Modern browsers do a good job of accommodating right-to-left layouts. However, we should set the <code>&lt;html dir&gt;<\/code> attribute to <code>rtl<\/code> to get our pages flowing correctly. <\/p>\n<p>Before that, let\u2019s install a handy little package called rtl-detect that detects the direction of a given locale.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npm install --save-dev rtl-detect @types\/rtl-detect\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1e259ea01e1e89a32f39d8a94c346a98\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We can install rtl-detect as a dev dependency because we\u2019ll only run it on the server.<\/p>\n<p>Now let\u2019s use the package to set the <code>&lt;html dir&gt;<\/code> attribute during SSR (server-side rendering).<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/entry.ssr.tsx\n\n  import { isDev } from \"@builder.io\/qwik\/build\";\n  import {\n    renderToStream,\n    type RenderOptions,\n    type RenderToStreamOptions,\n  } from \"@builder.io\/qwik\/server\";\n  import { manifest } from \"@qwik-client-manifest\";\n<span class=\"hljs-addition\">+ import rtlDetect from \"rtl-detect\";<\/span>\n  import Root from \".\/root\";\n  import { config } from \".\/speak-config\";\n\n  export function extractBase(\/* ... *\/) {\n    \/\/ ...\n  }\n\n  export default function (opts: RenderToStreamOptions) {\n    const lang =\n      opts.serverData?.locale || config.defaultLocale.lang;\n\n    return renderToStream(&lt;Root \/&gt;, {\n      manifest,\n      ...opts,\n      base: extractBase,\n      \/\/ Use container attributes to set attributes on\n      \/\/ the html tag.\n      containerAttributes: {\n        lang,\n<span class=\"hljs-addition\">+       \/\/ Use rtl-detect to determine the dir of<\/span>\n<span class=\"hljs-addition\">+       \/\/ the current locale and set it as the<\/span>\n<span class=\"hljs-addition\">+       \/\/ `&lt;html dir&gt;` attribute.<\/span>\n<span class=\"hljs-addition\">+       dir: rtlDetect.getLangDir(lang),<\/span>\n        ...opts.containerAttributes,\n      },\n      serverData: {\n        ...opts.serverData,\n      },\n    });\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_ccbd0a67775e0aca4cd64faccdefad8e\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>With this code in place, our Arabic pages will have a <code>&lt;html dir&gt;<\/code> value of <code>rtl<\/code>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-89872 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/html-dir-1024x123.png\" alt=\"Screenshot of the browser inspector DOM view showing the HTML element with attributes: lang=&quot;ar-EG&quot;, and dir=&quot;rtl&quot;.\" width=\"1024\" height=\"123\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/html-dir-1024x123.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/html-dir-300x36.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/html-dir-768x92.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/html-dir.png 1462w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>And, of course, this causes our page to be laid out from right to left.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-89914 aligncenter\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/rtl-1024x717.png\" alt=\"Screenshot of the Etttro demo app's home page showing the latest products laid out right-to-left. The header is in Arabic with a language switcher button labeled &quot;(Arabic) \u0627\u0644\u0639\u0631\u0628\u064a\u0629&quot;. The products displayed are a Sony Walkman for $199 dated 2024-06-10, a Nintendo Virtual Boy for $229 dated 2024-06-05, and a Commodore 64 for $149.99 dated 2024-06-01. The app has a dark green background.\" width=\"1024\" height=\"717\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/rtl-1024x717.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/rtl-300x210.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/rtl-768x538.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/rtl.png 1242w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0There\u2019s more to layout localization than simply <code>&lt;html dir&gt;<\/code>. Check out our <a href=\"https:\/\/phrase.com\/blog\/posts\/how-do-i-use-a-css-file-for-site-localization\/\">CSS Localization<\/a> guide for more details.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-on-the-server\"><\/span>How do I localize on the server?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Unlike components, we need to supply the current locale explicitly when using <code>inlineTranslate<\/code> in server contexts like endpoints and router loaders.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> useMyTranslatedString = routeLoader$&lt;string&gt;(\n  <span class=\"hljs-function\">(<span class=\"hljs-params\">requestEvent<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> t = inlineTranslate();\n    <span class=\"hljs-comment\">\/\/ We need to provide the locale explicitly<\/span>\n    <span class=\"hljs-comment\">\/\/ to `t()`. We can get the Qwik locale, from<\/span>\n    <span class=\"hljs-comment\">\/\/ `requestEvent.locale()`. We set this in our<\/span>\n    <span class=\"hljs-comment\">\/\/ plugin middleware.<\/span>\n    <span class=\"hljs-keyword\">const<\/span> translatedString = t(\n      <span class=\"hljs-string\">\"myTranslation\"<\/span>,\n      {},\n      requestEvent.locale(),\n    );\n\n    <span class=\"hljs-keyword\">return<\/span> translatedString;\n  },\n);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_849d817b53dde98ad286140f4b64a231\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Read more about <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/translate#server-translation\">Server translation<\/a> in the official docs.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0If you want to localize your static-side generated (SSG) Qwik apps, check out the <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/adapters#static-site-generation-ssg\">Static Site Generation (SSG)<\/a> of the docs.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"how-do-i-localize-page-metadata\"><\/span>How do I localize page metadata?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Since document metadata is generated on the server, it can be localized similarly to other server-side contexts. Let\u2019s provide the translations first.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ i18n\/en-US\/app.json\n\n{\n<span class=\"hljs-addition\">+ \"app\": {<\/span>\n<span class=\"hljs-addition\">+   \"meta\": {<\/span>\n<span class=\"hljs-addition\">+     \"description\": \"Etttro is your community second-hand market for all retro electronics.\",<\/span>\n<span class=\"hljs-addition\">+     \"title\": \"Etttro | Retro Hardware Marketplace\"<\/span>\n<span class=\"hljs-addition\">+   }<\/span>\n<span class=\"hljs-addition\">+ },<\/span>\n  \"appTitle\": \"Etttro\",\n  \/\/ ...\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b293007168055be24dab215e5225ea87\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ i18n\/ar-EG\/app.json\n\n{\n<span class=\"hljs-addition\">+ \"app\": {<\/span>\n<span class=\"hljs-addition\">+   \"meta\": {<\/span>\n<span class=\"hljs-addition\">+     \"description\": \"\u0625\u062a\u0631\u0648 \u0647\u0648 \u0633\u0648\u0642 \u0645\u062c\u062a\u0645\u0639\u064a \u0644\u062c\u0645\u064a\u0639 \u0627\u0644\u0623\u062c\u0647\u0632\u0629 \u0627\u0644\u0625\u0644\u0643\u062a\u0631\u0648\u0646\u064a\u0629 \u0627\u0644\u0631\u062c\u0639\u064a\u0629 \u0627\u0644\u0645\u0633\u062a\u0639\u0645\u0644\u0629.\",<\/span>\n<span class=\"hljs-addition\">+     \"title\": \"\u0625\u062a\u0631\u0648 | \u0633\u0648\u0642 \u0627\u0644\u0623\u062c\u0647\u0632\u0629 \u0627\u0644\u0631\u062c\u0639\u064a\u0629\"<\/span>\n<span class=\"hljs-addition\">+   }<\/span>\n<span class=\"hljs-addition\">+ },<\/span>\n  \"appTitle\": \"\u0625\u062a\u0631\u0648\",\n  \/\/ ...\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_8b7f639b9a708ea39711d922f363b0ab\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Instead of the inline object, we need to use the function form of Qwik Speak\u2019s <code>head<\/code>. This allows us to access route <code>params<\/code>, including the <code>lang<\/code> param, which we can pass to <code>t()<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/routes\/&#91;...lang]\/index.tsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { component$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> {\n  routeLoader$,\n  type DocumentHead,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik-city\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { inlineTranslate } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"qwik-speak\"<\/span>;\n\n<span class=\"hljs-comment\">\/\/ ...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> component$(() =&gt; {\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> head: DocumentHead = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ params }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> t = inlineTranslate();\n\n  <span class=\"hljs-comment\">\/\/ We pass the `lang` route param to `t()` as<\/span>\n  <span class=\"hljs-comment\">\/\/ the third argument.<\/span>\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">title<\/span>: t(<span class=\"hljs-string\">\"app.meta.title\"<\/span>, {}, params.lang),\n    <span class=\"hljs-attr\">meta<\/span>: &#91;\n      {\n        <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"description\"<\/span>,\n        <span class=\"hljs-attr\">content<\/span>: t(<span class=\"hljs-string\">\"app.meta.description\"<\/span>, {}, params.lang),\n      },\n    ],\n  };\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_1ba580c0f7bf8b7751a37bea5cfd376a\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<figure id=\"attachment_89896\" aria-describedby=\"caption-attachment-89896\" style=\"width: 1024px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-89896 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/metadata-ar-1024x138.png\" alt=\"Screenshot of the browser DOM inspector showing the metadata of the HTML document. The title tag contains (in Arabic) &quot;\u0625\u062a\u062a\u0631\u0648 | \u0633\u0648\u0642 \u0627\u0644\u0623\u062c\u0647\u0632\u0629 \u0627\u0644\u0631\u062c\u0639\u064a\u0629&quot;. The meta description tag contains (in Arabic) &quot;\u0625\u062a\u062a\u0631\u0648 \u0647\u0648 \u0633\u0648\u0642 \u0645\u062c\u062a\u0645\u0639\u064a \u0644\u062c\u0645\u064a\u0639 \u0627\u0644\u0623\u062c\u0647\u0632\u0629 \u0627\u0644\u0625\u0644\u0643\u062a\u0631\u0648\u0646\u064a\u0629 \u0627\u0644\u0631\u062c\u0639\u064a\u0629 \u0627\u0644\u0645\u0633\u062a\u0639\u0645\u0644\u0629.&quot; Other visible elements include canonical link, viewport meta tag, and favicon link.\" width=\"1024\" height=\"138\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/metadata-ar-1024x138.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/metadata-ar-300x41.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/metadata-ar-768x104.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/metadata-ar.png 1198w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption id=\"caption-attachment-89896\" class=\"wp-caption-text\">The title and meta description are translated to Arabic when our home page locale is ar-EG.<\/figcaption><\/figure>\n<p>The title and meta description are translated to Arabic when our home page locale is <code>ar-EG<\/code>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-work-with-basic-translation-messages\"><\/span>How do I work with basic translation messages?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We\u2019ve covered basic translations a few times by now, so we\u2019ll briefly refresh ourselves before we cover more advanced formatting topics.<\/p>\n<p>Translated strings are added to our translation files, one for each locale.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ i18n\/en-US\/app.json<\/span>\n{\n  <span class=\"hljs-comment\">\/\/...<\/span>\n  <span class=\"hljs-attr\">\"water\"<\/span>: <span class=\"hljs-string\">\"Water\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b293007168055be24dab215e5225ea87\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-39\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ i18n\/ar-EG\/app.json<\/span>\n{\n  <span class=\"hljs-comment\">\/\/...<\/span>\n  <span class=\"hljs-attr\">\"water\"<\/span>: <span class=\"hljs-string\">\"\u0645\u0627\u0621\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-39\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_967c7ec53603a162eb559317317b8fd1\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>We use the <code>t()<\/code> function to pull translations into our components.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-40\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { component$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { inlineTranslate } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"qwik-speak\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> component$(() =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> t = inlineTranslate();\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{t(\"water\")}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span>\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-40\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_762f1d65f6ae3e4c88841a8aef2881ad\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The above renders \u201cWater\u201d when the active locale is <code>en-US<\/code> and \u201c\u0645\u0627\u0621\u201d when the active locale is <code>ar-EG<\/code>.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0Instead of adding strings manually to translation files, we can use the CLI to extract them automatically. See the extraction section above for more.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-work-with-dynamic-values-in-translation-messages\"><\/span>How do I work with dynamic values in translation messages?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We sometimes need to inject runtime values into our translations. Qwik Speak\u2019s translation message format allows for this using a <code>{{variable}}<\/code> placeholder syntax. Let\u2019s add a user greeting to our home page to demonstrate.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-41\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ i18n\/en-US\/app.json\n  {\n    \"appTitle\": \"Ettro\",\n    \/\/...\n<span class=\"hljs-addition\">+   \"userGreeting\": \"Hello, {{username}}.\"<\/span>\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-41\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b293007168055be24dab215e5225ea87\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-42\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ i18n\/ar-EG\/app.json\n  {\n    \"appTitle\": \"\u0625\u062a\u0631\u0648\",\n    \/\/...\n<span class=\"hljs-addition\">+   \"userGreeting\": \"\u0645\u0631\u062d\u0628\u064b\u0627\u060c {{username}}.\"<\/span>\n  }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-42\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_a2e49e558be2df80b203ce1356c596e5\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Notice the <code>{{username}}<\/code> syntax in the above messages. This will be replaced by a <code>username<\/code> argument when we call <code>t()<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-43\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/routes\/&#91;...lang]\/index.tsx\n\nimport { $, component$ } from \"@builder.io\/qwik\";\n\/\/ ...\nimport {\n  inlineTranslate,\n  type Translation,\n} from \"qwik-speak\";\n\/\/ ...\n\n\/\/ ...\n\nexport default component$(() =&gt; {\n  const t = inlineTranslate();\n  const productsS = useProducts();\n\n  return (\n    &lt;&gt;\n      &lt;div class=\"...\"&gt;\n        &lt;h1 class=\"...\"&gt;\n          {t(\"homePageTitle\")}\n        &lt;\/h1&gt;\n\n<span class=\"hljs-addition\">+       &lt;p&gt;{t(\"userGreeting\", { username: \"Hannah\" })}&lt;\/p&gt;<\/span>\n      &lt;\/div&gt;\n\n      &lt;section class=\"...\"&gt;\n        {\/* ... *\/}\n      &lt;\/section&gt;\n    &lt;\/&gt;\n  );\n});\n\n\/\/ ...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-43\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c85f78aa13d97d2eda38ca33c6677855\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The second argument to <code>t()<\/code> is a map of key\/value pairs that will replace the placeholders at runtime, matching each by key\/name.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-89884\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/interpolation-1024x533.png\" alt=\"Screenshots of the user greeting in English and Arabic. The English greeting reads, &quot;Hello, Hannah.&quot; The Arabic one reads, &quot;\u0645\u0631\u062d\u0628\u0627\u064b \u0647\u0646\u0627&quot;.\" width=\"1024\" height=\"533\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/interpolation-1024x533.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/interpolation-300x156.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/interpolation-768x400.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/interpolation.png 1322w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Read more about <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/translate#params-interpolation\">Params interpolation<\/a> in the official docs.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-work-with-localized-plurals\"><\/span>How do I work with localized plurals?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Plurals in translation messages are more than just switching between singular and plural; different languages have a variety of plural forms. For instance, English has two forms: \u201cone message\u201d and \u201cmany messages.\u201d Meanwhile, some languages like Chinese have only one plural form, whereas Arabic and Russian each have six. To get it right, we must provide the different plural forms and an integer count to select the correct one.<\/p>\n<p>Qwik Speak offers plural support, using the standard JavaScript <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/PluralRules\">Intl.PluralRules<\/a> object under the hood. Let&#8217;s add a count to our &#8220;Latest products&#8221; header to showcase localized plurals.<\/p>\n<p>First, we\u2019ll add our messages. Plural translations have a special structure: each plural <strong>form<\/strong> is a key\/value pair in a nested object:<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-44\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ i18n\/en-US\/app.json\n\n{\n  \"appTitle\": \"Ettro\",\n<span class=\"hljs-deletion\">- \"latestProducts\": \"Latest products\",<\/span>\n<span class=\"hljs-addition\">+ \"latestProducts\": {<\/span>\n<span class=\"hljs-addition\">+   \"one\": \"{{value}} Latest Product\",<\/span>\n<span class=\"hljs-addition\">+   \"other\": \"{{value}} Latest Products\"<\/span>\n<span class=\"hljs-addition\">+ },<\/span>\n  \/\/ ...\n  \"userGreeting\": \"Hello, {{name}}.\"\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-44\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b293007168055be24dab215e5225ea87\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-45\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ i18n\/ar-EG\/app.json\n{\n  \"appTitle\": \"\u0625\u062a\u0631\u0648\",\n<span class=\"hljs-deletion\">- \"latestProducts\": \"\u0623\u062d\u062f\u062b \u0627\u0644\u0645\u0646\u062a\u062c\u0627\u062a\",<\/span>\n<span class=\"hljs-addition\">+ \"latestProducts\": {<\/span>\n<span class=\"hljs-addition\">+   \"zero\": \"\u0644\u0627 \u062a\u0648\u062c\u062f \u0645\u0646\u062a\u062c\u0627\u062a\",<\/span>\n<span class=\"hljs-addition\">+   \"one\": \"\u0623\u062d\u062f\u062b \u0645\u0646\u062a\u062c\",<\/span>\n<span class=\"hljs-addition\">+   \"two\": \"\u0623\u062d\u062f\u062b \u0645\u0646\u062a\u062c\u064a\u0646\",<\/span>\n<span class=\"hljs-addition\">+   \"few\": \"\u0623\u062d\u062f\u062b {{value}} \u0645\u0646\u062a\u062c\u0627\u062a\",<\/span>\n<span class=\"hljs-addition\">+   \"many\": \"\u0623\u062d\u062f\u062b {{value}} \u0645\u0646\u062a\u062c\",<\/span>\n<span class=\"hljs-addition\">+   \"other\": \"\u0623\u062d\u062f\u062b {{value}} \u0645\u0646\u062a\u062c\"<\/span>\n<span class=\"hljs-addition\">+ },<\/span>\n  \/\/ ...\n  \"userGreeting\": \"\u0645\u0631\u062d\u0628\u064b\u0627\u060c {{name}}.\"\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-45\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_0fdf08c1cc1be71ed315fcd3237d7f2a\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>As we mentioned, English has two plural forms, <code>one<\/code> and <code>other<\/code>, while Arabic has six plural forms. At runtime, the <code>{{value}}<\/code> placeholder will be replaced by given the integer counter.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0The <a href=\"https:\/\/www.unicode.org\/cldr\/charts\/42\/supplemental\/language_plural_rules.html\">CLDR Language Plural Rules<\/a> page is the canonical, and handy, listing of all languages\u2019 plural forms.<\/p>\n<p>To use plural messages in our components, we call Qwik Speak\u2019s <code>inlinePlural<\/code> instead of <code>inlineTranslate<\/code>.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-46\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">import { $, component$ } from \"@builder.io\/qwik\";\n\/\/ ...\nimport {\n<span class=\"hljs-addition\">+ inlinePlural,<\/span>\n  inlineTranslate,\n  type Translation,\n} from \"qwik-speak\";\n\n\/\/...\n\nexport default component$(() =&gt; {\n  const t = inlineTranslate();\n<span class=\"hljs-addition\">+ const p = inlinePlural();<\/span>\n  const productsS = useProducts();\n\n  return (\n    &lt;&gt;\n      &lt;div class=\"...\"&gt;\n        &lt;h1 class=\"...\"&gt;\n<span class=\"hljs-deletion\">-         {t(\"latestProducts\")}<\/span>\n<span class=\"hljs-addition\">+         {\/* The first argument is an integer<\/span>\n<span class=\"hljs-addition\">+             counter used to select the plural<\/span>\n<span class=\"hljs-addition\">+             form. The second argument is the<\/span>\n<span class=\"hljs-addition\">+             message key. *\/}<\/span>\n<span class=\"hljs-addition\">+         {p(productsS.value.length, \"latestProducts\")}<\/span>\n        &lt;\/h1&gt;\n\n        &lt;p&gt;{t(\"userGreeting\", { name: \"Hannah\" })}&lt;\/p&gt;\n      &lt;\/div&gt;\n\n      &lt;section class=\"...\"&gt;\n        {\/* ... *\/}\n      &lt;\/section&gt;\n    &lt;\/&gt;\n  );\n});\n\n\/\/ ...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-46\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_c64b7d2ee5c2213cef6e1cdb8946296b\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Now, depending on the number of products, the correct plural form is shown for the active locale.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-89908\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/plurals-1024x616.png\" alt=\"Screenshot comparing plural forms in English (en-US) and Arabic (ar-EG) for the phrase &quot;Latest Product.&quot; In English, &quot;one&quot; maps to &quot;1 Latest Product&quot; and &quot;other&quot; maps to &quot;3 Latest Products.&quot; In Arabic, &quot;zero&quot; maps to &quot;\u0644\u0627 \u062a\u0648\u062c\u062f \u0645\u0646\u062a\u062c\u0627\u062a,&quot; &quot;one&quot; maps to &quot;\u0623\u062d\u062f\u062b \u0645\u0646\u062a\u062c,&quot; &quot;two&quot; maps to &quot;\u0623\u062d\u062f\u062b \u0645\u0646\u062a\u062c\u064a\u0646,&quot; &quot;few&quot; maps to &quot;\u0623\u062d\u062f\u062b 3 \u0645\u0646\u062a\u062c\u0627\u062a,&quot; &quot;many&quot; maps to &quot;\u0623\u062d\u062f\u062b 11 \u0645\u0646\u062a\u062c,&quot; and &quot;other&quot; maps to &quot;\u0623\u062d\u062f\u062b 100 \u0645\u0646\u062a\u062c.&quot;\" width=\"1024\" height=\"616\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/plurals-1024x616.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/plurals-300x181.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/plurals-768x462.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/plurals.png 1467w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0If using the CLI to extract messages, <strong>all<\/strong> forms will be extracted for all languages. We need to remove the unused forms for the language ourselves.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0Unlike other i18n libraries, Qwik Speak doesn\u2019t seem to provide a way to override a language\u2019s standard plural forms. For example, if we wanted to handle the non-standard zero case in English, we would need to write conditional logic to handle this in our component code.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Read more about <a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/translate#inlineplural\">inlinePural<\/a> in the official docs.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0We go deep in <a href=\"https:\/\/phrase.com\/blog\/posts\/pluralization\/\">Pluralization: A Guide to Localizing Plurals<\/a> if you want to learn more.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-add-html-to-my-translation-messages\"><\/span>How do I add HTML to my translation messages?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Adding HTML markup to translation messages requires using Qwik\u2019s <code>dangerouslySetInnerHTML<\/code> prop. Since translations are effectively hard-coded, or <strong>inlined<\/strong>, at build time, there is little risk of the XSS (Cross-Site Scripting) attacks normally associated with rendering raw HTML. Let\u2019s add a footer component to our app to demonstrate.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-47\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ i18n\/en-US\/app.json\n\n{\n  \"appTitle\": \"Etttro\",\n<span class=\"hljs-addition\">+ \"footerText\": \"&lt;strong&gt;Etttro&lt;\/strong&gt; is a demo app made with Qwik &amp;amp; Qwik Speak.\",<\/span>\n  \/\/...\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-47\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_b293007168055be24dab215e5225ea87\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-48\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ i18n\/ar-EG\/app.json\n\n{\n  \"appTitle\": \"\u0625\u062a\u0631\u0648\",\n<span class=\"hljs-addition\">+ \"footerText\": \"&lt;strong&gt;\u0625\u062a\u0631\u0648&lt;\/strong&gt; \u0647\u0648 \u062a\u0637\u0628\u064a\u0642 \u062a\u062c\u0631\u064a\u0628\u064a \u062a\u0645 \u062a\u0635\u0645\u064a\u0645\u0647 \u0628\u0627\u0633\u062a\u062e\u062f\u0627\u0645 Qwik &amp;amp; Qwik Speak.\",<\/span>\n  \/\/ ...\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-48\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_9f2b42210caa1bfec790eefe49c12c19\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Our new footer component:<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-49\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/layout\/footer.tsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { component$ } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@builder.io\/qwik\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { inlineTranslate } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"qwik-speak\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> component$(() =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> t = inlineTranslate();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">footer<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>\n          <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"...\"<\/span>\n          <span class=\"hljs-attr\">dangerouslySetInnerHTML<\/span>=<span class=\"hljs-string\">{t(<\/span>\"<span class=\"hljs-attr\">footerText<\/span>\")}\n        &gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">footer<\/span>&gt;<\/span><\/span>\n  );\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-49\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_01f731264e186a75c48612c7f9c5fe9e\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Again, note the <code>dangerouslySetInnerHTML<\/code>, which allows injecting raw <code>innerHTML<\/code> into a DOM element.<\/p>\n<figure id=\"attachment_89878\" aria-describedby=\"caption-attachment-89878\" style=\"width: 930px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-89878 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/html-in-messages.png\" alt=\"Screenshot of browser DOM inspector showing rendered HTML. A  paragraph with the class attribute &quot;text-center&quot; is shown containing the text &quot;Etttro is a demo app made with Qwik &amp; Qwik Speak,&quot; where &quot;Etttro&quot; is enclosed in a strong tag.\" width=\"930\" height=\"260\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/html-in-messages.png 930w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/html-in-messages-300x84.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/html-in-messages-768x215.png 768w\" sizes=\"(max-width: 930px) 100vw, 930px\" \/><figcaption id=\"caption-attachment-89878\" class=\"wp-caption-text\">The HTML in the translation message is rendered as-is.<\/figcaption><\/figure>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0If you\u2019re coding along, tuck the new <code>Footer<\/code> component in the root layout to see it render.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-format-localized-numbers\"><\/span>How do I format localized numbers?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>i18n is more than just string translations. Working with numbers and dates is crucial for most apps, and each region handles number and date formatting differently.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"a-note-on-regional-formatting\"><\/span>A note on regional formatting<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Number and date formatting are determined by region, not just language. For example, the US and Canada use English but have different date formats and measurement units. So it&#8217;s better to use a qualified locale (like <code>en-US<\/code>) instead of just a language code (<code>en<\/code>).<\/p>\n<p>Using a language code alone, such as <code>ar<\/code> for Arabic, can lead to inconsistency. Different browsers might default to various regions, like Saudi Arabia (<code>ar-SA<\/code>) or Egypt (<code>ar-EG<\/code>), resulting in varied date formats due to distinct regional calendars.<\/p>\n<p>Luckily, Qwik Speak provides a number formatting function built on the robust standard <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/NumberFormat\">Intl.NumberFormat<\/a> object. Let\u2019s use it to format our product prices.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-50\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ src\/routes\/&#91;...lang]\/index.tsx\n\nimport { $, component$ } from \"@builder.io\/qwik\";\n\/\/ ...\nimport {\n  inlinePlural,\n  inlineTranslate,\n<span class=\"hljs-addition\">+ useFormatNumber,<\/span>\n  type Translation,\n} from \"qwik-speak\";\nimport LocLink from \"~\/components\/i18n\/loc-link\";\n\n\/\/ ...\n\nexport default component$(() =&gt; {\n  const t = inlineTranslate();\n  const p = inlinePlural();\n<span class=\"hljs-addition\">+ const fn = useFormatNumber();<\/span>\n  const productsS = useProducts();\n\n  return (\n    &lt;&gt;\n      &lt;div class=\"...\"&gt;\n        {\/* ... *\/}\n      &lt;\/div&gt;\n\n      &lt;section class=\"...\"&gt;\n        {productsS.value.map((product) =&gt; (\n          &lt;LocLink\n            href={`products\/${product.id}`}\n            key={product.id}\n          &gt;\n            &lt;article class=\"...\"&gt;\n              {\/* ... *\/}\n              &lt;img\n                width={600}\n                height={600}\n                alt={product.title}\n                class=\"block aspect-square w-full\"\n                src={`\/product-img\/${product.imageUrl}`}\n              \/&gt;\n              &lt;div class=\"...\"&gt;\n<span class=\"hljs-deletion\">-               &lt;p&gt;${product.priceInCents \/ 100.0}&lt;\/p&gt;<\/span>\n<span class=\"hljs-addition\">+               &lt;p&gt;<\/span>\n<span class=\"hljs-addition\">+                 {fn(product.priceInCents \/ 100.0, {<\/span>\n<span class=\"hljs-addition\">+                   style: \"currency\",<\/span>\n<span class=\"hljs-addition\">+                 })}<\/span>\n<span class=\"hljs-addition\">+               &lt;\/p&gt;<\/span>\n                {\/* ... *\/}\n              &lt;\/div&gt;\n              &lt;p class=\"...\"&gt;\n                {product.description.slice(0, 65)}...\n              &lt;\/p&gt;\n            &lt;\/article&gt;\n          &lt;\/LocLink&gt;\n        ))}\n      &lt;\/section&gt;\n    &lt;\/&gt;\n  );\n});\n\n\/\/ ...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-50\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_12bcd511158b79dd2ecd854fbbed8555\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>The <code>useFormatNumber<\/code> function returns a number formatting function, <code>fn<\/code>, which takes a number and formatting options. These options are the same ones the <code>Intl.NumberFormat<\/code> constructor accepts. <\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong> We can format numbers with different precisions, and format them as units, percentages, and more. <strong><\/strong><strong><\/strong>\u00a0<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/NumberFormat\/NumberFormat#parameters\">See all the options Intl.NumberFormat accepts in the MDN docs<\/a>.<\/p>\n<p>In the above call to <code>fn<\/code>, we specify a currency style. <code>fn<\/code> is locale-aware and will use the currency specified in <code>speak-config.ts<\/code> for the active locale.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-51\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/speak-config.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> type { SpeakConfig } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"qwik-speak\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> config: SpeakConfig = {\n  <span class=\"hljs-attr\">defaultLocale<\/span>: {\n    <span class=\"hljs-attr\">lang<\/span>: <span class=\"hljs-string\">\"en-US\"<\/span>,\n    <span class=\"hljs-attr\">currency<\/span>: <span class=\"hljs-string\">\"USD\"<\/span>,\n    <span class=\"hljs-attr\">timeZone<\/span>: <span class=\"hljs-string\">\"America\/Los_Angeles\"<\/span>,\n  },\n  <span class=\"hljs-attr\">supportedLocales<\/span>: &#91;\n    {\n      <span class=\"hljs-attr\">lang<\/span>: <span class=\"hljs-string\">\"ar-EG\"<\/span>,\n      <span class=\"hljs-comment\">\/\/ We can change the currency for Arabic<\/span>\n      <span class=\"hljs-comment\">\/\/ here, but we need to convert it from<\/span>\n      <span class=\"hljs-comment\">\/\/ USD in our app if we do.<\/span>\n      <span class=\"hljs-attr\">currency<\/span>: <span class=\"hljs-string\">\"USD\"<\/span>,\n      <span class=\"hljs-attr\">timeZone<\/span>: <span class=\"hljs-string\">\"Africa\/Cairo\"<\/span>,\n    },\n    {\n      <span class=\"hljs-attr\">lang<\/span>: <span class=\"hljs-string\">\"en-US\"<\/span>,\n      <span class=\"hljs-attr\">currency<\/span>: <span class=\"hljs-string\">\"USD\"<\/span>,\n      <span class=\"hljs-attr\">timeZone<\/span>: <span class=\"hljs-string\">\"America\/Los_Angeles\"<\/span>,\n    },\n  ],\n  <span class=\"hljs-attr\">assets<\/span>: &#91;<span class=\"hljs-string\">\"app\"<\/span>, <span class=\"hljs-string\">\"nav\"<\/span>],\n  <span class=\"hljs-attr\">runtimeAssets<\/span>: &#91;],\n};\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-51\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_bff37a48ee45c4b3f1d6d185513fc18a\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>\ud83d\uddd2\ufe0f <strong>Heads up<\/strong> <strong>\u00bb<\/strong>\u00a0If you specify a currency in the options to <code>fn<\/code>, e.g. <code>fn(29.99, { style: \"currency\", currency: \"EUR\" })<\/code>, it will be overridden by the currency for the active locale in <code>speak-config.ts<\/code>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-89902\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/number-format-818x1024.png\" alt=\"Screenshot of the Etttro demo app showing product listings with localized number formatting in English (en-US) and Arabic (ar-EG). The English section displays prices as $149.99 and $229.00, and the Arabic section displays prices as $US \u0661\u0669\u0669\u066b\u0660\u0660 and $US \u0662\u0663\u0669\u066b\u0660\u0660. The products are a Commodore 64, a Nintendo Virtual Boy, and a Sony Walkman, each with descriptions and prices in the respective languages and formats.\" width=\"818\" height=\"1024\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/number-format-818x1024.png 818w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/number-format-240x300.png 240w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/number-format-768x962.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/number-format-1227x1536.png 1227w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/number-format.png 1230w\" sizes=\"(max-width: 818px) 100vw, 818px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0<a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/translate#useformatnumber\">See the official docs for more info about useFormatNumber<\/a>.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Our <a href=\"https:\/\/phrase.com\/blog\/posts\/number-localization\/\">Concise Guide to Number Localization<\/a> goes into numeral systems, separators, and more regarding number localization.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-format-localized-dates\"><\/span>How do I format localized dates?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Like numbers, we often overlook the importance of date and time localization. It\u2019s important to handle date formatting carefully since dates and times are formatted differently worldwide.<\/p>\n<p>Qwik Speak provides a <code>useFormatDate<\/code> function as a counterpart to <code>useFormatNumber<\/code>. Let\u2019s use <code>useFormatDate<\/code> to localize our product dates.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note<\/strong> <strong>\u00bb<\/strong>\u00a0Localizing dates is similar to localizing numbers, so we recommend you read the previous section before this one.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-52\" data-shcb-language-name=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">import { $, component$ } from \"@builder.io\/qwik\";\n\/\/ ...\nimport {\n  inlinePlural,\n  inlineTranslate,\n<span class=\"hljs-addition\">+ useFormatDate,<\/span>\n  useFormatNumber,\n  type Translation,\n} from \"qwik-speak\";\nimport LocLink from \"~\/components\/i18n\/loc-link\";\n\n\/\/ ...\n\nexport default component$(() =&gt; {\n  const t = inlineTranslate();\n  const p = inlinePlural();\n  const fn = useFormatNumber();\n<span class=\"hljs-addition\">+ const fd = useFormatDate();<\/span>\n  const productsS = useProducts();\n\n  return (\n    &lt;&gt;\n      &lt;div class=\"...\"&gt;\n        {\/* ... *\/}\n      &lt;\/div&gt;\n\n      &lt;section class=\"...\"&gt;\n        {productsS.value.map((product) =&gt; (\n          &lt;LocLink\n            href={`products\/${product.id}`}\n            key={product.id}\n          &gt;\n            &lt;article class=\"...\"&gt;\n              {\/* ... *\/}\n              &lt;div class=\"...\"&gt;\n                &lt;p&gt;\n                  {fn(product.priceInCents \/ 100.0, {\n                      style: \"currency\",\n                   })}\n                &lt;\/p&gt;\n                &lt;p&gt;\n<span class=\"hljs-deletion\">-                 \/\/ We no longer need the custom<\/span>\n<span class=\"hljs-deletion\">-                 \/\/ `toShortDate$` function.<\/span>\n<span class=\"hljs-deletion\">-                 {toShortDate$(product.publishedAt)}<\/span>\n<span class=\"hljs-addition\">+                 {fd(product.publishedAt, {<\/span>\n<span class=\"hljs-addition\">+                   dateStyle: \"short\",<\/span>\n<span class=\"hljs-addition\">+                 })}<\/span>\n                &lt;\/p&gt;\n              &lt;\/div&gt;\n              &lt;p class=\"...\"&gt;\n                {product.description.slice(0, 65)}...\n              &lt;\/p&gt;\n            &lt;\/article&gt;\n          &lt;\/LocLink&gt;\n        ))}\n      &lt;\/section&gt;\n    &lt;\/&gt;\n  );\n});\n\n\/\/ ...\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-52\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Diff<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">diff<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_21c24980430830ba066f1a5a14aa0e6f\" class=\"pxblock pxblock--text alignfull spacing--default bg--white\">\n\n\t\n\t<div class=\"container\">\n\t\t<div class=\"wysiwyg animate-in\">\n\t\t\t<p>Unsurprisingly, the <code>fd<\/code> function is built on top of <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/DateTimeFormat\">Intl.DateTimeFormat<\/a>, and takes a date and formatting options arguments. The options that the <code>Intl.DateTimeFormat<\/code> accepts can be passed as the second argument to <code>fn<\/code>.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/DateTimeFormat\/DateTimeFormat#parameters\">See the MDN docs for Intl.DateTimeFormat for all available formatting options<\/a>.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Heads up \u00bb<\/strong> If we set <code>timeZone<\/code>s for our locales in <code>speak-config.ts<\/code> they will automatically be used by <code>fn<\/code>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-89854\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/date-format-818x1024.png\" alt=\"Screenshot of the Etttro demo app showing product listings with localized date formatting in English (en-US) and Arabic (ar-EG). The English section displays dates as 6\/1\/24 and 6\/5\/24, and the Arabic section displays dates as \u0661\/\u0666\/\u0662\u0660\u0662\u0664 and \u0665\/\u0666\/\u0662\u0660\u0662\u0664. The products are a Commodore 64, a Nintendo Virtual Boy, and a Sony Walkman, each with descriptions and prices in the respective languages and formats.\" width=\"818\" height=\"1024\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/date-format-818x1024.png 818w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/date-format-240x300.png 240w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/date-format-768x962.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/date-format-1227x1536.png 1227w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/07\/date-format.png 1230w\" sizes=\"(max-width: 818px) 100vw, 818px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0<a href=\"https:\/\/robisim74.gitbook.io\/qwik-speak\/library\/translate#useformatdate\">See the official docs for more info about useFormatDate<\/a>.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0Our <a href=\"https:\/\/phrase.com\/blog\/posts\/date-time-localization\/\">Guide to Date and Time Localization<\/a> covers formatting, time zones, regional calendars, and more.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"take-your-qwik-localization-to-the-next-level\"><\/span>Take your Qwik localization to the next level<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We hope you enjoyed this guide to localizing Qwik apps with Qwik Speak.<\/p>\n<p>\ud83d\udd17 <strong>Resource<\/strong> <strong>\u00bb<\/strong>\u00a0<a href=\"https:\/\/github.com\/PhraseApp-Blog\/Qwik\/\">Get the entire demo code from our GitHub repo<\/a>.<\/p>\n<p>When you\u2019re all set to start the translation process, rely on the Phrase Localization Platform to handle the heavy lifting. A specialized software localization platform, the Phrase Localization Platform comes with dozens of tools designed to automate your translation process and native integrations with platforms like GitHub, GitLab, and Bitbucket.<\/p>\n<p>With its user-friendly strings editor, translators can effortlessly access your content and transfer it into your target languages. Once your translations are set, easily integrate them back into your project with a single command or automated sync.<\/p>\n<p>This way, you can stay focused on what you love\u2014your code. <a href=\"https:\/\/eu.phrase.com\/idm-ui\/signup?uiLang=en-US\">Sign up for a free trial<\/a> and see for yourself why developers appreciate using The Phrase Localization Platform for software localization.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>We localize a Qwik app with the Qwik Speak i18n library, covering all the ins and outs of localization.<\/p>\n","protected":false},"author":41,"featured_media":90049,"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-89840","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\/89840"}],"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=89840"}],"version-history":[{"count":9,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/89840\/revisions"}],"predecessor-version":[{"id":90055,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/89840\/revisions\/90055"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media\/90049"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media?parent=89840"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/categories?post=89840"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}