Skip to content

Commit

Permalink
Merge pull request #189 from unicode-org/third-formatToParts
Browse files Browse the repository at this point in the history
(third) Implement formatToParts
  • Loading branch information
stasm authored Aug 18, 2021
2 parents 0499c47 + fc963f6 commit 661ecc6
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 46 deletions.
1 change: 1 addition & 0 deletions experiments/stasm/third/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Run each example individually:
npm run example phrases
npm run example list
npm run example number
npm run example opaque

## References

Expand Down
11 changes: 10 additions & 1 deletion experiments/stasm/third/example/example_glossary.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Argument, Message, Parameter} from "../impl/model.js";
import {REGISTRY} from "../impl/registry.js";
import {formatMessage, FormattingContext, StringValue} from "../impl/runtime.js";
import {formatMessage, FormattingContext, formatToParts, StringValue} from "../impl/runtime.js";
import {get_term} from "./glossary.js";

REGISTRY["NOUN"] = function get_noun(
Expand Down Expand Up @@ -140,6 +140,15 @@ console.log("==== English ====");
color: new StringValue("red"),
})
);

console.log(
Array.of(
...formatToParts(message, {
item: new StringValue("t-shirt"),
color: new StringValue("red"),
})
)
);
}

{
Expand Down
63 changes: 49 additions & 14 deletions experiments/stasm/third/example/example_list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import {Argument, Message, Parameter} from "../impl/model.js";
import {REGISTRY} from "../impl/registry.js";
import {
formatMessage,
FormattedPart,
FormattingContext,
formatToParts,
PluralValue,
RuntimeValue,
StringValue,
Expand All @@ -18,9 +20,24 @@ class Person {
}
}

class PeopleValue extends RuntimeValue<Array<Person>> {
format(ctx: FormattingContext): string {
throw new RangeError("Must be formatted via PEOPLE_LIST.");
// TODO(stasm): This is generic enough that it could be in impl/runtime.ts.
class ListValue<T> extends RuntimeValue<Array<T>> {
private opts: Intl.ListFormatOptions;

constructor(value: Array<T>, opts: Intl.ListFormatOptions = {}) {
super(value);
this.opts = opts;
}

formatToString(ctx: FormattingContext): string {
// TODO(stasm): Cache ListFormat.
let lf = new Intl.ListFormat(ctx.locale, this.opts);
return lf.format(this.value);
}

*formatToParts(ctx: FormattingContext): IterableIterator<FormattedPart> {
let lf = new Intl.ListFormat(ctx.locale, this.opts);
yield* lf.formatToParts(this.value);
}
}

Expand All @@ -30,7 +47,7 @@ REGISTRY["PLURAL_LEN"] = function (
opts: Record<string, Parameter>
): PluralValue {
let elements = ctx.toRuntimeValue(args[0]);
if (!(elements instanceof PeopleValue)) {
if (!(elements instanceof ListValue)) {
throw new TypeError();
}

Expand All @@ -41,13 +58,13 @@ REGISTRY["PEOPLE_LIST"] = function (
ctx: FormattingContext,
args: Array<Argument>,
opts: Record<string, Parameter>
): StringValue {
): ListValue<string> {
if (ctx.locale !== "ro") {
throw new Error("Only Romanian supported");
}

let elements = ctx.toRuntimeValue(args[0]);
if (!(elements instanceof PeopleValue)) {
if (!(elements instanceof ListValue)) {
throw new TypeError();
}

Expand All @@ -70,14 +87,21 @@ REGISTRY["PEOPLE_LIST"] = function (
break;
}

// @ts-ignore
let lf = new Intl.ListFormat(ctx.locale, {
// TODO(stasm): Type-check these.
let list_style = ctx.toRuntimeValue(opts["STYLE"]);
if (!(list_style instanceof StringValue)) {
throw new TypeError();
}

let list_type = ctx.toRuntimeValue(opts["TYPE"]);
if (!(list_type instanceof StringValue)) {
throw new TypeError();
}

return new ListValue(names, {
// TODO(stasm): Add default options.
style: ctx.toRuntimeValue(opts["STYLE"]).value,
type: ctx.toRuntimeValue(opts["TYPE"]).value,
style: list_style.value,
type: list_type.value,
});
return new StringValue(lf.format(names));

function decline(name: string): string {
let declension = ctx.toRuntimeValue(opts["CASE"]);
Expand Down Expand Up @@ -166,13 +190,24 @@ console.log("==== Romanian ====");
};
console.log(
formatMessage(message, {
names: new PeopleValue([
names: new ListValue([
new Person("Maria", "Stanescu"),
new Person("Ileana", "Zamfir"),
new Person("Petre", "Belu"),
]),
})
);
console.log(
Array.of(
...formatToParts(message, {
names: new ListValue([
new Person("Maria", "Stanescu"),
new Person("Ileana", "Zamfir"),
new Person("Petre", "Belu"),
]),
})
)
);
}

{
Expand Down Expand Up @@ -235,7 +270,7 @@ console.log("==== Romanian ====");
};
console.log(
formatMessage(message, {
names: new PeopleValue([
names: new ListValue([
new Person("Maria", "Stanescu"),
new Person("Ileana", "Zamfir"),
new Person("Petre", "Belu"),
Expand Down
9 changes: 8 additions & 1 deletion experiments/stasm/third/example/example_number.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Message} from "../impl/model.js";
import {formatMessage, NumberValue} from "../impl/runtime.js";
import {formatMessage, formatToParts, NumberValue} from "../impl/runtime.js";

console.log("==== English ====");

Expand Down Expand Up @@ -78,4 +78,11 @@ console.log("==== French ====");
payloadSize: new NumberValue(1.23),
})
);
console.log(
Array.of(
...formatToParts(message, {
payloadSize: new NumberValue(1.23),
})
)
);
}
53 changes: 53 additions & 0 deletions experiments/stasm/third/example/example_opaque.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {Message} from "../impl/model.js";
import {FormattingContext, formatToParts, OpaquePart, RuntimeValue} from "../impl/runtime.js";

// We want to pass it into the translation and get it back out unformatted, in
// the correct position in the sentence, via formatToParts.
class SomeUnstringifiableClass {}

// TODO(stasm): This is generic enough that it could be in impl/runtime.ts.
class WrappedValue extends RuntimeValue<SomeUnstringifiableClass> {
formatToString(ctx: FormattingContext): string {
throw new Error("Method not implemented.");
}
*formatToParts(ctx: FormattingContext): IterableIterator<OpaquePart> {
yield {type: "opaque", value: this.value};
}
}

console.log("==== English ====");

{
// "Ready? Then {$submitButton}!"
let message: Message = {
lang: "en",
id: "submit",
phrases: {},
selectors: [
{
expr: null,
default: {type: "StringLiteral", value: "default"},
},
],
variants: [
{
keys: [{type: "StringLiteral", value: "default"}],
value: [
{type: "StringLiteral", value: "Ready? Then "},
{
type: "VariableReference",
name: "submitButton",
},
{type: "StringLiteral", value: "!"},
],
},
],
};
console.log(
Array.of(
...formatToParts(message, {
submitButton: new WrappedValue(new SomeUnstringifiableClass()),
})
)
);
}
12 changes: 11 additions & 1 deletion experiments/stasm/third/example/example_phrases.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Message} from "../impl/model.js";
import {formatMessage, NumberValue, StringValue} from "../impl/runtime.js";
import {formatMessage, formatToParts, NumberValue, StringValue} from "../impl/runtime.js";

console.log("==== English ====");

Expand Down Expand Up @@ -90,6 +90,16 @@ console.log("==== English ====");
photoCount: new NumberValue(34),
})
);

console.log(
Array.of(
...formatToParts(message, {
userName: new StringValue("Mary"),
userGender: new StringValue("feminine"),
photoCount: new NumberValue(34),
})
)
);
}

console.log("==== polski ====");
Expand Down
25 changes: 25 additions & 0 deletions experiments/stasm/third/impl/intl.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
declare namespace Intl {
interface ListFormatOptions {
// I added `string` to avoid having to validate the exact values of options.
localeMatcher?: string | "best fit" | "lookup";
type?: string | "conjunction" | "disjunction | unit";
style?: string | "long" | "short" | "narrow";
}

type ListFormatPartTypes = "literal" | "element";

interface ListFormatPart {
type: ListFormatPartTypes;
value: string;
}

interface ListFormat {
format(value?: Array<unknown>): string;
formatToParts(value?: Array<unknown>): ListFormatPart[];
}

var ListFormat: {
new (locales?: string | string[], options?: ListFormatOptions): ListFormat;
(locales?: string | string[], options?: ListFormatOptions): ListFormat;
};
}
13 changes: 10 additions & 3 deletions experiments/stasm/third/impl/registry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import {Argument, Parameter} from "./model.js";
import {FormattingContext, NumberValue, PluralValue, RuntimeValue, StringValue} from "./runtime.js";
import {
FormattingContext,
NumberValue,
PatternValue,
PluralValue,
RuntimeValue,
StringValue,
} from "./runtime.js";

export type RegistryFunc<T> = (
ctx: FormattingContext,
Expand Down Expand Up @@ -31,15 +38,15 @@ function get_phrase(
ctx: FormattingContext,
args: Array<Argument>,
opts: Record<string, Parameter>
): StringValue {
): PatternValue {
let phrase_name = ctx.toRuntimeValue(args[0]);
if (!(phrase_name instanceof StringValue)) {
throw new TypeError();
}

let phrase = ctx.message.phrases[phrase_name.value];
let variant = ctx.selectVariant(phrase.variants, phrase.selectors);
return new StringValue(ctx.formatPattern(variant.value));
return new PatternValue(variant.value);
}

function format_number(
Expand Down
Loading

0 comments on commit 661ecc6

Please sign in to comment.