React Native - exporting an object based on switch case loop - javascript

I'm new to React Native and I'm struggling with an object exporting problem. In my app, I'm receiving from back-end simple string that I store in a variable Settings.translationType. When it's received, I'm rendering a simple view, let's say like this:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import Translations from '../constants/Translations';
export default class HomeScreen extends Component {
render() {
return(
<View>
<Text>{Translations.one}</Text>
</View>
)
}
}
And there comes to some troubles. I'm having a .js file (Translations), that depends on what comes from back-end service, it gives proper translated category names. It looks like this:
import Category1 from './translations/Category1';
import Category2 from './translations/Category2';
import Category3 from './translations/Category3';
const Translations = () => {
switch (Settings.translationType) {
case '2':
return Category2;
case '3':
return Category3;
default:
return Category1;
}
}
export default Translations();
Inside the ./translations folder, I'm having three .js files like below:
import LocalizedStrings from 'localized-strings';
const Category1 = new LocalizedStrings({
en: {
one: 'Restaurant',
two: 'Café',
three: 'Pub'
},
fi: {
one: 'Ravintola',
two: 'Kahvila',
three: 'Pub'
},
sw: {
one: 'Restaurang',
two: 'Kafé',
three: 'Pub'
},
de: {
one: 'Restaurant',
two: 'Cafe',
three: 'Pub'
},
})
export default Category1;
After I run my app in Expo CLI, the Settings.translationType is always fetched correctly from BE, but I'm having an error like: Unable to resolve module './translations/Category1.js' from '~/RN/MyProject/src/constants/Translations.js': The module './translations/Category1.js' could not be found from '~RN/MyProject/src/constants/Translations.js'. Indeed, non of these files exists: (and there are listed the files with other extensions of Category1 file, located at ~/RN/MyProject/src/constants/translations/)
I think I'm having some logical problem (syntax looks ok) so if I'm missing something or there is any other solution, thank you in advice!
EDIT:
Added my folder structure.

Okay I think I found your problem. In Translations.js your export statement should be
export default Translations;
instead of
export default Translations();
Also make sure all your Category files are exported the same way.
See if this works.

I found that in my source file I have misspelled the file's path, syntax bugs are the worst. After that change, all works correct. And just for your knowledge Atin Singh, export default Translations without braces passes '' instead of the proper translation string. Thanks all for your help!

Related

"Cannot access ModuleName before initialization" using import

