useTranslation() doesn't work with react-i18next - reactjs

I try to make react-i18next package working but i'm facing an issue.
I have a provider for i18n initialization :
import React, {useState, useEffect, useRef} from 'react';
import {useLocalization} from '#shopify/hydrogen';
import {I18nextProvider} from 'react-i18next';
import i18n from 'i18next';
import {initReactI18next} from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import resourcesToBackend from 'i18next-resources-to-backend';
// Bundle the default translation
import fr from '../assets/locales/fr/translation.json';
i18n
// load other translations dynamically
.use(
resourcesToBackend((language, namespace, callback) => {
import(`../assets/locales/${language}/${namespace}.json`)
.then((resources) => {
callback(null, resources);
})
.catch((error) => {
callback(error, null);
});
})
)
.use(LanguageDetector)
.use(initReactI18next);
export function TranslationProvider({children}) {
const {language} = useLocalization();
const init = useRef(false);
const [i18nInstance, setI18nInstance] = useState(null);
useEffect(() => {
if (init.current) return;
init.current = true;
const cachedLang = localStorage.getItem('i18nextLng');
const defaultLang = language.isoCode.toLowerCase();
if (!cachedLang) {
localStorage.setItem('i18nextLng', defaultLang);
}
// initialize i18next
i18n.init(
{
partialBundledLanguages: true, // This allows some resources to be set on init while others after
fallbackLng: defaultLang,
debug: true,
react: {useSuspense: true, wait: true},
ns: ['translation'],
defaultNS: 'translation',
interpolation: {
escapeValue: false,
},
resources: {
fr: {translation: fr},
},
},
() => {
setI18nInstance(i18n);
}
);
}, [i18n, setI18nInstance, i18nInstance, language]);
return (
i18nInstance && (
<I18nextProvider i18n={i18nInstance}>{children}</I18nextProvider>
)
);
}
Then I call my Provider like this :
<Suspense fallback={'Loading lang..'}>
<TranslationProvider>
<Router>
<FileRoutes
basePath={languageCode ? `/${languageCode}/` : undefined}
/>
<Route path="*" page={<NotFound />} />
</Router>
</TranslationProvider>
</Suspense>
Finally I use it in a component :
import {useTranslation} from 'react-i18next';
const {t} = useTranslation();
{t('footer.language')}
I have no error but it displays :
footer.language instead of the expected translation
When I inspect t, i get [Function: notReadyT]
Does anyone can help me with this ?
Thank you

Related

react-i18next switching i18n json file based on theme name isn't working

I’m trying to switch language based on two factors 1. Theme Name(MN, NY, ..) and 2.language (en,es) selected. It. Doesn’t seem to work.
Sample Button.json
{
"generic": "Button Text goes here..",
"submit": "Submit Button",
"reset": "Reset",
"cancel": "Cancel"
}
Storybook/Preview.js
import { I18nextProvider } from 'react-i18next';
import { ChakraProvider } from '#chakra-ui/react';
import { stateTheme_GI } from '../src/theme/foundations/colors/mn_theme';
import { stateTheme_GA } from '../src/theme/foundations/colors/ny_theme';
import { stateTheme_DEFAULT } from '../src/theme/foundations/colors/default_theme';
import './i18next';
export const parameters = {
actions: {argTypesRegex: '^on[A-Z].*'},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}
export const decorators = [
(Story, context) => {
const theme = context.globals.theme;
let storyTheme = {};
let i18n;
if (theme === 'mn') {
storyTheme = stateTheme_MN;
i18n = i18nMN
} else if (theme === 'ny') {
storyTheme = stateTheme_NY;
i18n = i18nNY
} else {
storyTheme = stateTheme_DEFAULT;
}
return (
<ChakraProvider theme={storyTheme}>
<I18nextProvider i18n={i18n}>
<Story/>
</I18nextProvider>
</ChakraProvider>
);
},
];
Storybook/i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
const i18nGI = await i18n.use(initReactI18next)
.use(Backend)
.init({
backend: {loadPath: '/i18n/mn/locales/{{lng}}/{{ns}}.json', addPath: "/locales/add/{{lng}}/{{ns}}",},
fallbackLng: 'en-US',
debug: true,
});
const i18nPA = await i18n.use(initReactI18next)
.use(Backend)
.init({
backend: {loadPath: '/i18n/ny/locales/{{lng}}/{{ns}}.json', addPath: "/locales/add/{{lng}}/{{ns}}",},
fallbackLng: 'en-US',
debug: true,
});
export {i18nMN, i18nNY};
Usage in Component:
import { Button } from '#chakra-ui/button';
import { useStyleConfig } from '#chakra-ui/react';
import { useTranslation } from 'react-i18next';
export default function MyButton({
buttonText='submit',
id,
name,
size='md',
type='submit',
variant='primary'}) {
const [t] = useTranslation(['button']);
return (
<Button
sx={styles}
id={id}
name={name}
size={size}
type={type}
variant={variant}
>
{t(buttonText)}
</Button>
);
}

