This commit is contained in:
604
src/api/locale.js
Normal file
604
src/api/locale.js
Normal file
@@ -0,0 +1,604 @@
|
||||
//Browser Locale conversion utilities
|
||||
//dates,numbers currency etc
|
||||
export default {
|
||||
////////////////////////////////////////////////////////
|
||||
// attempt to determine user's preferred language settings
|
||||
// As of Jan 2020 all major browsers support
|
||||
// navigator.languages
|
||||
// but some use navigator.language (singular) to denote UI language preference
|
||||
// not browsing language preference
|
||||
// so the ideal way to do this is to use navigator.languages[0] for the preferred language
|
||||
// and ignore the singular property since we don't care about the actual browser UI language
|
||||
// only how the user expects to see the page itself
|
||||
//
|
||||
// also for sake of future proofing and edge cases need to have it be manually settable as well
|
||||
//
|
||||
//https://appmakers.dev/bcp-47-language-codes-list/
|
||||
///////////////////////////////////////////
|
||||
// Get users default language code
|
||||
// first check if overriden in useroptions
|
||||
// if not then use browsers own setting
|
||||
//if not that then final default of en-US
|
||||
getResolvedLanguage() {
|
||||
let l = window.$gz.store.state.userOptions.languageOverride;
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(l)) {
|
||||
return l;
|
||||
} else {
|
||||
l = window.navigator.languages[0];
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(l)) {
|
||||
return l;
|
||||
} else {
|
||||
return "en-US";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Get users default time zone
|
||||
// first check if overriden in useroptions
|
||||
// if not then use browsers own setting
|
||||
// if that is empty then final default of "America/New_York"
|
||||
//https://www.iana.org/time-zones
|
||||
//https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
getResolvedTimeZoneName() {
|
||||
let tz = window.$gz.store.state.userOptions.timeZoneOverride;
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(tz)) {
|
||||
return tz;
|
||||
} else {
|
||||
tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(tz)) {
|
||||
return tz;
|
||||
} else {
|
||||
return "America/New_York";
|
||||
}
|
||||
}
|
||||
},
|
||||
//////////////////////////////////////////////////
|
||||
// Get the user's chosen currency name
|
||||
//https://en.wikipedia.org/wiki/ISO_4217
|
||||
//default to USD if nothing specified
|
||||
getCurrencyName() {
|
||||
const cur = window.$gz.store.state.userOptions.currencyName;
|
||||
if (!window.$gz.util.stringIsNullOrEmpty(cur)) {
|
||||
return cur;
|
||||
} else {
|
||||
return "USD";
|
||||
}
|
||||
},
|
||||
//////////////////////////////////////////////////
|
||||
// Get the user's chosen 12hr clock
|
||||
//
|
||||
getHour12() {
|
||||
return window.$gz.store.state.userOptions.hour12;
|
||||
},
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Turn a utc ISO date from server into a vuetify calendar
|
||||
// schedule control compatible (epoch) format
|
||||
// localized.
|
||||
// For ease of use in schedule the epoch (milliseconds) is the best format
|
||||
// "It must be a Date, number of seconds since Epoch, or a string in the format of YYYY-MM-DD or YYYY-MM-DD hh:mm. Zero-padding is optional and seconds are ignored.""
|
||||
//
|
||||
//
|
||||
utcDateToScheduleCompatibleFormatLocalized(value, timeZoneName) {
|
||||
//This function takes a UTC iso format date string, parses it into a date then converts that date to the User's configured time zone
|
||||
//outputs that in a format close to ISO, fixes the space in the middle of the output to match ISO 8601 format then returns as an
|
||||
//epoch
|
||||
//this is to support controls that are not time zone settable so they are always in local browser time zone of os, however user may be operating
|
||||
//sockeye in another desired time zone so this is all to support that scenario
|
||||
|
||||
if (!value) {
|
||||
if (window.$gz.dev) {
|
||||
throw new Error(
|
||||
`locale::utcDateToScheduleCompatibleFormatLocalized - Value is empty`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Date(
|
||||
new Date(value) //convert to locale timezone and output in the closest thing to iso-8601 format
|
||||
.toLocaleString("sv-SE", {
|
||||
timeZone: timeZoneName
|
||||
})
|
||||
.replace(" ", "T") //Safari can't parse the date from here because sv-SE puts a space between date and time and Safari will only parse if it has a T between
|
||||
).getTime();
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Convert a local schedule epoch timestamp
|
||||
// to specified time zone equivalent then
|
||||
// to UTC and output as ISO 8601
|
||||
//
|
||||
//
|
||||
localScheduleFormatToUTC8601String(value, timeZoneName) {
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
|
||||
//input: epoch in local browser time zone
|
||||
//output: transform to date and time string
|
||||
//convert to desired time zone but at same time and date
|
||||
//(i.e. if it browser is vancouver and 1pm is selected but desired is new york's 1pm
|
||||
// so convert the string as if it was new york then back to iso so that the time is adjusted forward
|
||||
// as if the user was in new york in their browser default)
|
||||
|
||||
//parse in the time in to the specified timezone
|
||||
let ret = window.$gz.DateTime.fromISO(
|
||||
//output the sched epoch as local time string without zone
|
||||
new Date(value).toLocaleString("sv-SE").replace(" ", "T"),
|
||||
{
|
||||
zone: timeZoneName
|
||||
}
|
||||
);
|
||||
|
||||
ret = ret.setZone("utc"); //convert to UTC
|
||||
ret = ret.toISO(); //output as ISO 8601
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a utc date into a displayable
|
||||
// short date and time
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
|
||||
//
|
||||
utcDateToShortDateAndTimeLocalized(
|
||||
value,
|
||||
timeZoneName,
|
||||
languageName,
|
||||
hour12
|
||||
) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
|
||||
if (!hour12) {
|
||||
hour12 = this.getHour12();
|
||||
}
|
||||
|
||||
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
|
||||
const parsedDate = new Date(value);
|
||||
|
||||
//is it a valid date?
|
||||
if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
|
||||
return "not valid";
|
||||
}
|
||||
|
||||
return parsedDate.toLocaleString(languageName, {
|
||||
timeZone: timeZoneName,
|
||||
dateStyle: "short",
|
||||
timeStyle: "short",
|
||||
hour12: hour12
|
||||
});
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a utc date into a displayable
|
||||
// date and time with specific formats
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
|
||||
//
|
||||
utcDateToSpecifiedDateAndTimeLocalized(
|
||||
value,
|
||||
timeZoneName,
|
||||
languageName,
|
||||
hour12,
|
||||
dateStyle,
|
||||
timeStyle
|
||||
) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
|
||||
if (!hour12) {
|
||||
hour12 = this.getHour12();
|
||||
}
|
||||
|
||||
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
|
||||
const parsedDate = new Date(value);
|
||||
|
||||
//is it a valid date?
|
||||
if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
|
||||
return "not valid";
|
||||
}
|
||||
|
||||
return parsedDate.toLocaleString(languageName, {
|
||||
timeZone: timeZoneName,
|
||||
dateStyle: dateStyle,
|
||||
timeStyle: timeStyle,
|
||||
hour12: hour12
|
||||
});
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a utc date into a displayable
|
||||
// short date
|
||||
//
|
||||
utcDateToShortDateLocalized(value, timeZoneName, languageName) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
|
||||
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
|
||||
const parsedDate = new Date(value);
|
||||
|
||||
//is it a valid date?
|
||||
if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
|
||||
return "not valid";
|
||||
}
|
||||
|
||||
return parsedDate.toLocaleDateString(languageName, {
|
||||
timeZone: timeZoneName,
|
||||
dateStyle: "short"
|
||||
});
|
||||
}, ///////////////////////////////////////////
|
||||
// Turn a utc date into a displayable
|
||||
// short time
|
||||
//
|
||||
utcDateToShortTimeLocalized(value, timeZoneName, languageName, hour12) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
|
||||
if (!hour12) {
|
||||
hour12 = this.getHour12();
|
||||
}
|
||||
|
||||
//parse the date which is identified as utc ("2020-02-06T18:18:49.148011Z")
|
||||
const parsedDate = new Date(value);
|
||||
|
||||
//is it a valid date?
|
||||
if (!(parsedDate instanceof Date && !isNaN(parsedDate))) {
|
||||
return "not valid";
|
||||
}
|
||||
|
||||
return parsedDate.toLocaleTimeString(languageName, {
|
||||
timeZone: timeZoneName,
|
||||
timeStyle: "short",
|
||||
hour12: hour12
|
||||
});
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a duration value into a display
|
||||
//
|
||||
durationLocalized(value, hideSeconds) {
|
||||
if (value == null || value == "00:00:00") {
|
||||
return "";
|
||||
}
|
||||
|
||||
let theDays = 0;
|
||||
let theHours = 0;
|
||||
let theMinutes = 0;
|
||||
let theSeconds = 0;
|
||||
let ret = "";
|
||||
|
||||
const work = value.split(":");
|
||||
//has days?
|
||||
if (work[0].includes(".")) {
|
||||
let dh = work[0].split(".");
|
||||
theDays = Number(dh[0]);
|
||||
theHours = Number(dh[1]);
|
||||
} else {
|
||||
theHours = Number(work[0]);
|
||||
}
|
||||
theMinutes = Number(work[1]);
|
||||
//has milliseconds? (ignore them)
|
||||
if (work[2].includes(".")) {
|
||||
let dh = work[2].split(".");
|
||||
theSeconds = Number(dh[0]);
|
||||
} else {
|
||||
theSeconds = Number(work[2]);
|
||||
}
|
||||
|
||||
if (theDays != 0) {
|
||||
ret += theDays + " " + window.$gz.translation.get("TimeSpanDays") + " ";
|
||||
}
|
||||
if (theHours != 0) {
|
||||
ret += theHours + " " + window.$gz.translation.get("TimeSpanHours") + " ";
|
||||
}
|
||||
if (theMinutes != 0) {
|
||||
ret +=
|
||||
theMinutes + " " + window.$gz.translation.get("TimeSpanMinutes") + " ";
|
||||
}
|
||||
if (!hideSeconds && theSeconds != 0) {
|
||||
ret +=
|
||||
theSeconds + " " + window.$gz.translation.get("TimeSpanSeconds") + " ";
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Convert a utc date to local time zone
|
||||
// and return time portion only in iso 8601
|
||||
// format (used by time and date picker components)
|
||||
//
|
||||
utcDateStringToLocal8601TimeOnlyString(value, timeZoneName) {
|
||||
if (!value) {
|
||||
//if no value, return the current time as expected by the time picker
|
||||
} else {
|
||||
//ok, the reason for sv-SE is that it's a locale that returns the time already in ISO format and 24hr by default
|
||||
//that can change over time so if this breaks that's why
|
||||
//also fr-CA does as well as possibly en-CA
|
||||
//https://stackoverflow.com/a/58633686/8939
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
|
||||
return new Date(value).toLocaleTimeString("sv-SE", {
|
||||
timeZone: timeZoneName
|
||||
});
|
||||
}
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Convert a local time only string with date string
|
||||
// to UTC and output as ISO 8601
|
||||
// also converts to time zone specified if diff from browser
|
||||
// (used by time and date picker components)
|
||||
//
|
||||
localTimeDateStringToUTC8601String(value, timeZoneName) {
|
||||
//https://moment.github.io/luxon/docs/manual/zones.html#creating-datetimes-in-a-zone
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
|
||||
//parse in the time in the currently used timezone
|
||||
let ret = window.$gz.DateTime.fromISO(value, {
|
||||
zone: timeZoneName
|
||||
});
|
||||
|
||||
ret = ret.setZone("utc"); //convert to UTC
|
||||
ret = ret.toISO(); //output as ISO 8601
|
||||
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// UTC Now in api format
|
||||
// to UTC and output as ISO 8601
|
||||
// (used to set defaults)
|
||||
//
|
||||
nowUTC8601String(timeZoneName) {
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
const ret = window.$gz.DateTime.local()
|
||||
.setZone(timeZoneName)
|
||||
.toUTC()
|
||||
.toString();
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// UTC ISO 8601 string add minutes
|
||||
// and return as UTC ISO 8601 string
|
||||
// (used to set automatic / default adjusted times)
|
||||
//
|
||||
addMinutesToUTC8601String(val, minutes) {
|
||||
if (!val || val == "" || minutes == null || minutes == 0) {
|
||||
return val;
|
||||
}
|
||||
//instantiate a luxon date object from val which is assumed to be an iso string
|
||||
let dt = window.$gz.DateTime.fromISO(val);
|
||||
if (!dt.isValid) {
|
||||
console.error("locale::addMinutes, input not valid:", {
|
||||
val: val,
|
||||
dt: dt
|
||||
});
|
||||
return val;
|
||||
}
|
||||
//add minutes
|
||||
dt = dt.plus({ minutes: minutes });
|
||||
return dt.toUTC().toString();
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// UTC ISO 8601 string add arbitrary value based
|
||||
// on luxon duration format
|
||||
// and return as UTC ISO 8601 string
|
||||
//https://moment.github.io/luxon/api-docs/index.html#datetimeplus
|
||||
//
|
||||
addDurationToUTC8601String(val, duration) {
|
||||
if (
|
||||
!val ||
|
||||
val == "" ||
|
||||
duration == null ||
|
||||
!typeof duration === "object"
|
||||
) {
|
||||
return val;
|
||||
}
|
||||
//instantiate a luxon date object from val which is assumed to be an iso string
|
||||
let dt = window.$gz.DateTime.fromISO(val);
|
||||
if (!dt.isValid) {
|
||||
console.error("locale::addDurationToUTC8601String, input not valid:", {
|
||||
val: val,
|
||||
dt: dt
|
||||
});
|
||||
return val;
|
||||
}
|
||||
//add minutes
|
||||
dt = dt.plus(duration);
|
||||
return dt.toUTC().toString();
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// parse UTC ISO 8601 strings, diff, return hours
|
||||
//
|
||||
diffHoursFromUTC8601String(start, stop) {
|
||||
if (!start || start == "" || !stop == null || stop == "") {
|
||||
return 0;
|
||||
}
|
||||
//instantiate a luxon date object from val which is assumed to be an iso string
|
||||
const startDate = window.$gz.DateTime.fromISO(start);
|
||||
if (!startDate.isValid) {
|
||||
console.error("locale::diffHours, start not valid:", {
|
||||
start: start,
|
||||
startDate: startDate
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
const stopDate = window.$gz.DateTime.fromISO(stop);
|
||||
if (!stopDate.isValid) {
|
||||
console.error("locale::diffHours, start not valid:", {
|
||||
stop: stop,
|
||||
stopDate: stopDate
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// "locale:diffhours...",
|
||||
// stopDate.diff(startDate, "hours").toObject().hours
|
||||
// );
|
||||
|
||||
// console.log(
|
||||
// "locale:diffhours.. ROUNDED.",
|
||||
// window.$gz.util.roundAccurately(
|
||||
// stopDate.diff(startDate, "hours").toObject().hours,
|
||||
// 2
|
||||
// )
|
||||
// );
|
||||
|
||||
return window.$gz.util.roundAccurately(
|
||||
stopDate.diff(startDate, "hours").toObject().hours,
|
||||
2
|
||||
);
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Local now timestamp converted to timeZoneName
|
||||
// and output as ISO 8601
|
||||
// (used to inform server of local client time)
|
||||
//
|
||||
clientLocalZoneTimeStamp(timeZoneName) {
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
const ret = window.$gz.DateTime.local()
|
||||
.setZone(timeZoneName)
|
||||
.toString();
|
||||
return ret;
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Get default start date time in api format
|
||||
// (this is used to centralize and for future)
|
||||
defaultStartDateTime() {
|
||||
return {
|
||||
start: window.$gz.DateTime.local()
|
||||
.toUTC()
|
||||
.toString(),
|
||||
end: window.$gz.DateTime.local()
|
||||
.plus({ hours: 1 })
|
||||
.toUTC()
|
||||
.toString()
|
||||
};
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Convert a utc date to local time zone
|
||||
// and return date only portion only in iso 8601
|
||||
// format (used by time and date picker components)
|
||||
//
|
||||
utcDateStringToLocal8601DateOnlyString(value, timeZoneName) {
|
||||
if (!value) {
|
||||
//if no value, return the current time as expected by the time picker
|
||||
} else {
|
||||
//ok, the reason for sv-SE is that it's a locale that returns the time already in ISO format and 24hr by default
|
||||
//that can change over time so if this breaks that's why
|
||||
//also fr-CA does as well as possibly en-CA
|
||||
//https://stackoverflow.com/a/58633686/8939
|
||||
if (!timeZoneName) {
|
||||
timeZoneName = this.getResolvedTimeZoneName();
|
||||
}
|
||||
return new Date(value).toLocaleDateString("sv-SE", {
|
||||
timeZone: timeZoneName
|
||||
});
|
||||
}
|
||||
},
|
||||
///////////////////////////////////////////////
|
||||
// Date/time past or future evaluation
|
||||
//
|
||||
dateIsPast(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Date(value) < new Date();
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a decimal number into a local
|
||||
// currency display
|
||||
//
|
||||
currencyLocalized(value, languageName, currencyName) {
|
||||
if (value == null) return "";
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
if (!currencyName) {
|
||||
currencyName = this.getCurrencyName();
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat(languageName, {
|
||||
style: "currency",
|
||||
currency: currencyName
|
||||
}).format(value);
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a decimal number into a local
|
||||
// decimal format display
|
||||
//
|
||||
decimalLocalized(value, languageName) {
|
||||
if (value == null) return "";
|
||||
if (!languageName) {
|
||||
languageName = this.getResolvedLanguage();
|
||||
}
|
||||
//This forces 2 digits after the decimal
|
||||
// return new Intl.NumberFormat(languageName, {
|
||||
// minimumFractionDigits: 2
|
||||
// }).format(value);
|
||||
//this goes with whatever is the local format which for dev testing turned out to be perfect: 1.00 displays as 1 and 1.75 displays as 1.75
|
||||
//alignment goes out the window but it follows v7 format
|
||||
return new Intl.NumberFormat(languageName).format(value);
|
||||
},
|
||||
///////////////////////////////////////////
|
||||
// Turn a file / memory size number into a local
|
||||
// decimal format display and in reasonable human readable range
|
||||
//
|
||||
humanFileSize(bytes, languageName, si = false, dp = 1) {
|
||||
const thresh = si ? 1000 : 1024;
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + " B";
|
||||
}
|
||||
|
||||
const units = si
|
||||
? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
||||
: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
||||
let u = -1;
|
||||
const r = 10 ** dp;
|
||||
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (
|
||||
Math.round(Math.abs(bytes) * r) / r >= thresh &&
|
||||
u < units.length - 1
|
||||
);
|
||||
|
||||
return (
|
||||
this.decimalLocalized(bytes.toFixed(dp), languageName) + " " + units[u]
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user