Last active
May 11, 2021 00:11
-
-
Save sergiodxa/40019e2e774481d56fdbe22edaaa79cc to your computer and use it in GitHub Desktop.
A custom Hook built on top of React Query to use translations from a backend
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ms from "ms"; | |
import * as React from "react"; | |
import { useQuery } from "react-query"; | |
import { hasOwn } from "../utils"; | |
let isProduction = process.env.NODE_ENV === "production"; | |
export interface Translations { | |
[key: string]: string; | |
} | |
export class MissingTranslationError extends Error { | |
constructor(public key: string, public translations: Translations) { | |
super(`The key "${key}" was not found on the translations.`); | |
} | |
} | |
export class InvalidTranslationKeyError extends Error { | |
constructor(public key: string, public translations: Translations) { | |
super( | |
`The key "${key}" returned a translations object instead of a message.` | |
); | |
} | |
} | |
export async function fetchTranslations() { | |
let headers = new Headers(); | |
headers.set("Accept", "application/json"); | |
let response = await fetch("/frontend/i18n", { headers }); | |
return await response.json(); | |
} | |
export function useTranslations() { | |
let query = useQuery<Translations, void>("translations", fetchTranslations, { | |
suspense: true, | |
cacheTime: ms("30 minutes"), | |
staleTime: ms("1 hour"), | |
}); | |
let translations = query.data!; | |
let translate = React.useCallback( | |
function translate( | |
message: string, | |
variables: { [key: string]: string | number } = {} | |
): string { | |
// In a production environment, if a translation is missing we want to use the default message as translation | |
if (!hasOwn(translations, message) && !isProduction) { | |
throw new MissingTranslationError(message, translations); | |
} | |
let match = (translations[message] ?? message).trim(); | |
if (match === "") { | |
throw new MissingTranslationError(message, translations); | |
} | |
return Object.entries(variables).reduce((message, [key, value]) => { | |
return message.replace(new RegExp(`%{${key}}`, "g"), value.toString()); | |
}, match); | |
}, | |
[translations] | |
); | |
let tag = React.useCallback( | |
(strings: string[], ...variables: React.ReactNode[]) => { | |
let message = translate(strings.join("{}")).split("{}"); | |
if (variables.length === 0) return message; | |
return ( | |
<> | |
{message.map((fragment, index) => ( | |
<React.Fragment key={fragment}> | |
{fragment} {variables[index]} | |
</React.Fragment> | |
))} | |
</> | |
); | |
}, | |
[translate] | |
); | |
return [tag] as const; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment