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

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

Related

Get i18n language from Async Storage

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.

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),
};

React Native - use constant from a file in an object on another file

I am using react-i18next to have a multi lang app but I am not able to use constants when trying to build my vocabulary.
I am trying to export constants from a separate file and add these constants as keys in the language object on the translation (i18n) file. However, for some reason it does not let me do this and I get a compilation error.
It does not even recognize the other consts within the object. When I take it outside of the object, it works.
Is there a limitation I am not aware of?
Please see the en object in the index.js file as well as other relevant files:
Consts.js
import React from 'react'
export const Constants = {
USERNMAE : 'Username',
PASSWORD : 'Password',
LOGIN : 'Login',
}
index.js
import { initReactI18next } from "react-i18next";
import {Constants} from '../constants/Consts'
//Constants.USERNAME works perfectly fine here
const resources = {
en: {
translation: {
Constants.USERNAME: "Username", //doesn't work and doesn't recognize USERNAME at all
"Password":"Password",
"Login":"Login"
}
},
he: {
translation: {
"Username": "שם משתמש",
"Password": "סיסמא",
"Login": "התחבר"
}
}
};
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
lng: "he",
keySeparator: false, // we do not use keys in form messages.welcome
interpolation: {
escapeValue: false // react already safes from xss
}
});
export default i18n;
Nothing wrong with imports here, but you can't use object value as an object key in that way
Try out to use an object literal (surround by [] brackets)
const resources = {
en: {
translation: {
[Constants.USERNAME]: 'Username', // <---- now it works :)
Password: 'Password',
Login: 'Login',
},
},
he: {
translation: {
Username: 'שם משתמש',
Password: 'סיסמא',
Login: 'התחבר',
},
},
}

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

aurealia-i18n and latest webpack typescript skeleton

