mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-11-13 02:55:24 +08:00
236 lines
6.1 KiB
JavaScript
236 lines
6.1 KiB
JavaScript
import ko from 'ko';
|
|
import { Notifications, UploadErrorCode } from 'Common/Enums';
|
|
import { langLink } from 'Common/Links';
|
|
import { doc, createElement } from 'Common/Globals';
|
|
import { getKeyByValue, forEachObjectEntry } from 'Common/Utils';
|
|
import { pInt } from 'Common/Utils';
|
|
import { LanguageStore } from 'Stores/Language';
|
|
|
|
let I18N_DATA = {};
|
|
|
|
const
|
|
init = () => {
|
|
if (rl.I18N) {
|
|
I18N_DATA = rl.I18N;
|
|
rl.I18N = null;
|
|
doc.documentElement.dir = I18N_DATA.LANG_DIR;
|
|
return 1;
|
|
}
|
|
},
|
|
|
|
i18nKey = key => key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(),
|
|
|
|
getNotificationMessage = code => {
|
|
let key = getKeyByValue(Notifications, code);
|
|
return key ? I18N_DATA.NOTIFICATIONS[key] : '';
|
|
},
|
|
|
|
fromNow = date => relativeTime(Math.round((date.getTime() - Date.now()) / 1000));
|
|
|
|
export const
|
|
translateTrigger = ko.observable(false),
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat
|
|
// see /snappymail/v/0.0.0/app/localization/relativetimeformat/
|
|
relativeTime = seconds => {
|
|
let unit = 'second',
|
|
t = [[60,'minute'],[3600,'hour'],[86400,'day'],[2628000,'month'],[31536000,'year']],
|
|
i = 5,
|
|
abs = Math.abs(seconds);
|
|
while (i--) {
|
|
if (t[i][0] <= abs) {
|
|
seconds = Math.round(seconds / t[i][0]);
|
|
unit = t[i][1];
|
|
break;
|
|
}
|
|
}
|
|
if (Intl.RelativeTimeFormat) {
|
|
return (new Intl.RelativeTimeFormat(doc.documentElement.lang)).format(seconds, unit);
|
|
}
|
|
// Safari < 14
|
|
abs = Math.abs(seconds);
|
|
let rtf = rl.relativeTime.long[unit][0 > seconds ? 'past' : 'future'],
|
|
plural = rl.relativeTime.plural(abs);
|
|
return (rtf[plural] || rtf).replace('{0}', abs);
|
|
},
|
|
|
|
/**
|
|
* @param {string} key
|
|
* @param {Object=} valueList
|
|
* @param {string=} defaulValue
|
|
* @returns {string}
|
|
*/
|
|
i18n = (key, valueList, defaulValue) => {
|
|
let result = defaulValue ?? key;
|
|
let path = key.split('/');
|
|
if (I18N_DATA[path[0]] && path[1]) {
|
|
result = I18N_DATA[path[0]][path[1]] || result;
|
|
}
|
|
valueList && forEachObjectEntry(valueList, (key, value) => {
|
|
result = result.replace('%' + key + '%', value);
|
|
});
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* @param {Object} elements
|
|
* @param {boolean=} animate = false
|
|
*/
|
|
i18nToNodes = element =>
|
|
setTimeout(() =>
|
|
element.querySelectorAll('[data-i18n]').forEach(element => {
|
|
const key = element.dataset.i18n;
|
|
if ('[' === key[0]) {
|
|
switch (key.slice(1, 6)) {
|
|
case 'html]':
|
|
element.innerHTML = i18n(key.slice(6));
|
|
break;
|
|
case 'place':
|
|
element.placeholder = i18n(key.slice(13));
|
|
break;
|
|
case 'title':
|
|
element.title = i18n(key.slice(7));
|
|
break;
|
|
// no default
|
|
}
|
|
} else {
|
|
element.textContent = i18n(key);
|
|
}
|
|
})
|
|
, 1),
|
|
|
|
timestampToString = (timeStampInUTC, formatStr) => {
|
|
const now = Date.now(),
|
|
time = 0 < timeStampInUTC ? timeStampInUTC * 1000 : (0 === timeStampInUTC ? now : 0);
|
|
if (31536000000 < time) {
|
|
const m = new Date(time), h = LanguageStore.hourCycle();
|
|
switch (formatStr) {
|
|
case 'FROMNOW':
|
|
return fromNow(m);
|
|
case 'AUTO': {
|
|
// 4 hours
|
|
if (14400000 >= now - time)
|
|
return fromNow(m);
|
|
const date = new Date,
|
|
dt = date.setHours(0,0,0,0);
|
|
return (time > dt - 86400000)
|
|
? i18n(
|
|
time > dt ? 'MESSAGE_LIST/TODAY_AT' : 'MESSAGE_LIST/YESTERDAY_AT',
|
|
{TIME: m.format('LT',0,h)}
|
|
)
|
|
: m.format(
|
|
date.getFullYear() === m.getFullYear()
|
|
? {day: '2-digit', month: 'short', hour: 'numeric', minute: 'numeric'}
|
|
: {dateStyle: 'medium', timeStyle: 'short'}
|
|
, 0, h);
|
|
}
|
|
case 'FULL':
|
|
return m.format('LLL',0,h);
|
|
default:
|
|
return m.format(formatStr,0,h);
|
|
}
|
|
}
|
|
|
|
return '';
|
|
},
|
|
|
|
timeToNode = (element, time) => {
|
|
try {
|
|
if (time) {
|
|
element.dateTime = new Date(time * 1000).toISOString();
|
|
} else {
|
|
time = Date.parse(element.dateTime) / 1000;
|
|
}
|
|
|
|
let key = element.dataset.timeFormat;
|
|
if (key) {
|
|
element.textContent = timestampToString(time, key);
|
|
if ('FULL' !== key && 'FROMNOW' !== key) {
|
|
element.title = timestampToString(time, 'FULL');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// prevent knockout crashes
|
|
console.error(e);
|
|
}
|
|
},
|
|
|
|
reloadTime = () => doc.querySelectorAll('time').forEach(element => timeToNode(element)),
|
|
|
|
/**
|
|
* @param {Function} startCallback
|
|
* @param {Function=} langCallback = null
|
|
*/
|
|
initOnStartOrLangChange = (startCallback, langCallback) => {
|
|
startCallback?.();
|
|
startCallback && translateTrigger.subscribe(startCallback);
|
|
langCallback && translateTrigger.subscribe(langCallback);
|
|
},
|
|
|
|
/**
|
|
* @param {number} code
|
|
* @param {*=} message = ''
|
|
* @param {*=} defCode = null
|
|
* @returns {string}
|
|
*/
|
|
getNotification = (code, message = '', defCode = 0) => {
|
|
code = pInt(code);
|
|
if (Notifications.ClientViewError === code && message) {
|
|
return message;
|
|
}
|
|
|
|
return getNotificationMessage(code)
|
|
|| getNotificationMessage(pInt(defCode))
|
|
|| '';
|
|
},
|
|
|
|
getErrorMessage = (code, data) =>
|
|
getNotification(code) || data?.messageAdditional || data?.message || data,
|
|
|
|
/**
|
|
* @param {*} code
|
|
* @returns {string}
|
|
*/
|
|
getUploadErrorDescByCode = code => {
|
|
let key = getKeyByValue(UploadErrorCode, parseInt(code, 10));
|
|
return i18n('UPLOAD/ERROR_' + (key ? i18nKey(key) : 'UNKNOWN'));
|
|
},
|
|
|
|
/**
|
|
* @param {boolean} admin
|
|
* @param {string} language
|
|
*/
|
|
translatorReload = (language, admin) =>
|
|
new Promise((resolve, reject) => {
|
|
const script = createElement('script');
|
|
script.onload = () => {
|
|
// reload the data
|
|
if (init()) {
|
|
i18nToNodes(doc);
|
|
translateTrigger(!translateTrigger());
|
|
// admin || reloadTime();
|
|
}
|
|
script.remove();
|
|
resolve();
|
|
};
|
|
script.onerror = () => reject(Error('Language '+language+' failed'));
|
|
script.src = langLink(language, admin);
|
|
// script.async = true;
|
|
doc.head.append(script);
|
|
}),
|
|
|
|
/**
|
|
* @param {string} language
|
|
* @param {boolean=} isEng = false
|
|
* @returns {string}
|
|
*/
|
|
convertLangName = (language, isEng = false) =>
|
|
i18n(
|
|
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/' + language,
|
|
null,
|
|
language
|
|
),
|
|
|
|
baseCollator = numeric => new Intl.Collator(doc.documentElement.lang, {numeric: !!numeric, sensitivity: 'base'});
|
|
|
|
init();
|