Skip to content

Commit

Permalink
locale query parameter mapping logic should be reusable, and localePr…
Browse files Browse the repository at this point in the history
…operty should support it with grace, #970

Signed-off-by: Michael Kauzmann <[email protected]>
  • Loading branch information
zepumph committed Jun 11, 2024
1 parent ff8657b commit 5f43ece
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 41 deletions.
62 changes: 30 additions & 32 deletions js/i18n/localeProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,53 +13,51 @@ import { globalKeyStateTracker, KeyboardUtils } from '../../../scenery/js/import
import Tandem from '../../../tandem/js/Tandem.js';
import StringIO from '../../../tandem/js/types/StringIO.js';
import joist from '../joist.js';

const FALLBACK_LOCALE = 'en';
import { ReadOnlyPropertyState } from '../../../axon/js/ReadOnlyProperty.js';

export type Locale = keyof typeof localeInfoModule;

assert && assert( phet.chipper.locale, 'phet.chipper.locale global expected' );
assert && assert( phet.chipper.localeData, 'phet.chipper.localeData global expected' );
assert && assert( phet.chipper.strings, 'phet.chipper.strings global expected' );

// All available locales for the runtime
export const availableRuntimeLocales = _.sortBy( Object.keys( phet.chipper.strings ), locale => {
const availableRuntimeLocales = _.sortBy( Object.keys( phet.chipper.strings ), locale => {
return StringUtils.localeToLocalizedName( locale ).toLowerCase();
} ) as Locale[];

// Start only with a valid locale, see https://github.com/phetsims/phet-io/issues/1882
const isLocaleValid = ( locale?: Locale ): boolean => {
return !!( locale && availableRuntimeLocales.includes( locale ) );
};

// Get the "most" valid locale, see https://github.com/phetsims/phet-io/issues/1882
// As part of https://github.com/phetsims/joist/issues/963, this as changed. We check a specific fallback order based
// on the locale. In general, it will usually try a prefix for xx_XX style locales, e.g. 'ar_SA' would try 'ar_SA', 'ar', 'en'
// NOTE: If the locale doesn't actually have any strings: THAT IS OK! Our string system will use the appropriate
// fallback strings.
const validInitialLocale = [
phet.chipper.locale,
...( phet.chipper.localeData[ phet.chipper.locale ]?.fallbackLocales ?? [] ),
FALLBACK_LOCALE
].find( isLocaleValid );

// Just in case we had an invalid locale, remap phet.chipper.locale to the "corrected" value
phet.chipper.locale = validInitialLocale;

class LocaleProperty extends Property<Locale> {
export class LocaleProperty extends Property<Locale> {
public readonly availableRuntimeLocales: Locale[] = availableRuntimeLocales;

// Override to provide grace and support for the full definition of allowed locales (aligned with the query parameter
// schema). For example three letter values, and case insensitivity. See checkAndRemapLocale() for details. NOTE that
// this will assert if the locale doesn't match the right format.
protected override unguardedSet( value: Locale ): void {
if ( availableRuntimeLocales.includes( value ) ) {
super.unguardedSet( value );
}
else {
assert && assert( false, 'Unsupported locale: ' + value );

// Do not try to set if the value was invalid
}
// NOTE: updates phet.chipper.locale as a side-effect
super.unguardedSet( phet.chipper.checkAndRemapLocale( value, true ) );
}

// This improves the PhET-iO Studio interface, by giving available values, without triggering validation if you want
// to use the more general locale schema (three digit/case-insensitive/etc).
protected override toStateObject<StateType>(): ReadOnlyPropertyState<StateType> {
const parentObject = super.toStateObject<StateType>();

// Provide via validValues without forcing validation assertions if a different value is set.
parentObject.validValues = this.availableRuntimeLocales as StateType[];
return parentObject;
}

protected override applyState<StateType>( stateObject: ReadOnlyPropertyState<StateType> ): void {
stateObject.validValues = null; // TODO: this should be removed in https://github.com/phetsims/axon/issues/453
super.applyState( stateObject );
}
}

const localeProperty = new LocaleProperty( validInitialLocale, {
const localeProperty = new LocaleProperty( phet.chipper.locale, {
tandem: Tandem.GENERAL_MODEL.createTandem( 'localeProperty' ),
phetioFeatured: true,
phetioValueType: StringIO,
validValues: availableRuntimeLocales,
phetioDocumentation: 'Specifies language currently displayed in the simulation'
} );

Expand Down
7 changes: 3 additions & 4 deletions js/preferences/LocalePanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@
import joist from '../joist.js';
import Panel from '../../../sun/js/Panel.js';
import { GridBox } from '../../../scenery/js/imports.js';
import Property from '../../../axon/js/Property.js';
import LanguageSelectionNode from './LanguageSelectionNode.js';
import { Locale } from '../i18n/localeProperty.js';
import JoistStrings from '../JoistStrings.js';
import StringUtils from '../../../phetcommon/js/util/StringUtils.js';
import { LocaleProperty } from '../i18n/localeProperty.js';

class LocalePanel extends Panel {
public constructor( localeProperty: Property<Locale> ) {
public constructor( localeProperty: LocaleProperty ) {

const locales = localeProperty.validValues!;
const locales = localeProperty.availableRuntimeLocales;

// Sort these properly by their localized name (without using _.sortBy, since string comparison does not provide
// a good sorting experience). See https://github.com/phetsims/joist/issues/965
Expand Down
8 changes: 4 additions & 4 deletions js/preferences/PreferencesModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import PhetioObject, { PhetioObjectOptions } from '../../../tandem/js/PhetioObje
import optionize, { EmptySelfOptions } from '../../../phet-core/js/optionize.js';
import SpeechSynthesisAnnouncer from '../../../utterance-queue/js/SpeechSynthesisAnnouncer.js';
import Tandem from '../../../tandem/js/Tandem.js';
import localeProperty, { Locale } from '../i18n/localeProperty.js';
import localeProperty, { LocaleProperty } from '../i18n/localeProperty.js';
import merge from '../../../phet-core/js/merge.js';
import TReadOnlyProperty from '../../../axon/js/TReadOnlyProperty.js';
import IOType from '../../../tandem/js/types/IOType.js';
Expand Down Expand Up @@ -170,7 +170,7 @@ export type InputModel = BaseModelType & {
} & Required<InputPreferencesOptions>;

export type LocalizationModel = BaseModelType & {
localeProperty: Property<Locale>;
localeProperty: LocaleProperty;
} & Required<LocalizationPreferencesOptions>;

type FeatureModel = SimulationModel | AudioModel | VisualModel | InputModel | LocalizationModel;
Expand Down Expand Up @@ -228,7 +228,7 @@ export default class PreferencesModel extends PhetioObject {
}, providedOptions.inputOptions ),
localizationOptions: optionize<LocalizationPreferencesOptions, LocalizationPreferencesOptions, BaseModelType>()( {
tandemName: 'localizationModel',
supportsDynamicLocale: !!localeProperty.validValues && localeProperty.validValues.length > 1 && phet.chipper.queryParameters.supportsDynamicLocale,
supportsDynamicLocale: !!localeProperty.availableRuntimeLocales && localeProperty.availableRuntimeLocales.length > 1 && phet.chipper.queryParameters.supportsDynamicLocale,
customPreferences: [],
includeLocalePanel: true
}, providedOptions.localizationOptions )
Expand Down Expand Up @@ -256,7 +256,7 @@ export default class PreferencesModel extends PhetioObject {
// Running with english locale OR an environment where locale switching is supported and
// english is one of the available languages.
phet.chipper.locale.startsWith( 'en' ) ||
( phet.chipper.queryParameters.supportsDynamicLocale && _.some( localeProperty.validValues, value => value.startsWith( 'en' ) ) )
( phet.chipper.queryParameters.supportsDynamicLocale && _.some( localeProperty.availableRuntimeLocales, value => value.startsWith( 'en' ) ) )
);

// Audio can be disabled explicitly via query parameter
Expand Down
2 changes: 1 addition & 1 deletion js/preferences/VoicingPanelSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class VoicingPanelSection extends PreferencesPanelSection {

// Voicing feature only works when running in English. If running in a version where you can change locale,
// indicate through the title that the feature will only work in English.
const titleStringProperty = ( localeProperty.validValues && localeProperty.validValues.length > 1 ) ?
const titleStringProperty = ( localeProperty.availableRuntimeLocales && localeProperty.availableRuntimeLocales.length > 1 ) ?
voicingEnglishOnlyLabelStringProperty : voicingLabelStringProperty;

// the checkbox is the title for the section and totally enables/disables the feature
Expand Down

0 comments on commit 5f43ece

Please sign in to comment.