mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-14 10:35:11 +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:
+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',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user