Localized content helps you connect with your audience in their preferred language. It not only helps you grow your business but helps your audience understand your offerings better. In this workshop, you will get an introduction to localization and will learn how to implement localization to your Contentful-powered Remix website.
Table of contents:
- Introduction to Localization
- Introduction to Contentful
- Localization in Contentful
- Introduction to Remix
- Setting up a new Remix project
- Rendering content on the website
- Implementing Localization in Remix Website
- Recap
- Next Steps
This workshop has been presented at React Summit 2023, check out the latest edition of this React Conference.
FAQ
The recipe web app allows users to access different recipes, switch languages to view content in their preferred language, and get detailed information about each recipe.
The language switcher in the web app enables users to change the language of the content. When a different language is selected, the app dynamically updates the names, values, and content to match the selected language.
The recipe web app is built using Node.js, React, and Remix. It also utilizes the Contentful platform for content management and GraphQL API for data fetching.
Internationalization is the process of designing and developing a product so that it can be easily localized for targeted audiences from different regions and languages. In the recipe web app, internationalization is implemented to prepare the app for easy localization of content.
Localization is adapting your product or content to meet the language, cultural, and other specific needs of a target market. It differs from internationalization, which is more about preparing the product at a structural level to support such adaptations easily.
The Workshop covered various topics such as introduction to localization and Remix, project setup, creating components, implementing content and internationalization, differences between Remix and Next.js, configuring internationalization, updating titles and navigation, introduction to Contentful, content model and localization, and dynamic routing. Key points include the importance of localization for target regions, the capabilities of Remix as a framework, the process of setting up a Remix project and configuring internationalization, the use of Contentful for managing content, and the implementation of dynamic routing in Remix.
To give you all an idea of what we are going to build today, it's a very simple recipe web app. We're going to learn it from scratch. The prerequisites are Node.js, a good code editor, and a Contentful account. Basic knowledge of JavaScript and GraphQL is also important. Let's move forward and talk about localization. Localization is making your application available in different languages and ensuring it looks and feels appropriate for the target region. McDonald's in India is an example of successful localization, focusing on vegetarian options. Localization is more than just translation.
To give you all an idea of what we are going to build today, it's a very simple recipe web app. As you can see, right now I have just three different recipes. These are some of my favorite recipes. and there is a language switcher over here. I can switch the language and you can see it changes the name. It updates the values over here as well as for the content as well. I can open up that recipe and get that information as well. If I change it to English, it again translates or gives me the content localized in English again. So this is something that we are going to build and we're going to learn it from scratch as well.
To give you a quick introduction about myself, my name is Harshal Agrawal, and I work as a developer advocate at Contentful, which is something that we are going to look into more in detail. I love to experiment with various different technologies. In my recent experiment, I have been playing with the WebHID API. If you're not aware about the WebHID API, it's a fantastic API that allows you to connect your HID devices to your web applications. And one fun project that I've been working is connecting my PS5 controller with a web-based game. And if you want to know more about me, you can just hit me up. you can go to that website where you can find all the social links that I have and read more about me and my work as well.
All right. Coming back to this, the prerequisites are like, you need to have Node.js installed on your machine. You can use platforms like Gitpod or something, but I am going to be running it locally on my machine. So if you run into errors on those cloud platforms, I might not be able to help you thoroughly with that. So I hope you are using by running it locally on your machine and you have node.js installed. We need a good code editor because it makes the life easy. If you want to use, what was it called? Text, I don't know. The default code editor that window provides. If you want to use notepad, it's on new. Go ahead and use that. As I mentioned, we're going to use Contentful. So having an account on Contentful is going to be super useful. So if you don't have an account on Contentful, you can quickly go ahead and create one. Just go to contentful.com and create an account. You don't need a credit card. You can get started without the need of entering your credit card over there. And we have a basic knowledge of JavaScript and basic knowledge of React is also important. We are going to not use a lot of React over here because we are using Remix. which is super wonderful. But we are gonna use JavaScript a lot. And a basic idea of GraphQL would also be super useful because we are gonna use the GraphQL API over here.
All right. What I'm gonna do is I'm gonna take a couple of minutes over here, see if there are any questions till now around any prerequisites or any requirements or any of those things that I just talked about. If there are no questions, can just give me a thumbs up so that i know you are all clear till now and then and that i can move forward all right okay super so let's move forward and talk about localization as i mentioned i want this to be more interactive so i'm curious to know like what localization means to you again feel free to unmute yourself and just talk about what it means to you. Or if you want, if you prefer writing it in the chat, go ahead and write it in the chat. But I'm curious to know like what is localization for you. And over here, there are no wrong answers. So don't worry about, you know, worry about giving an answer which might not be correct. so localization is making your application available in different languages true true that's absolutely correct all right uh i'm gonna build on top of that and i'm gonna say it's it's also making sure that it's not just the language, but also the way your application looks and feels in that particular region, right? Some of the wonderful examples about localization that I can think about is brands like McDonald's. India is a country where the majority of the population is vegetarian. so if you think about mcdonald's they give out like burgers right and burgers mostly contain have those patties which is not vegetarian friendly so to make to be successful in india mcdonald's adopted uh adopted their strategy and uh and came out with options which focused heavily on vegetarian so all their patties were focused mainly on vegetarians and this is how is like this is like one of an example of localization so again to talk like give you a proper definition of localization it's basically an adaption of product application or document content to meet the language again not just the language but the but the cultural and other requirements of a specific target market right and it's not just about translation it's more than that We got Emanuel. I hope I'm pronouncing your name right, joining in. Hey, thanks for joining in. We've just started the workshop and we talked about what is localization.
2. Introduction to Localization and Remix
Short description:
If you're not on discord, I would highly encourage you to join the discord server. We've covered the prerequisites for the workshop. We discussed the difference between localization and internationalization. Internationalization is the process of making your apps ready for localization. From a project implementation point of view, both localization and internationalization are different processes. We then introduced Remix as the framework we'll be using. Remix is not just a React framework, it's a compiler, a server-side HTTP handler, a server framework, and a browser framework. It compiles your code, generates both server-side and client-side code, and allows you to create APIs. Remix has built-in wrappers for existing frameworks like Express, making it easy to deploy on any platform. It is also a server framework, meaning you can build your whole API using Remix. It's like the view and controller in the MVC framework.
If you're not on discord, I would highly encourage you to join the discord server. I've already shared my GitHub repo that we are going to go through this workshop And just a quick recap of the prerequisites, you need to have Node.js, install a code editor, some basic knowledge of JavaScript, React, and GraphQL would be super useful for you in this workshop.
All right, so we talked about localization. The next part I often hear folks when I talk about localization is this term called internationalization. And folks get confused when they talk about localization or internationalization they often times use these words in exchange of each other which is not like really true because while localization is adapting your product to a re to a particular region internet internationalization is the process of getting over there if we look at the definition of internationalization it's design and development of a product application or document content that enables easy localization for a targeted audience again this audience can vary in culture in region and language as well right so to summarize this internationalization is the process of uh internationalization is the process of implementing or making your apps ready for localization. Does that make sense, and does that clear out the difference between localization and internationalization? I see one thumb going up. Two thumbs going up. Okay, I feel like there might be a question in here. No, okay. All right, that's perfect. I'm gonna wait for one more minute because Fez is joining us as well. I just wanna make sure that before we move to the next part, he can hear what we covered and then we can just move real quick.
Can I ask a question meanwhile? Sure. From implementation-wise, of course, definition is different for localization and internationalization. From a project implementation point of view, how different they are in implementation? From a project implementation point of view, the way this works is you would again a lot of times people use these words in exchange of each other which is not true you always if you have looked into localization earlier you might have come across libraries like i18n but if you think about it it's it does not stand for localization it stands for internationalization right because that's the process of implementing localization So whenever you are working on a project, you, uh, enable it to be localized, even though you may, or may not be needing that in the future. So you are basically adopting the principles which are defined by intent, uh, by, for internationalization because that makes it easy to implement localization. Now, uh, the way I like to think about this is, and I think you'll get more idea about this when we start working on the application is, if I come back to the example over here. Now, I know that a certain part of my text is going to change in the application, right? I have already started working on implementing that part and I know, for example, welcome over here is different in German. The way I created this is keeping in mind that this particular word is going to change, but this whole particular message is going to change. Because I kind of already knew what things I want to do. Does that give you an idea? Does that answer your question? Yes. Yeah, it does. So can we say that internationalization is a subset of localization? I would not put it that way because for me, like, both are two different processes. because the way I think about it is like internationalization allows you to make your product ready for localization, right? Now, you might want to then localize that product for just one region, but you still have to go through the internationalization process and make sure that your product is ready to do that. Later on, you may decide you want to extend it to a new region, right? because you have implemented internationalization already, all you have to do is prepare that localized content, make that content available in that localized language and then simply publish that. So for me, it's kind of two different steps, two different processes, and it's not a subset of one another. Okay, makes sense. Thanks. All right. Faz, I think you just joined in. So we talked about localization. We talked about internationalization. And we are moving to the next part of the workshop. And I see you already are on the Discord servers. You already have the GitHub wrapper, which is fantastic. So moving forward, we are going to talk a bit about Remix because that's the framework that we are going to use today. now how many of you have write out image before or have heard about image before can i get a thumbs up or again feel free to unmute yourselves just write in the chat okay all right so i can say majority of you have heard about uh image but uh one person hasn't that's completely fine we're gonna look into it uh when i started exploring remix when i started playing around with remix i always thought that it was uh another meta framework which is built on top of react but while going through the documentation while working with remix i learned that it's not just another framework a react framework it's more than that so what exactly is remix well remix if you go to the documentation they define remix as a compiler a server site http handler a server framework and a browser framework so it's not just a react framework anymore but it's more than that because when they talk about remix being a compiler it actually compiles your code and gives you client client-side and server-side code. It also generates an asset manifestation file, which gives you a dependency graph and allows the code to map to the particular piece of code correctly. So it basically just compiles your code and gives you both server-side and client-side code. And I love this part about Remix that it's a server-side HTTP handler, which means that you can simply just use Remix as a server. You can just create APIs using Remix. You don't need to then use like any other framework for that. So because you make use is web, the web fetch API under the hood, which makes it easy to deploy on like any kind of platform really like if you are using, It does not matter if you're using Node.js or Deno. Under the hood, it allows you to do that. And the way Remix allows you to do that is it has built up wrappers around the existing frameworks like Express. It already has those wrappers built on top of those frameworks so now you don't have to worry about learning Express or learning about how Cloudflare workers work or how do you manage servers on Vielhel or Netlify and stuff like that. And it is also a server framework, which means as I already mentioned, you can build your whole API just using Remix. And you don't need to also provide any client output or you don't need to have a front end for that. Remix can handle the whole thing. So, the way I like to think about Remix over here is the view and the controller in the MVC framework.
3. Introduction to Remix and Project Setup
Short description:
Remix allows you to use the model as both a view and a controller. It provides three main functions: action, loader, and default. Action handles post requests and form submissions, loader handles get requests and data fetching, and default renders the component. Remix is a browser framework that handles UI creation and hydration. Setting up a Remix project involves using the command npx create-remix latest and selecting JavaScript as the server. Tailwind can be added by setting the tailwind flag to true in the remix.config.js file and running npm install tailwind followed by npx tailwind init.
You're not worried about the model anymore. Remix gives you the possibility to use it as a view as well as a controller. So each and every route that you are gonna use in Remix can have its own view and can have its own controller as well. Now, this might feel a lot because right now that we are talking it but once we start looking into Remix, you will understand more about it.
But just to give you a quick idea, basically, there are like three different functions that you export in Remix. The first is an action function, which will handle any kind of post request or form submissions that you have in your application. The next is the loader function, which will handle any kind of GET request or any kind of data fetching that you want to do. Now using just these two functions, you can have API. But now if you want to give an output to the user, if you want to have a website for the user, all you have to do is you have to export a default function which would contain that component that you want to render. Then Remix as a browser framework. With the browser framework and it allows you to create a UI for the user, it also handles hydration, which makes it really easy. Really, Remix has a lot of concepts like optimistic UI, hydration, which it handles by itself, but I'm not going to go in-depth about all those things. Because most of the time, Remix handles it by itself, so you don't have to really worry about it. You can always go through the documentation for Remix and learn more about it.
Alright. Now that we talked about Remix, we're going to start with setting up a Remix project. Now it's time when I'm going to stop talking a lot and we're going to dive into the core part of the session. The first thing you want to do is you want to set up a new Remix project and to do that, we are going to use the command npx create-remix latest. will give you a nice walk through of how do you create a remix. I do have a video over here. As you can see, I'm just running the command npx create remix latest. And it will give, it will ask me the name of the project. And it will give me a couple of options over here. You can select if you already know which server you want to use. And for this workshop, we are going to use JavaScript, you're not going to be bothered with TypeScript. So make sure you select JavaScript, because I have not done any kind of type integrations in there. That makes sense. So when you run the function, sorry, when you run the command, npx create remix latest, make sure you select JavaScript and TypeScript, if you want, you can go ahead and select predefined preconfigured templates that they have, but that's not needed for this session.
All right. Once you have Remix project setup, give me a thumbs up and then we'll move to the next part. I'll just make sure you are in sort of the dependencies. So when it ask you to run npm install, say yes, and let it install the dependencies. I've already done this part over here. So I already have my app remix app up over here, configured one thing I've also done is I've added Tailwind. I kind of like Tailwind more because I don't know a lot of CSS. I mean, I don't like to write a lot of CSS or Tailwind helps you with that. So I've also installed Tailwind. If you want to know how to install Tailwind in your Remix app, I can quickly walk through that as well. and I see Mail has a Remix up and running. That's fantastic. I'm going to wait for the folks. Okay. While the folks are setting up that, setting up the Remix project, I'm going to quickly walk through how you can add Tailwind to the project. The first thing you want to do is in your remix.config.js file, flag. Set the tailwind flag to true. Next, you want to install tailwind so when the command NPM install tailwind, once that's installed, you want to run NPX tailwind init. That will basically create this tailwind configuration file. Now in that file you want to add the content. which is going to be this. It's basically telling Tailwind to look for the component of where the code lies.
4. Remix Project Structure and Tailwind Setup
Short description:
In the app directory, create a tailwind.css file and import the tailwind-based components and utilities. Import Tailwind in the root file and export a link function to add Tailwind to your Remix app. The project structure includes the app directory with routes, entry.client.js and entry.server files, and the root file for setting up the overall structure of your page. Use the link function to add style sheets and other search links. The meta function sets meta information like the title and SEO details. The outlet component contains the children elements of a route. The structure of a Remix project includes the tailwind.css file, build directory, node modules, public folder, and configuration files.
Once this is done, we are going to go in the app directory. We're going to create a tailwind.css file and we're going to import the tailwind-based components and utilities. Then in the root file we're going to import Tailwind. Then we're going to export a link function in which we're going to shut up the style sheet over here. That's how you can add Tailwind to your Remix app.
Now, moving forward, I'm going to quickly walk through the project structure over here. As you can see that there is an app directory. In that app directory, there are a couple of things. The first is the route. This is where you can define all your routes. It's similar in the way we work with Next.js. In Next.js, we have a directory called pages, while in Remix we have a directory called routes. Now, in this you can create a new file. If I call this let's just say example.jsx. The way this is going to work is it's going to have a domain slash example.jsx and then it can export a render whatever I'm doing over here. Say, export default function. And to run the application, let me close this server. You can do NPM run there. We'll start up the Remix server. Come back over here, we see we have now the default page, and if we go to example, it just a little example. So we have one route over here, the created.
Now, with Remix, You can do easily nesting of routes. You can do nesting of layouts and all these things as well. But there is entry.client.js. As you can see, we can delete this file. Remix takes care of this. It basically hydrates all the content on the client side over here. Then there is the entry.server file again. This is generated by Remix, and it handles the part on the server side. The next is the root. I would say this is an important file because using this, you can set up the overall structure of your page. In route over here, you can see that we imported Tailwind and we exported it as a link. If I go back to this page, open up the logs. If you see over here we exported the links and we now get the links over here. If you want to add a style sheets or if you want to add other search links. That's how you do it and in remix can basically add most of in here if you want. Similar to this that is, there is another function called meta function which allows you to set a meta information. So basically the title of your page. The SEO information, which is the open graph images, description and all those things in that meta function. And this is coming in again from Remix. We have the body, this is the outlet. Now the outlet basically contains all the children elements that this particular route has. Because this is a root, it has all the other routes that are present in here, become the children by default. But if you are creating a layout which might have another child in there, you can use the outlet component in there as well. Right? Then this was the tailwind.css file. There is a build directory, this is gonna be generated every time you start the remake server. the node modules, the public folder, then there are some configuration files over here, and that's basically the structure of a Remix project. Right. I am not sure if I missed out on folks. Yeah, I'm not sure if everyone has completed the setup, but I'm gonna move forward because we want to make sure that we complete or we cover most of the things that we want to do in this workshop.
5. Creating Navigation and Card Components
Short description:
We will update the title of the page, create a navigation component, and a card component to render the recipes. The GitHub repo contains all the steps we have covered. We need to create an F component in the F directory and add the nav bar. The nav bar has a language switcher for English and German. The navigation component is imported and added to the body. The next step is to create a card component that takes props for the title, image, ID, and description.
So now that we have the remix app up and running, the next thing we want to do is we want to start playing with this. So the first thing we will do is we will update the title of the page. We will create a navigation component. We'll also create a card component and we'll render the recipes using the card component. Now, again, all this is available on the GitHub repo. I'm just going to share that link over here as well. So in the GitHub repo, if you go to the branches and select the... This is weird. So anyways, if you go to the branches and select step one, you will see all the steps that we have covered and the steps that we need to take. So we need to create an F component. So for that, what I'm gonna do is I am gonna not shave this, close this in the F directory itself. I will create a new file which will lay under the components. And then over here I'm gonna put the nav bar now again, I'm not going to code it from scratch. But this is the simple nav bar that I have over here. Remix also has a link component, so I'm going to use that so that every time the user clicks on Remix recipes that is in the title, they get to the homepage of our website. Over here, this div, I'm going to let it be empty for now, because in here I want to add a language switcher so that if the user clicks on EN, the content is showed in English. If the user selects DE, the content is shown in German. So I have the navigation component nine my route. I'm going to import that. And I'm going to Add it inside the body so that it becomes accessible for all my child roots. So if I come over here, we can see that we have In here, if I click on it, it takes me to the homepage. The next step is to create a card component and now in this card component, we need to pass on props. So this props would be the title, the image, and ID, as well as some description for that particular recipe.
6. Creating Recipe Cards and Improving Appearance
Short description:
Let's start by creating a simple component where we pass data to another component. We'll include an image with a source and alt text, a title, and a description. In the index route, we'll have a main tag with two divs. One will display the title, and the other will contain the card component. We'll use dummy data to render the card component, mapping over an array of recipes. Finally, we'll update the code for the card, index, and root to improve the appearance.
Okay. Let's I'm going to try to create this one from scratch. So let's start with export. Now as you can see these are like simply components which I don't really I'm not using them or I'm not using this particular files to call or fetch any data from where they I'm just to pass on data to this particular component this can be a simple component where i don't need to have any kind of uh how do you say any kind of loader or action function here i want to link this to the slug that i will be passing so i'm gonna have to pass log over here
The next thing I want to do is have a dev within that dev there will be another dev. This dev will contain the image which will have a source. I'm gonna pass a cover image which will have the source and then and alt text as well. Call it description. Next, I want to have the title. So let's just have the title, which will be basically the name of that recipe, and then we want the description of the recipe. I'm not doing any styling over here for now. I'm just going to leave this as it is. And we are going to pass on the cover image, which would basically be an object.
Now, I have the car, I'm going to come to my index route. And I'm not interested in any of these. I'm going to remove them. I'm going to have a main tag. In that main tag, I want to have maybe two divs. This one is going to be... Welcome to remixed. Recipes right and over here, as I mentioned earlier, you can change the title. So right now, it says new remix app. Which is the title over here says new remix app. So I can go ahead and change this and call it. Remixed recipes. if I save this over here, we can see that the title over here changes as well. That's how you can work with the metatags and remits. And then another div and in here we'll have the card.
Now but for card we need to have some dummy data or some dummy information. Again I have prepared this information beforehand. So I'm going to use that. And this information is very similar to the information that is available that we are going to use, and we're going to fetch from contentful, so you don't have to worry about those data structure any any other things over here. All right? Now, we have an array which contains all this information. So what we're going to do is going to do recipe dot map. And over here, we are going to pass on a single recipe. and then we're going to render it out. Let's do the title. This is going to be cp.title What else do we need? We need a description. So let's do cp.description We had a slug, for slug over here I'm gonna use the ID. We're gonna do recipe.ht, and then the image we are calling it cover image, and in our dummy data, it's also called cover image. We're gonna simply pass on that whole object. And lastly, one important thing, because we are. Using the map function to create components, we need to add a key. Well, identify these components uniquely. The. Alright. This should hopefully work and we should get some things over here. You can see we have. The web page, which shows some dummy data. which does not look really good, but we got some data rendering on our website. So what I'm going to do is I'm going to update the code for the card and the index and probably the root to make it look pretty.
7. Implementing and Rendering Content
Short description:
You can start implementing the steps and rendering content on your web page. If you have any questions, feel free to ask. The code is available on the GitHub repo. Install and configure Tailwind. Let me know if you need any assistance.
In the meantime, you can go ahead and start and implement this, finish this implementation. Again, styling is something that is completely fine if it does not look that pretty. That's not what we are here for. We are here to learn about internationalization and localization. So that should be our main focus. So once you have completed these steps where you can render out some content on your web page, just give me a thumbs up or let me know in the chat so that we can move forward with the next steps.
Also, in the meantime, if any of you have any questions, feel free to unmute yourself, ask me in the chat, or you can also just post them in the chat. Let's do this. Now, again, we have not started with the internationalization part yet. All we are doing is trying to get a bit familiar with Remix and understanding how it works. Thank you. You see any questions popping up in the chat. And I don't think I've seen any thumbs going up. I'm going to assume you folks are working on this part and trying to implement it. Again, you can find the code on the GitHub repo. And if you wanted to look into this time, you can just get the code over there. I am using Tailwind, so you would have to install Tailwind and configure that. But apart from that, it should be quite straightforward to use. If you again have any questions, just let me know.
8. Differences between Remix and Next.js
Short description:
Remix is different from Next.js. It provides an easy way to work with caching and manage cookies. Remix handles form submissions and rendering output based on success or failure. It does heavy lifting, eliminating the need to worry about many things. Next.js is transforming into a full stack framework. A link with all the concepts about Remix has been shared in the chat.
What I'm going to do is I'm going to cheat a bit over here, and I'm going to just copy the stylings, basically the code, from the GitHub wrapper so that it looks a bit more pretty than this. This looks much better.
Bill seems to have completed, yeah go ahead. I don't have any background knowledge of Remix so maybe it's a not even a valid question but I just I was just wondering how it is different from next next year's like that's a good yeah that's back and thing yeah that's a good question uh so if you think about next year's like before next 13 I should say it was mainly it was mainly kind of a static site generator right it would always give you a static site uh but after like react suspension everything it is becoming more of a full stack framework. But Remix has a different approach than Next.js. So if... Right now, we haven't implemented the loader functions or the action functions, will not be implementing the action functions for this. But when we implement the loader function, you will see how Remix works with data fetching and how Remix fetches the data. The other thing that Remix also provides, there are a lot of things that Remix provides. One of those things is like an easy way to work with caching. It also has functions or methods to easily manage cookies. So, in a way, also, Remix does not generate or give you a static site, per se. What it does, it gives you a website which is not always a static site. You can obviously generate a static site out of Remix, but that's not how Remix is built. So that's another difference. And with Remix, you don't really need the React suspense because Remix handles everything via itself. So, let's just say you have, you're submitting a form, right. In an, in the current state of the static site generation or the frameworks that we have, what happens is when the form is submitted, you might show a loader to the user and then render out the output that you receive based on a success or a failure message, right. But Remix handles that itself, you just have a form submission. If it is successful, Remix with a well, so that success message or the logic out there, if it's not successful, Remix will render out the output in that way as well. So Remix does a lot of heavy lifting for you. You don't have to worry about a lot of things. Trying to find a link which contains all the concepts about Remix, because that is going to help you understand much better of how image differs from next . And also with next I feel like without like the serverless functions, you can not really create an API because it's again, it was again a front end first framework. Now it's again, transforming into more of a full stack framework which is a different thing. But that's what next was, right? So that's how it is changing over time. I've shared a link in the chat.
Okay, thank you. Pongs maybe you might have to select the particular rule. If you can see the pick your rule channel, I would suggest clicking on that React Summit emoji that they have. Yeah, you have to go to pick up your old channel before and select. Yeah, thank you. Thank you, Neil, for that. So yeah, if you do that, then probably you will get the option. Wait, what? You have to wait for 10 minutes. Okay. Let me share the link in the chat again. So there you that you don't have to wait for 10 minutes. Okay. You got it? Perfect. It's perfect. I'm gonna show it over here again, just in case. All right, so once we just completed the first step, which was updating the title of the page, creating a name of component, creating a card component, and then rendering out some mock recipes using a card component. We're going to wait for two minutes, and then we're going to move forward to the next step. While we do that in the meantime, if anyone wants to unmute themselves, Then, you know, just give a quick introduction about themselves. That would be wonderful. I would like to know where you are joining in from. What are you working on currently? And, yeah, joining in from France, that's fantastic. Where in France? Yeah, from where in France? I'm pretty sure it's beautiful because I've heard that cities in France are really really pretty and really beautiful so i'm pretty sure it's beautiful over there also for her it's it's quite hot in france right now i was talking to someone who lives in paris if i'm not wrong and they mentioned that france get really really really hot during summers No, no, it's not that hot actually. So, what's the temperature like today? It seems to be 15 degrees Celsius.
9. Internationalization Setup
Short description:
We're going to start with internationalization for this app. We'll add packages and do some configuration. Then, we'll create the i18n.js file for basic project configuration and the i18n.server.js file for server-side internationalization. Finally, we'll create the locales in the public folder.
That's a good temperature, yeah. But do you get a lot of sun? It's a little bit cloudy.
All right, okay, let's move forward to the next part, which is starting with the internationalization for this particular app. There are already libraries out there that make implementing internationalization a bit easier. They help with detecting the language and allow you to show content based on the user's selected language. For this, we're going to start by adding some packages and doing some configuration. We'll create files in the public folder that will contain the strings we want to localize.
Now, in the app directory, we need to create a new file called i18n.js. This file will contain the basic configuration for our project, such as the supported languages and the fallback language. We'll also create another file called i18n.server.js, which will handle the internationalization on the server side. We'll set up the backend and the load path for each supported language. Finally, we'll create the locales in the public folder, with each language having its own folder and a JSON file containing the translations.
10. Configuring Internationalization
Short description:
In the app directory, create a new file called i18n.next.options.js. Set the supported language as English US and Germany. Specify the fallback language as English US. Create a new file called i18n.server.js to handle server-side internationalization. Set up the backend and create locale folders for each supported language. Add translation files for the titles in English and German. Update the entry.client.gxs, entry.server.gsx, and root.gsx files by adding the title and head title.
So, again... All right, no worries. Again, the code is already with you so you can, whenever you come back, you can just get the code from this repository and move on with the next steps.
Alright, so now we are in the app directory, and in there we need to create a new file. We're going to call it i18n and which is again short for internationalization. I 18 next options.js. Yes. Now this is the file which contains the basic configuration that we want to have for our project such as the supported language. So for this one, I am going to go with English US. than Germany, now you can also have, you might have also seen the locale code such as simply like EN, DE for German, FR for French, but when you think about localization and when you think about languages, right, the same language can be spoken with a different dialect in different parts of the world for example if you talk about English it's spoken differently in the US and in the UK same is for German like there is different dialects of German the Austrian German is a bit different the Swiss German is a bit different and then Germany is also different from those two so if you want to be more localized you can specify the locales this way or if you want to have a more general approach you can use simply ende fr or yeah these kind of of notations as well.
Now that is, now what if your application or the user has selected a language that your application does not support? So for that, we have a fallback language. And for me, it is gonna be English U.S. Now you can have a different fallback language depending on your use case. But for this workshop, we're going to go with English US. The next is the default name space. This is basically the name of the file that we're going to create in those local folders. Supported lang, fallback, default namespace. Now one thing over here because we are using Remix, We are not going to use suspense. So we're going to say use suspense is false. And this particular flag is optional. You don't really have to set this. But if you want to have, if you want to log out information into the console, when in development, you can always set up debug to true. So if it's not in production, we're going to say like, Hey, give me all the information and it should have the debug flag enabled. this I did next option file. Now again over here, we're just setting up the basic configurations for our project. The next thing you want to do is create a new file and we are going to call this i18n.server.js and this file will basically handle the i18 part on the server side or the internationalization part on the server side. This is vmix i18 next. We also want the options so we're going to import i18 next options will be importing backend from i18 fs backend and then resolve from node and we're gonna export a new instance of remix iot next and we will be passing on some configuration values over here the first is the detection how we want the detection to take take place or what other supported languages because we have already configured that in our iot next options we're going to just pass it on over here supported length the next is the fallback once this is done We are gonna have i18 next and over here we are gonna set up the backend so let's say load path and let's do path, now we put it resource and this is gonna be coming in from Public Locals. Now, for each supported languages we're gonna create a new folder, and within that folder we're gonna have. A file which will contain the translation for us. ID next is configured and then we're gonna say backend is going to be backend. This is done. Let's go ahead and create the locales in the public folder. Folder. Locales. Locals and now over here because I have configured enus, I'm going to name this folder now enus. If you had just configured it as en, then you have to name it as simply en because if you see over here, the way this is referenced is coming in from the language channel, right? and we're gonna create a new folder as well for DE and then both of this will contain a command or JSON file which will contain our string so I'm gonna call this title and because it's English, we're gonna say welcome to Make it recipes and I am going to do the same for the DE locale but I am going to change the content in here. I am not a hundred percent sure if this is how it is said in German or written in German. I'm still learning German, but I am assuming this is the way to do it. So welcome to Remix Recipes is going to be the title in German. And Welcome to Remix Recipes is going to be the title in English. Right? And one other thing they can do is let's say had title or you can call it meta title if you want. In English, I want to call it remixed recipe. I don't really know what it or how do you call that in German? So I'm just going to. Just to make sure that we have We are updating this as well. I'm going to call it, de-remix. Going to come back to the slides and see, these are the steps that I have completed. Now, again, I'm going to take a short pause over here just to make sure that you folks followed what we have completed in now, and if you have any questions, And then I'm going to move forward with the next steps. Thank you. I'm curious, like, does any one of you also speak German? or like any other language apart from your native language? But male, I'm assuming because you live in France, you speak French and talking to you, was speaking in english so you speak in english as well so that's the other language you speak apart from native language so well okay uh but which of these is your native language is it french or is it english nice so you speak english then and that's your non-native language okay all right okay uh let's take a look at the next steps now so the next step uh we have to update the entry.client.gxs file the entry.server.gsx file the root.gsx file okay We have to add the title and the head title.
11. Configuring Entry, Server, and Root Files
Short description:
We've configured the entry.client.jsx file, copied the server file from GitHub, and updated the root file. The server file detects the locale from the user and renders the data. In the root file, we use the change language hook to detect language changes and load localized content. The loader function is used here.
We've already done that. And we have to run the values from command or Tition files. All right, so let's start with the entry dot client or JSX file. And then we'll move to the NT dot so order jsx and then the root okay. Let's close all these files for now. component, you don't need a route, I have anti.client.server, just quite delete everything over here I'm gonna start from scratch. so because uh we want to do hydration over here we're going to dehydrate the next is import remix browser and then the i18 file as well uh i did next from i t next and then import we're gonna need a provider so i 18 provider. And then you actually need to be an initialization. All right. Uh, we need something to detect the language should be gonna import the language did DACTA where is it you don't need the options I need the language detector that's interesting why Is this not showing me the language detector? I might have missed something. I don't know. Let's see. backend from R18 http backend and then import get initial namespaces and R18 next Next. But the first thing we want to the React I it next object. So we basically first going to check if I didn't. Next is initialized or not. We're going to do the initialization and leave it's not initialized. Let's do I it in next dot. we are going to use not use when we use他們 use when we use seles function let's do the language detector let's do the language detector we're gonna use the important backhand and in the in it we can also configure the options gonna spread these coming in from I it in next options So this is going to be local, language, namespace, JSON. Get initial namespace. This will give the namespace that is configured over there and the detection. It's always best practice to pass on the language with the HTML tag. to be seeing that you want to do that. We're going to use this. For the language detection. And we don't want to cash. We don't want to catch anything, so that's an empty array. Once this whole thing is done. So hydration. I hit next provider and it will take I t and read the next. component. And that's the document. Right. So, we have configured the entry.client.jsx file. Again, you can find all of that code in that GitHub repo. The next step is the server file. Now for this, I'm just going to copy this from GitHub, from the directly pasted over here and walk through the code. So again, we are importing all the options or the configuration that we already have and importing the it in next packages that we installed. We're creating a new instance. Now, this is an important function because this function basically detects the locale from the user, and we're going to use this a couple of times in the code as well. and then we are passing on the namespace and this the way we initialized it over here we are doing it over here we are saying it to use init react 18 init react i18 next object use the backend and then we're configuring some some information in here And then simply rendering out that data over here. So that's what we're doing. It's kind of standard similar to how we did for the client, but this is now for the server. We did this one other file that we need to update was the root file. So let's see what we have to do in the root file over here. Now in the root file is where we going to use. We're going to start using those things that we have configured over here. First thing we want to import is going to be, then it's going to come in from the Remix, I18, and from the I18 server. next you want to do you change language and then the hook use translation so they use change language hook will detect that the user has selected the different language or the language has changed and then it will use this to pass on that information and based on that it will load the localized content. Now over here, we are going to use the loader function just one.
12. Implementing Language Detection and Dynamic Title
Short description:
We use the loader function to fetch data and process it on the server side. The library handles locale detection. We return the creation object containing the locale. We extract the locale using the useLoadedData hook. We configure the useChangedLanguageHook to be dynamic. We fix an error and ensure the data is still accessible. We want to change the page title dynamically. We implement the loader function to detect the language and update the title.
So as I mentioned, we use the loader function to get or to fetch any kind of data that we have or also to do any kind of uh process processing on the server side before we rendered out the page export async function called loader and in here i'm going to do locale i.getlocal and this is going to take the request so now if you see this library itself handles the detection part and it is going to detect the locale and then use that so now let's go to the test library the next thing over here is oh no let's just return Yeah, let's just return the information. So I'm going to do remix JSON. This should be. Coming in from remix. Uh, no. were going to import it manually and now in here we are just going to return the creation object which contains the local. So lets go ahead and consume this and in here we gonna extract local using the hook use loadeddata and we are also gonna do write in an which is equal to use consolation so And lastly, we're gonna have the useChangedLanguageHook, which is going to take a local. So, I've configured the hooks that we want to use now. This right now is static, but it can change, it needs to be dynamic, so we're going to update this and we're going to use this coming in from itin. and resolve language. And that's it. That's all. Hmm, that's all I think we had to do over here. I'm gonna save this. I'm just gonna run the server. Uh, we're gonna not. We will not see any differential in here right now. But I just want to make sure that we haven't broke anything right now. All right, it looks like we've broken something. F s l e? okay. uh oh here? rebuilding thermal?�ー Refresh local is not defined. Right OK, Uh, this should be local and not loader. Right. Right. So as you can see, I have still loads still got data, I can still go to the example route and see some information over here. That is okay. But we want the title of the page, and also the title in here to change. But we haven't configure that yet so for that we'll have to go to our index file and this is where we will be using so we'll start using or we start basically implementing these things right before i do that is there any question for what we cover till now because you covered a lot of things. Alright, I see a thumbs up, so I'm going to zoom everything is kind of clear. So, alright, let's jump into it. So, now, again, what we are going to do is we are going to have the loader function over here. which is going to be again an async function. We are going to pass on the request parameter and one thing we want to do is detect the language. Let's so what I basically want to do is dynamically change the title of the page. So let's do, let's get on the function and we'll call it t equals a wait. And we have to import vmix. And in I hope you have a good time Bhaansh and feel free to hit me up anytime. If you have any questions, you can always post the questions on the thread in there. feel free to hit me up on Twitter or anywhere you want to. Thank you for joining in. Get fixed tree and over here we're going to pass the request and the namespace is going to be common. And so what this basically allows us to do is it will allow us to get the function which will help us grab the value from the files that we just created. so I'm going to do c to const I'm going to call it page title so it's not confusing and then t and then let's call it head title which is kind of uniform head title and then we're going to do request and Jason. I'm still not sure why. It does not show the correct import. Nevermind. Let's import Jason. From. Linux Node is written. Alright, so So now what you want to do is you want to dynamically update this.
13. Updating Title Based on Language
Short description:
We're going to pass on data and update the title based on the language. This is useful for localization and SEO purposes.
We're going to pass on data. And. In here we're going to say it's going to be data. Right title. So if I save this now and if you come back to the page. nothing really changed just because the title was configured in this way but if we switch on the language let's try this i've been tried this see this what happens we do p-e-d-e come over here you can see that the title i don't know how do i zoom this but the title of the page basically changes well i can show it to you in the console and inspector as well so over here now we see it's de remixes if hd this is how you can also change uh the values that you want to update in the meta tags as well so this works really well with this works really well if you want to do SEO you want to localize your SEO part as well.
14. Updating Title and Detecting Locale
Short description:
To update the title on the page, use the translation hook and pass the key 'title'. Use the ready flag to ensure the content is displayed when available. If it's not ready, return 'loading'. Check for any errors in the code and restart the server if needed. Make sure the lang tag and lang attribute are present in the HTML tag. Detect the locale in the root file using the resolved language. If there are any issues, refer to the code in the repository. The issue with the title not changing was due to a coding mistake. Now it works correctly.
Alright. So this, that's how you can do the title but we also want this to occur on our Page as well, which is basically. Updating the title. To be kind of get the function over here. We have one I have ready and we're gonna also have I18n then we're going to use to use translation hook. No. Over here we're going to use the function. And we'll be passing on the key and the key was title. I'm going to save that. Now you can use the ready flag to make sure that the content is displayed when it is available, so you can do if it's not ready. We should return. May be loading. Right if we come back to a page say it's loading. K but it should stop loading. It should give us information. It's not that's weird. but we seem to have run into some kind of an error over here that's interesting public locals are we calling it title calling a title right One thing to do is start the server. did I have messed something up I think that's in the entry that client loadpass. I think that's the wrong one. Yes. Which is ES at the end of locals. Right. Thank you for that. And yes. Locals, but okay. So I'm just going to restart this server very quick. still does not load uh but it was loading the title so things work implemented correctly it's just something i'm not doing it right first, if I move to Language, the title changes over here. yes there is no lang tag in lang attribute in your html tag that's correct why that's not there we come to root we have and resolved language I think I have missed one step somewhere which is kind of which is giving me this issue right I'm gonna by doing this const I'm going to detect the locale over here as well ntremix.cat.ocl, pass on the request that's not going to change anything yeah it's still going to make a difference All right, what I'm going to do is I'm going to get the code from the repo. Don't want to waste a lot of time with the debugging. So let's quickly dlatego that. It's been here. And just for the sake of it, I'm also going to copy the entry code. I know where this is coming from. it's because uh the card prop is uh image and not cover image uh in the index gs which the part that you made did not cover it All right, so this is working. But now the interesting part is. we got the title in English. This is an English, as well. We do language. Does not do that. It goes to the default language. Hmm. But this does not change for some reason. Ok, thanks! that we talked so after making change that did Jason goes from Jason. Okay. So the issue was I did not code it correctly over here. That's why you don't do live coding. Yeah, you don't put this one. Yeah. So it was basically like with a small E, not a capitalized E. And yes, now we know.
15. Updating Navigation and Implementing Cookies
Short description:
So this is working. We got the data coming in and it's getting rendered based on the language. We updated the navigation component to add a language switcher. We implemented cookies to handle language detection. Now, let's take a break and address any questions.
All right. So this is working. I don't want this. Okay. So this is working. We got the data coming in. It's getting rendered based on the language. Things look good. So let's move forward with the next step, which is updating the navigation component to add a language for Joe.
All right. So I already kind of gave a very kind of gave out the idea of how language which works. So if you see over here, we have the parameter Can I zoom in over here? I don't think so but I basically have a parameter lng equals and then the language parameter. If I remove that, it goes back to the default word. But if I add that again, tete, it's gonna grab that localized content and render it over here so in my nav component what i have to do is basically tell my component to or basically add that particular parameter at the end of the url so let's do that we already have the link component rendered in here Right. So we'll have, you can a bad way to do this is to do like hard code is so for each locale you would basically hardcore the link component or what you can do is you can have languages array. They can also do, get this information coming in from IIT Next and create this particular object. But for the simplicity, I'm going to use, keep it in here. The value we want to pass is going to be enus. We're going to do the same. for Germans, it's going to be de te de. And then just going to do languages dot map. And And over here we will be returning the link component which will now add the parameter. So, I have to do the key, which is gonna be language dot value and in here we're gonna have language dot name. And let me say anything else I need to do, maybe not. It's going to save this, come back over here. Now, if I click on en, we get the localized content in english if i click on de we get the localized content in german what i'm going to do is i'm just going to make it look again a bit more pretty for this i'm again going to use the use transition use translation hook class name FI18N.resolve language equals the current language then we want to have class underline class in here and then we want to do padding to to the right, maybe of two. Just to make this look pretty, we now have a language switcher which has this indicator as well. So we also implemented the language switcher. The next thing is implement cookies. Now, this is an optional part. But oftentimes, the way this library works is, it checks first for the cookie. If the I18n cookie is present or not, if not it checks for the language parameter, which we just implemented. If it also would check for these, if you have stored this information in this session. It will check the except language header and if it cannot find the information of that language in any of these places, it is gonna simply give out the content based on the fallback language that you have configured. So we just wanna go ahead and implement cookie because uh it's not too much of a work when it comes to remix when you walk with cookies. So we're gonna look into it right now to implement cookie. All we have to do is create a new file. We're gonna call it cookie.js. And in here we're gonna use the function create. a cookie pet and then export const iodine and cookie. Yes. we gonna call this line for so long. It was... Where was the detection parameter? Yeah, so over here? We want to say like, hey, you can also detect this. Using the cookie that we just created. I 18 and Cookie. and we will be importing that as well. okay done and in here where we are sending out the locale and the title we can also set the cookie just do that, headers and then like cookie await areHating and cookie dot to realize look at Why am I am I getting this error here now? changed Now if we check for the cookies we have the cookie over here if i change it to de the value changes change it over again the value changes as well we also implemented cookie where my slides jumping because it's a time for a break all right uh but before we jump into the So, Greg, I want to check in with you and see if you have any questions. Till now.
16. Introduction to Contentful
Short description:
Let's get started with Contentful, a composable content platform with an API-first architecture. Contentful allows you to orchestrate content from multiple platforms and publish it on any digital channel. It offers five APIs: Content Management API for read and write access, Asset API for interacting with assets, Content Delivery API for fetching and rendering content, Preview API for viewing draft content, and GraphQL API for fetching content in both draft and published states. You can create an account on Contentful.com and explore the various options like organizations, spaces, and environments. In the content model, you can define the structure for your content, including recipe content types and related fields. To import an existing content model, use the command 'npx contentful cli if then space import' followed by the content file. If you have cloned the GitHub repository, you can find the content file in the Contentful folder. Make sure to provide the space ID when prompted. You can obtain the space ID from the URL or the API keys section in the settings.
Till now. So, let's get started in the next part of this workshop, which is about Contentful. So, have you heard about Contentful or have you used Contentful before? No, never. All right. So, this is perfect because you'll get to learn it from scratch. So to give you a quick idea about Contentful, it is a composable content platform with an APR-first architecture. So with Contentful, you can basically orchestrate content from multiple platforms and publish it on any digital channel, right? So Contentful, earlier, when we talked about Contentful, we talked a lot about headless content management system. But now we are evolving as a platform and we are becoming more of a composable content platform. So you'll get more ideas of Contentful and how it works in the next couple of slides where I'm going to walk you through the Web App I'm going to take you through the, what do you call it, through the content models and everything over there. But to give you a quick idea with Contentful we give you like five different APIs. There is the Content Management API, which allows you to, which gives you like both read and write access. So, you can, you know, delete content. You can create content outside of the web app that we have. Then there is the Asset API, which allows you to interact with the assets that you have stored in Contentful. The next is the Content Delivery API. This is the API that you would want to use to fetch content from Contentful and render it on any digital channel. The fourth one is the Preview API. So, often times, when you're working with content that might be some content that might be published, but some might be in a draft state. Right? So, to view this content which is in the draft state, you would use something like the Preview API, because the preview API would give you the content both in the draft and the published state. And then the last is the GraphQL API. The GraphQL API can give you content both the draft and the published state and it basically is a GraphQL API that you can use to fetch content and get the content now apart from the content management api all the other four apis are just read only apis you cannot manipulate the content uh or the information that you have in contentful using this for apis all right that was uh not so short introduction about contentful and the apis are on contentful so let me open up uh So I am going to assume that you have created an account on Contentful. But if not, go ahead to Contentful.com and sign up for an account. Once you create an account, you will see a couple of things over there. And I'm going to try to walk you through those things and help you understand what these things are. So in Contentful, you can be a part of multiple organizations. So over here, let me zoom in. So over here, I have my personal organization where I do a lot of experiments and I call it Herschel. And in this organization, I have multiple spaces. So each space is for one particular project. Similar to this organization, I have a couple of other organizations and these organizations contain spaces. Now, these spaces can also have something that is called environments. So, over here I have a cookbook space, which I have created for this particular workshop. And that space also has two different environments. master environment and then the workshop environment. So, if I'm trying out something, let's just say I want to make some changes to the content model that I have. So, instead of just directly doing that on the master, I can create a new environment, make those changes, test those changes and if I am happy I can merge those changes to the master environment. so that's a quick overview of the environment now once you log into your content full space and you create a your contentful account and create a space you get a couple of options this is the default home screen and then there is the content model and this is where you will be defining all the you'll basically defining the structure for your content now for our application it's a recipe app so we need a recipe content time so this content type over here contains a title which is of short text it also has a short description then to make it look pretty i want to add an image so I created an asset content type, which has a cover image and then I'm creating a rich text type of a content field, which is called ingredients and then there is another rich text field, which is called instructions. Then there is another content type called ingredient and this basically contains all the ingredients that I have throughout my recipes. it contains the name and it has a quantity type right so let's go ahead and try to you know create and work on this uh content models and try to create a content time let's go back to the slide so what we want to do is the first thing is import an existing content model so i've already created a content model which contains right now only the recipe content type which is also a bit incomplete so in the next step once we are done with importing we are going to move forward and we are going to start adding more content types to this to import a content model you can run the command npx contentful cli if then space import and then specify the content file Now, if you have cloned the GitHub repository that I shared earlier and if you go to tab 5, I believe that is a contentful folder in there which contains a JSON file called workshop.json. I'm just going to quickly see if it's step 5. yes that's hmm alright it's still on step 4 you can still find that on step 4 and on step 5 so go ahead if you ever include close the repository run this command find The slides are not working well today. Yeah. And it also will ask you to pass on a space ID. Let me come back to contentful and show you how you can get that space. Like there are two different ways. The first is to get it from the URL. If you go to the URL, it is after hyphen spaces. The characters is your space ID or what you can do is you can go to settings. API keys. Click on add API key.
17. Contentful API Keys and Content Model
Short description:
This part covers the process of generating API keys and the space ID in Contentful. It also explains the importance of authentication for importing JSON files. After importing, the content model will include the recipe content type. Additionally, the import will include media and content. The next step is to create a content model for ingredients, including the fields 'name' and 'quantity type'. The 'quantity type' field will provide a list of options for the user to select from. Finally, a new field called 'ingredients' will be added as a rich text field to showcase the required ingredients for a recipe.
This will generate the API keys which you will need later on and then it will also give you the space ID. You can copy the space ID and then you will have to pass it in the command. One thing it will also ask you to do is do some sort of authentication. So make sure to do that authentication because without the authentication you would not be able to import this JSON file.
So once you have imported this, just let me know, and then we can start looking at the incomplete content model and start adding or updating the content model. So when you import it, this is the content model you will get. It will just have the recipe content type and it will not have ingredient field over here. I will just not have the ingredient content as well. Okay. It's important. The other thing this is going to do for you is it is going to import the content as well. so we don't waste a lot of time creating that content for from scratch for this workshop so you will already find some media and some content over here once the import is complete okay it's important right so if you if you check the content model tab or if you check the content tab you will be able to see the content and the content model yeah that's it perfect now there are two things we need to do we need to create a content model for ingredients and then we want to refer this in our recipe content time so for that let's go ahead and create an ingredient content type and it should have two fields. The first is the name and the quantity type. So we click on add content type. I'm going to call it ingredient create. I'm going to add a field which is going to be the name. All right, I can click on Add and configure and over here I can specify different settings for this particular field this field will represent the title of the entry and this is a required field. You can also set up other configuration values, but for this particular field this looks good enough. So I'm going to go ahead and click on confirm. All right. I'm going to go back to the slide. All right. So this is what it is. And then that is the quantity type, but with quantity type, what we want to do is we want to give the user a list of options to select from the quantity type. So an ingredient might come in a can. It might come in, uh, I don't know packages. So you want to specify like how many uh what kind of how much quantity is required for that particular ingredient so we are going to configure that as well so again in contentful i'm going to click on add field click on text let's call this quantity type uh all right this is going to be short text we don't want it to be a list I'm going to click on accept and configure. And now under validation, what I want to do is say like, hey, you can only select some specific values. So I'm going to add a few over here. People spoon maybe grams, milliliters. If you're baking a cup, we're baking a cake. You might use Cup as the value. But for now this looks good. I can change the appearance from single line to drop down. That's what I'm going to select. And then I'm gonna click on conform. Now I can save this. And over here if I go back to my content model, I have both the ingredient and the recipe content type. So this looks good. This works kind of well, but there is one more thing. It's not in the slides, never mind. But we want to. We have the ingredients, but we are not showcasing the ingredient with the recipe and without telling the the readers what the required ingredients are. Having a recipe does not make any sense. So we want to add a new field. this is gonna be a rich text field. You're gonna call the ingredients and in here. I'm I don't need all these formatings. I'm just gonna dislike them. I might need bullets or numbering.
18. Localization in Contentful
Short description:
We added new ingredients to our recipe content and saw how to reference them. Now let's discuss localization in Contentful. Contentful allows you to manage localized content for different regions and languages. Each space can have a set of locales identified by ISO codes. Localization is not just about language, but also about region. You can add new languages via the Contentful Web App or programmatically through the management API. Contentful also supports custom locales for targeting specific audiences. There are different ways to implement localization in Contentful.
So I'm going to leave that as it was. I'm I'm not going to use hyperlinks. But I want to have an in-line entries. I'm going to leave that as it is and I'm going to click on confirm. The other thing I want to do is. Let's see. In the embed inline entry validation. I want to accept only. The ingredient type so that. If someone is new and they don't have an onboarding for this, they at least know that they are not allowed to use any other content type, but they are just gonna get the option for ingredient. I'm gonna confirm this, save this, and now we have the ingredient as well. So, let's go ahead and add an entry for ingredient. I'm going to call this noodles. Let's just say it's quantity is going to be cup. If you see over here, now that we have a new entry, by default, the state of that particular entry or state of that content is going to be the draft date. If you are happy and if you want this to be used in production, you will have to publish that content, and now you will be getting this content in the delivery API or the GraphQL API, and you don't have to necessarily tell Contentful to pass on the contents which are in drafted. We've got noodles. Let's do two more. I'm going to check garlic, which is going to be crumbs. I'm going to publish it, create new ingredient, onions, it's also gonna be grams, and I'm gonna publish it. All right, now I have the ingredients, let's do any... I can see onion, garlic, and noodles, and then the recipes over here. Go to her camera. All right, I have gonna. Come to ingredients and in here I can say. 500 grams and then in line entry. I can select onions. i'm gonna do something similar uh let's see let's 200 grams and then two garlic and two cups of Now we have the ingredients we can publish the changes. So we got the ingredients in our recipe content. This looks okay. As we got the ingredients coming in, we created a new content type. We saw how to reference that content type.
Alright. Before we move forward, do you have any questions? No, it seems okay to me. Alright, perfect. So let's come back to the slides and talk about localization in Contentful. Because you can manage content in Contentful, you might have teams or you might want to have content which is localized for different regions, for different languages as well. So you can do that in Contentful. There are different ways to do localization in Contentful. But before that, let's talk about locales. So in every space, you can have a set of locales. And each locale, again, in those spaces can be identified by an ISO code. We already saw this, that there is ENUS, DEDE. for german for german is german you can simply do en or de or fr as well and i think i've been talking this throughout the workshop like localization is not just about the language but it's about the region as well so it's important to note that local does not just mean the language. You can create or add new languages via the Contentful Web App, which I'm going to show you. Or you can, if you want to do it programmatically, you can do that via the management API as well. For some reason, you want to create a custom locale. You can do that within Contentful as well. You can create a custom locale to target to a specific audience of your choice. Now that we've talked a bit about local, let's talk about the types of localization that is possible within Contentful.
19. Localization Methods and Governance
Short description:
Field level localization allows you to localize each individual field. Entry level localization involves creating a new entry for each locale. Content type level localization is best for strict separation of content. Field level and entry level localization allow asynchronous publishing. Field level and entry level localization have fallback options, while content type level does not. You can restrict access to specific content using content type level localization. Field level localization does not support asynchronous publishing.
We looked at the fields. With fields, you can have a field-level localization, you can have an entry-level localization as well, and then a content type or a space-level localization. I'm going to walk you through all three of these and also talk a bit more about what works well when,
The first thing is the field level localization. With this localization, you can basically localize each and every individual field. If I come back to my content model, over here in recipe, if I open the settings for the title, you will see I have said enable localization of this field. But if I show you description, I don't want it to be localized. So I don't have it enabled over here right you can select which field you want to localize and you can enable that in those settings. Now this is best in scenarios where you have Content types with fewer fields like we have right now over here and you want to content to be published synchronously. Now what does that mean? Let's just say I have I have enabled localization over here, right? and if I go to my content if I go to a carnage over here right now because this is not required field I can still publish it without having a German title, and also I have configured the fallback to be English. So if it does not find that, it's going to go to English. But that's generally not a best practice if you are using field-level localization, because in field-level localization you want to publish it synchronously, and that's when you want to do this.
Moving forward, is the entry level localization. now the way entry level localization it works is different than field level obviously and the way in which it works is that for each different locale you're going to create a new entry now in our example if i did not enable field level localization for the title field and i want to localize this whole thing and i would implement entry level localization, I would create a new content entry for this. And that entry is going to be in German itself. Right? So let's just let's just take an example over here with ingredients. For example, I want to have implement field level localization for ingredients. I already created one for onions. And I'm pretty sure it's not called we burn it's called something else and I'm completely blanking out, but I can do this. I will now have to create similar entry. Just for German over here. Now this works well because now what happens is you can do asynchronous publication. Even though I don't have an entry which is in German, I can still go ahead and publish that content out there.
The next is the content type level localization or the space level localization. The way this works is for each and every locale, will have a different content type. So if I want to have if you want to implement this I'll create a different content type for recipe which is just for the German language. I can do that the same thing for friends as well. Now this is best case where you have like a strict separation of content and you want to maybe have different fields based on the locale this is when this approach works really well and also this allows for asynchronous publishing just like entry level as well so that was a quick introduction about all the types of possible ways of implementing localization with contentful I'm going to talk a bit about the governance, because when you are working with content, you would have a team that would help you with this, and you would want to also look into the governance part of it. So with field-level localization, you have the governance, so you can basically restrict folks from creating or editing a specific part of the content. content. However, with entry level, because anyone can create a particular or a new entry, you cannot restrict that. But you can then do that with content type because with content type, you are specifying or you are giving permissions only to specific content to a particular user. You cannot do asynchronous publishing with field level localization, you can do it with the other two. And you can have like fallback for the field level and entry level. But you cannot have a fallback for the content type. You still can have a fallback, but then you'll have to hardcore that in your code. So you will have to have a couple of checks in your code to see if things are there or not. And if not, then you will have to get the content from your Fallback language. All right. I talked a lot, I caught a lot of information. So I'm gonna grab some water, but I'm gonna give you also a chance to ask any questions that you might have. No, okay. Seems clear to me. a lot of information, of course. Yeah. Yeah, this is a lot of information and really we're going to use this field level localization. But I mean, that's the most popular method I have seen across the community, the Contentful community. But based on your use case, you might want to try out different methods as well. We already have the documentation for all this. We have courses for all this where you can learn in-depth. I just gave an overview and I did not go in deep.
20. Adding Locale and Fetching Data
Short description:
To add a locale in Contentful, go to settings, Locales, and click on Add Locale. Select the desired locale and set a fallback. Enable the locale and allow empty fields if needed. You can have limitations on the number of locales based on your plan. Enable localization for specific fields and select the desired locales for display. You can also set up different images based on the locale. Import the final.json model to ensure all data is in Contentful. Use the GraphQL API to fetch data and create a function to handle API calls.
Moving forward, let's look at how we can add locale in Contentful. To add a locale in Contentful, you go to settings, you go to Locales and you click on Add Locale. now over here you can select whatever locale you want to add you can also as i mentioned have a custom locale i don't have french in there so i'm gonna do french i'm gonna see if there is french france that is world for this again i'm going to set a fallback and that is going to be english us i'm going to enable this locale in response enable editing of this local that's true. I I'm going to allow empty field for this local because I'm not going to set up this language for all the content that I have in there. I can click on share and now I have a new local in here. Now just an FYI based on the plan that you are. you might have limitations on the number of locales that you can have within Contentful uh I because you just signed up I am going to assume that you are going to be on the free tier which gives you I if I'm not wrong like two locales you can have two locales but if you want to have more than two you would have to uh upgrade your upgrade your plan All right, and the way to view this as if I go to. But I'm going to show you how to enable this again just for a quick recap. See if I go to recipe. I just want to also localize description. I'll click on settings I'm going to say enable localization of this field. I'm going to click on confirm. Click on save. Now, localization is available for this field as well. Click on Haka Noodles. By default, this is how you're going to see the entry. You can have just the title and the description. But if over here, if you go to Translation, click on Change, you can select which locales you want to display. So I can do French and German. Click on shave and now I have in French and in German as well. I don't know what haka is called in French but noodles are noodle? I have no clue how it is called in French. I think that the same as in English. Okay, but do you spell it the same way? Yes. Oh, interesting. Okay. no we just don't translate it got it okay interesting yeah and i can then publish the changes and now we have the title in english french and german all right you can also based on the locale you can also you know set up the images as well So if I again go back to content model, go to recipe do settings again do enable localization for this field. So based on each locale, you can have a different set of images going in the. Right? Let's go back to the slides.
We added some look as now what we want to do is when I start fetching the data from Contentful but before we do that let's go ahead and make sure that we have all the data in Contentful. So just like importing the workflow.json if you can just do the import for the final.json model so if you instead of workshop.json just do a final.json and run the same command it will import all the content with the ingredients and everything in there for you and we will look into then fetching the data Speaker 2 Okay. Already did that. Okay. Perfect. So now that you have the finished content model, we can start looking at fetching the data from content. So because we are using remix, we are going to, we are also going to use GraphQL API over here. We are not going to use the Delivery API or the SDK, but if you want, you can use the Delivery API or the SDK or the Preview API as well. The first thing we want to do is create a contentful.server.ch file in that we want to create a function that will handle the API calls. We'll have to fetch the recipes and then render all the recipes. So let's go ahead and do that. So in here inside the app, you're going to have. Utils Contentful. dot. Server dot JS. And in this I am going to. I'm also gonna add hmm, and in environment variable file, which I'm going to use to store my credentials. This is going to be contentful. Space ID. Contentful. content for token. We go to settings, let's go to the master branch.
21. Contentful API Keys and Fetching Recipes
Short description:
We go to settings and API keys, copying the space ID and using the preview API token. The API token is read-only, so even if exposed, data cannot be manipulated. The code uses the fetch API to get recipes from Contentful. Due to time constraints, the code is copied from GitHub. The loader function retrieves the recipes and passes them to the component for rendering. An error occurred, but it will be addressed in the next part on dynamic routing in Remix.
So we go to settings and now to API keys. I'm going to cookbook one, copy the space ID, paste it over here. Now, because I am using the GraphQL API, I'm going to use the preview API instead of the delivery API access token. Copy that. Paste it in here. Now if you buy a mistake, if you like, we often get this question like hey, what about our API tokens getting exposed? So I'm just going to reiterate this again. This is a read only API token, right? So even if it gets exposed, no one can manipulate your data or they can. We just fetch that data but cannot manipulate the data. You're still safe. You're content is not being. Or bad actors cannot manipulate your content. Let's get this ENV.contentful. I'm going to copy this because I every time misspell them. And then broken. So the way I did this even though I'm using GraphQL, I don't…I'm just using the fetch API instead of using a Graph QL specific API, because this is right now not a very complicated project. I don't want to implement any fancy things over here like caching and stuff. I'm going to simply do something like this. hurl we are gonna have options we will pass on the method and then the headers which is to contain type in the authorization. right and the body will have to be json.stringify query and variables return await fetch URL and the options This is a simple reusable function that we're going to use in the rest of our code. Now this function that I'm writing over here is going to get us all the recipes from Contentful. I just took a quick look at the time and we are running really behind on time. What I'm going to do is I'm going to copy the code from GitHub and paste it in here, and so that I can go through it and explain it to you what is happening here. For the GetRecipes, what we want to do is based on the local, we want to get that recipe information. I'm passing a local parameter and that goes into my recipe collection, And then I'm basically getting that information, making an API call and returning it over here. I'll save this and come to index.js over here. Instead of now using these over here, I can simply have a loader function which will grab me that recipe. I also need to import this. Let's go to that. vmx i 18 and from writing dot server this correct side all right i'm just going to again copy and paste the code just to of time. So basically having the loader function which is calling the get recipes function. Getting the recipes, passing it to my component and in the component is where I'm rendering it. When I call it. Cover image. We have to restart the server. All right, if we come back to our application, we ran into an error, that's fantastic. That's the last thing I wanted this time. Let's check real quick. click the contentful server.js same locale passing the locale the code in the repository works so I'm just going to skip on to the next part because we got just like 5 more minutes and I really want to make sure that we cover all the steps But if you again run into any kind of issues, just let me know. I'm happy to help you out with the next steps. So, assuming things are things were working fine. We had a page which would render out the recipes coming in from Contentful, but there was a problem. If we click on any of those recipes, it would go to a new route and it would give us a 404 because we did not set up dynamic routing. So in this particular step, we will be looking into dynamic routing, especially in Remix. So the way dynamic routing works in Remix is by using the dollar sign. You can have a dollar sign, I can call it id, and then I have .gsx, GSX. And then I can do something like export. Default function. Just gonna do a quick return. just do now. One, We see hello we're here.
22. Dynamic Routing and Localization Recap
Short description:
We implemented dynamic routing to fetch data from Contentful based on the ID passed by the user. Rich text is rendered using the library 'rich text react renderer'. The language switcher in the navigation bar was causing issues, but we resolved it using the 'uselocation' hook. Internationalization and localization were implemented in our Remix project. Localization in Contentful can be achieved in three ways. We didn't cover route-based localization or deployment, but you can implement them. If you have any questions or need assistance, feel free to reach out. Thank you for joining the workshop!
If I do two, we again see hello. So we basically implemented dynamic routing for now, but what we want to do is when we pass on the ID, we We want to get the information from the user. We want to get the ID from the user and then we want to pass it on to Contentful, fetch that data from Contentful and then render it over here. So over here, we have another function that is called GetSingleRecipes. And now in this function, with the local, we are also passing on the ID. This ID represents the UniqueContent that we want to fetch. So, we are passing on that and we are getting all these information to be getting the ingredients and instruction along with the description and the title and the cover image. Now, if you remember ingredients and instruction is rich text it is of the type rich text. So to implement rich text you need to import and use another library called rich text react renderer. Again, you can find all the code in there, but basically that file handles how rich text is rendered. For React, we have similar libraries. If you just want to do rich text to HTML, you can do that. Rich text to Markdown or any other content side you want to display the rich text, you can do that over there. So that's how, which text works in Contentful and that's how dynamic routing works in Next.JS. Sorry, in remix. But one thing, once you implement this, there is going to be one small issue that you might face and that is gonna be around the language switcher. Because if we go back to the code in our navigation bar, over here. What we are doing is we are telling it to like go back to the home route, but add the language parameter. So if we add on a language. If you are on a recipe page, right, aka noodles. If we click on de, for example, it's going to go back to the home route. It's going to source the localized content on the home route. But it's going to show it. it is going to show the homepage and not the harkahnoodles page. So for that, what we want to do is we want to use a hook again that is coming out of Remix and that is the uselocation hook. And from this hook, we can extract out the path name and then we are going to pass on the path name. So no matter which path we are on, we will be now switching the languages on on that particular path itself. So let's see if I can walk you through this and this particular repo. So route gComponents, name over here. Over here, as I mentioned, use location. We go passing the path name and then repass the path name over here. then we are finding the language parameter. And similarly in the IT file the route file we're getting the locale we are also getting the recipe you're passing the locale and the ID which we get from the parameters and then we render it out this is the render options for rich text renderer, and then we render out the information that we get. All right.
A quick recap. We are all like done, but I'm going to go through a quick recap. Internationalization is not same as localization. What we did today, we implemented internationalization for our Remix project. so that we can deliver localized content to our users. Remix handles the view and the controller part of the MVC framework, and localization in Contentful can be achieved in three different ways. There is still a couple of things that, as you can see, I could not cover and I had to rush a bit in this last segment of the workshop. But one of the things you might have observed when you have been using websites is that the localization is a route in those websites. If you go to Spotify, maybe there's spotify.com slash de for German, it might be for French, it could be slash fr for French. That's more of a route-based localization. The libraries out there don't support it, so that has to be hard-coded by us right now. To keep the interest in time, I skipped that part, but you can obviously go ahead and implement that structure for your application. The next is, we did not deploy it on the web, but if you want, you can go ahead and deploy it on the web. Because it's Remix, you can deploy it on AWS, you can deploy it on Vercel, Netlify, wherever you want. Now, before I say goodbye, I know I rushed through the last part of this session, so I want to take a couple of minutes to see if you have any questions or anything that you want to share. Everything was clear to me, so thank you very much for this workshop. Awesome. Thank you so much for joining in. And you already have the link to the GitHub repo. You can go through the code if there is any question, if you get stuck somewhere, feel free to hit me up anytime. I'm going to be monitoring that thread at least for the next couple of days so that if folks have any questions, I can help them out. And thank you once again for joining in and sticking around.
Comments