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
Globalize: writable
Hls: writable
humaneDate: writable
humaneElapsed: writable
dfnshelper: writable
LibraryMenu: writable
LinkParser: writable
LiveTvHelpers: writable

View file

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

View file

@ -112,3 +112,14 @@ var polyfill = require("@babel/polyfill/dist/polyfill");
_define("polyfill", function () {
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";
function getEntryHtml(entry, apiClient) {
@ -26,8 +26,7 @@ define(["events", "globalize", "dom", "datetime", "userSettings", "serverNotific
html += entry.Name;
html += "</div>";
html += '<div class="listItemBodyText secondary">';
var date = datetime.parseISO8601Date(entry.Date, true);
html += datetime.toLocaleString(date).toLowerCase();
html += datefns.formatRelative(Date.parse(entry.Date), Date.parse(new Date()), { locale: dfnshelper.getLocale() });
html += "</div>";
html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">';
html += entry.ShortOverview || "";

View file

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

View file

@ -89,7 +89,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
var instruction = options.instruction ? options.instruction + "<br/><br/>" : "";
html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
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()) {
html += "<br/>";
html += "<br/>";
@ -163,16 +163,15 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper-
}
}).catch(function(response) {
if (response) {
// TODO All alerts (across the project), should use Globalize.translate()
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();
}
if (response.status === 500) {
if (validateWriteable) {
alertText("Jellyfin Server requires write access to this folder. Please ensure write access and try again.");
alertText(Globalize.translate("WriteAccessRequired"));
} else {
alertText("The path could not be found. Please ensure the path is valid and try again.")
alertText(Globalize.translate("PathNotFound"))
}
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;">';
var startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount;
html += globalize.translate("ListPaging", startAtDisplay, recordsEnd, totalRecordCount);
html += '</span>';

View file

@ -309,7 +309,7 @@ define(["dialogHelper", "loading", "connectionManager", "require", "globalize",
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 + '"/>';

View file

@ -470,7 +470,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi
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="flex align-items-center">';

View file

@ -173,15 +173,15 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir
};
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;
} 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') {
notification.title = globalize.translate('PackageInstallFailed').replace('{0}', installation.Name + ' ' + installation.Version);
notification.title = globalize.translate('PackageInstallFailed', installation.Name, installation.Version);
notification.vibrate = true;
} 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 =
[

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";
function showPlaybackInfo(btn, session) {
@ -467,10 +467,11 @@ define(["datetime", "events", "itemHelper", "serverNotifications", "dom", "globa
getNowPlayingName: function (session) {
var imgUrl = "";
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) {
return {
html: "Last seen " + humaneDate(session.LastActivityDate),
html: globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(session.LastActivityDate), dfnshelper.localeWithSuffix)),
image: imgUrl
};
}

View file

@ -84,7 +84,7 @@ define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "e
}
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);
} else {
$("#pCurrentVersion", page).hide().html("");

View file

@ -116,7 +116,7 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby
return ip.Id == plugin.guid;
})[0];
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>";

View file

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

View file

@ -75,17 +75,19 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
html += "</div>";
context.querySelector(".taskTriggers").innerHTML = html;
},
// TODO: Replace this mess with date-fns and remove datetime completely
getTriggerFriendlyName: function (trigger) {
if ("DailyTrigger" == trigger.Type) {
return "Daily at " + ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks);
return globalize.translate("DailyAt", ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
}
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) {
return "On wake from sleep";
return globalize.translate("OnWakeFromSleep");
}
if (trigger.Type == "IntervalTrigger") {
@ -93,23 +95,23 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby
var hours = trigger.IntervalTicks / 36e9;
if (hours == 0.25) {
return "Every 15 minutes";
return globalize.translate("EveryXMinutes", "15");
}
if (hours == 0.5) {
return "Every 30 minutes";
return globalize.translate("EveryXMinutes", "30");
}
if (hours == 0.75) {
return "Every 45 minutes";
return globalize.translate("EveryXMinutes", "45");
}
if (hours == 1) {
return "Every hour";
return globalize.translate("EveryHour");
}
return "Every " + hours + " hours";
return globalize.translate("EveryXHours", hours);
}
if (trigger.Type == "StartupTrigger") {
return "On application startup";
return globalize.translate("OnApplicationStartup");
}
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";
function reloadList(page) {
@ -66,7 +66,10 @@ define(["jQuery", "loading", "events", "globalize", "serverNotifications", "huma
var html = "";
if (task.State === "Idle") {
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") {
html += " <span style='color:#FF0000;'>(" + globalize.translate("LabelFailed") + ")</span>";
} 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";
function canDelete(deviceId) {
@ -103,7 +103,7 @@ define(["loading", "dom", "libraryMenu", "globalize", "scripts/imagehelper", "hu
if (device.LastUserName) {
deviceHtml += device.LastUserName;
deviceHtml += ", " + humaneDate(device.DateLastActivity);
deviceHtml += ", " + datefns.formatDistanceToNow(Date.parse(device.DateLastActivity), dfnshelper.localeWithSuffix);
}
deviceHtml += "&nbsp;";

View file

@ -258,14 +258,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>";
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) {
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
} else {
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 += '<a is="emby-linkbutton" href="#" class="lnkEditSubProfile" data-profileindex="' + i + '">';
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) {
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
} else {
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 += '<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) {
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;
}).join(", "));
html += "</p>";
@ -476,11 +476,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>";
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) {
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;
}).join(", "));
html += "</p>";
@ -547,20 +547,20 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in
html += "<div>";
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) {
html += "<p>" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "</p>";
html += "<p>" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "</p>";
} else {
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) {
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;
}).join(", "));
html += "</p>";

