Last active
December 7, 2023 11:11
-
-
Save branneman/6e8bebdc73d93ee4b442bb391efdf167 to your computer and use it in GitHub Desktop.
React useTranslation Hook
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 { 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> | |
) | |
} |
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 { 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> | |
) | |
} |
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 { createContext } from 'react' | |
export const TranslationContext = createContext() |
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
{ | |
"defaultLanguage": "de", | |
"de": { | |
"switch-language": "Switch to English", | |
"play-the-game": "Schpielen" | |
}, | |
"en": { | |
"switch-language": "Auf Deutsch umstellen", | |
"play-the-game": "Play", | |
} | |
} |
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 { 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') | |
}) | |
}) |
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 { 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