Have you ever wished to have a flexible approach to localize your content to scale easily? Join my talk, and I'll show you multiple strategies to translate and localize your content with Remix. I'll share with you flexible dynamic route options from Remix to generate localized content in a practical way, including a headless approach demo and how to scale your solution in the future. Let’s “Remix” to localize your content!
Let’s Remix to Localize Content!
AI Generated Video Summary
This Talk explores Remix and internationalization, discussing the impact and logic of internationalization and the challenges developers face. It highlights the global usage of the internet and the importance of localizing content. The fundamental logic of internationalization is explained, including location-based, header-based, and URL-based approaches. The implementation of internationalization in Remix is demonstrated using Remix IAT Next package and a Content Management System. The Talk also covers client-side and server-side configuration, importing and wrapping translation files, and implementing dynamic routes with Remix.
1. Introduction to Remix and Internationalization
Hi everyone. I'm very excited to see you all and talk about Remix and internationalization. I'm Arisa, a dev rel engineer at Storyblock and an ambassador at Girl Code and GDE. We'll explore the impact and fundamental logic of internationalization, and how it works with Remix. There are still active discussions on Remix and internationalization, so feel free to contribute. Let's dive in and see what developers think about implementing internationalization logic. It's not a prioritized hot topic, but why? According to Maya, it's not implemented on a day-to-day basis and there are issues with localized text and internationalization keys. Let's explore internationalization from a global perspective and consider the user's point of view.
Well, not to waste any more time, here is little things about me. My name is Arisa and I'm a dev rel engineer at Storyblock, as well as an ambassador at Girl Code and GDE, Google Developer Expert in Web Technologies.
Well, there are 3 takeaways from my talk. First of all we are going to take a look at the impact of the internationalization as well as the fundamental logic, and lastly we are going to see how Remix and internationalization works together.
There are a few notes that I want you to keep in your mind before you watch my talk. First of all, there are still active discussions going on about Remix and internationalization, which means that you could be one of the persons to contribute to improve DX. So feel free to take a look at the discussions or even join the discussions. I'm going to share with you the slides later on after my talk on Twitter, so you will get access to all the links that I pasted in these slides.
All right, so let's get to the point. We are here to talk about the internationalization and how it works with Remix. So while I was preparing my talk, I wanted to include as many as opinions from the developers out there, not just from my side. So I started to ask this question on Twitter. So do you like to implement internationalization logic? And this is the result I got. So first of all, I got the result from 30... 36 developers And they say like about 41.97% of the developers. They say, like, it's not my favorite. It's kind of annoying process to do that.
And based on this fact, I also wanted to know, like, hmm, okay, I want to know more about the details. But probably from what I see this quick poll question on Twitter, maybe for us developers, it is not a prioritized hot topic compared to performance and accessibilities. But of course I want to know more. Why? So I asked. If the internationalization is not your favorite part, why is it like this? Here's my friend, Maya, responded me. Thanks, Maya. First of all, she says, like, well, it's not like she implements internationalization feature, like, on to the day to day basis. Means that when she needs to do that, she needs to take a look again at how it works from the internationalization libraries or the frameworks she uses every time. Also, there's a little bit of issue between localized text and internationalization keys, you know, during the test. So we know that, okay, probably, like, internationalization is not the hottest topic and that's not something like fun part for developers.
And based on what we think, of course, we also need to take a look at or see like, how is it like in the world? And how is it like for the user's perspective as well.
2. Global Internet Usage
Let's talk about the global usage of the internet. There are 5.07 billion internet users worldwide. Only 25.9% of the content is in English, while 74.1% is non-English. China has the most internet users, and Asia accounts for over half of global internet users. Localizing content may not be a hot topic for developers, but it's too significant to ignore.
So here's the little numbers and the facts, I'd like to start from something bigger numbers. So first of all, I want you to think about this number. What do you think this 5.07 billion numbers, number stands for, it is actually the numbers of the users in the world who use the internet. So breaking down, you know, like these big billions of numbers into smaller percentages, starting from 25.9%. What do you think about this percentage? This is actually a percentage of the English content on the 25.9%. It's less than 50%. Which means that if you're good at math, then you could probably like, calculate this number 74.1%. Yes, that's the rest of the percentage of the users who access non English content on the internet. Which leads us to this keyword, China. So China actually has the most internet users worldwide. No surprise from based on this fact. We also could think about this keyword, Asia. So yes, Asia needs more than a half of global internet users. That's massive, right? So yes, now we know. Probably localizing content is not the hottest topic for us developers. However, we cannot ignore no matter what. You know like, from this more than a half of the users in the world. It's too massive numbers to ignore.
3. Fundamental Logic of Internationalization
Let's talk about the fundamental logic of internationalization. It works in three ways: location from the IP address, accept language header from the HTTP request or navigator languages, and the identifier in the URL. We will use two hybrid ways to give users more flexibility and control. There are three patterns for localizing the URL: different domains, URL parameters, and localizing subdirectories. Now let's discuss frameworks and libraries that utilize internationalization.
So based on what we know, what we feel, let's talk about the fundamental logic of the internationalization. So first of all, internationalization works in three ways, to determine the languages and the regions. The first approach, the most top and here says look location from the IP address means that based on the IP address, for example, where I am based in written based based on right now in Germany, the content that I take a look at on the internet will detect I'm based in Germany. So the content will be, you know, displayed in German sometimes if they take this approach, if they use the second approach means using accept language header from the HTTP request or the navigator languages means that they would take a look at my language preference. So in the browser I prefer to use the English. So that's what I configure my setting and that's what you know, like the information that is going to be used to, you know, like you know, return the localized content for me. So sometimes even when I'm based in Germany, I see the content in English as I preferred. And the third option is using the identifier in URL. Basically, it's actually like translating or localizing the URL for the users. So it means like the easiest example would be like I will have the English, German and Japanese, let's say like language like, selector buttons on the browser, then I can click to select to take a look at the languages. In this talk, we are going to use two ways to go hybrid because I want you I want our users to be more flexible and to have more control by themselves. So I would leave the, you know, option for them to change the languages of their choice on the browser from the UI, as well as first of all, like, let's be nicer detecting their preferred language setting on the browser. So we saw together like how it works. Or what's the fundamental logic in the internationalization. And as for the identifier URL, there are three patterns to take a look at it. So let's break it down. So pattern one. So this is a way to differentiate, you know, like, the localized content by domains, basically, you're going to create totally different kind of websites in this case, but the domains are different. So it won't follow the same origin policy means that your websites could be considered as kind of copy. So from the security perspective, it's kind of suspicious. Let's move on to the pattern two. So pattern two uses the URL parameters. Maybe for developers, it makes sense, but it's not user friendly for the users. And also for everyone, this URL doesn't look clean, right, so we don't want to take this way. Moving on to the pattern three, which is localizing subdirectories. In this case, we are going to add the localized, you know, slug, after you know, like something.com, or slash in there. So in this way, it's quite clear for the users for which languages they are taking a look at it. It's also easier for us to identify which language we deliver to the users.
Alright, so let's now talk about frameworks and the libraries. Why suddenly from out of nowhere? Well, it's actually quite relevant topic, because some of the frameworks and the libraries, and they use the internationalization of frameworks.
4. Internationalization in Remix
Let's take a look at how internationalization works in Remix. There are two approaches: using the Remix, IAT Next package or a Content Management System. Remix IAT Next is an NPM package specifically designed for internationalization in Remix, built by Sergio. We'll create configuration files and translated files, with English as the default language. We'll use the property name 'greeting' and its localized values for translation. After creating the translated files, we'll create an i18next configuration file, specifying supported languages and a fallback language. Finally, we'll import the i18next configuration file and call the supported language and fallback language values.
So let's take a look at how it works in Remix. So in Remix case, there are in general two approaches to choose. So the first option would be using the package called Remix, IAT Next. And the second approach would be like using Content Management System.
So first of all, let's take a look at what is Remix IAT Next. So I told you before that it is an NPM package, right? And this is more precisely made for the Remix to use this internationalization framework called IAT Next. And that is built by Sergio. So thank you, Sergio, for building such an amazing NPM package because now, because of that, we have more options to choose.
Alright, so let's take a look at example from this case. And first of all, we are going to create a couple of configuration files. But to get started, we're going to create translated files. In this case, I'm going to create one default language and one another language. So I set English as a default language. So I created on the right hand side and on top, the file called common dot JSON file, I'm going to show you why I already know I can give a name to, you know, like the common dot JSON file in this case. But let's focus on for now how we can accept the property name and the key values, which is actually like localized on string values.
So first of all, I have decided to, let's say call this property name as greeting. So this value I'm going to use when I'm going to translate from the source code, you know level. So on the right hand side is the key value, which is localized string value. I want say hello in English and to translate into Japanese. In this case, I have created another translated file for, you know, like storing, you know, this Japanese translated file in, under the directory of JA. So the value will be hello in Japanese. So after creating these translated files, we are going to create i18next configuration file.
So, as I said before, for some reason I already knew that I was able to give the name of the translated files to be command.js, right? So here's the reason why. If you pay attention to this default, NS, by the way, NS stands for the namespaces, and I gave the name as command. So that's why I already knew that I could give a name as command.js. So there are a couple of other configurations, but nothing too complicated because first of all, I just want to list up the supported languages, English, Japanese, and of course, I want to have a fallback language to be the default language. Based on what we created to translate these files, and also the IAT Next translation, sorry, the configuration file, now is the time to import this IAT Next configuration file into the IAT Next.server.js file.
So after importing it, of course, I want to call couple of the values. First of all, the supported language list, and also the fallback language list. And based on that, remember, this, you know, like configuration file we created, these, you know, like languages list are the arrays inside of the arrays, and the values inside are strings.
5. Client Side Configuration and IATNextProvider API
So I can iterate these values from the configuration file that I have created. Here's the client side configuration file, entry.client.jsx, where we wrap the Remix browser component with the IATNextProvider API. It's important to load translation files before hydration to ensure the app is interactive. Wrapping the Remix browser component with the IATNextProvider API ensures the timing of hydration and translation file loading. This configuration is crucial for the app to display the UI with styles and loaded translation files.
So I can iterate these values from the configuration file that I have created. And in here, what I'm doing is that I am setting the translation file paths.
Alright, now moving on to create the client side and server side configuration files, I'm only going to show you the details of the client side configuration files, because you can take a look at more details about the server side configuration files, and couple of lines of the code are quite, quite similar. So I don't want to, you know, like repeat the similar content.
So here's the entry.client.jsx file, which is the client side configuration file. There are a couple of lines of the code, but I want you to pay attention to the only highlighted lines of the code. So first of all, there is an API called IATNextProvider coming from React IATNext. Keep in your mind that in this way, we are going to install and use a couple of other IATNext related packages. So make sure that this is actually coming from React IATNext. So after I told you that I want you to pay attention to this API, here is the place where you can actually like call inside of the JSX scope. So we are going to wrap this Remix browser component that is coming from Remix side, and we are going to see the logic why we need to wrap this component precisely with this IATNext provider API. But to give you a little bit more context, what we are wrapping is that while this Remix browser component is or should be used by React to hydrate the HTML. So there's another clue that why I am insisting and highlighting these lines of the code. Actually, it is very important to see the timing of when it's going to be hydrated. When the translation files are going to be loaded. So here are the answers to take a look at together. First of all, let's see together like why translation files, you know, should be loaded before the hydration. Well, let's probably like start to imagine from is translation files are not loaded before hydration happens. Imagine that hydration, let's say, sorry, not imagine, but then when hydration happens, users already will be able to see the UI with all the styles, right? But the application itself is not yet interactive. So in this case, translation files are not yet loaded and hydration already happened. So if I want to change the language from English to Japanese to see hello, I cannot see that because first of all, these, you know, translation values are not yet ready. Based on that, if we imagine if the translation files are already loaded before the hydration happens. Now we know this case would work because the app is already kind of interactive in a way. So when the hydration happens, the UI is ready with the styles and the translation files are already loaded. Moving on to a little bit more in depth, let's say question based on what we saw the configuration file from the client side. So why wrapping, you know, this remix API called remix browser with the API code IAT next provider that is coming from react IAT next. So I took a look at a little bit more details from the node modules file. And here's what I was able to reach out. So I 18 I 18 and provider on it actually includes this use memo, React hope.
6. Internationalization Configuration and Root.jsx
The use memo and React hope APIs help catch the result of calculations between re-renders, improving performance. Internationalization is key to improving performance, and using packages like Remix eliminates the need to implement features from scratch. Server-side configuration files can identify users' preferred languages and redirect them. In the root.jsx file, pay attention to the useLoaderData API, which retrieves the locale from the loader function. The JSON API simplifies the process of returning the locale. Switching between languages in the browser demonstrates the translation functionality.
So I 18 I 18 and provider on it actually includes this use memo, React hope. So this react hope is letting you to catch the result of the calculation between re-renders. If I say in a little bit easier way, basically like if the values of this internationalization configuration and default name spaces, mainly to translate the files are the same, then we are not going to trigger the re-rendering because that's going to cost you a lot and it's not performant. We want to avoid being not performant. So if the values of these, you know, like, let's say calculations are the same, that we are going to catch that and not trigger the re-rendering. But if the values are being updated, then we are going to trigger the re-rendering. So in this, in this perspective, you can see that internationalization is kind of the key to improve the performance or you need to consider about the performance as well. But by using all these packages, it's already considered about it. So you do not have to implement such kind of features from scratch on your end. So I'm just a little info about like completely, you know, instead of completely skipping about how to configure like the server-side configuration file, first of all, I want to mention you that identifying users, let's say preferred languages of their choice and redirecting them can be done on server side. And if you want to take a look at it, here's the link and the readme that you can take a look at it.
All right, let's move on to use the configurations that we have set up so far in action. So in here, we are going to take a look at the this file called root.jsx in under the directory of the app. This is the very fundamental and important, I would say, like file in the Remix app. So in here, I want you to pay attention to three APIs that are coming from Remix side. So starting from useLoaderData. So this useLoaderData will get the locale from the loader function in app above. So this loader function is not just a random function that we just defined. Instead, it is actually a back-end API that is coming from Remix and it's already wired up through the useLoaderData. Means that these APIs are linked to each other already. So what we need to do in the end is that we want to first of all, get the locale, right? Means we need to return the locale. So to do that, we can, you know, like request for the response to, you know, give us a locale. But instead of, you know, like defining by writing, you know, a few lines of the code in here to say like new response headers, etc. You can call another API called JSON that is coming from Remix to complete such kind of, let's say, process in one-liner-ish code. Alright, so let's take a look at, on the browser how it works. So let's see, if I switch back and forth between Spanish and English, now you can see the header navigation items are being translated. And also the Hello greeting message has been translated as well. And also the buttons below, over there as well. So to translate, let's say, this Hello part. Here's the little example.
7. Importing and Wrapping Translation Files
In any route of your Remix app, you can import the use translation from react-ia-t-next and wrap the property coming from the translated files. We have two translated files, with the 'greeting' property set as the body. However, I confess that I used URL parameters instead of translating the text. We don't want to maintain translation files in the source code and want to achieve localized URLs.
In any route of your Remix app, you can import this use translation coming from react-ia-t-next. And based on what we configure and created, all the fundamentals, all we need to do is just wrapping, you know, the property that is coming from the translated files. Remember that we created two translated files, right? And in the property, I set the body as greeting. So that's what I am wrapping up.
So here is the result that you can see. And before I'm going to actually, like, go to the next slide, I have a little confession to you. So you need you need to, or I want you to pay attention to this here at the URL path, you see a sneaky little like slash, which includes the URL parameters, right? So here are the three confessions from my side. Let me be honest, I use the URL parameters. Yes. And that's not something that I wanted to do. And in fact, in the beginning of this talk, I told you that we want to avoid using it. And secondly, do we developers need to maintain the translation files? If we think about let's say the process or the configurations that we have created, we now remember that there are two translation files right in English and Japanese. We do not want to maintain these translation files in the source code level, right? Also, the third confession. Did we translate this lux? No, I don't think so. I haven't yet translated this lux. Instead, I used the URL parameters. Here's another friend of mine on Twitter. Thanks again for giving me the comments. He also considered about the same. If he wants to split up the translation files for route, it is possible, but it's a little bit cumbersome. Of course, it's not an ideal to take care of all of these translated files on the source code level. We don't want to do that. Also, he wants to know about like, you know, how to localize the URLs. So let me be clear what we want to achieve for the rest of my talk. So we want to achieve actually localized URL, right. And also, we did not want to have like translation files in our code.
8. Implementing Internationalization with Remix
Remix can implement internationalization using either the Remix IAT Next NPM package or a content management system. Storyblock is a recommended CMS for its flexibility in localizing content. Different CMS options offer various ways to structure and store localized content. This approach involves storing localized content in dedicated folders, ensuring clear separation. Translators can work on translating content and managing where it is stored. Dynamic routes can be created using the flat route approach in Remix, allowing for easy creation of nested dynamic routes without editing source code.
So here's another example. You know, based on what we saw, like Remix in general, can have two approaches to implement internationalization. First of all, we took a look at together to use this NPM package called Remix IAT Next. And secondly, we're going to take a look at the content management system example. So you can choose whatever kind of content management system or headless CMS is out there.
In this talk, I picked up Storyblock because this is the most I would say like comfortable. And the familiar CMS for me, I use the most. And also it gives the most numbers of, let's say, structures to configure how you want to localize the content on the CMS side. But I'm not going to talk about like how you can implement between Storyblock and Remix. This can be explained in this tutorial, let's say, blog post, so you can easily take a look at it after my talk.
All right. Depending on the headless CMS is or the CMS is of your choice, they will give you average, like, one to four ways to structure to store localized content. And as part of it, it also will give you some possibilities, or not just you, for your translators and content editors to structure how they want to nest these dynamic routes. I wish I could have more time to explain and show you like all these four approaches, but for time conscious, I'm going to show you this folder and the translation.
So this approach is quite straightforward, because as you can see, all localized content are being, you know, like stored in the dedicated folders. So there's no way that the translators will mix up localized pages and content. So first of all, before going to take a look at more in depth in how it works with the source code level, I'm going to show you how it works on the browser. So here I'm at the default, let's say blog overview page, which is in English. And of course, if I'm going to take a look at the traveling to Salt Lake City, all these you know, like English blog posts for relevant pages are being stored at the folder. And the local, let's say URL shouldn't include any slugs, because this is the default language, right. But if I go to this exactly the same page traveling to Salt Lake City in Japanese, it should include JA, which stands for the Japanese slug.
And also, the contents are being translated on the right hand side, you can see the your translators can work on to store like the translated values of the content. And if I go back to the previous page, which is the blog overview page, and also includes JA in the URL. And if I go back to the default home page, which is in the most route, it shouldn't include any, let's say, even a n of the language slug, and of course the contents are being back to the English. So in this way you can you can see that you do not have to deal with translated files anymore in your source code. Instead, the translators who should be actually the one being responsible to start to translate the content, and managing the content where it should be stored, can have all the, let's say flexibility. And here's the little logic how you can actually create such kind of dynamic route logic in there. So in Remix, there are a couple of ways to let's say like render these kind of dynamic routes. But I picked up this flat route, because by using it, you will allow whoever you know like wants to create pages and even create nested dynamic routes from the content management system. They can do that without you know, like editing any source code.
9. Implementing Dynamic Routes with Remix
Once you implement this feature, dynamic routes, including nested structures, can be done from the CMS side. Remix provides a useful parameter called params, which displays the full dynamic path of the pages. This logic is used to render dynamic routes, including nested routes.
So once you implement this feature, by using this flat route then you know dynamic routes even including the nested structure can be done from the CMS side. So here's the little kind of example. You already know this use loader data and JSON, right. So I'm going to more paying attention to these green highlighted line of the code. So Remix already provides you a very useful parameter called params. And if you let's say like log out, if you log this params with a square brackets and a string value of the asterisk, like you can see on the source code. When you go back and forth different pages, it will show you, you know, like the full path, full dynamic path of the pages that you are taking a look at it. So we're using this logic to render all all these you know, dynamic routes, including the nested routes.