gui: Use localised time in duration (#9552)

https://github.com/syncthing/syncthing/pull/8291 inpired me to develop
this. I tested it with all the languages Syncthing currently supports,
and they all work.

The only issue is that when you change the language in the GUI, you have
to either refresh the page, or wait a few seconds for the page to
refresh by itself, before the duration is translated into the new
language.

### Screenshots


![2024-05-20_21-47-58](https://github.com/syncthing/syncthing/assets/220772/7e3b371e-3495-4e3e-853a-b5a41215e6c7)

### Documentation

The documentation for the translation widget is at
https://github.com/EvanHahn/HumanizeDuration.js/blob/main/README.md
This commit is contained in:
Ross Smith II 2024-06-05 03:05:51 -07:00 committed by GitHub
parent 5a304cf295
commit 23a900e096
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 861 additions and 2 deletions

View File

@ -1050,6 +1050,7 @@
<script type="text/javascript" src="vendor/bootstrap/js/bootstrap.js"></script>
<script type="text/javascript" src="vendor/daterangepicker/daterangepicker.js"></script>
<script type="text/javascript" src="vendor/fancytree/jquery.fancytree-all-deps.js"></script>
<script type="text/javascript" src="vendor/HumanizeDuration.js/humanize-duration.js"></script>
<!-- / vendor scripts -->
<!-- gui application code -->

View File

@ -7,16 +7,63 @@
* {{1|duration:"h"}} --> <1h
**/
angular.module('syncthing.core')
.filter('duration', function () {
.filter('duration', function ($translate) {
'use strict';
var SECONDS_IN = { "d": 86400, "h": 3600, "m": 60, "s": 1 };
return function (input, precision) {
var result = "";
if (!precision) {
precision = "s";
}
input = parseInt(input, 10);
var language_cc = $translate.use();
if (language_cc != null) {
language_cc = language_cc.replace("-", "_");
var fallbacks = [];
var language = language_cc.substr(0, 2);
switch (language) {
case "zh":
// Use zh_TW for zh_HK
fallbacks.push("zh_TW");
break
}
if (language != language_cc) {
fallbacks.push(language);
}
// Fallback to english, if the language isn't found
fallbacks.push("en");
var units = ["d", "h", "m", "s"];
switch (precision) {
case "d":
units.pop();
// fallthrough
case "h":
units.pop();
// fallthrough
case "m":
units.pop();
// fallthrough
case "s":
break
default:
return "[Error: precision must be d, h, m or s, it's " + precision + "]";
}
try {
// humanizeDuration accepts only milliseconds
return humanizeDuration(input * 1000, {
language: language_cc,
maxDecimalPoints: 0,
units: units,
fallbacks: fallbacks
});
} catch(err) {
console.log(err.message + ": language_cc=" + language_cc)
// if we crash, fallthrough to english
}
}
var result = "";
for (var k in SECONDS_IN) {
var t = (input / SECONDS_IN[k] | 0); // Math.floor

View File

@ -0,0 +1,56 @@
The MIT License (MIT)
Copyright (c) 2013-2024 Evan Hahn (@EvanHahn)
Portions copyright (c) 2024 Ross Smith II (@rasa)
Other portions copyright their respective authors, see
https://github.com/EvanHahn/HumanizeDuration.js/graphs/contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This project is also licensed under the Unlicense license, at your option:
The Unlicense
-----------
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>
-----------
SPDX-License-Identifier: MIT or UNLICENSE

View File

@ -0,0 +1,7 @@
help:
@echo To update this package, type: make update
update:
wget -O LICENSE.txt https://raw.githubusercontent.com/rasa/HumanizeDuration.js/main/LICENSE.txt
wget -O humanize-duration.js https://raw.githubusercontent.com/rasa/HumanizeDuration.js/main/humanize-duration.js

View File

@ -0,0 +1,748 @@
// HumanizeDuration.js - https://git.io/j0HgmQ
// @ts-check
/**
* @typedef {string | ((unitCount: number) => string)} Unit
*/
/**
* @typedef {("y" | "mo" | "w" | "d" | "h" | "m" | "s" | "ms")} UnitName
*/
/**
* @typedef {Object} UnitMeasures
* @prop {number} y
* @prop {number} mo
* @prop {number} w
* @prop {number} d
* @prop {number} h
* @prop {number} m
* @prop {number} s
* @prop {number} ms
*/
/**
* @internal
* @typedef {[string, string, string, string, string, string, string, string, string, string]} DigitReplacements
*/
/**
* @typedef {Object} Language
* @prop {Unit} y
* @prop {Unit} mo
* @prop {Unit} w
* @prop {Unit} d
* @prop {Unit} h
* @prop {Unit} m
* @prop {Unit} s
* @prop {Unit} ms
* @prop {string} [decimal]
* @prop {string} [delimiter]
* @prop {DigitReplacements} [_digitReplacements]
* @prop {boolean} [_numberFirst]
*/
/**
* @typedef {Object} Options
* @prop {string} [language]
* @prop {Record<string, Language>} [languages]
* @prop {string[]} [fallbacks]
* @prop {string} [delimiter]
* @prop {string} [spacer]
* @prop {boolean} [round]
* @prop {number} [largest]
* @prop {UnitName[]} [units]
* @prop {string} [decimal]
* @prop {string} [conjunction]
* @prop {number} [maxDecimalPoints]
* @prop {UnitMeasures} [unitMeasures]
* @prop {boolean} [serialComma]
* @prop {DigitReplacements} [digitReplacements]
*/
/**
* @internal
* @typedef {Required<Options>} NormalizedOptions
*/
(function () {
// Fallback for `Object.assign` if relevant.
var assign =
Object.assign ||
/** @param {...any} destination */
function (destination) {
var source;
for (var i = 1; i < arguments.length; i++) {
source = arguments[i];
for (var prop in source) {
if (has(source, prop)) {
destination[prop] = source[prop];
}
}
}
return destination;
};
// Fallback for `Array.isArray` if relevant.
var isArray =
Array.isArray ||
function (arg) {
return Object.prototype.toString.call(arg) === "[object Array]";
};
// This has to be defined separately because of a bug: we want to alias
// `gr` and `el` for backwards-compatiblity. In a breaking change, we can
// remove `gr` entirely.
// See https://github.com/EvanHahn/HumanizeDuration.js/issues/143 for more.
var GREEK = language("έ", "μ", "ε", "η", "ώ", "λ", "δ", "χδ", ","); //
/**
* @internal
* @type {Record<string, Language>}
*/
var LANGUAGES = {
// Afrikaans (Afrikaans)
af: language("j", "mnd", "w", "d", "u", "m", "s", "ms", ","),
// አማርኛ (Amharic)
am: language("ዓ", "ወ", "ሳ", "ቀ", "ሰ", "ደ", "ሰከ", "ሳ", "ሚሊ"),
//العربية (Arabic) (RTL)
// https://github.com/EvanHahn/HumanizeDuration.js/issues/221#issuecomment-2119762498
// year -> ع stands for "عام" or س stands for "سنة"
// month -> ش stands for "شهر"
// week -> أ stands for "أسبوع"
// day -> ي stands for "يوم"
// hour -> س stands for "ساعة"
// minute -> د stands for "دقيقة"
// second -> ث stands for "ثانية"
ar: assign(language("س", "ش", "أ", "ي", "س", "د", "ث", "م ث", ","), {
_digitReplacements: ["۰", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"]
}),
// български (Bulgarian)
bg: language("г", "мес", "с", "д", "ч", "м", "сек", "мс", ","),
// বাংলা (Bengali)
bn: language("ব", "ম", "সপ্তা", "দ", "ঘ", "মি", "স", "মি.স"),
// català (Catalan)
ca: language("a", "mes", "set", "d", "h", "m", "s", "ms", ","),
//کوردیی ناوەڕاست (Central Kurdish) (RTL)
ckb: language("م چ", "چ", "خ", "ک", "ڕ", "ه", "م", "س", "."),
// čeština (Czech)
cs: language("r", "měs", "t", "d", "h", "m", "s", "ms", ","),
// Cymraeg (Welsh)
cy: language("b", "mis", "wth", "d", "awr", "mun", "eil", "ms"),
// dansk (Danish)
da: language("å", "md", "u", "d", "t", "m", "s", "ms", ","),
// Deutsch (German)
de: language("J", "mo", "w", "t", "std", "m", "s", "ms", ","),
// Ελληνικά (Greek)
el: GREEK,
// English (English)
en: language("y", "mo", "w", "d", "h", "m", "s", "ms"),
// Esperanto (Esperanto)
eo: language("j", "mo", "se", "t", "h", "m", "s", "ms", ","),
// español (Spanish)
es: language("a", "me", "se", "d", "h", "m", "s", "ms", ","),
// eesti keel (Estonian)
et: language("a", "k", "n", "p", "t", "m", "s", "ms", ","),
// euskara (Basque)
eu: language("u", "h", "a", "e", "o", "m", "s", "ms", ","),
//فارسی (Farsi/Persian) (RTL)
fa: language("س", "ما", "ه", "ر", "سا", "دقی", "ثانی", "میلی‌ثانیه"),
// suomi (Finnish)
fi: language("v", "kk", "vk", "pv", "t", "m", "s", "ms", ","),
// føroyskt (Faroese)
fo: language("á", "má", "v", "d", "t", "m", "s", "ms", ","),
// français (French)
fr: language("a", "m", "sem", "j", "h", "m", "s", "ms", ","),
// Ελληνικά (Greek) (el)
gr: GREEK,
//עברית (Hebrew) (RTL)
he: language("ש׳", "ח׳", "שב׳", "י׳", "שע׳", "ד׳", "שנ׳", "מל׳"),
// hrvatski (Croatian)
hr: language("g", "mj", "t", "d", "h", "m", "s", "ms", ","),
// हिंदी (Hindi)
hi: language("व", "म", "स", "द", "घ", "मि", "से", "मि.से"),
// magyar (Hungarian)
hu: language("é", "h", "hét", "n", "ó", "p", "mp", "ms", ","),
// Indonesia (Indonesian)
id: language("t", "b", "mgg", "h", "j", "m", "d", "md"),
// íslenska (Icelandic)
is: language("ár", "mán", "v", "d", "k", "m", "s", "ms"),
// italiano (Italian)
it: language("a", "me", "se", "g", "h", "m", "s", "ms", ","),
// 日本語 (Japanese)
ja: language("年", "月", "週", "日", "時", "分", "秒", "ミリ秒"),
// ភាសាខ្មែរ (Khmer)
km: language("ឆ", "ខ", "សប្តា", "ថ", "ម", "ន", "វ", "មវ"),
// ಕನ್ನಡ (Kannada)
kn: language("ವ", "ತ", "ವ", "ದ", "ಗಂ", "ನಿ", "ಸೆ", "ಮಿಸೆ"),
// 한국어 (Korean)
ko: language("년", "월", "주", "일", "시", "분", "초", "밀리초"),
// Kurdî (Kurdish)
ku: language("sal", "m", "h", "r", "s", "d", "ç", "ms", ","),
// ລາວ (Lao)
lo: language("ປ", "ເດ", "ອ", "ວ", "ຊ", "ນທ", "ວິນ", "ມິລິວິນາທີ", ","),
// lietuvių (Lithuanian)
lt: language("met", "mėn", "sav", "d", "v", "m", "s", "ms", ","),
// latviešu (Latvian)
lv: language("g", "mēn", "n", "d", "st", "m", "s", "ms", ","),
// македонски (Macedonian)
mk: language("г", "мес", "н", "д", "ч", "м", "с", "мс", ","),
// монгол (Mongolian)
mn: language("ж", "с", "дх", "ө", "ц", "м", "с", "мс"),
// मराठी (Marathi)
mr: language("व", "म", "आ", "दि", "त", "मि", "से", "मि.से"),
// Melayu (Malay)
ms: language("thn", "bln", "mgg", "hr", "j", "m", "s", "ms"),
// Nederlands (Dutch)
nl: language("j", "mnd", "w", "d", "u", "m", "s", "ms", ","),
// norsk (Norwegian)
no: language("år", "mnd", "u", "d", "t", "m", "s", "ms", ","),
// polski (Polish)
pl: language("r", "mi", "t", "d", "g", "m", "s", "ms", ","),
// português (Portuguese)
pt: language("a", "mês", "sem", "d", "h", "m", "s", "ms", ","),
// română (Romanian) săpt?
ro: language("a", "l", "să", "z", "h", "m", "s", "ms", ","),
// русский (Russian)
ru: language("г", "мес", "н", "д", "ч", "м", "с", "мс", ","),
// shqip (Albanian) orë? muaj?
sq: language("v", "mu", "j", "d", "o", "m", "s", "ms", ","),
// српски (Serbian)
sr: language("г", "мес", "н", "д", "ч", "м", "с", "мс", ","),
// தமிழ் (Tamil)
ta: language("ஆ", "மா", "வ", "நா", "ம", "நி", "வி", "மி.வி"),
// తెలుగు (Telugu)
te: language("సం", "నె", "వ", "రో", "గం", "ని", "సె", "మి.సె"), //
// українська (Ukrainian)
uk: language("р", "м", "т", "д", "г", "хв", "с", "мс", ","),
//اردو (Urdu) (RTL)
ur: language("س", "م", "ہ", "د", "گ", "م", "س", "م س"),
// slovenčina (Slovak)
sk: language("r", "mes", "t", "d", "h", "m", "s", "ms", ","),
// slovenščina (Slovenian)
sl: language("l", "mes", "t", "d", "ur", "m", "s", "ms", ","),
// svenska (Swedish)
sv: language("å", "mån", "v", "d", "h", "m", "s", "ms", ","),
// Kiswahili (Swahili)
sw: assign(language("mw", "m", "w", "s", "h", "dk", "s", "ms"), {
_numberFirst: true
}),
// Türkçe (Turkish)
tr: language("y", "a", "h", "g", "sa", "d", "s", "ms", ","),
// ไทย (Thai)
th: language("ปี", "ด", "ส", "ว", "ชม", "น", "วิ", "มิลลิวินาที"),
// o'zbek (Uzbek)
uz: language("y", "o", "h", "k", "soa", "m", "s", "ms"),
// Ўзбек (Кирилл) (Uzbek (Cyrillic))
uz_CYR: language("й", "о", "х", "к", "соа", "д", "с", "мс"),
// Tiếng Việt (Vietnamese)
vi: language("n", "th", "t", "ng", "gi", "p", "g", "ms", ","),
// 中文 (简体) (Chinese, simplified)
zh_CN: language("年", "月", "周", "天", "时", "分", "秒", "毫秒"),
// 中文 (繁體) (Chinese, traditional)
zh_TW: language("年", "月", "週", "天", "時", "分", "秒", "毫秒")
};
/**
* Helper function for creating language definitions.
*
* @internal
* @param {Unit} y
* @param {Unit} mo
* @param {Unit} w
* @param {Unit} d
* @param {Unit} h
* @param {Unit} m
* @param {Unit} s
* @param {Unit} ms
* @param {string} [decimal]
* @returns {Language}
*/
function language(y, mo, w, d, h, m, s, ms, decimal) {
/** @type {Language} */
var result = { y: y, mo: mo, w: w, d: d, h: h, m: m, s: s, ms: ms };
if (typeof decimal !== "undefined") {
result.decimal = decimal;
}
return result;
}
/**
* Helper function for Arabic.
*
* @internal
* @param {number} c
* @returns {0 | 1 | 2}
*/
// function getArabicForm(c) {
// if (c === 2) {
// return 1;
// }
// if (c > 2 && c < 11) {
// return 2;
// }
// return 0;
// }
/**
* Helper function for Polish.
*
* @internal
* @param {number} c
* @returns {0 | 1 | 2 | 3}
*/
// function getPolishForm(c) {
// if (c === 1) {
// return 0;
// }
// if (Math.floor(c) !== c) {
// return 1;
// }
// if (c % 10 >= 2 && c % 10 <= 4 && !(c % 100 > 10 && c % 100 < 20)) {
// return 2;
// }
// return 3;
// }
/**
* Helper function for Slavic languages.
*
* @internal
* @param {number} c
* @returns {0 | 1 | 2 | 3}
*/
// function getSlavicForm(c) {
// if (Math.floor(c) !== c) {
// return 2;
// }
// if (
// (c % 100 >= 5 && c % 100 <= 20) ||
// (c % 10 >= 5 && c % 10 <= 9) ||
// c % 10 === 0
// ) {
// return 0;
// }
// if (c % 10 === 1) {
// return 1;
// }
// if (c > 1) {
// return 2;
// }
// return 0;
// }
/**
* Helper function for Czech or Slovak.
*
* @internal
* @param {number} c
* @returns {0 | 1 | 2 | 3}
*/
// function getCzechOrSlovakForm(c) {
// if (c === 1) {
// return 0;
// }
// if (Math.floor(c) !== c) {
// return 1;
// }
// if (c % 10 >= 2 && c % 10 <= 4 && c % 100 < 10) {
// return 2;
// }
// return 3;
// }
/**
* Helper function for Lithuanian.
*
* @internal
* @param {number} c
* @returns {0 | 1 | 2}
*/
// function getLithuanianForm(c) {
// if (c === 1 || (c % 10 === 1 && c % 100 > 20)) {
// return 0;
// }
// if (
// Math.floor(c) !== c ||
// (c % 10 >= 2 && c % 100 > 20) ||
// (c % 10 >= 2 && c % 100 < 10)
// ) {
// return 1;
// }
// return 2;
// }
/**
* Helper function for Latvian.
*
* @internal
* @param {number} c
* @returns {boolean}
*/
// function getLatvianForm(c) {
// return c % 10 === 1 && c % 100 !== 11;
// }
/**
* @internal
* @template T
* @param {T} obj
* @param {keyof T} key
* @returns {boolean}
*/
function has(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
/**
* @internal
* @param {Pick<Required<Options>, "language" | "fallbacks" | "languages">} options
* @throws {Error} Throws an error if language is not found.
* @returns {Language}
*/
function getLanguage(options) {
var possibleLanguages = [options.language];
if (has(options, "fallbacks")) {
if (isArray(options.fallbacks) && options.fallbacks.length) {
possibleLanguages = possibleLanguages.concat(options.fallbacks);
} else {
throw new Error("fallbacks must be an array with at least one element");
}
}
for (var i = 0; i < possibleLanguages.length; i++) {
var languageToTry = possibleLanguages[i];
if (has(options.languages, languageToTry)) {
return options.languages[languageToTry];
}
if (has(LANGUAGES, languageToTry)) {
return LANGUAGES[languageToTry];
}
}
throw new Error("No language found.");
}
/**
* @internal
* @param {Piece} piece
* @param {Language} language
* @param {Pick<Required<Options>, "decimal" | "spacer" | "maxDecimalPoints" | "digitReplacements">} options
*/
function renderPiece(piece, language, options) {
var unitName = piece.unitName;
var unitCount = piece.unitCount;
var spacer = options.spacer;
var maxDecimalPoints = options.maxDecimalPoints;
/** @type {string} */
var decimal;
if (has(options, "decimal")) {
decimal = options.decimal;
} else if (has(language, "decimal")) {
decimal = language.decimal;
} else {
decimal = ".";
}
/** @type {undefined | DigitReplacements} */
var digitReplacements;
if ("digitReplacements" in options) {
digitReplacements = options.digitReplacements;
} else if ("_digitReplacements" in language) {
digitReplacements = language._digitReplacements;
}
/** @type {string} */
var formattedCount;
var normalizedUnitCount =
maxDecimalPoints === void 0
? unitCount
: Math.floor(unitCount * Math.pow(10, maxDecimalPoints)) /
Math.pow(10, maxDecimalPoints);
var countStr = normalizedUnitCount.toString();
if (digitReplacements) {
formattedCount = "";
for (var i = 0; i < countStr.length; i++) {
var char = countStr[i];
if (char === ".") {
formattedCount += decimal;
} else {
// @ts-ignore because `char` should always be 0-9 at this point.
formattedCount += digitReplacements[char];
}
}
} else {
formattedCount = countStr.replace(".", decimal);
}
var languageWord = language[unitName];
var word;
if (typeof languageWord === "function") {
word = languageWord(unitCount);
} else {
word = languageWord;
}
if (language._numberFirst) {
return word + spacer + formattedCount;
}
return formattedCount + spacer + word;
}
/**
* @internal
* @typedef {Object} Piece
* @prop {UnitName} unitName
* @prop {number} unitCount
*/
/**
* @internal
* @param {number} ms
* @param {Pick<Required<Options>, "units" | "unitMeasures" | "largest" | "round">} options
* @returns {Piece[]}
*/
function getPieces(ms, options) {
/** @type {UnitName} */
var unitName;
/** @type {number} */
var i;
/** @type {number} */
var unitCount;
/** @type {number} */
var msRemaining;
var units = options.units;
var unitMeasures = options.unitMeasures;
var largest = "largest" in options ? options.largest : Infinity;
if (!units.length) return [];
// Get the counts for each unit. Doesn't round or truncate anything.
// For example, might create an object like `{ y: 7, m: 6, w: 0, d: 5, h: 23.99 }`.
/** @type {Partial<Record<UnitName, number>>} */
var unitCounts = {};
msRemaining = ms;
for (i = 0; i < units.length; i++) {
unitName = units[i];
var unitMs = unitMeasures[unitName];
var isLast = i === units.length - 1;
unitCount = isLast
? msRemaining / unitMs
: Math.floor(msRemaining / unitMs);
unitCounts[unitName] = unitCount;
msRemaining -= unitCount * unitMs;
}
if (options.round) {
// Update counts based on the `largest` option.
// For example, if `largest === 2` and `unitCount` is `{ y: 7, m: 6, w: 0, d: 5, h: 23.99 }`,
// updates to something like `{ y: 7, m: 6.2 }`.
var unitsRemainingBeforeRound = largest;
for (i = 0; i < units.length; i++) {
unitName = units[i];
unitCount = unitCounts[unitName];
if (unitCount === 0) continue;
unitsRemainingBeforeRound--;
// "Take" the rest of the units into this one.
if (unitsRemainingBeforeRound === 0) {
for (var j = i + 1; j < units.length; j++) {
var smallerUnitName = units[j];
var smallerUnitCount = unitCounts[smallerUnitName];
unitCounts[unitName] +=
(smallerUnitCount * unitMeasures[smallerUnitName]) /
unitMeasures[unitName];
unitCounts[smallerUnitName] = 0;
}
break;
}
}
// Round the last piece (which should be the only non-integer).
//
// This can be a little tricky if the last piece "bubbles up" to a larger
// unit. For example, "3 days, 23.99 hours" should be rounded to "4 days".
// It can also require multiple passes. For example, "6 days, 23.99 hours"
// should become "1 week".
for (i = units.length - 1; i >= 0; i--) {
unitName = units[i];
unitCount = unitCounts[unitName];
if (unitCount === 0) continue;
var rounded = Math.round(unitCount);
unitCounts[unitName] = rounded;
if (i === 0) break;
var previousUnitName = units[i - 1];
var previousUnitMs = unitMeasures[previousUnitName];
var amountOfPreviousUnit = Math.floor(
(rounded * unitMeasures[unitName]) / previousUnitMs
);
if (amountOfPreviousUnit) {
unitCounts[previousUnitName] += amountOfPreviousUnit;
unitCounts[unitName] = 0;
} else {
break;
}
}
}
/** @type {Piece[]} */
var result = [];
for (i = 0; i < units.length && result.length < largest; i++) {
unitName = units[i];
unitCount = unitCounts[unitName];
if (unitCount) {
result.push({ unitName: unitName, unitCount: unitCount });
}
}
return result;
}
/**
* @internal
* @param {Piece[]} pieces
* @param {Pick<Required<Options>, "units" | "language" | "languages" | "fallbacks" | "delimiter" | "spacer" | "decimal" | "conjunction" | "maxDecimalPoints" | "serialComma" | "digitReplacements">} options
* @returns {string}
*/
function formatPieces(pieces, options) {
var language = getLanguage(options);
if (!pieces.length) {
var units = options.units;
var smallestUnitName = units[units.length - 1];
return renderPiece(
{ unitName: smallestUnitName, unitCount: 0 },
language,
options
);
}
var conjunction = options.conjunction;
var serialComma = options.serialComma;
var delimiter;
if (has(options, "delimiter")) {
delimiter = options.delimiter;
} else if (has(language, "delimiter")) {
delimiter = language.delimiter;
} else {
delimiter = " ";
}
/** @type {string[]} */
var renderedPieces = [];
for (var i = 0; i < pieces.length; i++) {
renderedPieces.push(renderPiece(pieces[i], language, options));
}
if (!conjunction || pieces.length === 1) {
return renderedPieces.join(delimiter);
}
if (pieces.length === 2) {
return renderedPieces.join(conjunction);
}
return (
renderedPieces.slice(0, -1).join(delimiter) +
(serialComma ? "," : "") +
conjunction +
renderedPieces.slice(-1)
);
}
/**
* Create a humanizer, which lets you change the default options.
*
* @param {Options} [passedOptions]
*/
function humanizer(passedOptions) {
/**
* @param {number} ms
* @param {Options} [humanizerOptions]
* @returns {string}
*/
var result = function humanizer(ms, humanizerOptions) {
// Make sure we have a positive number.
//
// Has the nice side-effect of converting things to numbers. For example,
// converts `"123"` and `Number(123)` to `123`.
ms = Math.abs(ms);
var options = assign({}, result, humanizerOptions || {});
var pieces = getPieces(ms, options);
return formatPieces(pieces, options);
};
return assign(
result,
{
language: "en",
spacer: "",
conjunction: "",
serialComma: true,
units: ["y", "mo", "w", "d", "h", "m", "s"],
languages: {},
round: false,
unitMeasures: {
y: 31557600000,
mo: 2629800000,
w: 604800000,
d: 86400000,
h: 3600000,
m: 60000,
s: 1000,
ms: 1
}
},
passedOptions
);
}
/**
* Humanize a duration.
*
* This is a wrapper around the default humanizer.
*/
var humanizeDuration = assign(humanizer({}), {
getSupportedLanguages: function getSupportedLanguages() {
var result = [];
for (var language in LANGUAGES) {
if (has(LANGUAGES, language) && language !== "gr") {
result.push(language);
}
}
return result;
},
humanizer: humanizer
});
// @ts-ignore
if (typeof define === "function" && define.amd) {
// @ts-ignore
define(function () {
return humanizeDuration;
});
} else if (typeof module !== "undefined" && module.exports) {
module.exports = humanizeDuration;
} else {
this.humanizeDuration = humanizeDuration;
}
})();