I'm making an API for a web application, and I'm running into a weird problem when I try to import a module. I have a folder with a bunch of models used to get data from the database, and I have a folder named "global" with various modules used all over the project.
/
/api
/models
/Users.js
/Trainings.js
/TrainingsTypes.js
/TrainingsSubtypes.js
/global
/Functions.js
Some of the models import other models to check ID, get data, etc. I made a function in Functions.js that also needs some of the models to work. So basically, the import dependencies look like:
I must admit, this is a little crappy when showed like that. But here is my problem. As long as Functions.js doesn't import Users.js, everything is fine. The API works well and there's no crash at all. But, if I import Users.js in Functions.js, I immediatly get this error:
ReferenceError: Cannot access 'Users' before initialization
at file:///C:/Users/USERNAME/Desktop/project-api/global/Functions.js:30:10
at ModuleJob.run (internal/modules/esm/module_job.js:169:25)
at async Loader.import (internal/modules/esm/loader.js:177:24)
at async Object.loadESM (internal/process/esm_loader.js:68:5)
Here is the code of Functions.js (I put a comment to indicate line 30):
import check from "checkers";
import Users from "../api/models/Users.js";
import Trainings from "../api/models/Trainings.js";
import TrainingsTypes from "../api/models/TrainingsTypes.js";
import TrainingsSubtypes from "../api/models/TrainingsSubtypes.js";
/*****************************************************
* SQL Queries
*****************************************************/
export function fieldsToUpdate(fields) {...}
const fillers = {
"user": Users, // Line 30
"trainer": Users,
"type": TrainingsTypes,
"subtype": TrainingsSubtypes
};
export async function fillIDs(db, response) {...}
Moreover, the import itself doesn't cause problems. If I remove Users from the fillers object, there's still no crash. I've seen that might be a cyclic dependencies issue but some says that Node can handle this. I must add that I really need all my models in fillIDs() so I just can't remove all the import and I didn't want to copy/paste this code in every model. There must be a solution, but I need some help.
Have you any idea how to fix this?
Thanks
EDIT 1:
Users.js import these modules:
import bcrypt from "bcryptjs";
import generatePwd from "generate-password";
import { v4 as uuidv4 } from "uuid";
import check from "checkers";
import { fieldsToUpdate, fillIDs } from "../../global/Functions.js";
import { arrayToSerialComma } from "../../global/Converters.js";
import APIResp from "../../global/APIResp.js";
import Mailer from "../../global/Mailer.js";
import Genders from "./Genders.js";
import Roles from "./Roles.js";
import Tokens from "./Tokens.js";
import { Passwords } from "../../config/config.js";
EDIT 2:
Users.js is exported in this way:
const Users = {
isValid,
add,
logIn, getAll, getById, getByEmail, hasForgotPassword, getRolesOf,
update, updatePwd, passwordForgotten,
delete: del,
Trainers: {
getById: getTrainerById,
getAll: getAllTrainers
}
};
export default Users;
I managed to partially solve my problem. I've created a file in global exporting all the files in this folder and did the same for models.
// ROOT/global/global.js
export { default as APIResp } from "./APIResp.js";
export { default as SQLFunctions } from "./SQLFunctions.js";
export { default as Mailer } from "./Mailer.js";
export { default as ModuleConsole } from "./ModuleConsole.js";
// ROOT/api/models/models.js
export { default as Users } from "./Users.js";
export { default as Genders } from "./Genders.js";
export { default as Roles } from "./Roles.js";
export { default as Tokens } from "./Tokens.js";
export { default as Trainings } from "./Trainings.js";
export { default as TrainingsTypes } from "./TrainingsTypes.js";
export { default as TrainingsSubtypes } from "./TrainingsSubtypes.js";
I also removed some functions that was needed in both the front-end and back-end and moved them to an external module. I used madge to generate a dependency graph.
As we can see, it's not perfect yet. All the models files are actually in a big circular dependency but it's much cleaner than before and there are no more crashes.
This may have an impact on performance, and I have to be careful with that.
I mark this answer as correct for now, I may change it if someone else or I found something.

Is there a way to read a local JSON array in ES6?

