Skip to content

Commit

Permalink
some support for dynamic locale in keyboard help, #769
Browse files Browse the repository at this point in the history
  • Loading branch information
pixelzoom committed Aug 30, 2022
1 parent 5f2eea4 commit ef3b51a
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 33 deletions.
3 changes: 1 addition & 2 deletions js/keyboard/help/BasicActionsKeyboardHelpSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import KeyboardHelpSection from './KeyboardHelpSection.js';
import KeyboardHelpSectionRow from './KeyboardHelpSectionRow.js';

// constants
const keyboardHelpDialogBasicActionsString = sceneryPhetStrings.keyboardHelpDialog.basicActions;
const keyboardHelpDialogExitADialogString = sceneryPhetStrings.keyboardHelpDialog.exitADialog;
const keyboardHelpDialogMoveBetweenItemsInAGroupString = sceneryPhetStrings.keyboardHelpDialog.moveBetweenItemsInAGroup;
const keyboardHelpDialogMoveToNextItemOrGroupString = sceneryPhetStrings.keyboardHelpDialog.moveToNextItemOrGroup;
Expand Down Expand Up @@ -101,7 +100,7 @@ class BasicActionsKeyboardHelpSection extends KeyboardHelpSection {
].filter( row => row !== null ); // If any optional rows are null, omit them.

// order the rows of content
super( keyboardHelpDialogBasicActionsString, content, options );
super( sceneryPhetStrings.keyboardHelpDialog.basicActionsStringProperty, content, options );
}
}

Expand Down
53 changes: 35 additions & 18 deletions js/keyboard/help/ComboBoxKeyboardHelpSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* @author Michael Kauzmann (PhET Interactive Simulations)
*/

