Prismic Toolbar playground

Preview off · Toolbar https://static.cdn.prismic.io/prismic.js?repo=prismic-main

A Guide to Next.js Internationalization (i18n) with the App Router

blog · nextjs-i18n

callout info

title

This tutorial uses Slice Machine

content

Slice Machine has been replaced by the Prismic CLI and Type Builder, and is no longer maintained. The slice concepts in this tutorial still apply, but for current setup steps, see the Next.js docs.

Still on Slice Machine? Migrate to the Type Builder.

hidden_content
[]

text_content default

content

When building websites, it’s only natural to initially create it in our native language or the one of our target audience. But, over time, we might find ourselves looking to expand our reach to other target audiences or just wanting others to use and enjoy our website. But what happens if those audiences and users don’t speak the same language we developed the website in?

That’s where internationalization (also known as i18n) comes in. Internationalization is the process of designing and building a website so that it can be adapted and used for various languages, regions, and cultures. With roughly 7,000 languages spoken worldwide, internationalization uniquely enables websites to adapt their content for diverse global audiences, making them more accessible and beneficial for users across cultures. So, in this tutorial, we’re going to take a look at implementing internationalization into a Next.js App Router project.

text_content default

content

Internationalization and Next.js App Router

Before we start, it’s worth mentioning that the Next.js App Router supports i18n straight out of the box. We can set it up by using a custom middleware.ts file to redirect users to the correct nested routes for the locale they’re using in their browser.

So, for example, US users would be redirected to /en-US/route, and French users would be redirected to /fr-FR/route. Each of these pages would host the same content but displayed in their respective language.

In Next.js, content for each language can be stored in JSON files, often called ‘dictionaries.’ These files would contain all of the translated text we’d like to display for each page. This would look something like the ones below.

code_snippet default

code_snippet default

text_content default

content

We could then import the correct dictionary based on the route the user has been redirected to by the middleware.ts file and then display the correct text on the screen.

However, while this method of internationalization is pretty simple to implement, it does have some issues with it, most notably scalability. For example, if you have entire pages of content to translate, storing all of the content in JSON files will quickly become messy, difficult to manage, and likely out of sync with each other. So, how could we make this more scalable?👇

text_content default

content

A scalable solution to internationalization (i18n)

Luckily for us, there is a solution to internationalization that is scalable and will make our lives easier. And that solution is using a headless CMS that supports internationalization. By using a headless CMS, we can use it to control all of the content on our website as well as the translated versions of it.

So, for the rest of this post, let’s look at implementing this solution with a Next.js app router project. We will set up a basic example blog that supports internationalization by having an English version and a French version. For managing our content, we’ll be using Prismic (and their new page builder 🤩), but the concepts and principles we cover throughout the tutorial should work with other technologies and setups, too.

text_content default

content

Setting up our Next.js project

For our project, we will be implementing i18n into a basic blog and adding functionality to be able to switch between locales.

We’re going to break this tutorial up into two parts:

  1. Getting our blog set up and running with the English version.
  2. Setting up internationalization and the French version of our blog.

So, let’s get started!

text_content default

content

Setting up Prismic

The first thing we will do is create a new project in Prismic. You can do this by following the below steps:

  1. Login or signup for a Prismic account.
  2. Create a new Prismic repository using the Next.js option.
  3. Choose 'Connect your own web app' and click Select.
  4. Give your repository a name, select the free plan, and click Create repository.

Once you’ve finished creating your project in Prismic you should see a screen like this.

An image of the welcome screen of the Page Builder.

For now, don’t click on anything. Let’s move on to setting up our Next.js project.

text_content default

content

Download our Next.js blog starter

To minimize setup steps and be able to focus on i18n, we will be using a custom starter for this project. To download the starter, run the following command in your terminal, making sure to switch out your repository name for <your repository name>.

code_snippet default

text_content default

content

Running this command will do several things, including:

  • Copy the custom blog starter and connect it with your Prismic repository
  • Install all core dependencies
  • Sync data with Prismic
  • Install Slice Machine

You’ll be asked if you want to run Slice Machine - type Y. We won’t modify any slices for our project since we utilized a starter, but you can access the UI at http://localhost:9999/ if you’re curious.

callout info

title

What is Slice Machine?

content

Slice Machine is Prismic’s developer tool that allows you to build slices, or reusable website sections, as components in your code. You can create, preview, and test slices locally without affecting your live environment. Then, you can ship the slices directly to marketers in a custom page builder so they can start using them as building blocks to create on-brand, customizable pages independently.

hidden_content
[]

text_content default

content

Now, if we go back to our Prismic repository and refresh our page, we will see our documents section. You can also navigate to this page directly by going to https://your-repository-name.prismic.io/documents/. So, if your repository name is i18-tutorial you can go to https://i18-tutorial.prismic.io/documents/.

An image of the Prismic page builder with our starter docuements.

You can see, as shown in the image above, that we already have four documents created for us in our starter.

The last step is to get the front end of our site up and running. To do that, follow these steps:

  1. In a new tab in the terminal, cd into your project.
  2. Run npm run dev.
  3. Open your project in your favorite IDE.

Your site should be running locally at http://localhost:3000/ and look something like this.

An image of the frontend of our site.

text_content default

content

Setting up internationalization in Prismic

Now, with our blog configured and usable in English, let’s look at creating the French version and setting up internationalization. We’ll also look at the routing, content, and components required.

Configuring Prismic

To start setting up internationalization, we must first enable the new language we want to add (French in this case) in Prismic. We can do this from our Prismic repository’s settings page under 'Translations & locales.' On this page, add the French locale. When you're done, it should look like this.

An image of our English and French locales in Prismic.

Now, if you navigate back to the Documents page in Prismic, you should see a new locale dropdown added to the top right of the page near 'Create new.' You can use this dropdown selector to switch between locales and create new pages for each one.

An image of our French locale in the documents section of page builder.

Switching between locales

Now, with our extra locale added, we need to create a French version of each document we have in English. To do this, first select English in the locales dropdown and open on document. Then, you should see the same locales dropdown as before on the right-hand side of the page. Use the dropdown to change to your other locale, this will create a version of your document in this new locale.

A GIF showing how to change locales in Prismic.

You will notice that by switching locales it creates a blank document. Give it a UID an replicate the slices and content, only this time in French!

Do this, and translate every of your English documents.

callout info

title

Setup live previews

content

Since we are utilizing the new Prismic Page Builder, we can setup live previews of our slices so we can view and edit our content in real-time on the left. From within a document, simply click the three dots in the upper right corner of the page, and click Live preview settings. Then, paste the following URL in the input - `http://localhost:3000/slice-simulator`.

You will now be able to view your slices on the left of your document as you edit.

hidden_content
[]

text_content default

content

Create our French locale homepage

Let's start by returning to our documents and clicking on our Homepage. On the right, let's switch to our French locale and add the following information:

  • UID - home (the same UID from our English version)
  • Title - 'Page d'accueil'

We will also add a slice by clicking Add Slice on the left and selecting the RichText slice. In our rich text slice let's add:

  • Heading 1 - 'Bienvenue sur mon blog!'
  • Paragraph - 'Vous trouverez ici ce que j'ai écrit et bien plus encore. J’espère que vous l'apprécierez!'

It should look like this:

An image of our French locale in the page builder.

Let's also copy our title name and add it as the document name in the top left so it makes searching for our documents easier.

A GIF showing how to add a display name.

Make sure to click Save and then Publish in the upper right.

