Skip to content

Commit 661ecc6

Browse files
authored
Merge pull request #189 from unicode-org/third-formatToParts
(third) Implement formatToParts
2 parents 0499c47 + fc963f6 commit 661ecc6

File tree

10 files changed

+236
-46
lines changed

10 files changed

+236
-46
lines changed

experiments/stasm/third/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Run each example individually:
7373
npm run example phrases
7474
npm run example list
7575
npm run example number
76+
npm run example opaque
7677

7778
## References
7879

experiments/stasm/third/example/example_glossary.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Argument, Message, Parameter} from "../impl/model.js";
22
import {REGISTRY} from "../impl/registry.js";
3-
import {formatMessage, FormattingContext, StringValue} from "../impl/runtime.js";
3+
import {formatMessage, FormattingContext, formatToParts, StringValue} from "../impl/runtime.js";
44
import {get_term} from "./glossary.js";
55

66
REGISTRY["NOUN"] = function get_noun(
@@ -140,6 +140,15 @@ console.log("==== English ====");
140140
color: new StringValue("red"),
141141
})
142142
);
143+
144+
console.log(
145+
Array.of(
146+
...formatToParts(message, {
147+
item: new StringValue("t-shirt"),
148+
color: new StringValue("red"),
149+
})
150+
)
151+
);
143152
}
144153

