@saykit/transform-js 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/parser.mjs +58 -12
  2. package/package.json +2 -2
package/dist/parser.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, LiteralMessage, validateBranchIdentifier } from "@saykit/config/features/messages";
1
+ import { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, LiteralMessage, isArgumentType, validateArgumentStyle, validateBranchIdentifier } from "@saykit/config/features/messages";
2
2
  import * as t from "@babel/types";
3
3
  //#region src/parser.ts
4
4
  const PLACEHOLDER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -6,20 +6,33 @@ function parseTaggedTemplateExpression(tagged) {
6
6
  const processed = processExpression(tagged.tag);
7
7
  if (!processed) return null;
8
8
  const [accessor, descriptor] = processed;
9
- const children = tagged.quasi.quasis.reduce((c, q, i) => {
10
- c.push(new LiteralMessage(q.value.cooked ?? q.value.raw));
11
- if (t.isExpression(tagged.quasi.expressions[i])) c.push(parseExpression(tagged.quasi.expressions[i], true));
12
- return c;
13
- }, []);
9
+ const children = buildTemplateChildren(tagged.quasi);
14
10
  return new CompositeMessage({
15
11
  id: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "id") : void 0,
16
12
  context: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "context") : void 0
17
13
  }, getTranslatorComments(tagged.leadingComments ?? []), tagged.loc ? [`${tagged.loc.filename}:${tagged.loc.start.line}`] : [], children, accessor);
18
14
  }
15
+ /**
16
+ * The text and the values of a template literal, in the order they were
17
+ * written — the message the template spells out.
18
+ */
19
+ function buildTemplateChildren(template) {
20
+ return template.quasis.reduce((c, q, i) => {
21
+ c.push(new LiteralMessage(q.value.cooked ?? q.value.raw));
22
+ if (t.isExpression(template.expressions[i])) c.push(parseExpression(template.expressions[i], true));
23
+ return c;
24
+ }, []);
25
+ }
19
26
  function parseCallExpression(call) {
20
27
  const processed = processExpression(call.callee);
21
28
  if (!processed) return null;
22
29
  const [accessor, descriptor, kind] = processed;
30
+ const descriptorId = descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "id") : void 0;
31
+ const descriptorContext = descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "context") : void 0;
32
+ const wrap = (children) => new CompositeMessage({
33
+ id: descriptorId,
34
+ context: descriptorContext
35
+ }, [], call.loc ? [`${call.loc.filename}:${call.loc.start.line}`] : [], children, accessor);
23
36
  if (typeof kind === "string" && [
24
37
  "select",
25
38
  "ordinal",
@@ -28,10 +41,14 @@ function parseCallExpression(call) {
28
41
  if (call.arguments.length !== 2) return null;
29
42
  if (!t.isExpression(call.arguments[0])) return null;
30
43
  if (!t.isObjectExpression(call.arguments[1])) return null;
31
- const branches = call.arguments[1].properties.reduce((c, p) => {
44
+ const object = call.arguments[1];
45
+ const offset = kind === "select" ? void 0 : findPluralOffset(object);
46
+ const branches = object.properties.reduce((c, p) => {
32
47
  if (!t.isObjectProperty(p) || !t.isExpression(p.value)) return c;
48
+ if (offset !== void 0 && getPropertyNameAsString(p.key) === "offset") return c;
33
49
  let message = null;
34
50
  if (!message && t.isStringLiteral(p.value)) message = new LiteralMessage(p.value.value);
51
+ if (!message && t.isTemplateLiteral(p.value)) message = new CompositeMessage({}, [], [], buildTemplateChildren(p.value), p.value);
35
52
  if (!message) message = parseExpression(p.value, true);
36
53
  c.push({
37
54
  identifier: getPropertyNameAsString(p.key),
@@ -41,14 +58,43 @@ function parseCallExpression(call) {
41
58
  }, []);
42
59
  for (const branch of branches) validateBranchIdentifier(kind, branch.identifier);
43
60
  const [identifier, value] = unwrapPlaceholder(call.arguments[0]);
44
- const choice = new ChoiceMessage(kind, identifier, branches, value);
45
- return new CompositeMessage({
46
- id: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "id") : void 0,
47
- context: descriptor ? findPropertyValueIfStringLiteralAsString(descriptor, "context") : void 0
48
- }, [], call.loc ? [`${call.loc.filename}:${call.loc.start.line}`] : [], [choice], accessor);
61
+ return wrap([new ChoiceMessage(kind, identifier, branches, value, offset)]);
62
+ }
63
+ if (typeof kind === "string" && isArgumentType(kind)) {
64
+ if (call.arguments.length < 1 || call.arguments.length > 2) return null;
65
+ if (!t.isExpression(call.arguments[0])) return null;
66
+ let style;
67
+ if (call.arguments.length === 2) {
68
+ if (!t.isObjectExpression(call.arguments[1])) return null;
69
+ style = findPropertyValueIfStringLiteralAsString(call.arguments[1], "style");
70
+ if (style !== void 0) validateArgumentStyle(kind, style);
71
+ }
72
+ const [identifier, value] = unwrapPlaceholder(call.arguments[0]);
73
+ return wrap([new ArgumentMessage(identifier, value, {
74
+ type: kind,
75
+ style
76
+ })]);
49
77
  }
50
78
  return null;
51
79
  }
80
+ /**
81
+ * The `offset` a plural subtracts from its selector before `#` is formatted, so
82
+ * "You and 2 others" can select on a total of three.
83
+ *
84
+ * Read only from an integer literal: the offset is baked into the extracted
85
+ * message, so it has to be known at build time, and a fractional or negative
86
+ * one is not something ICU accepts. Anything else stays an ordinary branch and
87
+ * is validated as the key it looks like.
88
+ */
89
+ function findPluralOffset(object) {
90
+ for (const property of object.properties) {
91
+ if (!t.isObjectProperty(property) || property.computed) continue;
92
+ if (getPropertyNameAsString(property.key) !== "offset") continue;
93
+ if (!t.isNumericLiteral(property.value)) continue;
94
+ if (!Number.isInteger(property.value.value) || property.value.value < 0) continue;
95
+ return property.value.value;
96
+ }
97
+ }
52
98
  function processExpression(expression) {
53
99
  if (t.isIdentifier(expression) && expression.name === "say") return [
54
100
  expression,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saykit/transform-js",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "description": "JavaScript and TypeScript transformer for SayKit",
5
5
  "keywords": [
6
6
  "i18n",
@@ -47,7 +47,7 @@
47
47
  "@babel/parser": "^7.29.7",
48
48
  "@babel/traverse": "^7.29.7",
49
49
  "@babel/types": "^7.29.7",
50
- "@saykit/config": "^0.6.1"
50
+ "@saykit/config": "^0.8.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@babel/core": "^7.29.7",