diff --git a/fluent/CHANGELOG.md b/fluent/CHANGELOG.md index 1ece76735..944685814 100644 --- a/fluent/CHANGELOG.md +++ b/fluent/CHANGELOG.md @@ -2,7 +2,10 @@ ## Unreleased - - … + - Remove MessageContext.formatToParts. + + It's only use-case was passing React elements as arguments to + translations which is now possible thanks to DOM overlays (#101). ## fluent 0.4.2 (November 27, 2017) diff --git a/fluent/src/context.js b/fluent/src/context.js index 4c0896e99..4f96f27e0 100644 --- a/fluent/src/context.js +++ b/fluent/src/context.js @@ -1,6 +1,5 @@ import resolve from './resolver'; import parse from './parser'; -import { FluentNone } from './types'; /** * Message contexts are single-language stores of translations. They are @@ -80,7 +79,7 @@ export class MessageContext { * Return the internal representation of a message. * * The internal representation should only be used as an argument to - * `MessageContext.format` and `MessageContext.formatToParts`. + * `MessageContext.format`. * * @param {string} id - The identifier of the message to check. * @returns {Any} @@ -116,61 +115,6 @@ export class MessageContext { return errors; } - /** - * Format a message to an array of `FluentTypes` or null. - * - * Format a raw `message` from the context into an array of `FluentType` - * instances which may be used to build the final result. It may also return - * `null` if it has a null value. `args` will be used to resolve references - * to external arguments inside of the translation. - * - * See the documentation of {@link MessageContext#format} for more - * information about error handling. - * - * In case of errors `format` will try to salvage as much of the translation - * as possible and will still return a string. For performance reasons, the - * encountered errors are not returned but instead are appended to the - * `errors` array passed as the third argument. - * - * ctx.addMessages('hello = Hello, { $name }!'); - * const hello = ctx.getMessage('hello'); - * ctx.formatToParts(hello, { name: 'Jane' }, []); - * // → ['Hello, ', '\u2068', 'Jane', '\u2069'] - * - * The returned parts need to be formatted via `valueOf` before they can be - * used further. This will ensure all values are correctly formatted - * according to the `MessageContext`'s locale. - * - * const parts = ctx.formatToParts(hello, { name: 'Jane' }, []); - * const str = parts.map(part => part.valueOf(ctx)).join(''); - * - * @see MessageContext#format - * @param {Object | string} message - * @param {Object | undefined} args - * @param {Array} errors - * @returns {?Array} - */ - formatToParts(message, args, errors) { - // optimize entities which are simple strings with no attributes - if (typeof message === 'string') { - return [message]; - } - - // optimize simple-string entities with attributes - if (typeof message.val === 'string') { - return [message.val]; - } - - // optimize entities with null values - if (message.val === undefined) { - return null; - } - - const result = resolve(this, args, message, errors); - - return result instanceof FluentNone ? null : result; - } - /** * Format a message to a string or null. * @@ -217,13 +161,7 @@ export class MessageContext { return null; } - const result = resolve(this, args, message, errors); - - if (result instanceof FluentNone) { - return null; - } - - return result.map(part => part.valueOf(this)).join(''); + return resolve(this, args, message, errors); } _memoizeIntlObject(ctor, opts) { diff --git a/fluent/src/resolver.js b/fluent/src/resolver.js index 9916f1ccc..cf8d0d35a 100644 --- a/fluent/src/resolver.js +++ b/fluent/src/resolver.js @@ -59,27 +59,6 @@ const FSI = '\u2068'; const PDI = '\u2069'; -/** - * Helper for computing the total character length of a placeable. - * - * Used in Pattern. - * - * @param {Object} env - * Resolver environment object. - * @param {Array} parts - * List of parts of a placeable. - * @returns {Number} - * @private - */ -function PlaceableLength(env, parts) { - const { ctx } = env; - return parts.reduce( - (sum, part) => sum + part.valueOf(ctx).length, - 0 - ); -} - - /** * Helper for choosing the default value from a set of members. * @@ -504,26 +483,20 @@ function Pattern(env, ptn) { continue; } - const part = Type(env, elem); + const part = Type(env, elem).valueOf(ctx); if (ctx._useIsolating) { result.push(FSI); } - if (Array.isArray(part)) { - const len = PlaceableLength(env, part); - - if (len > MAX_PLACEABLE_LENGTH) { - errors.push( - new RangeError( - 'Too many characters in placeable ' + - `(${len}, max allowed is ${MAX_PLACEABLE_LENGTH})` - ) - ); - result.push(new FluentNone()); - } else { - result.push(...part); - } + if (part.length > MAX_PLACEABLE_LENGTH) { + errors.push( + new RangeError( + 'Too many characters in placeable ' + + `(${part.length}, max allowed is ${MAX_PLACEABLE_LENGTH})` + ) + ); + result.push(part.slice(MAX_PLACEABLE_LENGTH)); } else { result.push(part); } @@ -534,13 +507,11 @@ function Pattern(env, ptn) { } dirty.delete(ptn); - return result; + return result.join(''); } /** - * Format a translation into an `FluentType`. - * - * The return value must be unwrapped via `valueOf` by the caller. + * Format a translation into a string. * * @param {MessageContext} ctx * A MessageContext instance which will be used to resolve the @@ -558,5 +529,5 @@ export default function resolve(ctx, args, message, errors = []) { const env = { ctx, args, errors, dirty: new WeakSet() }; - return Type(env, message); + return Type(env, message).valueOf(ctx); } diff --git a/fluent/test/arguments_test.js b/fluent/test/arguments_test.js index d939e39d9..7da4ea37e 100644 --- a/fluent/test/arguments_test.js +++ b/fluent/test/arguments_test.js @@ -217,9 +217,8 @@ suite('External arguments', function() { let argval, args; class CustomType extends FluentType { - // This Type doesn't valueOf to a string. valueOf() { - return this.value; + return 'CUSTOM'; } } @@ -229,44 +228,25 @@ suite('External arguments', function() { foo = { $arg } bar = { foo } `); - // The argument value is an arbitrary object. - argval = new Object(); args = { // CustomType is a wrapper around the value - arg: new CustomType(argval) + arg: new CustomType() }; - }); - - test('interpolation', function(){ - const msg = ctx.getMessage('foo'); - const parts = ctx.formatToParts(msg, args, errs); - assert.equal(errs.length, 0); + test('interpolation', function () { + const msg = ctx.getMessage('foo'); + const value = ctx.format(msg, args, errs); + assert.equal(value, 'CUSTOM'); + assert.equal(errs.length, 0); + }); - const [part] = parts; - assert.equal(part, args.arg); - assert.equal(part.$$typeof, Symbol.for('FluentType')); - assert.equal(FluentType.isTypeOf(part), true); - - const vals = parts.map(part => part.valueOf(ctx)); - assert.deepEqual(vals, [argval]); - }); - - test('nested interpolation', function(){ - const msg = ctx.getMessage('bar'); - - const parts = ctx.formatToParts(msg, args, errs); - assert.equal(errs.length, 0); - - const [part] = parts; - assert.equal(part, args.arg); - assert.equal(part.$$typeof, Symbol.for('FluentType')); - assert.equal(FluentType.isTypeOf(part), true); - - const vals = parts.map(part => part.valueOf(ctx)); - assert.deepEqual(vals, [argval]); + test('nested interpolation', function () { + const msg = ctx.getMessage('bar'); + const value = ctx.format(msg, args, errs); + assert.equal(value, 'CUSTOM'); + assert.equal(errs.length, 0); + }); }); }); - }); diff --git a/fluent/test/format_parts_test.js b/fluent/test/format_parts_test.js deleted file mode 100644 index 92bd5d7cd..000000000 --- a/fluent/test/format_parts_test.js +++ /dev/null @@ -1,154 +0,0 @@ -'use strict'; - -import assert from 'assert'; - -import { MessageContext } from '../src/context'; -import { FluentNone, FluentNumber } from '../src/types'; -import { ftl } from './util'; - -function partsEqual(actual, expected) { - if (actual === null && expected === null) { - return true; - } - - if (!Array.isArray(actual) || !Array.isArray(expected)) { - return false; - } - - return actual.every((actu, i) => { - const expt = expected[i]; - const sameType = - typeof actu === typeof expt === 'string' || - actu.constructor === expt.constructor; - const sameValue = actu.value === expt.value; - return sameType && sameValue; - }); -} - -function assert_partsEqual(actual, expected) { - if (!partsEqual(actual, expected)) { - assert.fail(actual, expected, null, 'partsEqual'); - } -} - -suite('formatToParts', function(){ - let ctx, args, errs; - - setup(function() { - errs = []; - }); - - suite('Simple value', function(){ - suiteSetup(function() { - ctx = new MessageContext('en-US', { useIsolating: false }); - ctx.addMessages(ftl` - foo = Foo - `); - }); - - test('returns the parts', function(){ - const msg = ctx.getMessage('foo'); - const val = ctx.formatToParts(msg, args, errs); - assert_partsEqual(val, ['Foo']); - assert.equal(errs.length, 0); - }); - }); - - suite('Complex value', function(){ - suiteSetup(function() { - ctx = new MessageContext('en-US', { useIsolating: false }); - ctx.addMessages(ftl` - foo = Foo - bar = { foo } Bar - baz = { missing } - qux = { 1 } - `); - }); - - test('returns the parts', function(){ - const msg = ctx.getMessage('bar'); - const val = ctx.formatToParts(msg, args, errs); - assert_partsEqual(val, ['Foo', ' Bar']); - assert.equal(errs.length, 0); - }); - - test('returns FluentNone', function(){ - const msg = ctx.getMessage('baz'); - const val = ctx.formatToParts(msg, args, errs); - assert_partsEqual(val, [new FluentNone('missing')]); - assert.ok(errs[0] instanceof ReferenceError); // unknown message - }); - - test('returns FluentNumber', function(){ - const msg = ctx.getMessage('qux'); - const val = ctx.formatToParts(msg, args, errs); - assert_partsEqual(val, [new FluentNumber(1)]); - assert.equal(errs.length, 0); - }); - }); - - suite('Complex value referencing a null message', function(){ - suiteSetup(function() { - ctx = new MessageContext('en-US', { useIsolating: false }); - ctx.addMessages(ftl` - foo - .attr = Foo Attr - bar = { foo } Bar - `); - }); - - test('returns null', function(){ - const msg = ctx.getMessage('foo'); - const val = ctx.formatToParts(msg, args, errs); - assert_partsEqual(val, null); - assert.equal(errs.length, 0); - }); - - test('returns the parts of the attribute', function(){ - const msg = ctx.getMessage('foo'); - const val = ctx.formatToParts(msg.attrs.attr, args, errs); - assert_partsEqual(val, ['Foo Attr']); - assert.equal(errs.length, 0); - }); - - test('returns FluentNone', function(){ - const msg = ctx.getMessage('bar'); - const val = ctx.formatToParts(msg, args, errs); - assert_partsEqual(val, [new FluentNone(), ' Bar']); - assert.ok(errs[0] instanceof RangeError); // no default - }); - }); - - suite('Nested complex values', function(){ - suiteSetup(function() { - ctx = new MessageContext('en-US', { useIsolating: false }); - ctx.addMessages(ftl` - foo = Foo { 1 } - bar = { foo } Bar - baz = { bar } Baz - `); - }); - - test('returns parts of foo', function(){ - const msg = ctx.getMessage('foo'); - const val = ctx.formatToParts(msg, args, errs); - assert_partsEqual(val, ['Foo', new FluentNumber(1)]); - assert.equal(errs.length, 0); - }); - - test('returns flattened parts of bar', function(){ - const msg = ctx.getMessage('bar'); - const val = ctx.formatToParts(msg, args, errs); - assert_partsEqual(val, ['Foo', new FluentNumber(1), 'Bar']); - assert.equal(errs.length, 0); - }); - - test('returns flattened parts of baz', function(){ - const msg = ctx.getMessage('baz'); - const val = ctx.formatToParts(msg, args, errs); - assert_partsEqual(val, ['Foo', new FluentNumber(1), ' Bar', ' Baz']); - assert.equal(errs.length, 0); - }); - - }); -}); diff --git a/fluent/test/message_context_stub.js b/fluent/test/message_context_stub.js index 35e6a8b1b..3b1097c7f 100644 --- a/fluent/test/message_context_stub.js +++ b/fluent/test/message_context_stub.js @@ -16,8 +16,4 @@ export default class MessageContext { format(msg) { return msg; } - - formatToParts(msg) { - return [msg]; - } }