Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(third) Implement formatToParts #189

Merged
merged 5 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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