Font loaders must be called and assigned to a const in the module scope on Nextjs13

Am trying to load google font to Nextjs project but I'm getting the error below. In nextjs12 It works okay but I have to use the link for the fonts in the head. This error occurs also using #next/font/local.
I've struggled with this and any help is appreciated.
Am using the official docs process for adding global fonts. Am on Nextjs13.
My _app.tsx code is:
import { useApollo } from "#api/apollo/apolloclient";
import { ApolloProvider } from "#apollo/client";
import { CacheProvider, EmotionCache } from "#emotion/react";
import { Ubuntu } from "#next/font/google";
import "focus-visible/dist/focus-visible";
import { DefaultSeo } from "next-seo";
import { AppProps } from "next/app";
import { ChakraProvider, ColorModeScript } from "#chakra-ui/react";
import theme from "#definitions/chakra/theme";
import { ThemeColorProvider } from "#definitions/context/theme";
import createEmotionCache from "#definitions/utils/createEmotionCache";
import Layout from "#layouts/default";
import "#styles/app.css";
import "#styles/global.scss";
import SEO from "../../next-seo.config";
import { useEffect, useState } from "react";
type ComponentWithPageLayout = AppProps & {
Component: AppProps["Component"] & {
PageLayout?: React.ComponentType;
};
emotionCache: EmotionCache;
};
const clientSideEmotionCache = createEmotionCache();
function sApp({
Component,
emotionCache = clientSideEmotionCache,
pageProps,
}: ComponentWithPageLayout): JSX.Element {
const apolloClient = useApollo(pageProps);
const AnyComponent = Component as any;
const Layoutio = Component.PageLayout as any;
const ubt = Ubuntu({
weight: ["300", "400", "500", "700"],
style: ["normal", "italic"],
});
const [showChild, setShowChild] = useState(false);
useEffect(() => {
setShowChild(true);
}, []);
if (!showChild) {
return null;
}
if (typeof window === "undefined") {
return <></>;
} else {
return (
<>
<style jsx global>{`
html {
font-family: ${ubt.style.fontFamily};
}
`}</style>
<CacheProvider value={emotionCache}>
<ApolloProvider client={apolloClient}>
<ThemeColorProvider>
<ChakraProvider theme={theme}>
<ColorModeScript
initialColorMode={theme.config.initialColorMode}
/>
<DefaultSeo {...SEO} />
{Component.PageLayout ? (
<Layoutio>
<AnyComponent {...pageProps} />
</Layoutio>
) : (
<Layout>
<AnyComponent {...pageProps} />
</Layout>
)}
</ChakraProvider>
</ThemeColorProvider>
</ApolloProvider>
</CacheProvider>
</>
);
}
}
export default App;
Simply move the ubt constant above the sApp function :)
Under _app.tsx the minimal code that worked is shown below. Am using chakra. The downside is that this is experimental and worked on some devices and failed on some like Safari.
import { useApollo } from "#api/apollo/apolloclient";
import { EmotionCache, CacheProvider } from "#emotion/react";
import { Ubuntu } from "#next/font/google";
import "focus-visible/dist/focus-visible";
import { DefaultSeo } from "next-seo";
import { AppProps } from "next/app";
import theme from "#definitions/chakra/theme";
import { ThemeColorProvider } from "#definitions/context/theme";
import createEmotionCache from "#definitions/utils/createEmotionCache";
import "#styles/app.css";
import "#styles/global.scss";
import SEO from "../../next-seo.config";
import { ChakraProvider, ColorModeScript } from "#chakra-ui/react";
import { ApolloProvider } from "#apollo/client";
type ComponentWithPageLayout = AppProps & {
Component: AppProps["Component"] & {
PageLayout?: React.ComponentType;
};
emotionCache: EmotionCache;
};
const clientSideEmotionCache = createEmotionCache();
function sApp({
Component,
emotionCache = clientSideEmotionCache,
pageProps,
}: ComponentWithPageLayout): JSX.Element {
const apolloClient = useApollo(pageProps);
const AnyComponent = Component as any;
const Layoutio = Component.PageLayout as any;
const ubt = Ubuntu({
weight: ["300", "400", "500", "700"],
style: ["normal", "italic"],
});
return (
<>
<CacheProvider value={emotionCache}>
<ApolloProvider client={apolloClient}>
<ThemeColorProvider>
<ChakraProvider theme={theme}>
<ColorModeScript
initialColorMode={theme.config.initialColorMode}
/>
<DefaultSeo {...SEO} />
//Wrap component with classname
<main className={ubt.className}>
<AnyComponent {...pageProps} />
</main>
</ChakraProvider>
</ThemeColorProvider>
</ApolloProvider>
</CacheProvider>
</>
);
}
export default App;
For next config add this block in nextconfig.
experimental: {
appDir: true,
fontLoaders: [
{ loader: "#next/font/google", options: { subsets: ["latin"] } },
],},
I ran into this too, But here is my solution .
import React from "react";
import Link from "next/link";
import { Butterfly_Kids } from '#next/font/google';
import Button from "../Button";
const inter = Butterfly_Kids({ weight: "400", subsets:["latin-ext" ] })
You have to specify some properties to your font function object e.g,
Butterfly_Kids({
weight: "400",
subsets:["latin-ext" ],
style : "normal
})
etc
I hope this solve your problem.

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.

React - frontend component test with Jest

I've just written test file for my component, at the moment it's very rudimentary.. I'm quite inexperience in written test for frontend. I ran yarn test to this test file and it failed miserably..
Here is the message:
Unable to find an element with the text: Please review your billing details...
This is what I have so far for my test:
import React from 'react';
import { render, cleanup, waitForElement } from 'react-testing-library';
// React Router
import { MemoryRouter, Route } from "react-router";
import Show from './Show';
test('it shows the offer', async () => {
const { getByText } = render(
<MemoryRouter initialEntries={['/booking-requests/20-A1-C2/offer']}>
<Route
path="/booking-requests/:booking_request/offer"
render={props => (
<Show {...props} />
)}
/>
</MemoryRouter>
);
//displays the review prompt
await waitForElement(() => getByText('Please review your billing details, contract preview and Additions for your space. Once you’re happy, accept your offer'));
//displays the confirm button
await waitForElement(() => getByText('Confirm'));
});
and this is the component:
// #flow
import * as React from 'react';
import i18n from 'utils/i18n/i18n';
import { Btn } from '#appearhere/bloom';
import css from './Show.css';
import StepContainer from 'components/Layout/DynamicStepContainer/DynamicStepContainer';
const t = i18n.withPrefix('client.apps.offers.show');
const confirmOfferSteps = [
{
title: t('title'),
breadcrumb: t('breadcrumb'),
},
{
title: i18n.t('client.apps.offers.billing_information.title'),
breadcrumb: i18n.t('client.apps.offers.billing_information.breadcrumb'),
},
{
title: i18n.t('client.apps.offers.confirm_pay.title'),
breadcrumb: i18n.t('client.apps.offers.confirm_pay.breadcrumb'),
},
];
class Show extends React.Component<Props> {
steps = confirmOfferSteps;
renderCtaButton = (): React.Element<'Btn'> => {
const cta = t('cta');
return <Btn className={css.button} context='primary'>
{cta}
</Btn>
};
renderLeftContent = ({ isMobile }: { isMobile: boolean }): React.Element<'div'> => (
<div>
<p>{t('blurb')}</p>
{!isMobile && this.renderCtaButton()}
</div>
);
renderRightContent = () => {
return <div>Right content</div>;
};
render() {
const ctaButton = this.renderCtaButton();
return (
<StepContainer
steps={this.steps}
currentStep={1}
ctaButton={ctaButton}
leftContent={this.renderLeftContent}
rightContent={this.renderRightContent}
footer={ctaButton}
/>
);
}
}
export default Show;
what am I missing? Suggestions what else to add to my test file would be greatly appreciated!

