A minimal dependency-free translation system for Next.js
TLDR:
Introduction
I’ve been doing some web speed performance checks on the Next.js websites we are building at my current company lately, and one of the things that I noticed is that the translation system we are using is adding a lot of weight to the bundle size. I decided to create a minimal translation system that doesn’t add any dependencies to the bundle size. Of course this is no shade to the existing translation systems out there, but I wanted to see if I could create something that is minimal and dependency-free, without all the functionality which systems like next-intl provide.
Setting up the backend
For the purpose of this blog and demo I decided to use POEditor to host my translations. They have a generous free tier which is more than enough for this demo. I created a project, added 2 languages (NL and EN) and added a few translations to it.
Setting up the frontend
Getting started with a new Next.js app is easy, just run npx create-next-app
, follow the steps in your terminal and you’re good to go.
For this demo and blog I’m using the Pages Router, I might do another blog post on doing the same in the App Router later, but this might work a bit differently since React Context can not be used in RSC.
In next.config.js
I added
to add an extra language (Dutch) to the project.
Setting up the translation fetching from POEditor
Using the Fetch API, I’m fetching the translations for my project from POEditor in the specified language. I’m creating an object with the translations, where the key is the translation key and the value is the translation itself.
Setting up the translation context
Here I’m creating a React Context to store the translations.
Setting up the translation provider
In the _app.tsx
file (the entrypoint of the Next.js app) I’m importing my DictionaryContext
, and using it’s Provider
to provide the translations to all the pages in my app by wrapping everything in the render function in the Provider
.
The DictionaryContext.Provider
takes a value
prop, which should be the translations coming from POEditor. I fill the value with pageProps.dictionaryItems
, which will be provided by the getStaticProps
(or getServersideProps
) function in the pages.
Setting up the translation hook
For my custom translation hook I’m using the useContext
hook to get the translations from the DictionaryContext
and return the translation for the given key. I also added a second parameter to the function, which is an object containing variables which can be used in the translation. The translation should contain the variable name between double curly braces, and the variable will be replaced with the value passed to the translation function.
Setting up the translation fetching in the pages
In my demo I’m using getStaticProps
to avoid too many fetches to my backend, but you could also use getServerSideProps
if you want to fetch the translations on every request.
In the getStaticProps
function I’m fetching the translations for the current locale, and returning them in the dictionaryItems
prop.
I also chose to add the revalidate: 300
option to make sure the translations are only refetched every 5 minutes.
Inside my React components I can now simply use the useTranslation hook to get the translation function, and then use the function to translate the given key. In the Cta
component I’m also using the second parameter of the translation function to pass a variable to the translation. The translation value in POEditor looks like this: Cta description with count_variable: {{count_variable}}
.
Conclusion
This is a very basic translation system, but it works well for my use case, and has been enough for the projects at work. I hope it’s helpful for you as well, and if you have any questions or feedback, feel free to leave a comment below. Links to source code and live demo can be found at the top of this blog post.