The clever hack I used to translate my résumé<br>If you read my 2025 retrospective article, you might remember that<br>I promised that I would explain in details how I handled translations in<br>my upgraded résumé. It is now time that I reveal my magic trick.
#The goal<br>Quick reminder for those who have not read the aforementioned article: my résumé used to be a single, hand-crafted HTML<br>page that was turned into a PDF using Gotenberg. Tired of wrestling with Bootrap’s stylesheet<br>and having to do search-and-replace every time I wanted to change the style and layout, I elected to rewrite it using<br>React Server Components.<br>Most crucially, I also needed to translate it in multiple languages. Traditionally, you would go for the<br>“key-translation” method, which usually relies on JSON files like the one bellow and a function that will return the<br>correct string for a given key.<br>i18n/en.json{<br>"navbar": {<br>"appTitle": "Self-Host Planning Poker",<br>"appIcon": "Application icon",<br>"homeLink": "Homepage",<br>"playerInfo": {<br>"spectator": "Spectator",<br>"actions": {<br>"editPlayerName": "Edit your name"<br>},<br>"gameInfo": {<br>/* [...] */<br>/* [...] */<br>},<br>"gameForm": {<br>"name-label": "Game's name",<br>"name-placeholder": "Backlog Review Team Pizza"<br>/* [...] */<br>/* [...] */<br>}This is the way to go for virtually every project, as these JSON files are the perfect fit for translation tools and<br>services such as Crowdin, Weblate or Smartcat, just to name a few.<br>I must stress that the solution I describe here is not the one you should reach for in 99.9999% of projects.<br>Libraries such as next-intl, react-i18next or<br>Format.js are the way to go when you have to translate hundreds of strings across<br>your application into multiple languages.
But this is not the authoring experience I was looking for, I wanted to write texts for various languages next to each<br>other in my JSX:<br>const Charity = () => {<br>return (<br>section><br>h2><br>French>BénévolatFrench><br>English>Charity WorkEnglish><br>h2><br>section><br>h3><br>French>BénévoleFrench><br>English>VolunteerEnglish><br>— Emmaüs Connect<br>h3><br>p><br>French><br>J’accueille et accompagne le public, réalise la vente de recharges téléphoniques, téléphones,<br>tablettes et ordinateurs, et j'anime des formations sur l’informatique.<br>French><br>English><br>I welcome and assist public, sell phone recharges, phones, tablets and computers, and I conduct<br>training on information technology.<br>English><br>p><br>section><br>section><br>);<br>};And depending on the language I want, to only render the content of the French> or English><br>components. For example, if I wanted to have my résumé rendered in English, the JSX above would render as the following<br>HTML:<br>section><br>h2>Charity Workh2><br>section><br>h3>Volunteer — Emmaüs Connecth3><br>p><br>I welcome and assist public, sell phone recharges, phones, tablets and computers, and I conduct training on<br>information technology.<br>p><br>section><br>section>
#The ideas<br>Rendering a component’s children depending on some prop or hook is the easy part: read the value, compare it to the<br>language you want, and either return its children or do nothing.<br>const French = ({ children }: { children: ReactNode }) => {<br>const currentLanguage = getLanguageFromSomewhere();<br>if (currentLanguage === "fr") {<br>return children;<br>};The true challenge here is how to get the current language from the root of my React tree all the way down to each<br>component. Prop drilling would be impractical, so the obvious solution<br>would be to store the current language in a good old<br>Context.<br>But do I really want to? Using a Context makes sense when its values are dynamic, but in my case the language would be<br>set once, when the request starts. In addition, using a Context means that my JavaScript bundle would be needlesly<br>bloated by Client Components. This last point does matter since my résumé is also displayed on this website.<br>As I alluded to in my earlier article, the solution comes from a clever use of React’s<br>cache(), a function originally designed to cache values for the entire<br>duration of a single request. Daniel Saewitz already<br>diverted it to share state across Server Components, and my<br>solution is not too dissimilar, as I simply created a read-only and server-only Context:<br>LanguageCache.tsximport { cache, PropsWithChildren } from "react";
export type Language = "fr" | "en";<br>type LanguageCacheProps = PropsWithChildren<br>lang: Language;<br>}>;
// This is where the current language will be stored,<br>// with a fallback to English if not set<br>const langCache = cache(() => ({ lang: "en" as Language }));
export const getLanguage = (): ReadonlyLanguage> => langCache().lang;
// The component that acts as our Context<br>export const LanguageCache = ({ lang, children }: LanguageCacheProps) => {<br>// We update the value stored in our cache()<br>langCache().lang = lang;<br>return children;<br>};
const LangTag = ({ lang, children }: LanguageCacheProps) => {<br>// We read the value from our cache()<br>const currentLang = getLanguage();<br>// We compare the two values and act accordingly<br>if (lang === currentLang) {<br>return children;<br>};
export...