ezmedicationinput 0.1.1 → 0.1.3

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.
package/dist/format.js CHANGED
@@ -1,48 +1,51 @@
1
- import { EventTiming, FhirPeriodUnit, RouteCode } from "./types";
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatInternal = formatInternal;
4
+ const types_1 = require("./types");
2
5
  const ROUTE_SHORT = {
3
- [RouteCode["Oral route"]]: "PO",
4
- [RouteCode["Sublingual route"]]: "SL",
5
- [RouteCode["Buccal route"]]: "BUC",
6
- [RouteCode["Respiratory tract route (qualifier value)"]]: "INH",
7
- [RouteCode["Nasal route"]]: "IN",
8
- [RouteCode["Topical route"]]: "TOP",
9
- [RouteCode["Transdermal route"]]: "TD",
10
- [RouteCode["Subcutaneous route"]]: "SC",
11
- [RouteCode["Intramuscular route"]]: "IM",
12
- [RouteCode["Intravenous route"]]: "IV",
13
- [RouteCode["Per rectum"]]: "PR",
14
- [RouteCode["Per vagina"]]: "PV",
15
- [RouteCode["Ophthalmic route"]]: "OPH",
16
- [RouteCode["Intravitreal route (qualifier value)"]]: "IVT"
6
+ [types_1.RouteCode["Oral route"]]: "PO",
7
+ [types_1.RouteCode["Sublingual route"]]: "SL",
8
+ [types_1.RouteCode["Buccal route"]]: "BUC",
9
+ [types_1.RouteCode["Respiratory tract route (qualifier value)"]]: "INH",
10
+ [types_1.RouteCode["Nasal route"]]: "IN",
11
+ [types_1.RouteCode["Topical route"]]: "TOP",
12
+ [types_1.RouteCode["Transdermal route"]]: "TD",
13
+ [types_1.RouteCode["Subcutaneous route"]]: "SC",
14
+ [types_1.RouteCode["Intramuscular route"]]: "IM",
15
+ [types_1.RouteCode["Intravenous route"]]: "IV",
16
+ [types_1.RouteCode["Per rectum"]]: "PR",
17
+ [types_1.RouteCode["Per vagina"]]: "PV",
18
+ [types_1.RouteCode["Ophthalmic route"]]: "OPH",
19
+ [types_1.RouteCode["Intravitreal route (qualifier value)"]]: "IVT"
17
20
  };
