i18n-js in react native: language not loaded on startup - react-native

I have a react native app (ios) with multiple languages. I'm currently facing a weird issue: when I open the app, for a split second, I don't see the translations but I see an error: 'missing translation ...'. This error normally shows up when the translation key is not found (key-value pair) in the json file. Like I said, this error is only there for a split second, after like 500ms, the translations pop up. It seems like the translation file is not loaded properly when the app starts up.
This error is new since the last few days, before that it didn't happen. It also does not happen on the ios simulator, but only when I'm testing on a device. Both the debug scheme and the release scheme have this issue.
My language file looks like this:
// en.json
// { key: value }
{
"hello": "Hello",
"world": "World",
...
}
This is my code in react-native:
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { _setLanguage } from '../redux/Actions';
import _ from 'lodash';
import i18n from 'i18n-js';
import * as RNLocalize from "react-native-localize";
const translationGetters = {
'en': () => require('./translations/en.json')
};
export default useLanguage = (props) => {
const dispatch = useDispatch();
// get the stored language in the redux store (if set)
const _language = useSelector(state => state.data.config.language);
// translate function to memoize the language file
const translate = _.memoize(
(key, config) => i18n.t(key, config),
(key, config) => (config ? key + JSON.stringify(config) : key)
);
// useEffect function loaded on start up to get the language from redux
// (if set) or from the device
// setLanguage() is called to load the correct language file
useEffect(() => {
if (_.isNull(_language)) {
const fallback = { languageTag: 'en' };
const { languageTag } = RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) || fallback;
dispatch(_setLanguage({ language: languageTag }));
setLanguage(languageTag);
} else {
setLanguage(_language);
}
}, []);
// setLanguage()
// sets the language file
const setLanguage = (language) => {
translate.cache.clear();
i18n.translations = { ['en']: translationGetters['en'](), [language]: translationGetters[language]() };
i18n.locale = language;
i18n.defaultLocale = 'en';
i18n.fallbacks = true;
i18n.missingTranslation = () => (null);
dispatch(_setLanguage({ language }));
};
return {
setLanguage,
language: _language,
translate
};
};

Related

State in Pinia store seems to be undefined in Quasar v2 boot file

I'm having some trouble getting Pinia to work properly with two boot files.
Boot file #1 (authentication.js) fetches information about the current user and replaces the object in the userInfoStore:
import { boot } from 'quasar/wrappers';
import userClient from 'src/api/userClient';
import { useUserInfoStore } from 'stores/userInfo';
export default boot(async ({ router, store }) => {
const userInfoStore = useUserInfoStore(store);
router.beforeEach(async () => {
try {
const response = await userClient.getUserInfo();
userInfoStore.$state = response.data;
return true;
} catch (error) {
// error handling
}
return true;
});
});
Then, in boot file #2 (casl.js) I'd like to read the state from the userInfoStorelike this:
import { boot } from 'quasar/wrappers';
import { useUserInfoStore } from 'stores/userInfo';
export default boot(async ({ store }) => {
const userInfoStore = useUserInfoStore(store);
console.log(userInfoStore.givenName);
});
The givenName is always undefined. Inspecting the store via the Vue DevTools, however, shows a givenName. And printing console.log(userInfoStore.$state)also shows the name.
What could be wrong here?

React createContext running mutation function not working correctly

