From a3e55c828a63d6f9b72cbe89f163149371efde7b Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 21 May 2022 13:57:58 -0500 Subject: [PATCH] move useI18n to i18n.ts Since we are using the react-i18n babel loader plugin, useI18n can only be used once per file. Also we have to remember to add the istanbul ignore next comment each time we use it. By moving this to a common file, it can be easily copied and pasted when a new submodule is created and we don't have to remember the rules when calling it. --- src/about/AboutDialog.tsx | 6 +-- src/about/i18n.ts | 8 ++++ src/activities/Activities.tsx | 6 +-- src/activities/i18n.ts | 8 ++++ src/alerts/UnexpectedErrorAlert.tsx | 6 +-- src/alerts/i18n.ts | 8 ++++ src/components/HelpButton.tsx | 6 +-- src/components/HelpDialog.tsx | 6 +-- src/components/i18n.ts | 8 ++++ src/editor/Editor.tsx | 22 +++------ src/editor/i18n.ts | 8 ++++ src/explorer/Explorer.tsx | 46 ++++++------------- src/explorer/alerts/FileInUseAlert.tsx | 6 +-- src/explorer/alerts/i18n.ts | 8 ++++ .../deleteFileAlert/DeleteFileAlert.tsx | 6 +-- src/explorer/deleteFileAlert/i18n.ts | 8 ++++ .../DuplicateFileDialog.tsx | 6 +-- src/explorer/duplicateFileDialog/i18n.ts | 8 ++++ .../fileNameFormGroup/FileNameFormGroup.tsx | 13 ++---- src/explorer/fileNameFormGroup/i18n.ts | 8 ++++ src/explorer/i18n.ts | 8 ++++ src/explorer/newFileWizard/NewFileWizard.tsx | 6 +-- src/explorer/newFileWizard/i18n.ts | 8 ++++ .../renameFileDialog/RenameFileDialog.tsx | 5 +- src/explorer/renameFileDialog/i18n.ts | 8 ++++ src/licenses/LicenseDialog.tsx | 24 +++------- src/licenses/i18n.ts | 8 ++++ src/notifications/NotificationAction.tsx | 7 ++- src/notifications/NotificationMessage.tsx | 7 ++- src/notifications/i18n.ts | 8 ++++ src/settings/Settings.tsx | 6 +-- src/settings/i18n.ts | 8 ++++ src/status-bar/StatusBar.tsx | 28 +++-------- src/status-bar/i18n.ts | 8 ++++ src/terminal/Terminal.tsx | 6 +-- src/terminal/i18n.ts | 8 ++++ .../buttons/bluetooth/BluetoothButton.tsx | 6 +-- src/toolbar/buttons/bluetooth/i18n.ts | 8 ++++ src/toolbar/buttons/flash/FlashButton.tsx | 6 +-- src/toolbar/buttons/flash/i18n.ts | 8 ++++ src/toolbar/buttons/repl/ReplButton.tsx | 6 +-- src/toolbar/buttons/repl/i18n.ts | 8 ++++ src/toolbar/buttons/run/RunButton.tsx | 6 +-- src/toolbar/buttons/run/i18n.ts | 8 ++++ src/toolbar/buttons/stop/StopButton.tsx | 6 +-- src/toolbar/buttons/stop/i18n.ts | 8 ++++ 46 files changed, 256 insertions(+), 168 deletions(-) diff --git a/src/about/AboutDialog.tsx b/src/about/AboutDialog.tsx index 725e5e4f..76206bf8 100644 --- a/src/about/AboutDialog.tsx +++ b/src/about/AboutDialog.tsx @@ -5,7 +5,6 @@ import { AnchorButton, Button, Classes, Dialog } from '@blueprintjs/core'; import { firmwareVersion } from '@pybricks/firmware'; -import { useI18n } from '@shopify/react-i18n'; import React, { useState } from 'react'; import { appName, @@ -17,7 +16,7 @@ import { } from '../app/constants'; import LicenseDialog from '../licenses/LicenseDialog'; import ExternalLinkIcon from '../utils/ExternalLinkIcon'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import './about.scss'; @@ -29,8 +28,7 @@ const AboutDialog: React.VoidFunctionComponent = ({ }) => { const [isLicenseDialogOpen, setIsLicenseDialogOpen] = useState(false); - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); return ( { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const [selectedActivity, setSelectedActivity] = useLocalStorage( 'activities.selectedActivity', diff --git a/src/activities/i18n.ts b/src/activities/i18n.ts index 5d4ea7ef..703906be 100644 --- a/src/activities/i18n.ts +++ b/src/activities/i18n.ts @@ -3,6 +3,14 @@ // // Explorer translation keys. +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Title = 'title', Explorer = 'explorer', diff --git a/src/alerts/UnexpectedErrorAlert.tsx b/src/alerts/UnexpectedErrorAlert.tsx index f1938484..ebbf2978 100644 --- a/src/alerts/UnexpectedErrorAlert.tsx +++ b/src/alerts/UnexpectedErrorAlert.tsx @@ -3,11 +3,10 @@ import './UnexpectedErrorAlert.scss'; import { AnchorButton, Button, ButtonGroup, Collapse, Intent } from '@blueprintjs/core'; -import { useI18n } from '@shopify/react-i18n'; import React, { useState } from 'react'; import { useId } from 'react-aria'; import { CreateToast } from '../i18nToaster'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; type UnexpectedErrorAlertProps = { error: Error; @@ -16,8 +15,7 @@ type UnexpectedErrorAlertProps = { const UnexpectedErrorAlert: React.VoidFunctionComponent = ({ error, }) => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const [isExpanded, setIsExpanded] = useState(false); const labelId = useId(); diff --git a/src/alerts/i18n.ts b/src/alerts/i18n.ts index f6d2a72e..773d6e0f 100644 --- a/src/alerts/i18n.ts +++ b/src/alerts/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Message = 'message', TechnicalInfo = 'technicalInfo', diff --git a/src/components/HelpButton.tsx b/src/components/HelpButton.tsx index 2d911dae..8235a7e6 100644 --- a/src/components/HelpButton.tsx +++ b/src/components/HelpButton.tsx @@ -2,13 +2,12 @@ // Copyright (c) 2022 The Pybricks Authors import { Classes } from '@blueprintjs/core'; -import { useI18n } from '@shopify/react-i18n'; import React, { useCallback, useEffect, useState } from 'react'; import { OverlayContainer } from 'react-aria'; import { useBoolean } from 'usehooks-ts'; import { Button } from './Button'; import HelpDialog from './HelpDialog'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; type HelpButtonProps = { /** The label of the control this button provides help for. */ @@ -21,8 +20,7 @@ const HelpButton: React.VoidFunctionComponent = ({ helpForLabel, content, }) => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const { value: isDialogOpen, diff --git a/src/components/HelpDialog.tsx b/src/components/HelpDialog.tsx index b12611f1..8cd78e71 100644 --- a/src/components/HelpDialog.tsx +++ b/src/components/HelpDialog.tsx @@ -8,7 +8,6 @@ import { POPOVER_ARROW_SVG_SIZE, Popover2Arrow, } from '@blueprintjs/popover2/lib/cjs/popover2Arrow'; -import { useI18n } from '@shopify/react-i18n'; import classNames from 'classnames'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { @@ -21,7 +20,7 @@ import { useOverlay, } from 'react-aria'; import { usePopper } from 'react-popper'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; type HelpDialogProps = { /** The title of the dialog. */ @@ -45,8 +44,7 @@ const HelpDialog: React.FunctionComponent = ({ onAnimationEnd, children, }) => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); // this is the dialog element and the popper element const ref = useRef(null); diff --git a/src/components/i18n.ts b/src/components/i18n.ts index 50636f48..48784536 100644 --- a/src/components/i18n.ts +++ b/src/components/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { HelpButtonLabel = 'helpButton.label', HelpButtonDescription = 'helpButton.description', diff --git a/src/editor/Editor.tsx b/src/editor/Editor.tsx index 9dbe45c5..bc012945 100644 --- a/src/editor/Editor.tsx +++ b/src/editor/Editor.tsx @@ -16,7 +16,6 @@ import { Text, } from '@blueprintjs/core'; import { ContextMenu2, ResizeSensor2 } from '@blueprintjs/popover2'; -import { I18n, useI18n } from '@shopify/react-i18n'; import tomorrowNightEightiesTheme from 'monaco-themes/themes/Tomorrow-Night-Eighties.json'; import xcodeTheme from 'monaco-themes/themes/Xcode_default.json'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; @@ -34,7 +33,7 @@ import { useSelector } from '../reducers'; import { useSettingIsShowDocsEnabled } from '../settings/hooks'; import { isMacOS } from '../utils/os'; import { editorActivateFile, editorCloseFile } from './actions'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import * as pybricksMicroPython from './pybricksMicroPython'; import { pybricksMicroPythonId } from './pybricksMicroPython'; import { UntitledHintContribution } from './untitledHint'; @@ -114,14 +113,12 @@ const EditorContextMenuItem: React.VoidFunctionComponent< type EditorContextMenuProps = { /** The editor. */ editor: monaco.editor.IStandaloneCodeEditor | undefined; - /** Translation context. */ - i18n: I18n; }; const EditorContextMenu: React.VoidFunctionComponent = ({ editor, - i18n, }) => { + const i18n = useI18n(); const selection = editor?.getSelection(); const hasSelection = selection && !selection.isEmpty(); @@ -179,17 +176,13 @@ const EditorContextMenu: React.VoidFunctionComponent = ( type EditorTabsProps = Readonly<{ /** Called when the selected tab changes. */ onChange?: () => void; - /** Translation context. */ - i18n: I18n; }>; -const EditorTabs: React.VoidFunctionComponent = ({ - onChange, - i18n, -}) => { +const EditorTabs: React.VoidFunctionComponent = ({ onChange }) => { const openFiles = useSelector((s) => s.editor.openFiles); const activeFile = useSelector((s) => s.editor.activeFile); const dispatch = useDispatch(); + const i18n = useI18n(); const handleChange = useCallback( (newTabId: TabId) => { @@ -301,8 +294,7 @@ const Editor: React.VFC = () => { const { toggleIsSettingShowDocsEnabled } = useSettingIsShowDocsEnabled(); const { isDarkMode } = useTernaryDarkMode(); - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const options = useMemo( () => ({ @@ -423,7 +415,7 @@ const Editor: React.VFC = () => { return (
- editor?.focus()} i18n={i18n} /> + editor?.focus()} /> editor?.layout()}> { // NB: we have to create a new context menu each time it is // shown in order to get some state, like canUndo and canRedo // that don't have events to monitor changes. - content={() => } + content={() => } popoverProps={popoverProps} > ; type ActionButtonGroupProps = { /** The name of the file (displayed to user) */ item: TreeItem; - /** Translation context. */ - i18n: I18n; }; const FileActionButtonGroup: React.VoidFunctionComponent = ({ item, - i18n, }) => { + const i18n = useI18n(); const dispatch = useDispatch(); const environment = useTreeEnvironment(); @@ -145,16 +142,12 @@ const FileActionButtonGroup: React.VoidFunctionComponent ); }; -type HeaderProps = { - /** Translation context. */ - i18n: I18n; -}; - -const Header: React.VoidFunctionComponent = ({ i18n }) => { +const Header: React.VoidFunctionComponent = () => { const archiveButtonId = useId(); const exportButtonId = useId(); const newButtonId = useId(); const dispatch = useDispatch(); + const i18n = useI18n(); return ( = ({ i18n }) => { /** * Accessibility live descriptors. - * @param i18n Translation context. */ -function useLiveDescriptors(i18n: I18n): LiveDescriptors { +function useLiveDescriptors(): LiveDescriptors { + const i18n = useI18n(); + return useMemo( () => ({ introduction: ` @@ -301,15 +295,10 @@ const renderTreeContainer: typeof renderers.renderTreeContainer = (props) => { return
{renderers.renderTreeContainer(props)}
; }; -type FileTreeProps = { - /** Translation context. */ - i18n: I18n; -}; - -const FileTree: React.VoidFunctionComponent = ({ i18n }) => { +const FileTree: React.VoidFunctionComponent = () => { const [focusedItem, setFocusedItem] = useState(); const files = useFileStorageMetadata() ?? []; - const liveDescriptors = useLiveDescriptors(i18n); + const liveDescriptors = useLiveDescriptors(); const rootItemIndex = 'root'; @@ -326,12 +315,7 @@ const FileTree: React.VoidFunctionComponent = ({ i18n }) => { icon: 'document', secondaryLabel: ( - {(item) => ( - - )} + {(item) => } ), }, @@ -348,7 +332,7 @@ const FileTree: React.VoidFunctionComponent = ({ i18n }) => { }, } as Record, ), - [i18n, files], + [files], ); const getItemTitle = useCallback((item: FileTreeItem) => item.data.fileName, []); @@ -361,6 +345,7 @@ const FileTree: React.VoidFunctionComponent = ({ i18n }) => { ); const dispatch = useDispatch(); + const i18n = useI18n(); return ( @@ -388,14 +373,11 @@ const FileTree: React.VoidFunctionComponent = ({ i18n }) => { }; const Explorer: React.VFC = () => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); - return (
-
+
- + diff --git a/src/explorer/alerts/FileInUseAlert.tsx b/src/explorer/alerts/FileInUseAlert.tsx index b4da1782..4719c654 100644 --- a/src/explorer/alerts/FileInUseAlert.tsx +++ b/src/explorer/alerts/FileInUseAlert.tsx @@ -2,10 +2,9 @@ // Copyright (c) 2022 The Pybricks Authors import { Intent } from '@blueprintjs/core'; -import { useI18n } from '@shopify/react-i18n'; import React from 'react'; import { CreateToast } from '../../i18nToaster'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; type FileInUseAlertProps = { fileName: string; @@ -14,8 +13,7 @@ type FileInUseAlertProps = { const FileInUseAlert: React.VoidFunctionComponent = ({ fileName, }) => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); return <>{i18n.translate(I18nId.FileInUseMessage, { fileName })}; }; diff --git a/src/explorer/alerts/i18n.ts b/src/explorer/alerts/i18n.ts index 8df7b63b..40d331a8 100644 --- a/src/explorer/alerts/i18n.ts +++ b/src/explorer/alerts/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { FileInUseMessage = 'fileInUse.message', } diff --git a/src/explorer/deleteFileAlert/DeleteFileAlert.tsx b/src/explorer/deleteFileAlert/DeleteFileAlert.tsx index 7c35be81..06879e42 100644 --- a/src/explorer/deleteFileAlert/DeleteFileAlert.tsx +++ b/src/explorer/deleteFileAlert/DeleteFileAlert.tsx @@ -2,18 +2,16 @@ // Copyright (c) 2022 The Pybricks Authors import { Alert, Classes, Intent } from '@blueprintjs/core'; -import { useI18n } from '@shopify/react-i18n'; import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { useSelector } from '../../reducers'; import { deleteFileAlertDidAccept, deleteFileAlertDidCancel } from './actions'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; const DeleteFileAlert: React.VoidFunctionComponent = () => { const { isOpen, fileName } = useSelector((s) => s.explorer.deleteFileAlert); const dispatch = useDispatch(); - // istanbul ignore next: babel rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); // a11y: focus primary button when dialog is opened const handleOpened = useCallback((node: HTMLElement) => { diff --git a/src/explorer/deleteFileAlert/i18n.ts b/src/explorer/deleteFileAlert/i18n.ts index 718931cf..4e52d5f1 100644 --- a/src/explorer/deleteFileAlert/i18n.ts +++ b/src/explorer/deleteFileAlert/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Accept = 'action.accept', Cancel = 'action.cancel', diff --git a/src/explorer/duplicateFileDialog/DuplicateFileDialog.tsx b/src/explorer/duplicateFileDialog/DuplicateFileDialog.tsx index 5b3c1383..1413b448 100644 --- a/src/explorer/duplicateFileDialog/DuplicateFileDialog.tsx +++ b/src/explorer/duplicateFileDialog/DuplicateFileDialog.tsx @@ -2,7 +2,6 @@ // Copyright (c) 2022 The Pybricks Authors import { Button, Classes, Dialog } from '@blueprintjs/core'; -import { useI18n } from '@shopify/react-i18n'; import React, { useCallback, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useFileStorageMetadata } from '../../fileStorage/hooks'; @@ -13,11 +12,10 @@ import { import { useSelector } from '../../reducers'; import FileNameFormGroup from '../fileNameFormGroup/FileNameFormGroup'; import { duplicateFileDialogDidAccept, duplicateFileDialogDidCancel } from './actions'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; const DuplicateFileDialog: React.VFC = () => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const dispatch = useDispatch(); const isOpen = useSelector((s) => s.explorer.duplicateFileDialog.isOpen); const oldName = useSelector((s) => s.explorer.duplicateFileDialog.fileName); diff --git a/src/explorer/duplicateFileDialog/i18n.ts b/src/explorer/duplicateFileDialog/i18n.ts index c380ebc0..2a535dd7 100644 --- a/src/explorer/duplicateFileDialog/i18n.ts +++ b/src/explorer/duplicateFileDialog/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Title = 'title', ActionAccept = 'action.accept', diff --git a/src/explorer/fileNameFormGroup/FileNameFormGroup.tsx b/src/explorer/fileNameFormGroup/FileNameFormGroup.tsx index 257ed5dc..71795007 100644 --- a/src/explorer/fileNameFormGroup/FileNameFormGroup.tsx +++ b/src/explorer/fileNameFormGroup/FileNameFormGroup.tsx @@ -2,16 +2,13 @@ // Copyright (c) 2022 The Pybricks Authors import { Classes, FormGroup, InputGroup, Intent, Tag } from '@blueprintjs/core'; -import { I18n, useI18n } from '@shopify/react-i18n'; import React from 'react'; import { FileNameValidationResult } from '../../pybricksMicropython/lib'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; type FileNameHelpTextProps = { /** The result of the file name validation. */ validation: FileNameValidationResult; - /** Translation context. */ - i18n: I18n; }; /** @@ -19,8 +16,9 @@ type FileNameHelpTextProps = { */ const FileNameHelpText: React.VoidFunctionComponent = ({ validation, - i18n, }) => { + const i18n = useI18n(); + switch (validation) { case FileNameValidationResult.IsOk: return <>{i18n.translate(I18nId.HelpTextIsOk)}; @@ -78,8 +76,7 @@ const FileNameFormGroup: React.VoidFunctionComponent = ( inputRef, onChange, }) => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const fileNameIntent = validationResult === FileNameValidationResult.IsOk @@ -90,7 +87,7 @@ const FileNameFormGroup: React.VoidFunctionComponent = ( } + subLabel={} > { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const dispatch = useDispatch(); const isOpen = useSelector((s) => s.explorer.newFileWizard.isOpen); diff --git a/src/explorer/newFileWizard/i18n.ts b/src/explorer/newFileWizard/i18n.ts index e60cfcff..d15943b9 100644 --- a/src/explorer/newFileWizard/i18n.ts +++ b/src/explorer/newFileWizard/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Title = 'title', SmartHubLabel = 'smartHub.label', diff --git a/src/explorer/renameFileDialog/RenameFileDialog.tsx b/src/explorer/renameFileDialog/RenameFileDialog.tsx index 0e562cc9..89fecddc 100644 --- a/src/explorer/renameFileDialog/RenameFileDialog.tsx +++ b/src/explorer/renameFileDialog/RenameFileDialog.tsx @@ -2,7 +2,6 @@ // Copyright (c) 2022 The Pybricks Authors import { Button, Classes, Dialog } from '@blueprintjs/core'; -import { useI18n } from '@shopify/react-i18n'; import React, { useCallback, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useFileStorageMetadata } from '../../fileStorage/hooks'; @@ -13,11 +12,11 @@ import { import { useSelector } from '../../reducers'; import FileNameFormGroup from '../fileNameFormGroup/FileNameFormGroup'; import { renameFileDialogDidAccept, renameFileDialogDidCancel } from './actions'; +import { useI18n } from './i18n'; import { I18nId } from './i18n'; const RenameFileDialog: React.VFC = () => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const dispatch = useDispatch(); const isOpen = useSelector((s) => s.explorer.renameFileDialog.isOpen); const oldName = useSelector((s) => s.explorer.renameFileDialog.fileName); diff --git a/src/explorer/renameFileDialog/i18n.ts b/src/explorer/renameFileDialog/i18n.ts index cbbf27c2..0836aec1 100644 --- a/src/explorer/renameFileDialog/i18n.ts +++ b/src/explorer/renameFileDialog/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Title = 'title', ActionRename = 'action.rename', diff --git a/src/licenses/LicenseDialog.tsx b/src/licenses/LicenseDialog.tsx index 4ced5aa8..1c8b55e2 100644 --- a/src/licenses/LicenseDialog.tsx +++ b/src/licenses/LicenseDialog.tsx @@ -11,7 +11,6 @@ import { NonIdealState, Spinner, } from '@blueprintjs/core'; -import { I18n, useI18n } from '@shopify/react-i18n'; import React, { useCallback, useMemo, useState } from 'react'; import { ControlledTreeEnvironment, @@ -23,7 +22,7 @@ import { import { useFetch } from 'usehooks-ts'; import { appName } from '../app/constants'; import { TreeItemData, renderers } from '../utils/tree-renderer'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import './license.scss'; @@ -40,14 +39,12 @@ type LicenseList = ReadonlyArray; type LicenseListPanelProps = { /** Called when item is clicked. */ onItemClick(info?: LicenseInfo): void; - /** Translation context. */ - i18n: I18n; }; const LicenseListPanel: React.VoidFunctionComponent = ({ onItemClick, - i18n, }) => { + const i18n = useI18n(); const { data, error } = useFetch('static/oss-licenses.json'); const [focusedItem, setFocusedItem] = useState(); const [activeItem, setActiveItem] = useState(); @@ -124,12 +121,12 @@ const LicenseListPanel: React.VoidFunctionComponent = ({ type LicenseInfoPanelProps = { /** The license info to show or undefined if no license info is selected. */ licenseInfo: LicenseInfo | undefined; - /** Translation context. */ - i18n: I18n; }; const LicenseInfoPanel = React.forwardRef( - ({ licenseInfo, i18n }, ref) => { + ({ licenseInfo }, ref) => { + const i18n = useI18n(); + return (
{licenseInfo === undefined ? ( @@ -182,9 +179,7 @@ const LicenseDialog: React.VoidFunctionComponent = ({ }) => { const [licenseInfo, setLicenseInfo] = useState(undefined); const infoDiv = React.useRef(null); - - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); return ( = ({ infoDiv.current?.scrollTo(0, 0); setLicenseInfo(info); }} - i18n={i18n} - /> - +
diff --git a/src/licenses/i18n.ts b/src/licenses/i18n.ts index 01c55045..5b0d8f4b 100644 --- a/src/licenses/i18n.ts +++ b/src/licenses/i18n.ts @@ -3,6 +3,14 @@ // License dialog translation keys. +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Title = 'title', Description = 'description', diff --git a/src/notifications/NotificationAction.tsx b/src/notifications/NotificationAction.tsx index f6f7893b..5fe760a9 100644 --- a/src/notifications/NotificationAction.tsx +++ b/src/notifications/NotificationAction.tsx @@ -3,9 +3,9 @@ // provides translation for notification text -import { Replacements, useI18n } from '@shopify/react-i18n'; +import { Replacements } from '@shopify/react-i18n'; import React from 'react'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; type NotificationActionProps = { messageId: I18nId; @@ -16,8 +16,7 @@ const NotificationAction: React.VoidFunctionComponent = messageId, replacements, }) => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); return <>{i18n.translate(messageId, replacements)}; }; diff --git a/src/notifications/NotificationMessage.tsx b/src/notifications/NotificationMessage.tsx index da5b8167..7fc4e6df 100644 --- a/src/notifications/NotificationMessage.tsx +++ b/src/notifications/NotificationMessage.tsx @@ -3,9 +3,9 @@ // provides translation for notification text -import { Replacements, useI18n } from '@shopify/react-i18n'; +import { Replacements } from '@shopify/react-i18n'; import React from 'react'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; type NotificationMessageProps = { messageId: I18nId; @@ -16,8 +16,7 @@ const NotificationMessage: React.VoidFunctionComponent messageId, replacements, }) => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); let message = i18n.translate(messageId, replacements) as | React.ReactElement diff --git a/src/notifications/i18n.ts b/src/notifications/i18n.ts index 7ac7e55b..d7dea7f7 100644 --- a/src/notifications/i18n.ts +++ b/src/notifications/i18n.ts @@ -3,6 +3,14 @@ // // Notification translation keys. +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { AppNoUpdateFound = 'app.noUpdateFound', BleUnexpectedError = 'ble.unexpectedError', diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index a7d631ab..ccd28ac9 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -12,7 +12,6 @@ import { Label, Switch, } from '@blueprintjs/core'; -import { useI18n } from '@shopify/react-i18n'; import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { useTernaryDarkMode } from 'usehooks-ts'; @@ -36,7 +35,7 @@ import { useSettingHubName, useSettingIsShowDocsEnabled, } from './hooks'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import './settings.scss'; const Settings: React.VoidFunctionComponent = () => { @@ -61,8 +60,7 @@ const Settings: React.VoidFunctionComponent = () => { const dispatch = useDispatch(); - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); return (
diff --git a/src/settings/i18n.ts b/src/settings/i18n.ts index fdb33991..a72e9dfc 100644 --- a/src/settings/i18n.ts +++ b/src/settings/i18n.ts @@ -3,6 +3,14 @@ // // Settings translation keys. +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Title = 'title', AppearanceTitle = 'appearance.title', diff --git a/src/status-bar/StatusBar.tsx b/src/status-bar/StatusBar.tsx index 2a6dc878..224baeed 100644 --- a/src/status-bar/StatusBar.tsx +++ b/src/status-bar/StatusBar.tsx @@ -3,11 +3,10 @@ import { Button, Intent, ProgressBar } from '@blueprintjs/core'; import { Classes as Classes2, Popover2, Popover2Props } from '@blueprintjs/popover2'; -import { I18n, useI18n } from '@shopify/react-i18n'; import React from 'react'; import { BleConnectionState } from '../ble/reducers'; import { useSelector } from '../reducers'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import './status-bar.scss'; @@ -16,12 +15,8 @@ const commonPopoverProps: Partial = { placement: 'top', }; -type HubInfoButtonProps = { - /** Translation context. */ - i18n: I18n; -}; - -const HubInfoButton: React.VoidFunctionComponent = ({ i18n }) => { +const HubInfoButton: React.VoidFunctionComponent = () => { + const i18n = useI18n(); const deviceName = useSelector((s) => s.ble.deviceName); const deviceType = useSelector((s) => s.ble.deviceType); const deviceFirmwareVersion = useSelector((s) => s.ble.deviceFirmwareVersion); @@ -65,14 +60,8 @@ const HubInfoButton: React.VoidFunctionComponent = ({ i18n } ); }; -type BatteryIndicatorProps = { - /** Translation context. */ - i18n: I18n; -}; - -const BatteryIndicator: React.VoidFunctionComponent = ({ - i18n, -}) => { +const BatteryIndicator: React.VoidFunctionComponent = () => { + const i18n = useI18n(); const charging = useSelector((s) => s.ble.deviceBatteryCharging); const lowBatteryWarning = useSelector((s) => s.ble.deviceLowBatteryWarning); @@ -106,17 +95,14 @@ const BatteryIndicator: React.VoidFunctionComponent = ({ }; const StatusBar: React.VFC = (_props) => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); - const connection = useSelector((s) => s.ble.connection); return (
{connection === BleConnectionState.Connected && ( <> - - + + )}
diff --git a/src/status-bar/i18n.ts b/src/status-bar/i18n.ts index f00d261a..cc20a639 100644 --- a/src/status-bar/i18n.ts +++ b/src/status-bar/i18n.ts @@ -3,6 +3,14 @@ // // Status bar translation keys. +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { BatteryTitle = 'battery.title', BatteryLow = 'battery.low', diff --git a/src/terminal/Terminal.tsx b/src/terminal/Terminal.tsx index 504f946a..e6def098 100644 --- a/src/terminal/Terminal.tsx +++ b/src/terminal/Terminal.tsx @@ -3,7 +3,6 @@ import { Menu, MenuDivider, MenuItem, ResizeSensor } from '@blueprintjs/core'; import { ContextMenu2, ContextMenu2ContentProps } from '@blueprintjs/popover2'; -import { useI18n } from '@shopify/react-i18n'; import React, { useContext, useEffect, useMemo, useRef } from 'react'; import { useDispatch } from 'react-redux'; import { useTernaryDarkMode } from 'usehooks-ts'; @@ -12,7 +11,7 @@ import { FitAddon } from 'xterm-addon-fit'; import { isMacOS } from '../utils/os'; import { TerminalContext } from './TerminalContext'; import { receiveData } from './actions'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import 'xterm/css/xterm.css'; @@ -54,8 +53,7 @@ function createContextMenu( xterm: XTerm, ): (props: ContextMenu2ContentProps) => JSX.Element { const contextMenu = (_props: ContextMenu2ContentProps): JSX.Element => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); return ( diff --git a/src/terminal/i18n.ts b/src/terminal/i18n.ts index e8091901..842f7553 100644 --- a/src/terminal/i18n.ts +++ b/src/terminal/i18n.ts @@ -3,6 +3,14 @@ // // Terminal translation keys. +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Copy = 'copy', Paste = 'paste', diff --git a/src/toolbar/buttons/bluetooth/BluetoothButton.tsx b/src/toolbar/buttons/bluetooth/BluetoothButton.tsx index f08051f8..05caaaae 100644 --- a/src/toolbar/buttons/bluetooth/BluetoothButton.tsx +++ b/src/toolbar/buttons/bluetooth/BluetoothButton.tsx @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors -import { useI18n } from '@shopify/react-i18n'; import React from 'react'; import { useDispatch } from 'react-redux'; import { toggleBluetooth } from '../../../ble/actions'; @@ -11,7 +10,7 @@ import { useSelector } from '../../../reducers'; import ActionButton, { ActionButtonProps } from '../../ActionButton'; import connectedIcon from './connected.svg'; import disconnectedIcon from './disconnected.svg'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; type BluetoothButtonProps = Pick; @@ -23,8 +22,7 @@ const BluetoothButton: React.VoidFunctionComponent = ({ id bootloaderConnection === BootloaderConnectionState.Disconnected && bleConnection === BleConnectionState.Disconnected; - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const dispatch = useDispatch(); return ( diff --git a/src/toolbar/buttons/bluetooth/i18n.ts b/src/toolbar/buttons/bluetooth/i18n.ts index 4b021874..a2cb4aeb 100644 --- a/src/toolbar/buttons/bluetooth/i18n.ts +++ b/src/toolbar/buttons/bluetooth/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Label = 'label', TooltipConnect = 'tooltip.connect', diff --git a/src/toolbar/buttons/flash/FlashButton.tsx b/src/toolbar/buttons/flash/FlashButton.tsx index 30e70d0c..29966d1c 100644 --- a/src/toolbar/buttons/flash/FlashButton.tsx +++ b/src/toolbar/buttons/flash/FlashButton.tsx @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors -import { useI18n } from '@shopify/react-i18n'; import React from 'react'; import { useDispatch } from 'react-redux'; import { BleConnectionState } from '../../../ble/reducers'; @@ -14,7 +13,7 @@ import { useSettingHubName, } from '../../../settings/hooks'; import OpenFileButton, { OpenFileButtonProps } from '../../../toolbar/OpenFileButton'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import icon from './icon.svg'; type FlashButtonProps = Pick; @@ -27,8 +26,7 @@ const FlashButton: React.VoidFunctionComponent = ({ id }) => { const [isSettingFlashCurrentProgramEnabled] = useSettingFlashCurrentProgram(); const { hubName } = useSettingHubName(); - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const dispatch = useDispatch(); return ( diff --git a/src/toolbar/buttons/flash/i18n.ts b/src/toolbar/buttons/flash/i18n.ts index 4b6d51f7..72939203 100644 --- a/src/toolbar/buttons/flash/i18n.ts +++ b/src/toolbar/buttons/flash/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Label = 'label', TooltipAction = 'tooltip.action', diff --git a/src/toolbar/buttons/repl/ReplButton.tsx b/src/toolbar/buttons/repl/ReplButton.tsx index ad7e5cd5..2ad36d9f 100644 --- a/src/toolbar/buttons/repl/ReplButton.tsx +++ b/src/toolbar/buttons/repl/ReplButton.tsx @@ -1,21 +1,19 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors -import { useI18n } from '@shopify/react-i18n'; import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { repl } from '../../../hub/actions'; import { HubRuntimeState } from '../../../hub/reducers'; import { useSelector } from '../../../reducers'; import ActionButton, { ActionButtonProps } from '../../ActionButton'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import icon from './icon.svg'; type ReplButtonProps = Pick; const ReplButton: React.VoidFunctionComponent = ({ id }) => { - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const dispatch = useDispatch(); const enabled = useSelector((s) => s.hub.runtime === HubRuntimeState.Idle); diff --git a/src/toolbar/buttons/repl/i18n.ts b/src/toolbar/buttons/repl/i18n.ts index 8a962ff9..14dedb9a 100644 --- a/src/toolbar/buttons/repl/i18n.ts +++ b/src/toolbar/buttons/repl/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Label = 'label', Tooltip = 'tooltip', diff --git a/src/toolbar/buttons/run/RunButton.tsx b/src/toolbar/buttons/run/RunButton.tsx index cd8b142e..18d65a25 100644 --- a/src/toolbar/buttons/run/RunButton.tsx +++ b/src/toolbar/buttons/run/RunButton.tsx @@ -1,14 +1,13 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors -import { useI18n } from '@shopify/react-i18n'; import React from 'react'; import { useDispatch } from 'react-redux'; import { downloadAndRun } from '../../../hub/actions'; import { HubRuntimeState } from '../../../hub/reducers'; import { useSelector } from '../../../reducers'; import ActionButton, { ActionButtonProps } from '../../ActionButton'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import icon from './icon.svg'; type RunButtonProps = Pick; @@ -19,8 +18,7 @@ const RunButton: React.VoidFunctionComponent = ({ id }) => { const isEditorReady = useSelector((s) => s.editor.isReady); const keyboardShortcut = 'F5'; - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const dispatch = useDispatch(); return ( diff --git a/src/toolbar/buttons/run/i18n.ts b/src/toolbar/buttons/run/i18n.ts index 4b6d51f7..72939203 100644 --- a/src/toolbar/buttons/run/i18n.ts +++ b/src/toolbar/buttons/run/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Label = 'label', TooltipAction = 'tooltip.action', diff --git a/src/toolbar/buttons/stop/StopButton.tsx b/src/toolbar/buttons/stop/StopButton.tsx index 5081edc6..82adcc32 100644 --- a/src/toolbar/buttons/stop/StopButton.tsx +++ b/src/toolbar/buttons/stop/StopButton.tsx @@ -1,14 +1,13 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors -import { useI18n } from '@shopify/react-i18n'; import React from 'react'; import { useDispatch } from 'react-redux'; import { stop } from '../../../hub/actions'; import { HubRuntimeState } from '../../../hub/reducers'; import { useSelector } from '../../../reducers'; import ActionButton, { ActionButtonProps } from '../../ActionButton'; -import { I18nId } from './i18n'; +import { I18nId, useI18n } from './i18n'; import icon from './icon.svg'; type StopButtonProps = Pick; @@ -17,8 +16,7 @@ const StopButton: React.VoidFunctionComponent = ({ id }) => { const runtime = useSelector((s) => s.hub.runtime); const keyboardShortcut = 'F6'; - // istanbul ignore next: babel-loader rewrites this line - const [i18n] = useI18n(); + const i18n = useI18n(); const dispatch = useDispatch(); return ( diff --git a/src/toolbar/buttons/stop/i18n.ts b/src/toolbar/buttons/stop/i18n.ts index 8a962ff9..14dedb9a 100644 --- a/src/toolbar/buttons/stop/i18n.ts +++ b/src/toolbar/buttons/stop/i18n.ts @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors +import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n'; + +export function useI18n(): I18n { + // istanbul ignore next: babel-loader rewrites this line + const [i18n] = useShopifyI18n(); + return i18n; +} + export enum I18nId { Label = 'label', Tooltip = 'tooltip',