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

Fully add multi-language capability to blip #473

Merged
merged 32 commits into from
May 1, 2018

Conversation

coyotte508
Copy link
Contributor

@coyotte508 coyotte508 commented Apr 8, 2018

This is a follow-up to #468, with a lot more changes as every translatable string is flagged. Keys are used where html content is concerned, see 4e3b42d

This PR, along with the corresponding PRs in viz and tideline would allow multilanguage capabilities right now.

coyotte508 and others added 25 commits April 8, 2018 12:00
Procedure:
- surround all text to be translated with __()
- npm run update-translations will update the .po files in translations/
- Use appropriate software to add translations
- npm run build-translations will transform the .po files into .json

Then the webpack script takes care of generating a separate index.[xx].html and bundle.[xx].js for each language in dist.

For now, the server only serves index.en.html
Set default language to French (pending a per-user setting)
and add translations for the navbar
- Move the loading of translations out of dependencies/i18n
- Change languages.json from [languageCode] to {languageCode: language}
- Move the language setting out of bootstrap.js and into core/language
- Add language selection in userprofile to change language
- Language selection is only client-side, not yet stored server-side
If the user hasn't yet set a language in their settings, use
the browser's language instead.
Not yet using react-i18next. i18next allows to keep things minimal,
react-i18next would introduce decorators and extracting t from props.

I am still using i18n-extract to extract translations, next step
is using i18next-parser
- Sort translation keys in the JSON
- Translate the front page
- Remove unneeded dependencies
- Fix stuff
The weekly/trends chart have also been updated from "MMM D" to "D MMM" in French
Translations are now extracted from blip's two submodules. That
way they don't need the extra-tooling, only i18next as a dependency.

Furthermore, they can prefix their translatable strings with
'viz|' or 'tideline|' to use a different namespace and separate
their translations from blip's translation.
It gets confused by translate() calls in viz/tideline
@clintonium-119
Copy link
Member

Thanks @coyotte508

We're currently having our company-wide offsite this week. I should be available to dive into reviewing this (and the other corresponding PRs) next week.

@clintonium-119 clintonium-119 self-requested a review April 9, 2018 19:14
@clintonium-119
Copy link
Member

@coyotte508 Just to clarify: This seems to fully replace the #468 PR, correct? I haven't gone through it in-depth yet, but at a glance it seems that this one encompasses everything from it.

@coyotte508
Copy link
Contributor Author

@cbwebdevelomnent yes, this is identical to #468, with more commits to add translations to every component. #468 is the proof of concept, this PR is a generalization to the whole of blip.

@clintonium-119
Copy link
Member

Copy link
Member

@clintonium-119 clintonium-119 left a comment

Choose a reason for hiding this comment

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

Thanks for putting this all together! Looks good 👍

I do have a number of small changes requested, but overall a very clean PR.

The one main change I allude to in one of the review comments is that we'd like to have any translation-related UI changes, such as the language select on the user profile form, be enabled only by a feature flag (set via an envirionment variable).

Here are the local additions I made as a POC to illustrate the approach I recommend:

config.app.js

/* global __I18N_ENABLED__ */

module.exports {
  // ...
  I18N_ENABLED: booleanFromText(__I18N_ENABLED__, false),
};

config.webpack.js

var defineEnvPlugin = new webpack.DefinePlugin({
  // ...
  __I18N_ENABLED__: JSON.stringify(process.env.I18N_ENABLED || false),
});

webpack.config.js

var defineEnvPlugin = new webpack.DefinePlugin({
  // ...
  __I18N_ENABLED__: JSON.stringify(process.env.I18N_ENABLED || false),
});

Then, we can opt-in to show any translation-related UI elements via the config.app.js export

import config from './config';

config.I18N_ENABLED && showStuff();

@@ -190,7 +191,7 @@ var Weekly = React.createClass({

componentWillReceiveProps:function (nextProps) {
if (this.props.loading && !nextProps.loading) {
this.refs.chart.rerenderChart();
this.refs.chart.getWrappedInstance().rerenderChart();
Copy link
Member

Choose a reason for hiding this comment

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

This errors out because the WeeklyChart class above is not wrapped with the translate HOC.

I'd suggest that we wrap the WeeklyChart class for consistency with the other views. We then need to ensure all calls to methods on this.refs.chart are prececeded by getWrappedInstance()

Currently, there are two places where this has not been done:

  1. In componentDidMount
  2. In handleClickOneDay

/* The following is for the translation extracter */
// t('year', {context: 'timeago'});t('years', {context: 'timeago'});
// t('month', {context: 'timeago'});t('months', {context: 'timeago'});
// t('week', {context: 'timeago'});t('minutes', {context: 'timeago'});
Copy link
Member

Choose a reason for hiding this comment

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

Should this be weeks here instead of minutes?

@@ -15,6 +15,8 @@

import React, { Component } from 'react';
import _ from 'lodash';
import { translate, Trans } from 'react-i18next';
import i18next from '../../core/language';
Copy link
Member

Choose a reason for hiding this comment

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

Unused import

import { bindActionCreators } from 'redux';

import _ from 'lodash';
import sundial from 'sundial';
import { validateForm } from '../../core/validation';
import i18next from '../../core/language';
Copy link
Member

Choose a reason for hiding this comment

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

Unused import

return [
{name: 'fullName', label: t('Full name'), type: 'text'},
{name: 'username', label: t('Email'), type: 'email'},
{name: 'lang', label: t('Language'), type: 'select', items: [
Copy link
Member

Choose a reason for hiding this comment

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

Recommend that this be only displayed when enabled by a feature flag. Also, I ran this by our designer, and he'd prefer the language select as the last form input.

Here's what I've done locally as a POC:

const inputs = [
  {name: 'fullName', label: t('Full name'), type: 'text'},
  {name: 'username', label: t('Email'), type: 'email'},
  {name: 'password', label: t('Password'), type: 'password'},
  {name: 'passwordConfirm', label: t('Confirm password'), type: 'password'}
];

const langInput = {
  name: 'lang', label: t('Language'),
  type: 'select', items: [
    {value: 'en', label: 'English'},
    {value: 'fr', label: 'Français'},
  ],
  placeholder: t('Select language...'),
};

config.I18N_ENABLED && inputs.push(langInput);

return inputs;

I'll add additional comments to the main thread re: using a feature flag to enable i18n.

package.json Outdated
@@ -83,11 +87,13 @@
"devDependencies": {
"babel-eslint": "6.1.2",
"babel-plugin-rewire": "1.0.0-rc-6",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
Copy link
Member

Choose a reason for hiding this comment

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

Version needs to be locked down

package.json Outdated
"chai": "3.5.0",
"chromedriver": "2.23.1",
"eslint": "2.13.1",
"eslint-plugin-react": "5.2.2",
"gitbook-cli": "2.3.0",
"i18next-parser": "^1.0.0-beta9",
Copy link
Member

Choose a reason for hiding this comment

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

Version needs to be locked down

fullName: formValues.fullName,
// This seems to be the best place to put language. Preferences and Settings
// both refer to a patient id, but language doesn't have anything to do with patients
language: formValues.lang
Copy link
Member

Choose a reason for hiding this comment

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

I've spoken with our backend engineer responsible for our data model, and he's requested that the appropriate language code be stored in the preferences object as displayLanguageCode

@@ -418,6 +419,10 @@ export function mapStateToProps(state) {
if (state.blip.loggedInUserId === state.blip.currentPatientInViewId) {
userIsCurrentPatient = true;
}

if (user && user.profile && user.profile.language) {
i18next.changeLanguage(user.profile.language);
Copy link
Member

Choose a reason for hiding this comment

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

This will need to be changed once the language key is stored in preferences.displayLanguageCode

@@ -418,6 +419,10 @@ export function mapStateToProps(state) {
if (state.blip.loggedInUserId === state.blip.currentPatientInViewId) {
userIsCurrentPatient = true;
}

if (user && user.profile && user.profile.language) {
Copy link
Member

Choose a reason for hiding this comment

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

Recommend using _.get here:

if (_.get(user, 'profile.language')) {
...
}

@clintonium-119 clintonium-119 changed the base branch from master to i18n April 25, 2018 15:14
@clintonium-119
Copy link
Member

I just changed the base to a new i18n branch (currently identical to master.

This will allow us to get these PR's merged a bit quicker, and will also allow us to create some staging builds on our side so our QA team can smoke test this before we merge into master.

@coyotte508
Copy link
Contributor Author

Thank you. I'm taking note of everything and hope to have the PR ready soon. Since the base has been locked down to the current state of master, I will also fix the current conflicts.

- I18N_ENABLED environment variable to enable language selection in the GUI
- Bugs with `getWrappedInstance()` in the weekly chart
- Move language to preferences.displayLanguageCode
- Remove unused imports
@coyotte508
Copy link
Contributor Author

coyotte508 commented Apr 26, 2018

@cbwebdevelopment Changes were made to this PR, as well as tidepool-org/viz#102 and tidepool-org/tideline#331.

As language is now stored in preferences, the user needs to be fetched
before generating the pdf, so that the web worker can have language
information.
const hasDiabetesData = _.get(nextState, 'processedPatientData.diabetesData.length');

// Ahead-Of-Time pdf generation for non-blocked print popup.
// Whenever patientData is processed or the chartType changes, such as after a refresh
// we check to see if we need to generate a new pdf to avoid stale data
if (patientDataProcessed && hasDiabetesData && !pdfGenerating && !pdfGenerated) {
if (userFetched && patientDataProcessed && hasDiabetesData && !pdfGenerating && !pdfGenerated) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm curious. Were you running into any issues not having this check here? I can't recall anything from the user object being leveraged in the PDFs.

Copy link
Contributor Author

@coyotte508 coyotte508 Apr 30, 2018

Choose a reason for hiding this comment

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

It's to load preferences and the language. When the language information was stored in profile.language there were no issues as it's loaded with the patient, but the preferences are loaded later and as such preferences.displayLanguageCode.

Without userFetched, the generated pdf on a page reload doesn't take into account the language, the user would have to go to their profile and back to the data.

@clintonium-119
Copy link
Member

It all looks good to me. I just have the one question about the need for the userFetched check as a requirement to PDF generation.

Also, it would be good to track that change in a separate PR. If you do move it to a separate PR, I can approve this one right away.

Nice work :) We really appreciate it.

@coyotte508
Copy link
Contributor Author

Thank you :)

I removed the last commit with userFetched, I'm going to make it in a separate PR.

@clintonium-119
Copy link
Member

clintonium-119 commented Apr 30, 2018 via email

@coyotte508
Copy link
Contributor Author

coyotte508 commented Apr 30, 2018

Okay, added the commit back.

@clintonium-119
Copy link
Member

LGTM 👍

@clintonium-119 clintonium-119 merged commit 1442d09 into tidepool-org:i18n May 1, 2018
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.

3 participants