react-i18Next: Using an enum for translation key values - reactjs

We are using i18Next in our React project and I wonder if there is way to use an enum for keys of the translation file to avoid typos while using it like this:
export enum AppLocaleKey {
test = 'test'
}
...
import translationEN from './locales/en/translation';
const resources = {
en: { translation: translationEN },
...
};
i18n
.use(initReactI18next)
.init({
resources,
...
})
...
const translation = {
[AppLocaleKey.test]: 'Test...',
};
export default translation;
...
import { AppLocaleKey } from './locales/localeKeys';
import { useTranslation } from 'react-i18next';
const App = (props: Props) => {
const { t, i18n } = useTranslation();
return (
<>
<p>{t(AppLocaleKey.test)}</p>
<>
)
}
But this didn't work. Do you know any similar method?

If you are using TS you can with ts4.1 declare all the keys of the json as a valid inputs.
Check out the official example,
And working example out of it.

I believe this is what you are looking for
export enum VisitorType {
SCHOOL_VS,
SCHOOL_NOT_VS,
GROUP,
PRIVATE
}
...
resources: {
en: {
translation: {
visitorType: {
[VisitorType.SCHOOL_VS]: 'State school',
[VisitorType.SCHOOL_NOT_VS]: 'Non state school',
[VisitorType.GROUP]: 'Private groups',
[VisitorType.PRIVATE]: 'Private'
},
}
},
...
t(`visitorType.${VisitorType.SCHOOL_VS}`)

Related

Get i18n locale as global variable in all pages in Next.js

I want to get the locale as a global variable in any component of my app without using any other external libraries.
I have json files for translations en.js and es.js:
es.js
export default {
home: 'Inicio',
signin: 'Iniciar Sesion',
welcome: 'Bienvenido',
};
I have already set next.config.js:
i18n: {
locales: ['en', 'es'],
defaultLocale: 'es',
},
And I can get the locale in any component by router.locale:
home.js
import en from "../lib/i18n/en";
import es from "../lib/i18n/es";
const HomePage = () => {
const router = useRouter()
const { locale } = router;
const t = locale === 'en' ? en : es;
return (
<div>t.home</div>
)
}
All good, the question is, how can I replicate this in all the components without having to call the router all the time and replicating the logic to get t. A solution that would allow me something like this:
home.js
import t from "../lib/i18n/t";
const HomePage = () => {
return (
<div>t.home</div>
)
}
Making t a global variable or that all the components have the context to use it.
I am doing it generally different:
//localized.js
import { useRouter } from 'next/router';
export const words = {
login: {
de: 'Login',
en: 'Login',
},
register: {
de: 'Register',
en: 'Registrieren',
},
includeUpdate: {
de: 'Update Link anhängen.',
en: 'Include update link.',
},
// ......
}
export default function t(text) {
const { locale } = useRouter();
return text?.[locale] || text;
}
Nor you can use it like so:
import {words, t} from 'path/to/localized.js'
cont Test = () => {
return (
<div>{t(words.login)}</div>
<div>{t({de: "Ein anderer Text.", en: "Some other text."})}</div>
)
}

get sub types in graphql query using #graphql-codegen/cli

I have a below query
export const GET_ALL_ADVERT_CUSTOM_FIELD = gql(`
query advertCustomFields {
advertCustomFields {
nodes {
slug
valueType
displayName
description
canRead
}
}
}
`)
And I would like to get the list filtered of nodes like this
import { Props as SelectProps } from 'react-select'
import React, { FC, useState } from 'react'
import ObjectSelector from 'components/Common/ObjectSelector'
import OptionWithDescription from './OptionWithDescription'
import { useQuery } from '#apollo/client'
import { AdvertCustomFieldsDocument } from '__generated__/graphql'
export const AdvertCustomFieldSelector: FC<SelectProps> = (props) => {
const [data, setData] = useState<NodeType[]>()
useQuery(AdvertCustomFieldsDocument, {
onCompleted: (res) => {
const filterData = res.advertCustomFields?.nodes?.filter((e) => e.canRead)
setData(filterData)
},
})
return (
<ObjectSelector<Node>
name={props.name}
onChange={props.onChange}
options={data as any}
getOptionLabel={(option) => option?.displayName as string}
getOptionValue={(option) => `${option.slug}`}
components={{ Option: OptionWithDescription }}
/>
)
}
The thing is #graphql-codegen/cli does not export type for the NodeType.
This is my codegen config
import { CodegenConfig } from '#graphql-codegen/cli'
const config: CodegenConfig = {
schema: './app/graphql/schema.graphql',
documents: ['./facerberus/components/**/*.ts'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./facerberus/__generated__/': {
preset: 'client',
plugins: [],
presetConfig: {
gqlTagName: 'gql',
},
},
},
}
export default config
which config to make codegen export type of NodeType or how to achieve it via ts
Codegen doesn't generate a standalone type for every part of the operaation.
But you can easily extract the needed part from the operation type. Should be something like this:
type NodeType = AdvertCustomFieldsQuery['advertCustomFields']['nodes'][number]

Styled components with React context

