Use Intl.DateTimeFormat instead of momentjs where we can

Need to solve the Intl.RelativeTimeFormat to drop momentjs
This commit is contained in:
djmaze
2020-10-15 00:26:40 +02:00
parent a177081224
commit 6f585e1f1d
5 changed files with 115 additions and 214 deletions

View File

@@ -12,13 +12,14 @@ export function format(timeStampInUTC, formatStr) {
case 'SHORT': {
if (4 >= (now - time) / 3600000)
return m.fromNow();
const ymd = m.format('Ymd'), date = new Date;
if (date.format('Ymd') === ymd)
const mt = m.getTime(), date = new Date,
dt = date.setHours(0,0,0,0);
if (mt > dt)
return i18n('MESSAGE_LIST/TODAY_AT', {TIME: m.format('LT')});
if (new Date(now - 86400000).format('Ymd') === ymd)
if (mt > dt - 86400000)
return i18n('MESSAGE_LIST/YESTERDAY_AT', {TIME: m.format('LT')});
if (date.getFullYear() === m.getFullYear())
return m.format('d M.');
return m.format('d M');
return m.format('LL');
}
case 'FULL':

View File

@@ -132,26 +132,11 @@ export function i18nToNodes(element) {
, 1);
}
const reloadData = () => {
if (window.rainloopI18N) {
I18N_DATA = window.rainloopI18N || {};
i18nToNodes(doc);
dispatchEvent(new CustomEvent('reload-time'));
trigger(!trigger());
}
window.rainloopI18N = null;
};
/**
* @returns {void}
*/
export function initNotificationLanguage() {
I18N_NOTIFICATION_MAP.forEach((item) => {
I18N_NOTIFICATION_DATA[item[0]] = i18n(item[1]);
});
I18N_NOTIFICATION_MAP.forEach(item => I18N_NOTIFICATION_DATA[item[0]] = i18n(item[1]));
}
/**
@@ -159,22 +144,9 @@ export function initNotificationLanguage() {
* @param {Function=} langCallback = null
*/
export function initOnStartOrLangChange(startCallback, langCallback = null) {
if (startCallback) {
startCallback();
}
if (langCallback) {
trigger.subscribe(() => {
if (startCallback) {
startCallback();
}
if (langCallback) {
langCallback();
}
});
} else if (startCallback) {
trigger.subscribe(startCallback);
}
startCallback && startCallback();
startCallback && trigger.subscribe(startCallback);
langCallback && trigger.subscribe(langCallback);
}
/**
@@ -251,11 +223,8 @@ export function reload(admin, language) {
return new Promise((resolve, reject) => {
return fetch(langLink(language, admin), {cache: 'reload'})
.then(response => {
if (response.ok) {
const type = response.headers.get('Content-Type');
if (type.includes('application/javascript')) {
return response.text();
}
if (response.ok && response.headers.get('Content-Type').includes('application/javascript')) {
return response.text();
}
reject(new Error('Invalid response'))
}, error => {
@@ -267,15 +236,14 @@ export function reload(admin, language) {
doc.head.appendChild(script).remove();
setTimeout(
() => {
reloadData();
const isRtl = ['ar', 'ar_sa', 'he', 'he_he', 'ur', 'ur_ir'].includes((language || '').toLowerCase()),
htmlCL = doc.documentElement.classList;
htmlCL.remove('rl-rtl', 'rl-ltr');
htmlCL.add(isRtl ? 'rl-rtl' : 'rl-ltr');
// doc.documentElement.dir = isRtl ? 'rtl' : 'ltr'
// reload the data
if (window.rainloopI18N) {
I18N_DATA = window.rainloopI18N || {};
i18nToNodes(doc);
dispatchEvent(new CustomEvent('reload-time'));
trigger(!trigger());
}
window.rainloopI18N = null;
resolve();
},
500 < Date.now() - start ? 1 : 500

View File

@@ -31,12 +31,8 @@ class SettingsUserScreen extends AbstractSettingsScreen {
super([SystemDropDownSettingsUserView, MenuSettingsUserView, PaneSettingsUserView]);
initOnStartOrLangChange(
() => {
this.sSettingsTitle = i18n('TITLES/SETTINGS');
},
() => {
this.setSettingsTitle();
}
() => this.sSettingsTitle = i18n('TITLES/SETTINGS'),
() => this.setSettingsTitle()
);
}

200
dev/prototype.js vendored
View File

@@ -8,28 +8,62 @@
// Import momentjs locales function
w.moment = {
defineLocale: (name, config)=>{
locale = config;
const m = config.monthsShort;
if (Array.isArray(m)) {
Date.shortMonths = m;
} else for (let i = 0; i < 12; ++i) {
Date.shortMonths[i] = config.monthsShort({month:()=>i}, '-MMM-');
}
Date.longMonths = config.months,
Date.longDays = config.weekdays;
Date.shortDays = config.weekdaysMin;
}
defineLocale: (name, config) => locale = config
};
let locale = {
longDateFormat: {
LT : 'h:mm A', // 'g:i A',
L : 'MM/DD/YYYY', // 'Y-m-d',
LL : 'MMMM D, YYYY', // 'F j, Y'
LLL : 'MMMM D, YYYY h:mm A', // 'F j, Y g:i A'
LLLL : 'dddd, MMMM D, YYYY h:mm A' // 'l, F j, Y g:i A'
},
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
let formats = {
LT : {hour: 'numeric', minute: 'numeric'},
L : {},
LL : {year: 'numeric', month: 'short', day: 'numeric'},
LLL : {year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric'},
LLLL : {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric'},
'd M': {day: '2-digit', month: 'short'}
},
phpFormats = {
// Day
d: {day: '2-digit'},
D: {weekday: 'short'},
j: {day: 'numeric'},
l: {weekday: 'long'},
// N: {},
// w: {},
// z: {},
// Week
// W: {},
// Month
F: {month: 'long'},
m: {month: '2-digit'},
M: {month: 'short'},
n: {month: 'numeric'},
// t: {},
// Year
// L: {},
// o: {},
Y: {year: 'numeric'},
y: {year: '2-digit'},
// Time
a: {hour12: true},
A: {hour12: true},
g: {hour: 'numeric', hourCycle: 'h12'},
G: {hour: 'numeric'},
h: {hour: '2-digit', hourCycle: 'h12'},
H: {hour: '2-digit'},
i: {minute: '2-digit'},
s: {second: '2-digit'},
u: {fractionalSecondDigits: 3},
// Timezone
// O: return UTC ? 'Z' : (d.Z > 0 ? '+' : '-') + pad2(Math.abs(d.Z / 60)) + '00';
// P: return UTC ? 'Z' : (d.Z > 0 ? '+' : '-') + pad2(Math.abs(d.Z / 60)) + :' + pad2(Math.abs(d.Z % 60));
// T: return UTC ? 'UTC' : new Date(d.Y, 0, 1).toTimeString().replace(/^.+ \(?([^)]+)\)?$/, '$1');
Z: {timeZone: 'UTC'}
// Full Date/Time
// c: {},
// r: {},
// U: {},
// options.timeZoneName = 'short';
},
locale = {
relativeTime : {
future : 'in %s',
past : '%s ago',
@@ -47,117 +81,29 @@
yy : '%d years'
}
},
pad2 = v => 10 > v ? '0' + v : v,
getISODay = x => x.getDay() || 7,
getDayOfYear = x => Math.floor((Date.UTC(x.getFullYear(),x.getMonth(),x.getDate())
- Date.UTC(x.getFullYear(),0,1)) / 86400000),
getWeek = x => {
let d = new Date(x.getFullYear(),0,1),
wd = getISODay(d),
w = Math.ceil((getDayOfYear(x)+wd) / 7);
/* ISO 8601 states that week 1 is the week with january 4th in it */
if (4 < wd) --w;
return (1 > w
? getWeek(new Date(x.getFullYear()-1,11,31)) /* previous year, last week */
: (52 < w && 4 > getISODay(x) ? 1 /* next year, first week */ : w) );
};
// Defining locale
Date.shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date.longMonths = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
Date.shortDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
Date.longDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
pad2 = v => 10 > v ? '0' + v : v;
// Simulate PHP's date function
Date.prototype.format = function (str, UTC) {
if (locale.longDateFormat[str]) {
str = locale.longDateFormat[str]
.replace('YYYY', 'Y')
.replace(/(^|[^M])M([^M]|$)/, '$1n$2')
.replace('MMMM', 'F')
.replace('MMM', 'M')
.replace('MM', 'm')
.replace(/(^|[^D])D([^D]|$)/, '$1j$2')
.replace('DD', 'd')
.replace('dddd', 'l')
.replace('ddd', 'D')
.replace('dd', 'D')
.replace(/(^|[^H])H([^H]|$)/, '$1G$2')
.replace('HH', 'H')
.replace('h', 'g')
.replace('hh', 'h')
.replace('mm', 'i');
if ('Y-m-d\\TH:i:s' == str) {
return this.getFullYear() + '-' + pad2(1 + this.getMonth()) + '-' + pad2(this.getDate())
+ 'T' + pad2(this.getHours()) + ':' + pad2(this.getMinutes()) + ':' + pad2(this.getSeconds());
}
UTC = UTC || str.match(/\\Z$/);
let x = this,
d = UTC ? {
D: x.getUTCDay(),
Y: x.getUTCFullYear(),
m: x.getUTCMonth(),
d: x.getUTCDate(),
H: x.getUTCHours(),
Z: 0
} : {
D: x.getDay(),
Y: x.getFullYear(),
m: x.getMonth(),
d: x.getDate(),
H: x.getHours(),
Z: -x.getTimezoneOffset()
};
return str
? str.replace(/\\?[a-zA-Z]/g, m => {
if (m[0] === '\\') { return m[1]; }
switch (m) {
// Day
case 'd': return pad2(d.d);
case 'D': return Date.shortDays[d.D];
case 'j': return d.d;
case 'l': return Date.longDays[d.D];
case 'N': return getISODay(x);
case 'w': return d.D;
case 'z': return getDayOfYear(x);
// Week
case 'W': return pad2(getWeek(x));
// Month
case 'F': return Date.longMonths[d.m];
case 'm': return pad2(d.m + 1);
case 'M': return Date.shortMonths[d.m];
case 'n': return d.m + 1;
case 't': return 32 - new Date(x.getFullYear(), x.getMonth(), 32).getDate();
// Year
case 'L': return (((d.Y%4===0)&&(d.Y%100 !== 0)) || (d.Y%400===0)) ? '1' : '0';
case 'o': return new Date(
x.getFullYear(),
x.getMonth(),
x.getDate() - ((x.getDay() + 6) % 7) + 3
).getFullYear();
case 'Y': return d.Y;
case 'y': return ('' + d.Y).substr(2);
// Time
case 'a': return d.H < 12 ? "am" : "pm";
case 'A': return d.H < 12 ? "AM" : "PM";
case 'g': return d.H % 12 || 12;
case 'G': return d.H;
case 'h': return pad2(d.H % 12 || 12);
case 'H': return pad2(d.H);
case 'i': return pad2(UTC?x.getUTCMinutes():x.getMinutes());
case 's': return pad2(UTC?x.getUTCSeconds():x.getSeconds());
case 'u': return (UTC?x.getUTCMilliseconds():x.getMilliseconds()).toString().padStart(3,'0');
// Timezone
case 'O': return UTC ? 'Z' : (d.Z > 0 ? '+' : '-') + pad2(Math.abs(d.Z / 60)) + '00';
case 'P': return UTC ? 'Z' : (d.Z > 0 ? '+' : '-') + pad2(Math.abs(d.Z / 60)) + ':' + pad2(Math.abs(d.Z % 60));
case 'T': return UTC ? 'UTC' : new Date(d.Y, 0, 1).toTimeString().replace(/^.+ \(?([^)]+)\)?$/, '$1');
case 'Z': return d.Z * 60;
// Full Date/Time
case 'c': return x.format("Y-m-d\\TH:i:sO");
case 'r': return x.format("D, d M Y H:i:s O");
case 'U': return x.getTime() / 1000;
}
return m;
})
: x.toString();
let options = {};
if (formats[str]) {
options = formats[str];
} else {
console.log('Date.format('+str+', '+(UTC?'true':'false')+')');
if (UTC) {
str += 'Z';
}
let i = str.length;
while (i--) {
phpFormats[str[i]] && Object.entries(phpFormats[str[i]]).forEach(([k,v])=>options[k]=v);
}
formats[str] = options;
}
return new Intl.DateTimeFormat(document.documentElement.lang, options).format(this);
};
// Simulate momentjs fromNow function

