Get i18n language from Async Storage - react-native

I am using i18n to translate a React Native app. In i18n.js file:
const getLang = async () => {
const language = await AsyncStorage.getItem("locale");
// console.log(`language |==> `, language);
return "pt";
};
// passes i18n down to react-i18next
i18n.use(initReactI18next).init({
resources: {
en,
pt,
},
lng: getLang(), // getting language from local storage
interpolation: {
escapeValue: false,
},
react: { useSuspense: false },
});
export default i18n;
using getLang() function I try to access Async Storage and get the user's selected language, but I am getting the below error:
I couldn't copy-paste the error, so this is the error I get in the simulator. How do I resolve above stated issue?
Thank You

Because i was looking for help but couldn't find any, i did the following.
In use({}) added a custom languageDetector:
i18n
.use(initReactI18next)
.use({
type: 'languageDetector',
name: 'customDetector',
async: true, // If this is set to true, your detect function receives a callback function that you should call with your language, useful to retrieve your language stored in AsyncStorage for example
init: function () {
/* use services and options */
},
detect: function (callback: (val: string) => void) {
console.log('[LANG] detecting language');
AsyncStorage.getItem('LANG').then((val: string | null) => {
const detected = val || fallbackLanguage;
console.log('[LANG] detected:', detected);
callback(detected);
});
},
cacheUserLanguage: function (lng: string) {
return lng;
},
})
.init({
resources,
fallbackLng: fallbackLanguage,
interpolation: {
escapeValue: false, // react already safes from xss
},
returnObjects: true,
debug: true,
// react-i18next options
react: {
useSuspense: true,
},
detection: {
order: ['customDetector'],
},
})
.then(() => console.log('[INIT] i18n initialized'));
Note it's important the detection.order part because it won't be called otherwise.

Related

next-i18next, next export & 404 on non-generated pages (with fallback true/blocking)

Having followed the documentation on i18next/next-i18next to configure i18n and then the instructions on this locize blog post on how to export static sites with next export, I am able to export localised versions of each page.
The problem is that pages that have not been statically generated return a 404, despite setting fallback: true in the getStaticPaths return object. The page works on my localhost but not when deployed with Vercel.
Code:
const ArticlePage: NextPageWithLayout<Props> = ({ article }: Props) => {
const { i18n, t } = useTranslation('page/news/article')
const router = useRouter()
if (router.isFallback) return <div>Loading...</div>
return <div>Article</div>
}
export const getStaticPaths: GetStaticPaths = async () => {
let paths: { params: { aid: string; locale: TLocale } }[] = []
try {
const response = await api.get(`/articles?per_page=9999`)
const articles = response.data.data as IArticle[]
articles.forEach((a) => {
getI18nPaths().forEach((p) => {
paths.push({
params: {
aid: a.base_64_id,
locale: p.params.locale,
},
})
})
})
return {
paths,
fallback: true,
}
} catch (error) {
return {
paths,
fallback: true,
}
}
}
export const getStaticProps: GetStaticProps = async ({ locale, params }) => {
try {
const article = await api.get(`/articles/${params?.aid}`)
return {
props: {
...(await serverSideTranslations(locale || 'en', [
'footer',
'header',
'misc',
'page/news/index',
'page/news/article',
])),
article: article.data as IArticle,
},
}
} catch (error) {
return {
notFound: true,
}
}
}
ArticlePage.getLayout = function getLayout(page: ReactElement) {
return <Layout>{page}</Layout>
}
export default ArticlePage
"i18next": "22.4.9",
"next-i18next": "^13.1.5",
"react-i18next": "^12.1.5",
There is a warning in the console react-i18next:: You will need to pass in an i18next instance by using initReactI18next when entering an non-generated page (alongside the not-found error of course). An issue raised about this warning is interesting but I could not find an answer to my issue within: https://github.com/i18next/next-i18next/issues/1917.
Attempts to fix:
adding revalidate: 10 to the return object of getStaticProps
using fallback: 'blocking'
trying a few different variants of localePath in next-i18next.config including the recommendation featured here: https://github.com/i18next/next-i18next#vercel-and-netlify
adding react: { useSuspense: false } to next-i18next.config
combinations of the above

