-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move utility files related to Fluent.js to chipper so they can be use…
…d in multiple sim prototypes, see phetsims/joist#992 and #1532
- Loading branch information
1 parent
fbed399
commit df2757d
Showing
4 changed files
with
192 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Copyright 2024, University of Colorado Boulder | ||
|
||
/** | ||
* Utility functions for working with Fluent strings. | ||
* | ||
* @author Jesse Greenberg (PhET Interactive Simulations) | ||
*/ | ||
|
||
import { isTReadOnlyProperty } from '../../axon/js/TReadOnlyProperty.js'; | ||
import IntentionalAny from '../../phet-core/js/types/IntentionalAny.js'; | ||
import localizedFluentBundleProperty from './localizedFluentBundleProperty.js'; | ||
import LocalizedMessageProperty from './LocalizedMessageProperty.js'; | ||
|
||
const FluentUtils = { | ||
|
||
/** | ||
* Converts a camelCase id to a message key. For example, 'choose-unit-for-current' becomes | ||
* 'chooseUnitForCurrentMessageProperty'. | ||
*/ | ||
fluentIdToMessageKey: ( id: string ): string => { | ||
return `${id.replace( /-([a-z])/g, ( match, letter ) => letter.toUpperCase() )}MessageProperty`; | ||
}, | ||
|
||
/** | ||
* Changes a set of arguments for the message into a set of values that can easily be used to | ||
* format the message. Does things like get Property values and converts enumeration values to strings. | ||
*/ | ||
handleFluentArgs: ( args: IntentionalAny ): IntentionalAny => { | ||
const keys = Object.keys( args ); | ||
|
||
const newArgs: Record<string, string> = {}; | ||
|
||
keys.forEach( key => { | ||
let value = args[ key ]; | ||
|
||
// If the value is a Property, get the value. | ||
if ( isTReadOnlyProperty( value ) ) { | ||
value = value.value; | ||
} | ||
|
||
// If the value is an EnumerationValue, automatically use the enum name. | ||
if ( value && value.name ) { | ||
value = value.name; | ||
} | ||
|
||
newArgs[ key ] = value; | ||
} ); | ||
|
||
return newArgs; | ||
}, | ||
|
||
/**h | ||
* Directly format a fluent message. Most of the time, you should use a PatternMessageProperty instead. | ||
* This should only be used when the string does not need to be changed when the locale changes. Real-time | ||
* alerts are a good exaple. | ||
*/ | ||
formatMessage: ( localizedMessageProperty: LocalizedMessageProperty, args: IntentionalAny ): string => { | ||
const newArgs = FluentUtils.handleFluentArgs( args ); | ||
return localizedFluentBundleProperty.value.format( localizedMessageProperty.value, newArgs ); | ||
} | ||
}; | ||
|
||
export default FluentUtils; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Copyright 2024, University of Colorado Boulder | ||
|
||
/** | ||
* Prototype: A Property whose value is a message from a Fluent bundle. A Fluent bundle is a collection of messages | ||
* for a single locale. When the locale changes, the bundle will change, and then the LocalizedMessageProperty will | ||
* compute a new value based on the language and arguments. | ||
* | ||
* See https://github.com/phetsims/joist/issues/992. | ||
* | ||
* This is for a proof of concept. By creating Properties, we get a good sense of what usages would be like in simulation | ||
* code. But it is a partial implementation. The full solution needs to consider PhET-iO control, and be more integrated | ||
* into PhET's string modules. | ||
* | ||
* @author Jesse Greenberg (PhET Interactive Simulations) | ||
*/ | ||
|
||
import { DerivedProperty1 } from '../../axon/js/DerivedProperty.js'; | ||
import chipper from './chipper.js'; | ||
import localizedFluentBundleProperty, { englishBundle } from './localizedFluentBundleProperty.js'; | ||
|
||
export default class LocalizedMessageProperty extends DerivedProperty1<string, null> { | ||
|
||
/** | ||
* @param id - the id of the message in the fluent bundle | ||
*/ | ||
public constructor( id: string ) { | ||
|
||
// Just to get Property interface working, but this needs to be bi-directional | ||
// and use LocalizedString/LocalizedStringProperty stack. | ||
super( [ localizedFluentBundleProperty ], () => { | ||
|
||
// If the bundle does not have the message, fall back to english. | ||
return localizedFluentBundleProperty.value.getMessage( id ) || englishBundle.getMessage( id ); | ||
} ); | ||
} | ||
} | ||
|
||
chipper.register( 'LocalizedMessageProperty', LocalizedMessageProperty ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright 2024, University of Colorado Boulder | ||
|
||
/** | ||
* A Property whose value is a message from a bundle with arguments. Each argument can be a Property, | ||
* and the message will be updated either the message or the argument changes. | ||
* | ||
* A similar idea as PatternStringProperty, but for Fluent messages. | ||
* | ||
* @author Jesse Greenberg (PhET Interactive Simulations) | ||
*/ | ||
|
||
import { DerivedProperty1 } from '../../axon/js/DerivedProperty.js'; | ||
import TReadOnlyProperty, { isTReadOnlyProperty } from '../../axon/js/TReadOnlyProperty.js'; | ||
import IntentionalAny from '../../phet-core/js/types/IntentionalAny.js'; | ||
import chipper from './chipper.js'; | ||
import FluentUtils from './FluentUtils.js'; | ||
import localizedFluentBundleProperty from './localizedFluentBundleProperty.js'; | ||
import LocalizedMessageProperty from './LocalizedMessageProperty.js'; | ||
|
||
export default class PatternMessageProperty extends DerivedProperty1<string, string> { | ||
public constructor( messageProperty: LocalizedMessageProperty, values: IntentionalAny ) { | ||
const dependencies: TReadOnlyProperty<IntentionalAny>[] = [ messageProperty ]; | ||
const keys = Object.keys( values ); | ||
keys.forEach( key => { | ||
if ( isTReadOnlyProperty( values[ key ] ) ) { | ||
dependencies.push( values[ key ] ); | ||
} | ||
} ); | ||
|
||
// @ts-expect-error This is a prototype so I am not going to worry about this complicated TS for now. | ||
super( dependencies, ( message, ...unusedArgs ) => { | ||
|
||
const args = FluentUtils.handleFluentArgs( values ); | ||
|
||
// Format the message with the arguments to resolve a string. | ||
return localizedFluentBundleProperty.value.format( message, args ); | ||
} ); | ||
} | ||
} | ||
|
||
chipper.register( 'PatternMessageProperty', PatternMessageProperty ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Copyright 2024, University of Colorado Boulder | ||
|
||
/** | ||
* Prototype for a Property that provides a FluentBundle for the current locale. | ||
* Used by LocalizedMessageProperty to get the correct Fluent message for the current locale. | ||
* | ||
* Fluent has the following concepts: | ||
* - Bundle: A collection of messages for a single locale. | ||
* - Message: An data structure in a FluentBundle. The message can be formatted with arguments into a final string. | ||
* If there are no arguments, the message is a string. | ||
* | ||
* @author Jesse Greenberg (PhET Interactive Simulations) | ||
*/ | ||
|
||
import DerivedProperty from '../../axon/js/DerivedProperty.js'; | ||
import localeProperty from '../../joist/js/i18n/localeProperty.js'; | ||
|
||
const bundleMap = new Map(); | ||
|
||
// @ts-expect-error - Why isn't Fluent available here? It was availabel in ohms-law. I expected | ||
// it to be available because the types are included in perennial's package.json. | ||
const FluentRef = Fluent; | ||
|
||
// Create the Fluent bundles for each locale, and save them to the map for use. | ||
localeProperty.availableRuntimeLocales.forEach( locale => { | ||
|
||
// If strings are available for the locale, create a bundle. Graceful fallbacks | ||
// happen in the Properties below. | ||
if ( phet.chipper.fluentStrings[ locale ] ) { | ||
const bundle = new FluentRef.FluentBundle( locale ); | ||
const resource = FluentRef.FluentResource.fromString( phet.chipper.fluentStrings[ locale ] ); | ||
bundle.addResource( resource ); | ||
|
||
bundleMap.set( locale, bundle ); | ||
} | ||
} ); | ||
|
||
// Get the english fallback bundle. | ||
const englishBundle = bundleMap.get( 'en' ); | ||
if ( !englishBundle ) { | ||
throw new Error( 'English bundle is required' ); | ||
} | ||
|
||
// The bundle for the selected locale. Fall back to english if the bundle isn't available. | ||
const localizedBundleProperty = new DerivedProperty( [ localeProperty ], locale => { | ||
return bundleMap.has( locale ) ? bundleMap.get( locale ) : englishBundle; | ||
} ); | ||
|
||
export default localizedBundleProperty; | ||
export { englishBundle }; |