-
Notifications
You must be signed in to change notification settings - Fork 390
/
Copy pathI18nProvider.tsx
91 lines (79 loc) · 2.84 KB
/
I18nProvider.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import React, { ComponentType, FunctionComponent } from "react"
import { I18n } from "@lingui/core"
import { TransRenderProps } from "./Trans"
export type I18nContext = {
i18n: I18n
defaultComponent?: ComponentType<TransRenderProps>
}
export type I18nProviderProps = I18nContext & {
children?: React.ReactNode
}
export const LinguiContext = React.createContext<I18nContext>(null)
export function useLingui(): I18nContext {
const context = React.useContext<I18nContext>(LinguiContext)
if (process.env.NODE_ENV !== "production") {
if (context == null) {
throw new Error("useLingui hook was used without I18nProvider.")
}
}
return context
}
export const I18nProvider: FunctionComponent<I18nProviderProps> = ({
i18n,
defaultComponent,
children,
}) => {
const latestKnownLocale = React.useRef<string | undefined>(i18n.locale)
/**
* We can't pass `i18n` object directly through context, because even when locale
* or messages are changed, i18n object is still the same. Context provider compares
* reference identity and suggested workaround is to create a wrapper object every time
* we need to trigger re-render. See https://reactjs.org/docs/context.html#caveats.
*
* Due to this effect we also pass `defaultComponent` in the same context, instead
* of creating a separate Provider/Consumer pair.
*
* We can't use useMemo hook either, because we want to recalculate value manually.
*/
const makeContext = React.useCallback(
() => ({
i18n,
defaultComponent,
}),
[i18n, defaultComponent]
)
const [context, setContext] = React.useState<I18nContext>(makeContext())
/**
* Subscribe for locale/message changes
*
* I18n object from `@lingui/core` is the single source of truth for all i18n related
* data (active locale, catalogs). When new messages are loaded or locale is changed
* we need to trigger re-rendering of LinguiContext.Consumers.
*/
React.useEffect(() => {
const updateContext = () => {
latestKnownLocale.current = i18n.locale
setContext(makeContext())
}
const unsubscribe = i18n.on("change", updateContext)
/**
* unlikely, but if the locale changes before the onChange listener
* was added, we need to trigger a rerender
* */
if (latestKnownLocale.current !== i18n.locale) {
updateContext()
}
return unsubscribe
}, [makeContext])
if (!latestKnownLocale.current) {
process.env.NODE_ENV === "development" &&
console.log(
"I18nProvider rendered `null`. A call to `i18n.activate` needs to happen in order for translations to be activated and for the I18nProvider to render." +
"This is not an error but an informational message logged only in development."
)
return null
}
return (
<LinguiContext.Provider value={context}>{children}</LinguiContext.Provider>
)
}