AsyncStorage: Invariant Violation: Module AppRegistry is not a registered callable module

I'm using react-i18next to translate a React Native app. It works, but I can't retrieve the user's locale in AsyncStorage when launching the app. The error is:
ERROR TypeError: undefined is not a function, js engine: hermes
ERROR Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication). A frequent cause of the error is that the application entry file path is incorrect.
This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native., js engine: hermes
The code is:
import i18n, { LanguageDetectorAsyncModule } from "i18next";
import { initReactI18next } from "react-i18next";
import { Platform, NativeModules } from "react-native";
import AsyncStorage from "#react-native-async-storage/async-storage";
import en from "./en.json";
import fr from "./fr.json";
const languages = {
EN: "en",
FR: "fr",
};
const resources = {
en: { translation: en },
fr: { translation: fr },
};
const detectLocale = async () => {
const storedLocale = await AsyncStorage.getItem("locale");
if (storedLocale) {
return JSON.parse(storedLocale);
}
const mobileLocale =
Platform.OS === "ios"
? NativeModules.SettingsManager.settings.AppleLocale ||
NativeModules.SettingsManager.settings.AppleLanguages[0]
: NativeModules.I18nManager.localeIdentifier;
if (mobileLocale) {
return mobileLocale.split("_")[0];
}
return languages.EN;
};
const LanguageDetector = {
type: "languageDetector" as LanguageDetectorAsyncModule["type"],
async: false,
init: () => {},
detect: detectLocale,
cacheUserLanguage: () => {},
};
export default i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: languages.EN,
keySeparator: ".",
whitelist: [languages.EN, languages.FR],
interpolation: { escapeValue: false },
});
So the error comes from the detectLocale function, called in LanguageDetector.detect(). It's async, I use the await keyword. Everything works fine without AsyncStorage, so the error must come from here.
I use React Native CLI, no expo, the version is the most recent one to this date: 0.64.1.
I was having a similiar issue.
In order to use async custom language detector, you need to set async to true. Then you will receive callback function on detect().
const languageDetector: LanguageDetectorAsyncModule = {
type: "languageDetector",
async: true,
// Since we set async to true, detect has now callback function...
detect: async (callback: (lang: string) => void) => {
const language = await AsyncStorage.getItem('language')
callback(lastSelectedLang);
},
init: () => {},
cacheUserLanguage: (lng: string) =>
AsyncStorage.setItem('language', lng),
};

Conditionally execute a graphql mutation after a query is fetched