18
21
  const WHEN_TEXT = {
19
- [EventTiming["Before Sleep"]]: "at bedtime",
20
- [EventTiming["Before Meal"]]: "before meals",
21
- [EventTiming["Before Breakfast"]]: "before breakfast",
22
- [EventTiming["Before Lunch"]]: "before lunch",
23
- [EventTiming["Before Dinner"]]: "before dinner",
24
- [EventTiming["After Meal"]]: "after meals",
25
- [EventTiming["After Breakfast"]]: "after breakfast",
26
- [EventTiming["After Lunch"]]: "after lunch",
27
- [EventTiming["After Dinner"]]: "after dinner",
28
- [EventTiming.Meal]: "with meals",
29
- [EventTiming.Breakfast]: "with morning meal",
30
- [EventTiming.Lunch]: "with lunch",
31
- [EventTiming.Dinner]: "with evening meal",
32
- [EventTiming.Morning]: "in the morning",
33
- [EventTiming["Early Morning"]]: "in the early morning",
34
- [EventTiming["Late Morning"]]: "in the late morning",
35
- [EventTiming.Noon]: "at noon",
36
- [EventTiming.Afternoon]: "in the afternoon",
37
- [EventTiming["Early Afternoon"]]: "in the early afternoon",
38
- [EventTiming["Late Afternoon"]]: "in the late afternoon",
39
- [EventTiming.Evening]: "in the evening",
40
- [EventTiming["Early Evening"]]: "in the early evening",
41
- [EventTiming["Late Evening"]]: "in the late evening",
42
- [EventTiming.Night]: "at night",
43
- [EventTiming.Wake]: "after waking",
44
- [EventTiming["After Sleep"]]: "after sleep",
45
- [EventTiming.Immediate]: "immediately"
22
+ [types_1.EventTiming["Before Sleep"]]: "at bedtime",
23
+ [types_1.EventTiming["Before Meal"]]: "before meals",
24
+ [types_1.EventTiming["Before Breakfast"]]: "before breakfast",
25
+ [types_1.EventTiming["Before Lunch"]]: "before lunch",
26
+ [types_1.EventTiming["Before Dinner"]]: "before dinner",
27
+ [types_1.EventTiming["After Meal"]]: "after meals",
28
+ [types_1.EventTiming["After Breakfast"]]: "after breakfast",
29
+ [types_1.EventTiming["After Lunch"]]: "after lunch",
30
+ [types_1.EventTiming["After Dinner"]]: "after dinner",
31
+ [types_1.EventTiming.Meal]: "with meals",
32
+ [types_1.EventTiming.Breakfast]: "with breakfast",
33
+ [types_1.EventTiming.Lunch]: "with lunch",
34
+ [types_1.EventTiming.Dinner]: "with dinner",
35
+ [types_1.EventTiming.Morning]: "in the morning",
36
+ [types_1.EventTiming["Early Morning"]]: "in the early morning",
37
+ [types_1.EventTiming["Late Morning"]]: "in the late morning",
38
+ [types_1.EventTiming.Noon]: "at noon",
39
+ [types_1.EventTiming.Afternoon]: "in the afternoon",
40
+ [types_1.EventTiming["Early Afternoon"]]: "in the early afternoon",
41
+ [types_1.EventTiming["Late Afternoon"]]: "in the late afternoon",
42
+ [types_1.EventTiming.Evening]: "in the evening",
43
+ [types_1.EventTiming["Early Evening"]]: "in the early evening",
44
+ [types_1.EventTiming["Late Evening"]]: "in the late evening",
45
+ [types_1.EventTiming.Night]: "at night",
46
+ [types_1.EventTiming.Wake]: "after waking",
47
+ [types_1.EventTiming["After Sleep"]]: "after sleep",
48
+ [types_1.EventTiming.Immediate]: "immediately"
46
49
  };
