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 {
/ * *
2018-10-20 19:35:50 +03:00
* @ param { Array < String > } resourceIds - List of resource IDs
* @ param { Function } generateBundles - Function that returns a
* generator over FluentBundles
2017-06-02 11:36:56 +03:00
*
* @ returns { Localization }
* /
2018-10-20 19:35:50 +03:00
constructor ( resourceIds = [ ] , generateBundles = defaultGenerateBundles ) {
2017-06-02 11:36:56 +03:00
this . resourceIds = resourceIds ;
2018-10-20 19:35:50 +03:00
this . generateBundles = generateBundles ;
2019-01-11 03:25:49 +03:00
this . bundles = this . cached (
2018-10-20 19:35:50 +03:00
this . generateBundles ( this . resourceIds ) ) ;
2018-05-14 22:14:27 +03:00
}
2019-01-11 03:25:49 +03:00
cached ( iterable ) {
return CachedAsyncIterable . from ( iterable ) ;
}
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
2017-06-02 11:36:56 +03:00
* DOMLocalization . In case of errors , fetch the next context in the
* 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 ;
}
/ * *
2018-05-14 22:14:27 +03:00
* Format translations into { value , attributes } objects .
2017-06-02 11:36:56 +03:00
*
* The fallback logic is the same as in ` formatValues ` but the argument type
2018-05-14 22:14:27 +03:00
* is stricter ( an array of arrays ) and 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
* // ]
*
* Returns a Promise resolving to an array of the translation strings .
*
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
}
/ * *
* Retrieve translations corresponding to the passed keys .
*
2018-01-27 01:01:34 +03:00
* A generalized version of ` DOMLocalization.formatValue ` . Keys can
* either be simple string identifiers or ` [id, args] ` arrays .
*
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
}
/ * *
* 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!'
*
* Returns a Promise resolving to the translation string .
*
* 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 ;
}
/ * *
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-01-11 03:25:49 +03:00
this . bundles = this . cached (
2018-10-20 19:35:50 +03:00
this . generateBundles ( 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
}
}
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
] ) ;
2019-01-11 03:25:49 +03:00
class LocalizationSync extends Localization {
constructor ( resourceIds = [ ] , generateBundles = defaultGenerateBundlesSync ) {
super ( resourceIds , generateBundles ) ;
}
cached ( iterable ) {
return CachedSyncIterable . from ( iterable ) ;
}
formatWithFallback ( keys , method ) {
2019-03-26 22:34:27 +03:00
const translations = new Array ( keys . length ) ;
let hasAtLeastOneBundle = false ;
2019-01-11 03:25:49 +03:00
for ( const bundle of this . bundles ) {
2019-03-26 22:34:27 +03:00
hasAtLeastOneBundle = true ;
2019-01-11 03:25:49 +03:00
const missingIds = keysFromBundle ( method , bundle , keys , translations ) ;
if ( missingIds . size === 0 ) {
break ;
}
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 ) } . ` ) ;
2019-01-11 03:25:49 +03:00
}
return translations ;
}
formatValue ( id , args ) {
const [ val ] = this . formatValues ( [ { id , args } ] ) ;
return val ;
}
}
2017-06-02 11:36:56 +03:00
/ * *
* Format the value of a message into a string .
*
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 ` .
2017-06-02 11:36:56 +03:00
*
* If the function fails to retrieve the entity , it will return an ID of it .
* If formatting fails , it will return a partially resolved entity .
*
* In both cases , an error is being added to the errors array .
*
2018-10-20 19:35:50 +03:00
* @ param { FluentBundle } bundle
2017-06-02 11:36:56 +03:00
* @ param { Array < Error > } errors
* @ param { string } id
* @ param { Object } args
* @ returns { string }
* @ private
* /
2018-10-20 19:35:50 +03:00
function valueFromBundle ( bundle , errors , id , args ) {
const msg = bundle . getMessage ( id ) ;
return bundle . format ( msg , args , errors ) ;
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 .
*
* If the function fails to retrieve the entity , the value is set to the ID of
2018-05-14 22:14:27 +03:00
* an entity , and attributes to ` null ` . If formatting fails , it will return
2017-06-02 11:36:56 +03:00
* a partially resolved value and attributes .
*
* In both cases , an error is being added to the errors array .
*
2018-10-20 19:35:50 +03:00
* @ param { FluentBundle } bundle
2017-06-02 11:36:56 +03:00
* @ param { Array < Error > } errors
* @ param { String } id
* @ param { Object } args
* @ returns { Object }
* @ private
* /
2018-10-20 19:35:50 +03:00
function messageFromBundle ( bundle , errors , id , args ) {
const msg = bundle . getMessage ( id ) ;
2017-06-02 11:36:56 +03:00
const formatted = {
2018-10-20 19:35:50 +03:00
value : bundle . format ( msg , args , errors ) ,
2018-04-11 23:06:35 +03:00
attributes : null ,
2017-06-02 11:36:56 +03:00
} ;
if ( msg . attrs ) {
2018-04-11 23:06:35 +03:00
formatted . attributes = [ ] ;
for ( const [ name , attr ] of Object . entries ( msg . attrs ) ) {
2018-10-20 19:35:50 +03:00
const value = bundle . format ( attr , args , errors ) ;
2018-03-02 03:49:37 +03:00
if ( value !== null ) {
2018-04-11 23:06:35 +03:00
formatted . attributes . push ( { 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 ;
}
2018-10-20 19:35:50 +03:00
if ( bundle . hasMessage ( id ) ) {
2018-03-06 12:25:23 +03:00
messageErrors . length = 0 ;
2018-10-20 19:35:50 +03:00
translations [ i ] = method ( bundle , messageErrors , id , 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
}
} ) ;
2019-03-26 22:34:27 +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
* Localization from DocumentL10n .
* /
var getLocalization = ( ) => new Localization ( ) ;
2017-06-02 11:36:56 +03:00
this . Localization = Localization ;
2019-01-11 03:25:49 +03:00
this . LocalizationSync = LocalizationSync ;
2019-05-21 22:22:36 +03:00
var EXPORTED _SYMBOLS = [ "Localization" , "LocalizationSync" , "getLocalization" ] ;