Skip to content

Commit

Permalink
Add mapContextAsync
Browse files Browse the repository at this point in the history
This is the async counterpart to mapContextSync. Given an async iterable of
MessageContext instance and an array of ids (or a single id), it maps each
identifier to the first MessageContext which contains the message for it.

An ordered interable of MessageContext instances can represent the current
negotiated fallback chain of languages. This iterable can be used to find the
best existing translation for a given identifier.

The iterable of MessageContexts can now be async, allowing code like this:

    async formatString(id, args) {
        const ctx = await mapContextAsync(contexts, id);

        if (ctx === null) {
            return id;
        }

        const msg = ctx.getMessage(id);
        return ctx.format(msg, args);
    }

The iterable of MessageContexts should always be wrapped in CachedIterable to
optimize subsequent calls to mapContextSync and mapContextAsync.
  • Loading branch information
stasm committed Jan 12, 2018
1 parent a717135 commit e3c2f73
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 5 deletions.
2 changes: 1 addition & 1 deletion compat_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default {
'plugins': [
// Cf. https://github.com/rollup/rollup-plugin-babel#helpers
'external-helpers',
['babel-plugin-transform-builtin-extend', {
['transform-builtin-extend', {
globals: ['Error']
}]
]
Expand Down
34 changes: 33 additions & 1 deletion fluent/src/fallback.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
* @param {string|Array<string>} ids
* @returns {MessageContext|Array<MessageContext>}
*/

export function mapContextSync(iterable, ids) {
if (!Array.isArray(ids)) {
return getContextForId(iterable, ids);
Expand All @@ -71,3 +70,36 @@ function getContextForId(iterable, id) {

return null;
}

/*
* Asynchronously map an identifier or an array of identifiers to the best
* `MessageContext` instance(s).
*
* @param {AsyncIterable} iterable
* @param {string|Array<string>} ids
* @returns {MessageContext|Array<MessageContext>}
*/
export async function mapContextAsync(iterable, ids) {
if (!Array.isArray(ids)) {
for await (const context of iterable) {
if (context.hasMessage(ids)) {
return context;
}
}
}

const foundContexts = new Array(ids.length).fill(null);

for await (const context of iterable) {
// XXX Switch to const [index, id] of id.entries() when we move to Babel 7.
// See https://github.com/babel/babel/issues/5880.
for (let index = 0; index < ids.length; index++) {
const id = ids[index];
if (!foundContexts[index] && context.hasMessage(id)) {
foundContexts[index] = context;
}
}
}

return foundContexts;
}
2 changes: 1 addition & 1 deletion fluent/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ export {
} from './types';

export { default as CachedIterable } from './cached_iterable';
export { mapContextSync } from './fallback';
export { mapContextSync, mapContextAsync } from './fallback';
87 changes: 87 additions & 0 deletions fluent/test/fallback_async_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import assert from 'assert';

import CachedIterable from '../src/cached_iterable';
import MessageContext from './message_context_stub';
import { mapContextAsync } from '../src/index';

suite('Async Fallback — single id', function() {
let ctx1, ctx2;

suiteSetup(function() {
ctx1 = new MessageContext();
ctx1._setMessages(['bar']);
ctx2 = new MessageContext();
ctx2._setMessages(['foo', 'bar']);
});

test('eager iterable', async function() {
const contexts = new CachedIterable([ctx1, ctx2]);
assert.equal(await mapContextAsync(contexts, 'foo'), ctx2);
assert.equal(await mapContextAsync(contexts, 'bar'), ctx1);
});

test('eager iterable works more than once', async function() {
const contexts = new CachedIterable([ctx1, ctx2]);
assert.equal(await mapContextAsync(contexts, 'foo'), ctx2);
assert.equal(await mapContextAsync(contexts, 'bar'), ctx1);
assert.equal(await mapContextAsync(contexts, 'foo'), ctx2);
assert.equal(await mapContextAsync(contexts, 'bar'), ctx1);
});

test('lazy iterable', async function() {
async function *generateMessages() {
yield *[ctx1, ctx2];
}

const contexts = new CachedIterable(generateMessages());
assert.equal(await mapContextAsync(contexts, 'foo'), ctx2);
assert.equal(await mapContextAsync(contexts, 'bar'), ctx1);
});

test('lazy iterable works more than once', async function() {
async function *generateMessages() {
yield *[ctx1, ctx2];
}

const contexts = new CachedIterable(generateMessages());
assert.equal(await mapContextAsync(contexts, 'foo'), ctx2);
assert.equal(await mapContextAsync(contexts, 'bar'), ctx1);
assert.equal(await mapContextAsync(contexts, 'foo'), ctx2);
assert.equal(await mapContextAsync(contexts, 'bar'), ctx1);
});
});

suite('Async Fallback — multiple ids', async function() {
let ctx1, ctx2;

suiteSetup(function() {
ctx1 = new MessageContext();
ctx1._setMessages(['foo', 'bar']);
ctx2 = new MessageContext();
ctx2._setMessages(['foo', 'bar', 'baz']);
});

test('existing translations', async function() {
const contexts = new CachedIterable([ctx1, ctx2]);
assert.deepEqual(
await mapContextAsync(contexts, ['foo', 'bar']),
[ctx1, ctx1]
);
});

test('fallback translations', async function() {
const contexts = new CachedIterable([ctx1, ctx2]);
assert.deepEqual(
await mapContextAsync(contexts, ['foo', 'bar', 'baz']),
[ctx1, ctx1, ctx2]
);
});

test('missing translations', async function() {
const contexts = new CachedIterable([ctx1, ctx2]);
assert.deepEqual(
await mapContextAsync(contexts, ['foo', 'bar', 'baz', 'qux']),
[ctx1, ctx1, ctx2, null]
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import CachedIterable from '../src/cached_iterable';
import MessageContext from './message_context_stub';
import { mapContextSync } from '../src/index';

suite('Fallback — single id', function() {
suite('Sync Fallback — single id', function() {
let ctx1, ctx2;

suiteSetup(function() {
Expand Down Expand Up @@ -51,7 +51,7 @@ suite('Fallback — single id', function() {
});
});

suite('Fallback — multiple ids', function() {
suite('Sync Fallback — multiple ids', function() {
let ctx1, ctx2;

suiteSetup(function() {
Expand Down

0 comments on commit e3c2f73

Please sign in to comment.