summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gui/default/index.html1
-rw-r--r--gui/default/syncthing/core/durationFilter.js51
-rw-r--r--gui/default/vendor/HumanizeDuration.js/LICENSE.txt56
-rw-r--r--gui/default/vendor/HumanizeDuration.js/Makefile7
-rw-r--r--gui/default/vendor/HumanizeDuration.js/humanize-duration.js748
5 files changed, 861 insertions, 2 deletions
diff --git a/gui/default/index.html b/gui/default/index.html
index d0aee77d1..5765a5f7b 100644
--- a/gui/default/index.html
+++ b/gui/default/index.html
@@ -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 -->
diff --git a/gui/default/syncthing/core/durationFilter.js b/gui/default/syncthing/core/durationFilter.js
index dc73d3b58..f423e851f 100644
--- a/gui/default/syncthing/core/durationFilter.js
+++ b/gui/default/syncthing/core/durationFilter.js
@@ -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
diff --git a/gui/default/vendor/HumanizeDuration.js/LICENSE.txt b/gui/default/vendor/HumanizeDuration.js/LICENSE.txt
new file mode 100644
index 000000000..960366f61
--- /dev/null
+++ b/gui/default/vendor/HumanizeDuration.js/LICENSE.txt
@@ -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
diff --git a/gui/default/vendor/HumanizeDuration.js/Makefile b/gui/default/vendor/HumanizeDuration.js/Makefile
new file mode 100644
index 000000000..b967b0ce6
--- /dev/null
+++ b/gui/default/vendor/HumanizeDuration.js/Makefile
@@ -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
diff --git a/gui/default/vendor/HumanizeDuration.js/humanize-duration.js b/gui/default/vendor/HumanizeDuration.js/humanize-duration.js
new file mode 100644
index 000000000..bbe129e3c
--- /dev/null
+++ b/gui/default/vendor/HumanizeDuration.js/humanize-duration.js
@@ -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;
+ }
+})();