I am having a problem i can't seem to understand why is happening since i have the same example working in codesandbox, but in my app it shows a different behavior. In my app i can see the context from the consumer both the bool and the function, but when i run the function it runs the empty function "setUpdate: () => {}" instead of running the "updBool()" in UpdateDataProvider.js file. Anyone know why this behaviour happens.
(component.js is not my actual file just a short example of how im using the context)
UpdateDataProvider.js
export const UpdateDataContext = createContext({
update: false,
setUpdate: () => {},
});
export function UpdateDataContexProvider({ children }) {
function updBool(bool) {
setU({ ...u, update: bool });
}
const [u, setU] = useState({ update: false, setUpdate: updBool });
return (
<UpdateDataContext.Provider value={u}>
{children}
</UpdateDataContext.Provider>
);
}
useUpdateData.js
import { useContext } from 'react';
import { UpdateDataContext } from '../../context/updateDataContext';
export function useUpdateDataContext() {
return useContext(UpdateDataContext);
}
component.js
import { UpdateDataContexProvider } from '../../context/updateDataContext';
import { useUpdateDataContext } from '../../hooks/exports';
useEffect(() => {
// loging the context shows me update bool and setUpdate function
console.log(context)
// Running the function will run the empty function in createContext
// in UpdateDataProvider.
context.setUpdate(true)
}, [])
export default Home = () => {
const context = useUpdateDataContext()
return (
<UpdateDataContexProvider>
<Other />
</UpdateDataContexProvider>
)
}
Don't mind my question, the mistake was that i was trying to run the function in useEffect in the home component but not the childs

Testing React Native Image.getSize: Cannot spyOn on a primitive value; string given

