{"id":10743,"date":"2024-03-30T15:08:43","date_gmt":"2024-03-30T15:08:43","guid":{"rendered":"https:\/\/putridparrot.com\/blog\/?p=10743"},"modified":"2024-03-30T15:10:44","modified_gmt":"2024-03-30T15:10:44","slug":"i18n-in-react","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/i18n-in-react\/","title":{"rendered":"i18n in React"},"content":{"rendered":"<p>i18n (internationalization) is the process of making an application work in different languages and cultures. This includes things like, translations of string resources through to  to handling date formats as per the user&#8217;s language\/culture settings, along with things like decimal separators and more.<\/p>\n<p>There are several libraries available for helping with i18n coding but we&#8217;re going to focus on the <a href=\"https:\/\/react.i18next.com\/\" rel=\"noopener\" target=\"_blank\">react-i18next<\/a> in this post, for no other reason that it&#8217;s by other teams in the company I&#8217;m working for at the moment.<\/p>\n<p><strong>Getting Started<\/strong><\/p>\n<p>The <a href=\"https:\/\/react.i18next.com\/\" rel=\"noopener\" target=\"_blank\">react-i18next<\/a> website is a really good place to get started and frankly I&#8217;m very likely to cover much the same code here, so their website should be your first port of call.<\/p>\n<p>If you&#8217;re want to instead follow thought the process here then, let&#8217;s create a simple React app with TypeScript (as is my way) using<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nnpx create-react-app i18n-app --template typescript\r\n<\/pre>\n<p>Now add the packages <em>react-i18next<\/em> and <em>i18next<\/em> i.e.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nnpm install react-i18next i18next --save\r\n<\/pre>\n<p>In this <em>Getting Started<\/em> I&#8217;m going to also include the following libraries which will handle loading the translation files and more <\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nnpm install i18next-http-backend i18next-browser-languagedetector --save\r\n<\/pre>\n<p><strong>Adding localized strings<\/strong><\/p>\n<p>We&#8217;re going to first show an example of embeding the string into a .ts file, however it&#8217;s much more likely we&#8217;ll want them in a separate file (or multiple files). Let&#8217;s get this started by creating a file named <em>i18n1.ts<\/em>.<\/p>\n<p>In the file we&#8217;ll just write one string and here it is<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nimport i18n from &quot;i18next&quot;;\r\nimport { initReactI18next } from &quot;react-i18next&quot;;\r\nimport LanguageDetector from &quot;i18next-browser-languagedetector&quot;;\r\n\r\ni18n\r\n  .use(LanguageDetector)\r\n  .use(initReactI18next)\r\n  .init({\r\n    resources: {\r\n      en: {\r\n        translation: {\r\n          welcome: &quot;code: Hello string en World&quot;\r\n        },\r\n      },\r\n      fr: {\r\n        translation: {\r\n          welcome: &quot;code: Bonjour string fr World&quot;\r\n        }\r\n      }\r\n    },\r\n    debug: true,\r\n    interpolation: {\r\n      escapeValue: false, \r\n    },\r\n  });\r\n\r\nexport default i18n;\r\n<\/pre>\n<p>As you can see we&#8217;re using the LanguageDetector to automatically set our resources. On my browser the language is set to en-GB so my expectation is to see the <em>en<\/em> strings as I&#8217;ve not listed <em>en-GB<\/em> specific strings.<\/p>\n<p>IMPORTANT: Before we can use this, go to <em>index.tsx<\/em> and import this file <em>import &#8220;.\/i18n1&#8221;;<\/em> so the bundler includes it.<\/p>\n<p><strong>Display\/using our localized strings<\/strong><\/p>\n<p>Now we&#8217;ll need to actually use our translation strings. react-i18next comes with hooks, HOC&#8217;s and standard JS type functions to interact with our translated strings. Let&#8217;s clear out most of the App.tsx and make it look like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nimport React from &quot;react&quot;;\r\nimport &quot;.\/App.css&quot;;\r\nimport { useTranslation } from &quot;react-i18next&quot;;\r\n\r\nconst lngs: any = {\r\n  en: { nativeName: &quot;English&quot;},\r\n  fr: { nativeName: &quot;French&quot;},\r\n}\r\n\r\nfunction App() {\r\n  const { t, i18n } = useTranslation();\r\n\r\n  return (\r\n    &lt;div className=&quot;App&quot;&gt;\r\n      &lt;div className=&quot;App-header&quot;&gt;\r\n        {t(&quot;welcome&quot;)}\r\n        &lt;div&gt;\r\n          {Object.keys(lngs).map(lng =&gt; {\r\n            return &lt;button key={lng} style={{margin: &quot;3px&quot;}}\r\n              onClick={() =&gt; i18n.changeLanguage(lng)} disabled={i18n.resolvedLanguage === lng}&gt;{lngs&#x5B;lng].nativeName}&lt;\/button&gt;\r\n          })}\r\n        &lt;\/div&gt;\r\n      &lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n  );\r\n}\r\n\r\nexport default App;\r\n<\/pre>\n<p>This code will simply display the English\/French buttons to allow us to change the language as we wish. Notice, however, that if we add a new language, such as <em>de: { nativeName: &#8220;German&#8221;}<\/em> and no strings exist for that language, you&#8217;ll end up seeing the &#8220;key&#8221; for the string. We can solve this later.<\/p>\n<p>At this point if all is working, you can start the application up and switch between the languages. You&#8217;ll see that the strings will be prefixed with <em>code:<\/em> just to make it clear where the strings are coming from, i.e. our code file.<\/p>\n<p><strong>Moving to resource type files (part 1)<\/strong><\/p>\n<p>As I mentioned, we probably don&#8217;t want to embed our string in code. It&#8217;s preferable to move them into their own .JSON files. <\/p>\n<p>Create a folder within the <em>src<\/em> folder named <em>locales<\/em> (the names of the folders and files doesn&#8217;t really matter but it&#8217;s good to be consistent) and within that we&#8217;ll have one folder name <em>en-GB<\/em> and another named <em>fr<\/em>. So the English strings are specific to GB but the French covers all French languages locales.<\/p>\n<p>Now in <em>en-GB<\/em> create the file translations.json which will look like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n{\r\n  &quot;welcome&quot;: &quot;src: Hello en-GB World&quot;\r\n}\r\n<\/pre>\n<p>For the French translations, add translations.json to the <em>fr<\/em> folder and it should look like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n{\r\n &quot;welcome&quot;: &quot;src: Bonjour fr World&quot;\r\n}\r\n<\/pre>\n<p><em>Note the src: prefix is again, just there to allow us to see where our resources are coming from in this demo.<\/em><\/p>\n<p>We now need to change our i18n.ts file to look like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nimport i18n from &quot;i18next&quot;;\r\nimport { initReactI18next } from &quot;react-i18next&quot;;\r\nimport LanguageDetector from &quot;i18next-browser-languagedetector&quot;;\r\n\r\nimport enGB from &quot;..\/src\/locales\/en-GB\/translation.json&quot;;\r\nimport fr from &quot;..\/src\/locales\/fr\/translation.json&quot;;\r\n\r\nconst resources = {\r\n  en: {\r\n    translation: enGB\r\n  },\r\n  fr: {\r\n    translation: fr\r\n  }\r\n};\r\ni18n\r\n  .use(LanguageDetector)\r\n  .use(initReactI18next)\r\n  .init({\r\n    resources,\r\n    debug: true,\r\n    interpolation: {\r\n      escapeValue: false, \r\n    },\r\n  });\r\n\r\nexport default i18n;\r\n<\/pre>\n<p>So in this code we&#8217;reimporting the JSON and then assigning to the <em>resources<\/em> const. This is very similar to the other way we bought the resources into the i18n.ts file, just we&#8217;re importing via JSON files.<\/p>\n<p><strong>Moving to resource type files (part 2)<\/strong><\/p>\n<p>There&#8217;s another way to import the translated strings and that is to simply include the files in the public folder of our React application. So in the public folder add the same folders and files, i.e. <em>locales<\/em> folder with <em>en-GB<\/em> and <em>fr<\/em> folders with the same translation.json files as the last example. I&#8217;ve changed the src: prefix on the strings to public: again just so I can prove, for this demo, where the strings originate from.<\/p>\n<p>In other words, here&#8217;s the <em>en-GB<\/em> translations.json file<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n{\r\n  &quot;welcome&quot;: &quot;public: Hello en-GB World&quot;\r\n}\r\n<\/pre>\n<p>and the <em>fr<\/em> translations.json file<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n{\r\n  &quot;welcome&quot;: &quot;public: Bonjour fr World&quot;\r\n}\r\n<\/pre>\n<p>Now back in our i18n.ts file change it to look like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nimport i18n from &quot;i18next&quot;;\r\nimport { initReactI18next } from &quot;react-i18next&quot;;\r\nimport Backend from &quot;i18next-http-backend&quot;;\r\nimport LanguageDetector from &quot;i18next-browser-languagedetector&quot;;\r\n\r\ni18n\r\n  .use(Backend)\r\n  .use(LanguageDetector)\r\n  .use(initReactI18next)\r\n  .init({\r\n    fallbackLng: {\r\n      &quot;en&quot; : &#x5B;&quot;en-GB&quot;]\r\n    },\r\n    debug: true,\r\n    interpolation: {\r\n      escapeValue: false, \r\n    },\r\n  });\r\n\r\nexport default i18n;\r\n<\/pre>\n<p><strong>Fallback<\/strong><\/p>\n<p>My browser is setup as <em>English (United Kingdom)<\/em> which is <em>en-GB<\/em> and all works well. But what happens if we add a detect a language where we have no translation strings for? Well let&#8217;s try it by adding a German option to the <em>lngs<\/em> const in our App.tsx, so it looks like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nconst lngs: any = {\r\n  en: { nativeName: &quot;English&quot;},\r\n  fr: { nativeName: &quot;French&quot;},\r\n  de: { nativeName: &quot;German&quot;},\r\n}\r\n<\/pre>\n<p>Now, what happens ? Well we&#8217;ve see the key for the string, which is probably not what we want in production, better to fall back to a known language. So we need to change the i18n.ts file and add a <em>fallbackLng<\/em>, I&#8217;ll include the whole i18n object so it&#8217;s obvious<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ni18n\r\n  .use(LanguageDetector)\r\n  .use(initReactI18next)\r\n  .init({\r\n    resources,\r\n    lng: &quot;en-GB&quot;,\r\n    fallbackLng: &quot;en&quot;,\r\n    debug: true,\r\n    interpolation: {\r\n      escapeValue: false, \r\n    },\r\n  });\r\n<\/pre>\n<p>Now if our application encounters a locale it&#8217;s not setup for, it&#8217;ll default to the fallback language (in this case) English. We can also achieve this using<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nfallbackLng: {\r\n  &quot;default&quot;: &#x5B;&quot;en&quot;]\r\n},\r\n\r\nUsing this syntax we can also map different languages to specific fallback languages, so for example we might map Swiss locale to map to use French or Italian. This is achieved by passing an array of fallback languages (as taken from the &lt;a href=&quot;https:\/\/www.i18next.com\/principles\/fallback&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Fallback documentation&lt;\/a&gt;\r\n\r\n&#x5B;code]\r\nfallbackLng: { \r\n  &quot;de-CH&quot;: &#x5B;&quot;fr&quot;, &quot;it&quot;], \/\/ French and Italian are also spoken in Switzerland\r\n},\r\n<\/pre>\n<p>Finally with regards fallback languages, we can write code to determine the translation to use, again the <a href=\"https:\/\/www.i18next.com\/principles\/fallback\" rel=\"noopener\" target=\"_blank\">Fallback documentation<\/a> has a good example of this, so I&#8217;d suggest checking that link out.<\/p>\n<p><strong>Code<\/strong><\/p>\n<p>Code for this post is available on <a href=\"https:\/\/github.com\/putridparrot\/blog-projects\/tree\/master\/i18nReact\/i18n-app\" rel=\"noopener\" target=\"_blank\">github<\/a> which includes i18n files 1-3 for each of these options for creating translations. just change the index.tsx to import the one you wish to use.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>i18n (internationalization) is the process of making an application work in different languages and cultures. This includes things like, translations of string resources through to to handling date formats as per the user&#8217;s language\/culture settings, along with things like decimal separators and more. There are several libraries available for helping with i18n coding but we&#8217;re [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[137,243],"tags":[],"class_list":["post-10743","post","type-post","status-publish","format-standard","hentry","category-i18n","category-react"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/10743","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=10743"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/10743\/revisions"}],"predecessor-version":[{"id":10766,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/10743\/revisions\/10766"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=10743"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=10743"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=10743"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}