I'm trying to use the aurelia-i18n plugin in with the latest typescript webpack skeleton.
So I have installed the needed npm packages
npm install aurelia-i18n --save
npm install i18next-xhr-backend --save
npm install i18next-browser-languagedetector --save
Then I have changed my main.ts
import { Aurelia } from 'aurelia-framework';
import { PLATFORM } from 'aurelia-pal';
import XHR from 'i18next-xhr-backend';
import LngDetector from 'i18next-browser-languagedetector';
export async function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin(PLATFORM.moduleName('aurelia-i18n'), (instance) => {
// register i18n plugins
instance.i18next
.use(XHR)
.use(LngDetector);
// adapt options to your needs (see http://i18next.com/docs/options/)
// make sure to return the promise of the setup method, in order to guarantee proper loading
return instance.setup({
backend: { // <-- configure backend settings
loadPath: './Locale/{{lng}}/{{ns}}.json', // <-- XHR settings for where to get the files from
},
detection: {
order: ['localStorage', 'cookie', 'navigator'],
lookupCookie: 'i18next',
lookupLocalStorage: 'i18nextLng',
caches: ['localStorage', 'cookie']
},
attributes: ['t', 'i18n'],
fallbackLng: 'en',
load: 'languageOnly',
debug: false,
ns: ['translation',
'StammAlbum',
'StammCategory',
'StammCategoryValue',
'StammPictureAdmin',
'StammPictureUpload',
'StammVideoUpload',
'StammVideoAdmin',
'VideoKonverter',
'Router',
'Datamappings',
'Toasts',
'Alerts',
'Controls',
'Metadata',
'Dialogs',
'AuthRegister',
'SecurityQuestions',
'Countries',
'Validation',
'AuthConfirmAccount',
'AuthLogin',
'AuthForgotPassword',
'AuthAdminAccount',
'AuthNewPassword',
'Messages'],
defaultNS: 'translation'
});
});
await aurelia.start();
await aurelia.setRoot(PLATFORM.moduleName('FamilieLaissApp'));
}
The webpack bundler shows no errors. But in the browser console I can see a 404 Error for every Translation-File.
So I have tried the solution that is documented on the aurelia hub for using the built in backend and changed the main.ts
import { Aurelia } from 'aurelia-framework';
import { PLATFORM } from 'aurelia-pal';
import {I18N, Backend} from 'aurelia-i18n';
import LngDetector from 'i18next-browser-languagedetector';
export async function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin(PLATFORM.moduleName('aurelia-i18n'), (instance) => {
// register i18n plugins
instance.i18next
.use(Backend.with(aurelia.loader))
.use(LngDetector);
// adapt options to your needs (see http://i18next.com/docs/options/)
// make sure to return the promise of the setup method, in order to guarantee proper loading
return instance.setup({
backend: { // <-- configure backend settings
loadPath: './Locale/{{lng}}/{{ns}}.json', // <-- XHR settings for where to get the files from
},
detection: {
order: ['localStorage', 'cookie', 'navigator'],
lookupCookie: 'i18next',
lookupLocalStorage: 'i18nextLng',
caches: ['localStorage', 'cookie']
},
attributes: ['t', 'i18n'],
fallbackLng: 'en',
load: 'languageOnly',
debug: false,
ns: ['translation',
'StammAlbum',
'StammCategory',
'StammCategoryValue',
'StammPictureAdmin',
'StammPictureUpload',
'StammVideoUpload',
'StammVideoAdmin',
'VideoKonverter',
'Router',
'Datamappings',
'Toasts',
'Alerts',
'Controls',
'Metadata',
'Dialogs',
'AuthRegister',
'SecurityQuestions',
'Countries',
'Validation',
'AuthConfirmAccount',
'AuthLogin',
'AuthForgotPassword',
'AuthAdminAccount',
'AuthNewPassword',
'Messages'],
defaultNS: 'translation'
});
});
await aurelia.start();
await aurelia.setRoot(PLATFORM.moduleName('FamilieLaissApp'));
}
But also no luck with this solution. The 404 Errors are gone but the localized strings are not shown in my application. I can only see the Localization.Identifiers not the localized text, and the browser shows no error in the console output.
So what I have to do to get this thing to work?
Here is my working config. I don't use typescript, but your problem is related to the bundling of locale files with webpack
var Promise = require('bluebird'); // Promise polyfill for IE11
Promise.config({
// Enable warnings
warnings: false,
// Enable long stack traces
longStackTraces: true,
// Enable cancellation
cancellation: false,
// Enable monitoring
monitoring: false
});
import 'intl';
import 'intl/locale-data/jsonp/en';
import 'intl/locale-data/jsonp/de';
import {bootstrap} from 'aurelia-bootstrapper-webpack';
import '../theme/assets/css/jquery-ui.css';
//import '-!style!css!../theme/assets/css/jquery-ui.css';
// note! import bootstrap styles after ace
import '../theme/assets/css/ace.css';
import '../theme/assets/css/bootstrap.css';
import '../styles/main.less';
import 'jquery-ui/jquery-ui';
// just js from bootstrap
import 'bootstrap-webpack/bootstrap-scripts!bootstrap-webpack/bootstrap.config.js';
// always import ace-theme after jquery-ui and bootsrap
import 'ace-theme/ace';
import 'ace-theme/ace-elements';
import 'font-awesome-webpack';
import XHR from 'i18next-xhr-backend';
function loadLocales(url, options, callback, data) {
try {
let waitForLocale = require('bundle!json!../locale/' + url + '.json');
waitForLocale((locale) => {
callback(locale, {status: '200'});
})
} catch (e) {
callback(null, {status: '404'});
}
}
bootstrap(function (aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin('aurelia-i18n', (instance) => {
// register backend plugin
instance.i18next.use(XHR);
// adapt options to your needs (see http://i18next.com/docs/options/)
return instance.setup({
backend: { // <-- configure backend settings
//loadPath: '/locale/{{lng}}/{{ns}}.json' // <-- XHR settings for where to get the files from
loadPath: '{{lng}}/{{ns}}',
parse: (data) => data,
ajax: loadLocales
},
lng: 'de',
attributes: ['t', 'i18n'],
fallbackLng: 'en',
debug: false,
//debug: true,
//compatibilityJSON: 'v1',
ns: ['translation', 'nav', 'secuident', 'validation']
});
})
.plugin('aurelia-validation')
.plugin('aurelia-dialog', config => {
config.useDefaults();
config.settings.startingZIndex = 5000;
});
aurelia.start().then(() => aurelia.setRoot('app-webpack', document.body));
});