Skip to content

Instantly share code, notes, and snippets.

@branneman
Last active December 7, 2023 11:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save branneman/6e8bebdc73d93ee4b442bb391efdf167 to your computer and use it in GitHub Desktop.
Save branneman/6e8bebdc73d93ee4b442bb391efdf167 to your computer and use it in GitHub Desktop.
React useTranslation Hook
import { useState } from 'react'
import { Outlet } from 'react-router-dom' // you don't have to use this necessarily, just an example
import Header from 'components/Header'
import { makeTranslationValue } from 'hooks/translation'
import { TranslationContext } from 'context/translation'
import translations from 'data/translations.json'
export default function App() {
const [language, setLanguage] = useState('de')
const translationContextValue = makeTranslationValue(
translations,
language,
setLanguage,
)
return (
<TranslationContext.Provider value={translationContextValue}>
<Header />
<Outlet />
</TranslationContext.Provider>
)
}
import { useTranslation } from 'hooks/translation'
export default function Header() {
// Most of your components will only need the `t()` function,
// but this component has the language toggle, so it needs `language` + `setLanguage()` as well
const { t, language, setLanguage } = useTranslation()
// It supports as many languages as you want, but this example only shows 2
const otherLanguage = language === 'de' ? 'en' : 'de'
return (
<header>
<h1>Language Switcher</h1>
<p onClick={() => setLanguage(otherLanguage)}>
{t('switch-language')}
</p>
</header>
)
}
import { createContext } from 'react'
export const TranslationContext = createContext()
{
"defaultLanguage": "de",
"de": {
"switch-language": "Switch to English",
"play-the-game": "Schpielen"
},
"en": {
"switch-language": "Auf Deutsch umstellen",
"play-the-game": "Play",
}
}
import { describe, it, expect } from 'vitest' // Or whatever test framework you are using
import { getObjectPropertyByPathSpecifier } from 'hooks/translation'
describe('getObjectPropertyByPathSpecifier()', () => {
it('can access a level 1 property', () => {
const obj = { abc: 'def' }
const key = 'abc'
const res = getObjectPropertyByPathSpecifier(key, obj)
expect(res).toEqual('def')
})
it('can access a level 2 property', () => {
const obj = { abc: { def: 'ghi' } }
const key = 'abc.def'
const res = getObjectPropertyByPathSpecifier(key, obj)
expect(res).toEqual('ghi')
})
it('can access a level 7 property', () => {
const obj = {
a: { b: { c: { d: { e: { f: { g: 'leet' } } } } } },
}
const key = 'a.b.c.d.e.f.g'
const res = getObjectPropertyByPathSpecifier(key, obj)
expect(res).toEqual('leet')
})
it('returns undefined when key does not exist', () => {
const obj = { abc: { def: 'ghi' } }
const key = 'uvw.xyz'
const res = getObjectPropertyByPathSpecifier(key, obj)
expect(res).toEqual(undefined)
expect(typeof res).toEqual('undefined')
})
})
import { useContext } from 'react'
import { TranslationContext } from 'context/translation'
export function useTranslation() {
return useContext(TranslationContext)
}
export const makeTranslationValue = (
translations,
language,
setLanguage,
) => {
return {
t: makeTranslateFunction(translations, language),
language,
setLanguage,
}
}
export function makeTranslateFunction(
translations,
language,
) {
return (key) => {
const result = getObjectPropertyByPathSpecifier(
key,
translations[language],
)
if (result === undefined) {
throw new Error(
`Missing translation key "${key}" for language "${language}"`,
)
}
return result
}
}
export function getObjectPropertyByPathSpecifier(key, obj) {
const parts = key.split('.')
const iterator = (acc, curr) => {
if (typeof acc === 'string') return acc
return acc && acc[curr]
}
return parts.reduce(iterator, obj)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment