/* Copyright 2019 Mozilla Foundation and others * * 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. */ 'use strict'; /* fluent-react@0.10.0 */ Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } const react = require("resource://devtools/client/shared/vendor/react.js"); const PropTypes = _interopDefault(require("resource://devtools/client/shared/vendor/react-prop-types.js")); /* * Synchronously map an identifier or an array of identifiers to the best * `FluentBundle` instance(s). * * @param {Iterable} iterable * @param {string|Array} ids * @returns {FluentBundle|Array} */ function mapBundleSync(iterable, ids) { if (!Array.isArray(ids)) { return getBundleForId(iterable, ids); } return ids.map( id => getBundleForId(iterable, id) ); } /* * Find the best `FluentBundle` with the translation for `id`. */ function getBundleForId(iterable, id) { for (const bundle of iterable) { if (bundle.hasMessage(id)) { return bundle; } } return null; } /* * Asynchronously map an identifier or an array of identifiers to the best * `FluentBundle` instance(s). * * @param {AsyncIterable} iterable * @param {string|Array} ids * @returns {Promise>} */ /* * @module fluent-sequence * @overview Manage ordered sequences of FluentBundles. */ /* * Base CachedIterable class. */ 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); } } /* * 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]; } } /* * `ReactLocalization` handles translation formatting and fallback. * * The current negotiated fallback chain of languages is stored in the * `ReactLocalization` instance in form of an iterable of `FluentBundle` * instances. This iterable is used to find the best existing translation for * a given identifier. * * `Localized` components must subscribe to the changes of the * `ReactLocalization`'s fallback chain. When the fallback chain changes (the * `bundles` iterable is set anew), all subscribed compontent must relocalize. * * The `ReactLocalization` class instances are exposed to `Localized` elements * via the `LocalizationProvider` component. */ class ReactLocalization { constructor(bundles) { this.bundles = CachedSyncIterable.from(bundles); this.subs = new Set(); } /* * Subscribe a `Localized` component to changes of `bundles`. */ subscribe(comp) { this.subs.add(comp); } /* * Unsubscribe a `Localized` component from `bundles` changes. */ unsubscribe(comp) { this.subs.delete(comp); } /* * Set a new `bundles` iterable and trigger the retranslation. */ setBundles(bundles) { this.bundles = CachedSyncIterable.from(bundles); // Update all subscribed Localized components. this.subs.forEach(comp => comp.relocalize()); } getBundle(id) { return mapBundleSync(this.bundles, id); } /* * Find a translation by `id` and format it to a string using `args`. */ getString(id, args, fallback) { const bundle = this.getBundle(id); if (bundle) { const msg = bundle.getMessage(id); if (msg && msg.value) { let errors = []; let value = bundle.formatPattern(msg.value, args, errors); for (let error of errors) { this.reportError(error); } return value; } } return fallback || id; } // XXX Control this via a prop passed to the LocalizationProvider. // See https://github.com/projectfluent/fluent.js/issues/411. reportError(error) { /* global console */ // eslint-disable-next-line no-console console.warn(`[@fluent/react] ${error.name}: ${error.message}`); } } function isReactLocalization(props, propName) { const prop = props[propName]; if (prop instanceof ReactLocalization) { return null; } return new Error( `The ${propName} context field must be an instance of ReactLocalization.` ); } /* eslint-env browser */ let cachedParseMarkup; // We use a function creator to make the reference to `document` lazy. At the // same time, it's eager enough to throw in as soon as // it's first mounted which reduces the risk of this error making it to the // runtime without developers noticing it in development. function createParseMarkup() { if (typeof(document) === "undefined") { // We can't use