145154
{

experiments/stasm/third/example/example_list.ts

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import {Argument, Message, Parameter} from "../impl/model.js";
22
import {REGISTRY} from "../impl/registry.js";
33
import {
44
formatMessage,
5+
FormattedPart,
56
FormattingContext,
7+
formatToParts,
68
PluralValue,
79
RuntimeValue,
810
StringValue,
@@ -18,9 +20,24 @@ class Person {
1820
}
1921
}
2022

21-
class PeopleValue extends RuntimeValue<Array<Person>> {
22-
format(ctx: FormattingContext): string {
23-
throw new RangeError("Must be formatted via PEOPLE_LIST.");
23+
// TODO(stasm): This is generic enough that it could be in impl/runtime.ts.
24+
class ListValue<T> extends RuntimeValue<Array<T>> {
25+
private opts: Intl.ListFormatOptions;
26+
27+
constructor(value: Array<T>, opts: Intl.ListFormatOptions = {}) {
28+
super(value);
29+
this.opts = opts;
30+
}
31+
32+
formatToString(ctx: FormattingContext): string {
33+
// TODO(stasm): Cache ListFormat.
34+
let lf = new Intl.ListFormat(ctx.locale, this.opts);
35+
return lf.format(this.value);
36+
}
37+
38+
*formatToParts(ctx: FormattingContext): IterableIterator<FormattedPart> {
39+
let lf = new Intl.ListFormat(ctx.locale, this.opts);
40+
yield* lf.formatToParts(this.value);
2441
}
2542
}
2643

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

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

4966
let elements = ctx.toRuntimeValue(args[0]);
50-
if (!(elements instanceof PeopleValue)) {
67+
if (!(elements instanceof ListValue)) {
5168
throw new TypeError();
5269
}
5370

@@ -70,14 +87,21 @@ REGISTRY["PEOPLE_LIST"] = function (
7087
break;
7188
}
7289

73-
// @ts-ignore
74-
let lf = new Intl.ListFormat(ctx.locale, {
75-
// TODO(stasm): Type-check these.
90+
let list_style = ctx.toRuntimeValue(opts["STYLE"]);
91+
if (!(list_style instanceof StringValue)) {
92+
throw new TypeError();
93+
}
94+
95+
let list_type = ctx.toRuntimeValue(opts["TYPE"]);
96+
if (!(list_type instanceof StringValue)) {
97+
throw new TypeError();
98+
}
99+
100+
return new ListValue(names, {
76101
// TODO(stasm): Add default options.
77-
style: ctx.toRuntimeValue(opts["STYLE"]).value,
78-
type: ctx.toRuntimeValue(opts["TYPE"]).value,
102+
style: list_style.value,
103+
type: list_type.value,
79104
});
80-
return new StringValue(lf.format(names));
81105

82106
function decline(name: string): string {
83107
let declension = ctx.toRuntimeValue(opts["CASE"]);
@@ -166,13 +190,24 @@ console.log("==== Romanian ====");
166190
};
167191
console.log(
168192
formatMessage(message, {
169-
names: new PeopleValue([
193+
names: new ListValue([
170194
new Person("Maria", "Stanescu"),
171195
new Person("Ileana", "Zamfir"),
172196
new Person("Petre", "Belu"),
173197
]),
174198
})
175199
);
200+
console.log(
201+
Array.of(
202+
...formatToParts(message, {
203+
names: new ListValue([
204+
new Person("Maria", "Stanescu"),
205+
new Person("Ileana", "Zamfir"),
206+
new Person("Petre", "Belu"),
207+
]),
208+
})
209+
)
210+
);
176211
}
177212

178213
{
@@ -235,7 +270,7 @@ console.log("==== Romanian ====");
235270
};
236271
console.log(
237272
formatMessage(message, {
238-
names: new PeopleValue([
273+
names: new ListValue([
239274
new Person("Maria", "Stanescu"),
240275
new Person("Ileana", "Zamfir"),
241276
new Person("Petre", "Belu"),

experiments/stasm/third/example/example_number.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Message} from "../impl/model.js";
2-
import {formatMessage, NumberValue} from "../impl/runtime.js";
2+
import {formatMessage, formatToParts, NumberValue} from "../impl/runtime.js";
33

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

@@ -78,4 +78,11 @@ console.log("==== French ====");
7878
payloadSize: new NumberValue(1.23),
7979
})
8080
);
81+
console.log(
82+
Array.of(
83+
...formatToParts(message, {
84+
payloadSize: new NumberValue(1.23),
85+
})
86+
)
87+
);
8188
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {Message} from "../impl/model.js";
2+
import {FormattingContext, formatToParts, OpaquePart, RuntimeValue} from "../impl/runtime.js";
3+
4+
// We want to pass it into the translation and get it back out unformatted, in
5+
// the correct position in the sentence, via formatToParts.
6+
class SomeUnstringifiableClass {}
7+
8+
// TODO(stasm): This is generic enough that it could be in impl/runtime.ts.
9+
class WrappedValue extends RuntimeValue<SomeUnstringifiableClass> {
10+
formatToString(ctx: FormattingContext): string {
11+
throw new Error("Method not implemented.");
12+
}
13+
*formatToParts(ctx: FormattingContext): IterableIterator<OpaquePart> {
14+
yield {type: "opaque", value: this.value};
15+
}
16+
}
17+
18+
console.log("==== English ====");
19+
20+
{
21+
// "Ready? Then {$submitButton}!"
22+
let message: Message = {
23+
lang: "en",
24+
id: "submit",
25+
phrases: {},
26+
selectors: [
27+
{
28+
expr: null,
29+
default: {type: "StringLiteral", value: "default"},
30+
},
31+
],
32+
variants: [
33+
{
34+
keys: [{type: "StringLiteral", value: "default"}],
35+
value: [
36+
{type: "StringLiteral", value: "Ready? Then "},
37+
{
38+
type: "VariableReference",
39+
name: "submitButton",
40+
},
41+
{type: "StringLiteral", value: "!"},
42+
],
43+
},
44+
],
45+
};
46+
console.log(
47+
Array.of(
48+
...formatToParts(message, {
49+
submitButton: new WrappedValue(new SomeUnstringifiableClass()),
50+
})
51+
)
52+
);
53+
}

experiments/stasm/third/example/example_phrases.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Message} from "../impl/model.js";
2-
import {formatMessage, NumberValue, StringValue} from "../impl/runtime.js";
2+
import {formatMessage, formatToParts, NumberValue, StringValue} from "../impl/runtime.js";
33

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

@@ -90,6 +90,16 @@ console.log("==== English ====");
9090
photoCount: new NumberValue(34),
9191
})
9292
);
93+
94+
console.log(
95+
Array.of(
96+
...formatToParts(message, {
97+
userName: new StringValue("Mary"),
98+
userGender: new StringValue("feminine"),
99+
photoCount: new NumberValue(34),
100+
})
101+
)
102+
);
93103
}
94104

95105
console.log("==== polski ====");
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
declare namespace Intl {
2+
interface ListFormatOptions {
3+
// I added `string` to avoid having to validate the exact values of options.
4+
localeMatcher?: string | "best fit" | "lookup";
5+
type?: string | "conjunction" | "disjunction | unit";
6+
style?: string | "long" | "short" | "narrow";
7+
}
8+
9+
type ListFormatPartTypes = "literal" | "element";
10+
11+
interface ListFormatPart {
12+
type: ListFormatPartTypes;
13+
value: string;
14+
}
15+
16+
interface ListFormat {
17+
format(value?: Array<unknown>): string;
18+
formatToParts(value?: Array<unknown>): ListFormatPart[];
19+
}
20+
21+
var ListFormat: {
22+
new (locales?: string | string[], options?: ListFormatOptions): ListFormat;
23+
(locales?: string | string[], options?: ListFormatOptions): ListFormat;
24+
};
25+
}

experiments/stasm/third/impl/registry.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import {Argument, Parameter} from "./model.js";
2-
import {FormattingContext, NumberValue, PluralValue, RuntimeValue, StringValue} from "./runtime.js";
2+
import {
3+
FormattingContext,
4+
NumberValue,
5+
PatternValue,
6+
PluralValue,
7+
RuntimeValue,
8+
StringValue,
9+
} from "./runtime.js";
310

411
export type RegistryFunc<T> = (
512
ctx: FormattingContext,
@@ -31,15 +38,15 @@ function get_phrase(
3138
ctx: FormattingContext,
3239
args: Array<Argument>,
3340
opts: Record<string, Parameter>
34-
): StringValue {
41+
): PatternValue {
3542
let phrase_name = ctx.toRuntimeValue(args[0]);
3643
if (!(phrase_name instanceof StringValue)) {
3744
throw new TypeError();
3845
}
3946

4047
let phrase = ctx.message.phrases[phrase_name.value];
4148
let variant = ctx.selectVariant(phrase.variants, phrase.selectors);
42-
return new StringValue(ctx.formatPattern(variant.value));
49+
return new PatternValue(variant.value);
4350
}
4451

4552
function format_number(

0 commit comments

Comments
 (0)