mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 09:36:27 +00:00
i18n: use automatic type inference of translation keys
Finally figured out a way to make typescript strongly type translation keys so we can avoid the tedium of creating enums of translation keys.
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
} from '../app/constants';
|
||||
import LicenseDialog from '../licenses/LicenseDialog';
|
||||
import ExternalLinkIcon from '../utils/ExternalLinkIcon';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
import './about.scss';
|
||||
|
||||
@@ -32,7 +32,7 @@ const AboutDialog: React.VoidFunctionComponent<AboutDialogProps> = ({
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={i18n.translate(I18nId.Title, { appName })}
|
||||
title={i18n.translate('title', { appName })}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
@@ -41,7 +41,7 @@ const AboutDialog: React.VoidFunctionComponent<AboutDialogProps> = ({
|
||||
<img src="favicon.ico" />
|
||||
</div>
|
||||
<p>
|
||||
<strong>{i18n.translate(I18nId.Description)}</strong>
|
||||
<strong>{i18n.translate('description')}</strong>
|
||||
</p>
|
||||
<p>{`v${firmwareVersion} (${appName} v${appVersion})`}</p>
|
||||
<p>{pybricksCopyright}</p>
|
||||
@@ -52,14 +52,14 @@ const AboutDialog: React.VoidFunctionComponent<AboutDialogProps> = ({
|
||||
</p>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button onClick={() => setIsLicenseDialogOpen(true)}>
|
||||
{i18n.translate(I18nId.LicenseButtonLabel)}
|
||||
{i18n.translate('licenseButton.label')}
|
||||
</Button>
|
||||
<AnchorButton href={changelogUrl} target="blank_">
|
||||
{i18n.translate(I18nId.ChangelogButtonLabel)}
|
||||
{i18n.translate('changelogButton.label')}
|
||||
<ExternalLinkIcon />
|
||||
</AnchorButton>
|
||||
<AnchorButton href={pybricksWebsiteUrl} target="blank_">
|
||||
{i18n.translate(I18nId.WebsiteButtonLabel)}
|
||||
{i18n.translate('websiteButton.label')}
|
||||
<ExternalLinkIcon />
|
||||
</AnchorButton>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+5
-13
@@ -1,20 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
// About dialog translation keys.
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Title = 'title',
|
||||
Description = 'description',
|
||||
LicenseButtonLabel = 'licenseButton.label',
|
||||
ChangelogButtonLabel = 'changelogButton.label',
|
||||
WebsiteButtonLabel = 'websiteButton.label',
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { useLocalStorage } from 'usehooks-ts';
|
||||
import Explorer from '../explorer/Explorer';
|
||||
import Settings from '../settings/Settings';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
/** Indicates the selected activity. */
|
||||
export enum Activity {
|
||||
@@ -101,7 +101,7 @@ const Activities: React.VoidFunctionComponent = () => {
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
aria-label={i18n.translate(I18nId.Title)}
|
||||
aria-label={i18n.translate('title')}
|
||||
vertical={true}
|
||||
className="pb-activities"
|
||||
selectedTabId={selectedActivity}
|
||||
@@ -110,12 +110,12 @@ const Activities: React.VoidFunctionComponent = () => {
|
||||
ref={tabsRef}
|
||||
>
|
||||
<Tab
|
||||
aria-label={i18n.translate(I18nId.Explorer)}
|
||||
aria-label={i18n.translate('explorer')}
|
||||
className="pb-activities-tablist-tab"
|
||||
id={Activity.Explorer}
|
||||
title={
|
||||
<Icon
|
||||
htmlTitle={i18n.translate(I18nId.Explorer)}
|
||||
htmlTitle={i18n.translate('explorer')}
|
||||
size={35}
|
||||
icon="document"
|
||||
/>
|
||||
@@ -125,15 +125,11 @@ const Activities: React.VoidFunctionComponent = () => {
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Tab
|
||||
aria-label={i18n.translate(I18nId.Settings)}
|
||||
aria-label={i18n.translate('settings')}
|
||||
className="pb-activities-tablist-tab"
|
||||
id={Activity.Settings}
|
||||
title={
|
||||
<Icon
|
||||
htmlTitle={i18n.translate(I18nId.Settings)}
|
||||
size={35}
|
||||
icon="cog"
|
||||
/>
|
||||
<Icon htmlTitle={i18n.translate('settings')} size={35} icon="cog" />
|
||||
}
|
||||
panel={<Settings />}
|
||||
panelClassName="pb-activities-tabview"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+4
-10
@@ -1,18 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
//
|
||||
// Explorer translation keys.
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Title = 'title',
|
||||
Explorer = 'explorer',
|
||||
Settings = 'settings',
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AnchorButton, Button, ButtonGroup, Collapse, Intent } from '@blueprintj
|
||||
import React, { useState } from 'react';
|
||||
import { useId } from 'react-aria';
|
||||
import { CreateToast } from '../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
type UnexpectedErrorAlertProps = {
|
||||
error: Error;
|
||||
@@ -21,7 +21,7 @@ const UnexpectedErrorAlert: React.VoidFunctionComponent<UnexpectedErrorAlertProp
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{i18n.translate(I18nId.Message, { errorMessage: error.message })}</p>
|
||||
<p>{i18n.translate('message', { errorMessage: error.message })}</p>
|
||||
<span>
|
||||
<Button
|
||||
aria-labelledby={labelId}
|
||||
@@ -30,7 +30,7 @@ const UnexpectedErrorAlert: React.VoidFunctionComponent<UnexpectedErrorAlertProp
|
||||
icon={isExpanded ? 'chevron-down' : 'chevron-right'}
|
||||
onClick={() => setIsExpanded((v) => !v)}
|
||||
/>
|
||||
<span id={labelId}>{i18n.translate(I18nId.TechnicalInfo)}</span>
|
||||
<span id={labelId}>{i18n.translate('technicalInfo')}</span>
|
||||
</span>
|
||||
<Collapse isOpen={isExpanded}>
|
||||
<pre className="pb-alerts-stack-trace">{error.stack}</pre>
|
||||
@@ -46,7 +46,7 @@ const UnexpectedErrorAlert: React.VoidFunctionComponent<UnexpectedErrorAlertProp
|
||||
)
|
||||
}
|
||||
>
|
||||
{i18n.translate(I18nId.CopyErrorMessage)}
|
||||
{i18n.translate('copyErrorMessage')}
|
||||
</Button>
|
||||
<AnchorButton
|
||||
intent={Intent.DANGER}
|
||||
@@ -56,7 +56,7 @@ const UnexpectedErrorAlert: React.VoidFunctionComponent<UnexpectedErrorAlertProp
|
||||
)}+${encodeURIComponent(error.message)}`}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate(I18nId.ReportBug)}
|
||||
{i18n.translate('reportBug')}
|
||||
</AnchorButton>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+5
-10
@@ -1,17 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Message = 'message',
|
||||
TechnicalInfo = 'technicalInfo',
|
||||
CopyErrorMessage = 'copyErrorMessage',
|
||||
ReportBug = 'reportBug',
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const BluetoothNotAvailable: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
return (
|
||||
<>
|
||||
<p>{i18n.translate(I18nId.BluetoothNotAvailableMessage)}</p>
|
||||
<p>{i18n.translate(I18nId.BluetoothNotAvailableSuggestion)}</p>
|
||||
<p>{i18n.translate('bluetoothNotAvailable.message')}</p>
|
||||
<p>{i18n.translate('bluetoothNotAvailable.suggestion')}</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
type MissingServiceProps = {
|
||||
serviceName: string;
|
||||
@@ -18,9 +18,9 @@ const MissingService: React.VoidFunctionComponent<MissingServiceProps> = ({
|
||||
const i18n = useI18n();
|
||||
return (
|
||||
<>
|
||||
<p>{i18n.translate(I18nId.MissingServiceMessage, { serviceName })}</p>
|
||||
<p>{i18n.translate(I18nId.MissingServiceSuggestion1)}</p>
|
||||
<p>{i18n.translate(I18nId.MissingServiceSuggestion2, { hubName })}</p>
|
||||
<p>{i18n.translate('missingService.message', { serviceName })}</p>
|
||||
<p>{i18n.translate('missingService.suggestion1')}</p>
|
||||
<p>{i18n.translate('missingService.suggestion2', { hubName })}</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const NoGatt: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
return <p>{i18n.translate(I18nId.NoGattMessage)}</p>;
|
||||
return <p>{i18n.translate('noGatt.message')}</p>;
|
||||
};
|
||||
|
||||
export const noGatt: CreateToast = (onAction) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import React from 'react';
|
||||
import { appName, pybricksBluetoothTroubleshootingUrl } from '../../app/constants';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import ExternalLinkIcon from '../../utils/ExternalLinkIcon';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
type NoHubProps = {
|
||||
onFlashFirmware: () => void;
|
||||
@@ -18,28 +18,26 @@ const NoHub: React.VoidFunctionComponent<NoHubProps> = ({ onFlashFirmware }) =>
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{i18n.translate(I18nId.NoHubMessage)}</p>
|
||||
<p>{i18n.translate('noHub.message')}</p>
|
||||
<p>
|
||||
{i18n.translate(I18nId.NoHubSuggestion1, {
|
||||
{i18n.translate('noHub.suggestion1', {
|
||||
appName,
|
||||
buttonName: (
|
||||
<strong>
|
||||
{i18n.translate(I18nId.NoHubFlashFirmwareButton)}
|
||||
</strong>
|
||||
<strong>{i18n.translate('noHub.flashFirmwareButton')}</strong>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
<p>{i18n.translate(I18nId.NoHubSuggestion2)}</p>
|
||||
<p>{i18n.translate('noHub.suggestion2')}</p>
|
||||
<div className="pb-ble-alerts-buttons">
|
||||
<Button icon="download" onClick={onFlashFirmware}>
|
||||
{i18n.translate(I18nId.NoHubFlashFirmwareButton)}
|
||||
{i18n.translate('noHub.flashFirmwareButton')}
|
||||
</Button>
|
||||
<AnchorButton
|
||||
icon="help"
|
||||
href={pybricksBluetoothTroubleshootingUrl}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate(I18nId.NoHubTroubleshootButton)}
|
||||
{i18n.translate('noHub.troubleshootButton')}
|
||||
<ExternalLinkIcon />
|
||||
</AnchorButton>
|
||||
</div>
|
||||
|
||||
@@ -5,19 +5,19 @@ import { Button, Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { isIOS, isLinux } from '../../utils/os';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const NoWebBluetooth: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
return (
|
||||
<>
|
||||
<p>{i18n.translate(I18nId.NoWebBluetoothMessage)}</p>
|
||||
<p>{i18n.translate('noWebBluetooth.message')}</p>
|
||||
{!isLinux() && !isIOS() && (
|
||||
<p>{i18n.translate(I18nId.NoWebBluetoothSuggestion)}</p>
|
||||
<p>{i18n.translate('noWebBluetooth.suggestion')}</p>
|
||||
)}
|
||||
{isLinux() && (
|
||||
<>
|
||||
<p>{i18n.translate(I18nId.NoWebBluetoothLinux)}</p>
|
||||
<p>{i18n.translate('noWebBluetooth.linux')}</p>
|
||||
<p>
|
||||
<code>
|
||||
chrome://flags/#enable-experimental-web-platform-features
|
||||
|
||||
@@ -5,7 +5,7 @@ import './index.scss';
|
||||
import { Button, Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
type OldFirmwareProps = {
|
||||
onFlashFirmware: () => void;
|
||||
@@ -18,10 +18,10 @@ const OldFirmware: React.VoidFunctionComponent<OldFirmwareProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{i18n.translate(I18nId.OldFirmwareMessage)}</p>
|
||||
<p>{i18n.translate('oldFirmware.message')}</p>
|
||||
<div className="pb-ble-alerts-buttons">
|
||||
<Button icon="download" onClick={onFlashFirmware}>
|
||||
{i18n.translate(I18nId.OldFirmwareFlashFirmwareLabel)}
|
||||
{i18n.translate('oldFirmware.flashFirmware.label')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+4
-21
@@ -1,29 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
NoWebBluetoothMessage = 'noWebBluetooth.message',
|
||||
NoWebBluetoothSuggestion = 'noWebBluetooth.suggestion',
|
||||
NoWebBluetoothLinux = 'noWebBluetooth.linux',
|
||||
BluetoothNotAvailableMessage = 'bluetoothNotAvailable.message',
|
||||
BluetoothNotAvailableSuggestion = 'bluetoothNotAvailable.suggestion',
|
||||
NoGattMessage = 'noGatt.message',
|
||||
MissingServiceMessage = 'missingService.message',
|
||||
MissingServiceSuggestion1 = 'missingService.suggestion1',
|
||||
MissingServiceSuggestion2 = 'missingService.suggestion2',
|
||||
NoHubMessage = 'noHub.message',
|
||||
NoHubSuggestion1 = 'noHub.suggestion1',
|
||||
NoHubSuggestion2 = 'noHub.suggestion2',
|
||||
NoHubFlashFirmwareButton = 'noHub.flashFirmwareButton',
|
||||
NoHubTroubleshootButton = 'noHub.troubleshootButton',
|
||||
OldFirmwareMessage = 'oldFirmware.message',
|
||||
OldFirmwareFlashFirmwareLabel = 'oldFirmware.flashFirmware.label',
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { OverlayContainer } from 'react-aria';
|
||||
import { useBoolean } from 'usehooks-ts';
|
||||
import { Button } from './Button';
|
||||
import HelpDialog from './HelpDialog';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
type HelpButtonProps = {
|
||||
/** The label of the control this button provides help for. */
|
||||
@@ -49,9 +49,9 @@ const HelpButton: React.VoidFunctionComponent<HelpButtonProps> = ({
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
label={i18n.translate(I18nId.HelpButtonLabel)}
|
||||
label={i18n.translate('helpButton.label')}
|
||||
hideLabel
|
||||
description={i18n.translate(I18nId.HelpButtonDescription, {
|
||||
description={i18n.translate('helpButton.description', {
|
||||
helpForLabel,
|
||||
})}
|
||||
minimal
|
||||
@@ -62,7 +62,7 @@ const HelpButton: React.VoidFunctionComponent<HelpButtonProps> = ({
|
||||
{isDialogMounted && (
|
||||
<OverlayContainer className={Classes.PORTAL}>
|
||||
<HelpDialog
|
||||
title={i18n.translate(I18nId.HelpDialogTitle)}
|
||||
title={i18n.translate('helpDialog.title')}
|
||||
isOpen={isDialogOpen}
|
||||
openButton={openButton}
|
||||
onClose={setIsDialogOpenFalse}
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
useOverlay,
|
||||
} from 'react-aria';
|
||||
import { usePopper } from 'react-popper';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
type HelpDialogProps = {
|
||||
/** The title of the dialog. */
|
||||
@@ -147,7 +147,7 @@ const HelpDialog: React.FunctionComponent<HelpDialogProps> = ({
|
||||
<DismissButton
|
||||
id={dismissId}
|
||||
aria-label={i18n.translate(
|
||||
I18nId.HelpDialogCloseButtonLabel,
|
||||
'helpDialog.closeButton.label',
|
||||
)}
|
||||
onDismiss={onClose}
|
||||
/>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
HelpButtonLabel = 'helpButton.label',
|
||||
HelpButtonDescription = 'helpButton.description',
|
||||
HelpDialogTitle = 'helpDialog.title',
|
||||
HelpDialogCloseButtonLabel = 'helpDialog.closeButton.label',
|
||||
}
|
||||
|
||||
+11
-11
@@ -35,7 +35,7 @@ import { useSelector } from '../reducers';
|
||||
import { useSettingIsShowDocsEnabled } from '../settings/hooks';
|
||||
import { isMacOS } from '../utils/os';
|
||||
import { editorActivateFile, editorCloseFile } from './actions';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
import * as pybricksMicroPython from './pybricksMicroPython';
|
||||
import { pybricksMicroPythonId } from './pybricksMicroPython';
|
||||
import { UntitledHintContribution } from './untitledHint';
|
||||
@@ -129,9 +129,9 @@ const EditorContextMenu: React.VoidFunctionComponent<EditorContextMenuProps> = (
|
||||
const canRedo = model && model.canRedo();
|
||||
|
||||
return (
|
||||
<Menu aria-label={i18n.translate(I18nId.ContextMenuLabel)} role="menu">
|
||||
<Menu aria-label={i18n.translate('contextMenu.label')} role="menu">
|
||||
<EditorContextMenuItem
|
||||
label={i18n.translate(I18nId.Copy)}
|
||||
label={i18n.translate('copy')}
|
||||
icon="duplicate"
|
||||
keyboardShortcut={isMacOS() ? 'Cmd-C' : 'Ctrl-C'}
|
||||
disabled={!hasSelection}
|
||||
@@ -139,7 +139,7 @@ const EditorContextMenu: React.VoidFunctionComponent<EditorContextMenuProps> = (
|
||||
editorAction="editor.action.clipboardCopyAction"
|
||||
/>
|
||||
<EditorContextMenuItem
|
||||
label={i18n.translate(I18nId.Paste)}
|
||||
label={i18n.translate('paste')}
|
||||
icon="clipboard"
|
||||
keyboardShortcut={isMacOS() ? 'Cmd-V' : 'Ctrl-V'}
|
||||
disabled={!model}
|
||||
@@ -147,7 +147,7 @@ const EditorContextMenu: React.VoidFunctionComponent<EditorContextMenuProps> = (
|
||||
editorAction="editor.action.clipboardPasteAction"
|
||||
/>
|
||||
<EditorContextMenuItem
|
||||
label={i18n.translate(I18nId.SelectAll)}
|
||||
label={i18n.translate('selectAll')}
|
||||
icon="blank"
|
||||
keyboardShortcut={isMacOS() ? 'Cmd-A' : 'Ctrl-A'}
|
||||
disabled={!model}
|
||||
@@ -156,7 +156,7 @@ const EditorContextMenu: React.VoidFunctionComponent<EditorContextMenuProps> = (
|
||||
/>
|
||||
<MenuDivider />
|
||||
<EditorContextMenuItem
|
||||
label={i18n.translate(I18nId.Undo)}
|
||||
label={i18n.translate('undo')}
|
||||
icon="undo"
|
||||
keyboardShortcut={isMacOS() ? 'Cmd-Z' : 'Ctrl-Z'}
|
||||
disabled={!canUndo}
|
||||
@@ -164,7 +164,7 @@ const EditorContextMenu: React.VoidFunctionComponent<EditorContextMenuProps> = (
|
||||
editorAction="undo"
|
||||
/>
|
||||
<EditorContextMenuItem
|
||||
label={i18n.translate(I18nId.Redo)}
|
||||
label={i18n.translate('redo')}
|
||||
icon="redo"
|
||||
keyboardShortcut={isMacOS() ? 'Cmd-Shift-Z' : 'Ctrl-Shift-Z'}
|
||||
disabled={!canRedo}
|
||||
@@ -214,7 +214,7 @@ const TabCloseButton: React.VoidFunctionComponent<TabCloseButtonProps> = ({ uuid
|
||||
|
||||
return (
|
||||
<Button
|
||||
title={i18n.translate(I18nId.CloseFileTooltip, {
|
||||
title={i18n.translate('closeFile.tooltip', {
|
||||
fileName,
|
||||
})}
|
||||
minimal={true}
|
||||
@@ -369,7 +369,7 @@ const Editor: React.VFC = () => {
|
||||
(editor) => {
|
||||
const contrib = new UntitledHintContribution(
|
||||
editor,
|
||||
i18n.translate(I18nId.Placeholder),
|
||||
i18n.translate('placeholder'),
|
||||
);
|
||||
return () => contrib.dispose();
|
||||
},
|
||||
@@ -380,7 +380,7 @@ const Editor: React.VFC = () => {
|
||||
editor,
|
||||
() => ({
|
||||
id: 'pybricks.action.toggleDocs',
|
||||
label: i18n.translate(I18nId.ToggleDocs),
|
||||
label: i18n.translate('toggleDocs'),
|
||||
run: () => toggleIsSettingShowDocsEnabled(),
|
||||
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyD],
|
||||
}),
|
||||
@@ -391,7 +391,7 @@ const Editor: React.VFC = () => {
|
||||
editor,
|
||||
() => ({
|
||||
id: 'pybricks.action.check',
|
||||
label: i18n.translate(I18nId.Check),
|
||||
label: i18n.translate('check'),
|
||||
run: (e) => {
|
||||
// for checking, use the most recent compiler
|
||||
dispatch(compile(e.getValue(), 6, []));
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId as I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+5
-18
@@ -1,25 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
//
|
||||
// Editor translation keys.
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Placeholder = 'placeholder',
|
||||
Check = 'check',
|
||||
ToggleDocs = 'toggleDocs',
|
||||
Copy = 'copy',
|
||||
Paste = 'paste',
|
||||
SelectAll = 'selectAll',
|
||||
Undo = 'undo',
|
||||
Redo = 'redo',
|
||||
CloseFileTooltip = 'closeFile.tooltip',
|
||||
ContextMenuLabel = 'contextMenu.label',
|
||||
}
|
||||
|
||||
+18
-18
@@ -42,7 +42,7 @@ import {
|
||||
} from './actions';
|
||||
import DeleteFileAlert from './deleteFileAlert/DeleteFileAlert';
|
||||
import DuplicateFileDialog from './duplicateFileDialog/DuplicateFileDialog';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
import NewFileWizard from './newFileWizard/NewFileWizard';
|
||||
import RenameFileDialog from './renameFileDialog/RenameFileDialog';
|
||||
|
||||
@@ -121,13 +121,13 @@ const FileActionButtonGroup: React.VoidFunctionComponent<ActionButtonGroupProps>
|
||||
<ActionButton
|
||||
id={renameButtonId}
|
||||
icon="edit"
|
||||
tooltip={i18n.translate(I18nId.TreeItemRenameTooltip, { fileName })}
|
||||
tooltip={i18n.translate('treeItem.renameTooltip', { fileName })}
|
||||
onClick={() => dispatch(explorerRenameFile(fileName))}
|
||||
/>
|
||||
<ActionButton
|
||||
id={duplicateButtonId}
|
||||
icon="duplicate"
|
||||
tooltip={i18n.translate(I18nId.TreeItemDuplicateTooltip, {
|
||||
tooltip={i18n.translate('treeItem.duplicateTooltip', {
|
||||
fileName,
|
||||
})}
|
||||
onClick={() => dispatch(explorerDuplicateFile(fileName))}
|
||||
@@ -140,13 +140,13 @@ const FileActionButtonGroup: React.VoidFunctionComponent<ActionButtonGroupProps>
|
||||
// archive icon which is also used to indicate an export/
|
||||
// download operation
|
||||
icon="import"
|
||||
tooltip={i18n.translate(I18nId.TreeItemExportTooltip, { fileName })}
|
||||
tooltip={i18n.translate('treeItem.exportTooltip', { fileName })}
|
||||
onClick={() => dispatch(explorerExportFile(fileName))}
|
||||
/>
|
||||
<ActionButton
|
||||
id={deleteButtonId}
|
||||
icon="trash"
|
||||
tooltip={i18n.translate(I18nId.TreeItemDeleteTooltip, { fileName })}
|
||||
tooltip={i18n.translate('treeItem.deleteTooltip', { fileName })}
|
||||
onClick={() =>
|
||||
dispatch(explorerDeleteFile(fileName, item.index as UUID))
|
||||
}
|
||||
@@ -166,14 +166,14 @@ const Header: React.VoidFunctionComponent = () => {
|
||||
return (
|
||||
<Toolbar
|
||||
className="pb-explorer-header-toolbar"
|
||||
aria-label={i18n.translate(I18nId.HeaderToolbarTitle)}
|
||||
aria-label={i18n.translate('header.toolbar.title')}
|
||||
firstFocusableItemId={archiveButtonId}
|
||||
>
|
||||
<ButtonGroup minimal={true}>
|
||||
<ActionButton
|
||||
id={archiveButtonId}
|
||||
icon="archive"
|
||||
tooltip={i18n.translate(I18nId.HeaderToolbarExportAll)}
|
||||
tooltip={i18n.translate('header.toolbar.exportAll')}
|
||||
onClick={() => dispatch(explorerArchiveAllFiles())}
|
||||
/>
|
||||
<ActionButton
|
||||
@@ -182,13 +182,13 @@ const Header: React.VoidFunctionComponent = () => {
|
||||
// what we want here since import is analogous to upload
|
||||
// even though this is the "import" action
|
||||
icon="export"
|
||||
tooltip={i18n.translate(I18nId.HeaderToolbarImport)}
|
||||
tooltip={i18n.translate('header.toolbar.import')}
|
||||
onClick={() => dispatch(explorerImportFiles())}
|
||||
/>
|
||||
<ActionButton
|
||||
id={newButtonId}
|
||||
icon="plus"
|
||||
tooltip={i18n.translate(I18nId.HeaderToolbarAddNew)}
|
||||
tooltip={i18n.translate('header.toolbar.addNew')}
|
||||
onClick={() => dispatch(explorerCreateNewFile())}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
@@ -205,35 +205,35 @@ function useLiveDescriptors(): LiveDescriptors {
|
||||
return useMemo(
|
||||
() => ({
|
||||
introduction: `
|
||||
<p>${i18n.translate(I18nId.TreeLiveDescriptorIntroAccessibilityGuide, {
|
||||
<p>${i18n.translate('tree.liveDescriptor.intro.accessibilityGuide', {
|
||||
treeLabel: '{treeLabel}',
|
||||
})}</p>
|
||||
<p>${i18n.translate(I18nId.TreeLiveDescriptorIntroNavigation)}</p>
|
||||
<p>${i18n.translate('tree.liveDescriptor.intro.navigation')}</p>
|
||||
<ul>
|
||||
<li>${i18n.translate(
|
||||
I18nId.TreeLiveDescriptorIntroKeybindingsPrimaryAction,
|
||||
'tree.liveDescriptor.intro.keybindings.primaryAction',
|
||||
{ key: '{keybinding:primaryAction}' },
|
||||
)}</li>
|
||||
<li>${i18n.translate(
|
||||
I18nId.TreeLiveDescriptorIntroKeybindingsRename,
|
||||
'tree.liveDescriptor.intro.keybindings.rename',
|
||||
{ key: 'f2' },
|
||||
)}</li>
|
||||
<li>${i18n.translate(
|
||||
I18nId.TreeLiveDescriptorIntroKeybindingsDuplicate,
|
||||
'tree.liveDescriptor.intro.keybindings.duplicate',
|
||||
{ key: `${isMacOS() ? 'cmd' : 'ctrl'}+d` },
|
||||
)}</li>
|
||||
<li>${i18n.translate(
|
||||
I18nId.TreeLiveDescriptorIntroKeybindingsExport,
|
||||
'tree.liveDescriptor.intro.keybindings.export',
|
||||
{ key: `${isMacOS() ? 'cmd' : 'ctrl'}+e` },
|
||||
)}</li>
|
||||
<li>${i18n.translate(
|
||||
I18nId.TreeLiveDescriptorIntroKeybindingsDelete,
|
||||
'tree.liveDescriptor.intro.keybindings.delete',
|
||||
{ key: 'delete' },
|
||||
)}</li>
|
||||
</ul>
|
||||
`,
|
||||
renamingItem: 'not used',
|
||||
searching: `<p>${i18n.translate(I18nId.TreeLiveDescriptorSearching)}</p>`,
|
||||
searching: `<p>${i18n.translate('tree.liveDescriptor.searching')}</p>`,
|
||||
programmaticallyDragging: 'not used',
|
||||
programmaticallyDraggingTarget: 'not used',
|
||||
}),
|
||||
@@ -400,7 +400,7 @@ const FileTree: React.VoidFunctionComponent = () => {
|
||||
<Tree
|
||||
treeId={treeId}
|
||||
rootItem={rootItemIndex}
|
||||
treeLabel={i18n.translate(I18nId.TreeLabel)}
|
||||
treeLabel={i18n.translate('tree.label')}
|
||||
/>
|
||||
</div>
|
||||
</ControlledTreeEnvironment>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
type FileInUseAlertProps = {
|
||||
fileName: string;
|
||||
@@ -14,7 +14,7 @@ const FileInUseAlert: React.VoidFunctionComponent<FileInUseAlertProps> = ({
|
||||
fileName,
|
||||
}) => {
|
||||
const i18n = useI18n();
|
||||
return <>{i18n.translate(I18nId.FileInUseMessage, { fileName })}</>;
|
||||
return <>{i18n.translate('fileInUse.message', { fileName })}</>;
|
||||
};
|
||||
|
||||
export const fileInUse: CreateToast<{ fileName: string }> = (
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
import { Icon, Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const NoFilesToBackup: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
return (
|
||||
<>
|
||||
{i18n.translate(I18nId.NoFilesToBackupMessage, {
|
||||
{i18n.translate('noFilesToBackup.message', {
|
||||
icon: <Icon icon="plus" />,
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
FileInUseMessage = 'fileInUse.message',
|
||||
NoFilesToBackupMessage = 'noFilesToBackup.message',
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useSelector } from '../../reducers';
|
||||
import { deleteFileAlertDidAccept, deleteFileAlertDidCancel } from './actions';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const DeleteFileAlert: React.VoidFunctionComponent = () => {
|
||||
const { isOpen, fileName } = useSelector((s) => s.explorer.deleteFileAlert);
|
||||
@@ -37,13 +37,13 @@ const DeleteFileAlert: React.VoidFunctionComponent = () => {
|
||||
isOpen={isOpen}
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
confirmButtonText={i18n.translate(I18nId.Accept)}
|
||||
cancelButtonText={i18n.translate(I18nId.Cancel)}
|
||||
confirmButtonText={i18n.translate('action.accept')}
|
||||
cancelButtonText={i18n.translate('action.cancel')}
|
||||
onConfirm={() => dispatch(deleteFileAlertDidAccept())}
|
||||
onCancel={() => dispatch(deleteFileAlertDidCancel())}
|
||||
onOpened={handleOpened}
|
||||
>
|
||||
{i18n.translate(I18nId.Message, { fileName })}
|
||||
{i18n.translate('message', { fileName })}
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Accept = 'action.accept',
|
||||
Cancel = 'action.cancel',
|
||||
Message = 'message',
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { useSelector } from '../../reducers';
|
||||
import FileNameFormGroup from '../fileNameFormGroup/FileNameFormGroup';
|
||||
import { duplicateFileDialogDidAccept, duplicateFileDialogDidCancel } from './actions';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const DuplicateFileDialog: React.VFC = () => {
|
||||
const i18n = useI18n();
|
||||
@@ -46,7 +46,7 @@ const DuplicateFileDialog: React.VFC = () => {
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={i18n.translate(I18nId.Title, {
|
||||
title={i18n.translate('title', {
|
||||
fileName: oldName,
|
||||
})}
|
||||
isOpen={isOpen}
|
||||
@@ -74,7 +74,7 @@ const DuplicateFileDialog: React.VFC = () => {
|
||||
disabled={result !== FileNameValidationResult.IsOk}
|
||||
type="submit"
|
||||
>
|
||||
{i18n.translate(I18nId.ActionAccept)}
|
||||
{i18n.translate('action.accept')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Title = 'title',
|
||||
ActionAccept = 'action.accept',
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { AriaButtonProps } from '@react-types/button';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { useButton } from 'react-aria';
|
||||
import { FileNameValidationResult } from '../../pybricksMicropython/lib';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
/**
|
||||
* Trims trailing and leading whitespace and replaces additional whitespace
|
||||
@@ -51,7 +51,7 @@ const FixItButton: React.VoidFunctionComponent<FixItButtonProps> = (props) => {
|
||||
ref,
|
||||
);
|
||||
|
||||
return <a {...buttonProps}>{i18n.translate(I18nId.HelpTextFixIt)}</a>;
|
||||
return <a {...buttonProps}>{i18n.translate('helpText.fixIt')}</a>;
|
||||
};
|
||||
|
||||
type FileNameHelpTextProps = {
|
||||
@@ -87,27 +87,27 @@ const FileNameHelpText: React.VoidFunctionComponent<FileNameHelpTextProps> = ({
|
||||
|
||||
switch (validation) {
|
||||
case FileNameValidationResult.IsOk:
|
||||
return <>{i18n.translate(I18nId.HelpTextIsOk)}</>;
|
||||
return <>{i18n.translate('helpText.isOk')}</>;
|
||||
case FileNameValidationResult.IsEmpty:
|
||||
return <>{i18n.translate(I18nId.HelpTextIsEmpty)}</>;
|
||||
return <>{i18n.translate('helpText.isEmpty')}</>;
|
||||
case FileNameValidationResult.HasSpaces:
|
||||
return (
|
||||
<>
|
||||
{i18n.translate(I18nId.HelpTextHasSpaces)}{' '}
|
||||
{i18n.translate('helpText.hasSpaces')}{' '}
|
||||
<FixItButton onPress={handleHasSpaces} />
|
||||
</>
|
||||
);
|
||||
case FileNameValidationResult.HasFileExtension:
|
||||
return (
|
||||
<>
|
||||
{i18n.translate(I18nId.HelpTextHasFileExtension)}{' '}
|
||||
{i18n.translate('helpText.hasFileExtension')}{' '}
|
||||
<FixItButton onPress={handleHasFileExtension} />
|
||||
</>
|
||||
);
|
||||
case FileNameValidationResult.HasInvalidFirstCharacter:
|
||||
return (
|
||||
<>
|
||||
{i18n.translate(I18nId.HelpTextHasInvalidFirstCharacter, {
|
||||
{i18n.translate('helpText.hasInvalidFirstCharacter', {
|
||||
letters: <code className={Classes.CODE}>a…z</code>,
|
||||
underscore: <code className={Classes.CODE}>_</code>,
|
||||
})}
|
||||
@@ -116,7 +116,7 @@ const FileNameHelpText: React.VoidFunctionComponent<FileNameHelpTextProps> = ({
|
||||
case FileNameValidationResult.HasInvalidCharacters:
|
||||
return (
|
||||
<>
|
||||
{i18n.translate(I18nId.HelpTextHasInvalidCharacters, {
|
||||
{i18n.translate('helpText.hasInvalidCharacters', {
|
||||
letters: <code className={Classes.CODE}>a…z</code>,
|
||||
numbers: <code className={Classes.CODE}>0…9</code>,
|
||||
dash: <code className={Classes.CODE}>-</code>,
|
||||
@@ -126,7 +126,7 @@ const FileNameHelpText: React.VoidFunctionComponent<FileNameHelpTextProps> = ({
|
||||
</>
|
||||
);
|
||||
case FileNameValidationResult.AlreadyExists:
|
||||
return <>{i18n.translate(I18nId.HelpTextAlreadyExists)}</>;
|
||||
return <>{i18n.translate('helpText.alreadyExists')}</>;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -162,7 +162,7 @@ const FileNameFormGroup: React.VoidFunctionComponent<FileNameFormGroupProps> = (
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
label={i18n.translate('label')}
|
||||
intent={fileNameIntent}
|
||||
subLabel={
|
||||
<FileNameHelpText
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
HelpTextIsOk = 'helpText.isOk',
|
||||
HelpTextIsEmpty = 'helpText.isEmpty',
|
||||
HelpTextHasSpaces = 'helpText.hasSpaces',
|
||||
HelpTextHasFileExtension = 'helpText.hasFileExtension',
|
||||
HelpTextHasInvalidFirstCharacter = 'helpText.hasInvalidFirstCharacter',
|
||||
HelpTextHasInvalidCharacters = 'helpText.hasInvalidCharacters',
|
||||
HelpTextAlreadyExists = 'helpText.alreadyExists',
|
||||
HelpTextFixIt = 'helpText.fixIt',
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+4
-24
@@ -1,32 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
//
|
||||
// Explorer translation keys.
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
HeaderToolbarAddNew = 'header.toolbar.addNew',
|
||||
HeaderToolbarExportAll = 'header.toolbar.exportAll',
|
||||
HeaderToolbarImport = 'header.toolbar.import',
|
||||
HeaderToolbarTitle = 'header.toolbar.title',
|
||||
TreeItemDeleteTooltip = 'treeItem.deleteTooltip',
|
||||
TreeItemDuplicateTooltip = 'treeItem.duplicateTooltip',
|
||||
TreeItemExportTooltip = 'treeItem.exportTooltip',
|
||||
TreeItemRenameTooltip = 'treeItem.renameTooltip',
|
||||
TreeLabel = 'tree.label',
|
||||
TreeLiveDescriptorIntroAccessibilityGuide = 'tree.liveDescriptor.intro.accessibilityGuide',
|
||||
TreeLiveDescriptorIntroKeybindingsDelete = 'tree.liveDescriptor.intro.keybindings.delete',
|
||||
TreeLiveDescriptorIntroKeybindingsDuplicate = 'tree.liveDescriptor.intro.keybindings.duplicate',
|
||||
TreeLiveDescriptorIntroKeybindingsExport = 'tree.liveDescriptor.intro.keybindings.export',
|
||||
TreeLiveDescriptorIntroKeybindingsPrimaryAction = 'tree.liveDescriptor.intro.keybindings.primaryAction',
|
||||
TreeLiveDescriptorIntroKeybindingsRename = 'tree.liveDescriptor.intro.keybindings.rename',
|
||||
TreeLiveDescriptorIntroNavigation = 'tree.liveDescriptor.intro.navigation',
|
||||
TreeLiveDescriptorSearching = 'tree.liveDescriptor.searching',
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { useSelector } from '../../reducers';
|
||||
import FileNameFormGroup from '../fileNameFormGroup/FileNameFormGroup';
|
||||
import { newFileWizardDidAccept, newFileWizardDidCancel } from './actions';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
// This should be set to the most commonly used hub.
|
||||
const defaultHub = Hub.Technic;
|
||||
@@ -54,7 +54,7 @@ const NewFileWizard: React.VoidFunctionComponent = () => {
|
||||
return (
|
||||
<Dialog
|
||||
icon="plus"
|
||||
title={i18n.translate(I18nId.Title)}
|
||||
title={i18n.translate('title')}
|
||||
isOpen={isOpen}
|
||||
onOpening={() => setFileName('')}
|
||||
onOpened={() => fileNameInputRef.current?.focus()}
|
||||
@@ -69,7 +69,7 @@ const NewFileWizard: React.VoidFunctionComponent = () => {
|
||||
inputRef={fileNameInputRef}
|
||||
onChange={setFileName}
|
||||
/>
|
||||
<FormGroup label={i18n.translate(I18nId.SmartHubLabel)}>
|
||||
<FormGroup label={i18n.translate('smartHub.label')}>
|
||||
<HubPicker hubType={hubType} onChange={setHubType} />
|
||||
</FormGroup>
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@ const NewFileWizard: React.VoidFunctionComponent = () => {
|
||||
type="submit"
|
||||
>
|
||||
<span id={acceptButtonLabelId}>
|
||||
{i18n.translate(I18nId.ActionCreate)}
|
||||
{i18n.translate('action.create')}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Title = 'title',
|
||||
SmartHubLabel = 'smartHub.label',
|
||||
ActionCreate = 'action.create',
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { useSelector } from '../../reducers';
|
||||
import FileNameFormGroup from '../fileNameFormGroup/FileNameFormGroup';
|
||||
import { renameFileDialogDidAccept, renameFileDialogDidCancel } from './actions';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const RenameFileDialog: React.VFC = () => {
|
||||
const i18n = useI18n();
|
||||
@@ -46,7 +46,7 @@ const RenameFileDialog: React.VFC = () => {
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={i18n.translate(I18nId.Title, {
|
||||
title={i18n.translate('title', {
|
||||
fileName: oldName,
|
||||
})}
|
||||
isOpen={isOpen}
|
||||
@@ -74,7 +74,7 @@ const RenameFileDialog: React.VFC = () => {
|
||||
disabled={result !== FileNameValidationResult.IsOk}
|
||||
type="submit"
|
||||
>
|
||||
{i18n.translate(I18nId.ActionRename)}
|
||||
{i18n.translate('action.rename')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Title = 'title',
|
||||
ActionRename = 'action.rename',
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const FirmwareMismatch: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
return <p>{i18n.translate(I18nId.FirmwareMismatchMessage)}</p>;
|
||||
return <p>{i18n.translate('firmwareMismatch.message')}</p>;
|
||||
};
|
||||
|
||||
export const firmwareMismatch: CreateToast = (onAction) => {
|
||||
|
||||
@@ -7,26 +7,26 @@ import { pybricksUsbDfuTroubleshootingUrl } from '../../app/constants';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import ExternalLinkIcon from '../../utils/ExternalLinkIcon';
|
||||
import { isLinux, isWindows } from '../../utils/os';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const NoDfuHub: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{i18n.translate(I18nId.NoDfuHubMessage)}</p>
|
||||
<p>{i18n.translate('noDfuHub.message')}</p>
|
||||
|
||||
{isWindows() && <p>{i18n.translate(I18nId.NoDfuHubSuggestion1Windows)}</p>}
|
||||
{isLinux() && <p>{i18n.translate(I18nId.NoDfuHubSuggestion1Linux)}</p>}
|
||||
{isWindows() && <p>{i18n.translate('noDfuHub.suggestion1.windows')}</p>}
|
||||
{isLinux() && <p>{i18n.translate('noDfuHub.suggestion1.linux')}</p>}
|
||||
|
||||
<p>{i18n.translate(I18nId.NoDfuHubSuggestion2)}</p>
|
||||
<p>{i18n.translate('noDfuHub.suggestion2')}</p>
|
||||
|
||||
<AnchorButton
|
||||
icon="help"
|
||||
href={pybricksUsbDfuTroubleshootingUrl}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate(I18nId.NoDfuHubTroubleshootButton)}
|
||||
{i18n.translate('noDfuHub.suggestion2')}
|
||||
<ExternalLinkIcon />
|
||||
</AnchorButton>
|
||||
</>
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const NoDfuInterface: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
return <p>{i18n.translate(I18nId.NoDfuInterfaceMessage)}</p>;
|
||||
return <p>{i18n.translate('noDfuInterface.message')}</p>;
|
||||
};
|
||||
|
||||
export const noDfuInterface: CreateToast = (onAction) => {
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const NoWebUsb: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
return (
|
||||
<>
|
||||
<p>{i18n.translate(I18nId.NoWebUsbMessage)}</p>
|
||||
<p>{i18n.translate(I18nId.NoWebUsbSuggestion)}</p>
|
||||
<p>{i18n.translate('noWebUsb.message')}</p>
|
||||
<p>{i18n.translate('noWebUsb.suggestion')}</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
NoWebUsbMessage = 'noWebUsb.message',
|
||||
NoWebUsbSuggestion = 'noWebUsb.suggestion',
|
||||
NoDfuHubMessage = 'noDfuHub.message',
|
||||
NoDfuHubSuggestion1Windows = 'noDfuHub.suggestion1.windows',
|
||||
NoDfuHubSuggestion1Linux = 'noDfuHub.suggestion1.linux',
|
||||
NoDfuHubSuggestion2 = 'noDfuHub.suggestion2',
|
||||
NoDfuHubTroubleshootButton = 'noDfuHub.troubleshootButton',
|
||||
NoDfuInterfaceMessage = 'noDfuInterface.message',
|
||||
FirmwareMismatchMessage = 'firmwareMismatch.message',
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
firmwareInstallPybricksDialogCancel,
|
||||
} from './actions';
|
||||
import { useFirmware } from './hooks';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
import { validateHubName } from '.';
|
||||
|
||||
const dialogBody = classNames(
|
||||
@@ -63,7 +63,7 @@ const SelectHubPanel: React.VoidFunctionComponent<SelectHubPanelProps> = ({
|
||||
|
||||
return (
|
||||
<div className={dialogBody}>
|
||||
<p>{i18n.translate(I18nId.SelectHubPanelMessage)}</p>
|
||||
<p>{i18n.translate('selectHubPanel.message')}</p>
|
||||
<HubPicker hubType={hubType} onChange={onChange} />
|
||||
<Popover2
|
||||
popoverClassName={Classes2.POPOVER2_CONTENT_SIZING}
|
||||
@@ -72,47 +72,47 @@ const SelectHubPanel: React.VoidFunctionComponent<SelectHubPanelProps> = ({
|
||||
<div>
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
I18nId.SelectHubPanelNotOnListButtonInfoMindstormsTitle,
|
||||
'selectHubPanel.notOnListButton.info.mindstorms.title',
|
||||
)}
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
{i18n.translate(
|
||||
I18nId.SelectHubPanelNotOnListButtonInfoMindstormsRcx,
|
||||
'selectHubPanel.notOnListButton.info.mindstorms.rcx',
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
{i18n.translate(
|
||||
I18nId.SelectHubPanelNotOnListButtonInfoMindstormsNxt,
|
||||
'selectHubPanel.notOnListButton.info.mindstorms.nxt',
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
{i18n.translate(
|
||||
I18nId.SelectHubPanelNotOnListButtonInfoMindstormsEv3,
|
||||
'selectHubPanel.notOnListButton.info.mindstorms.ev3',
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
I18nId.SelectHubPanelNotOnListButtonInfoPoweredUpTitle,
|
||||
'selectHubPanel.notOnListButton.info.poweredUp.title',
|
||||
)}
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
{i18n.translate(
|
||||
I18nId.SelectHubPanelNotOnListButtonInfoPoweredUpWedo2,
|
||||
'selectHubPanel.notOnListButton.info.poweredUp.wedo2',
|
||||
)}
|
||||
<em>*</em>
|
||||
</li>
|
||||
<li>
|
||||
{i18n.translate(
|
||||
I18nId.SelectHubPanelNotOnListButtonInfoPoweredUpDuploTrain,
|
||||
'selectHubPanel.notOnListButton.info.poweredUp.duploTrain',
|
||||
)}
|
||||
<em>*</em>
|
||||
</li>
|
||||
<li>
|
||||
{i18n.translate(
|
||||
I18nId.SelectHubPanelNotOnListButtonInfoPoweredUpMario,
|
||||
'selectHubPanel.notOnListButton.info.poweredUp.mario',
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
@@ -120,7 +120,7 @@ const SelectHubPanel: React.VoidFunctionComponent<SelectHubPanelProps> = ({
|
||||
<em>
|
||||
*{' '}
|
||||
{i18n.translate(
|
||||
I18nId.SelectHubPanelNotOnListButtonInfoPoweredUpFootnote,
|
||||
'selectHubPanel.notOnListButton.info.poweredUp.footnote',
|
||||
)}
|
||||
</em>
|
||||
</div>
|
||||
@@ -130,7 +130,7 @@ const SelectHubPanel: React.VoidFunctionComponent<SelectHubPanelProps> = ({
|
||||
elementRef={ref as IRef<HTMLButtonElement>}
|
||||
{...targetProps}
|
||||
>
|
||||
{i18n.translate(I18nId.SelectHubPanelNotOnListButtonLabel)}
|
||||
{i18n.translate('selectHubPanel.notOnListButton.label')}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
@@ -163,16 +163,14 @@ const AcceptLicensePanel: React.VoidFunctionComponent<AcceptLicensePanelProps> =
|
||||
icon={error ? 'error' : <Spinner />}
|
||||
description={
|
||||
error
|
||||
? i18n.translate(
|
||||
I18nId.LicensePanelLicenseTextError,
|
||||
)
|
||||
? i18n.translate('licensePanel.licenseText.error')
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Checkbox
|
||||
label={i18n.translate(I18nId.LicensePanelAcceptCheckboxLabel)}
|
||||
label={i18n.translate('licensePanel.acceptCheckbox.label')}
|
||||
checked={licenseAccepted}
|
||||
onChange={(e) => onLicenseAcceptedChanged(e.currentTarget.checked)}
|
||||
disabled={!data}
|
||||
@@ -208,8 +206,8 @@ const ConfigureOptionsPanel: React.VoidFunctionComponent<SelectOptionsPanelProps
|
||||
return (
|
||||
<div className={dialogBody}>
|
||||
<FormGroup
|
||||
label={i18n.translate(I18nId.OptionsPanelHubNameLabel)}
|
||||
labelInfo={i18n.translate(I18nId.OptionsPanelHubNameLabelInfo)}
|
||||
label={i18n.translate('optionsPanel.hubName.label')}
|
||||
labelInfo={i18n.translate('optionsPanel.hubName.labelInfo')}
|
||||
>
|
||||
<ControlGroup>
|
||||
<InputGroup
|
||||
@@ -230,26 +228,26 @@ const ConfigureOptionsPanel: React.VoidFunctionComponent<SelectOptionsPanelProps
|
||||
}
|
||||
/>
|
||||
<HelpButton
|
||||
helpForLabel={i18n.translate(I18nId.OptionsPanelHubNameLabel)}
|
||||
content={i18n.translate(I18nId.OptionsPanelHubNameHelp)}
|
||||
helpForLabel={i18n.translate('optionsPanel.hubName.label')}
|
||||
content={i18n.translate('optionsPanel.hubName.help')}
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={i18n.translate(I18nId.OptionsPanelCustomMainLabel)}
|
||||
labelInfo={i18n.translate(I18nId.OptionsPanelCustomMainLabelInfo)}
|
||||
label={i18n.translate('optionsPanel.customMain.label')}
|
||||
labelInfo={i18n.translate('optionsPanel.customMain.labelInfo')}
|
||||
>
|
||||
{(hubHasExternalFlash(hubType) && (
|
||||
<p>
|
||||
{i18n.translate(
|
||||
I18nId.OptionsPanelCustomMainNotApplicableMessage,
|
||||
'optionsPanel.customMain.notApplicable.message',
|
||||
)}
|
||||
</p>
|
||||
)) || (
|
||||
<ControlGroup>
|
||||
<Switch
|
||||
labelElement={i18n.translate(
|
||||
I18nId.OptionsPanelCustomMainIncludeLabel,
|
||||
'optionsPanel.customMain.include.label',
|
||||
{ main: <code>main.py</code> },
|
||||
)}
|
||||
checked={includeProgram}
|
||||
@@ -280,7 +278,7 @@ const ConfigureOptionsPanel: React.VoidFunctionComponent<SelectOptionsPanelProps
|
||||
roleStructure="listoption"
|
||||
disabled={true}
|
||||
text={i18n.translate(
|
||||
I18nId.OptionsPanelCustomMainIncludeNoFiles,
|
||||
'optionsPanel.customMain.include.noFiles',
|
||||
)}
|
||||
/>
|
||||
}
|
||||
@@ -294,7 +292,7 @@ const ConfigureOptionsPanel: React.VoidFunctionComponent<SelectOptionsPanelProps
|
||||
text={
|
||||
selectedIncludeFile?.path ??
|
||||
i18n.translate(
|
||||
I18nId.OptionsPanelCustomMainIncludeNoSelection,
|
||||
'optionsPanel.customMain.include.noSelection',
|
||||
)
|
||||
}
|
||||
disabled={!includeProgram}
|
||||
@@ -302,11 +300,11 @@ const ConfigureOptionsPanel: React.VoidFunctionComponent<SelectOptionsPanelProps
|
||||
</Select2>
|
||||
<HelpButton
|
||||
helpForLabel={i18n.translate(
|
||||
I18nId.OptionsPanelCustomMainIncludeLabel,
|
||||
'optionsPanel.customMain.include.label',
|
||||
{ main: 'main.py' },
|
||||
)}
|
||||
content={i18n.translate(
|
||||
I18nId.OptionsPanelCustomMainIncludeHelp,
|
||||
'optionsPanel.customMain.include.help',
|
||||
{
|
||||
appName,
|
||||
},
|
||||
@@ -332,47 +330,45 @@ const BootloaderModePanel: React.VoidFunctionComponent<BootloaderModePanelProps>
|
||||
return {
|
||||
button: i18n.translate(
|
||||
hubHasBluetoothButton(hubType)
|
||||
? I18nId.BootloaderPanelButtonBluetooth
|
||||
: I18nId.BootloaderPanelButtonPower,
|
||||
? 'bootloaderPanel.button.bluetooth'
|
||||
: 'bootloaderPanel.button.power',
|
||||
),
|
||||
light: i18n.translate(
|
||||
hubHasBluetoothButton(hubType)
|
||||
? I18nId.BootloaderPanelLightBluetooth
|
||||
: I18nId.BootloaderPanelLightStatus,
|
||||
? 'bootloaderPanel.light.bluetooth'
|
||||
: 'bootloaderPanel.light.status',
|
||||
),
|
||||
lightPattern: i18n.translate(
|
||||
hubHasBluetoothButton(hubType)
|
||||
? I18nId.BootloaderPanelLightPatternBluetooth
|
||||
: I18nId.BootloaderPanelLightPatternStatus,
|
||||
? 'bootloaderPanel.lightPattern.bluetooth'
|
||||
: 'bootloaderPanel.lightPattern.status',
|
||||
),
|
||||
};
|
||||
}, [i18n, hubType]);
|
||||
|
||||
return (
|
||||
<div className={dialogBody}>
|
||||
<p>{i18n.translate(I18nId.BootloaderPanelInstruction1)}</p>
|
||||
<p>{i18n.translate('bootloaderPanel.instruction1')}</p>
|
||||
<ol>
|
||||
{hubHasUSB(hubType) && (
|
||||
<li>{i18n.translate(I18nId.BootloaderPanelStepDisconnectUsb)}</li>
|
||||
<li>{i18n.translate('bootloaderPanel.step.disconnectUsb')}</li>
|
||||
)}
|
||||
|
||||
<li>{i18n.translate(I18nId.BootloaderPanelStepPowerOff)}</li>
|
||||
<li>{i18n.translate('bootloaderPanel.step.powerOff')}</li>
|
||||
|
||||
{/* City hub has power issues and requires disconnecting motors/sensors */}
|
||||
{hubType === Hub.City && (
|
||||
<li>{i18n.translate(I18nId.BootloaderPanelStepDisconnectIo)}</li>
|
||||
<li>{i18n.translate('bootloaderPanel.step.disconnectIo')}</li>
|
||||
)}
|
||||
|
||||
<li>
|
||||
{i18n.translate(I18nId.BootloaderPanelStepHoldButton, { button })}
|
||||
</li>
|
||||
<li>{i18n.translate('bootloaderPanel.step.holdButton', { button })}</li>
|
||||
|
||||
{hubHasUSB(hubType) && (
|
||||
<li>{i18n.translate(I18nId.BootloaderPanelStepConnectUsb)}</li>
|
||||
<li>{i18n.translate('bootloaderPanel.step.connectUsb')}</li>
|
||||
)}
|
||||
|
||||
<li>
|
||||
{i18n.translate(I18nId.BootloaderPanelStepWaitForLight, {
|
||||
{i18n.translate('bootloaderPanel.step.waitForLight', {
|
||||
button,
|
||||
light,
|
||||
lightPattern,
|
||||
@@ -383,8 +379,8 @@ const BootloaderModePanel: React.VoidFunctionComponent<BootloaderModePanelProps>
|
||||
{i18n.translate(
|
||||
/* hubs with USB will keep the power on, but other hubs won't */
|
||||
hubHasUSB(hubType)
|
||||
? I18nId.BootloaderPanelStepReleaseButton
|
||||
: I18nId.BootloaderPanelStepKeepHolding,
|
||||
? 'bootloaderPanel.step.releaseButton'
|
||||
: 'bootloaderPanel.step.keepHolding',
|
||||
{
|
||||
button,
|
||||
},
|
||||
@@ -392,11 +388,9 @@ const BootloaderModePanel: React.VoidFunctionComponent<BootloaderModePanelProps>
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
{i18n.translate(I18nId.BootloaderPanelInstruction2, {
|
||||
{i18n.translate('bootloaderPanel.instruction2', {
|
||||
flashFirmware: (
|
||||
<strong>
|
||||
{i18n.translate(I18nId.FlashFirmwareButtonLabel)}
|
||||
</strong>
|
||||
<strong>{i18n.translate('flashFirmwareButton.label')}</strong>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
@@ -419,11 +413,11 @@ export const InstallPybricksDialog: React.VoidFunctionComponent = () => {
|
||||
|
||||
return (
|
||||
<MultistepDialog
|
||||
title={i18n.translate(I18nId.Title)}
|
||||
title={i18n.translate('title')}
|
||||
isOpen={isOpen}
|
||||
onClose={() => dispatch(firmwareInstallPybricksDialogCancel())}
|
||||
finalButtonProps={{
|
||||
text: i18n.translate(I18nId.FlashFirmwareButtonLabel),
|
||||
text: i18n.translate('flashFirmwareButton.label'),
|
||||
onClick: () =>
|
||||
dispatch(
|
||||
firmwareInstallPybricksDialogAccept(
|
||||
@@ -437,13 +431,13 @@ export const InstallPybricksDialog: React.VoidFunctionComponent = () => {
|
||||
>
|
||||
<DialogStep
|
||||
id="hub"
|
||||
title={i18n.translate(I18nId.SelectHubPanelTitle)}
|
||||
title={i18n.translate('selectHubPanel.title')}
|
||||
panel={<SelectHubPanel hubType={hubType} onChange={setHubType} />}
|
||||
nextButtonProps={{ text: i18n.translate(I18nId.NextButtonLabel) }}
|
||||
nextButtonProps={{ text: i18n.translate('nextButton.label') }}
|
||||
/>
|
||||
<DialogStep
|
||||
id="license"
|
||||
title={i18n.translate(I18nId.LicensePanelTitle)}
|
||||
title={i18n.translate('licensePanel.title')}
|
||||
panel={
|
||||
<AcceptLicensePanel
|
||||
hubType={hubType}
|
||||
@@ -451,15 +445,15 @@ export const InstallPybricksDialog: React.VoidFunctionComponent = () => {
|
||||
onLicenseAcceptedChanged={setLicenseAccepted}
|
||||
/>
|
||||
}
|
||||
backButtonProps={{ text: i18n.translate(I18nId.BackButtonLabel) }}
|
||||
backButtonProps={{ text: i18n.translate('backButton.label') }}
|
||||
nextButtonProps={{
|
||||
disabled: !licenseAccepted,
|
||||
text: i18n.translate(I18nId.NextButtonLabel),
|
||||
text: i18n.translate('nextButton.label'),
|
||||
}}
|
||||
/>
|
||||
<DialogStep
|
||||
id="options"
|
||||
title={i18n.translate(I18nId.OptionsPanelTitle)}
|
||||
title={i18n.translate('optionsPanel.title')}
|
||||
panel={
|
||||
<ConfigureOptionsPanel
|
||||
hubType={hubType}
|
||||
@@ -471,14 +465,14 @@ export const InstallPybricksDialog: React.VoidFunctionComponent = () => {
|
||||
onChangeSelectedIncludeFile={setSelectedIncludeFile}
|
||||
/>
|
||||
}
|
||||
backButtonProps={{ text: i18n.translate(I18nId.BackButtonLabel) }}
|
||||
nextButtonProps={{ text: i18n.translate(I18nId.NextButtonLabel) }}
|
||||
backButtonProps={{ text: i18n.translate('backButton.label') }}
|
||||
nextButtonProps={{ text: i18n.translate('nextButton.label') }}
|
||||
/>
|
||||
<DialogStep
|
||||
id="bootloader"
|
||||
title={i18n.translate(I18nId.BootloaderPanelTitle)}
|
||||
title={i18n.translate('bootloaderPanel.title')}
|
||||
panel={<BootloaderModePanel hubType={hubType} />}
|
||||
backButtonProps={{ text: i18n.translate(I18nId.BackButtonLabel) }}
|
||||
backButtonProps={{ text: i18n.translate('backButton.label') }}
|
||||
/>
|
||||
</MultistepDialog>
|
||||
);
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,63 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
//
|
||||
// Settings translation keys.
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Title = 'title',
|
||||
SelectHubPanelTitle = 'selectHubPanel.title',
|
||||
SelectHubPanelMessage = 'selectHubPanel.message',
|
||||
SelectHubPanelNotOnListButtonLabel = 'selectHubPanel.notOnListButton.label',
|
||||
SelectHubPanelNotOnListButtonInfoMindstormsTitle = 'selectHubPanel.notOnListButton.info.mindstorms.title',
|
||||
SelectHubPanelNotOnListButtonInfoMindstormsRcx = 'selectHubPanel.notOnListButton.info.mindstorms.rcx',
|
||||
SelectHubPanelNotOnListButtonInfoMindstormsNxt = 'selectHubPanel.notOnListButton.info.mindstorms.nxt',
|
||||
SelectHubPanelNotOnListButtonInfoMindstormsEv3 = 'selectHubPanel.notOnListButton.info.mindstorms.ev3',
|
||||
SelectHubPanelNotOnListButtonInfoPoweredUpTitle = 'selectHubPanel.notOnListButton.info.poweredUp.title',
|
||||
SelectHubPanelNotOnListButtonInfoPoweredUpWedo2 = 'selectHubPanel.notOnListButton.info.poweredUp.wedo2',
|
||||
SelectHubPanelNotOnListButtonInfoPoweredUpDuploTrain = 'selectHubPanel.notOnListButton.info.poweredUp.duploTrain',
|
||||
SelectHubPanelNotOnListButtonInfoPoweredUpMario = 'selectHubPanel.notOnListButton.info.poweredUp.mario',
|
||||
SelectHubPanelNotOnListButtonInfoPoweredUpFootnote = 'selectHubPanel.notOnListButton.info.poweredUp.footnote',
|
||||
LicensePanelTitle = 'licensePanel.title',
|
||||
LicensePanelLicenseTextError = 'licensePanel.licenseText.error',
|
||||
LicensePanelAcceptCheckboxLabel = 'licensePanel.acceptCheckbox.label',
|
||||
OptionsPanelTitle = 'optionsPanel.title',
|
||||
OptionsPanelHubNameLabel = 'optionsPanel.hubName.label',
|
||||
OptionsPanelHubNameLabelInfo = 'optionsPanel.hubName.labelInfo',
|
||||
OptionsPanelHubNameHelp = 'optionsPanel.hubName.help',
|
||||
OptionsPanelHubNameError = 'optionsPanel.hubName.error',
|
||||
OptionsPanelCustomMainLabel = 'optionsPanel.customMain.label',
|
||||
OptionsPanelCustomMainLabelInfo = 'optionsPanel.customMain.labelInfo',
|
||||
OptionsPanelCustomMainNotApplicableMessage = 'optionsPanel.customMain.notApplicable.message',
|
||||
OptionsPanelCustomMainIncludeLabel = 'optionsPanel.customMain.include.label',
|
||||
OptionsPanelCustomMainIncludeNoSelection = 'optionsPanel.customMain.include.noSelection',
|
||||
OptionsPanelCustomMainIncludeNoFiles = 'optionsPanel.customMain.include.noFiles',
|
||||
OptionsPanelCustomMainIncludeHelp = 'optionsPanel.customMain.include.help',
|
||||
BootloaderPanelTitle = 'bootloaderPanel.title',
|
||||
BootloaderPanelInstruction1 = 'bootloaderPanel.instruction1',
|
||||
BootloaderPanelButtonBluetooth = 'bootloaderPanel.button.bluetooth',
|
||||
BootloaderPanelButtonPower = 'bootloaderPanel.button.power',
|
||||
BootloaderPanelLightBluetooth = 'bootloaderPanel.light.bluetooth',
|
||||
BootloaderPanelLightStatus = 'bootloaderPanel.light.status',
|
||||
BootloaderPanelLightPatternBluetooth = 'bootloaderPanel.lightPattern.bluetooth',
|
||||
BootloaderPanelLightPatternStatus = 'bootloaderPanel.lightPattern.status',
|
||||
BootloaderPanelStepDisconnectUsb = 'bootloaderPanel.step.disconnectUsb',
|
||||
BootloaderPanelStepPowerOff = 'bootloaderPanel.step.powerOff',
|
||||
BootloaderPanelStepDisconnectIo = 'bootloaderPanel.step.disconnectIo',
|
||||
BootloaderPanelStepHoldButton = 'bootloaderPanel.step.holdButton',
|
||||
BootloaderPanelStepConnectUsb = 'bootloaderPanel.step.connectUsb',
|
||||
BootloaderPanelStepWaitForLight = 'bootloaderPanel.step.waitForLight',
|
||||
BootloaderPanelStepReleaseButton = 'bootloaderPanel.step.releaseButton',
|
||||
BootloaderPanelStepKeepHolding = 'bootloaderPanel.step.keepHolding',
|
||||
BootloaderPanelInstruction2 = 'bootloaderPanel.instruction2',
|
||||
NextButtonLabel = 'nextButton.label',
|
||||
BackButtonLabel = 'backButton.label',
|
||||
FlashFirmwareButtonLabel = 'flashFirmwareButton.label',
|
||||
}
|
||||
|
||||
+65
-2
@@ -1,7 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { I18nManager } from '@shopify/react-i18n';
|
||||
import { I18n, I18nManager, TranslateOptions } from '@shopify/react-i18n';
|
||||
import type {
|
||||
ComplexReplacementDictionary,
|
||||
PrimitiveReplacementDictionary,
|
||||
TranslationDictionary,
|
||||
} from '@shopify/react-i18n/build/ts/types';
|
||||
|
||||
// TODO: add locale setting and use browser preferred language as default
|
||||
|
||||
@@ -15,3 +20,61 @@ export const i18nManager = new I18nManager({
|
||||
export function pseudolocalize(pseudolocalize: boolean): void {
|
||||
i18nManager.update({ ...i18nManager.details, pseudolocalize });
|
||||
}
|
||||
|
||||
// brilliant magic to turn nested json keys into types
|
||||
// https://newbedev.com/typescript-deep-keyof-of-a-nested-object
|
||||
|
||||
type Join<K, P> = K extends string | number
|
||||
? P extends string | number
|
||||
? `${K}${'' extends P ? '' : '.'}${P}`
|
||||
: never
|
||||
: never;
|
||||
|
||||
// prettier-ignore
|
||||
type Prev = [
|
||||
never,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
...0[],
|
||||
];
|
||||
|
||||
type Paths<T, D extends number = 10> = [D] extends [never]
|
||||
? never
|
||||
: T extends object
|
||||
? {
|
||||
[K in keyof T]-?: K extends string | number
|
||||
? `${K}` | Join<K, Paths<T[K], Prev[D]>>
|
||||
: never;
|
||||
}[keyof T]
|
||||
: '';
|
||||
|
||||
/**
|
||||
* Interface for {@link I18n} that provides strong type checking for
|
||||
* translations ids.
|
||||
*
|
||||
* @typeParam T - The type of the translation json file.
|
||||
*/
|
||||
export type TypedI18n<T> = Omit<
|
||||
I18n,
|
||||
'translate' | 'getTranslationTree' | 'translationKeyExists'
|
||||
> & {
|
||||
translate(
|
||||
id: Paths<T>,
|
||||
options: TranslateOptions,
|
||||
replacements?: PrimitiveReplacementDictionary,
|
||||
): string;
|
||||
translate(
|
||||
id: Paths<T>,
|
||||
options: TranslateOptions,
|
||||
replacements?: ComplexReplacementDictionary,
|
||||
): React.ReactElement;
|
||||
translate(id: Paths<T>, replacements?: PrimitiveReplacementDictionary): string;
|
||||
translate(
|
||||
id: Paths<T>,
|
||||
replacements?: ComplexReplacementDictionary,
|
||||
): React.ReactElement;
|
||||
getTranslationTree(
|
||||
id: Paths<T>,
|
||||
replacements?: PrimitiveReplacementDictionary | ComplexReplacementDictionary,
|
||||
): string | TranslationDictionary;
|
||||
translationKeyExists(id: Paths<T>): boolean;
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ import React, { useCallback, useState } from 'react';
|
||||
import { mergeProps, useFocusRing, useListBox, useOption } from 'react-aria';
|
||||
import { useFetch } from 'usehooks-ts';
|
||||
import { appName } from '../app/constants';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
interface LicenseInfo {
|
||||
readonly name: string;
|
||||
@@ -158,11 +158,11 @@ const LicenseListPanel: React.VoidFunctionComponent<LicenseListPanelProps> = ({
|
||||
<div className="pb-license-list">
|
||||
{data === undefined ? (
|
||||
<NonIdealState>
|
||||
{error ? i18n.translate(I18nId.ErrorFetchFailed) : <Spinner />}
|
||||
{error ? i18n.translate('error.fetchFailed') : <Spinner />}
|
||||
</NonIdealState>
|
||||
) : (
|
||||
<ListBox
|
||||
aria-label={i18n.translate(I18nId.PackageListLabel)}
|
||||
aria-label={i18n.translate('packageList.label')}
|
||||
selectionMode="single"
|
||||
onSelectionChange={handleSelectionChanged}
|
||||
items={data}
|
||||
@@ -187,13 +187,13 @@ const LicenseInfoPanel = React.forwardRef<HTMLDivElement, LicenseInfoPanelProps>
|
||||
<div className="pb-license-info" ref={ref}>
|
||||
{licenseInfo === undefined ? (
|
||||
<NonIdealState>
|
||||
{i18n.translate(I18nId.SelectPackageHelp)}
|
||||
{i18n.translate('help.selectPackage')}
|
||||
</NonIdealState>
|
||||
) : (
|
||||
<div>
|
||||
<Card>
|
||||
<p>
|
||||
<strong>{i18n.translate(I18nId.PackageLabel)}</strong>{' '}
|
||||
<strong>{i18n.translate('packageLabel')}</strong>{' '}
|
||||
{licenseInfo.name}{' '}
|
||||
<span className={Classes.TEXT_MUTED}>
|
||||
v{licenseInfo.version}
|
||||
@@ -201,14 +201,12 @@ const LicenseInfoPanel = React.forwardRef<HTMLDivElement, LicenseInfoPanelProps>
|
||||
</p>
|
||||
{licenseInfo.author && (
|
||||
<p>
|
||||
<strong>
|
||||
{i18n.translate(I18nId.AuthorLabel)}
|
||||
</strong>{' '}
|
||||
<strong>{i18n.translate('authorLabel')}</strong>{' '}
|
||||
{licenseInfo.author}
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<strong>{i18n.translate(I18nId.LicenseLabel)}</strong>{' '}
|
||||
<strong>{i18n.translate('licenseLabel')}</strong>{' '}
|
||||
{licenseInfo.license}
|
||||
</p>
|
||||
</Card>
|
||||
@@ -240,13 +238,13 @@ const LicenseDialog: React.VoidFunctionComponent<LicenseDialogProps> = ({
|
||||
return (
|
||||
<Dialog
|
||||
className="pb-license-dialog"
|
||||
title={i18n.translate(I18nId.Title)}
|
||||
title={i18n.translate('title')}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<Callout className={Classes.INTENT_PRIMARY} icon="info-sign">
|
||||
{i18n.translate(I18nId.Description, {
|
||||
{i18n.translate('description', {
|
||||
name: appName,
|
||||
})}
|
||||
</Callout>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+5
-16
@@ -1,23 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
// License dialog translation keys.
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Title = 'title',
|
||||
Description = 'description',
|
||||
PackageListLabel = 'packageList.label',
|
||||
PackageLabel = 'packageLabel',
|
||||
AuthorLabel = 'authorLabel',
|
||||
LicenseLabel = 'licenseLabel',
|
||||
ErrorFetchFailed = 'error.fetchFailed',
|
||||
SelectPackageHelp = 'help.selectPackage',
|
||||
}
|
||||
|
||||
+23
-27
@@ -28,7 +28,7 @@ import { useSelector } from '../reducers';
|
||||
import ExternalLinkIcon from '../utils/ExternalLinkIcon';
|
||||
import { isMacOS } from '../utils/os';
|
||||
import { useSettingIsShowDocsEnabled } from './hooks';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
import './settings.scss';
|
||||
|
||||
const Settings: React.VoidFunctionComponent = () => {
|
||||
@@ -55,15 +55,15 @@ const Settings: React.VoidFunctionComponent = () => {
|
||||
return (
|
||||
<div className="pb-settings">
|
||||
<FormGroup
|
||||
label={i18n.translate(I18nId.AppearanceTitle)}
|
||||
helperText={i18n.translate(I18nId.AppearanceZoomHelp, {
|
||||
label={i18n.translate('appearance.title')}
|
||||
helperText={i18n.translate('appearance.zoom.help', {
|
||||
in: <span>{isMacOS() ? 'Cmd' : 'Ctrl'}-+</span>,
|
||||
out: <span>{isMacOS() ? 'Cmd' : 'Ctrl'}--</span>,
|
||||
})}
|
||||
>
|
||||
<ControlGroup>
|
||||
<Switch
|
||||
label={i18n.translate(I18nId.AppearanceDocumentationLabel)}
|
||||
label={i18n.translate('appearance.documentation.label')}
|
||||
checked={isSettingShowDocsEnabled}
|
||||
onChange={(e) =>
|
||||
setIsSettingShowDocsEnabled(
|
||||
@@ -72,15 +72,13 @@ const Settings: React.VoidFunctionComponent = () => {
|
||||
}
|
||||
/>
|
||||
<HelpButton
|
||||
helpForLabel={i18n.translate(
|
||||
I18nId.AppearanceDocumentationLabel,
|
||||
)}
|
||||
content={i18n.translate(I18nId.AppearanceDocumentationHelp)}
|
||||
helpForLabel={i18n.translate('appearance.documentation.label')}
|
||||
content={i18n.translate('appearance.documentation.help')}
|
||||
/>
|
||||
</ControlGroup>
|
||||
<ControlGroup>
|
||||
<Switch
|
||||
label={i18n.translate(I18nId.AppearanceDarkModeLabel)}
|
||||
label={i18n.translate('appearance.darkMode.label')}
|
||||
checked={isDarkMode}
|
||||
onChange={(e) =>
|
||||
setTernaryDarkMode(
|
||||
@@ -91,42 +89,42 @@ const Settings: React.VoidFunctionComponent = () => {
|
||||
}
|
||||
/>
|
||||
<HelpButton
|
||||
helpForLabel={i18n.translate(I18nId.AppearanceDarkModeLabel)}
|
||||
content={i18n.translate(I18nId.AppearanceDarkModeHelp)}
|
||||
helpForLabel={i18n.translate('appearance.darkMode.label')}
|
||||
content={i18n.translate('appearance.darkMode.help')}
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FormGroup>
|
||||
<FormGroup label={i18n.translate(I18nId.FirmwareTitle)}>
|
||||
<FormGroup label={i18n.translate('firmware.title')}>
|
||||
<Button
|
||||
minimal={true}
|
||||
icon="download"
|
||||
label={i18n.translate(I18nId.FirmwareFlashPybricksLabel)}
|
||||
label={i18n.translate('firmware.flashPybricksButton.label')}
|
||||
onPress={() => dispatch(firmwareInstallPybricks())}
|
||||
/>
|
||||
<InstallPybricksDialog />
|
||||
<Button
|
||||
minimal={true}
|
||||
icon="download"
|
||||
label={i18n.translate(I18nId.FirmwareFlashLegoLabel)}
|
||||
label={i18n.translate('firmware.flashLegoButton.label')}
|
||||
onPress={() => dispatch(firmwareRestoreLego())}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label={i18n.translate(I18nId.HelpTitle)}>
|
||||
<FormGroup label={i18n.translate('help.title')}>
|
||||
<ButtonGroup minimal={true} vertical={true} alignText="left">
|
||||
<AnchorButton
|
||||
icon="lightbulb"
|
||||
href={pybricksProjectsUrl}
|
||||
target="blank_"
|
||||
>
|
||||
{i18n.translate(I18nId.HelpProjectsLabel)}
|
||||
{i18n.translate('help.projects.label')}
|
||||
<ExternalLinkIcon />
|
||||
</AnchorButton>
|
||||
<AnchorButton icon="help" href={pybricksSupportUrl} target="blank_">
|
||||
{i18n.translate(I18nId.HelpSupportLabel)}
|
||||
{i18n.translate('help.support.label')}
|
||||
<ExternalLinkIcon />
|
||||
</AnchorButton>
|
||||
<AnchorButton icon="chat" href={pybricksGitterUrl} target="blank_">
|
||||
{i18n.translate(I18nId.HelpChatLabel)}
|
||||
{i18n.translate('help.chat.label')}
|
||||
<ExternalLinkIcon />
|
||||
</AnchorButton>
|
||||
<AnchorButton
|
||||
@@ -134,7 +132,7 @@ const Settings: React.VoidFunctionComponent = () => {
|
||||
href={pybricksBugReportsUrl}
|
||||
target="blank_"
|
||||
>
|
||||
{i18n.translate(I18nId.HelpBugsLabel)}
|
||||
{i18n.translate('help.bugs.label')}
|
||||
<ExternalLinkIcon />
|
||||
</AnchorButton>
|
||||
<AboutDialog
|
||||
@@ -144,15 +142,13 @@ const Settings: React.VoidFunctionComponent = () => {
|
||||
</ButtonGroup>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={i18n.translate(I18nId.AppTitle)}
|
||||
helperText={
|
||||
readyForOfflineUse && i18n.translate(I18nId.AppOfflineUseHelp)
|
||||
}
|
||||
label={i18n.translate('app.title')}
|
||||
helperText={readyForOfflineUse && i18n.translate('app.offlineUseHelp')}
|
||||
>
|
||||
<ButtonGroup minimal={true} vertical={true} alignText="left">
|
||||
{hasUnresolvedInstallPrompt && (
|
||||
<Button
|
||||
label={i18n.translate(I18nId.AppInstallLabel)}
|
||||
label={i18n.translate('app.install.label')}
|
||||
icon="add"
|
||||
onPress={() => dispatch(appShowInstallPrompt())}
|
||||
loading={promptingInstall}
|
||||
@@ -161,7 +157,7 @@ const Settings: React.VoidFunctionComponent = () => {
|
||||
{(process.env.NODE_ENV === 'development' ||
|
||||
(isServiceWorkerRegistered && !updateAvailable)) && (
|
||||
<Button
|
||||
label={i18n.translate(I18nId.AppCheckForUpdateLabel)}
|
||||
label={i18n.translate('app.checkForUpdate.label')}
|
||||
icon="refresh"
|
||||
onPress={() => dispatch(appCheckForUpdate())}
|
||||
loading={checkingForUpdate}
|
||||
@@ -170,13 +166,13 @@ const Settings: React.VoidFunctionComponent = () => {
|
||||
{(process.env.NODE_ENV === 'development' ||
|
||||
(isServiceWorkerRegistered && updateAvailable)) && (
|
||||
<Button
|
||||
label={i18n.translate(I18nId.AppRestartLabel)}
|
||||
label={i18n.translate('app.restart.label')}
|
||||
icon="refresh"
|
||||
onPress={() => dispatch(appReload())}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
label={i18n.translate(I18nId.AppAboutLabel)}
|
||||
label={i18n.translate('app.about.label')}
|
||||
icon="info-sign"
|
||||
onPress={() => {
|
||||
setIsAboutDialogOpen(true);
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+5
-29
@@ -1,36 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
//
|
||||
// Settings translation keys.
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Title = 'title',
|
||||
AppearanceTitle = 'appearance.title',
|
||||
AppearanceDocumentationLabel = 'appearance.documentation.label',
|
||||
AppearanceDocumentationHelp = 'appearance.documentation.help',
|
||||
AppearanceDarkModeLabel = 'appearance.darkMode.label',
|
||||
AppearanceDarkModeHelp = 'appearance.darkMode.help',
|
||||
AppearanceZoomHelp = 'appearance.zoom.help',
|
||||
FirmwareTitle = 'firmware.title',
|
||||
FirmwareFlashPybricksLabel = 'firmware.flashPybricksButton.label',
|
||||
FirmwareFlashLegoLabel = 'firmware.flashLegoButton.label',
|
||||
HelpTitle = 'help.title',
|
||||
HelpProjectsLabel = 'help.projects.label',
|
||||
HelpSupportLabel = 'help.support.label',
|
||||
HelpChatLabel = 'help.chat.label',
|
||||
HelpBugsLabel = 'help.bugs.label',
|
||||
AppTitle = 'app.title',
|
||||
AppOfflineUseHelp = 'app.offlineUseHelp',
|
||||
AppInstallLabel = 'app.install.label',
|
||||
AppCheckForUpdateLabel = 'app.checkForUpdate.label',
|
||||
AppRestartLabel = 'app.restart.label',
|
||||
AppAboutLabel = 'app.about.label',
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import React, { useMemo } from 'react';
|
||||
import { BleConnectionState } from '../ble/reducers';
|
||||
import { CompletionEngineStatus } from '../editor/redux/codeCompletion';
|
||||
import { useSelector } from '../reducers';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
import './status-bar.scss';
|
||||
|
||||
@@ -43,20 +43,20 @@ const CompletionEngineIndicator: React.VoidFunctionComponent = () => {
|
||||
const message = useMemo(() => {
|
||||
switch (status) {
|
||||
case CompletionEngineStatus.Loading:
|
||||
return i18n.translate(I18nId.CompletionEngineStatusMessageLoading);
|
||||
return i18n.translate('completionEngineStatus.message.loading');
|
||||
case CompletionEngineStatus.Ready:
|
||||
return i18n.translate(I18nId.CompletionEngineStatusMessageReady);
|
||||
return i18n.translate('completionEngineStatus.message.ready');
|
||||
case CompletionEngineStatus.Failed:
|
||||
return i18n.translate(I18nId.CompletionEngineStatusMessageFailed);
|
||||
return i18n.translate('completionEngineStatus.message.failed');
|
||||
default:
|
||||
return i18n.translate(I18nId.CompletionEngineStatusMessageUnknown);
|
||||
return i18n.translate('completionEngineStatus.message.unknown');
|
||||
}
|
||||
}, [status, i18n]);
|
||||
|
||||
return (
|
||||
<Popover2 {...commonPopoverProps} content={message}>
|
||||
<div
|
||||
aria-label={i18n.translate(I18nId.CompletionEngineStatusLabel)}
|
||||
aria-label={i18n.translate('completionEngineStatus.label')}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{icon}
|
||||
@@ -79,23 +79,19 @@ const HubInfoButton: React.VoidFunctionComponent = () => {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>
|
||||
{i18n.translate(I18nId.HubInfoConnectedTo)}
|
||||
</strong>
|
||||
<strong>{i18n.translate('hubInfo.connectedTo')}</strong>
|
||||
</td>
|
||||
<td>{deviceName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{i18n.translate(I18nId.HubInfoHubType)}</strong>
|
||||
<strong>{i18n.translate('hubInfo.hubType')}</strong>
|
||||
</td>
|
||||
<td>{deviceType}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>
|
||||
{i18n.translate(I18nId.HubInfoFirmware)}
|
||||
</strong>
|
||||
<strong>{i18n.translate('hubInfo.firmware')}</strong>
|
||||
</td>
|
||||
<td>v{deviceFirmwareVersion}</td>
|
||||
</tr>
|
||||
@@ -103,7 +99,7 @@ const HubInfoButton: React.VoidFunctionComponent = () => {
|
||||
</table>
|
||||
}
|
||||
>
|
||||
<Button title={i18n.translate(I18nId.HubInfoTitle)} minimal={true}>
|
||||
<Button title={i18n.translate('hubInfo.title')} minimal={true}>
|
||||
{deviceName}
|
||||
</Button>
|
||||
</Popover2>
|
||||
@@ -120,14 +116,12 @@ const BatteryIndicator: React.VoidFunctionComponent = () => {
|
||||
{...commonPopoverProps}
|
||||
content={
|
||||
<span className="no-wrap">
|
||||
{i18n.translate(
|
||||
lowBatteryWarning ? I18nId.BatteryLow : I18nId.BatteryOk,
|
||||
)}
|
||||
{i18n.translate(lowBatteryWarning ? 'battery.low' : 'battery.ok')}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div
|
||||
title={i18n.translate(I18nId.BatteryTitle)}
|
||||
title={i18n.translate('battery.title')}
|
||||
className="pb-battery-indicator"
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+5
-20
@@ -1,27 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
//
|
||||
// Status bar translation keys.
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
CompletionEngineStatusLabel = 'completionEngineStatus.label',
|
||||
CompletionEngineStatusMessageLoading = 'completionEngineStatus.message.loading',
|
||||
CompletionEngineStatusMessageReady = 'completionEngineStatus.message.ready',
|
||||
CompletionEngineStatusMessageFailed = 'completionEngineStatus.message.failed',
|
||||
CompletionEngineStatusMessageUnknown = 'completionEngineStatus.message.unknown',
|
||||
BatteryTitle = 'battery.title',
|
||||
BatteryLow = 'battery.low',
|
||||
BatteryOk = 'battery.ok',
|
||||
HubInfoTitle = 'hubInfo.title',
|
||||
HubInfoConnectedTo = 'hubInfo.connectedTo',
|
||||
HubInfoHubType = 'hubInfo.hubType',
|
||||
HubInfoFirmware = 'hubInfo.firmware',
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { FitAddon } from 'xterm-addon-fit';
|
||||
import { isMacOS } from '../utils/os';
|
||||
import { TerminalContext } from './TerminalContext';
|
||||
import { receiveData } from './actions';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
import 'xterm/css/xterm.css';
|
||||
|
||||
@@ -64,7 +64,7 @@ function createContextMenu(
|
||||
navigator.clipboard.writeText(selected);
|
||||
}
|
||||
}}
|
||||
text={i18n.translate(I18nId.Copy)}
|
||||
text={i18n.translate('copy')}
|
||||
icon="duplicate"
|
||||
label={isMacOS() ? 'Cmd-C' : 'Ctrl-Shift-C'}
|
||||
disabled={!xterm.hasSelection()}
|
||||
@@ -73,19 +73,19 @@ function createContextMenu(
|
||||
onClick={async (): Promise<void> => {
|
||||
xterm.paste(await navigator.clipboard.readText());
|
||||
}}
|
||||
text={i18n.translate(I18nId.Paste)}
|
||||
text={i18n.translate('paste')}
|
||||
icon="clipboard"
|
||||
label={isMacOS() ? 'Cmd-V' : 'Ctrl-V'}
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => xterm.selectAll()}
|
||||
text={i18n.translate(I18nId.SelectAll)}
|
||||
text={i18n.translate('selectAll')}
|
||||
icon="blank"
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
onClick={(): void => xterm.clear()}
|
||||
text={i18n.translate(I18nId.Clear)}
|
||||
text={i18n.translate('clear')}
|
||||
icon="trash"
|
||||
/>
|
||||
</Menu>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
+5
-12
@@ -1,19 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
//
|
||||
// Terminal translation keys.
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Copy = 'copy',
|
||||
Paste = 'paste',
|
||||
SelectAll = 'selectAll',
|
||||
Clear = 'clear',
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useSelector } from '../../../reducers';
|
||||
import ActionButton, { ActionButtonProps } from '../../ActionButton';
|
||||
import connectedIcon from './connected.svg';
|
||||
import disconnectedIcon from './disconnected.svg';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
type BluetoothButtonProps = Pick<ActionButtonProps, 'id'>;
|
||||
|
||||
@@ -28,9 +28,9 @@ const BluetoothButton: React.VoidFunctionComponent<BluetoothButtonProps> = ({ id
|
||||
return (
|
||||
<ActionButton
|
||||
id={id}
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
label={i18n.translate('label')}
|
||||
tooltip={i18n.translate(
|
||||
isDisconnected ? I18nId.TooltipConnect : I18nId.TooltipDisconnect,
|
||||
isDisconnected ? 'tooltip.connect' : 'tooltip.disconnect',
|
||||
)}
|
||||
icon={isDisconnected ? disconnectedIcon : connectedIcon}
|
||||
enabled={isDisconnected || bleConnection === BleConnectionState.Connected}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
TooltipConnect = 'tooltip.connect',
|
||||
TooltipDisconnect = 'tooltip.disconnect',
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { repl } from '../../../hub/actions';
|
||||
import { HubRuntimeState } from '../../../hub/reducers';
|
||||
import { useSelector } from '../../../reducers';
|
||||
import ActionButton, { ActionButtonProps } from '../../ActionButton';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
import icon from './icon.svg';
|
||||
|
||||
type ReplButtonProps = Pick<ActionButtonProps, 'id'>;
|
||||
@@ -22,8 +22,8 @@ const ReplButton: React.VoidFunctionComponent<ReplButtonProps> = ({ id }) => {
|
||||
return (
|
||||
<ActionButton
|
||||
id={id}
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
tooltip={i18n.translate(I18nId.Tooltip)}
|
||||
label={i18n.translate('label')}
|
||||
tooltip={i18n.translate('tooltip')}
|
||||
icon={icon}
|
||||
enabled={enabled}
|
||||
onAction={action}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
Tooltip = 'tooltip',
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { downloadAndRun } from '../../../hub/actions';
|
||||
import { HubRuntimeState } from '../../../hub/reducers';
|
||||
import { useSelector } from '../../../reducers';
|
||||
import ActionButton, { ActionButtonProps } from '../../ActionButton';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
import icon from './icon.svg';
|
||||
|
||||
type RunButtonProps = Pick<ActionButtonProps, 'id'>;
|
||||
@@ -25,14 +25,14 @@ const RunButton: React.VoidFunctionComponent<RunButtonProps> = ({ id }) => {
|
||||
return (
|
||||
<ActionButton
|
||||
id={id}
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
label={i18n.translate('label')}
|
||||
keyboardShortcut={keyboardShortcut}
|
||||
tooltip={
|
||||
downloadProgress
|
||||
? i18n.translate(I18nId.TooltipProgress, {
|
||||
? i18n.translate('tooltip.progress', {
|
||||
percent: i18n.formatPercentage(downloadProgress),
|
||||
})
|
||||
: i18n.translate(I18nId.TooltipAction, { key: keyboardShortcut })
|
||||
: i18n.translate('tooltip.action', { key: keyboardShortcut })
|
||||
}
|
||||
icon={icon}
|
||||
enabled={isEditorReady && runtime === HubRuntimeState.Idle}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
TooltipAction = 'tooltip.action',
|
||||
TooltipProgress = 'tooltip.progress',
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { stop } from '../../../hub/actions';
|
||||
import { HubRuntimeState } from '../../../hub/reducers';
|
||||
import { useSelector } from '../../../reducers';
|
||||
import ActionButton, { ActionButtonProps } from '../../ActionButton';
|
||||
import { I18nId, useI18n } from './i18n';
|
||||
import { useI18n } from './i18n';
|
||||
import icon from './icon.svg';
|
||||
|
||||
type StopButtonProps = Pick<ActionButtonProps, 'id'>;
|
||||
@@ -22,9 +22,9 @@ const StopButton: React.VoidFunctionComponent<StopButtonProps> = ({ id }) => {
|
||||
return (
|
||||
<ActionButton
|
||||
id={id}
|
||||
label={i18n.translate(I18nId.Label)}
|
||||
label={i18n.translate('label')}
|
||||
keyboardShortcut={keyboardShortcut}
|
||||
tooltip={i18n.translate(I18nId.Tooltip, { key: keyboardShortcut })}
|
||||
tooltip={i18n.translate('tooltip', { key: keyboardShortcut })}
|
||||
icon={icon}
|
||||
enabled={runtime === HubRuntimeState.Running}
|
||||
onAction={() => dispatch(stop())}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { lookup } from '../../../../test';
|
||||
import { I18nId } from './i18n';
|
||||
import en from './translations/en.json';
|
||||
|
||||
describe('Ensure .json file has matches for I18nId', () => {
|
||||
test.each(Object.values(I18nId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { I18n, useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): I18n {
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export enum I18nId {
|
||||
Label = 'label',
|
||||
Tooltip = 'tooltip',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user