Moved localization formats to ./util.js

This commit is contained in:
SwingTheVine 2026-02-26 03:12:33 -05:00
parent 4925623d5a
commit a3fc5bb8d3
13 changed files with 131 additions and 94 deletions

View file

@ -2,7 +2,7 @@
// @name Blue Marble
// @name:en Blue Marble
// @namespace https://github.com/SwingTheVine/
// @version 0.88.471
// @version 0.88.473
// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @author SwingTheVine
@ -88,6 +88,33 @@
};
// src/utils.js
function localizeNumber(number) {
const numberFormat = new Intl.NumberFormat();
return numberFormat.format(number);
}
function localizePercent(percent) {
const percentFormat = new Intl.NumberFormat(void 0, {
style: "percent",
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
return percentFormat.format(percent);
}
function localizeDate(date) {
const options = {
month: "long",
// July
day: "numeric",
// 23
hour: "2-digit",
// 17
minute: "2-digit",
// 47
second: "2-digit"
// 00
};
return date.toLocaleString(void 0, options);
}
function escapeHTML(text) {
const div = document.createElement("div");
div.textContent = text;
@ -1348,6 +1375,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
/** Adds a timer `time` element to the overlay.
* This timer will countdown until it reaches the end date that was passed in.
* Additionally, you can update the end date by changing the endDate dataset attribute on the element.
* Timer elements are not localized. Output is HH:MM:SS with no units.
* This timer will have properties shared between all timers in the overlay.
* You can override the shared properties by using a callback.
* @param {Date} [endDate=Date.now()] - The time to count down to.
@ -1747,18 +1775,13 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
*/
displayTemplateList_fn = function() {
const templates = this.currentJSON?.templates;
console.log("Loading Template Wizard...");
console.log(templates);
console.log(Object.keys(templates).length);
if (Object.keys(templates).length > 0) {
const templateListParentElement = document.querySelector(`#${this.windowID} .bm-scrollable`);
console.log(templateListParentElement);
const templateList = new Overlay(this.name, this.version);
templateList.addDiv({ "id": "bm-wizard-tlist", "class": "bm-container" });
for (const template in templates) {
const templateKey = template;
const templateValue = templates[template];
console.log(`Wzrd - Template Key: ${templateKey}`);
if (templates.hasOwnProperty(template)) {
const templateKeyArray = templateKey.split(" ");
const sortID = Number(templateKeyArray?.[0]);
@ -1767,11 +1790,6 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
const coords2 = templateValue?.coords?.split(",").map(Number);
const totalPixelCount = templateValue.pixels?.total ?? void 0;
const templateImage = void 0;
console.log("Sort ID:", sortID);
console.log("Author ID:", authorID);
console.log("Display Name:", displayName);
console.log("Coords", coords2);
console.log("Pixels:", totalPixelCount);
templateList.addDiv({ "class": "bm-container bm-flex-center" }).addDiv({ "class": "bm-flex-center", "style": "flex-direction: column; gap: 0;" }).addDiv({ "class": "bm-wizard-template-container-image", "textContent": templateImage || "\u{1F5BC}\uFE0F" }).buildElement().addSmall({ "textContent": `#${sortID}` }).buildElement().buildElement().addDiv({ "class": "bm-flex-center bm-wizard-template-container-flavor" }).addHeader(3, { "textContent": displayName }).buildElement().addSpan({ "textContent": `Uploaded by user #${authorID}` }).buildElement().addSpan({ "textContent": `Coordinates: ${coords2.join(", ")}` }).buildElement().addSpan({ "textContent": `Total Pixels: ${totalPixelCount || "???"}` }).buildElement().buildElement().buildElement();
}
}
@ -1925,7 +1943,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
);
return matchingTiles.length > 0;
}).reduce((sum, template) => sum + (template.pixelCount.total || 0), 0);
const pixelCountFormatted = new Intl.NumberFormat().format(totalPixels);
const pixelCountFormatted = localizeNumber(totalPixels);
this.overlay.handleDisplayStatus(
`Displaying ${templateCount} template${templateCount == 1 ? "" : "s"}.
Total pixels: ${pixelCountFormatted}`
@ -2237,8 +2255,8 @@ Could not fetch userdata.`);
chargeRefillTimer.dataset["endDate"] = Date.now() + (chargeData["max"] - chargeData["count"]) * chargeData["cooldownMs"];
}
}
overlay.updateInnerHTML("bm-user-droplets", `Droplets: <b>${new Intl.NumberFormat().format(dataJSON["droplets"])}</b>`);
overlay.updateInnerHTML("bm-user-nextlevel", `Next level in <b>${new Intl.NumberFormat().format(nextLevelPixels)}</b> pixel${nextLevelPixels == 1 ? "" : "s"}`);
overlay.updateInnerHTML("bm-user-droplets", `Droplets: <b>${localizeNumber(dataJSON["droplets"])}</b>`);
overlay.updateInnerHTML("bm-user-nextlevel", `Next level in <b>${localizeNumber(nextLevelPixels)}</b> pixel${nextLevelPixels == 1 ? "" : "s"}`);
break;
case "pixel":
const coordsTile = data["endpoint"].split("?")[0].split("/").filter((s) => s && !isNaN(Number(s)));
@ -2418,24 +2436,6 @@ Did you try clicking the canvas first?`);
this.templateManager = executor.apiManager?.templateManager;
this.eyeOpen = '<svg viewBox="0 .5 6 3"><path d="M0,2Q3-1 6,2Q3,5 0,2H2A1,1 0 1 0 3,1Q3,2 2,2"/></svg>';
this.eyeClosed = '<svg viewBox="0 1 12 6"><mask id="a"><path d="M0,0H12V8L0,2" fill="#fff"/></mask><path d="M0,4Q6-2 12,4Q6,10 0,4H4A2,2 0 1 0 6,2Q6,4 4,4ZM1,2L10,6.5L9.5,7L.5,2.5" mask="url(#a)"/></svg>';
this.localizeNumber = new Intl.NumberFormat();
this.localizePercent = new Intl.NumberFormat(void 0, {
style: "percent",
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
this.localizeDateTimeOptions = {
month: "long",
// July
day: "numeric",
// 23
hour: "2-digit",
// 17
minute: "2-digit",
// 47
second: "2-digit"
// 00
};
const { palette, LUT: _ } = this.templateManager.paletteBM;
this.palette = palette;
this.tilesLoadedTotal = 0;
@ -2512,11 +2512,11 @@ Did you try clicking the canvas first?`);
confettiManager.createConfetti(document.querySelector(`#${this.windowID}`));
}
const timeRemaining = new Date((allPixelsTotal - allPixelsCorrectTotal) * 30 * 1e3 + Date.now());
const timeRemainingLocalized = timeRemaining.toLocaleString(void 0, this.localizeDateTimeOptions);
this.updateInnerHTML("#bm-filter-tile-load", `<b>Tiles Loaded:</b> ${this.localizeNumber.format(this.tilesLoadedTotal)} / ${this.localizeNumber.format(this.tilesTotal)}`);
this.updateInnerHTML("#bm-filter-tot-correct", `<b>Correct Pixels:</b> ${this.localizeNumber.format(allPixelsCorrectTotal)}`);
this.updateInnerHTML("#bm-filter-tot-total", `<b>Total Pixels:</b> ${this.localizeNumber.format(allPixelsTotal)}`);
this.updateInnerHTML("#bm-filter-tot-remaining", `<b>Remaining:</b> ${this.localizeNumber.format((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0))} (${this.localizePercent.format(((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0)) / (allPixelsTotal || 1))})`);
const timeRemainingLocalized = localizeDate(timeRemaining);
this.updateInnerHTML("#bm-filter-tile-load", `<b>Tiles Loaded:</b> ${localizeNumber(this.tilesLoadedTotal)} / ${localizeNumber(this.tilesTotal)}`);
this.updateInnerHTML("#bm-filter-tot-correct", `<b>Correct Pixels:</b> ${localizeNumber(allPixelsCorrectTotal)}`);
this.updateInnerHTML("#bm-filter-tot-total", `<b>Total Pixels:</b> ${localizeNumber(allPixelsTotal)}`);
this.updateInnerHTML("#bm-filter-tot-remaining", `<b>Remaining:</b> ${localizeNumber((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0))} (${localizePercent(((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0)) / (allPixelsTotal || 1))})`);
this.updateInnerHTML("#bm-filter-tot-completed", `<b>Completed at:</b> <time datetime="${timeRemaining.toISOString().replace(/\.\d{3}Z$/, "Z")}">${timeRemainingLocalized}</time>`);
__privateMethod(this, _WindowFilter_instances, buildColorList_fn).call(this, scrollableContainer, allPixelsCorrect, allPixelsColor);
__privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, "id", "ascending", false);
@ -2543,14 +2543,14 @@ Did you try clicking the canvas first?`);
const colorTotalLocalized = this.localizeNumber.format(colorTotal);
let colorCorrect = 0;
let colorCorrectLocalized = "0";
let colorPercent = this.localizePercent.format(1);
let colorPercent = localizePercent(1);
if (colorTotal != 0) {
colorCorrect = allPixelsCorrect.get(color.id) ?? "???";
if (typeof colorCorrect != "number" && this.tilesLoadedTotal == this.tilesTotal && !!color.id) {
colorCorrect = 0;
}
colorCorrectLocalized = typeof colorCorrect == "string" ? colorCorrect : this.localizeNumber.format(colorCorrect);
colorPercent = isNaN(colorCorrect / colorTotal) ? "???" : this.localizePercent.format(colorCorrect / colorTotal);
colorPercent = isNaN(colorCorrect / colorTotal) ? "???" : localizePercent(colorCorrect / colorTotal);
}
const colorIncorrect = parseInt(colorTotal) - parseInt(colorCorrect);
const isColorHidden = !!(this.templateManager.shouldFilterColor.get(color.id) || false);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -51,7 +51,7 @@
<a href="https://discord.gg/tpeBPy46hf" target="_blank" rel="noopener noreferrer"><img alt="Contact Me" src="https://img.shields.io/badge/Contact_Me-gray?style=flat&logo=Discord&logoColor=white&logoSize=auto&labelColor=cornflowerblue"></a>
<a href="https://bluemarble.lol/" target="_blank" rel="noopener noreferrer"><img alt="Blue Marble Website" src="https://img.shields.io/badge/Blue_Marble_Website-crqch-blue?style=flat&logo=globe&logoColor=white"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="WakaTime" src="https://img.shields.io/badge/Coding_Time-169hrs_20mins-blue?style=flat&logo=wakatime&logoColor=black&logoSize=auto&labelColor=white"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-969-black?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Patches" src="https://img.shields.io/badge/Total_Patches-971-black?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Lines of Code" src="https://img.shields.io/badge/Lines_Of_Code-498-blue?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Total Comments" src="https://img.shields.io/badge/Lines_Of_Comments-498-blue?style=flat"></a>
<a href="" target="_blank" rel="noopener noreferrer"><img alt="Compression" src="https://img.shields.io/badge/Compression-70.19%25-blue"></a>

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "wplace-bluemarble",
"version": "0.88.471",
"version": "0.88.473",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wplace-bluemarble",
"version": "0.88.471",
"version": "0.88.473",
"devDependencies": {
"esbuild": "^0.25.0",
"jsdoc": "^4.0.5",

View file

@ -1,6 +1,6 @@
{
"name": "wplace-bluemarble",
"version": "0.88.471",
"version": "0.88.473",
"type": "module",
"homepage": "https://bluemarble.lol/",
"repository": {

View file

@ -2,7 +2,7 @@
// @name Blue Marble
// @name:en Blue Marble
// @namespace https://github.com/SwingTheVine/
// @version 0.88.471
// @version 0.88.473
// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA.
// @author SwingTheVine

View file

@ -1086,6 +1086,7 @@ export default class Overlay {
/** Adds a timer `time` element to the overlay.
* This timer will countdown until it reaches the end date that was passed in.
* Additionally, you can update the end date by changing the endDate dataset attribute on the element.
* Timer elements are not localized. Output is HH:MM:SS with no units.
* This timer will have properties shared between all timers in the overlay.
* You can override the shared properties by using a callback.
* @param {Date} [endDate=Date.now()] - The time to count down to.

View file

@ -1,6 +1,6 @@
import ConfettiManager from "./confetttiManager";
import Overlay from "./Overlay";
import { calculateRelativeLuminance } from "./utils";
import { calculateRelativeLuminance, localizeDate, localizeNumber, localizePercent } from "./utils";
/** The overlay builder for the color filter Blue Marble window.
* @description This class handles the overlay UI for the color filter window of the Blue Marble userscript.
@ -28,23 +28,6 @@ export default class WindowFilter extends Overlay {
this.eyeOpen = '<svg viewBox="0 .5 6 3"><path d="M0,2Q3-1 6,2Q3,5 0,2H2A1,1 0 1 0 3,1Q3,2 2,2"/></svg>';
this.eyeClosed = '<svg viewBox="0 1 12 6"><mask id="a"><path d="M0,0H12V8L0,2" fill="#fff"/></mask><path d="M0,4Q6-2 12,4Q6,10 0,4H4A2,2 0 1 0 6,2Q6,4 4,4ZM1,2L10,6.5L9.5,7L.5,2.5" mask="url(#a)"/></svg>';
// Localization formats
this.localizeNumber = new Intl.NumberFormat();
this.localizePercent = new Intl.NumberFormat(undefined, {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
// Localization string formatting for "Remaining Time" in color filter window.
// This is more of a hint than anything, as browsers seem to ignore it >:(
this.localizeDateTimeOptions = {
month: 'long', // July
day: 'numeric', // 23
hour: '2-digit', // 17
minute: '2-digit', // 47
second: '2-digit' // 00
}
// Obtains the color palette Blue Marble currently uses
const { palette: palette, LUT: _ } = this.templateManager.paletteBM;
this.palette = palette;
@ -216,14 +199,13 @@ export default class WindowFilter extends Overlay {
// Calculates the date & time the user will complete the templates
const timeRemaining = new Date(((allPixelsTotal - allPixelsCorrectTotal) * 30 * 1000) + Date.now());
const timeRemainingLocalized = timeRemaining.toLocaleString(undefined, this.localizeDateTimeOptions);
// "30" is seconds. "1000" converts to milliseconds. "undefined" forces the localization to be the users.
const timeRemainingLocalized = localizeDate(timeRemaining);
// Displays some template statistics to the user
this.updateInnerHTML('#bm-filter-tile-load', `<b>Tiles Loaded:</b> ${this.localizeNumber.format(this.tilesLoadedTotal)} / ${this.localizeNumber.format(this.tilesTotal)}`);
this.updateInnerHTML('#bm-filter-tot-correct', `<b>Correct Pixels:</b> ${this.localizeNumber.format(allPixelsCorrectTotal)}`);
this.updateInnerHTML('#bm-filter-tot-total', `<b>Total Pixels:</b> ${this.localizeNumber.format(allPixelsTotal)}`);
this.updateInnerHTML('#bm-filter-tot-remaining', `<b>Remaining:</b> ${this.localizeNumber.format((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0))} (${this.localizePercent.format(((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0)) / (allPixelsTotal || 1))})`);
this.updateInnerHTML('#bm-filter-tile-load', `<b>Tiles Loaded:</b> ${localizeNumber(this.tilesLoadedTotal)} / ${localizeNumber(this.tilesTotal)}`);
this.updateInnerHTML('#bm-filter-tot-correct', `<b>Correct Pixels:</b> ${localizeNumber(allPixelsCorrectTotal)}`);
this.updateInnerHTML('#bm-filter-tot-total', `<b>Total Pixels:</b> ${localizeNumber(allPixelsTotal)}`);
this.updateInnerHTML('#bm-filter-tot-remaining', `<b>Remaining:</b> ${localizeNumber((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0))} (${localizePercent(((allPixelsTotal || 0) - (allPixelsCorrectTotal || 0)) / (allPixelsTotal || 1))})`);
this.updateInnerHTML('#bm-filter-tot-completed', `<b>Completed at:</b> <time datetime="${timeRemaining.toISOString().replace(/\.\d{3}Z$/, 'Z')}">${timeRemainingLocalized}</time>`);
// These run when the user opens the Color Filter window
@ -269,7 +251,7 @@ export default class WindowFilter extends Overlay {
// This will be displayed if the total pixels for this color is zero
let colorCorrect = 0;
let colorCorrectLocalized = '0';
let colorPercent = this.localizePercent.format(1);
let colorPercent = localizePercent(1);
// This will be displayed if the total pixels for this color is non-zero
if (colorTotal != 0) {
@ -281,7 +263,7 @@ export default class WindowFilter extends Overlay {
}
colorCorrectLocalized = (typeof colorCorrect == 'string') ? colorCorrect : this.localizeNumber.format(colorCorrect);
colorPercent = isNaN(colorCorrect / colorTotal) ? '???' : this.localizePercent.format(colorCorrect / colorTotal);
colorPercent = isNaN(colorCorrect / colorTotal) ? '???' : localizePercent(colorCorrect / colorTotal);
}
// There are four outcomes:
// 1. The correct pixel count is displayed, because there are correct pixels.

View file

@ -149,18 +149,12 @@ export default class WindowWizard extends Overlay {
#displayTemplateList() {
const templates = this.currentJSON?.templates; // Templates in user storage
console.log('Loading Template Wizard...');
console.log(templates);
console.log(Object.keys(templates).length);
// If there is at least one template loaded...
if (Object.keys(templates).length > 0) {
// Obtains the parent element for the template list
const templateListParentElement = document.querySelector(`#${this.windowID} .bm-scrollable`);
console.log(templateListParentElement);
// Creates the template list DOM tree
const templateList = new Overlay(this.name, this.version);
@ -171,7 +165,6 @@ export default class WindowWizard extends Overlay {
const templateKey = template; // The identification key for the template. E.g., "0 $Z"
const templateValue = templates[template]; // The actual content of the template
console.log(`Wzrd - Template Key: ${templateKey}`);
// If the template is a direct child of the templates Object...
if (templates.hasOwnProperty(template)) {
@ -185,12 +178,6 @@ export default class WindowWizard extends Overlay {
const totalPixelCount = templateValue.pixels?.total ?? undefined;
const templateImage = undefined; // TODO: Add template image
console.log('Sort ID:', sortID);
console.log('Author ID:', authorID);
console.log('Display Name:', displayName);
console.log('Coords', coords);
console.log('Pixels:', totalPixelCount);
templateList.addDiv({'class': 'bm-container bm-flex-center'})
.addDiv({'class': 'bm-flex-center', 'style': 'flex-direction: column; gap: 0;'})
.addDiv({'class': 'bm-wizard-template-container-image', 'textContent': templateImage || '🖼️'})
@ -212,6 +199,4 @@ export default class WindowWizard extends Overlay {
templateList.buildElement().buildOverlay(templateListParentElement);
}
}
}

View file

@ -5,7 +5,7 @@
*/
import TemplateManager from "./templateManager.js";
import { consoleError, escapeHTML, numberToEncoded, serverTPtoDisplayTP } from "./utils.js";
import { consoleError, escapeHTML, localizeNumber, numberToEncoded, serverTPtoDisplayTP } from "./utils.js";
export default class ApiManager {
@ -90,8 +90,8 @@ export default class ApiManager {
}
// Updates displayed droplet information
overlay.updateInnerHTML('bm-user-droplets', `Droplets: <b>${new Intl.NumberFormat().format(dataJSON['droplets'])}</b>`); // Updates the text content of the droplets field
overlay.updateInnerHTML('bm-user-nextlevel', `Next level in <b>${new Intl.NumberFormat().format(nextLevelPixels)}</b> pixel${nextLevelPixels == 1 ? '' : 's'}`); // Updates the text content of the next level field
overlay.updateInnerHTML('bm-user-droplets', `Droplets: <b>${localizeNumber(dataJSON['droplets'])}</b>`); // Updates the text content of the droplets field
overlay.updateInnerHTML('bm-user-nextlevel', `Next level in <b>${localizeNumber(nextLevelPixels)}</b> pixel${nextLevelPixels == 1 ? '' : 's'}`); // Updates the text content of the next level field
break;
case 'pixel': // Request to retrieve pixel data

View file

@ -1,5 +1,5 @@
import Template from "./Template";
import { base64ToUint8, colorpaletteForBlueMarble, numberToEncoded } from "./utils";
import { base64ToUint8, colorpaletteForBlueMarble, localizeNumber, numberToEncoded } from "./utils";
import WindowWizard from "./WindowWizard";
/** Manages the template system.
@ -250,7 +250,7 @@ export default class TemplateManager {
// Format pixel count with locale-appropriate thousands separators for better readability
// Examples: "1,234,567" (US), "1.234.567" (DE), "1 234 567" (FR)
const pixelCountFormatted = new Intl.NumberFormat().format(totalPixels);
const pixelCountFormatted = localizeNumber(totalPixels);
// Display status information about the templates being rendered
this.overlay.handleDisplayStatus(

View file

@ -1,4 +1,73 @@
/** Returns the localized number format.
* @param {number} number - The number to localize
* @since 0.88.472
* @returns {string} Localized number as a string
*/
export function localizeNumber(number) {
const numberFormat = new Intl.NumberFormat();
return numberFormat.format(number);
}
/** Returns the localized percentage format.
* @param {number} percent - The percentage to localize
* @since 0.88.472
* @returns {string} Localized percentage as a string
*/
export function localizePercent(percent) {
const percentFormat = new Intl.NumberFormat(undefined, {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
return percentFormat.format(percent);
}
/** Returns the localized date format.
* @param {number} date - The date to localize
* @since 0.88.472
* @returns {string} Localized date as a string
*/
export function localizeDate(date) {
const options = {
month: 'long', // July
day: 'numeric', // 23
hour: '2-digit', // 17
minute: '2-digit', // 47
second: '2-digit' // 00
};
return date.toLocaleString(undefined, options);
}
/** Returns the localized duration format.
* @param {number} durationTotalMs - The duration to localize, in milliseconds
* @since 0.88.472
* @returns {string} Localized duration as a string
*/
export function localizeDuration(durationTotalMs) {
// "Total" indicates it is the total time for that unit. E.g. 62 minutes is "62" minutes.
const durationTotalSec = Math.floor(durationTotalMs / 1000);
const durationTotalHr = Math.floor(durationTotalSec / 3600);
// "Only" indicates it is formatted in that unit. E.g. 62 minutes is "2" minutes.
const durationOnlySec = Math.floor(durationTotalSec % 60);
const durationOnlyMin = Math.floor((durationTotalSec % 3600) / 60);
// Duration Object for localization
const duration = {
hours: durationTotalHr,
minutes: durationOnlyMin,
seconds: durationOnlySec
};
// Options Object for localization
const options = {
style: 'short'
};
return new Intl.DurationFormat(undefined, options).format(duration);
}
/** Sanitizes HTML to display as plain-text.
* This prevents some Cross Site Scripting (XSS).