When maxDecimalPoints truncates a unit count to exactly 1, the rendered
number showed 1 while the word was pluralized from the raw count (for
example humanizeDuration(1500, { maxDecimalPoints: 0 }) returned
"1 seconds"). Pluralize from the normalized count that is actually
displayed so the number and word agree.
2.4 KiB
Fix pluralization when maxDecimalPoints truncates the count to 1
Problem
When the maxDecimalPoints option truncates a unit count down to exactly 1,
the rendered number and the unit word disagree. The number is shown as 1 but
the word is pluralized:
const humanizeDuration = require("humanize-duration");
humanizeDuration(1500, { maxDecimalPoints: 0 }); // "1 seconds" (expected "1 second")
humanizeDuration(1005, { maxDecimalPoints: 2 }); // "1 seconds" (expected "1 second")
The bug is language-independent; any language whose unit word is a function of
the count is affected (for example German returns "1 Sekunden" instead of
"1 Sekunde").
Root cause
humanize-duration.js, in renderPiece.
The displayed count is normalizedUnitCount, computed from maxDecimalPoints:
var normalizedUnitCount =
maxDecimalPoints === void 0
? unitCount
: Math.floor(unitCount * Math.pow(10, maxDecimalPoints)) /
Math.pow(10, maxDecimalPoints);
var countStr = normalizedUnitCount.toString();
but the unit word was pluralized from the raw, un-truncated unitCount:
if (typeof languageWord === "function") {
word = languageWord(unitCount);
}
So 1.5 seconds with maxDecimalPoints: 0 renders the count as "1" (from
normalizedUnitCount) while languageWord(1.5) returns the plural form, giving
"1 seconds".
Fix
Pluralize based on the count that is actually rendered, normalizedUnitCount:
if (typeof languageWord === "function") {
word = languageWord(normalizedUnitCount);
}
When maxDecimalPoints is unset, normalizedUnitCount === unitCount, so
existing behavior is unchanged. The fix only affects cases where the option
truncates the count.
Test
Added a test to test/humanizer.js covering the truncated-to-one case, a
truncated count that stays plural, and the same mechanism in German:
assert.strictEqual(humanizer({ maxDecimalPoints: 0 })(1500), "1 second");
assert.strictEqual(humanizer({ maxDecimalPoints: 2 })(1005), "1 second");
assert.strictEqual(humanizer({ maxDecimalPoints: 0 })(2500), "2 seconds");
assert.strictEqual(
humanizer({ language: "de", maxDecimalPoints: 0 })(1500),
"1 Sekunde"
);
Counterfactual (revert the one-line fix in renderPiece):
Expected values to be strictly equal:
expected: '1 second'
actual: '1 seconds'
Gate
npm test(runsprettier --check+eslint+tsc --noEmit, thennode --test): exit 0, all tests pass.