Render base component based on .env config varibles

I want to show the root component of the App based on config variable from react-native-config
I want to achieve something like this. I have a IS_STORYBOOK variable in the .env file, i want to setup my environment so i can just set the value from config and switch to main application and storyboard mode in my react-native application.
By doing this way.. i am getting this error bundling failed: SyntaxError: D:\Projects\React\React-Native Sample app\MobileApp\App.js: 'import' and 'export' may only appear at the top level (62:1)
//App.js
import React from 'react'; // eslint-disable-line
import { Provider } from 'react-redux';
import { pushNotifications } from './src/global/services';
import configureStore from './src/store/configureStore';
import {StackNavigator, createDrawerNavigator } from 'react-navigation'
import { generateStack } from './src/navigation/routesBuilder'
import Drawer from './src/components/drawer/container'
import {items} from './src/components/drawer/draweritems';
import DrawerIcon from './src/components/navIcons/drawerIcon'
import {data} from './src/global/data'
import {scale} from './src/utils/scale'
import StoryBook from './storybook';
import Config from 'react-native-config'
const store = configureStore();
pushNotifications.configure();
data.populateData();
const drawerRoutes = {"app.home":{
screen:generateStack("app.home", "Home", true,true)
}}
for(var i=0; i<items.length; i++){
drawerRoutes[items[i].navigateTo] = {
screen : generateStack(items[i].navigateTo, items[i].title, true, true),
}
}
const RootStack = StackNavigator({
Splash: {
screen: generateStack('app.splash', '', false, false),
navigationOptions:{
header: null
}
},
Auth: {
screen : generateStack('auth.login', '', false, false),
navigationOptions:{
header: null
}
},
Home:{
screen : createDrawerNavigator({
...drawerRoutes
},
{
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
drawerPosition:'left',
drawerWidth:scale(300),
drawerIcon : (<DrawerIcon />),
contentComponent: (props) => <Drawer {...props}/>
}),
}
}, {
headerMode:
'none'
});
export default class App extends React.Component {
render() {
if(Config.IS_STORYBOOK){
return <StoryBoard />
} else {
return(
<Provider store={store}>
<RootStack />
</Provider>
)
}
}
}
//storybook.js
import { AppRegistry } from "react-native";
import { getStorybookUI, configure } from "#storybook/react-native";
import { loadStories } from "./storyLoader";
configure(() => {
loadStories();
}, module);
const StorybookUI = getStorybookUI({
port: 7007,
host: "localhost",
onDeviceUI: true,
resetStorybook: true
});
AppRegistry.registerComponent("sampleproject", () => StorybookUI);
export { StorybookUI as default };
// .env
IS_STORYBOOK=false
React components are bundled during build-time and not during run-time.
You cannot conditionally export or import your components. Instead you should conditionally render stuff in your components.
Something like this might work for you
import StoryBook from './storybook';
export default class App extends React.Component {
render() {
if(!Config.IS_STORYBOOK){
return(
<Provider store={store}>
<RootStack />
</Provider>
)
} else {
return <StoryBook />
}
}
}

Resources