Hi to whoever may read this! Would like to thank in advance any help or direction towards further research into this topic as I'm not sure I fully understand the logic.
I'm creating a react app which uses a button component and gets it's props from an external but local JSON array made up of names. For instance:
import React from 'react';
import * as buttonNames from '/localdirectory/buttonNames.json';
class Buttons extends React.Component {
render() {
return (
{buttonNames.map((buttonName, i) => {
return <button>{buttonName}</button>
}}
);
}
}
And the JSON array looks like this:
[
"10A",
"10B",
"10C",
"10D",
"10E",
"10F"
]
This is a simplified example of what I'm trying to do however the expected result from my perspective is that the mapping function should iterate through the JSON array and assign each to the buttonName variable which would then be used to create 6 different buttons with the innerHTML filled by the content of the array item.
However the program throws an error which states that map is not exported from the JSON array, why would it need to be in the first place? Shouldn't the program be simply importing it and treating it as a standard array which has built-in JS methods such as map and forEach
I've tried working with the file as a CSV and importing using D3, however that returns a promise which I'd prefer not to be the case as I'm using it load the UI and the file which contains the buttonNames isn't particularly large. Using JSON.parse() also does not work as the program states that the target is already an object.
Appreciate and open to any suggestions!
It's about your import statement.
while handling JSON file in js, it's been turned into a js that export data.
[1,2,3]
with
import data from "./data.json"
console.log(data) // [1,2,3]
with
import * as data from "./data.json"
console.log(data) // {0:1,1:2,2:3, default:[1,2,3]}
as it's been treated as ES module, so you import an object instead of the array itself, so the map function will be undefined, as it's just a plain object.
Here's why
Webpack load JSON with json-loader, which is officially supported that you won't need to configure yourself, and the json-loader will handle JSON files by stringify it and concat with module.exports=, source code here,
so the JSON will be in js like
module.exports = [0,1,2]
and as the module.exports object itself will be imported as default export in an ESM file, and all objects fields will be export as others, so you will get an ESM object while doing import * as data from "./data.json"
Of cause if you are using a different JSON loader the value will be different, but the error is what you meet.
Yes you're right, JSON array should be treated as native in JS. It is. You don't have to change your JSON. Problem is the import * syntax, do not use it. Instead:
import buttonNames from '/localdirectory/buttonNames.json';
Which will be imported smoothly by Webpack.
Plain JSON does not Support Arrays. Try:
{
0: "10A",
1: "10B",
2: "10C",
3: "10D",
4: "10E",
5: "10F"
}
import React from 'react';
import buttonNames from '/localdirectory/buttonNames.json';
class Buttons extends React.Component {
render() {
return (
{Object.values(buttonNames).map((buttonName, i) => {
return <button>{buttonName}</button>
}}
);
}
}
Working demo
Json file
{
"data": ["10A", "10B", "10C", "10D", "10E", "10F"]
}
JSX
import React from "react";
import "./styles.css";
import { data } from "./myJson.json";
export default function App() {
console.log(data);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{data.map(d => (
<p>{d}</p>
))}
</div>
);
}

Import JSON file in React

I'm new to React and I'm trying to import a JSON DATA variable from an external file. I'm getting the following error:
Cannot find module "./customData.json"
Could some one help me? It works if I have my DATA variable in index.js but not when it's in an external JSON file.
index.js
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import customData from './customData.json';
import Profile from './components/profile';
import Hobbies from './components/hobbies';
class App extends Component {
render() {
return (
<div>
<Profile name={this.props.profileData.name}imgUrl={this.props.profileData.imgURL} />
<Hobbies hobbyList={this.props.profileData.hobbyList}/>
</div>
);
}
}
ReactDOM.render(<App profileData={DATA}/>, document.querySelector('.container'));
hobbies.js
import React, {Component} from 'react';
var Hobbies = React.createClass({
render: function(){
var hobbies = this.props.hobbyList.map(function(hobby, index){
return (<li key={index}>{hobby}</li>);
});
return (
<div>
<h5>My hobbies:</h5>
<ul>
{hobbies}
</ul>
</div>
);
}
});
export default Hobbies;
profile.js
import React from 'react';
var Profile = React.createClass({
render: function(){
return (
<div>
<h3>{this.props.name}</h3>
<img src={this.props.imgUrl} />
</div>
)
}
});
export default Profile
customData.json
var DATA = {
name: 'John Smith',
imgURL: 'http://lorempixel.com/100/100/',
hobbyList: ['coding', 'writing', 'skiing']
}
export default DATA
One nice way (without adding a fake .js extension which is for code not for data and configs) is to use json-loader module. If you have used create-react-app to scaffold your project, the module is already included, you just need to import your json:
import Profile from './components/profile';
This answer explains more.
This old chestnut...
In short, you should be using require and letting node handle the parsing as part of the require call, not outsourcing it to a 3rd party module. You should also be taking care that your configs are bulletproof, which means you should check the returned data carefully.
But for brevity's sake, consider the following example:
For Example, let's say I have a config file 'admins.json' in the root of my app containing the following:
admins.json
[{
"userName": "tech1337",
"passSalted": "xxxxxxxxxxxx"
}]
Note the quoted keys, "userName", "passSalted"!
I can do the following and get the data out of the file with ease.
let admins = require('~/app/admins.json');
console.log(admins[0].userName);
Now the data is in and can be used as a regular (or array of) object.
With json-loader installed, you can use
import customData from '../customData.json';
or also, even more simply
import customData from '../customData';
To install json-loader
npm install --save-dev json-loader
Simplest approach is following
// Save this as someJson.js
const someJson = {
name: 'Name',
age: 20
}
export default someJson
then
import someJson from './someJson'
React 17 created from create-react-app, importing json just work by default.
import config from './config.json'
The solution that worked for me is that:-
I moved my data.json file from src to public directory.
Then used fetch API to fetch the file
fetch('./data.json').then(response => {
console.log(response);
return response.json();
}).then(data => {
// Work with JSON data here
console.log(data);
}).catch(err => {
// Do something for an error here
console.log("Error Reading data " + err);
});
The problem was that after compiling react app the fetch request looks for the file at URL "http://localhost:3000/data.json" which is actually the public directory of my react app. But unfortunately while compiling react app data.json file is not moved from src to public directory. So we have to explicitly move data.json file from src to public directory.
Please store your JSON file with the .js extension and make sure that your JSON should be in same directory.
// rename the .json file to .js and keep in src folder
Declare the json object as a variable
var customData = {
"key":"value"
};
Export it using module.exports
module.exports = customData;
From the component that needs it, make sure to back out two folders deep
import customData from '../customData';
In current react build you simply import and use:
import jsonData from 'path/to/myJson.json'
try with export default DATA or module.exports = DATA
there are multiple ways to do this without using any third-party code or libraries (the recommended way).
1st STATIC WAY: create a .json file then import it in your react component example
my file name is "example.json"
{"example" : "my text"}
the example key inside the example.json can be anything just keep in mind to use double quotes to prevent future issues.
How to import in react component
import myJson from "jsonlocation";
and you can use it anywhere like this
myJson.example
now there are a few things to consider. With this method, you are forced to declare your import at the top of the page and cannot dynamically import anything.
Now, what about if we want to dynamically import the JSON data? example a multi-language support website?
2 DYNAMIC WAY
1st declare your JSON file exactly like my example above
but this time we are importing the data differently.
let language = require('./en.json');
this can access the same way.
but wait where is the dynamic load?
here is how to load the JSON dynamically
let language = require(`./${variable}.json`);
now make sure all your JSON files are within the same directory
here you can use the JSON the same way as the first example
myJson.example
what changed? the way we import because it is the only thing we really need.
I hope this helps.
var langs={
ar_AR:require('./locale/ar_AR.json'),
cs_CZ:require('./locale/cs_CZ.json'),
de_DE:require('./locale/de_DE.json'),
el_GR:require('./locale/el_GR.json'),
en_GB:require('./locale/en_GB.json'),
es_ES:require('./locale/es_ES.json'),
fr_FR:require('./locale/fr_FR.json'),
hu_HU:require('./locale/hu_HU.json')
}
module.exports=langs;
Require it in your module:
let langs=require('./languages');
regards
This worked well in React 16.11.0
// in customData.js
export const customData = {
//json data here
name: 'John Smith',
imgURL: 'http://lorempixel.com/100/100/',
hobbyList: ['coding', 'writing', 'skiing']
}
// in index.js
import { customData } from './customData';
// example usage later in index.js
<p>{customData.name}</p>
My friends, if you are using React and TypeScript, just do these steps and DONE!
In the tsconfig.json add these 2 new lines:
// tsconfig.json
{
"compilerOptions": {
// ... other options
"esModuleInterop": true,
"resolveJsonModule": true
}
}
Import your json:
import yourJSON from "./data/yourJSON.json"
Something that worked for me was to simply place the JSON file in the public folder. You can simply import in any js using
brain.loadData("exampleFile.json");
It is as simple as that I guess. Definitely worth a try :D

What is the best way to export an object literal with ES6/2015?

Seemingly a very simple task...
export default function() {
return {
googleClientID:'xxxx'
}
}
Is it the best way to export object literal with app settings?
You can export object itself:
export default {
googleClientID:'xxxx'
};
The difference is that in your case you will get brand new object every time you call exported function. In this case you will get the same object every time. Depends on what you need.
You can simply export an object
export default { googleClientID:'xxxx' };
A default export can be a function, a class, an object or anything else. This value is to be considered as the "main" exported value since it will be the simplest to import.
#madox2 's and #void 's answer may be some kind of common misunderstanding.
I just ran into a similar problem while issuing a PR to DefinitelyTyped -- #18725. The typescript compiler complains about the generated files.
A example should be:
// ...
import zip from "./zip";
import zipObject from "./zipObject";
import zipObjectDeep from "./zipObjectDeep";
import zipWith from "./zipWith";
export default {
// ...
zip,
zipObject,
zipObjectDeep,
zipWith
};
At the first glance, i didn't think its my problem. Because i just copy the code from lodash-es. But then i can't find any simple approach to remove the errors.
So I go to the spec for answer. Wow, the spec doesn't talk about default export of an object, if I read it right.
Conclusion:
The follow is spec-respected:
export { googleClientID:'xxxx' }
Just found some more references:
https://medium.com/#timoxley/named-exports-as-the-default-export-api-670b1b554f65#a279
https://medium.com/#rauschma/note-that-default-exporting-objects-is-usually-an-anti-pattern-if-you-want-to-export-the-cf674423ac38
Exporting: not nicest, but handy when importing later.
export const ROOT = '/tmp/test'
export const EXT = '.backup'
// ... and so on
Importing: cleanest usage (and we usually import more than export).
import { ROOT, EXT } from './literals.js'
const file = ROOT + yourFileName + EXT

Implementing internationalization in React application [duplicate]

I'm building an app that will need to be available in multiple languages and locales.
My question is not purely technical, but rather about the architecture, and the patterns that people are actually using in production to solve this problem.
I couldn't find anywhere any "cookbook" for that, so I'm turning to my favourite Q/A website :)
Here are my requirements (they are really "standard"):
The user can choose the language (trivial)
Upon changing the language, the interface should translate automatically to the new selected language
I'm not too worried about formatting numbers, dates etc. at the moment, I want a simple solution to just translate strings
Here are the possible solutions I could think off:
Each component deal with translation in isolation
This means that each component have for example a set of en.json, fr.json etc. files alongside it with the translated strings. And a helper function to help reading the values from those depending on the selected language.
Pro: more respectful of the React philosophy, each component is "standalone"
Cons: you can't centralize all the translations in a file (to have someone else add a new language for example)
Cons: you still need to pass the current language as a prop, in every bloody component and their children
Each component receives the translations via the props
So they are not aware of the current language, they just take a list of strings as props which happen to match the current language
Pro: since those strings are coming "from the top", they can be centralized somewhere
Cons: Each component is now tied into the translation system, you can't just re-use one, you need to specify the correct strings every time
You bypass the props a bit and possibly use the context thingy to pass down the current language
Pro: it's mostly transparent, don't have to pass the current language and/or translations via props all the time
Cons: it looks cumbersome to use
If you have any other idea, please do say!
How do you do it?
After trying quite a few solutions, I think I found one that works well and should be an idiomatic solution for React 0.14 (i.e. it doesn't use mixins, but Higher Order Components) (edit: also perfectly fine with React 15 of course!).
So here the solution, starting by the bottom (the individual components):
The Component
The only thing your component would need (by convention), is a strings props.
It should be an object containing the various strings your Component needs, but really the shape of it is up to you.
It does contain the default translations, so you can use the component somewhere else without the need to provide any translation (it would work out of the box with the default language, english in this example)
import { default as React, PropTypes } from 'react';
import translate from './translate';
class MyComponent extends React.Component {
render() {
return (
<div>
{ this.props.strings.someTranslatedText }
</div>
);
}
}
MyComponent.propTypes = {
strings: PropTypes.object
};
MyComponent.defaultProps = {
strings: {
someTranslatedText: 'Hello World'
}
};
export default translate('MyComponent')(MyComponent);
The Higher Order Component
On the previous snippet, you might have noticed this on the last line:
translate('MyComponent')(MyComponent)
translate in this case is a Higher Order Component that wraps your component, and provide some extra functionality (this construction replaces the mixins of previous versions of React).
The first argument is a key that will be used to lookup the translations in the translation file (I used the name of the component here, but it could be anything). The second one (notice that the function is curryed, to allow ES7 decorators) is the Component itself to wrap.
Here is the code for the translate component:
import { default as React } from 'react';
import en from '../i18n/en';
import fr from '../i18n/fr';
const languages = {
en,
fr
};
export default function translate(key) {
return Component => {
class TranslationComponent extends React.Component {
render() {
console.log('current language: ', this.context.currentLanguage);
var strings = languages[this.context.currentLanguage][key];
return <Component {...this.props} {...this.state} strings={strings} />;
}
}
TranslationComponent.contextTypes = {
currentLanguage: React.PropTypes.string
};
return TranslationComponent;
};
}
It's not magic: it will just read the current language from the context (and that context doesn't bleed all over the code base, just used here in this wrapper), and then get the relevant strings object from loaded files. This piece of logic is quite naïve in this example, could be done the way you want really.
The important piece is that it takes the current language from the context and convert that into strings, given the key provided.
At the very top of the hierarchy
On the root component, you just need to set the current language from your current state. The following example is using Redux as the Flux-like implementation, but it can easily be converted using any other framework/pattern/library.
import { default as React, PropTypes } from 'react';
import Menu from '../components/Menu';
import { connect } from 'react-redux';
import { changeLanguage } from '../state/lang';
class App extends React.Component {
render() {
return (
<div>
<Menu onLanguageChange={this.props.changeLanguage}/>
<div className="">
{this.props.children}
</div>
</div>
);
}
getChildContext() {
return {
currentLanguage: this.props.currentLanguage
};
}
}
App.propTypes = {
children: PropTypes.object.isRequired,
};
App.childContextTypes = {
currentLanguage: PropTypes.string.isRequired
};
function select(state){
return {user: state.auth.user, currentLanguage: state.lang.current};
}
function mapDispatchToProps(dispatch){
return {
changeLanguage: (lang) => dispatch(changeLanguage(lang))
};
}
export default connect(select, mapDispatchToProps)(App);
And to finish, the translation files:
Translation Files
// en.js
export default {
MyComponent: {
someTranslatedText: 'Hello World'
},
SomeOtherComponent: {
foo: 'bar'
}
};
// fr.js
export default {
MyComponent: {
someTranslatedText: 'Salut le monde'
},
SomeOtherComponent: {
foo: 'bar mais en français'
}
};
What do you guys think?
I think is solves all the problem I was trying to avoid in my question: the translation logic doesn't bleed all over the source code, it is quite isolated and allows reusing the components without it.
For example, MyComponent doesn't need to be wrapped by translate() and could be separate, allowing it's reuse by anyone else wishing to provide the strings by their own mean.
[Edit: 31/03/2016]: I recently worked on a Retrospective Board (for Agile Retrospectives), built with React & Redux, and is multilingual.
Since quite a lot of people asked for a real-life example in the comments, here it is:
You can find the code here: https://github.com/antoinejaussoin/retro-board/tree/master
From my experience the best approach is to create an i18n redux state and use it, for many reasons:
1- This will allow you to pass the initial value from the database, local file or even from a template engine such as EJS or jade
2- When the user changes the language you can change the whole application language without even refreshing the UI.
3- When the user changes the language this will also allow you to retrieve the new language from API, local file or even from constants
4- You can also save other important things with the strings such as timezone, currency, direction (RTL/LTR) and list of available languages
5- You can define the change language as a normal redux action
6- You can have your backend and front end strings in one place, for example in my case I use i18n-node for localization and when the user changes the UI language I just do a normal API call and in the backend, I just return i18n.getCatalog(req) this will return all the user strings only for the current language
My suggestion for the i18n initial state is:
{
"language":"ar",
"availableLanguages":[
{"code":"en","name": "English"},
{"code":"ar","name":"عربي"}
],
"catalog":[
"Hello":"مرحباً",
"Thank You":"شكراً",
"You have {count} new messages":"لديك {count} رسائل جديدة"
],
"timezone":"",
"currency":"",
"direction":"rtl",
}
Extra useful modules for i18n:
1- string-template this will allow you to inject values in between your catalog strings for example:
import template from "string-template";
const count = 7;
//....
template(i18n.catalog["You have {count} new messages"],{count}) // لديك ٧ رسائل جديدة
2- human-format this module will allow you to converts a number to/from a human readable string, for example:
import humanFormat from "human-format";
//...
humanFormat(1337); // => '1.34 k'
// you can pass your own translated scale, e.g: humanFormat(1337,MyScale)
3- momentjs the most famous dates and times npm library, you can translate moment but it already has a built-in translation just you need to pass the current state language for example:
import moment from "moment";
const umoment = moment().locale(i18n.language);
umoment.format('MMMM Do YYYY, h:mm:ss a'); // أيار مايو ٢ ٢٠١٧، ٥:١٩:٥٥ م
Update (14/06/2019)
Currently, there are many frameworks implement the same concept using react context API (without redux), I personally recommended I18next
Antoine's solution works fine, but have some caveats :
It uses the React context directly, which I tend to avoid when already using Redux
It imports directly phrases from a file, which can be problematic if you want to fetch needed language at runtime, client-side
It does not use any i18n library, which is lightweight, but doesn't give you access to handy translation functionalities like pluralization and interpolation
That's why we built redux-polyglot on top of both Redux and AirBNB's Polyglot.
(I'm one of the authors)
It provides :
a reducer to store language and corresponding messages in your Redux store. You can supply both by either :
a middleware that you can configure to catch specific action, deduct current language and get/fetch associated messages.
direct dispatch of setLanguage(lang, messages)
a getP(state) selector that retrieves a P object that exposes 4 methods :
t(key): original polyglot T function
tc(key): capitalized translation
tu(key): upper-cased translation
tm(morphism)(key): custom morphed translation
a getLocale(state)selector to get current language
a translate higher order component to enhance your React components by injecting the p object in props
Simple usage example :
dispatch new language :
import setLanguage from 'redux-polyglot/setLanguage';
store.dispatch(setLanguage('en', {
common: { hello_world: 'Hello world' } } }
}));
in component :
import React, { PropTypes } from 'react';
import translate from 'redux-polyglot/translate';
const MyComponent = props => (
<div className='someId'>
{props.p.t('common.hello_world')}
</div>
);
MyComponent.propTypes = {
p: PropTypes.shape({t: PropTypes.func.isRequired}).isRequired,
}
export default translate(MyComponent);
Please tell me if you have any question/suggestion !
Yet another (light) proposal implemented in Typescript and based on ES6 & Redux & Hooks & JSON with no 3rd party dependencies.
Since the selected language is loaded in the redux state, changing the language becomes very fast without the need of rendering all pages, but just the affected texts.
Part 1: Redux setup:
/src/shared/Types.tsx
export type Language = 'EN' | 'CA';
/src/store/actions/actionTypes.tsx
export const SET_LANGUAGE = 'SET_LANGUAGE';
/src/store/actions/language.tsx:
import * as actionTypes from './actionTypes';
import { Language } from '../../shared/Types';
export const setLanguage = (language: Language) => ({
type: actionTypes.SET_LANGUAGE,
language: language,
});
/src/store/reducers/language.tsx:
import * as actionTypes from '../action/actionTypes';
import { Language } from '../../shared/Types';
import { RootState } from './reducer';
import dataEN from '../../locales/en/translation.json';
import dataCA from '../../locales/ca/translation.json';
type rootState = RootState['language'];
interface State extends rootState { }
interface Action extends rootState {
type: string,
}
const initialState = {
language: 'EN' as Language,
data: dataEN,
};
const setLanguage = (state: State, action: Action) => {
let data;
switch (action.language) {
case 'EN':
data = dataEN;
break;
case 'CA':
data = dataCA;
break;
default:
break;
}
return {
...state,
...{ language: action.language,
data: data,
}
};
};
const reducer = (state = initialState, action: Action) => {
switch (action.type) {
case actionTypes.SET_LANGUAGE: return setLanguage(state, action);
default: return state;
}
};
export default reducer;
/src/store/reducers/reducer.tsx
import { useSelector, TypedUseSelectorHook } from 'react-redux';
import { Language } from '../../shared/Types';
export interface RootState {
language: {
language: Language,
data: any,
}
};
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
/src/App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { createStore, combineReducers } from 'redux';
import languageReducer from './store/reducers/language';
import styles from './App.module.css';
// Set global state variables through Redux
const rootReducer = combineReducers({
language: languageReducer,
});
const store = createStore(rootReducer);
const App = () => {
return (
<Provider store={store}>
<div className={styles.App}>
// Your components
</div>
</Provider>
);
}
export default App;
Part 2: Dropdown menu with languages. In my case, I put this component within the navigation bar to be able to change the language from any screen:
/src/components/Navigation/Language.tsx
import React from 'react';
import { useDispatch } from 'react-redux';
import { setLanguage } from '../../store/action/language';
import { useTypedSelector } from '../../store/reducers/reducer';
import { Language as Lang } from '../../shared/Types';
import styles from './Language.module.css';
const Language = () => {
const dispatch = useDispatch();
const language = useTypedSelector(state => state.language.language);
return (
<div>
<select
className={styles.Select}
value={language}
onChange={e => dispatch(setLanguage(e.currentTarget.value as Lang))}>
<option value="EN">EN</option>
<option value="CA">CA</option>
</select>
</div>
);
};
export default Language;
Part 3: JSON files. In this example, just a test value with a couple of languages:
/src/locales/en/translation.json
{
"message": "Welcome"
}
/src/locales/ca/translation.json
{
"message": "Benvinguts"
}
Part 4: Now, at any screen, you can show the text in the selected language from the redux setup:
import React from 'react';
import { useTypedSelector } from '../../store/reducers/reducer';
const Test = () => {
const t = useTypedSelector(state => state.language.data);
return (
<div> {t.message} </div>
)
}
export default Test;
Sorry for the post extension, but I tried to show the complete setup to clarify all doubts. Once this is done, it is very quick and flexible to add languages and use descriptions anywhere.
From my research into this there appears to be two main approaches being used to i18n in JavaScript, ICU and gettext.
I've only ever used gettext, so I'm biased.
What amazes me is how poor the support is. I come from the PHP world, either CakePHP or WordPress. In both of those situations, it's a basic standard that all strings are simply surrounded by __(''), then further down the line you get translations using PO files very easily.
gettext
You get the familiarity of sprintf for formatting strings and PO files will be translated easily by thousands of different agencies.
There's two popular options:
i18next, with usage described by this arkency.com blog post
Jed, with usage described by the sentry.io post and this React+Redux post,
Both have gettext style support, sprintf style formatting of strings and import / export to PO files.
i18next has a React extension developed by themselves. Jed doesn't. Sentry.io appear to use a custom integration of Jed with React. The React+Redux post, suggests using
Tools: jed + po2json + jsxgettext
However Jed seems like a more gettext focussed implementation - that is it's expressed intention, where as i18next just has it as an option.
ICU
This has more support for the edge cases around translations, e.g. for dealing with gender. I think you will see the benefits from this if you have more complex languages to translate into.
A popular option for this is messageformat.js. Discussed briefly in this sentry.io blog tutorial. messageformat.js is actually developed by the same person that wrote Jed. He makes quite stong claims for using ICU:
Jed is feature complete in my opinion. I am happy to fix bugs, but generally am not interested in adding more to the library.
I also maintain messageformat.js. If you don't specifically need a gettext implementation, I might suggest using MessageFormat instead, as it has better support for plurals/gender and has built-in locale data.
Rough comparison
gettext with sprintf:
i18next.t('Hello world!');
i18next.t(
'The first 4 letters of the english alphabet are: %s, %s, %s and %s',
{ postProcess: 'sprintf', sprintf: ['a', 'b', 'c', 'd'] }
);
messageformat.js (my best guess from reading the guide):
mf.compile('Hello world!')();
mf.compile(
'The first 4 letters of the english alphabet are: {s1}, {s2}, {s3} and {s4}'
)({ s1: 'a', s2: 'b', s3: 'c', s4: 'd' });
If not yet done having a look at https://react.i18next.com/ might be a good advice. It is based on i18next: learn once - translate everywhere.
Your code will look something like:
<div>{t('simpleContent')}</div>
<Trans i18nKey="userMessagesUnread" count={count}>
Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.
</Trans>
Comes with samples for:
webpack
cra
expo.js
next.js
storybook integration
razzle
dat
...
https://github.com/i18next/react-i18next/tree/master/example
Beside that you should also consider workflow during development and later for your translators -> https://www.youtube.com/watch?v=9NOzJhgmyQE
I would like to propose a simple solution using create-react-app.
The application will be built for every language separately, therefore whole translation logic will be moved out of the application.
The web server will serve the correct language automatically, depending on Accept-Language header, or manually by setting a cookie.
Mostly, we do not change language more than once, if ever at all)
Translation data put inside same component file, that uses it, along styles, html and code.
And here we have fully independent component that responsible for its own state, view, translation:
import React from 'react';
import {withStyles} from 'material-ui/styles';
import {languageForm} from './common-language';
const {REACT_APP_LANGUAGE: LANGUAGE} = process.env;
export let language; // define and export language if you wish
class Component extends React.Component {
render() {
return (
<div className={this.props.classes.someStyle}>
<h2>{language.title}</h2>
<p>{language.description}</p>
<p>{language.amount}</p>
<button>{languageForm.save}</button>
</div>
);
}
}
const styles = theme => ({
someStyle: {padding: 10},
});
export default withStyles(styles)(Component);
// sets laguage at build time
language = (
LANGUAGE === 'ru' ? { // Russian
title: 'Транзакции',
description: 'Описание',
amount: 'Сумма',
} :
LANGUAGE === 'ee' ? { // Estonian
title: 'Tehingud',
description: 'Kirjeldus',
amount: 'Summa',
} :
{ // default language // English
title: 'Transactions',
description: 'Description',
amount: 'Sum',
}
);
Add language environment variable to your package.json
"start": "REACT_APP_LANGUAGE=ru npm-run-all -p watch-css start-js",
"build": "REACT_APP_LANGUAGE=ru npm-run-all build-css build-js",
That is it!
Also my original answer included more monolithic approach with single json file for each translation:
lang/ru.json
{"hello": "Привет"}
lib/lang.js
export default require(`../lang/${process.env.REACT_APP_LANGUAGE}.json`);
src/App.jsx
import lang from '../lib/lang.js';
console.log(lang.hello);

Categories