2017-06-02 11:36:56 +03:00
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/ * C o p y r i g h t 2 0 1 7 M o z i l l a F o u n d a t i o n a n d o t h e r s
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2018-01-27 01:01:34 +03:00
2018-10-20 19:35:50 +03:00
/* fluent-dom@fa25466f (October 12, 2018) */
2017-06-02 11:36:56 +03:00
/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
/* global console */
2019-01-17 21:18:31 +03:00
const { L10nRegistry } = ChromeUtils . import ( "resource://gre/modules/L10nRegistry.jsm" ) ;
const { Services } = ChromeUtils . import ( "resource://gre/modules/Services.jsm" ) ;
const { AppConstants } = ChromeUtils . import ( "resource://gre/modules/AppConstants.jsm" ) ;
2017-06-02 11:36:56 +03:00
2018-10-20 19:35:50 +03:00
/ *
* Base CachedIterable class .
* /
2018-08-07 03:08:29 +03:00
class CachedIterable extends Array {
/ * *
* Create a ` CachedIterable ` instance from an iterable or , if another
* instance of ` CachedIterable ` is passed , return it without any
* modifications .
*
* @ param { Iterable } iterable
* @ returns { CachedIterable }
* /
static from ( iterable ) {
if ( iterable instanceof this ) {
return iterable ;
}
return new this ( iterable ) ;
}
}
2018-10-20 19:35:50 +03:00
/ *
* CachedAsyncIterable caches the elements yielded by an async iterable .
*
* It can be used to iterate over an iterable many times without depleting the
* iterable .
* /
2018-08-07 03:08:29 +03:00
class CachedAsyncIterable extends CachedIterable {
2018-01-27 01:01:34 +03:00
/ * *
2018-04-24 20:31:59 +03:00
* Create an ` CachedAsyncIterable ` instance .
2018-01-27 01:01:34 +03:00
*
* @ param { Iterable } iterable
2018-04-24 20:31:59 +03:00
* @ returns { CachedAsyncIterable }
2018-01-27 01:01:34 +03:00
* /
2017-06-02 11:36:56 +03:00
constructor ( iterable ) {
2018-08-07 03:08:29 +03:00
super ( ) ;
2018-01-27 01:01:34 +03:00
if ( Symbol . asyncIterator in Object ( iterable ) ) {
this . iterator = iterable [ Symbol . asyncIterator ] ( ) ;
} else if ( Symbol . iterator in Object ( iterable ) ) {
this . iterator = iterable [ Symbol . iterator ] ( ) ;
} else {
2018-02-21 03:02:54 +03:00
throw new TypeError ( "Argument must implement the iteration protocol." ) ;
2017-06-02 11:36:56 +03:00
}
2018-08-07 03:08:29 +03:00
}
2017-06-02 11:36:56 +03:00
2018-08-07 03:08:29 +03:00
/ * *
* Asynchronous iterator caching the yielded elements .
*
* Elements yielded by the original iterable will be cached and available
* synchronously . Returns an async generator object implementing the
* iterator protocol over the elements of the original ( async or sync )
* iterable .
* /
2017-09-09 09:19:49 +03:00
[ Symbol . asyncIterator ] ( ) {
2018-08-07 03:08:29 +03:00
const cached = this ;
2017-06-02 11:36:56 +03:00
let cur = 0 ;
return {
2017-09-09 09:19:49 +03:00
async next ( ) {
2018-08-07 03:08:29 +03:00
if ( cached . length <= cur ) {
2018-10-22 18:05:35 +03:00
cached . push ( cached . iterator . next ( ) ) ;
2017-06-02 11:36:56 +03:00
}
2018-08-07 03:08:29 +03:00
return cached [ cur ++ ] ;
2018-10-19 15:55:39 +03:00
} ,
2017-06-02 11:36:56 +03:00
} ;
}
2017-09-10 05:23:03 +03:00
/ * *
* This method allows user to consume the next element from the iterator
* into the cache .
2018-04-24 20:31:59 +03:00
*
* @ param { number } count - number of elements to consume
2017-09-10 05:23:03 +03:00
* /
2018-04-24 20:31:59 +03:00
async touchNext ( count = 1 ) {
let idx = 0 ;
while ( idx ++ < count ) {
2018-08-07 03:08:29 +03:00
const last = this [ this . length - 1 ] ;
2018-10-22 18:05:35 +03:00
if ( last && ( await last ) . done ) {
2018-08-07 03:08:29 +03:00
break ;
2018-04-24 20:31:59 +03:00
}
2018-10-22 18:05:35 +03:00
this . push ( this . iterator . next ( ) ) ;
2017-09-10 05:23:03 +03:00
}
2018-08-07 03:08:29 +03:00
// Return the last cached {value, done} object to allow the calling
// code to decide if it needs to call touchNext again.
return this [ this . length - 1 ] ;
2017-09-10 05:23:03 +03:00
}
2017-06-02 11:36:56 +03:00
}
2019-01-11 03:25:49 +03:00
/ *
* CachedSyncIterable caches the elements yielded by an iterable .
*
* It can be used to iterate over an iterable many times without depleting the
* iterable .
* /
class CachedSyncIterable extends CachedIterable {
/ * *
* Create an ` CachedSyncIterable ` instance .
*
* @ param { Iterable } iterable
* @ returns { CachedSyncIterable }
* /
constructor ( iterable ) {
super ( ) ;
if ( Symbol . iterator in Object ( iterable ) ) {
this . iterator = iterable [ Symbol . iterator ] ( ) ;
} else {
throw new TypeError ( "Argument must implement the iteration protocol." ) ;
}
}
[ Symbol . iterator ] ( ) {
const cached = this ;
let cur = 0 ;
return {
next ( ) {
if ( cached . length <= cur ) {
cached . push ( cached . iterator . next ( ) ) ;
}
return cached [ cur ++ ] ;
} ,
} ;
}
/ * *
* This method allows user to consume the next element from the iterator
* into the cache .
*
* @ param { number } count - number of elements to consume
* /
touchNext ( count = 1 ) {
let idx = 0 ;
while ( idx ++ < count ) {
const last = this [ this . length - 1 ] ;
if ( last && last . done ) {
break ;
}
this . push ( this . iterator . next ( ) ) ;
}
// Return the last cached {value, done} object to allow the calling
// code to decide if it needs to call touchNext again.
return this [ this . length - 1 ] ;
}
}
2017-06-02 11:36:56 +03:00
/ * *
* The default localization strategy for Gecko . It comabines locales
* available in L10nRegistry , with locales requested by the user to
2018-10-20 19:35:50 +03:00
* generate the iterator over FluentBundles .
2017-06-02 11:36:56 +03:00
*
* In the future , we may want to allow certain modules to override this
* with a different negotitation strategy to allow for the module to
* be localized into a different language - for example DevTools .
* /
2018-10-20 19:35:50 +03:00
function defaultGenerateBundles ( resourceIds ) {
2018-09-21 18:30:37 +03:00
const appLocales = Services . locale . appLocalesAsBCP47 ;
2018-10-20 19:35:50 +03:00
return L10nRegistry . generateBundles ( appLocales , resourceIds ) ;
2017-06-02 11:36:56 +03:00
}
2019-01-11 03:25:49 +03:00
function defaultGenerateBundlesSync ( resourceIds ) {
const appLocales = Services . locale . appLocalesAsBCP47 ;
return L10nRegistry . generateBundlesSync ( appLocales , resourceIds ) ;
}
2019-03-26 22:34:27 +03:00
function maybeReportErrorToGecko ( error ) {
if ( AppConstants . NIGHTLY _BUILD || Cu . isInAutomation ) {
if ( Cu . isInAutomation ) {
// We throw a string, rather than Error
// to allow the C++ Promise handler
// to clone it
throw error ;
}
console . warn ( error ) ;
}
}
2017-06-02 11:36:56 +03:00
/ * *
* The ` Localization ` class is a central high - level API for vanilla
* JavaScript use of Fluent .
2018-10-20 19:35:50 +03:00
* It combines language negotiation , FluentBundle and I / O to
2017-06-02 11:36:56 +03:00
* provide a scriptable API to format translations .
* /
class Localization {
/ * *
2019-07-01 20:56:57 +03:00
* @ param { Array < String > } resourceIds - List of resource IDs
* @ param { Function } generateBundles - Function that returns an async
* generator over FluentBundles
* @ param { Function } generateBundlesSync - Function that returns a sync
* generator over FluentBundles
2017-06-02 11:36:56 +03:00
*
* @ returns { Localization }
* /
2019-07-01 20:56:57 +03:00
constructor ( resourceIds = [ ] , sync = false , generateBundles = defaultGenerateBundles , generateBundlesSync = defaultGenerateBundlesSync ) {
this . isSync = sync ;
2017-06-02 11:36:56 +03:00
this . resourceIds = resourceIds ;
2018-10-20 19:35:50 +03:00
this . generateBundles = generateBundles ;
2019-07-01 20:56:57 +03:00
this . generateBundlesSync = generateBundlesSync ;
2019-06-06 19:32:41 +03:00
this . onChange ( true ) ;
2018-05-14 22:14:27 +03:00
}
2019-01-11 03:25:49 +03:00
cached ( iterable ) {
2019-07-01 20:56:57 +03:00
if ( this . isSync ) {
return CachedSyncIterable . from ( iterable ) ;
} else {
return CachedAsyncIterable . from ( iterable ) ;
}
2019-01-11 03:25:49 +03:00
}
2018-09-07 04:19:26 +03:00
/ * *
* @ param { Array < String > } resourceIds - List of resource IDs
* @ param { bool } eager - whether the I / O for new context should
* begin eagerly
* /
addResourceIds ( resourceIds , eager = false ) {
2018-05-14 22:14:27 +03:00
this . resourceIds . push ( ... resourceIds ) ;
2018-09-07 04:19:26 +03:00
this . onChange ( eager ) ;
2018-07-11 03:49:02 +03:00
return this . resourceIds . length ;
2018-05-14 22:14:27 +03:00
}
removeResourceIds ( resourceIds ) {
this . resourceIds = this . resourceIds . filter ( r => ! resourceIds . includes ( r ) ) ;
2019-01-30 22:58:31 +03:00
this . onChange ( ) ;
2018-07-11 03:49:02 +03:00
return this . resourceIds . length ;
2017-06-02 11:36:56 +03:00
}
/ * *
* Format translations and handle fallback if needed .
*
2018-10-20 19:35:50 +03:00
* Format translations for ` keys ` from ` FluentBundle ` instances on this
2019-06-06 19:32:49 +03:00
* Localization . In case of errors , fetch the next context in the
2017-06-02 11:36:56 +03:00
* fallback chain .
*
2018-05-14 22:14:27 +03:00
* @ param { Array < Object > } keys - Translation keys to format .
2017-06-02 11:36:56 +03:00
* @ param { Function } method - Formatting function .
* @ returns { Promise < Array < string | Object >> }
* @ private
* /
async formatWithFallback ( keys , method ) {
2019-03-26 22:34:27 +03:00
const translations = new Array ( keys . length ) ;
let hasAtLeastOneBundle = false ;
2018-03-06 12:25:23 +03:00
2018-10-20 19:35:50 +03:00
for await ( const bundle of this . bundles ) {
2019-03-26 22:34:27 +03:00
hasAtLeastOneBundle = true ;
2018-10-20 19:35:50 +03:00
const missingIds = keysFromBundle ( method , bundle , keys , translations ) ;
2018-03-06 12:25:23 +03:00
if ( missingIds . size === 0 ) {
2017-06-02 11:36:56 +03:00
break ;
}
2018-03-06 12:25:23 +03:00
2019-03-26 22:34:27 +03:00
const locale = bundle . locales [ 0 ] ;
const ids = Array . from ( missingIds ) . join ( ", " ) ;
maybeReportErrorToGecko ( ` [fluent] Missing translations in ${ locale } : ${ ids } . ` ) ;
}
if ( ! hasAtLeastOneBundle ) {
maybeReportErrorToGecko ( ` [fluent] Request for keys failed because no resource bundles got generated. \n keys: ${ JSON . stringify ( keys ) } . \n resourceIds: ${ JSON . stringify ( this . resourceIds ) } . ` ) ;
2017-06-02 11:36:56 +03:00
}
2018-03-06 12:25:23 +03:00
2017-06-02 11:36:56 +03:00
return translations ;
}
2019-07-01 20:56:57 +03:00
/ * *
* Format translations and handle fallback if needed .
*
* Format translations for ` keys ` from ` FluentBundle ` instances on this
* Localization . In case of errors , fetch the next context in the
* fallback chain .
*
* @ param { Array < Object > } keys - Translation keys to format .
* @ param { Function } method - Formatting function .
* @ returns { Array < string | Object > }
* @ private
* /
formatWithFallbackSync ( keys , method ) {
if ( ! this . isSync ) {
throw new Error ( "Can't use sync formatWithFallback when state is async." ) ;
}
const translations = new Array ( keys . length ) ;
let hasAtLeastOneBundle = false ;
for ( const bundle of this . bundles ) {
hasAtLeastOneBundle = true ;
const missingIds = keysFromBundle ( method , bundle , keys , translations ) ;
if ( missingIds . size === 0 ) {
break ;
}
const locale = bundle . locales [ 0 ] ;
const ids = Array . from ( missingIds ) . join ( ", " ) ;
maybeReportErrorToGecko ( ` [fluent] Missing translations in ${ locale } : ${ ids } . ` ) ;
}
if ( ! hasAtLeastOneBundle ) {
maybeReportErrorToGecko ( ` [fluent] Request for keys failed because no resource bundles got generated. \n keys: ${ JSON . stringify ( keys ) } . \n resourceIds: ${ JSON . stringify ( this . resourceIds ) } . ` ) ;
}
return translations ;
}
2017-06-02 11:36:56 +03:00
/ * *
2018-05-14 22:14:27 +03:00
* Format translations into { value , attributes } objects .
2017-06-02 11:36:56 +03:00
*
2019-07-30 16:21:59 +03:00
* The fallback logic is the same as in ` formatValues ` but it returns { value ,
* attributes } objects which are suitable for the translation of DOM
* elements .
2017-06-02 11:36:56 +03:00
*
* docL10n . formatMessages ( [
2018-05-14 22:14:27 +03:00
* { id : 'hello' , args : { who : 'Mary' } } ,
* { id : 'welcome' }
2017-06-02 11:36:56 +03:00
* ] ) . then ( console . log ) ;
*
* // [
2018-05-14 22:14:27 +03:00
* // { value: 'Hello, Mary!', attributes: null },
2018-10-20 19:35:50 +03:00
* // {
* // value: 'Welcome!',
* // attributes: [ { name: "title", value: 'Hello' } ]
* // }
2017-06-02 11:36:56 +03:00
* // ]
*
2019-07-01 20:56:57 +03:00
* Returns a Promise resolving to an array of the translation messages .
2017-06-02 11:36:56 +03:00
*
2018-05-14 22:14:27 +03:00
* @ param { Array < Object > } keys
* @ returns { Promise < Array < { value : string , attributes : Object } >> }
2017-06-02 11:36:56 +03:00
* @ private
* /
formatMessages ( keys ) {
2018-10-20 19:35:50 +03:00
return this . formatWithFallback ( keys , messageFromBundle ) ;
2017-06-02 11:36:56 +03:00
}
2019-07-01 20:56:57 +03:00
/ * *
* Sync version of ` formatMessages ` .
*
* Returns an array of the translation messages .
*
* @ param { Array < Object > } keys
* @ returns { Array < { value : string , attributes : Object } > }
* @ private
* /
formatMessagesSync ( keys ) {
return this . formatWithFallbackSync ( keys , messageFromBundle ) ;
}
2017-06-02 11:36:56 +03:00
/ * *
* Retrieve translations corresponding to the passed keys .
*
2019-07-30 16:21:59 +03:00
* A generalized version of ` Localization.formatValue ` . Keys must
* be ` {id, args} ` objects .
2018-01-27 01:01:34 +03:00
*
2017-06-02 11:36:56 +03:00
* docL10n . formatValues ( [
2018-05-14 22:14:27 +03:00
* { id : 'hello' , args : { who : 'Mary' } } ,
* { id : 'hello' , args : { who : 'John' } } ,
* { id : 'welcome' }
2017-06-02 11:36:56 +03:00
* ] ) . then ( console . log ) ;
*
* // ['Hello, Mary!', 'Hello, John!', 'Welcome!']
*
* Returns a Promise resolving to an array of the translation strings .
*
2018-05-14 22:14:27 +03:00
* @ param { Array < Object > } keys
2017-06-02 11:36:56 +03:00
* @ returns { Promise < Array < string >> }
* /
formatValues ( keys ) {
2018-10-20 19:35:50 +03:00
return this . formatWithFallback ( keys , valueFromBundle ) ;
2017-06-02 11:36:56 +03:00
}
2019-07-01 20:56:57 +03:00
/ * *
* Sync version of ` formatValues ` .
*
* Returns an array of the translation strings .
*
* @ param { Array < Object > } keys
* @ returns { Array < string > }
* @ private
* /
formatValuesSync ( keys ) {
return this . formatWithFallbackSync ( keys , valueFromBundle ) ;
}
2017-06-02 11:36:56 +03:00
/ * *
* Retrieve the translation corresponding to the ` id ` identifier .
*
* If passed , ` args ` is a simple hash object with a list of variables that
* will be interpolated in the value of the translation .
*
* docL10n . formatValue (
* 'hello' , { who : 'world' }
* ) . then ( console . log ) ;
*
* // 'Hello, world!'
*
2019-07-01 20:56:57 +03:00
* Returns a Promise resolving to a translation string .
2017-06-02 11:36:56 +03:00
*
* Use this sparingly for one - off messages which don ' t need to be
* retranslated when the user changes their language preferences , e . g . in
* notifications .
*
* @ param { string } id - Identifier of the translation to format
* @ param { Object } [ args ] - Optional external arguments
* @ returns { Promise < string > }
* /
async formatValue ( id , args ) {
2018-05-14 22:14:27 +03:00
const [ val ] = await this . formatValues ( [ { id , args } ] ) ;
2017-06-02 11:36:56 +03:00
return val ;
}
2019-07-01 20:56:57 +03:00
/ * *
* Sync version of ` formatValue ` .
*
* Returns a translation string .
*
* @ param { Array < Object > } keys
* @ returns { string > }
* @ private
* /
formatValueSync ( id , args ) {
const [ val ] = this . formatValuesSync ( [ { id , args } ] ) ;
return val ;
}
2017-06-02 11:36:56 +03:00
/ * *
2018-02-14 03:25:41 +03:00
* Register weak observers on events that will trigger cache invalidation
2017-06-02 11:36:56 +03:00
* /
registerObservers ( ) {
2018-02-22 05:36:30 +03:00
Services . obs . addObserver ( this , "intl:app-locales-changed" , true ) ;
2018-06-01 22:37:13 +03:00
Services . prefs . addObserver ( "intl.l10n.pseudo" , this , true ) ;
2017-06-02 11:36:56 +03:00
}
/ * *
* Default observer handler method .
*
* @ param { String } subject
* @ param { String } topic
* @ param { Object } data
* /
observe ( subject , topic , data ) {
switch ( topic ) {
2018-02-21 03:02:54 +03:00
case "intl:app-locales-changed" :
2018-05-14 22:14:27 +03:00
this . onChange ( ) ;
2017-06-02 11:36:56 +03:00
break ;
2018-06-01 22:37:13 +03:00
case "nsPref:changed" :
switch ( data ) {
case "intl.l10n.pseudo" :
this . onChange ( ) ;
}
break ;
2017-06-02 11:36:56 +03:00
default :
break ;
}
}
/ * *
* This method should be called when there ' s a reason to believe
* that language negotiation or available resources changed .
2018-09-07 04:19:26 +03:00
*
* @ param { bool } eager - whether the I / O for new context should begin eagerly
2017-06-02 11:36:56 +03:00
* /
2018-09-07 04:19:26 +03:00
onChange ( eager = false ) {
2019-07-01 20:56:57 +03:00
let generateMessages = this . isSync ? this . generateBundlesSync : this . generateBundles ;
this . bundles = this . cached ( generateMessages ( this . resourceIds ) ) ;
2018-09-07 04:19:26 +03:00
if ( eager ) {
// If the first app locale is the same as last fallback
// it means that we have all resources in this locale, and
// we want to eagerly fetch just that one.
// Otherwise, we're in a scenario where the first locale may
// be partial and we want to eagerly fetch a fallback as well.
2018-09-21 18:30:37 +03:00
const appLocale = Services . locale . appLocaleAsBCP47 ;
2018-09-07 04:19:26 +03:00
const lastFallback = Services . locale . lastFallbackLocale ;
const prefetchCount = appLocale === lastFallback ? 1 : 2 ;
2018-10-20 19:35:50 +03:00
this . bundles . touchNext ( prefetchCount ) ;
2018-09-07 04:19:26 +03:00
}
2017-06-02 11:36:56 +03:00
}
2019-07-01 20:56:57 +03:00
setIsSync ( isSync ) {
this . isSync = isSync ;
this . onChange ( ) ;
}
2017-06-02 11:36:56 +03:00
}
2018-04-23 06:55:06 +03:00
Localization . prototype . QueryInterface = ChromeUtils . generateQI ( [
2018-10-19 15:55:39 +03:00
Ci . nsISupportsWeakReference ,
2018-02-14 03:25:41 +03:00
] ) ;
2017-06-02 11:36:56 +03:00
/ * *
2019-07-30 16:21:59 +03:00
* Format the value of a message into a string or ` null ` .
2017-06-02 11:36:56 +03:00
*
2018-10-20 19:35:50 +03:00
* This function is passed as a method to ` keysFromBundle ` and resolve
* a value of a single L10n Entity using provided ` FluentBundle ` .
2019-07-30 16:21:59 +03:00
* If the message doesn ' t have a value , return ` null ` .
2017-06-02 11:36:56 +03:00
*
2018-10-20 19:35:50 +03:00
* @ param { FluentBundle } bundle
2019-07-30 16:21:59 +03:00
* @ param { Array < Error > } errors
* @ param { Object } message
* @ param { Object } args
* @ returns { string | null }
2017-06-02 11:36:56 +03:00
* @ private
* /
2019-07-30 16:21:59 +03:00
function valueFromBundle ( bundle , errors , message , args ) {
if ( message . value ) {
return bundle . formatPattern ( message . value , args , errors ) ;
}
return null ;
2017-06-02 11:36:56 +03:00
}
/ * *
2018-05-14 22:14:27 +03:00
* Format all public values of a message into a { value , attributes } object .
2017-06-02 11:36:56 +03:00
*
2018-10-20 19:35:50 +03:00
* This function is passed as a method to ` keysFromBundle ` and resolve
* a single L10n Entity using provided ` FluentBundle ` .
2017-06-02 11:36:56 +03:00
*
* The function will return an object with a value and attributes of the
* entity .
*
2018-10-20 19:35:50 +03:00
* @ param { FluentBundle } bundle
2017-06-02 11:36:56 +03:00
* @ param { Array < Error > } errors
2019-07-30 16:21:59 +03:00
* @ param { Object } message
* @ param { Object } args
2017-06-02 11:36:56 +03:00
* @ returns { Object }
* @ private
* /
2019-07-30 16:21:59 +03:00
function messageFromBundle ( bundle , errors , message , args ) {
2017-06-02 11:36:56 +03:00
const formatted = {
2019-07-30 16:21:59 +03:00
value : null ,
2018-04-11 23:06:35 +03:00
attributes : null ,
2017-06-02 11:36:56 +03:00
} ;
2019-07-30 16:21:59 +03:00
if ( message . value ) {
formatted . value = bundle . formatPattern ( message . value , args , errors ) ;
}
let attrNames = Object . keys ( message . attributes ) ;
if ( attrNames . length > 0 ) {
formatted . attributes = new Array ( attrNames . length ) ;
for ( let [ i , name ] of attrNames . entries ( ) ) {
let value = bundle . formatPattern ( message . attributes [ name ] , args , errors ) ;
formatted . attributes [ i ] = { name , value } ;
2017-06-02 11:36:56 +03:00
}
}
return formatted ;
}
/ * *
* This function is an inner function for ` Localization.formatWithFallback ` .
*
2018-10-20 19:35:50 +03:00
* It takes a ` FluentBundle ` , list of l10n - ids and a method to be used for
* key resolution ( either ` valueFromBundle ` or ` messageFromBundle ` ) and
* optionally a value returned from ` keysFromBundle ` executed against
* another ` FluentBundle ` .
2017-06-02 11:36:56 +03:00
*
2018-10-20 19:35:50 +03:00
* The idea here is that if the previous ` FluentBundle ` did not resolve
2017-06-02 11:36:56 +03:00
* all keys , we ' re calling this function with the next context to resolve
* the remaining ones .
*
* In the function , we loop over ` keys ` and check if we have the ` prev `
* passed and if it has an error entry for the position we ' re in .
*
* If it doesn ' t , it means that we have a good translation for this key and
* we return it . If it does , we ' ll try to resolve the key using the passed
2018-10-20 19:35:50 +03:00
* ` FluentBundle ` .
2017-06-02 11:36:56 +03:00
*
2018-03-06 12:25:23 +03:00
* In the end , we fill the translations array , and return the Set with
* missing ids .
2017-06-02 11:36:56 +03:00
*
* See ` Localization.formatWithFallback ` for more info on how this is used .
*
* @ param { Function } method
2018-10-20 19:35:50 +03:00
* @ param { FluentBundle } bundle
2017-06-02 11:36:56 +03:00
* @ param { Array < string > } keys
2018-05-14 22:14:27 +03:00
* @ param { { Array < { value : string , attributes : Object } > } } translations
2017-06-02 11:36:56 +03:00
*
2018-03-06 12:25:23 +03:00
* @ returns { Set < string > }
2017-06-02 11:36:56 +03:00
* @ private
* /
2018-10-20 19:35:50 +03:00
function keysFromBundle ( method , bundle , keys , translations ) {
2017-06-02 11:36:56 +03:00
const messageErrors = [ ] ;
2018-03-06 12:25:23 +03:00
const missingIds = new Set ( ) ;
2017-06-02 11:36:56 +03:00
2018-05-14 22:14:27 +03:00
keys . forEach ( ( { id , args } , i ) => {
2017-06-02 11:36:56 +03:00
if ( translations [ i ] !== undefined ) {
return ;
}
2019-07-30 16:21:59 +03:00
let message = bundle . getMessage ( id ) ;
if ( message ) {
2018-03-06 12:25:23 +03:00
messageErrors . length = 0 ;
2019-07-30 16:21:59 +03:00
translations [ i ] = method ( bundle , messageErrors , message , args ) ;
2019-03-26 22:34:27 +03:00
if ( messageErrors . length > 0 ) {
const locale = bundle . locales [ 0 ] ;
const errors = messageErrors . join ( ", " ) ;
maybeReportErrorToGecko ( ` [fluent][resolver] errors in ${ locale } / ${ id } : ${ errors } . ` ) ;
}
2017-06-02 11:36:56 +03:00
} else {
2018-05-14 22:14:27 +03:00
missingIds . add ( id ) ;
2017-06-02 11:36:56 +03:00
}
} ) ;
2018-03-06 12:25:23 +03:00
return missingIds ;
2017-06-02 11:36:56 +03:00
}
2019-05-21 22:22:36 +03:00
/ * *
* Helper function which allows us to construct a new
2019-06-06 19:32:49 +03:00
* Localization from Localization .
2019-05-21 22:22:36 +03:00
* /
2019-07-01 20:56:57 +03:00
var getLocalization = ( resourceIds , sync = false ) => {
return new Localization ( resourceIds , sync ) ;
2019-06-06 19:32:41 +03:00
} ;
var getLocalizationWithCustomGenerateMessages = ( resourceIds , generateMessages ) => {
2019-07-01 20:56:57 +03:00
return new Localization ( resourceIds , false , generateMessages ) ;
2019-06-06 19:32:41 +03:00
} ;
2019-05-21 22:22:36 +03:00
2017-06-02 11:36:56 +03:00
this . Localization = Localization ;
2019-07-01 20:56:57 +03:00
var EXPORTED _SYMBOLS = [ "Localization" , "getLocalization" , "getLocalizationWithCustomGenerateMessages" ] ;