47
50
  const DAY_NAMES = {
48
51
  mon: "Monday",
@@ -53,6 +56,102 @@ const DAY_NAMES = {
53
56
  sat: "Saturday",
54
57
  sun: "Sunday"
55
58
  };
59
+ const DEFAULT_ROUTE_GRAMMAR = { verb: "Use" };
60
+ const ROUTE_GRAMMAR = {
61
+ [types_1.RouteCode["Oral route"]]: { verb: "Take", routePhrase: "by mouth" },
62
+ [types_1.RouteCode["Ophthalmic route"]]: {
63
+ verb: "Instill",
64
+ routePhrase: ({ hasSite }) => (hasSite ? undefined : "in the eye"),
65
+ sitePreposition: "in"
66
+ },
67
+ [types_1.RouteCode["Intravitreal route (qualifier value)"]]: {
68
+ verb: "Inject",
69
+ routePhrase: ({ hasSite }) => (hasSite ? undefined : "into the eye"),
70
+ sitePreposition: "into"
71
+ },
72
+ [types_1.RouteCode["Topical route"]]: {
73
+ verb: "Apply",
74
+ routePhrase: ({ hasSite }) => (hasSite ? undefined : "topically"),
75
+ sitePreposition: "to"
76
+ },
77
+ [types_1.RouteCode["Transdermal route"]]: {
78
+ verb: "Apply",
79
+ routePhrase: ({ hasSite }) => (hasSite ? undefined : "transdermally"),
80
+ sitePreposition: "to"
81
+ },
82
+ [types_1.RouteCode["Subcutaneous route"]]: {
83
+ verb: "Inject",
84
+ routePhrase: ({ hasSite }) => (hasSite ? undefined : "subcutaneously"),
85
+ sitePreposition: "into"
86
+ },
87
+ [types_1.RouteCode["Intramuscular route"]]: {
88
+ verb: "Inject",
89
+ routePhrase: ({ hasSite }) => (hasSite ? undefined : "intramuscularly"),
90
+ sitePreposition: "into"
91
+ },
92
+ [types_1.RouteCode["Intravenous route"]]: {
93
+ verb: "Inject",
94
+ routePhrase: ({ hasSite }) => (hasSite ? undefined : "intravenously"),
95
+ sitePreposition: "into"
96
+ },
97
+ [types_1.RouteCode["Nasal route"]]: {
98
+ verb: "Use",
99
+ routePhrase: ({ hasSite }) => (hasSite ? undefined : "via nasal route"),
100
+ sitePreposition: "into"
101
+ },
102
+ [types_1.RouteCode["Respiratory tract route (qualifier value)"]]: {
103
+ verb: "Use",
104
+ routePhrase: ({ hasSite }) => (hasSite ? undefined : "via inhalation"),
105
+ sitePreposition: "into"
106
+ }
107
+ };
108
+ function grammarFromRouteText(text) {
109
+ if (!text) {
110
+ return undefined;
111
+ }
112
+ const normalized = text.trim().toLowerCase();
113
+ if (!normalized) {
114
+ return undefined;
115
+ }
116
+ if (normalized.includes("mouth") || normalized.includes("oral")) {
117
+ return ROUTE_GRAMMAR[types_1.RouteCode["Oral route"]];
118
+ }
119
+ if (normalized.includes("ophthalm")) {
120
+ return ROUTE_GRAMMAR[types_1.RouteCode["Ophthalmic route"]];
121
+ }
122
+ if (normalized.includes("intravitreal")) {
123
+ return ROUTE_GRAMMAR[types_1.RouteCode["Intravitreal route (qualifier value)"]];
124
+ }
125
+ if (normalized.includes("topical")) {
126
+ return ROUTE_GRAMMAR[types_1.RouteCode["Topical route"]];
127
+ }
128
+ if (normalized.includes("transdermal")) {
129
+ return ROUTE_GRAMMAR[types_1.RouteCode["Transdermal route"]];
130
+ }
131
+ if (normalized.includes("subcutaneous") || normalized === "sc" || normalized === "sq") {
132
+ return ROUTE_GRAMMAR[types_1.RouteCode["Subcutaneous route"]];
133
+ }
134
+ if (normalized.includes("intramuscular") || normalized === "im") {
135
+ return ROUTE_GRAMMAR[types_1.RouteCode["Intramuscular route"]];
136
+ }
137
+ if (normalized.includes("intravenous") || normalized === "iv") {
138
+ return ROUTE_GRAMMAR[types_1.RouteCode["Intravenous route"]];
139
+ }
140
+ if (normalized.includes("nasal")) {
141
+ return ROUTE_GRAMMAR[types_1.RouteCode["Nasal route"]];
142
+ }
143
+ if (normalized.includes("inhal")) {
144
+ return ROUTE_GRAMMAR[types_1.RouteCode["Respiratory tract route (qualifier value)"]];
145
+ }
146
+ return undefined;
147
+ }
148
+ function resolveRouteGrammar(internal) {
149
+ var _a, _b;
150
+ if (internal.routeCode && ROUTE_GRAMMAR[internal.routeCode]) {
151
+ return (_a = ROUTE_GRAMMAR[internal.routeCode]) !== null && _a !== void 0 ? _a : DEFAULT_ROUTE_GRAMMAR;
152
+ }
153
+ return (_b = grammarFromRouteText(internal.routeText)) !== null && _b !== void 0 ? _b : DEFAULT_ROUTE_GRAMMAR;
154
+ }
56
155
  function pluralize(unit, value) {
57
156
  if (Math.abs(value) === 1) {
58
157
  if (unit === "tab")
@@ -83,7 +182,7 @@ function describeFrequency(internal) {
83
182
  const { frequency, frequencyMax, period, periodMax, periodUnit, timingCode } = internal;
84
183
  if (frequency !== undefined &&
85
184
  frequencyMax !== undefined &&
86
- periodUnit === FhirPeriodUnit.Day &&
185
+ periodUnit === types_1.FhirPeriodUnit.Day &&
87
186
  (!period || period === 1)) {
88
187
  if (frequency === 1 && frequencyMax === 1) {
89
188
  return "once daily";
@@ -93,7 +192,7 @@ function describeFrequency(internal) {
93
192
  }
94
193
  return `${stripTrailingZero(frequency)} to ${stripTrailingZero(frequencyMax)} times daily`;
95
194
  }
96
- if (frequency && periodUnit === FhirPeriodUnit.Day && (!period || period === 1)) {
195
+ if (frequency && periodUnit === types_1.FhirPeriodUnit.Day && (!period || period === 1)) {
97
196
  if (frequency === 1)
98
197
  return "once daily";
99
198
  if (frequency === 2)
@@ -104,13 +203,13 @@ function describeFrequency(internal) {
104
203
  return "four times daily";
105
204
  return `${stripTrailingZero(frequency)} times daily`;
106
205
  }
107
- if (periodUnit === FhirPeriodUnit.Hour && period) {
206
+ if (periodUnit === types_1.FhirPeriodUnit.Hour && period) {
108
207
  if (periodMax && periodMax !== period) {
109
208
  return `every ${stripTrailingZero(period)} to ${stripTrailingZero(periodMax)} hours`;
110
209
  }
111
210
  return `every ${stripTrailingZero(period)} hour${period === 1 ? "" : "s"}`;
112
211
  }
113
- if (periodUnit === FhirPeriodUnit.Day && period && period !== 1) {
212
+ if (periodUnit === types_1.FhirPeriodUnit.Day && period && period !== 1) {
114
213
  if (period === 2 && (!periodMax || periodMax === 2)) {
115
214
  return "every other day";
116
215
  }
@@ -119,7 +218,7 @@ function describeFrequency(internal) {
119
218
  }
120
219
  return `every ${stripTrailingZero(period)} days`;
121
220
  }
122
- if (periodUnit === FhirPeriodUnit.Week && period) {
221
+ if (periodUnit === types_1.FhirPeriodUnit.Week && period) {
123
222
  if (period === 1 && (!periodMax || periodMax === 1)) {
124
223
  return "once weekly";
125
224
  }
@@ -128,7 +227,7 @@ function describeFrequency(internal) {
128
227
  }
129
228
  return `every ${stripTrailingZero(period)} weeks`;
130
229
  }
131
- if (periodUnit === FhirPeriodUnit.Month && period) {
230
+ if (periodUnit === types_1.FhirPeriodUnit.Month && period) {
132
231
  if (period === 1 && (!periodMax || periodMax === 1)) {
133
232
  return "once monthly";
134
233
  }
@@ -197,33 +296,210 @@ function formatDoseLong(internal) {
197
296
  }
198
297
  return undefined;
199
298
  }
200
- function describeWhen(internal) {
299
+ function collectWhenPhrases(internal) {
201
300
  if (!internal.when.length) {
202
- return undefined;
301
+ return [];
302
+ }
303
+ const unique = [];
304
+ const seen = new Set();
305
+ for (const code of internal.when) {
306
+ if (!seen.has(code)) {
307
+ seen.add(code);
308
+ unique.push(code);
309
+ }
203
310
  }
204
- const parts = internal.when
205
- .map((code) => WHEN_TEXT[code] ?? code)
206
- .filter(Boolean);
311
+ const hasSpecificAfter = unique.some((code) => code === types_1.EventTiming["After Breakfast"] ||
312
+ code === types_1.EventTiming["After Lunch"] ||
313
+ code === types_1.EventTiming["After Dinner"]);
314
+ const hasSpecificBefore = unique.some((code) => code === types_1.EventTiming["Before Breakfast"] ||
315
+ code === types_1.EventTiming["Before Lunch"] ||
316
+ code === types_1.EventTiming["Before Dinner"]);
317
+ const hasSpecificWith = unique.some((code) => code === types_1.EventTiming.Breakfast ||
318
+ code === types_1.EventTiming.Lunch ||
319
+ code === types_1.EventTiming.Dinner);
320
+ return unique
321
+ .filter((code) => {
322
+ if (code === types_1.EventTiming["After Meal"] && hasSpecificAfter) {
323
+ return false;
324
+ }
325
+ if (code === types_1.EventTiming["Before Meal"] && hasSpecificBefore) {
326
+ return false;
327
+ }
328
+ if (code === types_1.EventTiming.Meal && hasSpecificWith) {
329
+ return false;
330
+ }
331
+ return true;
332
+ })
333
+ .map((code) => { var _a; return (_a = WHEN_TEXT[code]) !== null && _a !== void 0 ? _a : code; })
334
+ .filter((text) => Boolean(text));
335
+ }
336
+ function joinWithAnd(parts) {
207
337
  if (!parts.length) {
338
+ return "";
339
+ }
340
+ if (parts.length === 1) {
341
+ return parts[0];
342
+ }
343
+ if (parts.length === 2) {
344
+ return `${parts[0]} and ${parts[1]}`;
345
+ }
346
+ return `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
347
+ }
348
+ function combineFrequencyAndEvents(frequency, events) {
349
+ if (!frequency) {
350
+ if (!events.length) {
351
+ return {};
352
+ }
353
+ return { event: joinWithAnd(events) };
354
+ }
355
+ if (!events.length) {
356
+ return { frequency };
357
+ }
358
+ if (events.length === 1 && events[0] === "at bedtime") {
359
+ const lowerFrequency = frequency.toLowerCase();
360
+ if (lowerFrequency === "twice daily" || lowerFrequency === "three times daily" || lowerFrequency === "four times daily") {
361
+ return { frequency: `${frequency} and ${events[0]}` };
362
+ }
363
+ }
364
+ return { frequency, event: joinWithAnd(events) };
365
+ }
366
+ function buildRoutePhrase(internal, grammar, hasSite) {
367
+ var _a;
368
+ if (typeof grammar.routePhrase === "function") {
369
+ return grammar.routePhrase({ hasSite, internal });
370
+ }
371
+ if (typeof grammar.routePhrase === "string") {
372
+ return grammar.routePhrase;
373
+ }
374
+ const text = (_a = internal.routeText) === null || _a === void 0 ? void 0 : _a.trim();
375
+ if (!text) {
376
+ return undefined;
377
+ }
378
+ const normalized = text.toLowerCase();
379
+ if (normalized.startsWith("by ") || normalized.startsWith("per ") || normalized.startsWith("via ")) {
380
+ return text;
381
+ }
382
+ if (normalized === "oral") {
383
+ return "by mouth";
384
+ }
385
+ if (normalized === "intravenous") {
386
+ return "intravenously";
387
+ }
388
+ if (normalized === "intramuscular") {
389
+ return "intramuscularly";
390
+ }
391
+ if (normalized === "subcutaneous") {
392
+ return "subcutaneously";
393
+ }
394
+ if (normalized === "topical") {
395
+ return "topically";
396
+ }
397
+ if (normalized === "transdermal") {
398
+ return "transdermally";
399
+ }
400
+ if (normalized === "intranasal" || normalized === "nasal") {
401
+ return "via nasal route";
402
+ }
403
+ if (normalized.includes("inhal")) {
404
+ return "via inhalation";
405
+ }
406
+ return `via ${text}`;
407
+ }
408
+ function formatSite(internal, grammar) {
409
+ var _a;
410
+ const text = (_a = internal.siteText) === null || _a === void 0 ? void 0 : _a.trim();
411
+ if (!text) {
208
412
  return undefined;
209
413
  }
210
- return parts.join(" and ");
414
+ const lower = text.toLowerCase();
415
+ let preposition = grammar.sitePreposition;
416
+ if (!preposition) {
417
+ if (lower.includes("eye")) {
418
+ preposition = "in";
419
+ }
420
+ else if (lower.includes("nostril") || lower.includes("nose")) {
421
+ preposition = "into";
422
+ }
423
+ else if (lower.includes("lung") || lower.includes("airway") || lower.includes("bronch")) {
424
+ preposition = "into";
425
+ }
426
+ else if (lower.includes("ear")) {
427
+ preposition = "in";
428
+ }
429
+ else if (/(skin|arm|leg|thigh|abdomen|shoulder|hand|foot|cheek|forearm|back|buttock|hip|face|hair|scalp|forehead|chin|neck)/.test(lower)) {
430
+ preposition = "to";
431
+ }
432
+ else {
433
+ preposition = "at";
434
+ }
435
+ }
436
+ const noun = formatSiteNoun(text, preposition);
437
+ return `${preposition} ${noun}`.trim();
438
+ }
439
+ function formatSiteNoun(site, preposition) {
440
+ const trimmed = site.trim();
441
+ const lower = trimmed.toLowerCase();
442
+ const skipArticlePrefixes = [
443
+ "the ",
444
+ "both ",
445
+ "each ",
446
+ "either ",
447
+ "every ",
448
+ "all ",
449
+ "bilateral ",
450
+ ];
451
+ for (const prefix of skipArticlePrefixes) {
452
+ if (lower.startsWith(prefix)) {
453
+ return trimmed;
454
+ }
455
+ }
456
+ const needsArticle = /^(left|right|upper|lower|inner|outer|mid|middle|posterior|anterior|proximal|distal|medial|lateral|dorsal|ventral)\b/.test(lower);
457
+ if (needsArticle || preposition === "at") {
458
+ return `the ${trimmed}`;
459
+ }
460
+ if (/(eye|nostril|ear|arm|leg|thigh|abdomen|hand|foot|cheek|skin|back)/.test(lower)) {
461
+ return `the ${trimmed}`;
462
+ }
463
+ return `the ${trimmed}`;
211
464
  }
212
465
  function describeDayOfWeek(internal) {
213
466
  if (!internal.dayOfWeek.length) {
214
467
  return undefined;
215
468
  }
216
- const days = internal.dayOfWeek.map((d) => DAY_NAMES[d] ?? d);
217
- if (days.length === 1) {
218
- return `on ${days[0]}`;
469
+ const days = internal.dayOfWeek.map((d) => { var _a; return (_a = DAY_NAMES[d]) !== null && _a !== void 0 ? _a : d; });
470
+ if (!days.length) {
471
+ return undefined;
219
472
  }
220
- return `on ${days.join(" and ")}`;
473
+ return `on ${joinWithAnd(days)}`;
221
474
  }
222
- export function formatInternal(internal, style) {
223
- if (style === "short") {
224
- return formatShort(internal);
475
+ function formatInternal(internal, style, localization) {
476
+ const defaults = {
477
+ short: formatShort(internal),
478
+ long: formatLong(internal)
479
+ };
480
+ if (!localization) {
481
+ return defaults[style];
482
+ }
483
+ const formatDefault = (target) => defaults[target];
484
+ if (style === "short" && localization.formatShort) {
485
+ const context = {
486
+ style: "short",
487
+ internal,
488
+ defaultText: defaults.short,
489
+ formatDefault
490
+ };
491
+ return localization.formatShort(context);
492
+ }
493
+ if (style === "long" && localization.formatLong) {
494
+ const context = {
495
+ style: "long",
496
+ internal,
497
+ defaultText: defaults.long,
498
+ formatDefault
499
+ };
500
+ return localization.formatLong(context);
225
501
  }
226
- return formatLong(internal);
502
+ return defaults[style];
227
503
  }
228
504
  function formatShort(internal) {
229
505
  const parts = [];
@@ -248,12 +524,12 @@ function formatShort(internal) {
248
524
  }
249
525
  else if (internal.frequency !== undefined &&
250
526
  internal.frequencyMax !== undefined &&
251
- internal.periodUnit === FhirPeriodUnit.Day &&
527
+ internal.periodUnit === types_1.FhirPeriodUnit.Day &&
252
528
  (!internal.period || internal.period === 1)) {
253
529
  parts.push(`${stripTrailingZero(internal.frequency)}-${stripTrailingZero(internal.frequencyMax)}x/d`);
254
530
  }
255
531
  else if (internal.frequency &&
256
- internal.periodUnit === FhirPeriodUnit.Day &&
532
+ internal.periodUnit === types_1.FhirPeriodUnit.Day &&
257
533
  (!internal.period || internal.period === 1)) {
258
534
  parts.push(`${stripTrailingZero(internal.frequency)}x/d`);
259
535
  }
@@ -283,35 +559,44 @@ function formatShort(internal) {
283
559
  return parts.filter(Boolean).join(" ");
284
560
  }
285
561
  function formatLong(internal) {
286
- const parts = [];
287
- const dosePart = formatDoseLong(internal);
288
- if (dosePart) {
289
- parts.push(dosePart);
562
+ var _a;
563
+ const grammar = resolveRouteGrammar(internal);
564
+ const dosePart = (_a = formatDoseLong(internal)) !== null && _a !== void 0 ? _a : "the medication";
565
+ const sitePart = formatSite(internal, grammar);
566
+ const routePart = buildRoutePhrase(internal, grammar, Boolean(sitePart));
567
+ const frequencyPart = describeFrequency(internal);
568
+ const eventParts = collectWhenPhrases(internal);
569
+ const timing = combineFrequencyAndEvents(frequencyPart, eventParts);
570
+ const dayPart = describeDayOfWeek(internal);
571
+ const asNeededPart = internal.asNeeded
572
+ ? internal.asNeededReason
573
+ ? `as needed for ${internal.asNeededReason}`
574
+ : "as needed"
575
+ : undefined;
576
+ const segments = [dosePart];
577
+ if (routePart) {
578
+ segments.push(routePart);
290
579
  }
291
- if (internal.routeText) {
292
- parts.push(internal.routeText);
580
+ if (timing.frequency) {
581
+ segments.push(timing.frequency);
293
582
  }
294
- const freqText = describeFrequency(internal);
295
- if (freqText) {
296
- parts.push(freqText);
583
+ if (timing.event) {
584
+ segments.push(timing.event);
297
585
  }
298
- const whenText = describeWhen(internal);
299
- if (whenText) {
300
- parts.push(whenText);
586
+ if (dayPart) {
587
+ segments.push(dayPart);
301
588
  }
302
- const dayText = describeDayOfWeek(internal);
303
- if (dayText) {
304
- parts.push(dayText);
589
+ if (asNeededPart) {
590
+ segments.push(asNeededPart);
305
591
  }
306
- if (internal.asNeeded) {
307
- parts.push(internal.asNeededReason
308
- ? `as needed for ${internal.asNeededReason}`
309
- : "as needed");
592
+ if (sitePart) {
593
+ segments.push(sitePart);
310
594
  }
311
- if (internal.siteText) {
312
- parts.push(`at ${internal.siteText}`);
595
+ const body = segments.filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
596
+ if (!body) {
597
+ return `${grammar.verb}.`;
313
598
  }
314
- return parts.join(" ").trim();
599
+ return `${grammar.verb} ${body}.`;
315
600
  }
316
601
  function stripTrailingZero(value) {
317
602
  const text = value.toString();
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { ParsedSigInternal } from "./internal-types";
2
+ export interface SigFormatContext {
3
+ readonly style: "short" | "long";
4
+ readonly internal: ParsedSigInternal;
5
+ readonly defaultText: string;
6
+ formatDefault(style: "short" | "long"): string;
7
+ }
8
+ export interface SigShortContext extends SigFormatContext {
9
+ readonly style: "short";
10
+ }
11
+ export interface SigLongContext extends SigFormatContext {
12
+ readonly style: "long";
13
+ }
14
+ export interface SigLocalization {
15
+ readonly locale: string;
16
+ formatShort?(context: SigShortContext): string;
17
+ formatLong?(context: SigLongContext): string;
18
+ }
19
+ export interface SigLocalizationConfig extends Partial<Omit<SigLocalization, "locale">> {
20
+ locale?: string;
21
+ inherit?: string;
22
+ }
23
+ export declare function registerSigLocalization(localization: SigLocalization): void;
24
+ export declare function getRegisteredSigLocalizations(): SigLocalization[];
25
+ export declare function resolveSigLocalization(locale?: string, config?: SigLocalizationConfig): SigLocalization | undefined;
26
+ export type SigTranslation = SigLocalization;
27
+ export type SigTranslationConfig = SigLocalizationConfig;
28
+ export declare const registerSigTranslation: typeof registerSigLocalization;
29
+ export declare const getRegisteredSigTranslations: typeof getRegisteredSigLocalizations;
30
+ export declare function resolveSigTranslation(locale?: string, config?: SigTranslationConfig): SigTranslation | undefined;
31
+ export declare const THAI_SITE_TRANSLATIONS: Record<string, string>;