diff --git a/js/LocalizedString.ts b/js/LocalizedString.ts index bfc2b3784..b7f6d1dad 100644 --- a/js/LocalizedString.ts +++ b/js/LocalizedString.ts @@ -190,12 +190,18 @@ class LocalizedString { return localeOrder[ index + 1 ]; } else { - // doesn't exist in those - if ( locale.includes( '_' ) ) { - return locale.slice( 0, 2 ) as Locale; // zh_CN => zh + if ( phet.chipper.localeData[ locale ] ) { + // Pick either the first fallback locale from our localeData, or English + return ( phet.chipper.localeData[ locale ].fallbackLocales || [ 'en' ] )[ 0 ]; } else { - return 'en'; + // doesn't exist in those + if ( locale.includes( '_' ) ) { + return locale.slice( 0, 2 ) as Locale; // zh_CN => zh + } + else { + return 'en'; + } } } } diff --git a/js/data/newUpdateLocaleInfo.js b/js/data/newUpdateLocaleInfo.js new file mode 100644 index 000000000..de122c06f --- /dev/null +++ b/js/data/newUpdateLocaleInfo.js @@ -0,0 +1,153 @@ +// Copyright 2024, University of Colorado Boulder + +/** + * TODO: Move over to updateLocaleInfo.js once we are ready to propagate the locale changes https://github.com/phetsims/joist/issues/963 + * + * WARNING: This will commit/push the changes. Those changes likely be propagated immediately to the website and rosetta. + * + * NOTE: Run with CWD of chipper/js/data + * + * @author Matt Pennington (PhET Interactive Simulations) + */ + +const child_process = require( 'child_process' ); +const fs = require( 'fs' ); + +/** + * Converts locale data from babel/localeData.json into legacy formats used by rosetta and the website. + * + * Overall description of the localeData system: + * + * - babel/localeData.json - Ground truth, includes the "new" format with locale3 and englishName instead of name + * - chipper/js/data/localeInfo.js - CommonJS legacy module + * - chipper/js/data/localeInfoModule.js - ES6 legacy module + * - chipper/js/data/localeInfo.json - JSON legacy + * + * IMPORTANT - MUST READ!!! + * You may modify babel/localeData.json file with new locale information. After modifying the file you must take the following steps: + * 1. Run ./updateLocaleInfo.js, so that the automatically generated files are also update + * 2. Notify the responsible developers for rosetta, weddell, yotta, and the website that localeInfo was updated. + * 3. TODO figure out next steps, see https://github.com/phetsims/joist/issues/963 + * + * Locale data was originally based on Java's Locale object, but has been modified. Essentially each locale has the + * following data: + * + * - locale: Either in the format `xx` or `xx_XX` (ISO-639-1 with 2-letter country code optional). Sometimes these + * do not match with ISO-639-1, we have had to add some for our needs. + * - language codes are ISO 639-1, see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + * - country codes are ISO 3166-1 alpha2, see http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + * + * NOTE: We are using an older version of ISO 639-1 because java.util.Locale maps some of the newer language codes to + * older codes. See Locale.convertOldISOCodes. + * The affected country codes are: + * he -> iw (Hebrew) + * yi -> ji (Yiddish) + * id -> in (Indonesian) + * - locale3: Format of `xxx`. The ISO-639-2 code for the language (3-letter code), if available. Some locales do not + * have this information (most do). + * - direction: either `ltr` or `rtl` for left-to-right or right-to-left + * - englishName: The name of the locale in English + * - localizedName: The name of the locale in the locale itself + * + * ALSO NOTE: We had a request to support Lakota, which is not included in ISO 639-1, and is only defined as a three- + * letter code in ISO 639-3. The locale combination 'lk' was not taken in ISO 639-1, so we added it. Strictly + * speaking, this is a deviation from the spec. + * + * @author Jonathan Olson + */ + +// Load our ground source of truth +const localeData = JSON.parse( fs.readFileSync( '../../../babel/localeData.json', 'utf8' ) ); + +// Construct the concise JS that defines the legacy locale-info format +let localeInfoSnippet = '{'; +// eslint-disable-next-line bad-text +const badText = 'Slave'; // There is an englishName that contains this word, see https://en.wikipedia.org/?title=Slave_language_(Athapascan)&redirect=no +// Add properties for all locales +for ( const locale of Object.keys( localeData ) ) { + localeInfoSnippet += ` + ${locale}: { + ${localeData[ locale ].englishName.includes( badText ) ? '// eslint-disable-next-line bad-text\n ' : ''}name: '${localeData[ locale ].englishName.replace( /'/g, '\\\'' )}', + localizedName: '${localeData[ locale ].localizedName.replace( /'/g, '\\\'' )}', + direction: '${localeData[ locale ].direction}' + },`; +} +// Remove the trailing comma +localeInfoSnippet = localeInfoSnippet.slice( 0, -1 ); +// Close the object +localeInfoSnippet += '\n}'; + +const localeInfo = {}; +for ( const locale of Object.keys( localeData ) ) { + localeInfo[ locale ] = { + name: localeData[ locale ].englishName, + localizedName: localeData[ locale ].localizedName, + direction: localeData[ locale ].direction + }; +} + +const newLocaleInfo = { + _comment: 'This file is automatically generated by js/data/updateLocaleInfo.js. Do not modify it directly.', + ...localeInfo +}; + +fs.writeFileSync( '../../data/localeInfo.json', JSON.stringify( newLocaleInfo, null, 2 ) ); + +const commonDocumentation = `// Copyright 2015-${new Date().getFullYear()}, University of Colorado Boulder + +/** + * This file is automatically generated by js/data/updateLocaleInfo.js. Do not modify it directly. + * + * @author automatically generated by updateLocaleInfo.js + */ + +/* eslint-env browser, node */ + + +`; + +const newCommonJSSouceCode = `${commonDocumentation}module.exports = ${localeInfoSnippet};`; +fs.writeFileSync( './localeInfo.js', newCommonJSSouceCode ); + +const newModuleSourceCode = `${commonDocumentation}export default ${localeInfoSnippet};`; +fs.writeFileSync( './localeInfoModule.js', newModuleSourceCode ); + +console.log( 'locale info files updated' ); + +throw new Error( 'NO COMMIT YET, safeguard so we do not commit changes to main yet' ); // TODO: remove for https://github.com/phetsims/joist/issues/963 + +// eslint-disable-next-line no-unreachable +let needsCommit = false; +try { + + // 0 exit code if there are no working copy changes from HEAD. + child_process.execSync( 'git diff-index --quiet HEAD --' ); + console.log( 'No locale info changes, no commit needed.' ); +} +catch( e ) { + needsCommit = true; +} + +if ( needsCommit ) { + try { + + console.log( 'pulling' ); + + // Some devs have rebase set by default, and you cannot rebase-pull with working copy changes. + child_process.execSync( 'git pull --no-rebase' ); + + child_process.execSync( 'git add ../../data/localeInfo.json' ); + child_process.execSync( 'git add ./localeInfo.js' ); + child_process.execSync( 'git add ./localeInfoModule.js' ); + + if ( needsCommit ) { + console.log( 'committing' ); + child_process.execSync( 'git commit --no-verify ../../data/localeInfo.json ./localeInfo.js ./localeInfoModule.js -m "Automatically updated generated localeInfo files"' ); + console.log( 'pushing' ); + child_process.execSync( 'git push' ); + } + } + catch( e ) { + console.error( 'Unable to update files in git.', e ); + } +} \ No newline at end of file diff --git a/js/getStringModule.ts b/js/getStringModule.ts index 24b870e79..8d608faa1 100644 --- a/js/getStringModule.ts +++ b/js/getStringModule.ts @@ -21,7 +21,6 @@ import ObjectLiteralIO from '../../tandem/js/types/ObjectLiteralIO.js'; import LocalizedString, { LocalizedStringStateDelta, StringsStateStateObject } from './LocalizedString.js'; import TReadOnlyProperty from '../../axon/js/TReadOnlyProperty.js'; import { Locale } from '../../joist/js/i18n/localeProperty.js'; -import localeInfoModule from '../../chipper/js/data/localeInfoModule.js'; import { PhetioID } from '../../tandem/js/TandemConstants.js'; // constants @@ -106,7 +105,7 @@ const getStringModule = ( requirejsNamespace: string ): object => { // Our locale information is from phet.chipper.locale assert && assert( typeof phet.chipper.locale === 'string', 'phet.chipper.locale should have been loaded by now' ); - assert && assert( Object.keys( localeInfoModule ).includes( phet.chipper.locale ), 'phet.chipper.locale should have been loaded by now' ); + assert && assert( Object.keys( phet.chipper.localeData ).includes( phet.chipper.locale ), 'phet.chipper.locale should have been loaded by now' ); assert && assert( phet.chipper.strings, 'phet.chipper.strings should have been loaded by now' ); // Construct locales in increasing specificity, e.g. [ 'en', 'zh', 'zh_CN' ], so we get fallbacks in order diff --git a/js/grunt/buildRunnable.js b/js/grunt/buildRunnable.js index 127095c21..3b5d135ba 100644 --- a/js/grunt/buildRunnable.js +++ b/js/grunt/buildRunnable.js @@ -235,6 +235,7 @@ module.exports = async function( repo, minifyOptions, allHTML, brand, localesOpt const commonInitializationOptions = { brand: brand, repo: repo, + allLocales: allLocales, stringMap: stringMap, stringMetadata: stringMetadata, dependencies: dependencies, diff --git a/js/grunt/getInitializationScript.js b/js/grunt/getInitializationScript.js index fd2614ba8..80abf84d2 100644 --- a/js/grunt/getInitializationScript.js +++ b/js/grunt/getInitializationScript.js @@ -11,9 +11,11 @@ // modules +const _ = require( 'lodash' ); const assert = require( 'assert' ); const ChipperConstants = require( '../common/ChipperConstants' ); const ChipperStringUtils = require( '../common/ChipperStringUtils' ); +const fs = require( 'fs' ); const grunt = require( 'grunt' ); const transpile = require( './transpile' ); const stringEncoding = require( '../common/stringEncoding' ); @@ -29,6 +31,7 @@ module.exports = function( config ) { const { brand, // {string}, e.g. 'phet', 'phet-io' repo, // {string} + allLocales, // {string[]} stringMap, // {Object}, map[ locale ][ stringKey ] => {string} stringMetadata, // {Object}, map[ stringKey ] => {Object} version, // {string} @@ -51,17 +54,45 @@ module.exports = function( config ) { assert( typeof includeAllLocales === 'boolean', 'Requires includeAllLocales' ); assert( typeof isDebugBuild === 'boolean', 'Requires isDebugBuild' ); + // Load localeData + const fullLocaleData = JSON.parse( fs.readFileSync( '../babel/localeData.json', 'utf8' ) ); + + // Include a subset of locales' translated strings let phetStrings = stringMap; if ( !includeAllLocales ) { phetStrings = {}; - phetStrings[ ChipperConstants.FALLBACK_LOCALE ] = stringMap[ ChipperConstants.FALLBACK_LOCALE ]; - if ( locale !== ChipperConstants.FALLBACK_LOCALE ) { + + // Go through all of the potential fallback locales, and include the strings for each of them + const requiredLocales = [ + // duplicates OK + locale, + ...( fullLocaleData[ locale ].fallbackLocales || [] ), + ChipperConstants.FALLBACK_LOCALE + ]; + + for ( const locale of requiredLocales ) { phetStrings[ locale ] = stringMap[ locale ]; } - const splitLocale = locale.slice( 0, 2 ); - if ( locale.length > 2 && splitLocale !== ChipperConstants.FALLBACK_LOCALE ) { - phetStrings[ splitLocale ] = stringMap[ splitLocale ]; - } + } + + // Include a (larger) subset of locales' localeData. + const includedDataLocales = _.sortBy( _.uniq( [ + // Always include the fallback (en) + ChipperConstants.FALLBACK_LOCALE, + + // Include directly-used locales + ...allLocales, + + // Include locales that will fall back to directly-used locales + Object.keys( fullLocaleData ).filter( locale => { + return fullLocaleData[ locale ].fallbackLocales && fullLocaleData[ locale ].fallbackLocales.some( fallbackLocale => { + return allLocales.includes( fallbackLocale ); + } ); + } ) + ] ) ); + const localeData = {}; + for ( const locale of includedDataLocales ) { + localeData[ locale ] = fullLocaleData[ locale ]; } return ChipperStringUtils.replacePlaceholders( grunt.file.read( '../chipper/templates/chipper-initialization.js' ), { @@ -70,6 +101,7 @@ module.exports = function( config ) { PHET_BUILD_TIMESTAMP: timestamp, PHET_BRAND: brand, PHET_LOCALE: locale, + PHET_LOCALE_DATA: JSON.stringify( localeData ), PHET_DEPENDENCIES: JSON.stringify( dependencies, null, 2 ), // If it's a debug build, don't encode the strings, so that they are easier to inspect PHET_STRINGS: ( isDebugBuild || !encodeStringMap ) ? JSON.stringify( phetStrings, null, isDebugBuild ? 2 : '' ) : stringEncoding.encodeStringMapToJS( phetStrings ), diff --git a/js/grunt/getStringMap.js b/js/grunt/getStringMap.js index c360a0ca2..9098c9fa5 100644 --- a/js/grunt/getStringMap.js +++ b/js/grunt/getStringMap.js @@ -15,9 +15,27 @@ const pascalCase = require( '../common/pascalCase' ); const ChipperStringUtils = require( '../common/ChipperStringUtils' ); const fs = require( 'fs' ); const grunt = require( 'grunt' ); -const localeInfo = require( '../data/localeInfo' ); // Locale information const path = require( 'path' ); +const localeData = JSON.parse( fs.readFileSync( '../babel/localeData.json', 'utf8' ) ); + +/** + * For a given locale, return an array of specific locales that we'll use as fallbacks, e.g. + * 'ar_AE' => [ 'ar_AE', 'ar', 'ar_MA', 'en' ] (note, changed from zh_CN example, which does NOT use 'zh' as a fallback anymore) + * 'es' => [ 'es', 'en' ] + * 'en' => [ 'en' ] + * + * @param {string} locale + * @returns {Array.} + */ +const localeFallbacks = locale => { + return [ + ...( locale !== ChipperConstants.FALLBACK_LOCALE ? [ locale ] : [] ), + ...( localeData[ locale ].fallbackLocales || [] ), + ChipperConstants.FALLBACK_LOCALE // e.g. 'en' + ]; +}; + /** * Load all the required string files into memory, so we don't load them multiple times (for each usage). * @@ -55,19 +73,14 @@ const getStringFilesContents = ( reposWithUsedStrings, locales ) => { stringFilesContents[ repo ][ locale ] = fileContents; }; - locales.forEach( locale => { - assert( localeInfo[ locale ], `unsupported locale: ${locale}` ); - const isRTL = localeInfo[ locale ].direction === 'rtl'; + // Include fallback locales (they may have duplicates) + const includedLocales = _.sortBy( _.uniq( locales.flatMap( locale => { + assert( localeData[ locale ], `unsupported locale: ${locale}` ); - // Handle fallback locales - addLocale( locale, isRTL ); - if ( locale.length > 2 ) { - const middleLocale = locale.slice( 0, 2 ); - if ( !locales.includes( middleLocale ) ) { - addLocale( middleLocale, isRTL ); - } - } - } ); + return localeFallbacks( locale ); + } ) ) ); + + includedLocales.forEach( locale => addLocale( locale, localeData[ locale ].direction === 'rtl' ) ); } ); return stringFilesContents; @@ -85,23 +98,6 @@ module.exports = function( mainRepo, locales, phetLibs, usedModules ) { assert( locales.indexOf( ChipperConstants.FALLBACK_LOCALE ) !== -1, 'fallback locale is required' ); - /** - * For a given locale, return an array of specific locales that we'll use as fallbacks, e.g. - * 'zh_CN' => [ 'zh_CN', 'zh', 'en' ] - * 'es' => [ 'es', 'en' ] - * 'en' => [ 'en' ] - * - * @param {string} locale - * @returns {Array.} - */ - const localeFallbacks = locale => { - return [ - ...( locale !== ChipperConstants.FALLBACK_LOCALE ? [ locale ] : [] ), // e.g. 'zh_CN' - ...( ( locale.length > 2 && locale.slice( 0, 2 ) !== ChipperConstants.FALLBACK_LOCALE ) ? [ locale.slice( 0, 2 ) ] : [] ), // e.g. 'zh' - ChipperConstants.FALLBACK_LOCALE // e.g. 'en' - ]; - }; - // Load the file contents of every single JS module that used any strings const usedFileContents = usedModules.map( usedModule => fs.readFileSync( `../${usedModule}`, 'utf-8' ) ); diff --git a/js/initialize-globals.js b/js/initialize-globals.js index eab69af51..0bd67c8b9 100644 --- a/js/initialize-globals.js +++ b/js/initialize-globals.js @@ -964,9 +964,50 @@ stringTest; }; + // We will need to check for locale validity (once we have localeData loaded, if running unbuilt), and potentially + // either fall back to `en`, or remap from 3-character locales to our locale keys. + phet.chipper.checkAndRemapLocale = () => { + // We need both to proceed. Provided as a global, so we can call it from load-unbuilt-strings + // (IF initialize-globals loads first) + if ( !phet.chipper.localeData || !phet.chipper.locale ) { + return; + } + + let locale = phet.chipper.locale; + + if ( locale && locale.length === 3 ) { + for ( const candidateLocale of Object.keys( phet.chipper.localeData ) ) { + if ( phet.chipper.localeData[ candidateLocale ].locale3 === locale ) { + locale = candidateLocale; + break; + } + } + } + + if ( !phet.chipper.localeData[ locale ] ) { + const badLocale = phet.chipper.queryParameters.locale; + + const isPair = /^[a-z]{2}$/.test( badLocale ); + const isTriple = /^[a-z]{3}$/.test( badLocale ); + const isPair_PAIR = /^[a-z]{2}_[A-Z]{2}$/.test( badLocale ); + + if ( !isPair && !isTriple && !isPair_PAIR ) { + QueryStringMachine.addWarning( 'locale', phet.chipper.queryParameters.locale, `Invalid locale format received: ${badLocale}. ?locale query parameter accepts the following formats: "xx" for ISO-639-1, "xx_XX" for ISO-639-1 and a 2-letter country code, "xxx" for ISO-639-2` ); + } + + locale = 'en'; + } + + phet.chipper.locale = locale; + }; + // If locale was provided as a query parameter, then change the locale used by Google Analytics. if ( QueryStringMachine.containsKey( 'locale' ) ) { - window.phet.chipper.locale = phet.chipper.queryParameters.locale; + phet.chipper.locale = phet.chipper.queryParameters.locale; + + // NOTE: If we are loading in unbuilt mode, this may execute BEFORE we have loaded localeData. We have a similar + // remapping in load-unbuilt-strings when this happens. + phet.chipper.checkAndRemapLocale(); } else if ( !window.phet.chipper.locale ) { // Fill in a default @@ -990,18 +1031,23 @@ if ( stringOverrides[ key ] ) { return stringOverrides[ key ]; } - let stringMap = phet.chipper.strings[ phet.chipper.locale ]; - // Don't fail out on unsupported locales, see https://github.com/phetsims/chipper/issues/694 - if ( !stringMap ) { + // Get a list of locales in the order they should be searched + const fallbackLocales = [ + phet.chipper.locale, + ...( phet.chipper.localeData[ phet.chipper.locale ]?.fallbackLocales || [] ), + ( phet.chipper.locale !== 'en' ? [ 'en' ] : [] ) + ]; - // See if there's a translation for just the language code - stringMap = phet.chipper.strings[ phet.chipper.locale.slice( 0, 2 ) ]; + let stringMap = null; - if ( !stringMap ) { - stringMap = phet.chipper.strings.en; + for ( const locale of fallbackLocales ) { + stringMap = phet.chipper.strings[ locale ]; + if ( stringMap ) { + break; } } + return phet.chipper.mapString( stringMap[ key ] ); }; }() ); diff --git a/js/load-unbuilt-strings.js b/js/load-unbuilt-strings.js index 1874440db..b87e6b418 100644 --- a/js/load-unbuilt-strings.js +++ b/js/load-unbuilt-strings.js @@ -30,9 +30,13 @@ window.phet.chipper.strings = {}; window.phet.chipper.stringMetadata = {}; - // Prefixes, ideally a better way of accessing localeInfo on startup would exist. We have localeInfo, however it's + // Prefixes, ideally a better way of accessing localeData on startup would exist. We have localeData, however it's // in the form of a module, and we can't use that at this point. - const rtlLocales = [ 'ae', 'ar', 'fa', 'iw', 'ur' ]; + // NOTE: For built forms, we will always have this data. For unbuilt forms, we may conditionally have this data, + // depending on the load order + const rtlLocales = phet.chipper.localeData ? Object.keys( phet.chipper.localeData ).filter( locale => { + return phet.chipper.localeData[ locale ].direction === 'rtl'; + } ) : [ 'ac', 'ar', 'ar_AE', 'ar_BH', 'ar_DJ', 'ar_DZ', 'ar_EG', 'ar_EH', 'ar_ER', 'ar_IQ', 'ar_JO', 'ar_KM', 'ar_KW', 'ar_LB', 'ar_LY', 'ar_MA', 'ar_MR', 'ar_OM', 'ar_QA', 'ar_SA', 'ar_SD', 'ar_SO', 'ar_SY', 'ar_TD', 'ar_TN', 'ar_YE', 'bc', 'bx', 'de_AT', 'de_CH', 'de_LI', 'de_LU', 'di', 'es_AR', 'es_BO', 'es_CL', 'es_DO', 'es_EC', 'es_GQ', 'es_GT', 'es_HN', 'es_NI', 'es_PA', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'fa', 'fa_DA', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_EH', 'fr_GA', 'fr_GN', 'fr_GQ', 'fr_KM', 'fr_LU', 'fr_MC', 'fr_MG', 'fr_ML', 'fr_NE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_TD', 'fr_TG', 'it_CH', 'iw', 'jr', 'kq', 'lh', 'ly', 'ms_MY', 'nl_BE', 'nq', 'sr_BA', 'sv_FI', 'sy', 'ur', 'zh_MO', 'zh_SG' ]; const localeQueryParam = new window.URLSearchParams( window.location.search ).get( 'locale' ); const localesQueryParam = new window.URLSearchParams( window.location.search ).get( 'locales' ); @@ -195,6 +199,9 @@ ...( localesQueryParam ? localesQueryParam.split( ',' ) : [] ) ].forEach( locale => { if ( locale ) { + // NOTE: this is UNABLE to follow the normal fallback mechanisms from https://github.com/phetsims/joist/issues/963, + // as we don't have that data loaded yet. + // e.g. 'zh_CN' if ( !locales.includes( locale ) ) { locales.push( locale ); @@ -229,6 +236,15 @@ // phet.chipper.usedStringsEN = json; // } ); + // Load locale data + requestJSONFile( '../babel/localeData.json', json => { + phet.chipper.localeData = json; + + // Because load-unbuilt-strings' "loading" of the locale data might not have happened BEFORE initialize-globals + // runs (and sets phet.chipper.locale), we'll attempt to handle the case where it hasn't been set yet. + phet.chipper.checkAndRemapLocale && phet.chipper.checkAndRemapLocale(); + } ); + if ( localesQueryParam === '*' ) { // Load the conglomerate files diff --git a/js/scripts/generateDevelopmentStrings.js b/js/scripts/generateDevelopmentStrings.js index 715089182..1fb3091b6 100644 --- a/js/scripts/generateDevelopmentStrings.js +++ b/js/scripts/generateDevelopmentStrings.js @@ -52,6 +52,8 @@ module.exports = repo => { stringFiles.push( englishStringPath ); } + const localeData = JSON.parse( fs.readFileSync( '../babel/localeData.json', 'utf8' ) ); + // Do not generate a file if no translations were found. if ( stringFiles.length > 0 ) { @@ -63,6 +65,11 @@ module.exports = repo => { const localeMatches = join.substring( join.lastIndexOf( '/' ) ).match( localeRegex ); const locale = localeMatches[ 0 ]; + if ( !localeData[ locale ] ) { + console.log( '[WARNING] Locale not found in localeData.json: ' + locale ); + continue; + } + // Get the contents of the string file. const stringFileContents = fs.readFileSync( stringFile, 'utf8' ); diff --git a/templates/chipper-initialization.js b/templates/chipper-initialization.js index 6b8e81175..20f5d12f3 100644 --- a/templates/chipper-initialization.js +++ b/templates/chipper-initialization.js @@ -3,6 +3,7 @@ window.phet.chipper.version = '{{PHET_VERSION}}'; window.phet.chipper.buildTimestamp = '{{PHET_BUILD_TIMESTAMP}}'; window.phet.chipper.brand = '{{PHET_BRAND}}'; window.phet.chipper.locale = '{{PHET_LOCALE}}'; +window.phet.chipper.localeData = {{PHET_LOCALE_DATA}}; window.phet.chipper.dependencies = {{PHET_DEPENDENCIES}}; {{PHET_BEFORE_STRINGS}}window.phet.chipper.strings = {{PHET_STRINGS}};{{PHET_AFTER_STRINGS}} window.phet.chipper.stringMetadata = {{PHET_STRING_METADATA}};