Setting up our French locale blog posts

Now, let's go back to our documents and click on Example Blog Post 1. Switch to your French locale, and add the following:

  • UID - example-blog-post-1 (the same UID from our English version)
  • Title - "Exemple d'article de blog 1"

We will also add 2 RichText slices. We can fill these slices with the French translation of each of the slices from our English version, and then save.

For the first slice, we can add:

  • Paragraph - 'Ceci est un article de blog sur les chats !'

A GIF showing how to add a slice in the page builder.

For the second slice, we can add another RichText slice with:

  • Paragraph - 'Retour a la page d’accueil.'

This time, we can highlight the text and click Link to document.

An image showing how to link to document in the page builder.

Select our French locale homepage and then save the document.

An image showing how to link documents.

When finished, our page should look like this inside the Page Builder.

An image showing our blog post in the page builder.

Now click Publish in the upper right.

You will need to repeat this step for the 'Example Blog Post 2' and 'Example Blog Post 3' documents as well.

After you’ve finished creating and publishing the blog post pages for your French locale (you should now have three), it’s time for us to update our home page.

Add blog posts to our French locale homepage

The last step to configuring our content in Prismic is to add the French locale blog posts to our French Homepage document. So let’s navigate back to our French locale ‘Homepage’ document and add a PostCards slice below our existing RichText slice.

For each of our three blog posts we should click Add an item and include a Publication Date, Title, Description and Link.

A GIF that shows how to fill out a PostCards slice.

The link for each item should go to each of our blog posts in our French locale. When we are done filling out these items we should have something that looks like this.

An image showing our PostCards slice.

Make sure to save your page and click Publish.

Now if we go back to 'Documents' in our Prismic repository and switch to our French locale, we should be able to see our four published documents in our French locale.

An image of our docs in our French locale in Prismic.

text_content default

content

Adding Internationalization to Next.js

To add internationalization support to Next.js, we first need to create a custom middleware.ts file inside the src directory. This file will handle the detection of locales based on the current pathname and make sure the user is redirected to the correct pages and locales to match.

code_snippet default

text_content default

content

Once we’ve added the middleware.ts file we then need to configure our ./src/prismicio.ts file to support locales. We can do this by updating the Routes Resolvers array defined in the file to look like the one below.

code_snippet default

text_content default

content

Notice how we now include the lang parameter in the path. :lang? includes the document’s locale code, such as en-us. Because we included ?, the locale will be omitted if it is the Prismic repository’s default locale.

This parameter is important because the next thing we need to do is restructure our app directory so that all of the pages are located inside a [lang] directory. This is because all of our URLs will now contain a locale so we need to make sure our routes in Next.js match this. So, now inside the app directory, it should look like this.

code_snippet default

text_content default

content

After restructuring our application files, we then need to update our page.tsx files for both the home route and the [uid] route to accept the lang parameter we defined in the route. We need to accept this parameter because we’ll pass it through to our Prismic query to ensure we fetch the right locale version of the page. Below are the updated versions of the pages.

code_snippet default

code_snippet default

text_content default

content

To expand upon the note in the [uid]/page.tsx file, we pass a * to the lang parameter here to fetch all of the pages from Prismic regardless of the language. This is because it’s inside the generateStaticParams function that is used to generate all of the possible routes that can exist for that page. This means we need to fetch all pages of all the locales so we can generate a path for them.

At this point we technically have a working blog with internationalization and locales support although to change between locales users will need to manually edit the URL to switch to their chosen locale which is far from user-friendly. So, now let’s work on building a language switcher component that will show users what languages we have available and allow them to switch between them easily.

To do this, we need to define a getLocales.ts utility function in a new src/utils directory. Add this directory and getLocales.ts file, and add the below code.

code_snippet default

text_content default

content

In this function, we get all of the languages available in our Prismic repository and content. We then map over them to transform them into the shape we want and return them as an array.

With our getLocales utility function now defined, we need to build our language switcher component. To do that, create new components directory inside the /src directory and add a new file called LanguageSwitcher.tsx to that directory. Then, add the below code to it.

code_snippet default

text_content default

content

In this file, we take our array of locales as the prop and then map over them, creating a link for each of them that points to the current page but with the respective locale. We now just need to render our language switcher component out on both our home page.tsx and our [uid]/page.tsx files. Which we can do by updating the files to look like so.

code_snippet default

code_snippet default

text_content default

content

Testing our Next.js i18n app

At this point, our blog application should now be finished, and we should now be able to freely switch between English and French versions of it using our language switcher component.

To test this, refresh your page at http://localhost:3000 and you should be able to see the language switcher component and be able to use it to change the page's content.

A GIF of our completed Next.js i18n project.

callout info

title

Want to view the completed code?

content

If you would like to see the full example code for this project, check out this branch of the GitHub repo.

hidden_content
[]

text_content default

content

Closing thoughts on Next.js i18n

In this tutorial, we’ve taken a look at internationalization, why it’s important as well and how we can implement it in a scalable and easy-to-manage way in a Next.js app router project using a headless CMS such as Prismic. If you would like to learn more about internationalization and the Next.js app directory, you can read their documentation here. We hope you enjoyed this tutorial. Let us know what you thought in the comments below!👇

related_posts default

title

You might also like...