I want to use normal React context with styled-components. I am aware of generic theme providers in styled-components, however, it will be much better if I will be able to do this by using React.useContext(). Do you know is it possible? I tried something like this.
useConfig
export function useConfig() {
const configCtx = useContext<ConfigContextShape | undefined>(ConfigContext)
return configCtx
}
example styled.ts
import { useConfig } from 'state'
import styled from 'styled-components'
const { background_color } = useConfig()
export const VisualSearchWrapper = styled.main`
color: black;
background-color: ${background_color};
`
I will be appreciated for any help.
One more update:
In the example this issue occurred:
Uncaught TypeError: Cannot read properties of undefined (reading 'context')
The ConfigContextProvider is below
import { createContext } from 'preact'
import { FC } from 'preact/compat'
import { useContext, useEffect, useState } from 'preact/hooks'
export interface ConfigProfile {
background_color: string
}
interface ConfigFunctions {
updateConfigState: (updatedConfig: ConfigProfile) => void
}
type ConfigContextShape = ConfigProfile & ConfigFunctions
export const ConfigContext = createContext<ConfigContextShape | undefined>(undefined)
export const ConfigContextProvider: FC = ({ children }) => {
const [localState, setLocalState] = useState<ConfigProfile>({
background_color: '',
})
function updateConfigState(updatedConfig: ConfigProfile) {
setLocalState({ ...localState, ...updatedConfig })
}
const ctxValue: ConfigContextShape = {
background_color: localState?.background_color ?? '',
updateConfigState
}
return <ConfigContext.Provider value={ctxValue}>{children}</ConfigContext.Provider>
}
export function useConfig() {
const configCtx = useContext<ConfigContextShape>(ConfigContext)
if (configCtx === undefined) {
throw new Error('useConfig must be used within a ConfigProvider')
}
return configCtx
}

react-i18next: strings not being translated

I had a frustrating problem with the react-i18next library. I just couldn't get the library to translate the strings in my app.
The code was as follows:
App.tsx:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import resources from './resources';
// code omitted...
i18n
.use(initReactI18next)
.init({
resources,
lng: 'en',
interpolation: {
escapeValue: false,
},
});
// rest of the code here...
resources/index.tsx:
export default {
en: {
'Math Visualized': 'Math Visualized asd',
},
fi: {
'Math Visualized': 'Matematiikan visualisointia',
},
};
components/header/Header.tsx:
import { withTranslation } from 'react-i18next';
// code omitted...
class HeaderComponent extends React.Component<Props, State> {
render() {
const { headerText, subheaderText, t } = this.props;
// react-bootstrap used here
return (
<Navbar bg="dark" variant="dark">
<Navbar.Brand>{t(headerText)}</Navbar.Brand>
{subheaderText && <Navbar.Text>{t(subheaderText)}</Navbar.Text>}
</Navbar>
);
}
}
export const Header = withTranslation()(HeaderComponent);
The header and subheader text strings simply refused to be translated.
I had simply forgotten to add the translation namespace to the resources file. I modified it like this:
export default {
en: {
translation: { // THIS NAMESPACE HERE WAS MISSING
'Math Visualized': 'Math Visualized asd',
},
},
fi: {
translation: {
'Math Visualized': 'Matematiikan visualisointia',
},
},
};
And everything worked.

How to use i18next / react-i18next inside MDX / Markdown files with MDXJS?

We use the MDXJS package to write content in Markdown and use React components in it.
Is there a way of using the i18next / react-i18next package inside the MDX / Markdown files?
🌍 Using i18next inside MDX:
When you import an MDX file, you just use it as any other React component:
import { default as SomeContent } from './some-content.mdx';
...
<SomeContent />
Therefore, you can also pass down some props, in this case the t function, and use it inside in some specific ways:
import { default as SomeContent } from './some-content.mdx';
export const SomeComponent: React.FC = React.memo((props) => {
const { t } = useTranslation();
return (
<SomeContent t={ t } someProp="Some value" />
);
});
If you want to check if this is working or see which props are accessible from within your MDX file, add this inside it:
<pre>{ JSON.stringify(props, null, ' ') }</pre>
<pre>{ typeof props.t }</pre>
For the example above, it will display:
{"someProp":"Some value"}
function
Note that you can't use these props inside "raw" MD elements, even if you add a wrapper around them:
### Doesn't work: { props.t('some.translation') }
Doesn't work: { props.t('some.translation') }.
Doesn't work: <>{ props.t('some.translation') }</>.
Doesn't work: <Fragment>{ props.t('some.translation') }</Fragment>.
Doesn't work: <span>{ props.t('some.translation') }</span>.
So you would have to write HTML tags instead:
<h3>Works: { props.t('some.translation') }</h3>
<p>Works: { props.t('some.translation') }.</p>
<p>Works: <>{ props.t('some.translation') }</>.</p>
<p>Works: <Fragment>{ props.t('some.translation') }</Fragment>.</p>
<p>Works: <span>{ props.t('some.translation') }</span>.</p>
🧩 Using MDX in i18next:
If you set returnObjects: true in your i18next config, you can also add MDX components inside your translation files:
import { default as ContentEN } from './content.en.mdx';
import { default as ContentES } from './content.es.mdx';
i18next.use(initReactI18next).init({
resources: {
en: {
translation: {
content: ContentEN,
},
},
es: {
translation: {
content: ContentES,
},
},
},
returnObjects: true,
}));
And then you would use it like this in any of your components (and yes, you can also pass down t or any other prop, just as before:
export const SomeComponent: React.FC = React.memo((props) => {
const { t } = useTranslation();
const Content = t('content');
return (
<Content t={ t } someProp="Some value" />
);
});
🏃 Using i18next inside #mdx-js/runtime:
If you are using #mdx-js/runtime, then you would pass down your props as scope:
import { default as SomeContent } from './some-content.mdx';
export const SomeComponent: React.FC = React.memo((props) => {
const { t } = useTranslation();
return (
<MDX components={ ... } scope={ { t, someProp: 'Some value' } }>{ props.mdx }</MDX>
);
});

Resources