diff --git a/__tests__/fromDosageToText/rateRatio.test.tsx b/__tests__/fromDosageToText/rateRatio.test.tsx index ce93b20c..3e74e556 100644 --- a/__tests__/fromDosageToText/rateRatio.test.tsx +++ b/__tests__/fromDosageToText/rateRatio.test.tsx @@ -67,6 +67,29 @@ describe("fromDosageToText - rateRatio", () => { expect(result).toBe("at a rate of 3 pills every 5 hours"); }); + test("numerator (with comparator) and denominator", () => { + const dosage: Dosage = { + doseAndRate: [ + { + rateRatio: { + numerator: { + value: 3, + unit: "pills", + comparator: "<", + }, + denominator: { + value: 5, + unit: "hours", + }, + }, + }, + ], + }; + + let result = dosageUtils.fromDosageToText(dosage); + expect(result).toBe("at a rate of < 3 pills every 5 hours"); + }); + test("no numerator", () => { const dosage: Dosage = { doseAndRate: [ @@ -82,7 +105,9 @@ describe("fromDosageToText - rateRatio", () => { }; let result = dosageUtils.fromDosageToText(dosage); - expect(result).toBe("at a rate of every 5 hours"); + // From FHIR https://build.fhir.org/datatypes.html#Ratio + // "A proper ratio has both a numerator and a denominator; however, these are not mandatory in order to allow an invalid ratio with an extension with further information" + expect(result).toBe("at a rate of every 5 hours"); }); test("no unit", () => { diff --git a/documentation/docs/faq.mdx b/documentation/docs/faq.mdx index 300b19d8..46719508 100644 --- a/documentation/docs/faq.mdx +++ b/documentation/docs/faq.mdx @@ -59,6 +59,7 @@ You can use [English files](https://github.com/jy95/fhir-dosage-utils/tree/main/ - [daysOfWeek](https://github.com/jy95/fhir-dosage-utils/blob/main/src/locales/en/daysOfWeek.json) : Codes related to [Days Of Week](https://build.fhir.org/valueset-days-of-week.html) ValueSet - [eventTiming](https://github.com/jy95/fhir-dosage-utils/blob/main/src/locales/en/eventTiming.json) : Codes related to [EventTiming](https://build.fhir.org/valueset-event-timing.html) ValueSet - [unitsOfTime](https://github.com/jy95/fhir-dosage-utils/blob/main/src/locales/en/unitsOfTime.json) : Codes related to [UnitsOfTime](https://build.fhir.org/valueset-units-of-time.html) ValueSet +- [quantityComparator](https://github.com/jy95/fhir-dosage-utils/blob/main/src/locales/en/quantityComparator.json) : Codes related to [QuantityComparator](https://build.fhir.org/valueset-quantity-comparator.html) ValueSet diff --git a/src/classes/Configurator.ts b/src/classes/Configurator.ts index 82f4e504..5b766aa5 100644 --- a/src/classes/Configurator.ts +++ b/src/classes/Configurator.ts @@ -37,7 +37,13 @@ export class Configurator { // default attributes fallbackLng: "en", lng: this.config.language, - ns: ["common", "daysOfWeek", "eventTiming", "unitsOfTime"], + ns: [ + "common", + "daysOfWeek", + "eventTiming", + "unitsOfTime", + "quantityComparator", + ], defaultNS: "common", // attributes set by user ...i18nConfig, diff --git a/src/i18n.ts b/src/i18n.ts index f695d050..1d0d96aa 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -3,6 +3,7 @@ import common from "./locales/en/common.json"; import unitsOfTime from "./locales/en/unitsOfTime.json"; import daysOfWeek from "./locales/en/daysOfWeek.json"; import eventTiming from "./locales/en/eventTiming.json"; +import quantityComparator from "./locales/en/quantityComparator.json"; export const defaultNS = "common"; export const resources = { @@ -11,5 +12,6 @@ export const resources = { unitsOfTime: unitsOfTime, daysOfWeek: daysOfWeek, eventTiming: eventTiming, + quantityComparator: quantityComparator, }, } as const; diff --git a/src/internal/extractTimingRepeat.ts b/src/internal/extractTimingRepeat.ts index 45b7fd99..ded09ef3 100644 --- a/src/internal/extractTimingRepeat.ts +++ b/src/internal/extractTimingRepeat.ts @@ -2,7 +2,7 @@ import type { Dosage } from "../types"; export function extractTimingRepeat(dos: Dosage) { // If empty, return undefined - if (dos.timing === undefined || dos.timing.repeat === undefined) { + if (dos.timing?.repeat === undefined) { return undefined; } diff --git a/src/internal/isEmptyArray.ts b/src/internal/isEmptyArray.ts new file mode 100644 index 00000000..a35019cf --- /dev/null +++ b/src/internal/isEmptyArray.ts @@ -0,0 +1,3 @@ +export function isArrayEmpty(array?: any[]): array is undefined | [] { + return array === undefined || array.length === 0; +} diff --git a/src/locales/de/common.json b/src/locales/de/common.json index ea7be53c..7da7e1cd 100644 --- a/src/locales/de/common.json +++ b/src/locales/de/common.json @@ -17,16 +17,8 @@ } }, "ratio": { - "withUnit": { - "numerator": "{{count}} {{numeratorUnit}}", - "denominator_one": "pro {{denominatorUnit}}", - "denominator_other": "jeder {{count}} {{denominatorUnit}}" - }, - "withoutUnit": { - "numerator": "{{count}}", - "denominator_one": ":{{count}}", - "denominator_other": ":{{count}}" - } + "denominatorLinkword_one": "pro", + "denominatorLinkword_other": "jeder" }, "quantity": { "withUnit": "{{quantity}} {{unit}}", diff --git a/src/locales/de/quantityComparator.json b/src/locales/de/quantityComparator.json new file mode 100644 index 00000000..df011f42 --- /dev/null +++ b/src/locales/de/quantityComparator.json @@ -0,0 +1,7 @@ +{ + "<": "<", + "<=": "<=", + ">=": ">=", + ">": ">", + "ad": "ad" +} diff --git a/src/locales/en/common.json b/src/locales/en/common.json index a9fd243e..85e7e01a 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -17,16 +17,8 @@ } }, "ratio": { - "withUnit": { - "numerator": "{{count}} {{numeratorUnit}}", - "denominator_one": "per {{denominatorUnit}}", - "denominator_other": "every {{count}} {{denominatorUnit}}" - }, - "withoutUnit": { - "numerator": "{{count}}", - "denominator_one": ":{{count}}", - "denominator_other": ":{{count}}" - } + "denominatorLinkword_one": "per", + "denominatorLinkword_other": "every" }, "quantity": { "withUnit": "{{quantity}} {{unit}}", diff --git a/src/locales/en/quantityComparator.json b/src/locales/en/quantityComparator.json new file mode 100644 index 00000000..df011f42 --- /dev/null +++ b/src/locales/en/quantityComparator.json @@ -0,0 +1,7 @@ +{ + "<": "<", + "<=": "<=", + ">=": ">=", + ">": ">", + "ad": "ad" +} diff --git a/src/locales/fr/common.json b/src/locales/fr/common.json index 925311b6..86bcd5b3 100644 --- a/src/locales/fr/common.json +++ b/src/locales/fr/common.json @@ -17,16 +17,8 @@ } }, "ratio": { - "withUnit": { - "numerator": "{{count}} {{numeratorUnit}}", - "denominator_one": "par {{denominatorUnit}}", - "denominator_other": "chaque {{count}} {{denominatorUnit}}" - }, - "withoutUnit": { - "numerator": "{{count}}", - "denominator_one": ":{{count}}", - "denominator_other": ":{{count}}" - } + "denominatorLinkword_one": "par", + "denominatorLinkword_other": "chaque" }, "quantity": { "withUnit": "{{quantity}} {{unit}}", diff --git a/src/locales/fr/quantityComparator.json b/src/locales/fr/quantityComparator.json new file mode 100644 index 00000000..df011f42 --- /dev/null +++ b/src/locales/fr/quantityComparator.json @@ -0,0 +1,7 @@ +{ + "<": "<", + "<=": "<=", + ">=": ">=", + ">": ">", + "ad": "ad" +} diff --git a/src/locales/nl/common.json b/src/locales/nl/common.json index e004f94d..f3c6dc7a 100644 --- a/src/locales/nl/common.json +++ b/src/locales/nl/common.json @@ -17,16 +17,8 @@ } }, "ratio": { - "withUnit": { - "numerator": "{{count}} {{numeratorUnit}}", - "denominator_one": "per {{denominatorUnit}}", - "denominator_other": "elke {{count}} {{denominatorUnit}}" - }, - "withoutUnit": { - "numerator": "{{count}}", - "denominator_one": ":{{count}}", - "denominator_other": ":{{count}}" - } + "denominatorLinkword_one": "per", + "denominatorLinkword_other": "elke" }, "quantity": { "withUnit": "{{quantity}} {{unit}}", diff --git a/src/locales/nl/quantityComparator.json b/src/locales/nl/quantityComparator.json new file mode 100644 index 00000000..df011f42 --- /dev/null +++ b/src/locales/nl/quantityComparator.json @@ -0,0 +1,7 @@ +{ + "<": "<", + "<=": "<=", + ">=": ">=", + ">": ">", + "ad": "ad" +} diff --git a/src/translators/additionalInstruction.ts b/src/translators/additionalInstruction.ts index e718d1da..ac7180dc 100644 --- a/src/translators/additionalInstruction.ts +++ b/src/translators/additionalInstruction.ts @@ -1,5 +1,6 @@ // Function import { fromListToString } from "../utils/fromListToString"; +import { isArrayEmpty } from "../internal/isEmptyArray"; // types import type { DisplayOrderParams } from "../types"; @@ -10,10 +11,7 @@ export function transformAdditionalInstructionToText({ i18next, }: DisplayOrderParams): string | undefined { // If empty, return undefined - if ( - dos.additionalInstruction === undefined || - dos.additionalInstruction.length === 0 - ) { + if (isArrayEmpty(dos.additionalInstruction)) { return undefined; } diff --git a/src/translators/asNeeded.ts b/src/translators/asNeeded.ts index 3b55820a..d229c01a 100644 --- a/src/translators/asNeeded.ts +++ b/src/translators/asNeeded.ts @@ -53,14 +53,10 @@ export function transformAsNeededToText({ config, ), }); - } else { - // merge boolean to make it simpler - let booleanValue = asNeededBoolean || asNeeded || false; - - if (booleanValue) { - return i18next.t("fields.asNeeded"); - } else { - return undefined; - } } + + // merge boolean to make it simpler + let booleanValue = [asNeededBoolean, asNeeded].includes(true); + + return booleanValue ? i18next.t("fields.asNeeded") : undefined; } diff --git a/src/translators/boundsDuration.ts b/src/translators/boundsDuration.ts index 25223797..713ad2c6 100644 --- a/src/translators/boundsDuration.ts +++ b/src/translators/boundsDuration.ts @@ -44,13 +44,9 @@ export function transformBoundsDurationToText({ // Do nothing if no boundsDuration, I am not a wizard if (boundsDuration === undefined) { return undefined; - } else { - let durationText = transformDurationToString( - i18next, - boundsDuration, - config, - ); - - return i18next.t("fields.boundsDuration", { durationText: durationText }); } + + let durationText = transformDurationToString(i18next, boundsDuration, config); + + return i18next.t("fields.boundsDuration", { durationText: durationText }); } diff --git a/src/translators/boundsPeriod.ts b/src/translators/boundsPeriod.ts index 7a79a13a..abec8dcb 100644 --- a/src/translators/boundsPeriod.ts +++ b/src/translators/boundsPeriod.ts @@ -10,11 +10,7 @@ export function transformBoundsPeriodToText({ i18next, }: DisplayOrderParams): string | undefined { // If empty, return undefined - if ( - dos.timing === undefined || - dos.timing.repeat === undefined || - dos.timing.repeat.boundsPeriod === undefined - ) { + if (dos.timing?.repeat?.boundsPeriod === undefined) { return undefined; } diff --git a/src/translators/boundsRange.ts b/src/translators/boundsRange.ts index 3cc70535..0aec3d28 100644 --- a/src/translators/boundsRange.ts +++ b/src/translators/boundsRange.ts @@ -23,22 +23,22 @@ export function transformBoundsRangeToText({ // Do nothing if no boundsRange, I am not a wizard if (boundsRange === undefined) { return undefined; - } else { - // Turn range into a text - const rangeText = fromRangeToString({ - range: boundsRange, - config, - i18next, - }); - - // Reject if empty - if (rangeText === undefined) { - return undefined; - } - - // return the final string - return i18next.t("fields.boundsRange", { - rangeText: rangeText, - }); } + + // Turn range into a text + const rangeText = fromRangeToString({ + range: boundsRange, + config, + i18next, + }); + + // Reject if empty + if (rangeText === undefined) { + return undefined; + } + + // return the final string + return i18next.t("fields.boundsRange", { + rangeText: rangeText, + }); } diff --git a/src/translators/countCountMax.ts b/src/translators/countCountMax.ts index fd49bddc..32a7b084 100644 --- a/src/translators/countCountMax.ts +++ b/src/translators/countCountMax.ts @@ -20,23 +20,23 @@ export function transformCountCountMaxToText({ // Do nothing if no count, I am not a wizard if (count === undefined && countMax === undefined) { return undefined; - } else { - // Three cases - - // 1. Both count & countMax are present - if (count !== undefined && countMax !== undefined) { - return i18next.t("fields.countMax.countMax", { - count: countMax, - low: count, - }); - } - - // 2. Only countMax is present - if (countMax !== undefined) { - return i18next.t("fields.count.count", { count: countMax }); - } - - // 3. Only count is present - return i18next.t("fields.count.count", { count: count }); } + + // Three cases + + // 1. Both count & countMax are present + if (count !== undefined && countMax !== undefined) { + return i18next.t("fields.countMax.countMax", { + count: countMax, + low: count, + }); + } + + // 2. Only countMax is present + if (countMax !== undefined) { + return i18next.t("fields.count.count", { count: countMax }); + } + + // 3. Only count is present + return i18next.t("fields.count.count", { count: count }); } diff --git a/src/translators/dayOfWeek.ts b/src/translators/dayOfWeek.ts index 7082ae42..009d4630 100644 --- a/src/translators/dayOfWeek.ts +++ b/src/translators/dayOfWeek.ts @@ -1,6 +1,7 @@ // Function import { fromListToString } from "../utils/fromListToString"; import { extractTimingRepeat } from "../internal/extractTimingRepeat"; +import { isArrayEmpty } from "../internal/isEmptyArray"; // Types import type { DisplayOrderParams } from "../types"; @@ -21,18 +22,18 @@ export function transformDayOfWeekToText({ let dayOfWeek = repeat.dayOfWeek; // If empty, skip it - if (dayOfWeek === undefined || dayOfWeek.length === 0) { + if (isArrayEmpty(dayOfWeek)) { return undefined; - } else { - // Turn it into a string - const dayOfWeeks = dayOfWeek.map((dayCode) => - i18next.t(`daysOfWeek:${dayCode}`), - ); - const dayOfWeeksAsString = fromListToString(i18next, dayOfWeeks); - - return i18next.t("fields.dayOfWeek.dayOfWeek", { - count: dayOfWeek.length, - dayOfWeek: dayOfWeeksAsString, - }); } + + // Turn it into a string + const dayOfWeeks = dayOfWeek.map((dayCode) => + i18next.t(`daysOfWeek:${dayCode}`), + ); + const dayOfWeeksAsString = fromListToString(i18next, dayOfWeeks); + + return i18next.t("fields.dayOfWeek.dayOfWeek", { + count: dayOfWeek.length, + dayOfWeek: dayOfWeeksAsString, + }); } diff --git a/src/translators/durationDurationMax.ts b/src/translators/durationDurationMax.ts index d658dfd2..f990672d 100644 --- a/src/translators/durationDurationMax.ts +++ b/src/translators/durationDurationMax.ts @@ -21,24 +21,24 @@ export function transformDurationDurationMaxToText({ // Do nothing if no unit, I am not a wizard if (unit === undefined) { return undefined; - } else { - return [ - // duration - duration !== undefined && - i18next.t("fields.duration", { - durationText: i18next.t(`unitsOfTime:withCount.${unit}`, { - count: duration, - }), + } + + return [ + // duration + duration !== undefined && + i18next.t("fields.duration", { + durationText: i18next.t(`unitsOfTime:withCount.${unit}`, { + count: duration, }), - // durationMax - max !== undefined && - i18next.t("fields.durationMax", { - durationMaxText: i18next.t(`unitsOfTime:withCount.${unit}`, { - count: max, - }), + }), + // durationMax + max !== undefined && + i18next.t("fields.durationMax", { + durationMaxText: i18next.t(`unitsOfTime:withCount.${unit}`, { + count: max, }), - ] - .filter((s) => s !== false) - .join(" "); - } + }), + ] + .filter((s) => s !== false) + .join(" "); } diff --git a/src/translators/event.ts b/src/translators/event.ts index 2a27094e..3df1d1b4 100644 --- a/src/translators/event.ts +++ b/src/translators/event.ts @@ -1,6 +1,7 @@ // Functions import { fromListToString } from "../utils/fromListToString"; import { formatDatetimes } from "../utils/formatDatetimes"; +import { isArrayEmpty } from "../internal/isEmptyArray"; // types import type { DisplayOrderParams } from "../types"; @@ -11,17 +12,11 @@ export function transformEventToText({ i18next, }: DisplayOrderParams): string | undefined { // If empty, return undefined - if ( - dos.timing === undefined || - dos.timing.event === undefined || - dos.timing.event.length === 0 - ) { + let events = dos.timing?.event; + if (isArrayEmpty(events)) { return undefined; } - // Generate the string version of them - let events = dos.timing.event; - // List to string let eventList = formatDatetimes({ config, datetimes: events }); let eventsAsString = fromListToString(i18next, eventList); diff --git a/src/translators/frequencyFrequencyMax.ts b/src/translators/frequencyFrequencyMax.ts index ec082230..25621c9a 100644 --- a/src/translators/frequencyFrequencyMax.ts +++ b/src/translators/frequencyFrequencyMax.ts @@ -20,23 +20,23 @@ export function transformFrequencyFrequencyMaxToText({ // Do nothing if no frequency / frequencyMax, I am not a wizard if (frequency === undefined && max === undefined) { return undefined; - } else { - // Three cases - - // 1. Frequency and frequencyMax are present - if (frequency !== undefined && max !== undefined) { - return i18next.t("fields.frequency.withfrequencyMax", { - count: max, - frequency: frequency, - }); - } - - // 2. Only frequencyMax is present - if (max !== undefined) { - return i18next.t("fields.frequencyMax.frequencyMax", { count: max }); - } - - // 3. Only frequency is present - return i18next.t("fields.frequency.onlyFrequency", { count: frequency }); } + + // Three cases + + // 1. Frequency and frequencyMax are present + if (frequency !== undefined && max !== undefined) { + return i18next.t("fields.frequency.withfrequencyMax", { + count: max, + frequency: frequency, + }); + } + + // 2. Only frequencyMax is present + if (max !== undefined) { + return i18next.t("fields.frequencyMax.frequencyMax", { count: max }); + } + + // 3. Only frequency is present + return i18next.t("fields.frequency.onlyFrequency", { count: frequency }); } diff --git a/src/translators/offsetWhen.ts b/src/translators/offsetWhen.ts index a4f53215..fe232565 100644 --- a/src/translators/offsetWhen.ts +++ b/src/translators/offsetWhen.ts @@ -1,6 +1,7 @@ // Functions import { fromListToString } from "../utils/fromListToString"; import { extractTimingRepeat } from "../internal/extractTimingRepeat"; +import { isArrayEmpty } from "../internal/isEmptyArray"; // Types import type { DisplayOrderParams, I18N } from "../types"; @@ -74,7 +75,7 @@ function transformOffset(i18next: I18N, offset?: number): string | undefined { // Function to transform when[] into a string function transformWhen(i18next: I18N, when?: string[]): string | undefined { // Only run when array is not empty - if (when === undefined || when.length === 0) { + if (isArrayEmpty(when)) { return undefined; } @@ -82,9 +83,7 @@ function transformWhen(i18next: I18N, when?: string[]): string | undefined { const whens = (when as TimeKeys[]).map((whenCode) => i18next.t(`eventTiming:${whenCode}`), ); - const finalString = fromListToString(i18next, whens); - - return finalString; + return fromListToString(i18next, whens); } export function transformOffsetWhenToText({ diff --git a/src/translators/periodPeriodMax.ts b/src/translators/periodPeriodMax.ts index 8b634102..7fbb7c40 100644 --- a/src/translators/periodPeriodMax.ts +++ b/src/translators/periodPeriodMax.ts @@ -21,30 +21,30 @@ export function transformPeriodPeriodMaxToText({ // Do nothing if no unit, I am not a wizard if (unit === undefined) { return undefined; - } else { - // Three cases - - // 1. period and periodMax are present - if (period !== undefined && max !== undefined) { - return i18next.t("fields.periodMax.withPeriod", { - period: period, - count: max, - unit: i18next.t(`unitsOfTime:withoutCount.${unit}`, { count: max }), - }); - } - - // 2. Only periodMax is present - if (max !== undefined) { - return i18next.t("fields.periodMax.onlyPeriodMax", { - count: max, - unit: i18next.t(`unitsOfTime:withoutCount.${unit}`, { count: max }), - }); - } - - // 3. Only period present - return i18next.t("fields.period.period", { - count: period, - unit: i18next.t(`unitsOfTime:withoutCount.${unit}`, { count: period }), + } + + // Three cases + + // 1. period and periodMax are present + if (period !== undefined && max !== undefined) { + return i18next.t("fields.periodMax.withPeriod", { + period: period, + count: max, + unit: i18next.t(`unitsOfTime:withoutCount.${unit}`, { count: max }), }); } + + // 2. Only periodMax is present + if (max !== undefined) { + return i18next.t("fields.periodMax.onlyPeriodMax", { + count: max, + unit: i18next.t(`unitsOfTime:withoutCount.${unit}`, { count: max }), + }); + } + + // 3. Only period present + return i18next.t("fields.period.period", { + count: period, + unit: i18next.t(`unitsOfTime:withoutCount.${unit}`, { count: period }), + }); } diff --git a/src/translators/timeOfDay.ts b/src/translators/timeOfDay.ts index 830341b0..a76942c4 100644 --- a/src/translators/timeOfDay.ts +++ b/src/translators/timeOfDay.ts @@ -1,6 +1,7 @@ // Functions import { fromListToString } from "../utils/fromListToString"; import { extractTimingRepeat } from "../internal/extractTimingRepeat"; +import { isArrayEmpty } from "../internal/isEmptyArray"; // Types import type { DisplayOrderParams } from "../types"; @@ -39,16 +40,16 @@ export function transformTimeOfDayToText({ let timeOfDay = repeat.timeOfDay; // If empty, skip it - if (timeOfDay === undefined || timeOfDay.length === 0) { + if (isArrayEmpty(timeOfDay)) { return undefined; - } else { - // Turn it into a string - const timeOfDays = timeOfDay.map(formatString); - const timeOfDaysAsString = fromListToString(i18next, timeOfDays); - - return i18next.t("fields.timeOfDay", { - timeOfDay: timeOfDaysAsString, - count: timeOfDays.length, - }); } + + // Turn it into a string + const timeOfDays = timeOfDay.map(formatString); + const timeOfDaysAsString = fromListToString(i18next, timeOfDays); + + return i18next.t("fields.timeOfDay", { + timeOfDay: timeOfDaysAsString, + count: timeOfDays.length, + }); } diff --git a/src/utils/formatDatetimes.ts b/src/utils/formatDatetimes.ts index 425420c4..b3ee574d 100644 --- a/src/utils/formatDatetimes.ts +++ b/src/utils/formatDatetimes.ts @@ -16,13 +16,21 @@ type MappedDate = { function generateDateStyleFormatOptions( options: Intl.DateTimeFormatOptions, ): Intl.DateTimeFormatOptions { + if (options.dateStyle !== undefined) { + return { + dateStyle: options.dateStyle, + }; + } + + const defaults: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "2-digit", + day: "2-digit", + }; + return { - year: - options.dateStyle === undefined ? options.year || "numeric" : undefined, - month: - options.dateStyle === undefined ? options.month || "2-digit" : undefined, - day: options.dateStyle === undefined ? options.day || "2-digit" : undefined, - weekday: options.dateStyle === undefined ? options.weekday : undefined, + ...options, + ...defaults, }; } @@ -30,13 +38,21 @@ function generateDateStyleFormatOptions( function generateTimeStyleFormatOptions( options: Intl.DateTimeFormatOptions, ): Intl.DateTimeFormatOptions { + if (options.timeStyle !== undefined) { + return { + timeStyle: options.timeStyle, + }; + } + + const defaults: Intl.DateTimeFormatOptions = { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }; + return { - hour: - options.timeStyle === undefined ? options.hour || "2-digit" : undefined, - minute: - options.timeStyle === undefined ? options.minute || "2-digit" : undefined, - second: - options.timeStyle === undefined ? options.second || "2-digit" : undefined, + ...options, + ...defaults, }; } @@ -89,8 +105,7 @@ export function formatDatetimes({ config, datetimes }: Args): string[] { if (!hasTimePart) { let df3 = new Intl.DateTimeFormat(config.language, { // retrieve value from user - dateStyle: options.dateStyle, - // fallback if dateStyle is not defined + // and fallback if dateStyle is not defined ...generateDateStyleFormatOptions(options), }); return df3.format(date); @@ -99,9 +114,7 @@ export function formatDatetimes({ config, datetimes }: Args): string[] { // Otherwise, we have a full datetime let df4 = new Intl.DateTimeFormat(config.language, { // retrieve value from user - dateStyle: options.dateStyle, - timeStyle: options.timeStyle, - // fallback if dateStyle / timeStyle is not defined + // and fallback if dateStyle / timeStyle is not defined ...generateDateStyleFormatOptions(options), ...generateTimeStyleFormatOptions(options), }); diff --git a/src/utils/fromCodeableConceptToString.ts b/src/utils/fromCodeableConceptToString.ts index b313e448..1ea69dc1 100644 --- a/src/utils/fromCodeableConceptToString.ts +++ b/src/utils/fromCodeableConceptToString.ts @@ -1,3 +1,7 @@ +// Functions +import { isArrayEmpty } from "../internal/isEmptyArray"; + +// Types import type { CodeableConcept } from "../types"; /** @@ -27,7 +31,7 @@ export function defaultFromCodeableConceptToString({ } // If empty, skip it - if (code.coding === undefined || code.coding.length === 0) { + if (isArrayEmpty(code.coding)) { return undefined; } diff --git a/src/utils/fromExtensionsToString.ts b/src/utils/fromExtensionsToString.ts index 166b6d9a..1d57045e 100644 --- a/src/utils/fromExtensionsToString.ts +++ b/src/utils/fromExtensionsToString.ts @@ -1,3 +1,7 @@ +// Functions +import { isArrayEmpty } from "../internal/isEmptyArray"; + +// Types import type { Extension } from "../types"; /** @@ -17,7 +21,7 @@ export function defaultFromExtensionsToString({ extensions, }: FromExtensionsToStringArgs) { // If no extensions, skip it - if (extensions === undefined || extensions.length === 0) { + if (isArrayEmpty(extensions)) { return undefined; } diff --git a/src/utils/fromQuantityToString.ts b/src/utils/fromQuantityToString.ts index 1b5ec586..ec482a86 100644 --- a/src/utils/fromQuantityToString.ts +++ b/src/utils/fromQuantityToString.ts @@ -9,7 +9,7 @@ export function fromQuantityToString({ quantity, config, i18next, -}: QuantityParams): string | undefined { +}: QuantityParams): string { // extract function for the unit display from config const { fromFHIRQuantityUnitToString, language } = config; @@ -18,14 +18,28 @@ export function fromQuantityToString({ let value = quantity.value || 1; // If no unit is present (in other words ""), we don't put it - if (unit.length === 0) { - return i18next.t("amount.quantity.withoutUnit", { - quantity: value, - }); + let quantityString = + unit.length === 0 + ? i18next.t("amount.quantity.withoutUnit", { + quantity: value, + }) + : i18next.t("amount.quantity.withUnit", { + quantity: value, + unit: unit, + }); + + // Compute the comparator + let comparatorCode = quantity.comparator; + let comparatorString = + comparatorCode !== undefined + ? i18next.t(`quantityComparator:${comparatorCode}`) + : undefined; + + // If no comparator, print it + if (comparatorString === undefined) { + return quantityString; } else { - return i18next.t("amount.quantity.withUnit", { - quantity: value, - unit: unit, - }); + // concatenate comparator and quantity + return `${comparatorString} ${quantityString}`; } } diff --git a/src/utils/fromRangeToString.ts b/src/utils/fromRangeToString.ts index 3d5b87d4..5f1a7e61 100644 --- a/src/utils/fromRangeToString.ts +++ b/src/utils/fromRangeToString.ts @@ -14,13 +14,13 @@ function transformQuantityUnitToString( return i18next.t(`unitsOfTime:withoutCount.${code}`, { count: quantityValue, }); - } else { - // otherwise, it is UCUM, ... so let the user do the job - return config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: quantity, - }); } + + // otherwise, it is UCUM, ... so let the user do the job + return config.fromFHIRQuantityUnitToString({ + language: config.language, + quantity: quantity, + }); } // To cover all nasty cases of Range, only once diff --git a/src/utils/fromRatioToString.ts b/src/utils/fromRatioToString.ts index 36941496..9a45145a 100644 --- a/src/utils/fromRatioToString.ts +++ b/src/utils/fromRatioToString.ts @@ -1,8 +1,43 @@ -import type { RatioParams, Quantity } from "../types"; +// Functions +import { fromQuantityToString } from "../utils/fromQuantityToString"; + +// Type +import type { RatioParams, Quantity, QuantityParams } from "../types"; // Quantity has an unit ? function hasUnit(quantity?: Quantity): boolean { - return (quantity?.unit || quantity?.code) !== undefined; + return [quantity?.unit, quantity?.code].some((field) => field !== undefined); +} + +// To cover all nasty cases of denominator +function fromDenominatorToString({ + config, + i18next, + quantity, +}: QuantityParams): string { + let hasUnitDenominator = hasUnit(quantity); + let value = quantity.value!; + + // If no unit, it is quite simple + if (!hasUnitDenominator) return `:${value}`; + + // Get correct linkword (depending of the quantity value) + let linkword = i18next.t("amount.ratio.denominatorLinkword", { + count: value, + }); + + // Get quantity text (depending of the quantity value) + let quantityText = + value !== 1 + ? fromQuantityToString({ quantity, config, i18next }) + : config.fromFHIRQuantityUnitToString({ + quantity, + language: config.language, + }); + + // Concatenate all computed parts + // The space before is intentional so that numerator and denominator are well printed regardless of situation + return ` ${linkword} ${quantityText}`; } // To cover all nasty cases of Ratio, only once @@ -15,62 +50,35 @@ export function fromRatioToString({ // Extract params const { denominator, numerator } = ratio; - // units as text - let numeratorUnit = hasUnit(numerator) - ? config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: numerator!, - }) - : undefined; - - let denominatorUnit = hasUnit(denominator) - ? config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: denominator!, - }) - : undefined; - // quantity let quantityNumerator = numerator?.value; let quantityDenominator = denominator?.value; // Collect results const parts: string[] = []; - let noUnits = numeratorUnit === undefined && denominatorUnit === undefined; - let separator = noUnits ? "" : " "; // Deal with numerator first if (quantityNumerator !== undefined) { - let technicalKey: "withUnit" | "withoutUnit" = - numeratorUnit !== undefined ? "withUnit" : "withoutUnit"; - const numeratorString = i18next.t( - `amount.ratio.${technicalKey}.numerator`, - { - count: quantityNumerator, - numeratorUnit: numeratorUnit, - }, - ); + // Reuse the quantity to string translation + const numeratorString = fromQuantityToString({ + quantity: numerator!, + config, + i18next, + }); parts.push(numeratorString); } // Deal with denominator if (quantityDenominator !== undefined) { - let technicalKey: "withUnit" | "withoutUnit" = - denominatorUnit !== undefined ? "withUnit" : "withoutUnit"; - const denominatorString = i18next.t( - `amount.ratio.${technicalKey}.denominator`, - { - count: quantityDenominator, - denominatorUnit: denominatorUnit, - }, - ); + // Several cases exist for that, let use a proper function for that + const denominatorString = fromDenominatorToString({ + config, + i18next, + quantity: denominator!, + }); parts.push(denominatorString); } // Concatenate the result - if (parts.length === 0) { - return undefined; - } else { - return parts.join(separator); - } + return parts.length > 0 ? parts.join("") : undefined; }