1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge pull request #960 from ferferga/translate-everything

Translate missing items - Part 1
This commit is contained in:
dkanada 2020-04-04 15:59:45 +09:00 committed by GitHub
commit 521068558f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 229 additions and 155 deletions

View file

@ -49,8 +49,7 @@ globals:
getWindowLocationSearch: writable getWindowLocationSearch: writable
Globalize: writable Globalize: writable
Hls: writable Hls: writable
humaneDate: writable dfnshelper: writable
humaneElapsed: writable
LibraryMenu: writable LibraryMenu: writable
LinkParser: writable LinkParser: writable
LiveTvHelpers: writable LiveTvHelpers: writable

View file

@ -55,6 +55,7 @@
"dependencies": { "dependencies": {
"alameda": "^1.4.0", "alameda": "^1.4.0",
"core-js": "^3.6.4", "core-js": "^3.6.4",
"date-fns": "^2.11.1",
"document-register-element": "^1.14.3", "document-register-element": "^1.14.3",
"flv.js": "^1.5.0", "flv.js": "^1.5.0",
"hls.js": "^0.13.1", "hls.js": "^0.13.1",
@ -89,7 +90,8 @@
"src/components/input/keyboardnavigation.js", "src/components/input/keyboardnavigation.js",
"src/components/sanatizefilename.js", "src/components/sanatizefilename.js",
"src/scripts/settings/webSettings.js", "src/scripts/settings/webSettings.js",
"src/components/scrollManager.js" "src/components/scrollManager.js",
"src/scripts/dfnshelper.js"
], ],
"plugins": [ "plugins": [
"@babel/plugin-transform-modules-amd" "@babel/plugin-transform-modules-amd"

View file

@ -112,3 +112,14 @@ var polyfill = require("@babel/polyfill/dist/polyfill");
_define("polyfill", function () { _define("polyfill", function () {
return polyfill; return polyfill;
}); });
// Date-FNS
var date_fns = require("date-fns");
_define("date-fns", function () {
return date_fns;
});
var date_fns_locale = require("date-fns/locale");
_define("date-fns/locale", function () {
return date_fns_locale;
});

View file

@ -1,4 +1,4 @@
define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datetime, userSettings, serverNotifications, connectionManager) { define(["events", "globalize", "dom", "date-fns", "dfnshelper", "userSettings", "serverNotifications", "connectionManager", "emby-button", "listViewStyle"], function (events, globalize, dom, datefns, dfnshelper, userSettings, serverNotifications, connectionManager) {
"use strict"; "use strict";
function getEntryHtml(entry, apiClient) { function getEntryHtml(entry, apiClient) {
@ -26,8 +26,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
html += entry.Name; html += entry.Name;
html += "</div>"; html += "</div>";
html += '<div class="listItemBodyText secondary">'; html += '<div class="listItemBodyText secondary">';
var date = datetime.parseISO8601Date(entry.Date, true); html += datefns.formatRelative(Date.parse(entry.Date), Date.parse(new Date()), { locale: dfnshelper.getLocale() });
html += datetime.toLocaleString(date).toLowerCase();
html += "</div>"; html += "</div>";
html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">'; html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">';
html += entry.ShortOverview || ""; html += entry.ShortOverview || "";

View file

@ -1082,11 +1082,7 @@ import 'programStyles';
if (options.showPersonRoleOrType) { if (options.showPersonRoleOrType) {
if (item.Role) { if (item.Role) {
lines.push('as ' + item.Role); lines.push(globalize.translate('PersonRole', item.Role));
} else if (item.Type) {
lines.push(globalize.translate('' + item.Type));
} else {
lines.push('');
} }
} }
} }

View file

@ -89,7 +89,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
var instruction = options.instruction ? options.instruction + "<br/><br/>" : ""; var instruction = options.instruction ? options.instruction + "<br/><br/>" : "";
html += '<div class="infoBanner" style="margin-bottom:1.5em;">'; html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
html += instruction; html += instruction;
html += Globalize.translate("MessageDirectoryPickerInstruction").replace("{0}", "<b>\\\\server</b>").replace("{1}", "<b>\\\\192.168.1.101</b>"); html += Globalize.translate("MessageDirectoryPickerInstruction", "<b>\\\\server</b>", "<b>\\\\192.168.1.101</b>");
if ("bsd" === systemInfo.OperatingSystem.toLowerCase()) { if ("bsd" === systemInfo.OperatingSystem.toLowerCase()) {
html += "<br/>"; html += "<br/>";
html += "<br/>"; html += "<br/>";
@ -163,16 +163,15 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
} }
}).catch(function(response) { }).catch(function(response) {
if (response) { if (response) {
// TODO All alerts (across the project), should use Globalize.translate()
if (response.status === 404) { if (response.status === 404) {
alertText("The path could not be found. Please ensure the path is valid and try again."); alertText(Globalize.translate("PathNotFound"));
return Promise.reject(); return Promise.reject();
} }
if (response.status === 500) { if (response.status === 500) {
if (validateWriteable) { if (validateWriteable) {
alertText("Jellyfin Server requires write access to this folder. Please ensure write access and try again."); alertText(Globalize.translate("WriteAccessRequired"));
} else { } else {
alertText("The path could not be found. Please ensure the path is valid and try again.") alertText(Globalize.translate("PathNotFound"))
} }
return Promise.reject() return Promise.reject()
} }

View file

@ -1,74 +0,0 @@
define(["datetime"], function (datetime) {
"use strict";
function humaneDate(date_str) {
var format;
var time_formats = [
[90, "a minute"],
[3600, "minutes", 60],
[5400, "an hour"],
[86400, "hours", 3600],
[129600, "a day"],
[604800, "days", 86400],
[907200, "a week"],
[2628e3, "weeks", 604800],
[3942e3, "a month"],
[31536e3, "months", 2628e3],
[47304e3, "a year"],
[31536e5, "years", 31536e3]
];
var dt = new Date();
var date = datetime.parseISO8601Date(date_str, true);
var seconds = (dt - date) / 1000.0;
var i = 0;
if (seconds < 0) {
seconds = Math.abs(seconds);
}
// eslint-disable-next-line no-cond-assign
for (; format = time_formats[i++];) {
if (seconds < format[0]) {
if (2 == format.length) {
return format[1] + " ago";
}
return Math.round(seconds / format[2]) + " " + format[1] + " ago";
}
}
if (seconds > 47304e5) {
return Math.round(seconds / 47304e5) + " centuries ago";
}
return date_str;
}
function humaneElapsed(firstDateStr, secondDateStr) {
// TODO replace this whole script with a library or something
var dateOne = new Date(firstDateStr);
var dateTwo = new Date(secondDateStr);
var delta = (dateTwo.getTime() - dateOne.getTime()) / 1e3;
var days = Math.floor(delta % 31536e3 / 86400);
var hours = Math.floor(delta % 31536e3 % 86400 / 3600);
var minutes = Math.floor(delta % 31536e3 % 86400 % 3600 / 60);
var seconds = Math.round(delta % 31536e3 % 86400 % 3600 % 60);
var elapsed = "";
elapsed += 1 == days ? days + " day " : "";
elapsed += days > 1 ? days + " days " : "";
elapsed += 1 == hours ? hours + " hour " : "";
elapsed += hours > 1 ? hours + " hours " : "";
elapsed += 1 == minutes ? minutes + " minute " : "";
elapsed += minutes > 1 ? minutes + " minutes " : "";
elapsed += elapsed.length > 0 ? "and " : "";
elapsed += 1 == seconds ? seconds + " second" : "";
elapsed += 0 == seconds || seconds > 1 ? seconds + " seconds" : "";
return elapsed;
}
window.humaneDate = humaneDate;
window.humaneElapsed = humaneElapsed;
return {
humaneDate: humaneDate,
humaneElapsed: humaneElapsed
};
});

View file

@ -109,7 +109,7 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
html += '<span style="margin-right: 10px;">'; html += '<span style="margin-right: 10px;">';
var startAtDisplay = totalRecordCount ? startIndex + 1 : 0; var startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount; html += globalize.translate("ListPaging", startAtDisplay, recordsEnd, totalRecordCount);
html += '</span>'; html += '</span>';

View file

@ -309,7 +309,7 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize",
fullName = idInfo.Name + " " + globalize.translate(idInfo.Type); fullName = idInfo.Name + " " + globalize.translate(idInfo.Type);
} }
var idLabel = globalize.translate("LabelDynamicExternalId").replace("{0}", fullName); var idLabel = globalize.translate("LabelDynamicExternalId", fullName);
html += '<input is="emby-input" class="txtLookupId" data-providerkey="' + idInfo.Key + '" id="' + id + '" label="' + idLabel + '"/>'; html += '<input is="emby-input" class="txtLookupId" data-providerkey="' + idInfo.Key + '" id="' + id + '" label="' + idLabel + '"/>';

View file

@ -470,7 +470,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi
fullName = idInfo.Name + " " + globalize.translate(idInfo.Type); fullName = idInfo.Name + " " + globalize.translate(idInfo.Type);
} }
var labelText = globalize.translate("LabelDynamicExternalId").replace("{0}", fullName); var labelText = globalize.translate('LabelDynamicExternalId', fullName);
html += '<div class="inputContainer">'; html += '<div class="inputContainer">';
html += '<div class="flex align-items-center">'; html += '<div class="flex align-items-center">';

View file

@ -173,15 +173,15 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir
}; };
if (status === 'completed') { if (status === 'completed') {
notification.title = globalize.translate('PackageInstallCompleted').replace('{0}', installation.Name + ' ' + installation.Version); notification.title = globalize.translate('PackageInstallCompleted', installation.Name, installation.Version);
notification.vibrate = true; notification.vibrate = true;
} else if (status === 'cancelled') { } else if (status === 'cancelled') {
notification.title = globalize.translate('PackageInstallCancelled').replace('{0}', installation.Name + ' ' + installation.Version); notification.title = globalize.translate('PackageInstallCancelled', installation.Name, installation.Version);
} else if (status === 'failed') { } else if (status === 'failed') {
notification.title = globalize.translate('PackageInstallFailed').replace('{0}', installation.Name + ' ' + installation.Version); notification.title = globalize.translate('PackageInstallFailed', installation.Name, installation.Version);
notification.vibrate = true; notification.vibrate = true;
} else if (status === 'progress') { } else if (status === 'progress') {
notification.title = globalize.translate('InstallingPackage').replace('{0}', installation.Name + ' ' + installation.Version); notification.title = globalize.translate('InstallingPackage', installation.Name, installation.Version);
notification.actions = notification.actions =
[ [

View file

@ -1,4 +1,4 @@
define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globalize", "loading", "connectionManager", "playMethodHelper", "cardBuilder", "imageLoader", "components/activitylog", "scripts/imagehelper", "indicators", "humanedate", "listViewStyle", "emby-button", "flexStyles", "emby-button", "emby-itemscontainer"], function (datetime, events, itemHelper, serverNotifications, dom, globalize, loading, connectionManager, playMethodHelper, cardBuilder, imageLoader, ActivityLog, imageHelper, indicators) { define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globalize", "date-fns", "dfnshelper", "loading", "connectionManager", "playMethodHelper", "cardBuilder", "imageLoader", "components/activitylog", "scripts/imagehelper", "indicators", "listViewStyle", "emby-button", "flexStyles", "emby-button", "emby-itemscontainer"], function (datetime, events, itemHelper, serverNotifications, dom, globalize, datefns, dfnshelper, loading, connectionManager, playMethodHelper, cardBuilder, imageLoader, ActivityLog, imageHelper, indicators) {
"use strict"; "use strict";
function showPlaybackInfo(btn, session) { function showPlaybackInfo(btn, session) {
@ -467,10 +467,11 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
getNowPlayingName: function (session) { getNowPlayingName: function (session) {
var imgUrl = ""; var imgUrl = "";
var nowPlayingItem = session.NowPlayingItem; var nowPlayingItem = session.NowPlayingItem;
// FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix
// how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences
if (!nowPlayingItem) { if (!nowPlayingItem) {
return { return {
html: "Last seen " + humaneDate(session.LastActivityDate), html: globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(session.LastActivityDate), dfnshelper.localeWithSuffix)),
image: imgUrl image: imgUrl
}; };
} }

View file

@ -84,7 +84,7 @@ define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "e
} }
if (installedPlugin) { if (installedPlugin) {
var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled").replace("{0}", "<strong>" + installedPlugin.Version + "</strong>"); var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled", "<strong>" + installedPlugin.Version + "</strong>");
$("#pCurrentVersion", page).show().html(currentVersionText); $("#pCurrentVersion", page).show().html(currentVersionText);
} else { } else {
$("#pCurrentVersion", page).hide().html(""); $("#pCurrentVersion", page).hide().html("");

View file

@ -116,7 +116,7 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby
return ip.Id == plugin.guid; return ip.Id == plugin.guid;
})[0]; })[0];
html += "<div class='cardText cardText-secondary'>"; html += "<div class='cardText cardText-secondary'>";
html += installedPlugin ? globalize.translate("LabelVersionInstalled").replace("{0}", installedPlugin.Version) : "&nbsp;"; html += installedPlugin ? globalize.translate("LabelVersionInstalled", installedPlugin.Version) : "&nbsp;";
html += "</div>"; html += "</div>";
html += "</div>"; html += "</div>";
html += "</div>"; html += "</div>";

View file

@ -2,7 +2,7 @@ define(["loading", "libraryMenu", "dom", "globalize", "cardStyle", "emby-button"
"use strict"; "use strict";
function deletePlugin(page, uniqueid, name) { function deletePlugin(page, uniqueid, name) {
var msg = globalize.translate("UninstallPluginConfirmation").replace("{0}", name); var msg = globalize.translate("UninstallPluginConfirmation", name);
require(["confirm"], function (confirm) { require(["confirm"], function (confirm) {
confirm({ confirm({

View file

@ -75,17 +75,19 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
html += "</div>"; html += "</div>";
context.querySelector(".taskTriggers").innerHTML = html; context.querySelector(".taskTriggers").innerHTML = html;
}, },
// TODO: Replace this mess with date-fns and remove datetime completely
getTriggerFriendlyName: function (trigger) { getTriggerFriendlyName: function (trigger) {
if ("DailyTrigger" == trigger.Type) { if ("DailyTrigger" == trigger.Type) {
return "Daily at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks); return globalize.translate("DailyAt", ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
} }
if ("WeeklyTrigger" == trigger.Type) { if ("WeeklyTrigger" == trigger.Type) {
return trigger.DayOfWeek + "s at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks); // TODO: The day of week isn't localised as well
return globalize.translate("WeeklyAt", trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
} }
if ("SystemEventTrigger" == trigger.Type && "WakeFromSleep" == trigger.SystemEvent) { if ("SystemEventTrigger" == trigger.Type && "WakeFromSleep" == trigger.SystemEvent) {
return "On wake from sleep"; return globalize.translate("OnWakeFromSleep");
} }
if (trigger.Type == "IntervalTrigger") { if (trigger.Type == "IntervalTrigger") {
@ -93,23 +95,23 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
var hours = trigger.IntervalTicks / 36e9; var hours = trigger.IntervalTicks / 36e9;
if (hours == 0.25) { if (hours == 0.25) {
return "Every 15 minutes"; return globalize.translate("EveryXMinutes", "15");
} }
if (hours == 0.5) { if (hours == 0.5) {
return "Every 30 minutes"; return globalize.translate("EveryXMinutes", "30");
} }
if (hours == 0.75) { if (hours == 0.75) {
return "Every 45 minutes"; return globalize.translate("EveryXMinutes", "45");
} }
if (hours == 1) { if (hours == 1) {
return "Every hour"; return globalize.translate("EveryHour");
} }
return "Every " + hours + " hours"; return globalize.translate("EveryXHours", hours);
} }
if (trigger.Type == "StartupTrigger") { if (trigger.Type == "StartupTrigger") {
return "On application startup"; return globalize.translate("OnApplicationStartup");
} }
return trigger.Type; return trigger.Type;

View file

@ -1,4 +1,4 @@
define(["jQuery", "loading", "events", "globalize", "serverNotifications", "humanedate", "listViewStyle", "emby-button"], function($, loading, events, globalize, serverNotifications) { define(["jQuery", "loading", "events", "globalize", "serverNotifications", "date-fns", "dfnshelper", "listViewStyle", "emby-button"], function ($, loading, events, globalize, serverNotifications, datefns, dfnshelper) {
"use strict"; "use strict";
function reloadList(page) { function reloadList(page) {
@ -66,7 +66,10 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
var html = ""; var html = "";
if (task.State === "Idle") { if (task.State === "Idle") {
if (task.LastExecutionResult) { if (task.LastExecutionResult) {
html += globalize.translate("LabelScheduledTaskLastRan").replace("{0}", humaneDate(task.LastExecutionResult.EndTimeUtc)).replace("{1}", humaneElapsed(task.LastExecutionResult.StartTimeUtc, task.LastExecutionResult.EndTimeUtc)); var endtime = Date.parse(task.LastExecutionResult.EndTimeUtc);
var starttime = Date.parse(task.LastExecutionResult.StartTimeUtc);
html += globalize.translate("LabelScheduledTaskLastRan", datefns.formatDistanceToNow(endtime, dfnshelper.localeWithSuffix),
datefns.formatDistance(starttime, endtime, dfnshelper.localeWithSuffix));
if (task.LastExecutionResult.Status === "Failed") { if (task.LastExecutionResult.Status === "Failed") {
html += " <span style='color:#FF0000;'>(" + globalize.translate("LabelFailed") + ")</span>"; html += " <span style='color:#FF0000;'>(" + globalize.translate("LabelFailed") + ")</span>";
} else if (task.LastExecutionResult.Status === "Cancelled") { } else if (task.LastExecutionResult.Status === "Cancelled") {

View file

@ -1,4 +1,4 @@
define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "humanedate", "emby-button", "emby-itemscontainer", "cardStyle"], function (loading, dom, libraryMenu, globalize, imageHelper) { define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "date-fns", "dfnshelper", "emby-button", "emby-itemscontainer", "cardStyle"], function (loading, dom, libraryMenu, globalize, imageHelper, datefns, dfnshelper) {
"use strict"; "use strict";
function canDelete(deviceId) { function canDelete(deviceId) {
@ -103,7 +103,7 @@ define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "hu
if (device.LastUserName) { if (device.LastUserName) {
deviceHtml += device.LastUserName; deviceHtml += device.LastUserName;
deviceHtml += ", " + humaneDate(device.DateLastActivity); deviceHtml += ", " + datefns.formatDistanceToNow(Date.parse(device.DateLastActivity), dfnshelper.localeWithSuffix);
} }
deviceHtml += "&nbsp;"; deviceHtml += "&nbsp;";

View file

@ -258,14 +258,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>"; html += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
if ("Video" == profile.Type) { if ("Video" == profile.Type) {
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
} else { } else {
if ("Audio" == profile.Type) { if ("Audio" == profile.Type) {
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "</p>";
} }
} }
@ -319,14 +319,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>"; html += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
html += "<p>Protocol: " + (profile.Protocol || "Http") + "</p>"; html += "<p>Protocol: " + (profile.Protocol || "Http") + "</p>";
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
if ("Video" == profile.Type) { if ("Video" == profile.Type) {
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
} else { } else {
if ("Audio" == profile.Type) { if ("Audio" == profile.Type) {
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "</p>";
} }
} }
@ -404,11 +404,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>"; html += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
if (profile.Conditions && profile.Conditions.length) { if (profile.Conditions && profile.Conditions.length) {
html += "<p>"; html += "<p>";
html += Globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) { html += Globalize.translate("ValueConditions", profile.Conditions.map(function (c) {
return c.Property; return c.Property;
}).join(", ")); }).join(", "));
html += "</p>"; html += "</p>";
@ -476,11 +476,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>"; html += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.Codec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueCodec", profile.Codec || allText) + "</p>";
if (profile.Conditions && profile.Conditions.length) { if (profile.Conditions && profile.Conditions.length) {
html += "<p>"; html += "<p>";
html += Globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) { html += Globalize.translate("ValueConditions", profile.Conditions.map(function (c) {
return c.Property; return c.Property;
}).join(", ")); }).join(", "));
html += "</p>"; html += "</p>";
@ -547,20 +547,20 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>"; html += "<div>";
html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">'; html += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
html += "<p>" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueContainer", profile.Container || allText) + "</p>";
if ("Video" == profile.Type) { if ("Video" == profile.Type) {
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
} else { } else {
if ("Audio" == profile.Type) { if ("Audio" == profile.Type) {
html += "<p>" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "</p>"; html += "<p>" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "</p>";
} }
} }
if (profile.Conditions && profile.Conditions.length) { if (profile.Conditions && profile.Conditions.length) {
html += "<p>"; html += "<p>";
html += Globalize.translate("ValueConditions").replace("{0}", profile.Conditions.map(function (c) { html += Globalize.translate("ValueConditions", profile.Conditions.map(function (c) {
return c.Property; return c.Property;
}).join(", ")); }).join(", "));
html += "</p>"; html += "</p>";

View file

@ -591,7 +591,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
try { try {
var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString(); var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString();
itemBirthday.classList.remove("hide"); itemBirthday.classList.remove("hide");
itemBirthday.innerHTML = globalize.translate("BirthDateValue").replace("{0}", birthday); itemBirthday.innerHTML = globalize.translate("BirthDateValue", birthday);
} catch (err) { } catch (err) {
itemBirthday.classList.add("hide"); itemBirthday.classList.add("hide");
} }
@ -605,7 +605,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
try { try {
var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString(); var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString();
itemDeathDate.classList.remove("hide"); itemDeathDate.classList.remove("hide");
itemDeathDate.innerHTML = globalize.translate("DeathDateValue").replace("{0}", deathday); itemDeathDate.innerHTML = globalize.translate("DeathDateValue", deathday);
} catch (err) { } catch (err) {
itemDeathDate.classList.add("hide"); itemDeathDate.classList.add("hide");
} }
@ -618,7 +618,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
if ("Person" == item.Type && item.ProductionLocations && item.ProductionLocations.length) { if ("Person" == item.Type && item.ProductionLocations && item.ProductionLocations.length) {
var gmap = '<a is="emby-linkbutton" class="button-link textlink" target="_blank" href="https://maps.google.com/maps?q=' + item.ProductionLocations[0] + '">' + item.ProductionLocations[0] + "</a>"; var gmap = '<a is="emby-linkbutton" class="button-link textlink" target="_blank" href="https://maps.google.com/maps?q=' + item.ProductionLocations[0] + '">' + item.ProductionLocations[0] + "</a>";
itemBirthLocation.classList.remove("hide"); itemBirthLocation.classList.remove("hide");
itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue").replace("{0}", gmap); itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue", gmap);
} else { } else {
itemBirthLocation.classList.add("hide"); itemBirthLocation.classList.add("hide");
} }

View file

@ -91,21 +91,21 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu"
switch (recommendation.RecommendationType) { switch (recommendation.RecommendationType) {
case "SimilarToRecentlyPlayed": case "SimilarToRecentlyPlayed":
title = Globalize.translate("RecommendationBecauseYouWatched").replace("{0}", recommendation.BaselineItemName); title = Globalize.translate("RecommendationBecauseYouWatched", recommendation.BaselineItemName);
break; break;
case "SimilarToLikedItem": case "SimilarToLikedItem":
title = Globalize.translate("RecommendationBecauseYouLike").replace("{0}", recommendation.BaselineItemName); title = Globalize.translate("RecommendationBecauseYouLike", recommendation.BaselineItemName);
break; break;
case "HasDirectorFromRecentlyPlayed": case "HasDirectorFromRecentlyPlayed":
case "HasLikedDirector": case "HasLikedDirector":
title = Globalize.translate("RecommendationDirectedBy").replace("{0}", recommendation.BaselineItemName); title = Globalize.translate("RecommendationDirectedBy", recommendation.BaselineItemName);
break; break;
case "HasActorFromRecentlyPlayed": case "HasActorFromRecentlyPlayed":
case "HasLikedActor": case "HasLikedActor":
title = Globalize.translate("RecommendationStarring").replace("{0}", recommendation.BaselineItemName); title = Globalize.translate("RecommendationStarring", recommendation.BaselineItemName);
break; break;
} }

View file

@ -1,4 +1,4 @@
define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light", "cardStyle", "emby-button", "indicators", "flexStyles"], function (loading, dom, globalize) { define(["loading", "dom", "globalize", "date-fns", "dfnshelper", "paper-icon-button-light", "cardStyle", "emby-button", "indicators", "flexStyles"], function (loading, dom, globalize, datefns, dfnshelper) {
"use strict"; "use strict";
function deleteUser(page, id) { function deleteUser(page, id) {
@ -125,10 +125,11 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light",
html += "</div>"; html += "</div>";
return html + "</div>"; return html + "</div>";
} }
// FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix
// how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences
function getLastSeenText(lastActivityDate) { function getLastSeenText(lastActivityDate) {
if (lastActivityDate) { if (lastActivityDate) {
return "Last seen " + humaneDate(lastActivityDate); return globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(lastActivityDate), dfnshelper.localeWithSuffix));
} }
return ""; return "";

104
src/scripts/dfnshelper.js Normal file
View file

@ -0,0 +1,104 @@
import { ar, be, bg, ca, cs, da, de, el, enGB, enUS, es, faIR, fi, fr, frCA, he, hi, hr, hu, id, it, kk, ko, lt, ms, nb, nl, pl, ptBR, pt, ro, ru, sk, sl, sv, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale';
import globalize from 'globalize';
export function getLocale() {
switch (globalize.getCurrentLocale()) {
case 'ar':
return ar;
case 'be-by':
return be;
case 'bg-bg':
return bg;
case 'ca':
return ca;
case 'cs':
return cs;
case 'da':
return da;
case 'de':
return de;
case 'el':
return el;
case 'en-gb':
return enGB;
case 'en-us':
return enUS;
case 'es':
return es;
case 'es-ar':
return es;
case 'es-mx':
return es;
case 'fa':
return faIR;
case 'fi':
return fi;
case 'fr':
return fr;
case 'fr-ca':
return frCA;
case 'gsw':
return de;
case 'he':
return he;
case 'hi-in':
return hi;
case 'hr':
return hr;
case 'hu':
return hu;
case 'id':
return id;
case 'it':
return it;
case 'kk':
return kk;
case 'ko':
return ko;
case 'lt-lt':
return lt;
case 'ms':
return ms;
case 'nb':
return nb;
case 'nl':
return nl;
case 'pl':
return pl;
case 'pt-br':
return ptBR;
case 'pt-pt':
return pt;
case 'ro':
return ro;
case 'ru':
return ru;
case 'sk':
return sk;
case 'sl-si':
return sl;
case 'sv':
return sv;
case 'tr':
return tr;
case 'uk':
return uk;
case 'vi':
return vi;
case 'zh-cn':
return zhCN;
case 'zh-hk':
return zhCN;
case 'zh-tw':
return zhTW;
default:
return enUS;
}
}
export const localeWithSuffix = { addSuffix: true, locale: getLocale() };
export default {
getLocale: getLocale,
localeWithSuffix: localeWithSuffix
}

View file

@ -83,7 +83,7 @@ define(["userSettings"], function (userSettings) {
if (html += '<div class="listPaging">', showControls) { if (html += '<div class="listPaging">', showControls) {
html += '<span style="vertical-align:middle;">'; html += '<span style="vertical-align:middle;">';
html += (totalRecordCount ? startIndex + 1 : 0) + "-" + recordsEnd + " of " + totalRecordCount; html += Globalize.translate("ListPaging", (totalRecordCount ? startIndex + 1 : 0), recordsEnd, totalRecordCount);
html += "</span>"; html += "</span>";
} }

View file

@ -574,6 +574,7 @@ var AppInfo = {};
} }
require(["mediaSession", "serverNotifications"]); require(["mediaSession", "serverNotifications"]);
require(["date-fns", "date-fns/locale"]);
if (!browser.tv && !browser.xboxOne) { if (!browser.tv && !browser.xboxOne) {
require(["components/playback/playbackorientation"]); require(["components/playback/playbackorientation"]);
@ -647,12 +648,12 @@ var AppInfo = {};
inputManager: "scripts/inputManager", inputManager: "scripts/inputManager",
datetime: "scripts/datetime", datetime: "scripts/datetime",
globalize: "scripts/globalize", globalize: "scripts/globalize",
dfnshelper: "scripts/dfnshelper",
libraryMenu: "scripts/librarymenu", libraryMenu: "scripts/librarymenu",
playlisteditor: componentsPath + "/playlisteditor/playlisteditor", playlisteditor: componentsPath + "/playlisteditor/playlisteditor",
medialibrarycreator: componentsPath + "/medialibrarycreator/medialibrarycreator", medialibrarycreator: componentsPath + "/medialibrarycreator/medialibrarycreator",
medialibraryeditor: componentsPath + "/medialibraryeditor/medialibraryeditor", medialibraryeditor: componentsPath + "/medialibraryeditor/medialibraryeditor",
imageoptionseditor: componentsPath + "/imageoptionseditor/imageoptionseditor", imageoptionseditor: componentsPath + "/imageoptionseditor/imageoptionseditor",
humanedate: componentsPath + "/humanedate",
apphost: componentsPath + "/apphost", apphost: componentsPath + "/apphost",
visibleinviewport: componentsPath + "/visibleinviewport", visibleinviewport: componentsPath + "/visibleinviewport",
qualityoptions: componentsPath + "/qualityoptions", qualityoptions: componentsPath + "/qualityoptions",
@ -693,6 +694,7 @@ var AppInfo = {};
"webcomponents", "webcomponents",
"material-icons", "material-icons",
"jellyfin-noto", "jellyfin-noto",
"date-fns",
"page", "page",
"polyfill" "polyfill"
] ]

View file

@ -521,7 +521,7 @@
"Images": "Images", "Images": "Images",
"ImportFavoriteChannelsHelp": "If enabled, only channels that are marked as favorite on the tuner device will be imported.", "ImportFavoriteChannelsHelp": "If enabled, only channels that are marked as favorite on the tuner device will be imported.",
"ImportMissingEpisodesHelp": "If enabled, information about missing episodes will be imported into your Jellyfin database and displayed within seasons and series. This may cause significantly longer library scans.", "ImportMissingEpisodesHelp": "If enabled, information about missing episodes will be imported into your Jellyfin database and displayed within seasons and series. This may cause significantly longer library scans.",
"InstallingPackage": "Installing {0}", "InstallingPackage": "Installing {0} (version {1})",
"InstantMix": "Instant mix", "InstantMix": "Instant mix",
"ItemCount": "{0} items", "ItemCount": "{0} items",
"Items": "Items", "Items": "Items",
@ -1210,9 +1210,9 @@
"OriginalAirDateValue": "Original air date: {0}", "OriginalAirDateValue": "Original air date: {0}",
"OtherArtist": "Other Artist", "OtherArtist": "Other Artist",
"Overview": "Overview", "Overview": "Overview",
"PackageInstallCancelled": "{0} installation cancelled.", "PackageInstallCancelled": "{0} (version {1}) installation cancelled.",
"PackageInstallCompleted": "{0} installation completed.", "PackageInstallCompleted": "{0} (version {1}) installation completed.",
"PackageInstallFailed": "{0} installation failed.", "PackageInstallFailed": "{0} (version {1}) installation failed.",
"ParentalRating": "Parental rating", "ParentalRating": "Parental rating",
"PasswordMatchError": "Password and password confirmation must match.", "PasswordMatchError": "Password and password confirmation must match.",
"PasswordResetComplete": "The password has been reset.", "PasswordResetComplete": "The password has been reset.",
@ -1480,5 +1480,17 @@
"XmlTvPathHelp": "A path to a XMLTV file. Jellyfin will read this file and periodically check it for updates. You are responsible for creating and updating the file.", "XmlTvPathHelp": "A path to a XMLTV file. Jellyfin will read this file and periodically check it for updates. You are responsible for creating and updating the file.",
"XmlTvSportsCategoriesHelp": "Programs with these categories will be displayed as sports programs. Separate multiple with '|'.", "XmlTvSportsCategoriesHelp": "Programs with these categories will be displayed as sports programs. Separate multiple with '|'.",
"Yes": "Yes", "Yes": "Yes",
"Yesterday": "Yesterday" "Yesterday": "Yesterday",
"PathNotFound": "The path could not be found. Please ensure the path is valid and try again.",
"WriteAccessRequired": "Jellyfin Server requires write access to this folder. Please ensure write access and try again.",
"ListPaging": "{0}-{1} of {2}",
"PersonRole": "as {0}",
"LastSeen": "Last seen {0}",
"DailyAt": "Daily at {0}",
"WeeklyAt": "{0}s at {1}",
"OnWakeFromSleep": "On wake from sleep",
"EveryXMinutes": "Every {0} minutes",
"EveryHour": "Every hour",
"EveryXHours": "Every {0} hours",
"OnApplicationStartup": "On application startup"
} }

View file

@ -425,7 +425,7 @@
"Images": "Imágenes", "Images": "Imágenes",
"ImportFavoriteChannelsHelp": "Si está activado, sólo los canales guardados como favoritos en el sintonizador se importarán.", "ImportFavoriteChannelsHelp": "Si está activado, sólo los canales guardados como favoritos en el sintonizador se importarán.",
"ImportMissingEpisodesHelp": "Si está activada, la información sobre los episodios que faltan se importará en su base de datos Jellyfin y se mostrará en temporadas y series. Esto puede causar exploraciones de bibliotecas significativamente más largas.", "ImportMissingEpisodesHelp": "Si está activada, la información sobre los episodios que faltan se importará en su base de datos Jellyfin y se mostrará en temporadas y series. Esto puede causar exploraciones de bibliotecas significativamente más largas.",
"InstallingPackage": "Instalando {0}", "InstallingPackage": "Instalando {0} (versión {1})",
"InstantMix": "Mix instantáneo", "InstantMix": "Mix instantáneo",
"ItemCount": "Elementos {0}", "ItemCount": "Elementos {0}",
"Items": "Elemento", "Items": "Elemento",
@ -998,9 +998,9 @@
"OptionWeekly": "Semanal", "OptionWeekly": "Semanal",
"OriginalAirDateValue": "Fecha de emisión original: {0}", "OriginalAirDateValue": "Fecha de emisión original: {0}",
"Overview": "Sinopsis", "Overview": "Sinopsis",
"PackageInstallCancelled": "{0} instalación cancelada.", "PackageInstallCancelled": "{0} (versión {1}) instalación cancelada.",
"PackageInstallCompleted": "{0} instalación completada.", "PackageInstallCompleted": "{0} (versión {1}) instalación completada.",
"PackageInstallFailed": "{0} instalación fallida.", "PackageInstallFailed": "{0} (versión {1}) instalación fallida.",
"ParentalRating": "Calificación de los padres", "ParentalRating": "Calificación de los padres",
"PasswordMatchError": "La contraseña y la confirmación de la contraseña deben de ser iguales.", "PasswordMatchError": "La contraseña y la confirmación de la contraseña deben de ser iguales.",
"PasswordResetComplete": "La contraseña se ha restablecido.", "PasswordResetComplete": "La contraseña se ha restablecido.",
@ -1477,5 +1477,17 @@
"AllowFfmpegThrottling": "Acelerar transcodificación", "AllowFfmpegThrottling": "Acelerar transcodificación",
"ClientSettings": "Ajustes de cliente", "ClientSettings": "Ajustes de cliente",
"PreferEmbeddedEpisodeInfosOverFileNames": "Priorizar la información embebida sobre los nombres de archivos", "PreferEmbeddedEpisodeInfosOverFileNames": "Priorizar la información embebida sobre los nombres de archivos",
"PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Usar la información de episodio de los metadatos embebidos si está disponible." "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Usar la información de episodio de los metadatos embebidos si está disponible.",
"PathNotFound": "No se encontró la ruta especificada. Asegúrate de que existe e inténtalo de nuevo.",
"WriteAccessRequired": "Jellyfin requiere de permisos de escritura en esta carpeta. Asegúrate de que existe este permiso e inténtalo de nuevo.",
"ListPaging": "{0}-{1} de {2}",
"PersonRole": "como {0}",
"LastSeen": "Última vez {0}",
"DailyAt": "Diariamente a las {0}",
"WeeklyAt": "Los {0}s a las {1}",
"OnWakeFromSleep": "Al reanudar el servidor",
"EveryXMinutes": "Cada {0} minutos",
"EveryHour": "Cada hora",
"EveryXHours": "Cada {0} horas",
"OnApplicationStartup": "Al iniciarse el servidor"
} }

View file

@ -3107,6 +3107,11 @@ dashdash@^1.12.0:
dependencies: dependencies:
assert-plus "^1.0.0" assert-plus "^1.0.0"
date-fns@^2.11.1:
version "2.11.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.11.1.tgz#197b8be1bbf5c5e6fe8bea817f0fe111820e7a12"
integrity sha512-3RdUoinZ43URd2MJcquzBbDQo+J87cSzB8NkXdZiN5ia1UNyep0oCyitfiL88+R7clGTeq/RniXAc16gWyAu1w==
dateformat@^2.0.0: dateformat@^2.0.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062"