Document data
{
  "title": [
    {
      "type": "heading1",
      "text": "A Guide to Next.js Internationalization (i18n) with the App Router",
      "spans": []
    }
  ],
  "image": {
    "dimensions": {
      "width": 2000,
      "height": 2000
    },
    "alt": null,
    "copyright": null,
    "url": "https://prismic-main.cdn.prismic.io/prismic-main/6542ad880b105250cf53c871_i18n.svg?auto=compress,format",
    "id": "ZUKtiAsQUlDPU8hx",
    "edit": {
      "x": 0,
      "y": 0,
      "zoom": 1,
      "background": "transparent"
    }
  },
  "author": {
    "id": "ZO8HtREAACwARj8G",
    "type": "blog_author",
    "tags": [],
    "lang": "en-eu",
    "slug": "coner-murphy",
    "first_publication_date": "2023-08-30T09:19:09+0000",
    "last_publication_date": "2023-08-30T09:53:41+0000",
    "uid": "coner-murphy",
    "link_type": "Document",
    "key": "df84a1cf-87c8-4c4c-bc28-60188a36bfe9",
    "isBroken": false
  },
  "additional_author": {
    "link_type": "Document"
  },
  "category": {
    "id": "ZO8HtREAACwARj50",
    "type": "blog_category",
    "tags": [],
    "lang": "en-eu",
    "slug": "tech-stack",
    "first_publication_date": "2023-08-30T09:19:09+0000",
    "last_publication_date": "2026-04-22T11:48:49+0000",
    "uid": "tech-stack",
    "link_type": "Document",
    "key": "cf33d52c-71c9-43be-bde7-01cb03e14b13",
    "isBroken": false
  },
  "date": "2023-11-02",
  "updated_date": null,
  "learning_objective": null,
  "card_description": [],
  "slices": [
    {
      "variation": "info",
      "version": "sktwi1xtmkfgx8626",
      "items": [],
      "primary": {
        "title": [
          {
            "type": "heading2",
            "text": "This tutorial uses Slice Machine",
            "spans": [],
            "direction": "ltr"
          }
        ],
        "content": [
          {
            "type": "paragraph",
            "text": "Slice Machine has been replaced by the Prismic CLI and Type Builder, and is no longer maintained. The slice concepts in this tutorial still apply, but for current setup steps, see the Next.js docs.",
            "spans": [
              {
                "start": 184,
                "end": 196,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/docs/nextjs",
                  "target": "_blank"
                }
              }
            ],
            "direction": "ltr"
          },
          {
            "type": "paragraph",
            "text": "Still on Slice Machine? Migrate to the Type Builder.",
            "spans": [
              {
                "start": 24,
                "end": 51,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/docs/slice-machine#migrate-to-the-type-builder",
                  "target": "_blank"
                }
              }
            ],
            "direction": "ltr"
          }
        ],
        "hidden_content": []
      },
      "id": "callout$5673d8c4-2ab0-437d-b1d1-64e0d16f53ec",
      "slice_type": "callout",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "When building websites, it’s only natural to initially create it in our native language or the one of our target audience. But, over time, we might find ourselves looking to expand our reach to other target audiences or just wanting others to use and enjoy our website. But what happens if those audiences and users don’t speak the same language we developed the website in?",
            "spans": [],
            "direction": "ltr"
          },
          {
            "type": "paragraph",
            "text": "That’s where internationalization (also known as i18n) comes in. Internationalization is the process of designing and building a website so that it can be adapted and used for various languages, regions, and cultures. With roughly 7,000 languages spoken worldwide, internationalization uniquely enables websites to adapt their content for diverse global audiences, making them more accessible and beneficial for users across cultures. So, in this tutorial, we’re going to take a look at implementing internationalization into a Next.js App Router project.",
            "spans": [
              {
                "start": 13,
                "end": 54,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/glossary/internationalization-i18n",
                  "target": "_self"
                }
              }
            ],
            "direction": "ltr"
          }
        ]
      },
      "id": "text_content$22777a7c-d9f3-44f3-a2b3-40c8fa2725be",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [],
      "primary": {
        "content": [
          {
            "type": "heading2",
            "text": "Internationalization and Next.js App Router",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "Before we start, it’s worth mentioning that the Next.js App Router supports i18n straight out of the box. We can set it up by using a custom middleware.ts file to redirect users to the correct nested routes for the locale they’re using in their browser.",
            "spans": [
              {
                "start": 48,
                "end": 66,
                "type": "hyperlink",
                "data": {
                  "id": "ZSV1uxIAAOwrGDaS",
                  "type": "blog",
                  "tags": [
                    "Next.js"
                  ],
                  "lang": "en-eu",
                  "slug": "understanding-the-next.js-app-directory-and-how-to-use-it",
                  "first_publication_date": "2023-10-12T16:37:50+0000",
                  "last_publication_date": "2026-10-07T15:29:25+0000",
                  "uid": "nextjs-13-app-directory",
                  "link_type": "Document",
                  "isBroken": false
                }
              },
              {
                "start": 141,
                "end": 154,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "So, for example, US users would be redirected to /en-US/route, and French users would be redirected to /fr-FR/route. Each of these pages would host the same content but displayed in their respective language.",
            "spans": [
              {
                "start": 49,
                "end": 61,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 103,
                "end": 115,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "In Next.js, content for each language can be stored in JSON files, often called ‘dictionaries.’ These files would contain all of the translated text we’d like to display for each page. This would look something like the ones below.",
            "spans": []
          }
        ]
      },
      "id": "text_content$e4594f3c-661e-4a64-941a-4cf3adfe4f95",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "json",
          "code": [
            {
              "type": "preformatted",
              "text": "// dictionaries/en.json\n\n{\n  \"products\": {\n    \"cart\": \"Add to Cart\"\n  }\n}",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$ff183193-6333-442a-a92a-f53c4bf1f4b5",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "json",
          "code": [
            {
              "type": "preformatted",
              "text": "// dictionaries/fr.json\n\n{\n  \"products\": {\n    \"cart\": \"Ajouter au panier\"\n  }\n}",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$fbf91bc6-f504-418f-b89e-6ce5d8c0827a",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "We could then import the correct dictionary based on the route the user has been redirected to by the middleware.ts file and then display the correct text on the screen.",
            "spans": [
              {
                "start": 102,
                "end": 115,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "However, while this method of internationalization is pretty simple to implement, it does have some issues with it, most notably scalability. For example, if you have entire pages of content to translate, storing all of the content in JSON files will quickly become messy, difficult to manage, and likely out of sync with each other. So, how could we make this more scalable?👇",
            "spans": []
          }
        ]
      },
      "id": "text_content$fde93afd-b4b4-4f74-9cf0-89be1e4c8cd6",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "heading2",
            "text": "A scalable solution to internationalization (i18n)",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "Luckily for us, there is a solution to internationalization that is scalable and will make our lives easier. And that solution is using a headless CMS that supports internationalization. By using a headless CMS, we can use it to control all of the content on our website as well as the translated versions of it.",
            "spans": [
              {
                "start": 138,
                "end": 150,
                "type": "hyperlink",
                "data": {
                  "id": "ZO8HtREAACwARj9U",
                  "type": "blog",
                  "tags": [
                    "Headless",
                    "Headless CMS"
                  ],
                  "lang": "en-eu",
                  "slug": "headless-cms-explained-a-guide-for-marketers-and-developers",
                  "first_publication_date": "2023-08-30T09:19:09+0000",
                  "last_publication_date": "2026-10-13T17:13:28+0000",
                  "uid": "what-is-headless-cms",
                  "link_type": "Document",
                  "isBroken": false
                }
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "So, for the rest of this post, let’s look at implementing this solution with a Next.js app router project. We will set up a basic example blog that supports internationalization by having an English version and a French version. For managing our content, we’ll be using Prismic (and their new page builder 🤩), but the concepts and principles we cover throughout the tutorial should work with other technologies and setups, too.",
            "spans": [
              {
                "start": 270,
                "end": 277,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/"
                }
              },
              {
                "start": 293,
                "end": 305,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/page-builder"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$5aa432a8-8df1-40d1-9b12-8bfcca69bd2f",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "heading2",
            "text": "Setting up our Next.js project",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "For our project, we will be implementing i18n into a basic blog and adding functionality to be able to switch between locales.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "We’re going to break this tutorial up into two parts:",
            "spans": []
          },
          {
            "type": "o-list-item",
            "text": "Getting our blog set up and running with the English version.",
            "spans": []
          },
          {
            "type": "o-list-item",
            "text": "Setting up internationalization and the French version of our blog.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "So, let’s get started!",
            "spans": []
          }
        ]
      },
      "id": "text_content$289b4c3b-b196-46d0-a55c-8f1e0e0764a4",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "heading3",
            "text": "Setting up Prismic",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "The first thing we will do is create a new project in Prismic. You can do this by following the below steps:",
            "spans": []
          },
          {
            "type": "o-list-item",
            "text": "Login or signup for a Prismic account.",
            "spans": [
              {
                "start": 0,
                "end": 15,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/dashboard/signup",
                  "target": "_blank"
                }
              }
            ]
          },
          {
            "type": "o-list-item",
            "text": "Create a new Prismic repository using the Next.js option.",
            "spans": [
              {
                "start": 0,
                "end": 31,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/dashboard",
                  "target": "_blank"
                }
              }
            ]
          },
          {
            "type": "o-list-item",
            "text": "Choose 'Connect your own web app' and click Select.",
            "spans": [
              {
                "start": 44,
                "end": 51,
                "type": "strong"
              }
            ]
          },
          {
            "type": "o-list-item",
            "text": "Give your repository a name, select the free plan, and click Create repository.",
            "spans": [
              {
                "start": 61,
                "end": 79,
                "type": "strong"
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "Once you’ve finished creating your project in Prismic you should see a screen like this.",
            "spans": []
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/17d85e9e-460f-4766-af11-12124ede163a_page_builder_welcome.png?auto=compress,format",
            "alt": "An image of the welcome screen of the Page Builder.",
            "copyright": null,
            "dimensions": {
              "width": 3024,
              "height": 1712
            },
            "id": "ZUJkshIAACkArYHk",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          },
          {
            "type": "paragraph",
            "text": "For now, don’t click on anything. Let’s move on to setting up our Next.js project.",
            "spans": []
          }
        ]
      },
      "id": "text_content$0b569ebb-1e9c-4926-8ccb-1628bcf30053",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "heading3",
            "text": "Download our Next.js blog starter",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "To minimize setup steps and be able to focus on i18n, we will be using a custom starter for this project. To download the starter, run the following command in your terminal, making sure to switch out your repository name for <your repository name>.",
            "spans": [
              {
                "start": 226,
                "end": 248,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "",
            "spans": []
          }
        ]
      },
      "id": "text_content$27b382af-d8bd-4223-8a94-944dbca3f7bd",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "bash",
          "code": [
            {
              "type": "preformatted",
              "text": "npx @slicemachine/init@latest --repository <your repository name> --starter prismic-i18n-tutorial",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$2bc1f5c2-f8ed-4482-8a20-ca08098c0eac",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "Running this command will do several things, including:",
            "spans": []
          },
          {
            "type": "list-item",
            "text": "Copy the custom blog starter and connect it with your Prismic repository",
            "spans": []
          },
          {
            "type": "list-item",
            "text": "Install all core dependencies",
            "spans": []
          },
          {
            "type": "list-item",
            "text": "Sync data with Prismic",
            "spans": []
          },
          {
            "type": "list-item",
            "text": "Install Slice Machine",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "You’ll be asked if you want to run Slice Machine - type Y. We won’t modify any slices for our project since we utilized a starter, but you can access the UI at http://localhost:9999/ if you’re curious.",
            "spans": [
              {
                "start": 56,
                "end": 57,
                "type": "strong"
              },
              {
                "start": 160,
                "end": 182,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 160,
                "end": 182,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "http://localhost:9999/"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$5df6b79c-99b9-4e56-9851-7c62674a5aae",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "info",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "title": [
          {
            "type": "heading2",
            "text": "What is Slice Machine?",
            "spans": []
          }
        ],
        "content": [
          {
            "type": "paragraph",
            "text": "Slice Machine is Prismic’s developer tool that allows you to build slices, or reusable website sections, as components in your code. You can create, preview, and test slices locally without affecting your live environment. Then, you can ship the slices directly to marketers in a custom page builder so they can start using them as building blocks to create on-brand, customizable pages independently.",
            "spans": [
              {
                "start": 0,
                "end": 13,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/slice-machine"
                }
              },
              {
                "start": 67,
                "end": 73,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/docs/slice"
                }
              },
              {
                "start": 287,
                "end": 299,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://prismic.io/page-builder"
                }
              }
            ]
          }
        ],
        "hidden_content": []
      },
      "id": "callout$78ef6147-fc8d-44db-826b-099220caf050",
      "slice_type": "callout",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "Now, if we go back to our Prismic repository and refresh our page, we will see our documents section. You can also navigate to this page directly by going to https://your-repository-name.prismic.io/documents/. So, if your repository name is i18-tutorial you can go to https://i18-tutorial.prismic.io/documents/.",
            "spans": [
              {
                "start": 158,
                "end": 208,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 241,
                "end": 253,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 268,
                "end": 310,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/79fb1c85-12f7-41fa-8476-6b76fd8c250d_pagebuilder.png?auto=compress,format",
            "alt": "An image of the Prismic page builder with our starter docuements.",
            "copyright": null,
            "dimensions": {
              "width": 1371,
              "height": 464
            },
            "id": "ZUEiZRIAACwAqBlj",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          },
          {
            "type": "paragraph",
            "text": "You can see, as shown in the image above, that we already have four documents created for us in our starter.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "The last step is to get the front end of our site up and running. To do that, follow these steps:",
            "spans": []
          },
          {
            "type": "o-list-item",
            "text": "In a new tab in the terminal, cd into your project.",
            "spans": [
              {
                "start": 30,
                "end": 32,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "o-list-item",
            "text": "Run npm run dev.",
            "spans": [
              {
                "start": 4,
                "end": 15,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "o-list-item",
            "text": "Open your project in your favorite IDE.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "Your site should be running locally at http://localhost:3000/ and look something like this.",
            "spans": [
              {
                "start": 39,
                "end": 61,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 39,
                "end": 61,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "http://localhost:3000/"
                }
              }
            ]
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/28a1c3b2-a0f0-4ab8-9ff4-e430fc6b6b9e_frontend.png?auto=compress,format",
            "alt": "An image of the frontend of our site.",
            "copyright": null,
            "dimensions": {
              "width": 3004,
              "height": 1572
            },
            "id": "ZUEi4xIAAC0AqBus",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          }
        ]
      },
      "id": "text_content$9ebecdfe-c21d-4098-b121-6744e7971ce9",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "heading2",
            "text": "Setting up internationalization in Prismic",
            "spans": [],
            "direction": "ltr"
          },
          {
            "type": "paragraph",
            "text": "Now, with our blog configured and usable in English, let’s look at creating the French version and setting up internationalization. We’ll also look at the routing, content, and components required.",
            "spans": [],
            "direction": "ltr"
          },
          {
            "type": "heading3",
            "text": "Configuring Prismic",
            "spans": [],
            "direction": "ltr"
          },
          {
            "type": "paragraph",
            "text": "To start setting up internationalization, we must first enable the new language we want to add (French in this case) in Prismic. We can do this from our Prismic repository’s settings page under 'Translations & locales.' On this page, add the French locale. When you're done, it should look like this.",
            "spans": [],
            "direction": "ltr"
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/95cf766f-50cb-47fa-9fc7-c2718a613c53_locales.png?auto=compress,format",
            "alt": "An image of our English and French locales in Prismic.",
            "copyright": null,
            "dimensions": {
              "width": 1115,
              "height": 434
            },
            "id": "ZUEkvxIAACoAqCSU",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          },
          {
            "type": "paragraph",
            "text": "Now, if you navigate back to the Documents page in Prismic, you should see a new locale dropdown added to the top right of the page near 'Create new.' You can use this dropdown selector to switch between locales and create new pages for each one.",
            "spans": [],
            "direction": "ltr"
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/ec59a9b3-70fa-4f69-9b2c-8b3c45547523_french_locale.png?auto=compress,format",
            "alt": "An image of our French locale in the documents section of page builder.",
            "copyright": null,
            "dimensions": {
              "width": 1511,
              "height": 461
            },
            "id": "ZUEmIBIAACwAqCr4",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          },
          {
            "type": "heading3",
            "text": "Switching between locales",
            "spans": [],
            "direction": "ltr"
          },
          {
            "type": "paragraph",
            "text": "Now, with our extra locale added, we need to create a French version of each document we have in English. To do this, first select English in the locales dropdown and open on document. Then, you should see the same locales dropdown as before on the right-hand side of the page. Use the dropdown to change to your other locale, this will create a version of your document in this new locale.",
            "spans": [],
            "direction": "ltr"
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/eac57191-4bc3-4fa9-816f-11bc20e786e9_click_locale.gif?auto=compress,format",
            "alt": "A GIF showing how to change locales in Prismic.",
            "copyright": null,
            "dimensions": {
              "width": 1336,
              "height": 720
            },
            "id": "ZUEoeBIAAC4AqDWh",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "#fff"
            }
          },
          {
            "type": "paragraph",
            "text": "You will notice that by switching locales it creates a blank document. Give it a UID an replicate the slices and content, only this time in French! ",
            "spans": [],
            "direction": "ltr"
          },
          {
            "type": "paragraph",
            "text": "Do this, and translate every of your English documents.",
            "spans": [],
            "direction": "ltr"
          }
        ]
      },
      "id": "text_content$43ec71cc-b210-413a-ab16-c3a61694c663",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "info",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "title": [
          {
            "type": "heading2",
            "text": "Setup live previews",
            "spans": []
          }
        ],
        "content": [
          {
            "type": "paragraph",
            "text": "Since we are utilizing the new Prismic Page Builder, we can setup live previews of our slices so we can view and edit our content in real-time on the left. From within a document, simply click the three dots in the upper right corner of the page, and click Live preview settings. Then, paste the following URL in the input - `http://localhost:3000/slice-simulator`. \n\nYou will now be able to view your slices on the left of your document as you edit.",
            "spans": [
              {
                "start": 257,
                "end": 278,
                "type": "strong"
              }
            ]
          }
        ],
        "hidden_content": []
      },
      "id": "callout$bac7a59d-6a71-4601-9349-ba85a345f4ba",
      "slice_type": "callout",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "heading3",
            "text": "Create our French locale homepage",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "Let's start by returning to our documents and clicking on our Homepage. On the right, let's switch to our French locale and add the following information:",
            "spans": [
              {
                "start": 62,
                "end": 71,
                "type": "strong"
              }
            ]
          },
          {
            "type": "list-item",
            "text": "UID - home (the same UID from our English version)",
            "spans": [
              {
                "start": 0,
                "end": 3,
                "type": "strong"
              },
              {
                "start": 6,
                "end": 10,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "list-item",
            "text": "Title - 'Page d'accueil'",
            "spans": [
              {
                "start": 0,
                "end": 5,
                "type": "strong"
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "We will also add a slice by clicking Add Slice on the left and selecting the RichText slice. In our rich text slice let's add:",
            "spans": [
              {
                "start": 37,
                "end": 46,
                "type": "strong"
              }
            ]
          },
          {
            "type": "list-item",
            "text": "Heading 1 - 'Bienvenue sur mon blog!'",
            "spans": [
              {
                "start": 0,
                "end": 9,
                "type": "strong"
              }
            ]
          },
          {
            "type": "list-item",
            "text": "Paragraph - 'Vous trouverez ici ce que j'ai écrit et bien plus encore. J’espère que vous l'apprécierez!'",
            "spans": [
              {
                "start": 0,
                "end": 9,
                "type": "strong"
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "It should look like this:",
            "spans": []
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/da336bae-f335-464c-8227-2ff445397d4a_home_french.png?auto=compress,format",
            "alt": "An image of our French locale in the page builder.",
            "copyright": null,
            "dimensions": {
              "width": 2630,
              "height": 914
            },
            "id": "ZUFhvBIAACgAqTbs",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          },
          {
            "type": "paragraph",
            "text": "Let's also copy our title name and add it as the document name in the top left so it makes searching for our documents easier.",
            "spans": []
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/5e68c444-b223-4315-894f-dbc35d00c1c1_display_name.gif?auto=compress,format",
            "alt": "A GIF showing how to add a display name.",
            "copyright": null,
            "dimensions": {
              "width": 1356,
              "height": 720
            },
            "id": "ZUKjKhIAAC0ArpWY",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "#fff"
            }
          },
          {
            "type": "paragraph",
            "text": "Make sure to click Save and then Publish in the upper right.",
            "spans": [
              {
                "start": 19,
                "end": 23,
                "type": "strong"
              },
              {
                "start": 33,
                "end": 40,
                "type": "strong"
              }
            ]
          },
          {
            "type": "heading3",
            "text": "Setting up our French locale blog posts",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "Now, let's go back to our documents and click on Example Blog Post 1. Switch to your French locale, and add the following:",
            "spans": [
              {
                "start": 49,
                "end": 68,
                "type": "strong"
              }
            ]
          },
          {
            "type": "list-item",
            "text": "UID - example-blog-post-1 (the same UID from our English version)",
            "spans": [
              {
                "start": 0,
                "end": 3,
                "type": "strong"
              },
              {
                "start": 6,
                "end": 25,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "list-item",
            "text": "Title - \"Exemple d'article de blog 1\"",
            "spans": [
              {
                "start": 0,
                "end": 5,
                "type": "strong"
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "We will also add 2 RichText slices. We can fill these slices with the French translation of each of the slices from our English version, and then save.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "For the first slice, we can add:",
            "spans": []
          },
          {
            "type": "list-item",
            "text": "Paragraph - 'Ceci est un article de blog sur les chats !'",
            "spans": [
              {
                "start": 0,
                "end": 9,
                "type": "strong"
              }
            ]
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/37f40d40-bf6e-427b-98f4-67fc0fbe86d0_addslice.gif?auto=compress,format",
            "alt": "A GIF showing how to add a slice in the page builder.",
            "copyright": null,
            "dimensions": {
              "width": 1336,
              "height": 720
            },
            "id": "ZUFb3BIAAC4AqRzr",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "#fff"
            }
          },
          {
            "type": "paragraph",
            "text": "For the second slice, we can add another RichText slice with:",
            "spans": []
          },
          {
            "type": "list-item",
            "text": "Paragraph -  'Retour a la page d’accueil.' ",
            "spans": [
              {
                "start": 0,
                "end": 9,
                "type": "strong"
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "This time, we can highlight the text and click Link to document.",
            "spans": [
              {
                "start": 47,
                "end": 63,
                "type": "strong"
              }
            ]
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/6060d91e-6d42-44e7-9831-fdb0f1a2faa4_link_doc.png?auto=compress,format",
            "alt": "An image showing how to link to document in the page builder.",
            "copyright": null,
            "dimensions": {
              "width": 2582,
              "height": 362
            },
            "id": "ZUFdqBIAAC8AqSTB",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          },
          {
            "type": "paragraph",
            "text": "Select our French locale homepage and then save the document.",
            "spans": []
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/e22eb2e0-dc53-4b44-a202-cf90c46378d5_link.png?auto=compress,format",
            "alt": "An image showing how to link documents.",
            "copyright": null,
            "dimensions": {
              "width": 1398,
              "height": 1218
            },
            "id": "ZUFkZxIAACoAqULh",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          },
          {
            "type": "paragraph",
            "text": "When finished, our page should look like this inside the Page Builder.",
            "spans": []
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/4f966cf1-c6e0-4d1d-b628-ce9ca8edd08b_page_builder_setup.png?auto=compress,format",
            "alt": "An image showing our blog post in the page builder.",
            "copyright": null,
            "dimensions": {
              "width": 2510,
              "height": 1248
            },
            "id": "ZUFklxIAAC4AqUO3",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          },
          {
            "type": "paragraph",
            "text": "Now click Publish in the upper right.",
            "spans": [
              {
                "start": 10,
                "end": 17,
                "type": "strong"
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "You will need to repeat this step for the 'Example Blog Post 2' and 'Example Blog Post 3' documents as well.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "After you’ve finished creating and publishing the blog post pages for your French locale (you should now have three), it’s time for us to update our home page. ",
            "spans": []
          },
          {
            "type": "heading3",
            "text": "Add blog posts to our French locale homepage",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "The last step to configuring our content in Prismic is to add the French locale blog posts to our French Homepage document. So let’s navigate back to our French locale ‘Homepage’ document and add a PostCards slice below our existing RichText slice.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "For each of our three blog posts we should click Add an item and include a Publication Date, Title, Description and Link.",
            "spans": [
              {
                "start": 49,
                "end": 60,
                "type": "strong"
              }
            ]
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/191499c8-48e8-433a-82f3-79cccdbc89c1_filling_out_slice.gif?auto=compress,format",
            "alt": "A GIF that shows how to fill out a PostCards slice.",
            "copyright": null,
            "dimensions": {
              "width": 1336,
              "height": 720
            },
            "id": "ZUFpkBIAACsAqVpe",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "#fff"
            }
          },
          {
            "type": "paragraph",
            "text": "The link for each item should go to each of our blog posts in our French locale. When we are done filling out these items we should have something that looks like this.",
            "spans": []
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/e6136784-31ab-47f7-9ff2-1a56e653288c_scroll.gif?auto=compress,format",
            "alt": "An image showing our PostCards slice.",
            "copyright": null,
            "dimensions": {
              "width": 1336,
              "height": 720
            },
            "id": "ZUFrIRIAACkAqWFS",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "#fff"
            }
          },
          {
            "type": "paragraph",
            "text": "Make sure to save your page and click Publish.",
            "spans": [
              {
                "start": 38,
                "end": 45,
                "type": "strong"
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "Now if we go back to 'Documents' in our Prismic repository and switch to our French locale, we should be able to see our four published documents in our French locale.",
            "spans": []
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/87ee53f0-8c40-44b4-ae86-5e0b6c094fab_french_docs.png?auto=compress,format",
            "alt": "An image of our docs in our French locale in Prismic.",
            "copyright": null,
            "dimensions": {
              "width": 1526,
              "height": 503
            },
            "id": "ZUFsDxIAACoAqWWK",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "transparent"
            }
          },
          {
            "type": "paragraph",
            "text": "",
            "spans": []
          }
        ]
      },
      "id": "text_content$8c5fd5c6-9da8-45ed-b5b8-ed03ed400912",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "heading2",
            "text": "Adding Internationalization to Next.js",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "To add internationalization support to Next.js, we first need to create a custom middleware.ts file inside the src directory. This file will handle the detection of locales based on the current pathname and make sure the user is redirected to the correct pages and locales to match.",
            "spans": [
              {
                "start": 81,
                "end": 94,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 111,
                "end": 114,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$e3860ee8-ea14-4e5b-bc8c-c6646f09c431",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "typescript",
          "code": [
            {
              "type": "preformatted",
              "text": "// ./src/middleware.ts\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { createClient } from '@/prismicio';\n\nexport async function middleware(request: NextRequest) {\n  const client = createClient();\n  const repository = await client.getRepository();\n\n  const locales = repository.languages.map((lang) => lang.id);\n  const defaultLocale = locales[0];\n\n  // Check if there is any supported locale in the pathname\n  const { pathname } = request.nextUrl;\n\n  const pathnameIsMissingLocale = locales.every(\n    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`\n  );\n\n  // Redirect to default locale if there is no supported locale prefix\n  if (pathnameIsMissingLocale) {\n    return NextResponse.rewrite(\n      new URL(`/${defaultLocale}${pathname}`, request.url)\n    );\n  }\n}\n\nexport const config = {\n\t// Don’t change the URL of Next.js assets starting with _next\n  matcher: ['/((?!_next).*)'],\n};",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$c7e8a5f1-8f11-4f59-938d-d9943444190f",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "Once we’ve added the middleware.ts file we then need to configure our ./src/prismicio.ts file to support locales. We can do this by updating the Routes Resolvers array defined in the file to look like the one below.",
            "spans": [
              {
                "start": 21,
                "end": 34,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 70,
                "end": 88,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$0272440d-179d-4e8f-b6d7-adf2a5901acc",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "typescript",
          "code": [
            {
              "type": "preformatted",
              "text": "// ./src/prismicio.ts\n\nconst routes: prismic.ClientConfig['routes'] = [\n  {\n    type: 'page',\n    uid: 'home',\n    path: '/:lang?',\n  },\n  {\n    type: 'page',\n    path: '/:lang?/:uid',\n  },\n];",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$984999bd-d18a-48ac-be64-26b1b627e891",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "Notice how we now include the lang parameter in the path. :lang? includes the document’s locale code, such as en-us. Because we included ?, the locale will be omitted if it is the Prismic repository’s default locale.",
            "spans": [
              {
                "start": 30,
                "end": 34,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 58,
                "end": 64,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 110,
                "end": 115,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 137,
                "end": 138,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "This parameter is important because the next thing we need to do is restructure our app directory so that all of the pages are located inside a [lang] directory. This is because all of our URLs will now contain a locale so we need to make sure our routes in Next.js match this. So, now inside the app directory, it should look like this.",
            "spans": [
              {
                "start": 84,
                "end": 87,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 144,
                "end": 150,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 297,
                "end": 300,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$5b7ab5f0-aa57-4170-bf75-29f561527b37",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "text",
          "code": [
            {
              "type": "preformatted",
              "text": "- app\n  - [lang]\n    - page.tsx\n    - [uid]\n      - page.tsx\n    - slice-simulator\n      - page.tsx\n  - api\n    - exit-preview/route.ts\n    - preview/route.ts\n    - revalidate/route.ts\n  - layout.tsx\n  - styles.css",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$9d6f7467-1c84-41eb-ac43-43f865d1cf57",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "After restructuring our application files, we then need to update our page.tsx files for both the home route and the [uid] route to accept the lang parameter we defined in the route. We need to accept this parameter because we’ll pass it through to our Prismic query to ensure we fetch the right locale version of the page. Below are the updated versions of the pages.",
            "spans": [
              {
                "start": 70,
                "end": 78,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 117,
                "end": 122,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 143,
                "end": 147,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$58b22b4e-d9fb-4339-bab0-5cb6900fc62d",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "typescript",
          "code": [
            {
              "type": "preformatted",
              "text": "// ./src/app/[lang]/page.tsx\n\nimport { Metadata } from 'next';\nimport { SliceZone } from '@prismicio/react';\nimport * as prismic from '@prismicio/client';\nimport { createClient } from '@/prismicio';\nimport { components } from '@/slices';\n\nexport async function generateMetadata({\n  params: { lang },\n}: {\n  params: { lang: string };\n}): Promise<Metadata> {\n  const client = createClient();\n\t// ⬇️ Note this line with the `lang` parameter being passed in\n  const home = await client.getByUID('page', 'home', { lang });\n\n  return {\n    title: prismic.asText(home.data.title),\n    description: home.data.meta_description,\n    openGraph: {\n      title: home.data.meta_title || undefined,\n      images: [\n        {\n          url: home.data.meta_image.url || '',\n        },\n      ],\n    },\n  };\n}\n\nexport default async function Index({\n  params: { lang },\n}: {\n  params: { lang: string };\n}) {\n  const client = createClient();\n\t// ⬇️ Note this line with the `lang` parameter being passed in\n  const home = await client.getByUID('page', 'home', {\n    lang,\n  });\n\n  return <SliceZone slices={home.data.slices} components={components} />;\n}",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$5e6dee89-bb42-4daf-9191-f45b7e59d78d",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "typescript",
          "code": [
            {
              "type": "preformatted",
              "text": "// ./src/app/[lang]/[uid]/page.tsx\n\nimport { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { SliceZone } from '@prismicio/react';\nimport * as prismic from '@prismicio/client';\nimport { createClient } from '@/prismicio';\nimport { components } from '@/slices';\n\ntype Params = { uid: string; lang: string };\n\nexport async function generateMetadata({\n  params,\n}: {\n  params: Params;\n}): Promise<Metadata> {\n  const client = createClient();\n\t// ⬇️ Note this line with the `lang` parameter being passed in\n  const page = await client\n    .getByUID('page', params.uid, { lang: params.lang })\n    .catch(() => notFound());\n\n  return {\n    title: prismic.asText(page.data.title),\n    description: page.data.meta_description,\n    openGraph: {\n      title: page.data.meta_title || undefined,\n      images: [\n        {\n          url: page.data.meta_image.url || '',\n        },\n      ],\n    },\n  };\n}\n\nexport default async function Page({ params }: { params: Params }) {\n  const client = createClient();\n\t// ⬇️ Note this line with the `lang` parameter being passed in\n  const page = await client\n    .getByUID('page', params.uid, {\n      lang: params.lang,\n    })\n    .catch(() => notFound());\n\n  return <SliceZone slices={page.data.slices} components={components} />;\n}\n\nexport async function generateStaticParams() {\n  const client = createClient();\n\n\t// ⬇️ Note this line using a '*' for the lang parameter\n  const pages = await client.getAllByType('page', {\n    predicates: [prismic.filter.not('my.page.uid', 'home')],\n    lang: '*',\n  });\n\n  return pages.map((page) => ({ uid: page.uid, lang: page.lang }));\n}",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$41cac6a4-75ad-4ed2-b88a-0828bed23bad",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "To expand upon the note in the [uid]/page.tsx file, we pass a * to the lang parameter here to fetch all of the pages from Prismic regardless of the language. This is because it’s inside the generateStaticParams function that is used to generate all of the possible routes that can exist for that page. This means we need to fetch all pages of all the locales so we can generate a path for them.",
            "spans": [
              {
                "start": 31,
                "end": 45,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 62,
                "end": 63,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 71,
                "end": 75,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 190,
                "end": 210,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "paragraph",
            "text": "At this point we technically have a working blog with internationalization and locales support although to change between locales users will need to manually edit the URL to switch to their chosen locale which is far from user-friendly. So, now let’s work on building a language switcher component that will show users what languages we have available and allow them to switch between them easily.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "To do this, we need to define a getLocales.ts utility function in a new src/utils directory. Add this directory and getLocales.ts file, and add the below code.",
            "spans": [
              {
                "start": 32,
                "end": 45,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 72,
                "end": 81,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 116,
                "end": 129,
                "type": "label",
                "data": {
                  "label": "label"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$1d2de5c8-6f23-47de-8f6d-ec73da835bc4",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "typescript",
          "code": [
            {
              "type": "preformatted",
              "text": "// ./src/utils/getLocales.ts\n\nimport { Client, Content } from '@prismicio/client';\n\nexport async function getLocales(\n  doc: Content.AllDocumentTypes,\n  client: Client<Content.AllDocumentTypes>\n) {\n  const [repository, altDocs] = await Promise.all([\n    client.getRepository(),\n    doc.alternate_languages.length > 0\n      ? client.getAllByIDs(\n          doc.alternate_languages.map((altLang) => altLang.id),\n          {\n            lang: '*',\n            // Exclude all fields to speed up the query.\n            fetch: `${doc.type}.__nonexistent-field__`,\n          }\n        )\n      : Promise.resolve([]),\n  ]);\n\n  return [doc, ...altDocs].map((page) => {\n    const lang = repository?.languages.find((l) => l.id === page.lang);\n\n    return {\n      lang: lang?.id || '',\n      url: page?.url || '',\n      lang_name: lang?.name || '',\n    };\n  });\n}",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$31e54c84-a482-4bb2-85d1-95af8c389eff",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "In this function, we get all of the languages available in our Prismic repository and content. We then map over them to transform them into the shape we want and return them as an array.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "With our getLocales utility function now defined, we need to build our language switcher component. To do that, create new components directory inside the /src directory and add a new file called LanguageSwitcher.tsx to that directory. Then, add the below code to it.",
            "spans": [
              {
                "start": 9,
                "end": 19,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 155,
                "end": 159,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 196,
                "end": 216,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$deb9f526-950d-43c7-8cc4-e7c9187cd566",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "typescript",
          "code": [
            {
              "type": "preformatted",
              "text": "// ./src/components/LanguageSwitcher.tsx\n\nimport { PrismicNextLink } from '@prismicio/next';\n\ninterface LanguageSwitcherProps {\n  locales: {\n    lang: string;\n    lang_name: string;\n    url: string;\n  }[];\n}\n\nconst localeLabels = {\n  'en-us': 'EN',\n  'fr-fr': 'FR',\n};\n\nexport const LanguageSwitcher = ({ locales }: LanguageSwitcherProps) => (\n  <div className=\"flex flex-wrap gap-3\">\n    <span aria-hidden>🌐</span>\n    <ul className=\"flex flex-wrap gap-3\">\n      {locales.map((locale) => (\n        <li key={locale.lang} className=\"first:font-semibold\">\n          <PrismicNextLink\n            href={locale.url}\n            locale={locale.lang}\n            aria-label={`Change language to ${locale.lang_name}`}\n          >\n            {localeLabels[locale.lang as keyof typeof localeLabels] ||\n              locale.lang}\n          </PrismicNextLink>\n        </li>\n      ))}\n    </ul>\n  </div>\n);",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$04b1cf48-1371-49e8-855b-e33100ea9da0",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "paragraph",
            "text": "In this file, we take our array of locales as the prop and then map over them, creating a link for each of them that points to the current page but with the respective locale. We now just need to render our language switcher component out on both our home page.tsx and our [uid]/page.tsx files. Which we can do by updating the files to look like so.",
            "spans": [
              {
                "start": 256,
                "end": 264,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              },
              {
                "start": 273,
                "end": 287,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$79cbbe29-1dd3-4f7d-9e11-1ea8ac4cfe4d",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "typescript",
          "code": [
            {
              "type": "preformatted",
              "text": "// ./src/app/[lang]/page.tsx\n\nimport { Metadata } from 'next';\nimport { SliceZone } from '@prismicio/react';\nimport * as prismic from '@prismicio/client';\nimport { createClient } from '@/prismicio';\nimport { components } from '@/slices';\n// ⬇️ Note the imports of `getLocales` and `LanguageSwitcher`\nimport { getLocales } from '@/utils/getLocales';\nimport { LanguageSwitcher } from '@/components/LanguageSwitcher';\n\nexport async function generateMetadata({\n  params: { lang },\n}: {\n  params: { lang: string };\n}): Promise<Metadata> {\n  const client = createClient();\n  const home = await client.getByUID('page', 'home', { lang });\n\n  return {\n    title: prismic.asText(home.data.title),\n    description: home.data.meta_description,\n    openGraph: {\n      title: home.data.meta_title || undefined,\n      images: [\n        {\n          url: home.data.meta_image.url || '',\n        },\n      ],\n    },\n  };\n}\n\nexport default async function Index({\n  params: { lang },\n}: {\n  params: { lang: string };\n}) {\n  const client = createClient();\n  const home = await client.getByUID('page', 'home', {\n    lang,\n  });\n\t// ⬇️ Note the fetching of the locales\n  const locales = await getLocales(home, client);\n\n\t// ⬇️ Note the rendering of the LanguageSwitcher component\n  return (\n    <>\n      <LanguageSwitcher locales={locales} />\n      <SliceZone slices={home.data.slices} components={components} />\n    </>\n  );\n}",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$c06b4802-696e-48e1-b59d-604644066248",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "name": null,
          "language": "typescript",
          "code": [
            {
              "type": "preformatted",
              "text": "// ./src/app/[lang]/[uid]/page.tsx\n\nimport { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { SliceZone } from '@prismicio/react';\nimport * as prismic from '@prismicio/client';\nimport { createClient } from '@/prismicio';\nimport { components } from '@/slices';\n// ⬇️ Note the imports of `getLocales` and `LanguageSwitcher`\nimport { getLocales } from '@/utils/getLocales';\nimport { LanguageSwitcher } from '@/components/LanguageSwitcher';\n\ntype Params = { uid: string; lang: string };\n\nexport async function generateMetadata({\n  params,\n}: {\n  params: Params;\n}): Promise<Metadata> {\n  const client = createClient();\n  const page = await client\n    .getByUID('page', params.uid, { lang: params.lang })\n    .catch(() => notFound());\n\n  return {\n    title: prismic.asText(page.data.title),\n    description: page.data.meta_description,\n    openGraph: {\n      title: page.data.meta_title || undefined,\n      images: [\n        {\n          url: page.data.meta_image.url || '',\n        },\n      ],\n    },\n  };\n}\n\nexport default async function Page({ params }: { params: Params }) {\n  const client = createClient();\n  const page = await client\n    .getByUID('page', params.uid, {\n      lang: params.lang,\n    })\n    .catch(() => notFound());\n\n\t// ⬇️ Note the fetching of the locales\n  const locales = await getLocales(page, client);\n\n\t// ⬇️ Note the rendering of the LanguageSwitcher component\n  return (\n    <>\n      <LanguageSwitcher locales={locales} />\n      <SliceZone slices={page.data.slices} components={components} />\n    </>\n  );\n}\n\nexport async function generateStaticParams() {\n  const client = createClient();\n\n  const pages = await client.getAllByType('page', {\n    predicates: [prismic.filter.not('my.page.uid', 'home')],\n    lang: '*',\n  });\n\n  return pages.map((page) => ({ uid: page.uid, lang: page.lang }));\n}",
              "spans": []
            }
          ],
          "wrap_code": null,
          "category": "body_code"
        }
      ],
      "primary": {},
      "id": "code_snippet$70df035d-e5fb-4087-83d2-bd70f14b2013",
      "slice_type": "code_snippet",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "heading2",
            "text": "Testing our Next.js i18n app",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "At this point, our blog application should now be finished, and we should now be able to freely switch between English and French versions of it using our language switcher component.",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "To test this, refresh your page at http://localhost:3000 and you should be able to see the language switcher component and be able to use it to change the page's content.",
            "spans": [
              {
                "start": 35,
                "end": 56,
                "type": "label",
                "data": {
                  "label": "inline-code"
                }
              }
            ]
          },
          {
            "type": "image",
            "url": "https://images.prismic.io/prismic-main/64df68b1-004a-4f57-b9d9-9f8783dd35f2_final_app.gif?auto=compress,format",
            "alt": "A GIF of our completed Next.js i18n project.",
            "copyright": null,
            "dimensions": {
              "width": 1336,
              "height": 720
            },
            "id": "ZUKueRIAACoArskO",
            "edit": {
              "x": 0,
              "y": 0,
              "zoom": 1,
              "background": "#fff"
            }
          }
        ]
      },
      "id": "text_content$a0b5c0df-2944-4026-867b-85b098e0c92d",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "info",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "title": [
          {
            "type": "heading2",
            "text": "Want to view the completed code?",
            "spans": []
          }
        ],
        "content": [
          {
            "type": "paragraph",
            "text": "If you would like to see the full example code for this project, check out this branch of the GitHub repo.",
            "spans": [
              {
                "start": 75,
                "end": 105,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://github.com/prismicio-community/prismic-i18n-tutorial/tree/final",
                  "target": "_blank"
                }
              }
            ]
          }
        ],
        "hidden_content": []
      },
      "id": "callout$f6eb526c-fc56-4cb0-afe0-6f9db04a74ff",
      "slice_type": "callout",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {}
      ],
      "primary": {
        "content": [
          {
            "type": "heading2",
            "text": "Closing thoughts on Next.js i18n",
            "spans": []
          },
          {
            "type": "paragraph",
            "text": "In this tutorial, we’ve taken a look at internationalization, why it’s important as well and how we can implement it in a scalable and easy-to-manage way in a Next.js app router project using a headless CMS such as Prismic. If you would like to learn more about internationalization and the Next.js app directory, you can read their documentation here. We hope you enjoyed this tutorial. Let us know what you thought in the comments below!👇",
            "spans": [
              {
                "start": 244,
                "end": 352,
                "type": "hyperlink",
                "data": {
                  "link_type": "Web",
                  "url": "https://nextjs.org/docs/app/building-your-application/routing/internationalization"
                }
              }
            ]
          }
        ]
      },
      "id": "text_content$6daf0994-af06-4c37-bbb9-aff04db53d37",
      "slice_type": "text_content",
      "slice_label": null
    },
    {
      "variation": "default",
      "version": "sktwi1xtmkfgx8626",
      "items": [
        {
          "related": {
            "id": "ZTfNLRIAAI853VPA",
            "type": "broken_type",
            "tags": [],
            "lang": null,
            "slug": "-",
            "first_publication_date": null,
            "last_publication_date": null,
            "link_type": "Document",
            "key": "9c5ba740-b235-446f-8a8a-d1c2d10279a6",
            "isBroken": true
          }
        },
        {
          "related": {
            "id": "ZSV1uxIAAOwrGDaS",
            "type": "blog",
            "tags": [
              "Next.js"
            ],
            "lang": "en-eu",
            "slug": "understanding-the-next.js-app-directory-and-how-to-use-it",
            "first_publication_date": "2023-10-12T16:37:50+0000",
            "last_publication_date": "2026-10-07T15:29:25+0000",
            "uid": "nextjs-13-app-directory",
            "link_type": "Document",
            "key": "fdd23104-ad64-46af-817e-890aabf11609",
            "isBroken": false
          }
        }
      ],
      "primary": {
        "title": [
          {
            "type": "heading2",
            "text": "You might also like...",
            "spans": []
          }
        ]
      },
      "id": "related_posts$850e90b3-6971-4146-85c2-078c242fe78f",
      "slice_type": "related_posts",
      "slice_label": null
    }
  ],
  "meta_title": "Next.js Internationalization (i18n) Tutorial [App Router]",
  "meta_description": "Next.js internationalization (i18n) is fully supported with the Next.js 13 app router. Learn how to build a multilingual site with Next.js and Prismic.",
  "social_image": {},
  "short_name": null,
  "part_of_guide": {
    "link_type": "Document"
  }
}