Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App translations framework #74

Merged
merged 10 commits into from
Oct 4, 2019
Merged

App translations framework #74

merged 10 commits into from
Oct 4, 2019

Conversation

arghgr
Copy link
Member

@arghgr arghgr commented Sep 27, 2019

Closes #63, #73 & #75 .

Copy link
Collaborator

@alexgraffeocohen alexgraffeocohen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So exciting to see translations entering the app! I have a few questions, but overall things look great.

@@ -88,7 +90,8 @@ export default class App extends Component {
const AppContainer = createAppContainer(AppNavigator);

return (
<Provider store={store}>
<Provider store={store}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a bit deal, but there are some whitespace issues here that would probably be fixed with our linter.

@@ -102,7 +104,36 @@ class SettingsPage extends Component {
<Content style={styles.content}>
{userBlock}
<View style={styles.view}>
<H3>Device Settings</H3>
<Translate>
{({ translate }) => (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another whitespace issue, but it's fine if you don't feel like fixing it.

/>
</Item>
<Item>
<Label>Description (ES)</Label>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there's a way we can encapsulate the idea of repeating the same form element multiple times for each language option in a component, like <LocalizedInputs> that takes the attribute it's affecting, like description in this case. Not something we need to do right now, but if we need to do a lot of this kind of thing, we should consider it later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!

import { StyleSheet, View } from "react-native";
import { Translate, withLocalize, getLanguages, getActiveLanguage, setActiveLanguage
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not use Translate, getActiveLanguage, or setActiveLanguage in this component, so we can remove those imports.

@@ -112,9 +112,9 @@ class EventsMap extends Component {
const {
props: { events: fetchedEvents }
} = this;
const previousEventIds = prevProps.events.map(event => event.id);
const previousEventIds = prevProps.events.map(event => event._id);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we transition to _id here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The underscore is actually a default of the server, and while we had a server-side i18n library hooked up I took out the underscore on the id to keep that library from getting confused. Now that the library is taken out, though, none of that is necessary, so I changed it back.

@@ -112,7 +111,7 @@ class SettingsPage extends Component {
<Form>
<Item>
<Label><Translate id="settings.language" /></Label>
<Text>{this.props.currentLanguage}</Text>
<Text>{this.props.activeLanguage.name}</Text>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are we getting activeLanguage from?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activeLanguage is a prop from react-localize-redux, along with the translate() function and languages object. These props are added to a component by wrapping the component in withLocalize().

@@ -2,6 +2,7 @@
import React, { Component } from "react";
import { StyleSheet, Text, View } from "react-native";
import { connect } from "react-redux";
import { Translate, withLocalize, setActiveLanguage } from "react-localize-redux";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like Translate is being used in EventsMap, so we can remove that import.

}
// Save settings in redux store
await this.props.saveDevice(deviceSettings);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we saving settings about the language and device into Redux from the EventsMap? If it's because we need to grab settings from local storage and configure the app on first load or something, is there some way we can do that more globally, so that EventsMap isn't responsible for it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question-- I put all of the first-load store fetching stuff in EventsMap because that's the first screen users land on, and putting stuff in App.js didn't work since that's where the framework-level stuff (Redux, React Navigation, NativeBase, react-localize-redux) hooks in, and I wasn't sure where else to put it because React Navigation is in there and the root component this logic should be in wasn't super clear. Open to suggestions!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha! It's fine to leave it for now if we want to just keep moving, but one suggestion would be to create a new component underneath App.js wrapping everything else that just does the saving on componentDidMount and then renders children. Or a Higher Order Component that wraps EventsMap and does what I just described.

I'm just imagining that, if we ever change the first screen that appears, then we'll have to move this logic, or duplicate it if there's a choice for what screen shows up first somehow. Like I said, not a huge deal right now, though. Perhaps this screen will always be first.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! Going to try to add a higher-order wrapper component to hook into in the next PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I merged this is already, but FYI I just came across this in the React Navigation docs, which I might try: https://reactnavigation.org/docs/en/localization.html

// set active language in device (redux store)
await this.props.saveDeviceLanguage(change);
// save device settings to asyncstorage (device storage)
await deviceServices.set(this.props.device);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we not set up device storage as where we store redux data yet? I thought we had in some previous work, but I could be misremembering. If we have done that, do we have to set both redux and device storage separately?

Copy link
Member Author

@arghgr arghgr Oct 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're saving to/fetching from device storage one item at a time, and whenever the user makes a change to a given item, that item has to be changed in both the Redux store and device storage. Not super efficient code-wise, but a) I figured if the Redux store gets big it wouldn't be efficient to save the whole thing to device storage every time the user makes a change, and b) If we want to make async calls (like any call to asyncStorage) in Redux actions, I believe we need to add redux-thunk or something. (But I just noticed that getEvents in EventsMap appears to work even though it's making an API call with an async dispatch function sans thunk.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure that's fine! I only ask because if another dev tries to build similar work, I could see them missing this and forgetting to save to one of the places. I'm just looking for ways to make it was easy as possible for future contributors to expand on our patterns.

And yeah I don't think you need to use redux-thunk for async calls. I'm pretty sure that library just makes it easier to make API calls, removing some of the boilerplate. But it's not required.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do believe that we're already saving all of the Events to Redux already, and that's definitely going to be the vast majority of the size of the Redux store. Maybe we don't worry about whether the Redux store is too big until that actually happens, and so go for the easiest to follow code rather than the most optimized code at this stage?

It's fine to retain this approach, though, especially if we encapsulate saving to both of those places into some kind of utility so we don't have to remember to save to both every time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll do some refactoring of this in the next PR-- I agree that we need more utilities for hooking up Redux and the async store, and if I don't have to add another library just to handle async calls from Redux dispatch, so much the better.

@arghgr arghgr merged commit 40d44df into Cosecha:qa Oct 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants