-
Notifications
You must be signed in to change notification settings - Fork 75
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
Add a simple translations model #4019
Conversation
483c1c5
to
8ca48ec
Compare
Some links for testing: Spanish translation: https://translation-api.pfe-preview.zooniverse.org/projects/eatyourgreens/-project-testing-ground?language=es Italian translation: https://translation-api.pfe-preview.zooniverse.org/projects/eatyourgreens/-project-testing-ground?language=it Greek translation (doesn't exist, falls back to English): https://translation-api.pfe-preview.zooniverse.org/projects/eatyourgreens/-project-testing-ground?language=el FIXED: |
01b002e
to
e4bd0f9
Compare
Moving the localisation of the interface text into #4027 |
7f3a4cd
to
910b3a8
Compare
Rebased off #4027 instead of master. |
73064c1
to
beb59a0
Compare
Rebased to latest master. |
f2e4988
to
dfcd0e3
Compare
Rebased to bring in updates to the survey task summary. |
ecc7004
to
2ce959b
Compare
TODO: mock up some translations for the tests. Survey task tests are failing because default translations don't exist. |
852601e
to
c519179
Compare
Following discussions on Slack yesterday, components might be loaded in from zooniverse/Zooniverse-React-Components. So, components shouldn't have to decide where strings are coming from (a translation resource or the original Panoptes resource.) In that case, translations might be better handled in a wrapper and passed down to individual components as a prop, to avoid awkward code like |
7b07d49
to
03cbbf8
Compare
import React from 'react'; | ||
import translations from '../../../pages/project/translations'; | ||
|
||
function SurveyTranslations(props) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it really hard to follow what this function does 😞 (and I wrote it!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is really hard. I lost track at the 6th "characteristics". I know I always say this but some destructuring may help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any specific thoughts on how destructuring would work here? I had the same thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've started to rewrite the first block and I've got this
const taskStrings = translations.strings.workflow.tasks;
const { characteristics, choices, questions } = task;
Object.keys(characteristics).map((characteristicId) => {
translation.characteristics[characteristicId] = Object.assign({}, characteristics[characteristicId]);
if (taskStrings[`${props.taskKey}.characteristics.${characteristicId}.label`]) {
translation.characteristics[characteristicId].label = taskStrings[`${props.taskKey}.characteristics.${characteristicId}.label`];
}
translation.characteristics[characteristicId].values = Object.assign({}, characteristics[characteristicId].values);
Object.keys(task.characteristics[characteristicId].values).map((valueId) => {
translation.characteristics[characteristicId].values[valueId] = Object.assign({}, characteristics[characteristicId].values[valueId]);
if (taskStrings[`${props.taskKey}.characteristics.${characteristicId}.values.${valueId}.label`]) {
translation.characteristics[characteristicId].values[valueId].label =
taskStrings[`${props.taskKey}.characteristics.${characteristicId}.values.${valueId}.label`];
}
});
});
which is slightly less convoluted, but I'd appreciate any advice you can offer to simplify it further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only thing I can think of is moving the second .map
block out to its own method. Just to give the eyes a little rest. I'll keep thinking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Roger wrote this for WGE at some point. I think it does what you want.
/**
* Simple is object check.
* @param item
* @returns {boolean}
*/
export function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item) && item !== null);
}
/**
* Deep merge two objects.
* @param target
* @param source
*/
export function mergeDeep(target, source) {
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
});
}
return target;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deep merge will get rid of all the Object.assign
calls.
I'll have a think about how to reduce the number of maps. Maybe I'm looking at this the wrong way round and the code should be looping through taskStrings
in order to assign translation strings to the translation
object.
|
||
function SurveyTranslations(props) { | ||
const { task } = props; | ||
const translation = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe try const translation = Object.assign({}, task)
here, so that translation labels automatically default to the workflow task labels, and the following code can be greatly simplified.
I can't remember if Object.assign()
does a deep merge of nested objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Specifically, this needs to be done in such a way that properties on translation
aren't pointers to the original properties on task
. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it does
The Object.assign() method only copies enumerable and own properties from a source object to a target object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the MDN article I linked to says that it creates references, not actual copies. We want to be able to change translation
without changing task
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lodash has a deep merge method and we have it as a dependency. I think it might be used in the codebase somewhere already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll have a look. I've rewritten the object cloning in cc21143 to make better use of temp variables and destructuring.
Here, we want to overwrite a key's value if a corresponding key exists in the Panoptes translation resource. The translation wrapper basically gives us something that looks like a task (but contains translated text when available) for backwards compatibility with projects that haven't been migrated over to use translations yet.
I'm also thinking that it might allow us to wrap ZRC components, and pass text into them via a translation prop. Code that doesn't use translations yet should still work by passing a task object as the translation (since the structures are identical) <Task translation={workflow.tasks[taskKey]} />
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also hoping that these adapters can be ripped out, or greatly simplified, once every Panoptes resource has been migrated across to use translations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, maybe translations is a good case for a generalized higher order component in ZRC?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly. I'm finding each resource needs its own, specific adapter component. There isn't much consistency between tasks, for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I can figure out a generalised map from taskStrings
to translation
then all of this can be turned into a generic adapter for workflow task translations.
Generic translations for all resources might require some reworking of Panoptes internals. I remember @camallen saying that workflow translations have kept the awkward structure that they have here, with the long string keys for tasks, because changing those keys would break anything that relies on them, like classification exports.
app/pages/project/translations.js
Outdated
counterpart.setFallbackLocale('en'); | ||
|
||
|
||
const translations = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all works, but should probably be replaced with a Redux store, and load
action, at some point.
@@ -69,7 +73,7 @@ const ProjectHomePage = (props) => { | |||
|
|||
<div> | |||
<img role="presentation" src={avatarSrc} /> | |||
<span>"{props.project.researcher_quote}"</span> | |||
<span>"{translations.strings.project.researcher_quote || props.project.researcher_quote}"</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This, and subsequent changes, should be updated to use the translation wrapper pattern that's being used by workflow tasks.
b5dec24
to
2d84685
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for clarifying what's to be expected. I only have one minor comment about the new tests and The Shortcut translations prop also is throwing a prop type warning on the test survey project when using the default English translation:
Warning: Failed prop type: The prop `translation` is marked as required in `Shortcut`, but its value is `undefined`.
}; | ||
|
||
const project = { | ||
id: 12345, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker, but for accuracy with these mocks, the ids should be strings.
@srallen Thanks for taking a look. I will have a look to see why the Shortcut task is failing like that. |
@srallen I can see the required prop warning, but the |
@eatyourgreens ok, it's not a blocker since it didn't seem to break anything, but it's nice to remove warning messages when we can. I'll do one last look over today. |
I think this will ready to merge after a final rebase. |
Adds a simple object to load and store translations. Adds translations to some project components.
Adds a general translation wrapper for all workflow tasks. Adds tests for the task translation wrapper.
Pass project translations as a separate object from project resources.
Break up some long variable names in the characteristics filter.
Translate the correct task for shortcuts. Validate shortcut answers as an array.
Move the translations object to a redux store. Add actions for loading and setting resource translations. Remove the original store object. Update ask translation tests to work with redux.
Renames project-translations for consistency.
Report resource type, ID, language and HTTP status code in API error warnings.
d8a33c9
to
1672d5d
Compare
Project and workflows front-end for the translations API from zooniverse/panoptes#2435.
Gets localised strings from the translations API.
Uses Redux to store the resource translations.
Updates some project and task components to use localised strings instead of API resource properties (like
project.display_name
.)Review Checklist
rm -rf node_modules/ && npm install
and app works as expected?Optional
ChangeListener
orPromiseRenderer
components with code that updates component state?