View File

@@ -470,8 +470,8 @@ class ServiceActions
if (!empty($this->aPaths[3]))
{
$bAdmim = 'Admin' === (isset($this->aPaths[2]) ? (string) $this->aPaths[2] : 'App');
$sLanguage = $this->oActions->ValidateLanguage($this->aPaths[3], '', $bAdmim);
$bAdmin = 'Admin' === (isset($this->aPaths[2]) ? (string) $this->aPaths[2] : 'App');
$sLanguage = $this->oActions->ValidateLanguage($this->aPaths[3], '', $bAdmin);
$bCacheEnabled = $this->Config()->Get('labs', 'cache_system_data', true);
if (!empty($sLanguage) && $bCacheEnabled)
@@ -483,14 +483,14 @@ class ServiceActions
if ($bCacheEnabled)
{
$sCacheFileName = KeyPathHelper::LangCache(
$sLanguage, $bAdmim, $this->oActions->Plugins()->Hash());
$sLanguage, $bAdmin, $this->oActions->Plugins()->Hash());
$sResult = $this->Cacher()->Get($sCacheFileName);
}
if (0 === \strlen($sResult))
{
$sResult = $this->compileLanguage($sLanguage, $bAdmim, false);
$sResult = $this->compileLanguage($sLanguage, $bAdmin);
if ($bCacheEnabled && 0 < \strlen($sCacheFileName))
{
$this->Cacher()->Set($sCacheFileName, $sResult);
@@ -1041,28 +1041,10 @@ class ServiceActions
return $bJsOutput ? 'rl.TEMPLATES='.\MailSo\Base\Utils::Php2js($sHtml, $this->Logger()).';' : $sHtml;
}
private function convertLanguageNameToMomentLanguageName(string $sLanguage) : string
{
$aHelper = array('en_gb' => 'en-gb', 'fr_ca' => 'fr-ca', 'pt_br' => 'pt-br',
'uk_ua' => 'ua', 'zh_cn' => 'zh-cn', 'zh_tw' => 'zh-tw', 'fa_ir' => 'fa');
return isset($aHelper[$sLanguage]) ? $aHelper[$sLanguage] : \substr($sLanguage, 0, 2);
}
private function compileLanguage(string $sLanguage, bool $bAdmin = false, bool $bWrapByScriptTag = true) : string
private function compileLanguage(string $sLanguage, bool $bAdmin = false) : string
{
$aResultLang = array();
$sMoment = 'window.moment && window.moment.locale && window.moment.locale(\'en\');';
$sMomentFileName = APP_VERSION_ROOT_PATH.'app/localization/moment/'.
$this->convertLanguageNameToMomentLanguageName($sLanguage).'.js';
if (\is_file($sMomentFileName))
{
$sMoment = \file_get_contents($sMomentFileName);
$sMoment = \preg_replace('/\/\/[^\n]+\n/', '', $sMoment);
}
Utils::ReadAndAddLang(APP_VERSION_ROOT_PATH.'app/localization/langs.yml', $aResultLang);
Utils::ReadAndAddLang(APP_VERSION_ROOT_PATH.'app/localization/'.
($bAdmin ? 'admin' : 'webmail').'/_source.en.yml', $aResultLang);
@@ -1071,7 +1053,7 @@ class ServiceActions
$this->Plugins()->ReadLang($sLanguage, $aResultLang);
$sLangJs = '';
$sResult = '';
$aLangKeys = \array_keys($aResultLang);
foreach ($aLangKeys as $sKey)
{
@@ -1081,17 +1063,25 @@ class ServiceActions
$sString = \implode("\n", $sString);
}
$sLangJs .= '"'.\str_replace('"', '\\"', \str_replace('\\', '\\\\', $sKey)).'":'
$sResult .= '"'.\str_replace('"', '\\"', \str_replace('\\', '\\\\', $sKey)).'":'
.'"'.\str_replace(array("\r", "\n", "\t"), array('\r', '\n', '\t'),
\str_replace('"', '\\"', \str_replace('\\', '\\\\', $sString))).'",';
}
$sResult = $sResult ? '{'.\substr($sResult, 0, -1).'}' : 'null';
$sResult = empty($sLangJs) ? 'null' : '{'.\substr($sLangJs, 0, -1).'}';
$sLanguage = strtr($sLanguage, '_', '-');
return
($bWrapByScriptTag ? '<script data-cfasync="false">' : '').
'window.rainloopI18N='.$sResult.';'.$sMoment.
($bWrapByScriptTag ? '</script>' : '')
;
$sMoment = 'window.moment && window.moment.locale && window.moment.locale(\'en\');';
$options = [$sLanguage, \substr($sLanguage, 0, 2)];
foreach ($options as $lang) {
$sMomentFileName = APP_VERSION_ROOT_PATH.'app/localization/moment/'.$lang.'.js';
if (\is_file($sMomentFileName)) {
$sMoment = \file_get_contents($sMomentFileName);
$sMoment = \preg_replace('/\/\/[^\n]+\n/', '', $sMoment);
break;
}
}
return 'document.documentElement.lang = "'.$sLanguage.'";window.rainloopI18N='.$sResult.';'.$sMoment;
}
}