I have a hook that detects the orientation of a React Native Image:
import { useState, useEffect } from 'react'
import { Image } from 'react-native'
const useFindImageSize = (image) => {
const [width, setWidth] = useState(0)
const [height, setHeight] = useState(0)
useEffect(() => {
(async() => {
await Image.getSize(image,
(width, height) => {
setWidth(width)
setHeight(height)
})
})()
}, [image])
return { width, height }
}
And have written a test initially to see if getSize has been called:
import { Image } from 'react-native'
import { renderHook } from '#testing-library/react-hooks'
import useFindImageSize from '../useFindImageSize'
describe('useFindImageSize', () => {
const getSize = jest.spyOn(
Image, 'getSize').mockImplementation(jest.fn()
)
afterEach(() => {
jest.clearAllMocks()
})
it('should call getSize', () => {
const { result } = renderHook(() =>
useFindImageSize('test_image')
)
expect(getSize).toHaveBeenCalledTimes(1)
})
})
A basic test which I would've thought would work, (this is based on this question/answer about the same topic).
But I'm getting this error when running my test:
● ImageOrientation › useFindImageSize › encountered a declaration exception
Cannot spyOn on a primitive value; string given
Which refers to const getSizeSpyOn = jest.spyOn(
The hook takes an image uri as its argument and then decides whether it should be a portrait or landscape image, but I'm pretty stuck on how to get around this. Anyone know what I'm doing wrong?
Try something like:
const getSize = jest.spyOn(Image.prototype, 'getSize')
.mockImplementation(jest.fn())
Same as your code, with just ".prototype" added (without quotes).

How to make react native app save the language when changed from the app with i18next?

I'm using i18next in react native to use multi languages in the app :
the user can change the language from the app by clicking on a button
in this button I make an action to set the language in AsyncStorage ,
in i18next init file I want to use the value of the AsyncStorage, but its not changing it because AsyncStorage it need async and await so it take long time to change the value ,
this is the code :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import English from '../Translation/Languages/English.json';
import Spanish from '../Translation/Languages/Spanish.json';
import Arabic from '../Translation/Languages/Arabic.json';
import AsyncStorage from '#react-native-async-storage/async-storage';
let language = null;
const changeLanguage = async () => {
try {
const Lang = await AsyncStorage.getItem('Language');
if (Lang !== null) {
language = Lang;
}
}
catch (error) {
console.log("Error ", error)
}
};
changeLanguage();
i18n
.use(initReactI18next)
.init({
lng: language,
fallbackLng: 'en',
resources: {
en: English,
es: Spanish,
ar: Arabic
}
});
export default i18n;
Checkout this medium-post for a working sample...
Github Repo
https://github.com/hend-elsahli/ReactNativeLocalization
use languageDetector
i18n
.use(languageDetector)
.init(...)
const languageDetector = {
init: Function.prototype,
type: 'languageDetector',
async: true, // flags below detection to be async
detect: async callback => {
const selectedLanguage = await AsyncStorage.getItem('Language');
/** ... */
callback(selectedLanguage);
},
cacheUserLanguage: () => {},
};

How to integrate i18n-js in React Native Expo app

I'm having issues instantiating I18n in my Expo app. The TL;DR of the problem is that components that need the translations are rendered before
Expo.Util.getLocaleAsync()
returns and sets the locale. I can't figure out how to best set it up. As of now, I have a file for my instance of I18n, which I then import and use everywhere else in my app. It looks something like this:
import Expo from 'expo';
import I18n from 'i18n-js';
import english from './resources/strings/en';
import danish from './resources/strings/da';
I18n.defaultLocale = 'en-GB';
I18n.fallbacks = true;
I18n.initAsync = async () => {
var locale = 'en-GB';
try {
locale = await Expo.Util.getCurrentLocaleAsync();
} catch (error) {
console.log('Error getting locale: ' + error);
}
I18n.locale = locale ? locale.replace(/_/, '-') : '';
};
I18n.translations = {
en: english,
da: danish,
};
export default I18n;
Then, in my root app component, I do the following:
import I18n from './src/i18n';
class App extends React.Component {
async componentWillMount() {
console.log('Current locale (main1): ' + I18n.currentLocale());
try {
await I18n.initAsync();
} catch (error) {
console.log('Error setting locale ' + error);
}
console.log('Current locale (main2): ' + I18n.currentLocale());
}
render() {
return <AppContainer />;
}
}
Expo.registerRootComponent(App);
The logs state the expected values - first the default locale, and then the updated locale in main2. The problem is that the child views are rendered with the first locale before the change is made, and I don't understand why?
I can't figure out a better way to do this, any ideas/tips would be much appreciated :-)
This is what worked for me:
In main.js:
import I18n from 'react-native-i18n';
class App extends React.Component {
state = {
appIsReady: false,
}
async componentWillMount() {
await I18n.initAsync();
this.setState({appIsReady: true});
}
render() {
if (!this.state.appIsReady) {
return (
<AppLoading />
);
}
return (
<RootComponent>
);
)
and in some component:
import I18n from '../i18n';
render() {
return (
<View>
<Text>{I18n.t('favorites')}</Text>
<Text>{I18n.locale}</Text>
</View>
)
}
And from the root dir I created i18n/index.js:
import I18n from 'react-native-i18n';
I18n.fallbacks = true; // let 'en-GB' fallback to 'en' if not found
I18n.translations = {
'en': require('./en.json'),
'nl': require('./nl.json'),
}
export default I18n;
my initial Dutch translation file in i18n/nl.json:
{
favorites: 'Jouw Favorieten',
}
I have this in my package.json:
"react-native-i18n": "git+https://github.com/xcarpentier/ex-react-native-i18n.git",
This might be a solution for you: https://github.com/xcarpentier/ex-react-native-i18n
in react native expo it's easy.
1- step 1 install and import
expo install expo-localization
app.js
import { I18nManager } from 'react-native';
import * as Localization from 'expo-localization';
import i18n from 'i18n-js';
1- step 2 create an object with supported language.
i18n.translations = {
en: { addPrice: 'add Price Here',subTax:' Sub Tax -'},
ar: {addPrice: 'ادخل السعر هنا' ,subTax:'قبل ضريبة - '}
};
// Set the locale once at the starting app.
i18n.locale = Localization.locale;
// When a value is missing from a language it'll fallback to another language with the key present.
i18n.fallbacks = true;
//don't change app dir to rtl.
I18nManager.forceRTL(false);
I18nManager.allowRTL(false);
1- step 3 use it Everywhere
<TouchableOpacity >
<Text >{i18n.t('subTax')}</Text>
</TouchableOpacity>