Scenario
When a user is authenticated (isAuthenticated booelan ref):
Check if a user has preferences by a graphql call to the backend (useViewerQuery)
If there are no preferences for the user set the default (useSetPreferenceDefaultMutation)
Problem
Both the query and the mutation work correctly in the graphql Playground and in the Vue app. They have been generated with the graphql codegenerator which uses useQuery and useMutation in the background.
The issue we're having is that we can't define the correct order. Sometimes useSetPreferenceDefaultMutation is executed before useViewerQuery. This resets the user's settings to the defaults and it not the desired behavior.
Also, on a page refresh all is working correctly. However, when closing an reopening the page it always calls useSetPreferenceDefaultMutation.
Code
export default defineComponent({
setup() {
const {
result: queryResult,
loading: queryLoading,
error: queryError,
} = useViewerQuery(() => ({
enabled: isAuthenticated.value,
}))
const {
mutate: setDefaultPreferences,
loading: mutationLoading,
error: mutationError,
called: mutationCalled,
} = useSetPreferenceDefaultMutation({
variables: {
language: 'en-us',
darkMode: false,
},
})
onMounted(() => {
watchEffect(() => {
if (
isAuthenticated.value &&
!queryLoading.value &&
!queryResult.value?.viewer?.preference &&
!mutationCalled.value
) {
void setDefaultPreferences()
}
})
})
return {
isAuthenticated,
loading: queryLoading || mutationLoading,
error: queryError || mutationError,
}
},
})
Failed efforts
We opened an issue here and here to have extra options on useQuery or useMutation which could help in our scenario but no luck.
Use fetch option with sync or post on watchEffect
Use watch instead of watchEffect
Thanks to comment from #xadm it's fixed now by using the onResult event hook on the query, so it will execute the mutation afterwards.
onResult(handler): Event hook called when a new result is available.
export default defineComponent({
setup(_, { root }) {
const {
loading: queryLoading,
error: queryError,
onResult: onQueryResult,
} = useViewerQuery(() => ({
enabled: isAuthenticated.value,
}))
const {
mutate: setDefaultPreferences,
loading: mutationLoading,
error: mutationError,
} = useSetPreferenceDefaultMutation({
variables: {
language: 'en-us',
darkMode: false,
},
})
onQueryResult((result) => {
if (!result.data.viewer.preference) {
void setDefaultPreferences()
}
})
return {
isAuthenticated,
loading: queryLoading || mutationLoading,
error: queryError || mutationError,
}
},
})

Shouldn't i18next wait for language detection result before loading fallback language

I use react-i18next example as a base.
I replaced 'i18next-browser-languagedetector' by custom language detector:
class MyLanguageDetector {
constructor(services, options = {}) {
this.async = true;
this.init(services, options);
}
init(services, options = {}, i18nOptions = {}) { }
detect(callback) {
setTimeout(() => {
callback('de')
}, 5000);
}
cacheUserLanguage(lng, caches) { }
}
MyLanguageDetector.type = 'languageDetector';
Config:
i18n
.use(MyLanguageDetector)
.use(Backend)
.use(reactI18nextModule)
.init({
fallbackLng: 'en',
debug: true,
interpolation: { escapeValue: false },
react: { wait: true }
});
What I see is:
i18next::backendConnector: loaded namespace translation for language en
and after ~5 seconds:
i18next::backendConnector: loaded namespace translation for language de
I expected that 'en' won't be loaded since detected language is 'de'
fallbackLng is always loaded so that it can be used if current language translation file missing the particular key/resource
See related issue

Handling locale change with react-native-i18next/locize/locize backend

I'm trying to get RN i18next and locize to provide translations based on the current locale from react-native-i18n (Already have translations setup locally)
I was having issues with the languageDetector library found here:
https://github.com/DylanVann/i18next-react-native-language-detector
It would throw an error about not finding the parameter "replace" of an object.
So I figured I would setup my own locize/languageDetector using the following code.
However.. It doesn't seem to update when the locale changes..
What am I doing wrong?
in i18next.js:
import i18next from "i18next";
import LocizeBackend from "i18next-locize-backend";
import I18n from "../i18n/i18n";
const languageDetector = {
init: Function.prototype,
type: "languageDetector",
async: true, // flags below detection to be async
detect: lng => I18n.locale,
cacheUserLanguage: () => {}
};
i18next
.use(languageDetector)
.use(LocizeBackend)
.init({
fallbackLng: I18n.locale,
lng: I18n.locale,
debug: true,
whitelist: ["en", "fr", "sv", "dev"],
keySeparator: false,
ns: ["common"],
defaultNS: "common",
saveMissing: false,
interpolation: {
escapeValue: false
},
backend: {
referenceLng: "en",
}
});
Looks like the language detection you use I18n.locale is not async -> so remove async: true...further if you set language on i18next.init it won't use the detector...
you might also use this detector as sample: https://github.com/dormakaba-digital/digital-reactnative-client/blob/master/src/modules/i18n/i18n.js#L4 -> used deviceInfo module