{"id":79623,"date":"2024-03-27T02:10:44","date_gmt":"2024-03-27T01:10:44","guid":{"rendered":"https:\/\/phrase.com\/?p=79623"},"modified":"2024-04-04T10:26:56","modified_gmt":"2024-04-04T08:26:56","slug":"chatgpt-localization","status":"publish","type":"post","link":"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/","title":{"rendered":"Software Localization with ChatGPT"},"content":{"rendered":"\n<div id=\"acf\/text-block_2d14482df624d4e9a7f7c6e23fe4402e\" 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>ChatGPT famously <a href=\"https:\/\/www.reuters.com\/technology\/chatgpt-sets-record-fastest-growing-user-base-analyst-note-2023-02-01\/\">reached 100 million monthly active users two months after its launch<\/a>, shattering adoption records. The LLM (Large Language Model) has taken the world by storm and excels at generating certain kinds of content, code being chief among them. However, utilizing the chatbot to extract quality code takes time and effort. In this guide, we walk through localizing a React demo app with the popular i18next library, sitting side-by-side with ChatGPT as we prompt and iterate to get the best code possible.<\/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\/chatgpt-localization\/#versions-used\" title=\"Versions used\">Versions used<\/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\/chatgpt-localization\/#a-note-on-chatgpt-versions\" title=\"A note on ChatGPT versions\">A note on ChatGPT versions<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#our-demo\" title=\"Our demo\">Our demo<\/a><\/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\/chatgpt-localization\/#the-build-plan\" title=\"The build plan\">The build plan<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#the-prompt\" title=\"The prompt\">The prompt<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#the-response\" title=\"The response\">The response<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#validation-and-iteration\" title=\"Validation and iteration\">Validation and iteration<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#execution\" title=\"Execution\">Execution<\/a><\/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\/chatgpt-localization\/#generated-code-setting-up-i18next\" title=\"Generated code: setting up i18next\">Generated code: setting up i18next<\/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\/chatgpt-localization\/#integrating-i18next-into-components\" title=\"Integrating i18next into components\">Integrating i18next into components<\/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\/chatgpt-localization\/#the-language-switcher\" title=\"The language switcher\">The language switcher<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#the-prompt-2\" title=\"The prompt\">The prompt<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#the-response-2\" title=\"The response\">The response<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#interpolation-and-plurals\" title=\"Interpolation and plurals\">Interpolation and plurals<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#the-prompt-3\" title=\"The prompt\">The prompt<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#the-response-3\" title=\"The response\">The response<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/phrase.com\/blog\/posts\/chatgpt-localization\/#validation-and-iteration-2\" title=\"Validation and iteration\">Validation and iteration<\/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\/chatgpt-localization\/#localized-routes\" title=\"Localized routes\">Localized routes<\/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\/chatgpt-localization\/#key-takeaways\" title=\"Key takeaways\">Key takeaways<\/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\/chatgpt-localization\/#wrapping-up\" title=\"Wrapping up\">Wrapping up<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"versions-used\"><\/span>Versions used<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The demo app we\u2019ll build in this guide uses the following NPM packages. We\u2019ll install them as needed (some will be pre-installed in the starter project).<\/p>\n<table style=\"width: 100%;\">\n<thead>\n<tr>\n<td ><span class=\"link-annotation-unknown-block-id-1146405731\">Package<\/span><\/td>\n<td >Version<\/td>\n<td >Notes<\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><a class=\"notion-link-token notion-focusable-token notion-enable-hover\" tabindex=\"0\" href=\"https:\/\/vitejs.dev\/\" rel=\"noopener noreferrer\" data-token-index=\"0\"><span class=\"link-annotation-unknown-block-id-1146405731\">Vite<\/span><\/a><a href=\"https:\/\/vitejs.dev\"><!-- notionvc: d9c69d2c-9851-4f50-816e-3ccd01b137c6 --><\/a><\/td>\n<td>5.1<\/td>\n<td>Build tool and bundler<\/td>\n<\/tr>\n<tr id=\"1bd8c6d4-e76a-4dff-ad10-0a620f1ed0fa\">\n<td><a href=\"https:\/\/react.dev\/\">React<\/a><\/td>\n<td>18.2<\/td>\n<td>UI library<\/td>\n<\/tr>\n<tr>\n<td><a class=\"notion-link-token notion-focusable-token notion-enable-hover\" tabindex=\"0\" href=\"https:\/\/reactrouter.com\/en\/main\" rel=\"noopener noreferrer\" data-token-index=\"0\"><span class=\"link-annotation-unknown-block-id--1821518404\">React Router<\/span><\/a><a href=\"https:\/\/reactrouter.com\/en\/main\"><!-- notionvc: dcd9851a-fdc8-47a8-8849-298852d41106 --><\/a><\/td>\n<td>6.22<\/td>\n<td>Used for multi-page routing<\/td>\n<\/tr>\n<tr>\n<td><a class=\"notion-link-token notion-focusable-token notion-enable-hover\" tabindex=\"0\" href=\"https:\/\/github.com\/facebook\/prop-types\" rel=\"noopener noreferrer\" data-token-index=\"0\"><span class=\"link-annotation-unknown-block-id--2083925906\">Prop Types<\/span><\/a><a href=\"https:\/\/github.com\/facebook\/prop-types\"><!-- notionvc: c3810238-7fed-4e95-a4f5-5112b629bc81 --><\/a><\/td>\n<td>15.8<\/td>\n<td>React component props validation<\/td>\n<\/tr>\n<tr>\n<td><a class=\"notion-link-token notion-focusable-token notion-enable-hover\" tabindex=\"0\" href=\"https:\/\/tailwindcss.com\/\" rel=\"noopener noreferrer\" data-token-index=\"0\"><span class=\"link-annotation-unknown-block-id--964706048\">Tailwind CSS<\/span><\/a><a href=\"https:\/\/tailwindcss.com\/\"><!-- notionvc: 2cb41fb3-ee55-400a-a16b-4e59afdef9bb --><\/a><\/td>\n<td>3.4.1<\/td>\n<td>Styling framework<\/td>\n<\/tr>\n<tr>\n<td><a class=\"notion-link-token notion-focusable-token notion-enable-hover\" tabindex=\"0\" href=\"https:\/\/www.i18next.com\/\" rel=\"noopener noreferrer\" data-token-index=\"0\"><span class=\"link-annotation-unknown-block-id--639852961\">i18next<\/span><\/a><a href=\"https:\/\/www.i18next.com\/\"><!-- notionvc: 594de441-7948-458c-9e4b-65bc630c5ef2 --><\/a><\/td>\n<td>23.10<\/td>\n<td>Popular i18n library<\/td>\n<\/tr>\n<tr>\n<td><a class=\"notion-link-token notion-focusable-token notion-enable-hover\" tabindex=\"0\" href=\"https:\/\/react.i18next.com\/\" rel=\"noopener noreferrer\" data-token-index=\"0\"><span class=\"link-annotation-unknown-block-id-66663351\">react-i18next<\/span><\/a><a href=\"https:\/\/react.i18next.com\/\"><!-- notionvc: a60800dc-6e39-469f-b850-feb6a433fd24 --><\/a><\/td>\n<td>14.1<\/td>\n<td>React extensions for i18next<\/td>\n<\/tr>\n<tr>\n<td><a class=\"notion-link-token notion-focusable-token notion-enable-hover\" tabindex=\"0\" href=\"https:\/\/github.com\/i18next\/i18next-http-backend\" rel=\"noopener noreferrer\" data-token-index=\"0\"><span class=\"link-annotation-unknown-block-id-1256191151\">i18next-http-backend<\/span><\/a><a href=\"https:\/\/github.com\/i18next\/i18next-http-backend\"><!-- notionvc: 2fe714e6-45d2-4b4a-939b-2efbbc1dc34c --><\/a><\/td>\n<td>2.5<\/td>\n<td>Loads translations from network<\/td>\n<\/tr>\n<tr>\n<td><a class=\"notion-link-token notion-focusable-token notion-enable-hover\" tabindex=\"0\" href=\"https:\/\/github.com\/i18next\/i18next-browser-languageDetector\" rel=\"noopener noreferrer\" data-token-index=\"0\"><span class=\"link-annotation-unknown-block-id-634077603\">i18next-browser-languagedetector<\/span><\/a><a href=\"https:\/\/github.com\/i18next\/i18next-browser-languageDetector\"><!-- notionvc: e2e79353-ca69-4831-812f-9510679cb32c --><\/a><\/td>\n<td>7.2<\/td>\n<td>Detects user\u2019s preferred locales<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3><span class=\"ez-toc-section\" id=\"a-note-on-chatgpt-versions\"><\/span>A note on ChatGPT versions<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>To generate build plans, design, and code, we\u2019ve used ChatGPT 4. ChatGPT 4 is a paid service at the time of writing, but you can work along with the free Chat GPT 3.5 if you like. However, there are significant differences between GPT 3.5 and 4, so your mileage may vary.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note \u00bb<\/strong>\u00a0We used The Professional Code (Auto programming) custom GPT, which <a href=\"https:\/\/github.com\/ai-boost\/awesome-prompts\/blob\/main\/prompts\/%F0%9F%92%BBProfessional%20Coder.md\">prompts ChatGPT<\/a> to act like a professional software developer. The custom GPT was taken down as we published the article. You don\u2019t have to use the custom GPT, however. Plain vanilla ChatGPT will work fine for our purposes.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"our-demo\"><\/span>Our demo<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We have a little app to localize in this guide. The fictional <strong>Migrant Taco<\/strong> is a forum for fans of EDM (electronic dance music).<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note \u00bb<\/strong> We don\u2019t implement any CRUD functionality since we want to focus on the i18n here.<\/p>\n<figure id=\"attachment_79625\" aria-describedby=\"caption-attachment-79625\" style=\"width: 946px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-79625\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/app-before-localization-946x1024.jpg\" alt=\"Our app before localization\" width=\"946\" height=\"1024\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/app-before-localization-946x1024.jpg 946w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/app-before-localization-277x300.jpg 277w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/app-before-localization-768x831.jpg 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/app-before-localization.jpg 1000w\" sizes=\"(max-width: 946px) 100vw, 946px\" \/><figcaption id=\"caption-attachment-79625\" class=\"wp-caption-text\">Our app before localization<\/figcaption><\/figure>\n<p>\ud83d\uddd2\ufe0f <strong>Note \u00bb<\/strong>\u00a0We <a href=\"https:\/\/vitejs.dev\/guide\/#scaffolding-your-first-vite-project\">scaffolded the project<\/a> using the Vite React template, which installed Vite and React.<\/p>\n<p>The starter app (before localization) has the following file structure.<\/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=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">.\n\u251c\u2500\u2500 public\n\u2502   \u2514\u2500\u2500 assets\n\u2502       \u251c\u2500\u2500 hot-albums.json\n\u2502       \u2514\u2500\u2500 trending-posts.json\n\u2514\u2500\u2500 src\n    \u251c\u2500\u2500 components\n    \u2502   \u251c\u2500\u2500 AlbumCard.jsx\n    \u2502   \u251c\u2500\u2500 Navbar.jsx\n    \u2502   \u2514\u2500\u2500 PostTeaser.jsx\n    \u251c\u2500\u2500 hooks\n    \u2502   \u251c\u2500\u2500 use-fetch.js\n    \u2502   \u251c\u2500\u2500 use-hot-albums.js\n    \u2502   \u2514\u2500\u2500 use-trending-posts.js\n    \u251c\u2500\u2500 pages\n    \u2502   \u251c\u2500\u2500 HomePage.jsx\n    \u2502   \u251c\u2500\u2500 HotAlbums.jsx\n    \u2502   \u2514\u2500\u2500 TrendingPosts.jsx\n    \u251c\u2500\u2500 App.jsx\n    \u2514\u2500\u2500 main.jsx\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><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_0675ef6ad4b4efb44f593a69ad95e539\" 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>Note \u00bb<\/strong>\u00a0Even the starter app was created using ChatGPT, but to focus on i18n we\u2019re skipping the steps to build the largely straightforward starter app. ChatGPT is great at building small apps like this and excels at generating common boilerplate.<\/p>\n<p>\ud83d\udd17 <strong>Resource \u00bb<\/strong> \u00a0<a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/tree\/main\/start\">Get the starter project from GitHub<\/a>.<\/p>\n<p>Hooks are used to load album and post JSON data from the network. Here are excerpts of the JSON:<\/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=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ public\/assets\/hot-albums.json<\/span>\n&#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\">\"Euphoric Nights\"<\/span>,\n    <span class=\"hljs-attr\">\"artist\"<\/span>: <span class=\"hljs-string\">\"DJ Pulse\"<\/span>,\n    <span class=\"hljs-attr\">\"coverUrl\"<\/span>: <span class=\"hljs-string\">\"https:\/\/picsum.photos\/id\/100\/300\/300\"<\/span>,\n    <span class=\"hljs-attr\">\"releaseDate\"<\/span>: <span class=\"hljs-string\">\"2024-02-15\"<\/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\">\"Bass in the Shadows\"<\/span>,\n    <span class=\"hljs-attr\">\"artist\"<\/span>: <span class=\"hljs-string\">\"Bass Master\"<\/span>,\n    <span class=\"hljs-attr\">\"coverUrl\"<\/span>: <span class=\"hljs-string\">\"https:\/\/picsum.photos\/id\/101\/300\/300\"<\/span>,\n    <span class=\"hljs-attr\">\"releaseDate\"<\/span>: <span class=\"hljs-string\">\"2024-03-01\"<\/span>\n  },\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n]\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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_61c32da80bac656691128013ef6b6a98\" 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-3\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ public\/assets\/trending-posts.json<\/span>\n&#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\">\"Top 10 EDM Tracks of 2024\"<\/span>,\n    <span class=\"hljs-attr\">\"excerpt\"<\/span>: <span class=\"hljs-string\">\"Discover the tracks that are setting the stage on fire in 2024.\"<\/span>,\n    <span class=\"hljs-attr\">\"author\"<\/span>: <span class=\"hljs-string\">\"DJ Reviewer\"<\/span>,\n    <span class=\"hljs-attr\">\"date\"<\/span>: <span class=\"hljs-string\">\"2024-02-20\"<\/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\">\"The Evolution of EDM Festivals\"<\/span>,\n    <span class=\"hljs-attr\">\"excerpt\"<\/span>: <span class=\"hljs-string\">\"A deep dive into how EDM festivals have transformed over the years.\"<\/span>,\n    <span class=\"hljs-attr\">\"author\"<\/span>: <span class=\"hljs-string\">\"Festival Historian\"<\/span>,\n    <span class=\"hljs-attr\">\"date\"<\/span>: <span class=\"hljs-string\">\"2024-03-05\"<\/span>\n  },\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n]\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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_12deb1846db320fec637e37232111000\" 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>Simple hooks are used to load this JSON:<\/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\/hooks\/use-hot-albums.js<\/span>\n\n<span class=\"hljs-comment\">\/\/ Handles JSON file fetching and error handling<\/span>\n<span class=\"hljs-keyword\">import<\/span> useFetch <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/use-fetch\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> useHotAlbums = <span class=\"hljs-function\">(<span class=\"hljs-params\">limit = <span class=\"hljs-literal\">null<\/span><\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> { data, loading, error } = useFetch(\n    <span class=\"hljs-string\">\"\/assets\/hot-albums.json\"<\/span>\n  );\n  \n  <span class=\"hljs-keyword\">const<\/span> albums =\n    data &amp;&amp; limit ? data.slice(<span class=\"hljs-number\">0<\/span>, limit) : data;\n    \n  <span class=\"hljs-keyword\">return<\/span> { albums, loading, error };\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> useHotAlbums;\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_f9b1edb86c716c0a93082b76c04402ae\" 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>A similar hook, <a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/blob\/main\/start\/src\/hooks\/use-trending-posts.js\">useTrendingPosts<\/a>, is used for trending posts. The <code>useHotAlbums<\/code> and <code>useTrendingPosts<\/code> hooks use a <code>useFetch<\/code> hook under the hood. The latter fetches JSON from the network and has some basic error handling. We\u2019re skipping the code for <code>useFetch<\/code> here, but you can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/blob\/main\/start\/src\/hooks\/use-fetch.js\">get it on GitHub<\/a>.<\/p>\n<p>Three pages are managed by React Router, a home page (screenshot above), a <strong>Hot Albums<\/strong> page, and a <strong>Trending Posts<\/strong> page. They use our hooks to load the JSON data and present it. Here\u2019s the home page:<\/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\/pages\/HomePage.jsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Link } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-router-dom\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> AlbumCard <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/components\/AlbumCard\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> PostTeaser <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/components\/PostTeaser\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> useHotAlbums <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/hooks\/use-hot-albums\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> useTrendingPosts <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/hooks\/use-trending-posts\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> HomePage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> {\n    albums,\n    <span class=\"hljs-attr\">loading<\/span>: loadingAlbums,\n    <span class=\"hljs-attr\">error<\/span>: errorAlbums,\n  } = useHotAlbums(<span class=\"hljs-number\">3<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> {\n    posts,\n    <span class=\"hljs-attr\">loading<\/span>: loadingPosts,\n    <span class=\"hljs-attr\">error<\/span>: errorPosts,\n  } = useTrendingPosts(<span class=\"hljs-number\">4<\/span>);\n\n  <span class=\"hljs-keyword\">if<\/span> (loadingAlbums || loadingPosts)\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Loading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>;\n    \n  <span class=\"hljs-keyword\">if<\/span> (errorAlbums || errorPosts)\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Error loading data<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>;\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          Hot Albums\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          {albums.map((album) =&gt; (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AlbumCard<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{album.id}<\/span> <span class=\"hljs-attr\">album<\/span>=<span class=\"hljs-string\">{album}<\/span> \/&gt;<\/span>\n          ))}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">to<\/span>=<span class=\"hljs-string\">\"\/hot-albums\"<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n            View all 9 albums\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/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\">section<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          Trending Posts\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          {posts.map((post) =&gt; (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PostTeaser<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{post.id}<\/span> <span class=\"hljs-attr\">post<\/span>=<span class=\"hljs-string\">{post}<\/span> \/&gt;<\/span>\n          ))}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">to<\/span>=<span class=\"hljs-string\">\"\/trending-posts\"<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n            View all 9 posts\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/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\">section<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> HomePage;\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_4105639c85786839d726c156162df98f\" 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 home page uses an <code>AlbumCard<\/code> component and a <code>PostTeaser<\/code> component to display its fetched data. These are presentational components, and you can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/tree\/main\/start\/src\/components\">get their code from GitHub<\/a>. <\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note \u00bb<\/strong>\u00a0We omit CSS styles here for brevity. These Tailwind styles are available in the <a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/tree\/main\">full code listings on GitHub<\/a>.<\/p>\n<p>React Router uses the <code>HomePage<\/code> and others (all similar to the home page) in the root <code>App<\/code> component. You can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/tree\/main\/start\/src\/pages\">find the code on GitHub<\/a>.<\/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\/App.jsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> {\n  BrowserRouter <span class=\"hljs-keyword\">as<\/span> Router,\n  Routes,\n  Route,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-router-dom\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> HomePage <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/pages\/HomePage\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> HotAlbums <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/pages\/HotAlbums\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> TrendingPosts <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/pages\/TrendingPosts\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Navbar <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/Navbar\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> App = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Router<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Routes<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span> <span class=\"hljs-attr\">path<\/span>=<span class=\"hljs-string\">\"\/\"<\/span> <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">HomePage<\/span> \/&gt;<\/span>} \/&gt;\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span>\n              <span class=\"hljs-attr\">path<\/span>=<span class=\"hljs-string\">\"\/hot-albums\"<\/span>\n              <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">HotAlbums<\/span> \/&gt;<\/span>}\n            \/&gt;\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span>\n              <span class=\"hljs-attr\">path<\/span>=<span class=\"hljs-string\">\"\/trending-posts\"<\/span>\n              <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">TrendingPosts<\/span> \/&gt;<\/span>}\n            \/&gt;\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Routes<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/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\">Router<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> App;\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_5ff96c95a1bf5944bb599da0f970c980\" 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>OK, this is our starting point. Let\u2019s walk through how we localized it with ChatGPT.<\/p>\n<p>\ud83d\udd17 <strong>Resource \u00bb<\/strong> \u00a0<a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/tree\/main\/start\">Get the starter project from GitHub<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"the-build-plan\"><\/span>The build plan<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>From this point, we will go through our specific experience localizing the demo app with ChatGPT. <\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note \u00bb<\/strong> ChatGPT is non-deterministic: It can give varying responses for the same prompt. We\u2019ll try to focus on broad guidelines that can help you when using ChatGPT to generate software solutions.<\/p>\n<p>If we jumped in head-first and asked ChatGPT to start generating code, we could go down branches of reasoning that miss important high-level information. This can be frustrating and lead to a lot of back-and-forth with the AI as we provide missing gaps in its knowledge about our specific app. It\u2019s wise to do some high-level <strong>chain of thought<\/strong> prompting, getting ChatGPT to create a build plan that solves our problem before it generates any code. <\/p>\n<h3><span class=\"ez-toc-section\" id=\"the-prompt\"><\/span>The prompt<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Here\u2019s our initial prompt:<\/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=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">Hello, I have a small website made with React, Vite,\nreact-router, and Tailwind CSS. I would like to\ninternationalize this site and localize it to English\n(en) and Arabic (ar), with the option to add more\nlocales in the future. I want to use the react-i18next\nlibrary to do this. What are the steps I should take?\nPlease outline a general plan. Don't generate any code.\nOutput the plan in a way that I can easily copy and paste,\nplease.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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_d2f614c7ba9c0bc0203f9cc50b230fa4\" 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>Note how we gave ChatGPT a good amount of context. We should be specific when prompting, or the AI will make many assumptions about our app, often derailing our efforts. So we tell which packages we\u2019re using and what we generally want to accomplish. We could have been more specific here and will have to iterate more on the build plan in a moment.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"the-response\"><\/span>The response<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>ChatGPT did a good job identifying the steps needed for localizing a React app with i18next. Here\u2019s a summarized excerpt of its response:<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\">To internationalize and localize a React website with\nreact-i18next for English and Arabic, ensuring future\nscalability:\n<span class=\"hljs-bullet\">\n1. <\/span>Install Dependencies: Include react-i18next,\ni18next, and possibly i18next-browser-languagedetector for\nautomatic language detection.\n<span class=\"hljs-bullet\">\n2. <\/span>Configuration: Set up a <span class=\"hljs-code\">`locales`<\/span> directory in <span class=\"hljs-code\">`src`<\/span>,\nwith subfolders for each language (e.g., en, ar),\ncontaining <span class=\"hljs-code\">`translation.json`<\/span> files for key-value pairs.\n<span class=\"hljs-bullet\">\n3. <\/span>Initialize react-i18next: In <span class=\"hljs-code\">`src`<\/span>, create <span class=\"hljs-code\">`i18n.js`<\/span>\nto configure i18next with locale details, a fallback\nlanguage, and plugins like the language detector.\n<span class=\"hljs-bullet\">\n4. <\/span>Integration with React: Use I18nextProvider or\nuseTranslation hook for translation function access.\nReplace strings with translation keys using <span class=\"hljs-code\">`t`<\/span> function,\nand change languages with <span class=\"hljs-code\">`i18next.changeLanguage`<\/span>.\n<span class=\"hljs-bullet\">\n5. <\/span>RTL Layout for Arabic: Utilize Tailwind CSS's RTL\nsupport by adding <span class=\"hljs-code\">`dir=\"rtl\"`<\/span> to the root element for\nArabic.\n\n&#91;<span class=\"hljs-symbol\">\/\/<\/span>]: <span class=\"hljs-link\"># (omitting rest of response for brevity...)<\/span>\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_7f5df4ce2224a5172b8206d51b9b1faa\" 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>Note \u00bb<\/strong>\u00a0We\u2019re showing prompts and response as Markdown blocks here to differentiate them. The ChatGPT UI looks different, of course.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"validation-and-iteration\"><\/span>Validation and iteration<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We wanted to load our translation files asynchronously from the network using the official i18next plugin (<code>i18next-http-backend<\/code>). We also wanted to localize our routes. So we told ChatGPT to update its plan:<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\">Let's add the following to the plan:\n<span class=\"hljs-bullet\">- <\/span>Loading translation files asynchronously from\n  the network using the official i18next plugin\n  to do so\n<span class=\"hljs-bullet\">- <\/span>Localizing routes so that \/en\/foo loads the\n  foo English page and \/ar\/foo load the same\n  page in Arabic (foo is an example route here,\n  of course)\n\nPlease output the updated plan.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_43bb359734335d0518b5c0d583c1f7b8\" 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>ChatGPT amended the plan appropriately, adding the <code>i18next-http-backend<\/code> package to the install list in step 1. It also added a step to the build plan for localizing routes:<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\"><span class=\"hljs-bullet\">6. <\/span>Localizing Routes: Configure react-router for routes\nwith language codes (e.g., \/en\/foo), dynamically update\ni18next's language on navigation, and adjust UI and\nlanguage files accordingly.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_52cf9f59f2d89c9deae892ec2f8d85e1\" 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<h2><span class=\"ez-toc-section\" id=\"execution\"><\/span>Execution<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>At this point, we felt confident enough to have ChatGPT start generating specific instructions and code:<\/p>\n\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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\">OK, let's move on to Step 1. Please walk me through\nthis step and show me the code.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_e13195660af35029faf544f6c6dd75d7\" 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>ChatGPT generated NPM install instructions for needed packages, giving NPM and Yarn alternatives for commands. Here\u2019s the NPM version:<\/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=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npm install react-i18next i18next i18next-http-backend i18next-browser-languagedetector\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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_67d88af140e1f139497fa597bcfc45c0\" 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 AI also explained what each package does. We asked it to continue:<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\">Wonderful :) Let's go through step 2, please. Show me the code.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_f21e2e93c4c0f217898d51afb12eba02\" 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>A generally good plan with code was generated. Here\u2019s a summary:<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\"><span class=\"hljs-bullet\">2. <\/span>Setting Up Translation Resources:\n<span class=\"hljs-bullet\">\n- <\/span>Create a <span class=\"hljs-code\">`locales`<\/span> directory in your project's <span class=\"hljs-code\">`src`<\/span>\nfolder, and inside it, make subdirectories for each\nlanguage (e.g., <span class=\"hljs-code\">`en`<\/span>, <span class=\"hljs-code\">`ar`<\/span>).\n<span class=\"hljs-bullet\">\n- <\/span>In each language subdirectory, add a <span class=\"hljs-code\">`translation.json`<\/span>\nfile containing translation key-value pairs.\n<span class=\"hljs-bullet\">\n- <\/span>Use your IDE or file explorer to create and edit these\ndirectories and files, ensuring key consistency across\nlanguages for seamless translation.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_51468cfca213ef9ccb859c2ce29dd2ad\" 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>ChatGPT gave us a helpful file hierarchy and example translation JSON. However, the AI made a mistake we didn\u2019t immediately catch: It told us to create our <code>locales<\/code> directory under <code>src<\/code>. We wanted to load our translations from the network using i18next\u2019s HTTP loader, so <code>locales<\/code> needed to go under the <code>public<\/code> directory. We asked ChatGPT to correct this and it generated an updated hierarchy:<\/p>\n<figure id=\"attachment_79631\" aria-describedby=\"caption-attachment-79631\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-79631\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/corrected-trans-file-hierarchy-1024x306.png\" alt=\"Corrected example of the translation file hierarchy.\" width=\"1024\" height=\"306\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/corrected-trans-file-hierarchy-1024x306.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/corrected-trans-file-hierarchy-300x90.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/corrected-trans-file-hierarchy-768x229.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/corrected-trans-file-hierarchy-1536x459.png 1536w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/corrected-trans-file-hierarchy.png 1634w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption id=\"caption-attachment-79631\" class=\"wp-caption-text\">Corrected example of the translation file hierarchy.<\/figcaption><\/figure>\n<figure id=\"attachment_79637\" aria-describedby=\"caption-attachment-79637\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-79637\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/chatgpt-translation-examples-1024x630.png\" alt=\"ChatGPT generated helpful example translation files.\" width=\"1024\" height=\"630\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/chatgpt-translation-examples-1024x630.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/chatgpt-translation-examples-300x184.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/chatgpt-translation-examples-768x472.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/chatgpt-translation-examples-1536x944.png 1536w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/chatgpt-translation-examples.png 1672w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption id=\"caption-attachment-79637\" class=\"wp-caption-text\">ChatGPT generated helpful example translation files.<\/figcaption><\/figure>\n<p>\ud83d\uddd2\ufe0f <strong>Note \u00bb<\/strong> We started prompting ChatGPT assuming we would use <code>en-US<\/code> and <code>ar-EG<\/code> for supported locales. We later changed this to <code>en<\/code> and <code>ar<\/code>, so if you see any disparity know that we landed on the latter set.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"generated-code-setting-up-i18next\"><\/span>Generated code: setting up i18next<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A quick prompt to move to step 3 of the build plan, setting up i18next, got ChatGPT generating its first block of production code.<\/p>\n\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=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/i18n.js<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> i18n <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"i18next\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { initReactI18next } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-i18next\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> HttpBackend <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"i18next-http-backend\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> LanguageDetector <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"i18next-browser-languagedetector\"<\/span>;\n\ni18n\n  <span class=\"hljs-comment\">\/\/ Load translation using http -&gt; see \/public\/locales.<\/span>\n  <span class=\"hljs-comment\">\/\/ We will serve our locales directory as a static asset<\/span>\n  .use(HttpBackend)\n  <span class=\"hljs-comment\">\/\/ Detect user language<\/span>\n  .use(LanguageDetector)\n  <span class=\"hljs-comment\">\/\/ Pass the i18n instance to react-i18next.<\/span>\n  .use(initReactI18next)\n  <span class=\"hljs-comment\">\/\/ Initialize i18next<\/span>\n  .init({\n    <span class=\"hljs-comment\">\/\/ The lng and fallbackLng options define your<\/span>\n    <span class=\"hljs-comment\">\/\/ application's default language and fallback language<\/span>\n    <span class=\"hljs-comment\">\/\/ in case the user's language can't be detected or<\/span>\n    <span class=\"hljs-comment\">\/\/ isn't supported.<\/span>\n    <span class=\"hljs-attr\">fallbackLng<\/span>: <span class=\"hljs-string\">\"en\"<\/span>,\n    <span class=\"hljs-comment\">\/\/ if you're using a language detector, you can remove<\/span>\n    <span class=\"hljs-comment\">\/\/ this line<\/span>\n    <span class=\"hljs-attr\">lng<\/span>: <span class=\"hljs-string\">\"en\"<\/span>,\n    <span class=\"hljs-attr\">detection<\/span>: {\n      <span class=\"hljs-comment\">\/\/ Order and from where user language should be<\/span>\n      <span class=\"hljs-comment\">\/\/ detected. Here we list possible options for<\/span>\n      <span class=\"hljs-comment\">\/\/ detection.<\/span>\n      <span class=\"hljs-attr\">order<\/span>: &#91;\n        <span class=\"hljs-string\">\"querystring\"<\/span>,\n        <span class=\"hljs-string\">\"cookie\"<\/span>,\n        <span class=\"hljs-string\">\"localStorage\"<\/span>,\n        <span class=\"hljs-string\">\"sessionStorage\"<\/span>,\n        <span class=\"hljs-string\">\"navigator\"<\/span>,\n        <span class=\"hljs-string\">\"htmlTag\"<\/span>,\n        <span class=\"hljs-string\">\"path\"<\/span>,\n        <span class=\"hljs-string\">\"subdomain\"<\/span>,\n      ],\n      <span class=\"hljs-comment\">\/\/ Keys or params to lookup language from<\/span>\n      <span class=\"hljs-attr\">lookupQuerystring<\/span>: <span class=\"hljs-string\">\"lng\"<\/span>,\n      <span class=\"hljs-attr\">lookupCookie<\/span>: <span class=\"hljs-string\">\"i18next\"<\/span>,\n      <span class=\"hljs-attr\">lookupLocalStorage<\/span>: <span class=\"hljs-string\">\"i18nextLng\"<\/span>,\n      <span class=\"hljs-comment\">\/\/ Cache user language on<\/span>\n      <span class=\"hljs-attr\">caches<\/span>: &#91;<span class=\"hljs-string\">\"localStorage\"<\/span>, <span class=\"hljs-string\">\"cookie\"<\/span>],\n      <span class=\"hljs-comment\">\/\/ Exclude cache for specific languages<\/span>\n      <span class=\"hljs-comment\">\/\/ languages to not persist (cookie, localStorage)<\/span>\n      <span class=\"hljs-attr\">excludeCacheFor<\/span>: &#91;<span class=\"hljs-string\">\"cimode\"<\/span>],\n    },\n    <span class=\"hljs-comment\">\/\/ Path where resources get loaded from, or a function<\/span>\n    <span class=\"hljs-comment\">\/\/ returning a path:<\/span>\n    <span class=\"hljs-comment\">\/\/ function(lngs, namespaces) { return customPath; }<\/span>\n    <span class=\"hljs-comment\">\/\/ The function receives the lngs and namespaces as<\/span>\n    <span class=\"hljs-comment\">\/\/ the arguments (in case you need to load languages<\/span>\n    <span class=\"hljs-comment\">\/\/ and namespaces dynamically).The returned path will<\/span>\n    <span class=\"hljs-comment\">\/\/ interpolate lng, ns if provided like giving a static<\/span>\n    <span class=\"hljs-comment\">\/\/ path.<\/span>\n    <span class=\"hljs-attr\">backend<\/span>: {\n      <span class=\"hljs-attr\">loadPath<\/span>: <span class=\"hljs-string\">\"\/locales\/{{lng}}\/{{ns}}.json\"<\/span>,\n    },\n    <span class=\"hljs-comment\">\/\/ Have a common namespace used around the full app<\/span>\n    <span class=\"hljs-attr\">ns<\/span>: &#91;<span class=\"hljs-string\">\"translation\"<\/span>],\n    <span class=\"hljs-attr\">defaultNS<\/span>: <span class=\"hljs-string\">\"translation\"<\/span>,\n\n    <span class=\"hljs-comment\">\/\/ Boolean or function to enable\/disable adding keys<\/span>\n    <span class=\"hljs-comment\">\/\/ not found in translation to backend<\/span>\n    <span class=\"hljs-attr\">saveMissing<\/span>: <span class=\"hljs-literal\">true<\/span>,\n\n    <span class=\"hljs-comment\">\/\/ Set to true if you're using a language detector<\/span>\n    <span class=\"hljs-attr\">debug<\/span>: <span class=\"hljs-literal\">true<\/span>,\n\n    <span class=\"hljs-attr\">interpolation<\/span>: {\n      <span class=\"hljs-comment\">\/\/ Not needed for React as it escapes by default<\/span>\n      <span class=\"hljs-attr\">escapeValue<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    },\n  });\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> i18n;\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\">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_d3d8e0815a2d1f86ab649d4c041bf8ae\" 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>Interestingly, this reads like it was taken from the documentation of the various packages we\u2019re using. We cleaned this code up, removing comments and using implicit defaults rather than writing them explicitly. We also removed the <code>saveMissing: true<\/code> option, since that would cause our back-end to write missing translations to our translation files, something we don\u2019t support in our app and would likely lead to errors.<\/p>\n<p>Here\u2019s our cleaned-up <code>i18n.js<\/code>:<\/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=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/i18n.js<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> i18n <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"i18next\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { initReactI18next } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-i18next\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> HttpBackend <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"i18next-http-backend\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> LanguageDetector <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"i18next-browser-languagedetector\"<\/span>;\n\ni18n\n  .use(HttpBackend)\n  .use(LanguageDetector)\n  .use(initReactI18next)\n  .init({\n    <span class=\"hljs-attr\">fallbackLng<\/span>: <span class=\"hljs-string\">\"en\"<\/span>,\n    <span class=\"hljs-attr\">interpolation<\/span>: {\n      <span class=\"hljs-attr\">escapeValue<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    },\n  });\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> i18n;\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\">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_f0a6c39e9d3707a404fc8cf1f9fc344c\" 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>ChatGPT also asked us to import this new file into our entry file, <code>src\/main.jsx<\/code>. <\/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\/main.jsx\n\n  import React from \"react\";\n  import ReactDOM from \"react-dom\/client\";\n  import App from \".\/App.jsx\";\n  import \".\/index.css\";\n<span class=\"hljs-addition\">+ import \".\/i18n.js\";<\/span>\n\n  ReactDOM.createRoot(document.getElementById(\"root\")).render(\n    &lt;React.StrictMode&gt;\n      &lt;App \/&gt;\n    &lt;\/React.StrictMode&gt;,\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_3cf882d4fb4ef192f8cc7db2c779c129\" 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<h2><span class=\"ez-toc-section\" id=\"integrating-i18next-into-components\"><\/span>Integrating i18next into components<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>As part of step 3, ChatGPT inadvertently gave us an example of integrating i18next into our components via the <code>useTranslation<\/code> hook. It gave us an alternative place to import our <code>i18n.js<\/code> file, the root <code>App<\/code> component.<\/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=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ Example from ChatGPT<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/App.css'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/i18n'<\/span>; <span class=\"hljs-comment\">\/\/ Import i18n configuration<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useTranslation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-i18next'<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">App<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { t } = useTranslation();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"App\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"App-header\"<\/span>&gt;<\/span>\n        {t('welcomeMessage')}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> App;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><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_497c5e8e1f670e8ede354297da67817b\" 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 had already imported <code>i18n.js<\/code> in <code>main.jsx<\/code>, and used the example as a guide to localize our <code>Navbar<\/code> component. We first moved all hard-coded strings in the component to our translation files:<\/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=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ public\/locales\/en\/translation.json<\/span>\n{\n  <span class=\"hljs-attr\">\"appName\"<\/span>: <span class=\"hljs-string\">\"Migrant Taco\"<\/span>,\n  <span class=\"hljs-attr\">\"nav\"<\/span>: {\n    <span class=\"hljs-attr\">\"home\"<\/span>: <span class=\"hljs-string\">\"Home\"<\/span>,\n    <span class=\"hljs-attr\">\"hotAlbums\"<\/span>: <span class=\"hljs-string\">\"Hot Albums\"<\/span>,\n    <span class=\"hljs-attr\">\"trendingPosts\"<\/span>: <span class=\"hljs-string\">\"Trending Posts\"<\/span>\n  }\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\">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_61c32da80bac656691128013ef6b6a98\" 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-20\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-comment\">\/\/ public\/locales\/ar\/translation.json<\/span>\n{\n  <span class=\"hljs-attr\">\"appName\"<\/span>: <span class=\"hljs-string\">\"\u0645\u064a\u062c\u0631\u0627\u0646\u062a \u062a\u0627\u0643\u0648\"<\/span>,\n  <span class=\"hljs-attr\">\"nav\"<\/span>: {\n    <span class=\"hljs-attr\">\"home\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629\"<\/span>,\n    <span class=\"hljs-attr\">\"hotAlbums\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u0623\u0644\u0628\u0648\u0645\u0627\u062a \u0627\u0644\u0633\u0627\u062e\u0646\u0629\"<\/span>,\n    <span class=\"hljs-attr\">\"trendingPosts\"<\/span>: <span class=\"hljs-string\">\"\u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0627\u0644\u0631\u0627\u0626\u062c\u0629\"<\/span>\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\">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_63cc59e245788d1c1454d6aac1d1d2e3\" 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>Afterwards, we replaced the occurrences of these strings with calls to the <code>t()<\/code> translation function:<\/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\/components\/Navbar.jsx\n<span class=\"hljs-addition\">+ import { useTranslation } from \"react-i18next\";<\/span>\n  import { Link } from \"react-router-dom\";\n\n  const Navbar = () =&gt; {\n<span class=\"hljs-addition\">+   const { t } = useTranslation();<\/span>\n\n    return (\n      &lt;nav className=\"...\"&gt;\n        &lt;div className=\"...\"&gt;\n          &lt;h1 className=\"...\"&gt;\n<span class=\"hljs-deletion\">-           Migrant Taco<\/span>\n<span class=\"hljs-addition\">+           {t(\"appName\")}<\/span>\n          &lt;\/h1&gt;\n          &lt;ul className=\"...\"&gt;\n            &lt;li&gt;\n              &lt;Link to=\"\/\" className=\"...\"&gt;\n<span class=\"hljs-deletion\">-               Home<\/span>\n<span class=\"hljs-addition\">+               {t(\"nav.home\")}<\/span>\n              &lt;\/Link&gt;\n            &lt;\/li&gt;\n<span class=\"hljs-addition\">+          &lt;!-- Localize other link text... --&gt;<\/span>\n          &lt;\/ul&gt;\n        &lt;\/div&gt;\n      &lt;\/nav&gt;\n    );\n  };\n\n  export default Navbar;\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_51d1241d9514bbc113883bd741c7a3f5\" 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>After the update, switching our locale to <code>ar<\/code> revealed our <code>Navbar<\/code> in Arabic.<\/p>\n<figure id=\"attachment_79643\" aria-describedby=\"caption-attachment-79643\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-large wp-image-79643\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/navbar-localized-1024x204.png\" alt=\"English and Arabic versions of our Navbar.\" width=\"1024\" height=\"204\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/navbar-localized-1024x204.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/navbar-localized-300x60.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/navbar-localized-768x153.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/navbar-localized.png 1100w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption id=\"caption-attachment-79643\" class=\"wp-caption-text\">English and Arabic versions of our Navbar.<\/figcaption><\/figure>\n<p>\ud83d\uddd2\ufe0f <strong>Note \u00bb<\/strong>\u00a0To quickly test a locale when developing, set <code>lng: \"ar\"<\/code> in the <code>i18n.init()<\/code> options (found in the <code>src\/i18n.js<\/code> file). Note that when using the language detector plugin, this will cause the set language to be stored in your local storage to be used in future visits. Deleting the local storage entry should start you fresh when testing.<\/p>\n<p>\ud83d\udd17 <strong>Resource \u00bb<\/strong> \u00a0<a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/blob\/main\/complete\/src\/components\/Navbar.jsx\">Get the full code for the Navbar component from GitHub<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"the-language-switcher\"><\/span>The language switcher<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Although not part of the build plan, we wanted to add a language switcher UI to our app. Even though the language detector plugin will try to match the user\u2019s preferred locales (set in her browser) to our app\u2019s locales, we often want to provide a manual override for the user.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"the-prompt-2\"><\/span>The prompt<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We prompted ChatGPT to generate the code for the switcher:<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\">Please generate the locale switcher. Use a\n<span class=\"hljs-code\">`&lt;select&gt;`<\/span> dropdown instead of buttons. Use\nTailwind CSS styles to style the <span class=\"hljs-code\">`&lt;select&gt;`<\/span>. \nGive the component a background of bg-gray-200\nand text-slate-900. Keep in mind that I want to\nplace the localeswitcher into this navbar:\n\n&#91;<span class=\"hljs-symbol\">\/\/<\/span>]: <span class=\"hljs-link\"># (pasted code for Navbar component)<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_6f26da3914c608ffeca868098ae9ef50\" 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 asked ChatGPT to add Tailwind styles to the switcher since it\u2019s notoriously difficult to style <code>&lt;select&gt;<\/code> dropdowns across browsers. (It didn\u2019t do a perfect job styling the <code>&lt;select&gt;<\/code>: the dropdown looks OK on the Chromium-based Arc browser and Firefox but needs work in Safari).<\/p>\n<h3><span class=\"ez-toc-section\" id=\"the-response-2\"><\/span>The response<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>ChatGPT\u2019s first take on creating the locale switcher was inlining it directly in the <code>Navbar<\/code>. We prompted it to extract the switcher to a component, and got this code back:<\/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\">\/\/ LocaleSwitcher.js<\/span>\n<span class=\"hljs-keyword\">import<\/span> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useTranslation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-i18next'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> LocaleSwitcher = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> { i18n } = useTranslation();\n\n  <span class=\"hljs-keyword\">const<\/span> changeLanguage = <span class=\"hljs-function\">(<span class=\"hljs-params\">event<\/span>) =&gt;<\/span> {\n    i18n.changeLanguage(event.target.value);\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\">{changeLanguage}<\/span>\n      <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{i18n.language}<\/span>\n      <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-gray-200 text-slate-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\"<\/span>\n    &gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"en\"<\/span>&gt;<\/span>English<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"ar\"<\/span>&gt;<\/span>\u0627\u0644\u0639\u0631\u0628\u064a\u0629<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">select<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> LocaleSwitcher;\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_a1d11c2cb081297f049d54396013446d\" 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>While generally good, the generated code used <code>i18n.language<\/code> for setting the dropdown\u2019s value, whereas we want <code>i18n.resolvedLanguage<\/code> here. <code>language<\/code> will always be the <strong>detected<\/strong> user locale whereas <code>resolvedLanguage<\/code> will be a locale our app supports that\u2019s best-matched to the detected locale. Since the value has to match one of our supported locales (<code>en|ar<\/code>) to work with the dropdown, we corrected this in the code before we added it to our app:<\/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\/LocaleSwitcher.jsx\n\n  import { useTranslation } from \"react-i18next\";\n\n  const LocaleSwitcher = () =&gt; {\n    const { i18n } = useTranslation();\n\n    const changeLanguage = (event) =&gt; {\n      i18n.changeLanguage(event.target.value);\n    };\n\n    return (\n      &lt;select\n        onChange={changeLanguage}\n<span class=\"hljs-deletion\">-       value={i18n.language}<\/span>\n<span class=\"hljs-addition\">+       value={i18n.resolvedLanguage}<\/span>\n        className=\"...\"\n      &gt;\n        &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n        &lt;option value=\"ar\"&gt;\u0627\u0644\u0639\u0631\u0628\u064a\u0629&lt;\/option&gt;\n      &lt;\/select&gt;\n    );\n  };\n\n  export default LocaleSwitcher;\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_1007f1c4b14e97db85677769fcb948ed\" 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>After placing the <code>LocaleSwitcher<\/code> into our <code>Navbar<\/code>, we got an easy UX enhancement to our app.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-79649\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/locale-switcher.gif\" alt=\"The locale switcher shown switching between Arabic and English versions of our site.\" width=\"600\" height=\"102\" \/><\/p>\n<h2><span class=\"ez-toc-section\" id=\"interpolation-and-plurals\"><\/span>Interpolation and plurals<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Still at step 4, we wanted to handle dynamic values in our translation messages. The \u201cview all\u201d buttons on our home page needed this treatment, as well as plural support.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-79655\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/view-all-btn.png\" alt=\"A button labelled &quot;View all 9 albums.&quot;\" width=\"191\" height=\"56\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"the-prompt-3\"><\/span>The prompt<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A simple prompt that describes the problem got us started.<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\">I have text in my app that reads \"View all\n9 albums\". The 9 here is a dynamic value\nknown at runtime. How do I localize this\ntext?\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_61664781f122f8e138fd9bb9963d8c5e\" 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<h3><span class=\"ez-toc-section\" id=\"the-response-3\"><\/span>The response<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>ChatGPT had a good starting skeleton to solve the problem. Here\u2019s its response, summarized for brevity:<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\"><span class=\"hljs-bullet\">- <\/span>Add translation keys with placeholders for dynamic values\nin your JSON files (e.g., `\"viewAllAlbums\": \"View all\n{{count}} albums\"`).\n<span class=\"hljs-bullet\">\n- <\/span>Use the <span class=\"hljs-code\">`t`<\/span> function with interpolation to insert dynamic\nvalues (e.g., <span class=\"hljs-code\">`{t('viewAllAlbums', { count: albumCount })}`<\/span>).\n<span class=\"hljs-bullet\">\n- <\/span>For pluralization, define singular and plural keys in your\ntranslation files. <span class=\"hljs-code\">`react-i18next`<\/span> automatically selects the\nappropriate form based on the count.\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\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_59df10724a0991ac6e849542093cf126\" 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<h3><span class=\"ez-toc-section\" id=\"validation-and-iteration-2\"><\/span>Validation and iteration<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The AI correctly identified that we\u2019re dealing with a plural value but used i18next\u2019s outdated singular and plural forms. Here are the translation messages it suggested:<\/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=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"viewAllAlbums\"<\/span>: <span class=\"hljs-string\">\"View all {{count}} album\"<\/span>,\n  <span class=\"hljs-attr\">\"viewAllAlbums_plural\"<\/span>: <span class=\"hljs-string\">\"View all {{count}} albums\"<\/span>\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\">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_94c9b56c8d273974be906fb5bcef1ed2\" 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>This will work for languages with simple plurals like English, and won\u2019t work with newer versions of i18next that no longer use simple singular\/plural messages. ChatGPT identified the former problem noting \u201cthat Arabic and other languages have more complex pluralization rules, which <code>i18next<\/code> supports through its [newer] pluralization feature. You might need to add additional keys for these rules, depending on the language.\u201d<\/p>\n<p>So we immediately asked:<\/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=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">How do I handle Arabic pluralization in\nmy translations?\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><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_bb4986609a5f7f23f7a1a67878caae6d\" 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>ChatGPT responded with the newer type of pluralization, which works for all languages, including Arabic.<\/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\">{\n  <span class=\"hljs-attr\">\"viewAllAlbums_zero\"<\/span>: <span class=\"hljs-string\">\"\u0644\u0645 \u064a\u062a\u0645 \u0639\u0631\u0636 \u0627\u0644\u0623\u0644\u0628\u0648\u0645\u0627\u062a\"<\/span>,\n  <span class=\"hljs-attr\">\"viewAllAlbums_one\"<\/span>: <span class=\"hljs-string\">\"\u0639\u0631\u0636 \u0627\u0644\u0628\u0648\u0645 \u0648\u0627\u062d\u062f\"<\/span>,\n  <span class=\"hljs-attr\">\"viewAllAlbums_two\"<\/span>: <span class=\"hljs-string\">\"\u0639\u0631\u0636 \u0627\u0644\u0628\u0648\u0645\u0627\u0646\"<\/span>,\n  <span class=\"hljs-attr\">\"viewAllAlbums_few\"<\/span>: <span class=\"hljs-string\">\"\u0639\u0631\u0636 {{count}} \u0623\u0644\u0628\u0648\u0645\u0627\u062a\"<\/span>,\n  <span class=\"hljs-attr\">\"viewAllAlbums_many\"<\/span>: <span class=\"hljs-string\">\"\u0639\u0631\u0636 {{count}} \u0623\u0644\u0628\u0648\u0645\"<\/span>,\n  <span class=\"hljs-attr\">\"viewAllAlbums_other\"<\/span>: <span class=\"hljs-string\">\"\u0639\u0631\u0636 \u0643\u0644 {{count}} \u0623\u0644\u0628\u0648\u0645\"<\/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_4543ee5be89f464b10ba94d2df74bb7a\" 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 added the above JSON to our Arabic translation file, <code>public\/locales\/en\/translation.json<\/code>, nesting them under the <code>home<\/code> object. We used the same format for English:<\/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\">\/\/ public\/locales\/en\/translation.json<\/span>\n{\n  <span class=\"hljs-attr\">\"appName\"<\/span>: <span class=\"hljs-string\">\"Migrant Taco\"<\/span>,\n  <span class=\"hljs-comment\">\/\/...<\/span>\n  <span class=\"hljs-attr\">\"home\"<\/span>: {\n    <span class=\"hljs-attr\">\"viewAllAlbums_one\"<\/span>: <span class=\"hljs-string\">\"View {{count}} album\"<\/span>,\n    <span class=\"hljs-attr\">\"viewAllAlbums_other\"<\/span>: <span class=\"hljs-string\">\"View {{count}} albums\"<\/span>,\n  },\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\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_700130e2fe60b5b31887a75182543cf7\" 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>Of course, we had to update the button text in our <code>HomePage<\/code> component to use the pluralized translations.<\/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\/pages\/HomePage.jsx\n\n  import { Link } from \"react-router-dom\";\n  \/\/ ...\n<span class=\"hljs-addition\">+ import { useTranslation } from \"react-i18next\";<\/span>\n\n  const HomePage = () =&gt; {\n<span class=\"hljs-addition\">+   const { t } = useTranslation();<\/span>\n\n    \/\/ ...\n\n    return (\n      &lt;div className=\"...\"&gt;\n        &lt;section&gt;\n          {\/* ... *\/}\n          &lt;div className=\"...\"&gt;\n            &lt;Link to=\"\/hot-albums\" className=\"...\"&gt;\n<span class=\"hljs-deletion\">-             View all 9 albums<\/span>\n<span class=\"hljs-addition\">+             {t(\"home.viewAllAlbums\", { count: 9 })}<\/span>\n            &lt;\/Link&gt;\n          &lt;\/div&gt;\n        &lt;\/section&gt;\n        {\/* ... *\/}\n      &lt;\/div&gt;\n    );\n  };\n\n  export default HomePage;\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_28dc8314e5e07ffa355f2988b14b427d\" 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 we switched our app to Arabic, we could see the correct plural form showing for any <code>count<\/code> value.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-79661\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/ar-view-all-btn.png\" alt=\"The &quot;view all 9 albums&quot; button translated to Arabic. The &quot;9&quot; is show in Western Arabic numerals.\" width=\"272\" height=\"96\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource \u00bb<\/strong> \u00a0Our <a href=\"https:\/\/phrase.com\/blog\/posts\/pluralization\/\">pluralization guide<\/a> covers the subject in detail.<\/p>\n<p>The translation messages ChatGPT outputted didn\u2019t specify the <code>number<\/code> format for Arabic, so the Arabic translation above uses the wrong numeral system. We corrected this manually:<\/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=\"Diff\" data-shcb-language-slug=\"diff\"><span><code class=\"hljs language-diff\">\/\/ public\/locales\/ar\/translation.json\n{\n  \"appName\": \"\u0645\u064a\u062c\u0631\u0627\u0646\u062a \u062a\u0627\u0643\u0648\",\n  \/\/ ...\n  \"home\": {\n    \/\/ ...\n    \"viewAllAlbums_zero\": \"\u0644\u0627 \u062a\u0648\u062c\u062f \u0623\u0644\u0628\u0648\u0645\u0627\u062a\",\n    \"viewAllAlbums_one\": \"\u0639\u0631\u0636 \u0627\u0644\u0628\u0648\u0645 \u0648\u0627\u062d\u062f\",\n    \"viewAllAlbums_two\": \"\u0639\u0631\u0636 \u0627\u0644\u0628\u0648\u0645\u0627\u0646\",\n<span class=\"hljs-deletion\">-   \"viewAllAlbums_few\": \"\u0639\u0631\u0636 {{count}} \u0623\u0644\u0628\u0648\u0645\u0627\u062a\",<\/span>\n<span class=\"hljs-addition\">+   \"viewAllAlbums_few\": \"\u0639\u0631\u0636 {{count, number}} \u0623\u0644\u0628\u0648\u0645\u0627\u062a\",<\/span>\n<span class=\"hljs-deletion\">-   \"viewAllAlbums_many\": \"\u0639\u0631\u0636 {{count}} \u0623\u0644\u0628\u0648\u0645\",<\/span>\n<span class=\"hljs-addition\">+   \"viewAllAlbums_many\": \"\u0639\u0631\u0636 {{count, number}} \u0623\u0644\u0628\u0648\u0645\",<\/span>\n<span class=\"hljs-deletion\">-   \"viewAllAlbums_other\": \"\u0639\u0631\u0636 {{count}} \u0623\u0644\u0628\u0648\u0645\",<\/span>\n<span class=\"hljs-addition\">+   \"viewAllAlbums_other\": \"\u0639\u0631\u0636 {{count, number}} \u0623\u0644\u0628\u0648\u0645\",<\/span>\n    \/\/ ...\n  },\n  \/\/ ...\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><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_779816640f1de5ed2a484137d61e3704\" 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 change, our Arabic plurals showed the <code>count<\/code> variable in the correct, Eastern Arabic numeral system.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-79667\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/ar-plural-eastern-numerals.png\" alt=\"The &quot;view all albums&quot; button showing its Arabic label with the number &quot;3&quot; as the correct Easter Arabic numeral.\" width=\"306\" height=\"94\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/ar-plural-eastern-numerals.png 306w, https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/ar-plural-eastern-numerals-300x92.png 300w\" sizes=\"(max-width: 306px) 100vw, 306px\" \/><\/p>\n<p>\ud83d\udd17 <strong>Resource \u00bb<\/strong> Our <a href=\"https:\/\/phrase.com\/blog\/posts\/number-localization\/\">Concise Guide to Number Localization<\/a> covers numeral systems, currency formatting, and other number localization goodness.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"localized-routes\"><\/span>Localized routes<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>When we arrived at step 6 of our build plan, localizing routes, we had to iterate quite a bit to get ChatGPT to do exactly what we wanted. The AI did devise a good plan to implement the new feature. In summary:<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\"><span class=\"hljs-bullet\">- <\/span>Prefix routes with language codes (e.g., <span class=\"hljs-code\">`\/en\/about`<\/span>).\n<span class=\"hljs-bullet\">\n- <\/span>Update react-router to capture language codes from URLs and\nset the active language in i18next.\n<span class=\"hljs-bullet\">\n- <\/span>Use a wrapper component or hook to set i18next's language\nbased on the route's lang parameter.\n<span class=\"hljs-bullet\">\n- <\/span>Ensure links include the current language code in the URL,\npossibly using a helper function or custom link component.\n<span class=\"hljs-bullet\">\n- <\/span>Provide a method for users to switch languages, updating\nthe URL and content to the selected language.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_d938cd7f3b100683f4b9c525812de449\" 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>Here\u2019s ChatGPT\u2019s first take on route prefixing with react-router:<\/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\">import<\/span> { BrowserRouter <span class=\"hljs-keyword\">as<\/span> Router, Routes, Route } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-router-dom'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useTranslation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-i18next'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> HomePage <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/pages\/HomePage'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> AboutPage <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/pages\/AboutPage'<\/span>;\n<span class=\"hljs-comment\">\/\/ Import other components and pages as needed<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> App = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> { i18n } = useTranslation();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Router<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Routes<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span> <span class=\"hljs-attr\">path<\/span>=<span class=\"hljs-string\">\"\/:lang\"<\/span> <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">Layout<\/span> \/&gt;<\/span>}&gt;\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span> <span class=\"hljs-attr\">index<\/span> <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">HomePage<\/span> \/&gt;<\/span>} \/&gt;\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span> <span class=\"hljs-attr\">path<\/span>=<span class=\"hljs-string\">\"about\"<\/span> <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">AboutPage<\/span> \/&gt;<\/span>} \/&gt;\n          \/\/ Define other nested routes\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Route<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Routes<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Router<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> App;\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_eff095964448d2b0d8fb270c6cde4a7f\" 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>This was meant as an example, but it assumed we have a <code>Layout<\/code> component in our app (we don\u2019t). ChatGPT also didn\u2019t factor in initializing the new <code>:lang<\/code> param if the route didn\u2019t include a locale. We\u2019ll address these two points shortly.<\/p>\n<p>The <code>LocalizedLink<\/code> component ChatGPT offered as a drop-in replacement for react-router\u2019s built-in <code>Link<\/code> was exactly what we needed. We used it almost as-is in our app.<\/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=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/LocalizedLink.jsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { Link } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-router-dom'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useTranslation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-i18next'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> LocalizedLink = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ to, ...props }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> { i18n } = useTranslation();\n  <span class=\"hljs-keyword\">const<\/span> lang = i18n.language;\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">to<\/span>=<span class=\"hljs-string\">{<\/span>`\/${<span class=\"hljs-attr\">lang<\/span>}${<span class=\"hljs-attr\">to<\/span>}`} {<span class=\"hljs-attr\">...props<\/span>} \/&gt;<\/span><\/span>;\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\">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_86978df6e144bc08facce6b48b734023\" 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 AI also generated a <code>LocaleWrapper<\/code> component that was meant to wrap each of our pages:<\/p>\n\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=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/LocaleWrapper.jsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { useParams } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-router-dom'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useEffect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useTranslation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-i18next'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> LocaleWrapper = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ children }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> { lang } = useParams();\n  <span class=\"hljs-keyword\">const<\/span> { i18n } = useTranslation();\n\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-keyword\">if<\/span> (lang) {\n      i18n.changeLanguage(lang);\n    }\n  }, &#91;lang, i18n]);\n\n  <span class=\"hljs-keyword\">return<\/span> children;\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> LocaleWrapper;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><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_be0af01da69d18b63619100cd76c9e9c\" 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 were confused about using this component in our app and didn\u2019t have enough info to tie the solution together. We prompted further:<\/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=\"Markdown\" data-shcb-language-slug=\"markdown\"><span><code class=\"hljs language-markdown\"><span class=\"hljs-bullet\">- <\/span>I don't have a <span class=\"hljs-code\">`&lt;Layout&gt;`<\/span> element in my app; I'd like to use\nsomething \"blank\" here instead\n<span class=\"hljs-bullet\">- <\/span>How does the <span class=\"hljs-code\">`:lang`<\/span> param get initialized on first load?\n<span class=\"hljs-bullet\">- <\/span>How do I use the &#91;LocaleWrapper] component in my app?\n\nPlease provide detailed code for the localized routes\nsolution with accommodation for the above.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Markdown<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">markdown<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div id=\"acf\/text-block_710ae6e53966eebb5d18d474432e5f3c\" 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>At this point, ChatGPT generated a much more robust <code>LocaleWrapper<\/code>, which checked for supported locales and used a fallback if the <code>lang<\/code> param was not supported:<\/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=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/LocaleWrapper.jsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> React, { useEffect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useParams, Navigate } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-router-dom\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useTranslation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-i18next\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> LocaleWrapper = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ children }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> { lang } = useParams();\n  <span class=\"hljs-keyword\">const<\/span> { i18n } = useTranslation();\n\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-comment\">\/\/ If there's a lang param and it's different<\/span>\n    <span class=\"hljs-comment\">\/\/ from the current language, update it<\/span>\n    <span class=\"hljs-keyword\">if<\/span> (lang &amp;&amp; i18n.language !== lang) {\n      <span class=\"hljs-comment\">\/\/ Extend this list as needed<\/span>\n      <span class=\"hljs-keyword\">const<\/span> supportedLanguages = &#91;<span class=\"hljs-string\">\"en\"<\/span>, <span class=\"hljs-string\">\"ar\"<\/span>];\n      <span class=\"hljs-keyword\">const<\/span> fallbackLanguage = <span class=\"hljs-string\">\"en\"<\/span>;\n      <span class=\"hljs-keyword\">const<\/span> newLang = supportedLanguages.includes(lang)\n        ? lang\n        : fallbackLanguage;\n      i18n.changeLanguage(newLang);\n    }\n  }, &#91;lang, i18n]);\n\n  <span class=\"hljs-comment\">\/\/ Redirect to a default language route if no<\/span>\n  <span class=\"hljs-comment\">\/\/ lang param is present<\/span>\n  <span class=\"hljs-keyword\">if<\/span> (!lang) {\n    <span class=\"hljs-keyword\">const<\/span> detectedLang = i18n.language || <span class=\"hljs-string\">\"en\"<\/span>;\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navigate<\/span> <span class=\"hljs-attr\">to<\/span>=<span class=\"hljs-string\">{<\/span>`\/${<span class=\"hljs-attr\">detectedLang<\/span>}`} <span class=\"hljs-attr\">replace<\/span> \/&gt;<\/span><\/span>;\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>{children}<span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>;\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> LocaleWrapper;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><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_2c803dd57db5776ff8077b1554adf94f\" 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 LLM also introduced a <code>RootRedirect<\/code> component for us to add to our routes. <code>RootRedirect<\/code> redirects the root <code>\/<\/code> route to our default locale <code>\/en<\/code>, forcing a locale on all routes.<\/p>\n\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=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/components\/RootRedirect.jsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { useEffect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useNavigate } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-router-dom\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useTranslation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-i18next\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> RootRedirect = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> navigate = useNavigate();\n  <span class=\"hljs-keyword\">const<\/span> { i18n } = useTranslation();\n\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-comment\">\/\/ Determine the default or detected language<\/span>\n    <span class=\"hljs-keyword\">const<\/span> defaultOrDetectedLang = i18n.language || <span class=\"hljs-string\">\"en\"<\/span>;\n    <span class=\"hljs-comment\">\/\/ Redirect to the route with the default or detected language<\/span>\n    navigate(<span class=\"hljs-string\">`\/<span class=\"hljs-subst\">${defaultOrDetectedLang}<\/span>`<\/span>, {\n      <span class=\"hljs-attr\">replace<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    });\n  }, &#91;navigate, i18n.language]);\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">null<\/span>;\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> RootRedirect;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-39\"><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_cb1634d89fa8daf5a7e9cb37ca2f234b\" 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 followed ChatGPT\u2019s direction and added the new components to our root <code>App<\/code>:<\/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-comment\">\/\/ src\/App.jsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> {\n  BrowserRouter <span class=\"hljs-keyword\">as<\/span> Router,\n  Routes,\n  Route,\n} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-router-dom\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> HomePage <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/pages\/HomePage\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> HotAlbums <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/pages\/HotAlbums\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> TrendingPosts <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/pages\/TrendingPosts\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Navbar <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/Navbar\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> LocaleWrapper <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/LocaleWrapper\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> RootRedirect <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/RootRedirect\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> App = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Router<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"...\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Routes<\/span>&gt;<\/span>\n           <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span> <span class=\"hljs-attr\">path<\/span>=<span class=\"hljs-string\">\"\/\"<\/span> <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">RootRedirect<\/span> \/&gt;<\/span>} \/&gt;\n\n           <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span>\n             <span class=\"hljs-attr\">path<\/span>=<span class=\"hljs-string\">\"\/:lang\"<\/span>\n             <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">LocaleWrapper<\/span> \/&gt;<\/span>}\n            &gt;\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span> <span class=\"hljs-attr\">index<\/span> <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">HomePage<\/span> \/&gt;<\/span>} \/&gt;\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span>\n                <span class=\"hljs-attr\">path<\/span>=<span class=\"hljs-string\">\"hot-albums\"<\/span>\n                <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">HotAlbums<\/span> \/&gt;<\/span>}\n              \/&gt;\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Route<\/span>\n                <span class=\"hljs-attr\">path<\/span>=<span class=\"hljs-string\">\"trending-posts\"<\/span>\n                <span class=\"hljs-attr\">element<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">TrendingPosts<\/span> \/&gt;<\/span>}\n              \/&gt;\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Route<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Routes<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/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\">Router<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> App;\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_bf91e0fe6d547cc76c88e717ef886656\" 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>At this point, we discovered an error where the <code>LocaleWrapper<\/code> wasn\u2019t rendering its children. We prompted ChatGPT about this, and it correctly realized that its generated code targeted older versions of react-router. New versions don\u2019t use React <code>children<\/code> but an <code>Outlet&gt;<\/code> component instead:<\/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\">\/\/ src\/components\/LocaleWrapper.jsx\n\n  import { useEffect } from \"react\";\n  import {\n    useParams,\n    Navigate,\n<span class=\"hljs-addition\">+   Outlet,<\/span>\n  } from \"react-router-dom\";\n  import { useTranslation } from \"react-i18next\";\n  import { supportedLngs, fallbackLng } from \"..\/i18n\";\n\n<span class=\"hljs-deletion\">- const LocaleWrapper = ({ children} ) =&gt; {<\/span>\n<span class=\"hljs-addition\">+ const LocaleWrapper = () =&gt; {<\/span>\n    const { lang } = useParams();\n    const { i18n } = useTranslation();\n\n    useEffect(() =&gt; {\n      \/\/ ...\n    }, &#91;lang, i18n]);\n\n<span class=\"hljs-deletion\">-   \/\/ While we're at it, let's remove this redirection<\/span>\n<span class=\"hljs-deletion\">-   \/\/ logic that ChatGPT generated. Our `&lt;RootRedirect&gt;`<\/span>\n<span class=\"hljs-deletion\">-   \/\/ component is already taking care of this. <\/span>\n<span class=\"hljs-deletion\">-   if (!lang) {<\/span>\n<span class=\"hljs-deletion\">-     const detectedLang = i18n.language || fallbackLng;<\/span>\n<span class=\"hljs-deletion\">-     return &lt;Navigate to={`\/${detectedLang}`} replace \/&gt;;<\/span>\n<span class=\"hljs-deletion\">-   }<\/span>\n\n<span class=\"hljs-deletion\">-   return &lt;&gt;{children}&lt;\/&gt;;<\/span>\n<span class=\"hljs-addition\">+   return &lt;Outlet \/&gt;;<\/span>\n  };\n\n  export default LocaleWrapper;\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_81f3438b0fcccc0015d651f822203991\" 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>This fixed the rendering error.<\/p>\n<p>\ud83d\uddd2\ufe0f <strong>Note \u00bb<\/strong>\u00a0We had to iterate further with ChatGPT to ensure that routes without locales redirect to localized routes e.g. <code>\/trending-posts<\/code> \u2192 <code>\/en\/trending-posts<\/code>. We also wanted to address the 404 Not Found case. ChatGPT was able to help us solve these problems, and you can <a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/tree\/main\/complete\">see the final code in our GitHub repo<\/a>.<\/p>\n<p>\ud83d\udd17 <strong>Resource \u00bb<\/strong> We continued with ChatGPT, localizing dates, numbers, and more. <a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/tree\/main\/complete\">See the complete project on GitHub<\/a>.<\/p>\n<p>\ud83d\udd17 <strong>Resource \u00bb<\/strong> You can view the entire conversation with ChatGPT on GitHub. Go to the <a href=\"https:\/\/github.com\/PhraseApp-Blog\/dev-with-llm\/tree\/main\/conversation\">conversation directory<\/a> and run <code>npx serve .<\/code><\/p>\n<figure id=\"attachment_79673\" aria-describedby=\"caption-attachment-79673\" style=\"width: 600px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-79673\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2024\/03\/completed-app.gif\" alt=\"An animation showing navigation between the different pages and translations of our app.\" width=\"600\" height=\"450\" \/><figcaption id=\"caption-attachment-79673\" class=\"wp-caption-text\">Our completed app, with Chinese added as well<\/figcaption><\/figure>\n<p>\ud83d\udd17 <strong>Resource \u00bb<\/strong> We wanted to focus on working with ChatGPT in this tutorial. We have a <a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-react-apps-with-i18next\/\">complete tutorial for React and i18next<\/a> if you want to dive deeper.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"key-takeaways\"><\/span>Key takeaways<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Working with ChatGPT to generate code can be both frustrating and rewarding. A few important practices can increase our chances of success:<\/p>\n<ul>\n<li><strong>Know your stuff!<\/strong> \u2014 Think of ChatGPT as a junior friend with incredibly wide-reaching knowledge. You must know your tech well to guide it to an appropriate solution. Otherwise, you\u2019re at the mercy of whatever it generates, for better or worse.<\/li>\n<li><strong>Create a plan (without code)<\/strong> \u2014\u00a0Before solving a complex problem, prompt ChatGPT to give you a step-by-step plan without any code. This <strong>chain of thought<\/strong> prompt will reveal the AI\u2019s approach to your problem early and highlight any misunderstandings that could create problems.<\/li>\n<li><strong>Give appropriate context <\/strong><strong>\u2014<\/strong>\u00a0ChatGPT knows all about React, i18next, and many other popular technologies. However, it doesn\u2019t know <strong>your<\/strong> app and how <strong>you<\/strong> want it to work. Give the AI enough details to guide it.<\/li>\n<li><strong>Verify!<\/strong> \u2014 ChatGPT will hallucinate and make mistakes; it doesn\u2019t always know the latest versions of libraries you\u2019re using. Make sure to review and test the code it gives you thoroughly.<\/li>\n<li><strong>Iterate<\/strong> \u2014\u00a0When you don\u2019t get exactly what you want, tell ChatGPT and ask it to help with your specific issues.<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"wrapping-up\"><\/span>Wrapping up<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We hope you enjoyed this adventure in coding with an LLM. And we hope you learned a thing or two. Stay tuned for more software localization tutorials. Happy coding.<\/p>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>We build and localize an app using ChatGPT to generate code while observing best prompting practices.<\/p>\n","protected":false},"author":41,"featured_media":80090,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_stopmodifiedupdate":true,"_modified_date":"","_searchwp_excluded":"","footnotes":""},"categories":[40],"class_list":["post-79623","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\/79623"}],"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=79623"}],"version-history":[{"count":10,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/79623\/revisions"}],"predecessor-version":[{"id":81804,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/79623\/revisions\/81804"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media\/80090"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media?parent=79623"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/categories?post=79623"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}