"use strict";
// SPDX-License-Identifier: GPL-3.0-or-later
// myMPD (c) 2018-2024 Juergen Mang <mail@jcgames.de>
// https://github.com/jcorporation/mympd
/** @module locale_js */
/**
* Checks for singular or plural
* @param {number} number number to check
* @returns {number} 0 = singular, 1 = plural
*/
function checkSmartCount(number) {
if (number === 1) { return 0; }
return 1;
}
/**
* Translates the phrase and resolves variables
* Variables are in the format %{name}
* Phrase can include singular and plural separated by ||||
* Singular or plural is detected by the special data key smartCount
* @param {string} phrase the prase to translate
* @param {object} [data] variable data
* @returns {string} translated phrase
*/
function tn(phrase, data) {
// @ts-ignore
if (isNaN(phrase) === false) {
//do not translate numbers
return phrase;
}
if (phrase === undefined) {
logError('Phrase is undefined');
return 'undefinedPhrase';
}
//translate
let result = phrases[phrase];
/*debug*/ if (result === undefined &&
/*debug*/ locale !== 'en-US')
/*debug*/ {
/*debug*/ logDebug('Phrase "' + phrase + '" for locale ' + locale + ' not found');
/*debug*/ }
//fallback if phrase is not translated
if (result === undefined || result === '') {
result = phrasesDefault[phrase] !== undefined ? phrasesDefault[phrase] : phrase;
}
//check for smartCount
if (data !== undefined &&
data.smartCount !== undefined)
{
const p = result.split(' |||| ');
if (p.length > 1) {
result = p[checkSmartCount(data.smartCount)];
}
result = result.replace('%{smart_count}', data.smartCount);
}
//replace variables
if (data !== undefined) {
const tnRegex = /%\{(\w+)\}/g;
result = result.replace(tnRegex, function(m0, m1) {
return data[m1];
});
}
return result;
}
/**
* Returns timestamp as formatted date string
* @param {number} secs unix timestamp
* @returns {string} formatted date
*/
function fmtDate(secs) {
return new Date(secs * 1000).toLocaleString(locale);
}
/**
* Returns timestamp as formatted time string
* @param {number} secs unix timestamp
* @returns {string} formatted date
*/
function fmtTime(secs) {
return new Date(secs * 1000).toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit", second: "2-digit" });
}
/**
* Returns seconds as formatted duration string
* @param {number} secs duration to format
* @returns {string} formatted duration
*/
function fmtDuration(secs) {
const days = Math.floor(secs / 86400);
const hours = Math.floor(secs / 3600) - days * 24;
const minutes = Math.floor(secs / 60) - hours * 60 - days * 1440;
const seconds = secs - days * 86400 - hours * 3600 - minutes * 60;
return (days > 0 ? days + smallSpace + tn('Days') + ' ' : '') +
(hours > 0 ? hours + smallSpace + tn('Hours') + ' ' +
(minutes < 10 ? '0' : '') : '') + minutes + smallSpace + tn('Minutes') + ' ' +
(seconds < 10 ? '0' : '') + seconds + smallSpace + tn('Seconds');
}
/**
* Returns seconds as formatted song duration string
* @param {number} secs duration to format
* @returns {string} formatted song duration
*/
function fmtSongDuration(secs) {
const hours = Math.floor(secs / 3600);
const minutes = Math.floor(secs / 60) - hours * 60;
const seconds = Math.floor(secs - hours * 3600 - minutes * 60);
return (hours > 0 ? hours + ':' + (minutes < 10 ? '0' : '') : '') +
minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
}
/**
* Sets and fetches the locale and translates the dom
* @param {string} newLocale locale to set
* @returns {void}
*/
function setLocale(newLocale) {
if (newLocale === 'default') {
//auto detection
locale = navigator.language || navigator.userLanguage;
const shortLocale = locale.substring(0, 2);
locale = localeMap[locale] === undefined
? localeMap[shortLocale] === undefined
? locale
: localeMap[shortLocale]
: localeMap[locale];
}
else {
locale = newLocale;
}
//check if locale is available
if (i18n[locale] === undefined) {
//fallback to default locale
logError('Locale "' + locale + '" not defined');
locale = 'en-US';
}
if (getData(domCache.body, 'locale') === locale) {
//locale already set
logDebug('Locale already set');
return;
}
//get phrases and translate dom
httpGet(subdir + '/assets/i18n/' + locale + '.json', function(obj) {
phrases = obj;
i18nHtml(domCache.body);
i18nPregenerated();
setData(domCache.body, 'locale', locale);
}, true);
}
/**
* Translates all phrases in the dom
* @param {HTMLElement} root root element to translate
* @returns {void}
*/
function i18nHtml(root) {
const attributes = [
['data-phrase', 'textContent'],
['data-title-phrase', 'title'],
['data-label-phrase', 'label'],
['data-placeholder-phrase', 'placeholder']
];
for (let i = 0, j = attributes.length; i < j; i++) {
const els = root.querySelectorAll('[' + attributes[i][0] + ']');
const elsLen = els.length;
for (let k = 0; k < elsLen; k++) {
//get phrase data
const data = els[k].getAttribute('data-phrase-data');
let dataObj = {};
if (data !== null) {
dataObj = JSON.parse(data);
}
//add smartCount to data from data-phrase-number attribute
const smartCount = els[k].getAttribute('data-phrase-number');
if (smartCount !== null) {
dataObj.smartCount = Number(smartCount);
}
//translate
els[k][attributes[i][1]] = tn(els[k].getAttribute(attributes[i][0]), dataObj);
}
}
}
/**
* Set translations for pregenerated elements
* @returns {void}
*/
function i18nPregenerated() {
for (const el in pEl) {
if (pEl[el].nodeName !== undefined) {
const titles = pEl[el].querySelectorAll('[data-title-phrase]');
for (const tit of titles) {
tit.setAttribute('title', tn(tit.getAttribute('data-title-phrase')));
}
if (pEl[el].getAttribute('data-title-phrase') !== null) {
pEl[el].setAttribute('title', tn(pEl[el].getAttribute('data-title-phrase')));
}
const tcs = pEl[el].querySelectorAll('[data-phrase]');
for (const tc of tcs) {
tc.textContent = tn(tc.getAttribute('data-phrase'));
}
if (pEl[el].getAttribute('data-phrase') !== null) {
pEl[el].textContent = tn(pEl[el].getAttribute('data-phrase'));
}
}
}
}