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

[BD-46] feat: implement i18n in Paragon components and in docs site #1100

Merged
merged 10 commits into from
Jun 17, 2022

Conversation

viktorrusakov
Copy link
Contributor

@viktorrusakov viktorrusakov commented Feb 18, 2022

This is an ongoing work to implement i18n support in Paragon.

To see i18n in action go to https://deploy-preview-1100--paragon-openedx.netlify.app/components/alert/?feature=LANGUAGE_SWITCHER, switch language in settings menu and see how the label of 'Dismiss' button of the Alert changes (there are sample translation for this label in this PR).

JIRA:

@netlify
Copy link

netlify bot commented Feb 18, 2022

Deploy Preview for paragon-openedx ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 18760fd
🔍 Latest deploy log https://app.netlify.com/sites/paragon-openedx/deploys/62ab184f94329c000951cb0a
😎 Deploy Preview https://deploy-preview-1100--paragon-openedx.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Feb 18, 2022

✔️ Deploy Preview for paragon-edx ready!

🔨 Explore the source changes: 08ebdef

🔍 Inspect the deploy log: https://app.netlify.com/sites/paragon-edx/deploys/623058c031fb4e00087735a5

😎 Browse the preview: https://deploy-preview-1100--paragon-edx.netlify.app

@openedx-webhooks
Copy link

openedx-webhooks commented Feb 18, 2022

Thanks for the pull request, @viktorrusakov!

When this pull request is ready, tag your edX technical lead.

@openedx-webhooks openedx-webhooks added blended PR is managed through 2U's blended developmnt program needs triage labels Feb 18, 2022
@viktorrusakov viktorrusakov marked this pull request as draft February 18, 2022 09:46
@@ -24,7 +24,7 @@ const Card = React.forwardRef(({
clickable: isClickable,
})}
ref={ref}
tabindex={isClickable ? '0' : '-1'}
Copy link
Member

Choose a reason for hiding this comment

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

[inform] This was fixed in a separate PR and should be all set upon a rebase of this branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, rebasing removed it, thanks!

};

DataTableLayout.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired,
filtersTitle: PropTypes.string,
Copy link
Member

Choose a reason for hiding this comment

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

This prop should likely accept things beyond strings (either node or element maybe?) , e.g. say a consumer wants to wrap the title in a custom element with a class name, (e.g., filtersTitle={<span className="...">{...}</span>}).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it definitely should, missed it, thanks

{clearFiltersText || (
<FormattedMessage
id="pgn.DataTable.FilterStatus.clearFiltersText"
defaultMessage="Clear Filters"
Copy link
Member

Choose a reason for hiding this comment

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

Lowercase "filters"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated, thanks

@@ -1,12 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Button } from '..';

const ExpandAll = ({ getToggleAllRowsExpandedProps, isAllRowsExpanded }) => (
<span {...getToggleAllRowsExpandedProps()}>
{isAllRowsExpanded
Copy link
Member

Choose a reason for hiding this comment

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

formatting nit:

{isAllRowsExpanded ? (
  <Button variant="link" size="inline">
    <FormattedMessage
      id="pgn.DataTable.ExpandAll.collapseAllLabel"
      defaultMessage="Collapse all"
      description="Title of the filters components"
    />
  </Button>
) : (
  <Button variant="link" size="inline">
    <FormattedMessage
      id="pgn.DataTable.ExpandAll.expandAllLabel"
      defaultMessage="Expand all"
      description="Title of the filters components"
    />
  </Button>
)}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, this looks better, thanks 🙂

className: PropTypes.string,
/** A text that appears on the `Clear selection` button, defaults to 'Clear Selection' */
clearSelectionText: PropTypes.string,
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 also handle elements in addition to strings as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it should, thanks, updated

floatingLabel="Direction"
>
<option value="ltr">Left to right</option>
<option value="rtl">Right to left</option>
</Form.Control>
</Form.Group>
)}
{FEATURES.LANGUAGE_SWITCHER && (
Copy link
Member

Choose a reason for hiding this comment

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

Let's maybe use Stack here to add a bit of vertical space between these 3 Form.Control components (e.g., <Stack gap={5}>) ?

Copy link
Contributor Author

@viktorrusakov viktorrusakov Mar 15, 2022

Choose a reason for hiding this comment

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

Good idea! Added Stack gap={3}, setting gap to 5 seemed like too much extra space

document.body.setAttribute('dir', value);
}
setSettings(prevState => ({ ...prevState, [key]: value }));
global.localStorage.setItem('pgn__settings', JSON.stringify({ ...settings, [key]: value }));
Copy link
Member

Choose a reason for hiding this comment

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

Nice, this is a great idea to colocate the settings to a single storage item!

@viktorrusakov viktorrusakov force-pushed the vrusakov/implement-i18n branch 3 times, most recently from ba82c89 to 08ebdef Compare March 15, 2022 09:13
@codecov
Copy link

codecov bot commented Mar 15, 2022

Codecov Report

Merging #1100 (18760fd) into master (0765840) will increase coverage by 0.00%.
The diff coverage is 93.10%.

@@           Coverage Diff           @@
##           master    #1100   +/-   ##
=======================================
  Coverage   90.87%   90.87%           
=======================================
  Files         198      199    +1     
  Lines        3266     3288   +22     
  Branches      735      753   +18     
=======================================
+ Hits         2968     2988   +20     
- Misses        284      286    +2     
  Partials       14       14           
Impacted Files Coverage Δ
src/DataTable/DataTableLayout.jsx 87.50% <ø> (ø)
.../DataTable/selection/ControlledSelectionStatus.jsx 100.00% <ø> (ø)
src/DataTable/selection/SelectionStatus.jsx 100.00% <ø> (ø)
src/DataTable/SidebarFilters.jsx 25.00% <60.00%> (+12.50%) ⬆️
src/Alert/index.jsx 97.36% <100.00%> (+0.14%) ⬆️
src/DataTable/ExpandAll.jsx 100.00% <100.00%> (ø)
src/DataTable/FilterStatus.jsx 100.00% <100.00%> (ø)
src/DataTable/RowStatus.jsx 100.00% <100.00%> (ø)
src/DataTable/selection/BaseSelectionStatus.jsx 100.00% <100.00%> (ø)
src/Toast/index.jsx 75.00% <100.00%> (+3.57%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 86d19c1...18760fd. Read the comment docs.

@viktorrusakov viktorrusakov force-pushed the vrusakov/implement-i18n branch from 08ebdef to 61e880b Compare March 22, 2022 16:48
@viktorrusakov viktorrusakov marked this pull request as ready for review March 22, 2022 16:53
@viktorrusakov viktorrusakov changed the title [BD-46] feat: implement i18n in Paragon component and in docs site [BD-46] feat: implement i18n in Paragon components and in docs site Mar 22, 2022
[main]
host = https://www.transifex.com

[edx-platform.paragon]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should this be a separate project, not edx-platform?

Copy link
Member

Choose a reason for hiding this comment

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

As discussed, we will keep this under edx-platform for the time being, as it seems most of the projects that consume Paragon are under this project as well. I agree it's a bit odd, though.

@viktorrusakov viktorrusakov force-pushed the vrusakov/implement-i18n branch from bf1ef73 to f14c771 Compare March 25, 2022 12:36
Copy link
Member

@adamstankiewicz adamstankiewicz left a comment

Choose a reason for hiding this comment

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

Awesome! Left a few nits here and there, but this is looking great 🎉

[main]
host = https://www.transifex.com

[edx-platform.paragon]
Copy link
Member

Choose a reason for hiding this comment

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

As discussed, we will keep this under edx-platform for the time being, as it seems most of the projects that consume Paragon are under this project as well. I agree it's a bit odd, though.

.tx/config Outdated
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = STRUCTURED_JSON
Copy link
Member

Choose a reason for hiding this comment

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

nit: newline

Makefile Outdated
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json

NPM_TESTS=build i18n_extract lint test is-es5
Copy link
Member

Choose a reason for hiding this comment

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

I think the is-es5 script can be removed, as Paragon does not have it defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

true, thanks!

Makefile Outdated
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:
# Checking for package-lock.json changes...
git diff --exit-code package-lock.json
Copy link
Member

Choose a reason for hiding this comment

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

nit: newline

README.md Outdated
)
```

Note that if you are using ``frontend-platform``'s ``AppProvider`` component you don't need a separate context,
Copy link
Member

Choose a reason for hiding this comment

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

nit: @edx/frontend-platform

@@ -56,10 +56,10 @@ Consequences
ReactDOM.render(
<AppProvider>
<App />
</IntlProvider>,
</AppProvider>,
Copy link
Member

Choose a reason for hiding this comment

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

D'oh ;)

package.json Outdated
@@ -47,6 +48,7 @@
"prop-types": "^15.8.1",
"react-bootstrap": "^1.6.4",
"react-focus-on": "^3.5.4",
"react-intl": "2.9.0",
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for keeping this pinned 🙌

Comment on lines 64 to 67
it('hides filter text', () => {
const wrapper = mount(<FilterStatusWrapper value={instance} props={filterPropsNoFiltered} />);
expect(wrapper.text()).toEqual('');
});
Copy link
Member

Choose a reason for hiding this comment

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

Should we be removing this test? Is there another test covering the showFilteredFields={false} case? There is at least one or two teams using this prop to hide the "Filtered by..." text because it shows the accessor's value which may be a programmatic value, e.g. "Filtered by requestStatus".

Copy link
Contributor Author

@viktorrusakov viktorrusakov Apr 1, 2022

Choose a reason for hiding this comment

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

Yeah, my bad...fixed it and reverted the change, glad you noticed, thanks!

@@ -69,7 +69,7 @@ const PropsTable = ({ props: componentProps, displayName, content }) => (
{content && <div className="small mb-3">{content}</div>}
{componentProps.length > 0 ? (
<ul className="list-unstyled">
{componentProps.map(metadata => <Prop key={metadata.name} {...metadata} />)}
{componentProps.map(metadata => metadata.name !== 'intl' && <Prop key={metadata.name} {...metadata} />)}
Copy link
Member

Choose a reason for hiding this comment

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

nit: think it's worth creating a IGNORED_COMPONENT_PROPS=['intl'] (or similar) and use it in a .filter(...) to make this simple to add to in the future if needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This definitely seems like a better approach, thanks 🙂

@adamstankiewicz
Copy link
Member

@viktorrusakov Is there any way we'd be able to support a case where a code repository is using @edx/paragon (with i18n) and does not use @edx/frontend-platform, but already has react-intl@5 installed? Will this reliance on the older react-intl@2 in Paragon cause issue in this case?

Related: One idea worth exploring here is whether we prioritize getting react-intl upgraded in frontend-platform to v5 instead of v2, so that we don't have these version conflicts. There is an issue about this on the Frontend Working Group board. I'd be curious to hear your thoughts on this as well 😄

@viktorrusakov viktorrusakov force-pushed the vrusakov/implement-i18n branch from f14c771 to c607100 Compare April 1, 2022 06:05
@viktorrusakov
Copy link
Contributor Author

@adamstankiewicz I'm pretty positive that there will be issues if provider has react-intl@5. I believe the context component provided by react-intl@5 is not compatible with react-intl@2, so Paragon's components will not be able to receive required translations this way. I might be wrong though, I can test it to be sure.

As for updating react-intl in frontend-platform I do believe that's the best approach, as the issue you linked stated we are falling quite a bit behind, since there has already been 3 major releases (from react-intl@2 to react-intl@5). I don't know how much work it would take though...
Also, if we do this it would be a good opportunity to also update Transifex scripts and remove deprecated packages from usage😉

@adamstankiewicz
Copy link
Member

@viktorrusakov Yeah, it might be worth a manual test, if possible. I'm also leaning towards prioritizing the upgrade to react-intl@5 in @edx/frontend-platform regardless as an enabler for this i18n change in Paragon.

I just added some notes to the linked issue about the impacts of each breaking change in react-intl since v2. It doesn't seem like it would be too bad of a migration for @edx/frontend-platform or consumers, and we'll have to do it regardless one of these days 😅

Let me know your thoughts after you give those release notes a review! Maybe we can chat about this in more detail during our "in-person" sync today.

@adamstankiewicz
Copy link
Member

Also, if we do this it would be a good opportunity to also update Transifex scripts and remove deprecated packages from usage😉

@viktorrusakov Yes, I agree we should do something here as well. I'm not sure if we will directly get involved with upgrading Transifex scripts in each project, though we could likely go that route, if we'd like. I might be leaning more towards creating the documentation / examples for it, and empowering teams to make the improvements themselves. Open to your thoughts here on methodology, too, of course 😄

@viktorrusakov
Copy link
Contributor Author

@adamstankiewicz Tested Paragon's i18n implementation with react-intl@5 in consuming application, unfortunately it does't work😞, Paragon just doesn't recognize IntlProvider component from future releases (I've also tested it withreact-intl@3 - same problem), here's the error I get
[React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry. Using default message as fallback.
Once I downgrade to react-intl@2 this goes away and i18n works just fine in consuming app.

Judging from the breaking changes list for v3 release I think the reason might be

IntlProvider no longer inherits from upstream IntlProvider

I don't know what this changes exactly 😅 but IntlProvider might have been changed incompatibly with previous releases, that's my guess. Also, they are saying that in v3 they started using new React Context API, this might also be one of the reasons.

@adamstankiewicz adamstankiewicz mentioned this pull request May 20, 2022
17 tasks
@adamstankiewicz adamstankiewicz linked an issue May 20, 2022 that may be closed by this pull request
@viktorrusakov viktorrusakov force-pushed the vrusakov/implement-i18n branch 3 times, most recently from c259dce to cd58bdb Compare June 3, 2022 08:10
Copy link
Member

@adamstankiewicz adamstankiewicz left a comment

Choose a reason for hiding this comment

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

Hey @viktorrusakov, I left a few comments throughout the PR. Two notable concerns:

  1. Should we treat react-intl as a peer dependency (see comment for more details)?
  2. If the default message is from a variable defined as a constant (as in Alert), it doesn't appear that its string is exported in the exported transifex_input.json file, e.g.:
{
  "pgn.Alert.closeLabel": {
    "developer_comment": "Label of a close button on Alert component"
  },
}

On another note, I'm working to get the PRs for the Transifex pipeline job created for the repo in our Jenkins instance as well along with alerting for when the jobs fail.

package.json Outdated
@@ -55,6 +56,7 @@
"prop-types": "^15.8.1",
"react-bootstrap": "^1.6.4",
"react-focus-on": "^3.5.4",
"react-intl": "^5.25.0",
Copy link
Member

Choose a reason for hiding this comment

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

Should we treat react-intl as a peer dependency, especially since it's already included as a dependency in @edx/frontend-platform? That way, @edx/frontend-platform's version of react-intl would remain the source of truth. It would also allow peer dependency warnings during install if the consumer doesn't have react-intl (at the correct version) in their installed dependency tree.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree, that's a great idea!

<FormattedMessage
id="pgn.DataTable.ExpandAll.expandAllLabel"
defaultMessage="Expand all"
description="Title of the filters components"
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 description differ from the "Collapse all" message above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah... and "Collapse all" message also should be different and not copied from some filters component 😅
updated, thanks!

@@ -23,7 +24,15 @@ const FilterStatus = ({
size={size}
onClick={() => setAllFilters([])}
>
{clearFiltersText}
{clearFiltersText === undefined
Copy link
Member

Choose a reason for hiding this comment

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

[curious] Why the explicit check for the undefined type versus relying on a falsey value alone? For example, if consumer passes an empty string, I'd think we might still want to fallback to the default message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question! Initially I tried to check for falsey value but decided against it because there is a specific test case which tests that no text gets rendered if you pass an empty string, it was failing.

I could change that test but I thought that maybe there is a use case for not displaying any text..

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, interesting. I suppose that could be possible; best to handle it explicitly I guess, considering there is a specific test for it 🤷‍♂️ Though, I cant imagine why you'd want to do this haha.

<FormattedMessage
id="pgn.DataTable.BaseSelectionStatus.selectAllText"
defaultMessage="Select all {itemCount}"
description="Clear selection button label"
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 description reflect that it's the "Select all" message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely, thanks!

const [autoHide, setAutoHide] = useState(true);
const intlCloseLabel = closeLabel || intl.formatMessage({
id: 'pgn.Toast.closeLabel',
defaultMessage: 'Close',
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 using the TOAST_CLOSE_LABEL_TEXT constant?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe not, if the string isn't getting extracted when this is treated as a constant 🙃

<Form.Group>
<Form.Control
as="select"
value={currentTheme}
onChange={onThemeChange}
value={settings.theme}
Copy link
Member

Choose a reason for hiding this comment

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

Should the Form.Control for the direction switcher also be reading from settings?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good catch, thanks! Rebasing is hard 🙃

@viktorrusakov viktorrusakov force-pushed the vrusakov/implement-i18n branch from fb7519d to 18760fd Compare June 16, 2022 11:47
Copy link
Member

@adamstankiewicz adamstankiewicz left a comment

Choose a reason for hiding this comment

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

LGTM 🚀 I have a PR created that is also awaiting review from our DevOps team to enable the Transifex pipeline jobs on Jenkins to automate the push/pull of translations for the repo.

@@ -23,7 +24,15 @@ const FilterStatus = ({
size={size}
onClick={() => setAllFilters([])}
>
{clearFiltersText}
{clearFiltersText === undefined
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, interesting. I suppose that could be possible; best to handle it explicitly I guess, considering there is a specific test for it 🤷‍♂️ Though, I cant imagine why you'd want to do this haha.

@adamstankiewicz adamstankiewicz merged commit 53e0ac6 into openedx:master Jun 17, 2022
@openedx-webhooks
Copy link

@viktorrusakov 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future.

edx-semantic-release pushed a commit that referenced this pull request Jun 17, 2022
# [20.0.0](v19.25.3...v20.0.0) (2022-06-17)

* feat!: implement i18n in Paragon components and in docs site (#1100) ([53e0ac6](53e0ac6)), closes [#1100](#1100)

### BREAKING CHANGES

* By adding i18n support to the Paragon design system, we are introducing a peer dependency on `[email protected]` or greater. This may be a breaking change for some consumers, if your repository:
* Uses v1 of `@edx/frontend-platform`
* Uses older version of `react-intl` than v5.25.0 directly.
* Does not use `react-intl`.
@edx-semantic-release
Copy link
Contributor

🎉 This PR is included in version 20.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blended PR is managed through 2U's blended developmnt program released
Projects
No open projects
Archived in project
Development

Successfully merging this pull request may close these issues.

i18n support for Paragon components
4 participants