Let’s Remix to Localize Content!


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!



Hi everyone, I hope you're doing well and I'm very excited to see you all as well as I'm very excited to talk about remix and internationalization. Well, not to waste any more time, here's little things about me. My name is Arisa and I'm a DevRel engineer at Storyblock as well as an ambassador at Girl Code and GDE, Google Developer Expert in Web Technologies. Well, there are three 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. Means that you could be one of the person to contribute to improve the DX. So yeah, 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 this slides. All right, so let's get to the point. We are here to talk about 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 36 developers. And they say like about 41.97% of the developers, they say like, it's not my favorite. It's kind of an annoying process to do that. And based on this fact, I also wanted to know like, 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? So here's my friend, Maya, responded me. Thanks, Maya. And first of all, she says like, well, it's not like she implements internationalization feature, like on to day to day basis. Means that when she needs to do that, then she needs to take a look at again 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, like 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. 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 the percentage of the English content on the internet. 25.9 is 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 leads 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. 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 mouse top and here says location from the IP address means that based on the IP address, for example, where I am 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 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 will 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. Usually 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 sub directories. In this case, we are going to add the localized, you know, slug after, you know, like something.com forward slash in there. So in this way, it's quite clear for the users for which languages they're taking a look at it. It's also easier for us to identify which language we deliver to the users. All right. So let's now talk about frameworks and the libraries. Why suddenly from out of nowhere? Well, it's actually a quite relevant topic because some of the frameworks and the libraries, they use the internationalization of frameworks. 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 code and remix, sorry, 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 code 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. All right. So let's take a look at the example from this case. And first of all, we are going to create a couple of configuration files. But to get started, we are going to create the 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 the top, the file called command.json file. I'm going to show you why I already know I can give a name to the command.json file in this case. But let's focus on for now how we can set the property name and the key values, which is actually like localized 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 level. So on the right hand side is the key value, which is the localized string value. I want to say hello in English to translate into Japanese. In this case, I have created another translated file for storing this Japanese translated file under the directory of JA. So the value will be hello in Japanese. So after creating these translated files, we are going to create IAT next 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. 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 a 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, the translated 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 a couple of the values. First of all, the supported language list, and also the fallback language list. And based on that, remember these configuration file we created, these languages list are the arrays, inside of the arrays, and the values inside are strings. 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. All right, 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. A couple of lines of the codes are quite similar, so I don't want to 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 i18next provider, coming from react i18next. Keep in your mind that in this way, we are going to install and use a couple of other i18next related packages. So make sure that this is actually coming from react i18next. So after I told you that I want you to pay attention to this api, here is the place where you can actually 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 i18next 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 and 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 why translation files should be loaded before the hydration. Well, let's probably start to imagine if translation files are not loaded before hydration happens. Imagine that hydration, let's say like, sorry, not imagine, but then when hydration happens, users already will be able to see the UI with all the styles. 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 say hello, I cannot say 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. So if I want to switch from English to Japanese to say hello, now I can see that because the translation files are already loaded. Moving on to a little bit more in depth, let's say a 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 called i18next provider that is coming from react i18next. 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 i18n provider, it actually includes this use memo react hook. So this react hook is letting you to cache 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 the default namespaces, mainly the translated 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, right? So if the values of these, you know, like let's say calculations are the same, then we are going to cache 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 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 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 the 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 this file called root.jsx and 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 and up above. So this loader function is not just a random function that we just defined. Instead, it is actually a backend 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 the 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, et cetera, 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. All right. 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. In any route of your remix app, you can import this use translation coming from react IAT test. 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? 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 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 won't 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 the slugs? No, I don't think so. I haven't yet translated the slugs. But I use the URL parameters. And here's another friend of mine on Twitter. Bart, thanks again for giving me the comments. He also considered about the same. So if he wants to split up the translation files for route, it is possible, but it's a little bit cumbersome. And of course, it's not an ideal to take care of all these, you know, 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. 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 i18next. 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 it's the most, I would say, 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 CMSs or the CMSs 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 stored in the dedicated folders. So there's no way that the translators will mix up localized pages and the 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 English blog posts relevant pages are being stored at the folder. And the local, let's say URL shouldn't include any slacks because this is the default language. 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 slack. And also the contents are being translated. On the right hand side, you can see 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, it also includes JA in the URL. And if I go back to the default home page, which is in the most root, it shouldn't include any, let's say, even EN of the language slack. And of course, the contents are being back to the English. So in this way, 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, render these kind of dynamic routes. But I picked up this Slack route because by using it, you will allow whoever wants to create the pages and even create nested dynamic routes from the content management system, they can do that without editing any source code. So once you implement this feature by using this Slack routes, then 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 this green highlighted line of the code. So remix already provides you a very useful parameter called params. And if you, let's say, log out, if you log this params with the square brackets and 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 the full path, full dynamic path of the pages that you are taking a look at it. So we are using this logic to render all these dynamic routes, including the nested routes. So here's a little summary of what we saw together in my talk. We know that more than a half of the users in this world access localized content. And so we cannot simply ignore, you know, like more than half of the users in this world, right? And also we took a look at together, like multiple ways of making internationalization. So I hope it was helpful for you to choose the best way for your cases, as well as we know that, you know, implementing internationalization does matter a lot to think about better performance, also the UI and UX. So consider performance and UI, UX as well with internationalization. There's a little kind of request from my side to you in the end. So please send me the feedback, you know, like on Twitter. I really would love to improve my talks in general. Any feedback would improve me. So you can mention my name, mention the username on Twitter and tweet about the feedback. All right, that would be all from my side. Thank you so much for watching my talk and I hope, yeah, you enjoyed it. Oh, hey Arisa. And the first thing we want to do, because I know you posed the question to all of our viewers and you wanted to know in general, how do you describe your internalization developer experience so far? 47% said so-so, 33% said I like it and 20% said I don't like it at all. And I think that is just applicable of some of the challenges that you may face, you know, when doing it. And I like how stuff is changing in real time. So it's kind of cool to see. So for majority of people, it's so-so. And I think it's something that is just challenging to do, especially for the people who are new to it, especially if someone has never done it before. And I think your talk was really helpful in kind of showing us some of the ways of going about to improve our developer experience when looking to translate content. And I know there's a lot of different people in the audience that may have any questions, but in general, let's say someone is a newbie and I know you've spoken in your talk a little bit, but why is localization important in your mind? I would say the importance of the localization could be something that you can imagine when you read the documentation of whatever kind of, let's say, technologies that you want to master it. Imagine that your first language is not English. And it means that, let's say, if you want to really fully understand some of the developers, because I know I'm from Japan and I know how the Japanese tech world would work, and they prefer to read in localized content to have better understanding, but it's not always being translated right away, depending on the numbers of the developers who can help and also the, I would say, the speed that they can also contribute to the content. So this kind of experience you get is exactly what your users would get. So I would say the importance of the internationalization is something difficult to imagine for you, how it's going to be for your users. The example I gave you would be one of the examples that you might already experience. Yeah, no, that's great. And the reason I wanted to ask that first is because just seeing that sometimes it is extra work as developers to add localization to our applications, but for users, it's just such a huge benefit. So whatever the experience might be, so you like it or not, I think at the end of the day, it is really helping your users to be able to navigate your website and have access to content based on their location and the language that they're more comfortable with. So with that being said, I want to ask you a question, actually, something we ask all of our speakers is what is your favorite remix feature? I would say, or in the beginning, I was about to say maybe Loader and Action, but I changed my mind in the last minute because I'm at the other conference in person in London and a really good friend of mine gave a talk about the optimistic UI in remix. And I've been using it quite some time, not that I'm super expert about, but I really like, yeah, enjoy using the optimistic UI because you can already give on the UI side that already has enough information while the data is taking time to digest and then update the data on the data layer side. But I mean, if you already have enough sources to display for the users, they don't need to see the spinners all the time. And I know how people or users really hate seeing the spinners. Sometimes I've seen really extreme cases, like my friends, like whenever they see a spinner in one second, they would immediately leave. Oh my gosh. Yeah. There are some extreme people out there, but I mean, they're everywhere. So yeah, it's amazing how remix solves that issue. And so coming back to the talk you gave, I know you had a couple of different examples. How does the rest of the translation approaches work from one of your slides? There was a slide that showed four approaches to creating localization structure in a CMS, but it only shows one approach called folder level translations. Yeah. So that's something I also wanted to explain in a talk too, but I'm going to take the chance this time. So the rest of the four approaches would be maybe starting from one approach called field level translation. So in this way, instead of creating the folders to store all the localized content pages inside and there, you do not need to create the folders if you do not want to. And instead, what you can do to translate or localize content is that you're going to decide the field types, means like the headline in inside of the hero page or specific, precise field types, maybe like inputs or something like this to decide to translate. So there are the check boxes that you can take to make it to be translatable or not. So in this way, you can decide which components of the field types should be translated and localized and which kind of components and the field types should be kept from the default language. So if you want to take this approach, then field level translation is for you. And the other two approaches would be like combining with this field level translation approach I explained just right now, and also combining with the folder level translation that I introduced in my talk. So there will be like three in total. And the last approach would be that, well, from the content management system, precisely from the headless content management system, we have the terminology called spaces. So that's something that you can imagine as the repository or like per, let's say, I would say, like, for example, the web in this case. So you can dedicate one entire space for an environment for the default language first, and then another one entire space for another localized, let's say, like content per there. So that's something that we always explain. I always explain as like space level translation. So there will be four in total field level translation, folder level translation, two mix of it, and then space level translation. Awesome, awesome. And just a quick question here. Do you think it's better to use keys to identify translated messages or a full message, for example, greetings versus something like hello world? Ah, that's really good question. I would say to avoid the complexity, depending on which localized, let's say, keys you want to have, I would say, like, keep it as short as possible, because you might encounter some, you know, like mismatch from the testing or a couple of, you know, like not matching key values in the testing phase. And that's what I actually heard from one of my good friends out there. She posted on Twitter and responded to my question about that. Okay, that totally makes sense. And we are out of time. I had so many more questions, but Arisa, thank you so much for being here. Thank you for that amazing talk. And let's go back to Brittany. Thank you.
36 min
18 Nov, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic