use i18n for buttons

This commit is contained in:
David Lechner
2020-05-27 20:57:22 -05:00
committed by David Lechner
parent 24acab901c
commit 0d30e113da
12 changed files with 84 additions and 16 deletions
+11 -4
View File
@@ -1,17 +1,20 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020 The Pybricks Authors
import { WithI18nProps, withI18n } from '@shopify/react-i18n';
import React from 'react';
import Button from 'react-bootstrap/Button';
import Image from 'react-bootstrap/Image';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Tooltip from 'react-bootstrap/Tooltip';
import { TooltipId } from './button';
import en from './button.en.json';
export interface ActionButtonProps {
/** A unique id for each instance. */
readonly id: string;
/** Tooltip text that appears when hovering over the button. */
readonly tooltip: string;
readonly tooltip: TooltipId;
/** Icon shown on the button. */
readonly icon: string;
/** When true or undefined, the button is enabled. */
@@ -20,14 +23,16 @@ export interface ActionButtonProps {
readonly onAction: () => void;
}
class ActionButton extends React.Component<ActionButtonProps> {
type Props = ActionButtonProps & WithI18nProps;
class ActionButton extends React.Component<Props> {
render(): JSX.Element {
return (
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`${this.props.id}-tooltip`}>
{this.props.tooltip}.
{this.props.i18n.translate(this.props.tooltip)}.
</Tooltip>
}
>
@@ -48,4 +53,6 @@ class ActionButton extends React.Component<ActionButtonProps> {
}
}
export default ActionButton;
export default withI18n({ id: 'actionButton', fallback: en, translations: { en } })(
ActionButton,
);
+3 -2
View File
@@ -8,6 +8,7 @@ import { RootState } from '../reducers';
import { BLEConnectionState } from '../reducers/ble';
import { BootloaderConnectionState } from '../reducers/bootloader';
import ActionButton, { ActionButtonProps } from './ActionButton';
import { TooltipId } from './button';
import btConnectedIcon from './images/bt-connected.svg';
import btDisconnectedIcon from './images/bt-disconnected.svg';
@@ -20,13 +21,13 @@ const mapStateToProps = (state: RootState): StateProps => {
state.bootloader.connection === BootloaderConnectionState.Disconnected
) {
return {
tooltip: 'Connect using Bluetooth',
tooltip: TooltipId.BluetoothConnect,
icon: btDisconnectedIcon,
enabled: true,
};
} else {
return {
tooltip: 'Disconnect Bluetooth',
tooltip: TooltipId.BluetoothDisconnect,
icon: btConnectedIcon,
enabled: state.ble.connection === BLEConnectionState.Connected,
};
+2 -1
View File
@@ -8,6 +8,7 @@ import * as notification from '../actions/notification';
import { RootState } from '../reducers';
import { BootloaderConnectionState } from '../reducers/bootloader';
import OpenFileButton, { OpenFileButtonProps } from './OpenFileButton';
import { TooltipId } from './button';
import firmwareIcon from './images/firmware.svg';
type StateProps = Pick<OpenFileButtonProps, 'enabled'>;
@@ -38,7 +39,7 @@ const mergeProps = (
ownProps: OwnProps,
): OpenFileButtonProps => ({
fileExtension: '.zip',
tooltip: 'Flash hub firmware',
tooltip: TooltipId.Flash,
icon: firmwareIcon,
...ownProps,
...stateProps,
+2 -1
View File
@@ -7,6 +7,7 @@ import * as editor from '../actions/editor';
import * as notification from '../actions/notification';
import { RootState } from '../reducers';
import OpenFileButton, { OpenFileButtonProps } from './OpenFileButton';
import { TooltipId } from './button';
import openIcon from './images/open.svg';
type StateProps = Pick<OpenFileButtonProps, 'enabled'>;
@@ -34,7 +35,7 @@ const mergeProps = (
ownProps: OwnProps,
): OpenFileButtonProps => ({
fileExtension: '.py',
tooltip: 'Open file',
tooltip: TooltipId.Open,
icon: openIcon,
...ownProps,
...stateProps,
+12 -5
View File
@@ -1,12 +1,15 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020 The Pybricks Authors
import { WithI18nProps, withI18n } from '@shopify/react-i18n';
import React from 'react';
import Button from 'react-bootstrap/Button';
import Image from 'react-bootstrap/Image';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Tooltip from 'react-bootstrap/Tooltip';
import Dropzone, { FileRejection } from 'react-dropzone';
import { TooltipId } from './button';
import en from './button.en.json';
export interface OpenFileButtonProps {
/** A unique id for each instance. */
@@ -14,7 +17,7 @@ export interface OpenFileButtonProps {
/** The accepted file extension */
readonly fileExtension: string;
/** Tooltip text that appears when hovering over the button. */
readonly tooltip: string;
readonly tooltip: TooltipId;
/** Icon shown on the button. */
readonly icon: string;
/** When true or undefined, the button is enabled. */
@@ -27,11 +30,13 @@ export interface OpenFileButtonProps {
readonly onClick?: () => void;
}
type Props = OpenFileButtonProps & WithI18nProps;
/**
* Button that opens a file chooser dialog or accepts files dropped on it.
*/
class OpenFileButton extends React.Component<OpenFileButtonProps> {
constructor(props: OpenFileButtonProps) {
class OpenFileButton extends React.Component<Props> {
constructor(props: Props) {
super(props);
this.onDropAccepted = this.onDropAccepted.bind(this);
this.onDropRejected = this.onDropRejected.bind(this);
@@ -80,7 +85,7 @@ class OpenFileButton extends React.Component<OpenFileButtonProps> {
placement="bottom"
overlay={
<Tooltip id={`${this.props.id}-tooltip`}>
{this.props.tooltip}.
{this.props.i18n.translate(this.props.tooltip)}.
</Tooltip>
}
>
@@ -110,4 +115,6 @@ class OpenFileButton extends React.Component<OpenFileButtonProps> {
}
}
export default OpenFileButton;
export default withI18n({ id: 'openFileButton', fallback: en, translations: { en } })(
OpenFileButton,
);
+2 -1
View File
@@ -7,6 +7,7 @@ import { repl } from '../actions/hub';
import { RootState } from '../reducers';
import { HubRuntimeState } from '../reducers/hub';
import ActionButton, { ActionButtonProps } from './ActionButton';
import { TooltipId } from './button';
import replIcon from './images/repl.svg';
type StateProps = Pick<ActionButtonProps, 'enabled'>;
@@ -28,7 +29,7 @@ const mergeProps = (
dispatchProps: DispatchProps,
ownProps: OwnProps,
): ActionButtonProps => ({
tooltip: 'Start REPL in terminal',
tooltip: TooltipId.Repl,
icon: replIcon,
...ownProps,
...stateProps,
+2
View File
@@ -7,6 +7,7 @@ import { downloadAndRun } from '../actions/hub';
import { RootState } from '../reducers';
import { HubRuntimeState } from '../reducers/hub';
import ActionButton, { ActionButtonProps } from './ActionButton';
import { TooltipId } from './button';
import runIcon from './images/run.svg';
type StateProps = Pick<ActionButtonProps, 'enabled'>;
@@ -27,6 +28,7 @@ const mergeProps = (
dispatchProps: DispatchProps,
ownProps: OwnProps,
): ActionButtonProps => ({
tooltip: TooltipId.Run,
icon: runIcon,
...ownProps,
...stateProps,
+2 -1
View File
@@ -6,6 +6,7 @@ import { Dispatch } from '../actions';
import * as editor from '../actions/editor';
import { RootState } from '../reducers';
import ActionButton, { ActionButtonProps } from './ActionButton';
import { TooltipId } from './button';
import downloadIcon from './images/download.svg';
type StateProps = Pick<ActionButtonProps, 'enabled'>;
@@ -27,7 +28,7 @@ const mergeProps = (
dispatchProps: DispatchProps,
ownProps: OwnProps,
): ActionButtonProps => ({
tooltip: 'Download file',
tooltip: TooltipId.SaveAs,
icon: downloadIcon,
...ownProps,
...stateProps,
+2 -1
View File
@@ -7,6 +7,7 @@ import { stop } from '../actions/hub';
import { RootState } from '../reducers';
import { HubRuntimeState } from '../reducers/hub';
import ActionButton, { ActionButtonProps } from './ActionButton';
import { TooltipId } from './button';
import stopIcon from './images/stop.svg';
type StateProps = Pick<ActionButtonProps, 'enabled'>;
@@ -26,7 +27,7 @@ const mergeProps = (
dispatchProps: DispatchProps,
ownProps: OwnProps,
): ActionButtonProps => ({
tooltip: 'Stop everything',
tooltip: TooltipId.Stop,
icon: stopIcon,
...ownProps,
...stateProps,
+12
View File
@@ -0,0 +1,12 @@
{
"open": { "tooltip": "Open file" },
"saveAs": { "tooltip": "Download file" },
"stop": { "tooltip": "Stop everything" },
"run": { "tooltip": "Download and run this program" },
"repl": { "tooltip": "Start REPL in terminal" },
"bluetooth": {
"connect": { "tooltip": "Connect using Bluetooth" },
"disconnect": { "tooltip": "Disconnect Bluetooth" }
},
"flash": { "tooltip": "Flash hub firmware" }
}
+21
View File
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020 The Pybricks Authors
import { TooltipId } from './button';
import en from './button.en.json';
function lookup(obj: object, id: string): string | undefined {
const value = id
.split('.')
.reduce((pv, cv) => pv && (pv as Record<string, object>)[cv], obj);
if (typeof value === 'string') {
return value;
}
return undefined;
}
describe('Ensure .json file has matches for TooltipIds', () => {
test.each(Object.values(TooltipId))('%s', (id) => {
expect(lookup(en, id)).toBeDefined();
});
});
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020 The Pybricks Authors
export enum TooltipId {
Open = 'open.tooltip',
SaveAs = 'saveAs.tooltip',
Run = 'run.tooltip',
Stop = 'stop.tooltip',
Repl = 'repl.tooltip',
Flash = 'flash.tooltip',
BluetoothConnect = 'bluetooth.connect.tooltip',
BluetoothDisconnect = 'bluetooth.disconnect.tooltip',
}