import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import StringProperty from '../../../../axon/js/StringProperty.js';
import merge from '../../../../phet-core/js/merge.js';
import StringUtils from '../../../../phetcommon/js/util/StringUtils.js';
import sceneryPhet from '../../sceneryPhet.js';
Expand All @@ -24,13 +26,13 @@ class ComboBoxKeyboardHelpSection extends KeyboardHelpSection {

options = merge( {

// Heading for the section, should be capitalized as a title
headingString: sceneryPhetStrings.keyboardHelpDialog.comboBox.headingString,
// {string|TReadOnlyProperty.<string>} Heading for the section, should be capitalized as a title
headingString: sceneryPhetStrings.keyboardHelpDialog.comboBox.headingStringStringProperty,

// the item being changed by the combo box, lower case as used in a sentence.
// {string|TReadOnlyProperty.<string>} the item being changed by the combo box, lower case as used in a sentence.
thingAsLowerCaseSingular: sceneryPhetStrings.keyboardHelpDialog.comboBox.option,

// plural version of thingAsLowerCaseSingular
// {string|TReadOnlyProperty.<string>} plural version of thingAsLowerCaseSingular
thingAsLowerCasePlural: sceneryPhetStrings.keyboardHelpDialog.comboBox.options,

a11yContentTagName: 'ol', // ordered list
Expand All @@ -39,30 +41,45 @@ class ComboBoxKeyboardHelpSection extends KeyboardHelpSection {
}
}, options );

// convenience function for all the filling in done below
const fillIn = stringPattern => StringUtils.fillIn( stringPattern, {
thingPlural: options.thingAsLowerCasePlural,
thingSingular: options.thingAsLowerCaseSingular
} );

const popUpList = KeyboardHelpSectionRow.labelWithIcon( fillIn( sceneryPhetStrings.keyboardHelpDialog.comboBox.popUpListPattern ),
// options may be string or TReadOnlyProperty<string>, so ensure that we have a TReadOnlyProperty<string>.
const thingAsLowerCasePluralStringProperty = ( typeof options.thingAsLowerCasePlural === 'string' ) ?
new StringProperty( options.thingAsLowerCasePlural ) :
options.thingAsLowerCasePlural;
const thingAsLowerCaseSingularStringProperty = ( typeof options.thingAsLowerCaseSingular === 'string' ) ?
new StringProperty( options.thingAsLowerCaseSingular ) :
options.thingAsLowerCaseSingular;

// Create a DerivedProperty that fills in a plural/singular pattern, and support dynamic locale.
const createDerivedStringProperty = patternStringProperty => new DerivedProperty(
[ patternStringProperty, thingAsLowerCasePluralStringProperty, thingAsLowerCaseSingularStringProperty ],
( patternString, thingAsLowerCasePluralString, thingAsLowerCaseSingular ) =>
StringUtils.fillIn( patternString, {
thingPlural: thingAsLowerCasePluralString,
thingSingular: thingAsLowerCaseSingular
} ) );

const popUpList = KeyboardHelpSectionRow.labelWithIcon(
createDerivedStringProperty( sceneryPhetStrings.keyboardHelpDialog.comboBox.popUpListPatternStringProperty ),
KeyboardHelpIconFactory.iconOrIcon( TextKeyNode.space(), TextKeyNode.enter() ), {
labelInnerContent: fillIn( sceneryPhetStrings.a11y.keyboardHelpDialog.comboBox.popUpListPatternDescription )
labelInnerContent: createDerivedStringProperty( sceneryPhetStrings.a11y.keyboardHelpDialog.comboBox.popUpListPatternDescriptionStringProperty )
} );

const moveThrough = KeyboardHelpSectionRow.labelWithIcon( fillIn( sceneryPhetStrings.keyboardHelpDialog.comboBox.moveThroughPattern ),
const moveThrough = KeyboardHelpSectionRow.labelWithIcon(
createDerivedStringProperty( sceneryPhetStrings.keyboardHelpDialog.comboBox.moveThroughPatternStringProperty ),
KeyboardHelpIconFactory.upDownArrowKeysRowIcon(), {
labelInnerContent: fillIn( sceneryPhetStrings.a11y.keyboardHelpDialog.comboBox.moveThroughPatternDescription )
labelInnerContent: createDerivedStringProperty( sceneryPhetStrings.a11y.keyboardHelpDialog.comboBox.moveThroughPatternDescriptionStringProperty )
} );

const chooseNew = KeyboardHelpSectionRow.labelWithIcon( fillIn( sceneryPhetStrings.keyboardHelpDialog.comboBox.chooseNewPattern ),
const chooseNew = KeyboardHelpSectionRow.labelWithIcon(
createDerivedStringProperty( sceneryPhetStrings.keyboardHelpDialog.comboBox.chooseNewPatternStringProperty ),
TextKeyNode.enter(), {
labelInnerContent: fillIn( sceneryPhetStrings.a11y.keyboardHelpDialog.comboBox.chooseNewPatternDescription )
labelInnerContent: createDerivedStringProperty( sceneryPhetStrings.a11y.keyboardHelpDialog.comboBox.chooseNewPatternDescriptionStringProperty )
} );

const closeWithoutChanging = KeyboardHelpSectionRow.labelWithIcon( sceneryPhetStrings.keyboardHelpDialog.comboBox.closeWithoutChanging,
const closeWithoutChanging = KeyboardHelpSectionRow.labelWithIcon(
sceneryPhetStrings.keyboardHelpDialog.comboBox.closeWithoutChangingStringProperty,
TextKeyNode.esc(), {
labelInnerContent: sceneryPhetStrings.a11y.keyboardHelpDialog.comboBox.closeWithoutChangingDescription
labelInnerContent: sceneryPhetStrings.a11y.keyboardHelpDialog.comboBox.closeWithoutChangingDescriptionStringProperty
} );

// order the rows of content
Expand Down
10 changes: 8 additions & 2 deletions js/keyboard/help/KeyboardHelpSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* @author Jesse Greenberg
*/

import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
import optionize, { combineOptions } from '../../../../phet-core/js/optionize.js';
import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js';
import StringUtils from '../../../../phetcommon/js/util/StringUtils.js';
Expand Down Expand Up @@ -66,7 +67,7 @@ export type KeyboardHelpSectionOptions = SelfOptions & ParentOptions;
export default class KeyboardHelpSection extends ReadingBlock( VBox ) {

// the translatable heading for this section
private readonly headingString: string;
private readonly headingString: string | TReadOnlyProperty<string>;

// collection of icons in this section
private readonly icons: Node[];
Expand All @@ -87,7 +88,8 @@ export default class KeyboardHelpSection extends ReadingBlock( VBox ) {
* see labelWithIcon() and labelWithIconList().
* @param [providedOptions]
*/
public constructor( headingString: string, content: KeyboardHelpSectionRow[], providedOptions?: KeyboardHelpSectionOptions ) {
public constructor( headingString: string | TReadOnlyProperty<string>, content: KeyboardHelpSectionRow[],
providedOptions?: KeyboardHelpSectionOptions ) {

const options = optionize<KeyboardHelpSectionOptions, SelfOptions, ParentOptions>()( {

Expand All @@ -98,6 +100,8 @@ export default class KeyboardHelpSection extends ReadingBlock( VBox ) {

// pdom
tagName: 'h2',

//TODO https://github.com/phetsims/scenery-phet/issues/769 is it OK to use dynamic translated string here?
innerContent: headingString
},
textMaxWidth: DEFAULT_LABEL_MAX_WIDTH,
Expand Down Expand Up @@ -174,6 +178,8 @@ export default class KeyboardHelpSection extends ReadingBlock( VBox ) {
// Include the section heading. Headings typically don't have punctuation, but don't use a period because
// it may appear to the synth as an abbreviation and change the pronunciation.
let readingBlockNameResponse = '';

//TODO https://github.com/phetsims/scenery-phet/issues/769 is it OK to use a dynamic translated string here?
readingBlockNameResponse += `${this.headingString}, `;

// Append the readingBlockNameResponse assigned to each row.
Expand Down
28 changes: 19 additions & 9 deletions js/keyboard/help/KeyboardHelpSectionRow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* @author Jesse Greenberg (PhET Interactive Simulations)
*/

import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
import optionize, { combineOptions } from '../../../../phet-core/js/optionize.js';
import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js';
import { AlignBoxOptions, AlignGroup, HBox, Node, RichText, RichTextOptions, Text, VBox, VBoxOptions } from '../../../../scenery/js/imports.js';
Expand All @@ -32,7 +33,8 @@ const OR_TEXT_MAX_WIDTH = 16;
type LabelWithIconListOptions = {

// content for the parallel DOM, read by a screen reader
labelInnerContent?: string | null;
//TODO https://github.com/phetsims/scenery-phet/issues/769 is it OK to use a dynamic translated string here?
labelInnerContent?: string | TReadOnlyProperty<string> | null;

// voicing
// Content for this icon that is read by the Voicing feature when in a KeyboardHelpSection. If null,
Expand Down Expand Up @@ -98,7 +100,8 @@ class KeyboardHelpSectionRow {
* Horizontally align a label and an icon, with the label on the left and the icon on the right. AlignGroup is used
* to give the label and icon identical dimensions for easy layout in KeyboardHelpSection.
*/
public static labelWithIcon( labelString: string, icon: Node, providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
public static labelWithIcon( labelString: string | TReadOnlyProperty<string>, icon: Node,
providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
const options = optionize<LabelWithIconOptions>()( {
labelInnerContent: null,
readingBlockContent: null,
Expand Down Expand Up @@ -132,14 +135,16 @@ class KeyboardHelpSectionRow {
* @param labelString
* @param [providedOptions]
*/
public static createKeysRowFromStrings( keyStrings: string[], labelString: string, providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
public static createKeysRowFromStrings( keyStrings: string[], labelString: string | TReadOnlyProperty<string>,
providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
return KeyboardHelpSectionRow.createKeysRow( keyStrings.map( key => new LetterKeyNode( key ) ), labelString, providedOptions );
}

/**
* Creates a row with one or more keys, with keys separated by '+'.
*/
public static createKeysRow( keyIcons: Node[], labelString: string, providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
public static createKeysRow( keyIcons: Node[], labelString: string | TReadOnlyProperty<string>,
providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
assert && assert( keyIcons.length > 0, 'expected keys' );
let keysNode = null;
for ( let i = 0; i < keyIcons.length; i++ ) {
Expand All @@ -161,29 +166,33 @@ class KeyboardHelpSectionRow {
* @param labelString - visual label
* @param [providedOptions]
*/
public static createJumpKeyRow( keyString: string, labelString: string, providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
public static createJumpKeyRow( keyString: string, labelString: string | TReadOnlyProperty<string>,
providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
return KeyboardHelpSectionRow.createKeysRowFromStrings( [ 'J', keyString ], labelString, providedOptions );
}

/**
* Create a KeyboardHelpSectionRow that describes how to play and pause the sim with the "Alt" + "K" hotkey.
*/
public static createPlayPauseKeyRow( labelString: string, providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
public static createPlayPauseKeyRow( labelString: string | TReadOnlyProperty<string>,
providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
return KeyboardHelpSectionRow.createGlobalHotkeyRow( labelString, 'K', providedOptions );
}

/**
* Create a KeyboardHelpSectionRow that describes how to step forward the sim with the "Alt" + "L" hotkeys.
*/
public static createStepForwardKeyRow( labelString: string, providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
public static createStepForwardKeyRow( labelString: string | TReadOnlyProperty<string>,
providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
return KeyboardHelpSectionRow.createGlobalHotkeyRow( labelString, 'L', providedOptions );
}

/**
* Create a KeyboardHelpSectionRow that describes how to use a global hotkey. Global hotkeys are triggered with "Alt" plus
* some other key, to be provided.
*/
public static createGlobalHotkeyRow( labelString: string, keyString: string, providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
public static createGlobalHotkeyRow( labelString: string | TReadOnlyProperty<string>, keyString: string,
providedOptions?: LabelWithIconOptions ): KeyboardHelpSectionRow {
return KeyboardHelpSectionRow.createKeysRow( [ TextKeyNode.alt(), new LetterKeyNode( keyString ) ], labelString, providedOptions );
}

Expand All @@ -197,7 +206,8 @@ class KeyboardHelpSectionRow {
* Icon2 or
* Icon3
*/
public static labelWithIconList( labelString: string, icons: Node[], providedOptions?: LabelWithIconListOptions ): KeyboardHelpSectionRow {
public static labelWithIconList( labelString: string | TReadOnlyProperty<string>, icons: Node[],
providedOptions?: LabelWithIconListOptions ): KeyboardHelpSectionRow {

const options = optionize<LabelWithIconListOptions>()( {
labelInnerContent: null,
Expand Down
2 changes: 1 addition & 1 deletion js/keyboard/help/SliderControlsKeyboardHelpSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class SliderControlsKeyboardHelpSection extends KeyboardHelpSection {
arrowKeyIconDisplay: ArrowKeyIconDisplay.BOTH,

// heading string for this content
headingString: sceneryPhetStrings.keyboardHelpDialog.sliderControls,
headingString: sceneryPhetStrings.keyboardHelpDialog.sliderControlsStringProperty,

// verb used to describe the movement of the slider
verbString: sceneryPhetStrings.keyboardHelpDialog.adjust,
Expand Down
3 changes: 2 additions & 1 deletion js/keyboard/help/TimingControlsKeyboardHelpSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* @author Jesse Greenberg (PhET Interactive Simulations)
*/

import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
import optionize from '../../../../phet-core/js/optionize.js';
import sceneryPhet from '../../sceneryPhet.js';
import KeyboardHelpSection, { KeyboardHelpSectionOptions } from './KeyboardHelpSection.js';
Expand All @@ -20,7 +21,7 @@ const pauseOrPlayActionDescriptionString = 'Pause or play action with alt key pl
type SelfOptions = {

// The heading string for this section of keyboard help content
headingString?: string;
headingString?: string | TReadOnlyProperty<string>;

// Visible string that describes the action of pause/play from a key command. You may want sim-specific terminology
// for this command.
Expand Down

0 comments on commit ef3b51a

Please sign in to comment.