Skip to content

Commit df50edd

Browse files
Merge pull request #429 from messageformat/mf2-updates
2 parents 79ac455 + f690548 commit df50edd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+814
-1068
lines changed

packages/mf2-fluent/src/fluent-to-message.ts

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import * as Fluent from '@fluent/syntax';
22
import deepEqual from 'fast-deep-equal';
33
import {
44
Expression,
5-
FunctionAnnotation,
5+
FunctionRef,
6+
InputDeclaration,
67
Literal,
8+
LocalDeclaration,
79
PatternMessage,
810
SelectMessage,
911
VariableRef,
@@ -49,16 +51,21 @@ function findSelectArgs(pattern: Fluent.Pattern): SelectArg[] {
4951
return args;
5052
}
5153

52-
function asSelectExpression(
54+
function asSelectorDeclaration(
5355
{ selector, defaultName, keys }: SelectArg,
56+
index: number,
5457
detectNumberSelection: boolean = true
55-
): Expression {
58+
): InputDeclaration | LocalDeclaration {
5659
switch (selector.type) {
5760
case 'StringLiteral':
5861
return {
59-
type: 'expression',
60-
arg: asValue(selector),
61-
annotation: { type: 'function', name: 'string' }
62+
type: 'local',
63+
name: `_${index}`,
64+
value: {
65+
type: 'expression',
66+
arg: asValue(selector),
67+
functionRef: { type: 'function', name: 'string' }
68+
}
6269
};
6370
case 'VariableReference': {
6471
let name = detectNumberSelection ? 'number' : 'string';
@@ -74,15 +81,28 @@ function asSelectExpression(
7481
}
7582
}
7683
return {
77-
type: 'expression',
78-
arg: asValue(selector),
79-
annotation: { type: 'function', name }
84+
type: 'input',
85+
name: selector.id.name,
86+
value: {
87+
type: 'expression',
88+
arg: asValue(selector),
89+
functionRef: { type: 'function', name }
90+
}
8091
};
8192
}
8293
}
83-
return asExpression(selector);
94+
const exp = asExpression(selector);
95+
return exp.arg?.type === 'variable'
96+
? {
97+
type: 'input',
98+
name: exp.arg.name,
99+
value: exp as Expression<VariableRef>
100+
}
101+
: { type: 'local', name: `_${index}`, value: exp };
84102
}
85103

104+
function asValue(exp: Fluent.VariableReference): VariableRef;
105+
function asValue(exp: Fluent.InlineExpression): Literal | VariableRef;
86106
function asValue(exp: Fluent.InlineExpression): Literal | VariableRef {
87107
switch (exp.type) {
88108
case 'NumberLiteral':
@@ -102,14 +122,14 @@ function asExpression(exp: Fluent.Expression): Expression {
102122
return {
103123
type: 'expression',
104124
arg: asValue(exp),
105-
annotation: { type: 'function', name: 'number' }
125+
functionRef: { type: 'function', name: 'number' }
106126
};
107127
case 'StringLiteral':
108128
case 'VariableReference': {
109129
return { type: 'expression', arg: asValue(exp) };
110130
}
111131
case 'FunctionReference': {
112-
const annotation: FunctionAnnotation = {
132+
const annotation: FunctionRef = {
113133
type: 'function',
114134
name: exp.id.name.toLowerCase()
115135
};
@@ -130,8 +150,8 @@ function asExpression(exp: Fluent.Expression): Expression {
130150
}
131151
}
132152
return args.length > 0
133-
? { type: 'expression', arg: args[0], annotation }
134-
: { type: 'expression', annotation };
153+
? { type: 'expression', arg: args[0], functionRef: annotation }
154+
: { type: 'expression', functionRef: annotation };
135155
}
136156
case 'MessageReference': {
137157
const msgId = exp.attribute
@@ -140,13 +160,13 @@ function asExpression(exp: Fluent.Expression): Expression {
140160
return {
141161
type: 'expression',
142162
arg: { type: 'literal', value: msgId },
143-
annotation: { type: 'function', name: 'message' }
163+
functionRef: { type: 'function', name: 'fluent:message' }
144164
};
145165
}
146166
case 'TermReference': {
147-
const annotation: FunctionAnnotation = {
167+
const annotation: FunctionRef = {
148168
type: 'function',
149-
name: 'message'
169+
name: 'fluent:message'
150170
};
151171
const msgId = exp.attribute
152172
? `-${exp.id.name}.${exp.attribute.name}`
@@ -165,7 +185,7 @@ function asExpression(exp: Fluent.Expression): Expression {
165185
return {
166186
type: 'expression',
167187
arg: { type: 'literal', value: msgId },
168-
annotation
188+
functionRef: annotation
169189
};
170190
}
171191

@@ -248,7 +268,7 @@ export function fluentToMessage(
248268
keys: key.map((k, i) =>
249269
k === CATCHALL
250270
? { type: '*', value: args[i].defaultName }
251-
: { type: 'literal', quoted: false, value: String(k) }
271+
: { type: 'literal', value: String(k) }
252272
),
253273
value: []
254274
}));
@@ -299,10 +319,16 @@ export function fluentToMessage(
299319
}
300320
addParts(ast, []);
301321

322+
const declarations = args.map((arg, index) =>
323+
asSelectorDeclaration(arg, index, detectNumberSelection)
324+
);
302325
return {
303326
type: 'select',
304-
declarations: [],
305-
selectors: args.map(arg => asSelectExpression(arg, detectNumberSelection)),
327+
declarations,
328+
selectors: declarations.map(decl => ({
329+
type: 'variable',
330+
name: decl.name
331+
})),
306332
variants
307333
};
308334
}

packages/mf2-fluent/src/fluent.test.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -715,24 +715,24 @@ describe('fluentToResourceData', () => {
715715
const msg = data.get('multi')?.get('') as SelectMessage;
716716
expect(msg.variants.map(v => v.keys)).toMatchObject([
717717
[
718-
{ type: 'literal', quoted: false, value: '0' },
719-
{ type: 'literal', quoted: false, value: 'feminine' }
718+
{ type: 'literal', value: '0' },
719+
{ type: 'literal', value: 'feminine' }
720720
],
721721
[
722-
{ type: 'literal', quoted: false, value: '0' },
723-
{ type: 'literal', quoted: false, value: 'masculine' }
722+
{ type: 'literal', value: '0' },
723+
{ type: 'literal', value: 'masculine' }
724724
],
725725
[
726-
{ type: 'literal', quoted: false, value: '0' },
726+
{ type: 'literal', value: '0' },
727727
{ type: '*', value: 'neuter' }
728728
],
729729
[
730730
{ type: '*', value: 'other' },
731-
{ type: 'literal', quoted: false, value: 'feminine' }
731+
{ type: 'literal', value: 'feminine' }
732732
],
733733
[
734734
{ type: '*', value: 'other' },
735-
{ type: 'literal', quoted: false, value: 'masculine' }
735+
{ type: 'literal', value: 'masculine' }
736736
],
737737
[
738738
{ type: '*', value: 'other' },
@@ -779,13 +779,11 @@ describe('messagetoFluent', () => {
779779
value: {
780780
type: 'expression',
781781
arg: { type: 'variable', name: 'num' },
782-
annotation: { type: 'function', name: 'number' }
782+
functionRef: { type: 'function', name: 'number' }
783783
}
784784
}
785785
],
786-
selectors: [
787-
{ type: 'expression', arg: { type: 'variable', name: 'local' } }
788-
],
786+
selectors: [{ type: 'variable', name: 'local' }],
789787
variants: [
790788
{
791789
keys: [{ type: '*' }],
@@ -849,12 +847,12 @@ describe('messagetoFluent', () => {
849847
{
850848
type: 'expression',
851849
arg: { type: 'literal', value: 'msg' },
852-
annotation: { type: 'function', name: 'message' }
850+
functionRef: { type: 'function', name: 'fluent:message' }
853851
},
854852
{
855853
type: 'expression',
856854
arg: { type: 'variable', name: 'local' },
857-
annotation: { type: 'function', name: 'message' }
855+
functionRef: { type: 'function', name: 'fluent:message' }
858856
}
859857
]
860858
};
@@ -891,7 +889,7 @@ describe('messagetoFluent', () => {
891889
{
892890
type: 'expression',
893891
arg: { type: 'variable', name: 'input' },
894-
annotation: { type: 'function', name: 'message' }
892+
functionRef: { type: 'function', name: 'fluent:message' }
895893
}
896894
]
897895
};

packages/mf2-fluent/src/functions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,5 @@ export function getFluentFunctions(res: FluentMessageResource) {
4848
}
4949
Object.freeze(message);
5050

51-
return { message };
51+
return { 'fluent:message': message };
5252
}

packages/mf2-fluent/src/message-to-fluent.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
CatchallKey,
44
Declaration,
55
Expression,
6-
FunctionAnnotation,
6+
FunctionRef,
77
Literal,
88
Message,
99
Pattern,
@@ -34,7 +34,7 @@ export type FunctionMap = Record<string, string | symbol | null>;
3434
*/
3535
export const defaultFunctionMap: FunctionMap = {
3636
datetime: 'DATETIME',
37-
message: FluentMessageRef,
37+
'fluent:message': FluentMessageRef,
3838
number: 'NUMBER',
3939
plural: 'NUMBER',
4040
string: null
@@ -77,7 +77,7 @@ export function messageToFluent(
7777
}));
7878
const k0 = variants[0].keys;
7979
while (k0.length > 0) {
80-
const sel = expressionToFluent(ctx, msg.selectors[k0.length - 1]);
80+
const sel = variableRefToFluent(ctx, msg.selectors[k0.length - 1]);
8181
let baseKeys: (Literal | CatchallKey)[] = [];
8282
let exp: Fluent.SelectExpression | undefined;
8383
for (let i = 0; i < variants.length; ++i) {
@@ -161,7 +161,7 @@ function patternToFluent(ctx: MsgContext, pattern: Pattern) {
161161
function functionRefToFluent(
162162
ctx: MsgContext,
163163
arg: Fluent.InlineExpression | null,
164-
{ name, options }: FunctionAnnotation
164+
{ name, options }: FunctionRef
165165
): Fluent.InlineExpression {
166166
const args = new Fluent.CallArguments();
167167
if (arg) args.positional[0] = arg;
@@ -236,18 +236,10 @@ function literalToFluent({ value }: Literal) {
236236

237237
function expressionToFluent(
238238
ctx: MsgContext,
239-
{ arg, annotation }: Expression
239+
{ arg, functionRef }: Expression
240240
): Fluent.InlineExpression {
241241
const fluentArg = arg ? valueToFluent(ctx, arg) : null;
242-
if (annotation) {
243-
if (annotation.type === 'function') {
244-
return functionRefToFluent(ctx, fluentArg, annotation);
245-
} else {
246-
throw new Error(
247-
`Conversion of ${annotation.type} annotation to Fluent is not supported`
248-
);
249-
}
250-
}
242+
if (functionRef) return functionRefToFluent(ctx, fluentArg, functionRef);
251243
if (fluentArg) return fluentArg;
252244
throw new Error('Invalid empty expression');
253245
}
@@ -273,7 +265,12 @@ function variableRefToFluent(
273265
{ name }: VariableRef
274266
): Fluent.InlineExpression {
275267
const local = ctx.declarations.find(decl => decl.name === name);
276-
return local?.value
277-
? expressionToFluent(ctx, local.value)
278-
: new Fluent.VariableReference(new Fluent.Identifier(name));
268+
if (local?.value) {
269+
const idx = ctx.declarations.indexOf(local);
270+
return expressionToFluent(
271+
{ ...ctx, declarations: ctx.declarations.slice(0, idx) },
272+
local.value
273+
);
274+
}
275+
return new Fluent.VariableReference(new Fluent.Identifier(name));
279276
}

packages/mf2-icu-mf1/src/functions.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,9 @@ function number(
180180
*
181181
* @beta
182182
*/
183-
export const getMF1Functions = () => ({ date, duration, number, time });
183+
export const getMF1Functions = () => ({
184+
'mf1:date': date,
185+
'mf1:duration': duration,
186+
'mf1:number': number,
187+
'mf1:time': time
188+
});

0 commit comments

Comments
 (0)