{
  "path": "/libraries-i18n",
  "site": "at://did:plc:er6erflnnxcozlbqmrpflt6h/site.standard.publication/3miclijid6r24",
  "tags": [
    "next.js",
    "i18n",
    "intl",
    "react"
  ],
  "$type": "site.standard.document",
  "title": "More libraries to the library god or how I remade i18n [next.js v14]",
  "content": {
    "$type": "pub.leaflet.content",
    "pages": [
      {
        "$type": "pub.leaflet.pages.linearDocument",
        "blocks": [
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.image",
              "image": {
                "$type": "blob",
                "ref": {
                  "$link": "bafkreihefewoxfiip6ysroyns5pl2k4mfnea5jos2uxohphq3nqn5oqpgi"
                },
                "mimeType": "image/jpeg",
                "size": 147338
              },
              "aspectRatio": {
                "width": 1200,
                "height": 600
              }
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "There are dozens of amazing libraries made for internationalization, such as i18n, react-intl, next-intl. They all do an excellent job of adding translations to an application or website. Most of them are tested, debugged, and consistently supported."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "But they are all outdated."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "After all, during this time, the react ecosystem has been developing. The latest version of next.js has major updates from react.js — cache, taint, new hooks, and, of course, server components. The React.js team will likely introduce these changes in May."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "In this article, I will talk about key changes, personal experience, problems with existing solutions, necessary updates, solutions I came up with, and, of course, answer the questions of “why” and most importantly — “why”?"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Changes"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "The first thing to start with is how the changes in React.js made translation libraries obsolete."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Despite the fact that the latest stable version of React.js was released almost two years ago, it has 2 other channels — canary and experimental, where canary is also considered a stable channel and is recommended for use by libraries."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 280,
                    "byteStart": 227
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#italic"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 287,
                    "byteStart": 280
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#italic"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 294,
                    "byteStart": 287
                  },
                  "features": [
                    {
                      "uri": "https://medium.com/@vordgi/next-js-app-router-experience-of-use-path-to-the-future-or-wrong-turn-277dde4369a6",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                }
              ],
              "plaintext": "This is the channel that Next.js uses. Next.js launched server components without additional flags inside the so-called App Router — a new directory as an alternative to pages, which uses its conventions and different sugar (about changes and problems which I wrote in a recent articlearticle)."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Server components definitely solve a number of problems and are a new milestone for optimizations. Including for translations. Without server components, translations were stored both in the compiled HTML and as a large object in the client script. Now you can get ready-made HTML, which doesn’t need anything on the client."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Next.js paid special attention to this feature."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Personal experience"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 43,
                    "byteStart": 26
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#italic"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 64,
                    "byteStart": 43
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#italic"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 85,
                    "byteStart": 64
                  },
                  "features": [
                    {
                      "uri": "https://nextjs.org/docs/app/building-your-application/routing/internationalization",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                }
              ],
              "plaintext": "You can add translations (according to the Next.js documentationNext.js documentation) as follows:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "javascript",
              "plaintext": "// app/[lang]/dictionaries.js\nimport 'server-only'\n\nconst dictionaries = {\n  en: () => import('./dictionaries/en.json').then((module) => module.default),\n  nl: () => import('./dictionaries/nl.json').then((module) => module.default),\n}\nexport const getDictionary = async (locale) => dictionaries[locale]()"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "javascript",
              "plaintext": "// app/[lang]/page.js\nimport { getDictionary } from './dictionaries'\n\nexport default async function Page({ params: { lang } }) {\n  const dict = await getDictionary(lang) // en\n  return <button>{dict.products.cart}</button> // Add to Cart\n}"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "This solution is described as ready and fully optimized. It works entirely on the server, and the client already receives ready-made HTML. However, the Next.js team missed one important detail — how to pass the language deep into server components."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "A big problem with server components is that contexts are not available in them. The Next.js team explains the absence of these functions by the fact that Layout is not re-rendered, and everything that depends on props should be client-side."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Perhaps translation libraries were most affected by this. As a temporary solution, they suggest determining the language in the middleware and adding it to cookies. Then when building the page, read it in the necessary places. But reading cookies means enabling server rendering, which is not suitable for everyone."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "In general, the main problem with existing solutions is that most of them are not made for server components. Components and functions were developed for runtime, using hooks and synchronicity."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Another inconvenience was caching in Next.js. Namely — it works fully only for GET requests, and if the weight of translations is more than the limit of 2MB — they will not be cached."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Implementation"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 16,
                    "byteStart": 0
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#bold"
                    }
                  ]
                }
              ],
              "plaintext": "Goals and tasks:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.unorderedList",
              "children": [
                {
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "The library must have full functionality in both client and server components;"
                  }
                },
                {
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Usage should be simple (without extra props);"
                  }
                },
                {
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "All complex logic should be moved to the server;"
                  }
                },
                {
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Translations should be loaded only as necessary and without additional requests;"
                  }
                },
                {
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Support for updating translations without rebuilding (ISR/SSR);"
                  }
                },
                {
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Everything should work on a static site (not with switching to SSR);"
                  }
                },
                {
                  "content": {
                    "$type": "pub.leaflet.blocks.text",
                    "plaintext": "Html entities should be supported."
                  }
                }
              ]
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "To my surprise, there is not a single library that would satisfy all these requirements."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 110,
                    "byteStart": 109
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 124,
                    "byteStart": 119
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "The first thing you need is functionality. In the standard version, this is a hook that returns the function t and the Trans component for more complex translations. However, such functionality is needed in server components, and they have many of their own features."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Functionality"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "The main functionality is divided into two versions — for client components and for server ones and includes:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 14,
                    "byteStart": 0
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 30,
                    "byteStart": 16
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "useTranslation, getTranslation - which return the function t inside the DOM and the language;"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "javascript",
              "plaintext": "import getTranslation from 'next-translation/getTranslation'\n\nexport default function ServerComponent() {\n  const { t } = getTranslation()\n\n  return (\n    <p>{t('intro.title')}</p>\n  )\n}"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "javascript",
              "plaintext": "'use client';\n\nimport useTranslation from 'next-translation/useTranslation'\n\nexport default function ClientComponent() {\n  const { t } = useTranslation()\n\n  return (\n    <p>{t('intro.title')}</p>\n  )\n}"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 78,
                    "byteStart": 69
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 88,
                    "byteStart": 83
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "The interface turned out to be quite familiar, the functions support namespace and query. It is recommended to use it by default, as it is simple to use and in logic. Returns a ready-made string."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 67,
                    "byteStart": 50
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 89,
                    "byteStart": 72
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "For more complex translations, you should use the ClientTranslation and ServerTranslation components. They can replace pseudo-components with real ones."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "javascript",
              "plaintext": "import ServerTranslation from 'next-tranlation/ServerTranslation';\n\nexport default function ServerComponent() {\n  return(\n    <ServerTranslation\n      term='intro.description'\n      components={{\n        link: <a href='#' />\n      }}\n    />\n  )\n}"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "javascript",
              "plaintext": "\"use client\";\n\nimport ClientTranslation from 'next-tranlation/ClientTranslation';\n\nexport default function ClientTranslation() {\n  return(\n    <ClientTranslation\n      term='intro.description'\n      components={{\n        link: <a href='#' />\n      }}\n    />\n  )\n}"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 119,
                    "byteStart": 102
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "There are also cases when translations need to be added outside the react tree. For this, you can use createTranslation anywhere."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "javascript",
              "plaintext": "import createTranslation from 'next-translation/createTranslation'\n// ...\nexport async function generateMetadata({ params }: { params: { lang: string } }) {\n  const { t } = await createTranslation(params.lang);\n\n  return {\n    title: t('homePage.meta.title'),\n  }\n}"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Page setup"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 185,
                    "byteStart": 172
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 241,
                    "byteStart": 224
                  },
                  "features": [
                    {
                      "uri": "https://github.com/vordgi/next-impl-getters",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 271,
                    "byteStart": 252
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 292,
                    "byteStart": 276
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Now about setting up the page. To work with translations, you need to know the language. However, in server components, you cannot use context. For this, an alternative to createContext was made for server components in the next-impl-getters package - createServerContext and getServerContext."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 69,
                    "byteStart": 46
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "In the package for this, you need to create a NextTranslationProvider. It is recommended to do this at the page level to avoid problems with Layout re-rendering."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "javascript",
              "plaintext": "import NextTranlationProvider from 'next-translation/NextTranlationProvider'\n\nexport default function HomePage({ params }: { params: { lang: string } }) {\n  return (\n    <NextTranlationProvider lang={params.lang} clientTerms={['shared', 'banking.about']}>\n      {/* ... */}\n    </NextTranlationProvider>\n  )\n}"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 204,
                    "byteStart": 181
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 226,
                    "byteStart": 215
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "It is also necessary to indicate which translations are needed specifically on the client and to pass only them there. To do this, you can pass an array of client keys or groups to NextTranslationProvider using the clientTerms prop."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 270,
                    "byteStart": 244
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Also, sometimes situations arise when a component needs different translations or different blocks are rendered depending on conditions. In such cases, different translations need to be passed to the client. Condition options can be wrapped in NextTranslationTransmitter and client terms are passed to it."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "javascript",
              "plaintext": "import NextTranslationTransmitter from 'next-tranlation/NextTranslationTransmitter';\nimport ClientComponent from './ClientComponent';\n\nconst ServerComponent: React.FC = () => (\n  <NextTranslationTransmitter terms={['header.nav']}>\n    <ClientComponent />\n  </NextTranslationTransmitter>\n)"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 82,
                    "byteStart": 59
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 112,
                    "byteStart": 86
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "As a result, only those terms that were specified above in NextTranslationProvider or NextTranslationTransmitter will be passed to client components."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Package setup"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 173,
                    "byteStart": 169
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 248,
                    "byteStart": 239
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 285,
                    "byteStart": 281
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Before working with translations, they need to be loaded. For this, you need to create a configuration file in the root of the project. Its minimum configuration is the load function, which will return current translations and an array of languages with permissible languages. The load function is called in server components, and the necessary keys will be passed to the client."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "A very important point was the absence of unnecessary requests, that is, full caching is needed."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Here it is worth digressing a little. Starting from the latest version, Next.js builds the application in parallel in several processes. If each process lived with its own cache — requests would be sent from each. Probably, to avoid this, the Next.js team redesigned fetch — now it works with a common cache."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 175,
                    "byteStart": 156
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 193,
                    "byteStart": 179
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "The package solves the problem in the same way — it creates a common cache and works from each process already with it. For this to work, you need to use withNextTranslation in next.config.js."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "plaintext": "Conclusion"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "The solution turned out to be truly tailored to next.js — taking into account all its capabilities and problems. It also includes all the optimization capabilities provided by server components. The package is fully optimized for next.js, their concepts and views, which I fully share."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "I faced the problem of translations and I had to make my own solution, which would work exactly as expected."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "plaintext": "Despite the significant advantage in optimizations, the package is still inferior to large libraries in terms of translation capabilities. There is a lot of work ahead."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 128,
                    "byteStart": 0
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#italic"
                    }
                  ]
                }
              ],
              "plaintext": "P.S. I will be grateful if you describe what you lacked in existing solutions or what functionality you consider most important."
            }
          }
        ]
      }
    ]
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreihefewoxfiip6ysroyns5pl2k4mfnea5jos2uxohphq3nqn5oqpgi"
    },
    "mimeType": "image/jpeg",
    "size": 147338
  },
  "description": "There are dozens of amazing libraries made for internationalization, such as i18n, react-intl, next-intl. They all do an excellent job of adding translations to an application or website. Most of them are tested, debugged, and consistently supported.",
  "publishedAt": "2024-02-13T20:45:00.000Z",
  "textContent": "There are dozens of amazing libraries made for internationalization, such as i18n, react-intl, next-intl. They all do an excellent job of adding translations to an application or website. Most of them are tested, debugged, and consistently supported.\n\nBut they are all outdated.\n\nAfter all, during this time, the react ecosystem has been developing. The latest version of next.js has major updates from react.js — cache, taint, new hooks, and, of course, server components. The React.js team will likely introduce these changes in May.\n\nIn this article, I will talk about key changes, personal experience, problems with existing solutions, necessary updates, solutions I came up with, and, of course, answer the questions of “why” and most importantly — “why”?\n\nChanges\n\nThe first thing to start with is how the changes in React.js made translation libraries obsolete.\n\nDespite the fact that the latest stable version of React.js was released almost two years ago, it has 2 other channels — canary and experimental, where canary is also considered a stable channel and is recommended for use by libraries.\n\nThis is the channel that Next.js uses. Next.js launched server components without additional flags inside the so-called App Router — a new directory as an alternative to pages, which uses its conventions and different sugar (about changes and problems which I wrote in a recent articlearticle).\n\nServer components definitely solve a number of problems and are a new milestone for optimizations. Including for translations. Without server components, translations were stored both in the compiled HTML and as a large object in the client script. Now you can get ready-made HTML, which doesn’t need anything on the client.\n\nNext.js paid special attention to this feature.\n\nPersonal experience\n\nYou can add translations (according to the Next.js documentationNext.js documentation) as follows:\n\n// app/[lang]/dictionaries.js\nimport 'server-only'\n\nconst dictionaries = {\n  en: () => import('./dictionaries/en.json').then((module) => module.default),\n  nl: () => import('./dictionaries/nl.json').then((module) => module.default),\n}\nexport const getDictionary = async (locale) => dictionaries[locale]()\n\n// app/[lang]/page.js\nimport { getDictionary } from './dictionaries'\n\nexport default async function Page({ params: { lang } }) {\n  const dict = await getDictionary(lang) // en\n  return <button>{dict.products.cart}</button> // Add to Cart\n}\n\nThis solution is described as ready and fully optimized. It works entirely on the server, and the client already receives ready-made HTML. However, the Next.js team missed one important detail — how to pass the language deep into server components.\n\nA big problem with server components is that contexts are not available in them. The Next.js team explains the absence of these functions by the fact that Layout is not re-rendered, and everything that depends on props should be client-side.\n\nPerhaps translation libraries were most affected by this. As a temporary solution, they suggest determining the language in the middleware and adding it to cookies. Then when building the page, read it in the necessary places. But reading cookies means enabling server rendering, which is not suitable for everyone.\n\nIn general, the main problem with existing solutions is that most of them are not made for server components. Components and functions were developed for runtime, using hooks and synchronicity.\n\nAnother inconvenience was caching in Next.js. Namely — it works fully only for GET requests, and if the weight of translations is more than the limit of 2MB — they will not be cached.\n\nImplementation\n\nGoals and tasks:\n\nTo my surprise, there is not a single library that would satisfy all these requirements.\n\nThe first thing you need is functionality. In the standard version, this is a hook that returns the function t and the Trans component for more complex translations. However, such functionality is needed in server components, and they have many of their own features.\n\nFunctionality\n\nThe main functionality is divided into two versions — for client components and for server ones and includes:\n\nuseTranslation, getTranslation - which return the function t inside the DOM and the language;\n\nimport getTranslation from 'next-translation/getTranslation'\n\nexport default function ServerComponent() {\n  const { t } = getTranslation()\n\n  return (\n    <p>{t('intro.title')}</p>\n  )\n}\n\n'use client';\n\nimport useTranslation from 'next-translation/useTranslation'\n\nexport default function ClientComponent() {\n  const { t } = useTranslation()\n\n  return (\n    <p>{t('intro.title')}</p>\n  )\n}\n\nThe interface turned out to be quite familiar, the functions support namespace and query. It is recommended to use it by default, as it is simple to use and in logic. Returns a ready-made string.\n\nFor more complex translations, you should use the ClientTranslation and ServerTranslation components. They can replace pseudo-components with real ones.\n\nimport ServerTranslation from 'next-tranlation/ServerTranslation';\n\nexport default function ServerComponent() {\n  return(\n    <ServerTranslation\n      term='intro.description'\n      components={{\n        link: <a href='#' />\n      }}\n    />\n  )\n}\n\n\"use client\";\n\nimport ClientTranslation from 'next-tranlation/ClientTranslation';\n\nexport default function ClientTranslation() {\n  return(\n    <ClientTranslation\n      term='intro.description'\n      components={{\n        link: <a href='#' />\n      }}\n    />\n  )\n}\n\nThere are also cases when translations need to be added outside the react tree. For this, you can use createTranslation anywhere.\n\nimport createTranslation from 'next-translation/createTranslation'\n// ...\nexport async function generateMetadata({ params }: { params: { lang: string } }) {\n  const { t } = await createTranslation(params.lang);\n\n  return {\n    title: t('homePage.meta.title'),\n  }\n}\n\nPage setup\n\nNow about setting up the page. To work with translations, you need to know the language. However, in server components, you cannot use context. For this, an alternative to createContext was made for server components in the next-impl-getters package - createServerContext and getServerContext.\n\nIn the package for this, you need to create a NextTranslationProvider. It is recommended to do this at the page level to avoid problems with Layout re-rendering.\n\nimport NextTranlationProvider from 'next-translation/NextTranlationProvider'\n\nexport default function HomePage({ params }: { params: { lang: string } }) {\n  return (\n    <NextTranlationProvider lang={params.lang} clientTerms={['shared', 'banking.about']}>\n      {/* ... */}\n    </NextTranlationProvider>\n  )\n}\n\nIt is also necessary to indicate which translations are needed specifically on the client and to pass only them there. To do this, you can pass an array of client keys or groups to NextTranslationProvider using the clientTerms prop.\n\nAlso, sometimes situations arise when a component needs different translations or different blocks are rendered depending on conditions. In such cases, different translations need to be passed to the client. Condition options can be wrapped in NextTranslationTransmitter and client terms are passed to it.\n\nimport NextTranslationTransmitter from 'next-tranlation/NextTranslationTransmitter';\nimport ClientComponent from './ClientComponent';\n\nconst ServerComponent: React.FC = () => (\n  <NextTranslationTransmitter terms={['header.nav']}>\n    <ClientComponent />\n  </NextTranslationTransmitter>\n)\n\nAs a result, only those terms that were specified above in NextTranslationProvider or NextTranslationTransmitter will be passed to client components.\n\nPackage setup\n\nBefore working with translations, they need to be loaded. For this, you need to create a configuration file in the root of the project. Its minimum configuration is the load function, which will return current translations and an array of languages with permissible languages. The load function is called in server components, and the necessary keys will be passed to the client.\n\nA very important point was the absence of unnecessary requests, that is, full caching is needed.\n\nHere it is worth digressing a little. Starting from the latest version, Next.js builds the application in parallel in several processes. If each process lived with its own cache — requests would be sent from each. Probably, to avoid this, the Next.js team redesigned fetch — now it works with a common cache.\n\nThe package solves the problem in the same way — it creates a common cache and works from each process already with it. For this to work, you need to use withNextTranslation in next.config.js.\n\nConclusion\n\nThe solution turned out to be truly tailored to next.js — taking into account all its capabilities and problems. It also includes all the optimization capabilities provided by server components. The package is fully optimized for next.js, their concepts and views, which I fully share.\n\nI faced the problem of translations and I had to make my own solution, which would work exactly as expected.\n\nDespite the significant advantage in optimizations, the package is still inferior to large libraries in terms of translation capabilities. There is a lot of work ahead.\n\nP.S. I will be grateful if you describe what you lacked in existing solutions or what functionality you consider most important."
}