View file

@ -591,7 +591,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
try {
var birthday = datetime.parseISO8601Date(item.PremiereDate, true).toDateString();
itemBirthday.classList.remove("hide");
itemBirthday.innerHTML = globalize.translate("BirthDateValue").replace("{0}", birthday);
itemBirthday.innerHTML = globalize.translate("BirthDateValue", birthday);
} catch (err) {
itemBirthday.classList.add("hide");
}
@ -605,7 +605,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
try {
var deathday = datetime.parseISO8601Date(item.EndDate, true).toDateString();
itemDeathDate.classList.remove("hide");
itemDeathDate.innerHTML = globalize.translate("DeathDateValue").replace("{0}", deathday);
itemDeathDate.innerHTML = globalize.translate("DeathDateValue", deathday);
} catch (err) {
itemDeathDate.classList.add("hide");
}
@ -618,7 +618,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "userSetti
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>";
itemBirthLocation.classList.remove("hide");
itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue").replace("{0}", gmap);
itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue", gmap);
} else {
itemBirthLocation.classList.add("hide");
}

View file

@ -91,21 +91,21 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu"
switch (recommendation.RecommendationType) {
case "SimilarToRecentlyPlayed":
title = Globalize.translate("RecommendationBecauseYouWatched").replace("{0}", recommendation.BaselineItemName);
title = Globalize.translate("RecommendationBecauseYouWatched", recommendation.BaselineItemName);
break;
case "SimilarToLikedItem":
title = Globalize.translate("RecommendationBecauseYouLike").replace("{0}", recommendation.BaselineItemName);
title = Globalize.translate("RecommendationBecauseYouLike", recommendation.BaselineItemName);
break;
case "HasDirectorFromRecentlyPlayed":
case "HasLikedDirector":
title = Globalize.translate("RecommendationDirectedBy").replace("{0}", recommendation.BaselineItemName);
title = Globalize.translate("RecommendationDirectedBy", recommendation.BaselineItemName);
break;
case "HasActorFromRecentlyPlayed":
case "HasLikedActor":
title = Globalize.translate("RecommendationStarring").replace("{0}", recommendation.BaselineItemName);
title = Globalize.translate("RecommendationStarring", recommendation.BaselineItemName);
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";
function deleteUser(page, id) {
@ -125,10 +125,11 @@ define(["loading", "dom", "globalize", "humanedate", "paper-icon-button-light",
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) {
if (lastActivityDate) {
return "Last seen " + humaneDate(lastActivityDate);
return globalize.translate("LastSeen", datefns.formatDistanceToNow(Date.parse(lastActivityDate), dfnshelper.localeWithSuffix));
}
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) {
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>";
}

View file

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

View file

@ -521,7 +521,7 @@
"Images": "Images",
"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.",
"InstallingPackage": "Installing {0}",
"InstallingPackage": "Installing {0} (version {1})",
"InstantMix": "Instant mix",
"ItemCount": "{0} items",
"Items": "Items",
@ -1210,9 +1210,9 @@
"OriginalAirDateValue": "Original air date: {0}",
"OtherArtist": "Other Artist",
"Overview": "Overview",
"PackageInstallCancelled": "{0} installation cancelled.",
"PackageInstallCompleted": "{0} installation completed.",
"PackageInstallFailed": "{0} installation failed.",
"PackageInstallCancelled": "{0} (version {1}) installation cancelled.",
"PackageInstallCompleted": "{0} (version {1}) installation completed.",
"PackageInstallFailed": "{0} (version {1}) installation failed.",
"ParentalRating": "Parental rating",
"PasswordMatchError": "Password and password confirmation must match.",
"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.",
"XmlTvSportsCategoriesHelp": "Programs with these categories will be displayed as sports programs. Separate multiple with '|'.",
"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",
"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.",
"InstallingPackage": "Instalando {0}",
"InstallingPackage": "Instalando {0} (versión {1})",
"InstantMix": "Mix instantáneo",
"ItemCount": "Elementos {0}",
"Items": "Elemento",
@ -998,9 +998,9 @@
"OptionWeekly": "Semanal",
"OriginalAirDateValue": "Fecha de emisión original: {0}",
"Overview": "Sinopsis",
"PackageInstallCancelled": "{0} instalación cancelada.",
"PackageInstallCompleted": "{0} instalación completada.",
"PackageInstallFailed": "{0} instalación fallida.",
"PackageInstallCancelled": "{0} (versión {1}) instalación cancelada.",
"PackageInstallCompleted": "{0} (versión {1}) instalación completada.",
"PackageInstallFailed": "{0} (versión {1}) instalación fallida.",
"ParentalRating": "Calificación de los padres",
"PasswordMatchError": "La contraseña y la confirmación de la contraseña deben de ser iguales.",
"PasswordResetComplete": "La contraseña se ha restablecido.",
@ -1477,5 +1477,17 @@
"AllowFfmpegThrottling": "Acelerar transcodificación",
"ClientSettings": "Ajustes de cliente",
"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:
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:
version "2.2.0"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062"