Skip to content

Instantly share code, notes, and snippets.

@sergiodxa
Last active May 11, 2021 00:11
Show Gist options
  • Save sergiodxa/40019e2e774481d56fdbe22edaaa79cc to your computer and use it in GitHub Desktop.
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
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