diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 7c7801b86..9c231e139 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -95,7 +95,7 @@ jobs: displayName: 'Install Dependencies' condition: ne(variables.CACHE_RESTORED, 'true') - - script: 'yarn run lint' + - script: 'yarn run lint --quiet' displayName: 'Run ESLint' - script: 'yarn run stylelint' diff --git a/.eslintrc.yml b/.eslintrc.yml index 377716d53..f501f3354 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,21 +1,31 @@ env: - es6: true - browser: true amd: true + browser: true + es6: true + es2017: true + es2020: true parserOptions: - ecmaVersion: 6 + ecmaVersion: 2020 sourceType: module ecmaFeatures: impliedStrict: true +plugins: + - promise + - import + - eslint-comments + +extends: + - eslint:recommended + - plugin:promise/recommended + - plugin:import/errors + - plugin:import/warnings + - plugin:eslint-comments/recommended + globals: - # New browser globals - DataView: readonly + # Browser globals MediaMetadata: readonly - Promise: readonly - # Deprecated browser globals - DocumentTouch: readonly # Tizen globals tizen: readonly webapis: readonly @@ -24,7 +34,6 @@ globals: # Dependency globals $: readonly jQuery: readonly - queryString: readonly requirejs: readonly # Jellyfin globals ApiClient: writable @@ -40,8 +49,7 @@ globals: getWindowLocationSearch: writable Globalize: writable Hls: writable - humaneDate: writable - humaneElapsed: writable + dfnshelper: writable LibraryMenu: writable LinkParser: writable LiveTvHelpers: writable @@ -52,9 +60,6 @@ globals: UserParentalControlPage: writable Windows: readonly -extends: - - eslint:recommended - rules: block-spacing: ["error"] brace-style: ["error"] @@ -75,3 +80,8 @@ rules: no-redeclare: ["warn"] no-unused-vars: ["warn"] no-useless-escape: ["warn"] + promise/catch-or-return: ["warn"] + promise/always-return: ["warn"] + promise/no-return-wrap: ["warn"] + # TODO: Remove after ES6 migration is complete + import/no-unresolved: ["warn"] diff --git a/package.json b/package.json index 2f34ea230..b112d0be6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,9 @@ "cssnano": "^4.1.10", "del": "^5.1.0", "eslint": "^6.8.0", + "eslint-plugin-eslint-comments": "^3.1.2", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-promise": "^4.2.1", "file-loader": "^6.0.0", "gulp": "^4.0.2", "gulp-babel": "^8.0.0", @@ -53,6 +56,7 @@ "alameda": "^1.4.0", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "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", @@ -87,7 +91,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" diff --git a/src/bundle.js b/src/bundle.js index a8d872d4e..e6807c540 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -118,3 +118,14 @@ var classlist = require("classlist.js"); _define("classlist-polyfill", function () { return classlist; }); + +// 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; +}); diff --git a/src/components/activitylog.js b/src/components/activitylog.js index 05971f01b..934a610ad 100644 --- a/src/components/activitylog.js +++ b/src/components/activitylog.js @@ -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 += ""; html += '
'; - 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 += "
"; html += '
'; html += entry.ShortOverview || ""; diff --git a/src/components/autoFocuser.js b/src/components/autoFocuser.js index a469eb885..43c341bfd 100644 --- a/src/components/autoFocuser.js +++ b/src/components/autoFocuser.js @@ -95,8 +95,10 @@ import layoutManager from "layoutManager"; return focusedElement; } - export default { - isEnabled: isEnabled, - enable: enable, - autoFocus: autoFocus - }; +/* eslint-enable indent */ + +export default { + isEnabled: isEnabled, + enable: enable, + autoFocus: autoFocus +}; diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 1249f802a..a4cf6edad 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -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)); } } } @@ -1854,6 +1850,8 @@ import 'programStyles'; } } +/* eslint-enable indent */ + export default { getCardsHtml: getCardsHtml, getDefaultBackgroundClass: getDefaultBackgroundClass, diff --git a/src/components/directorybrowser/directorybrowser.js b/src/components/directorybrowser/directorybrowser.js index b71f7bbb0..1d5c23eb1 100644 --- a/src/components/directorybrowser/directorybrowser.js +++ b/src/components/directorybrowser/directorybrowser.js @@ -89,7 +89,7 @@ define(['loading', 'dialogHelper', 'dom', 'listViewStyle', 'emby-input', 'paper- var instruction = options.instruction ? options.instruction + "

" : ""; html += '
'; html += instruction; - html += Globalize.translate("MessageDirectoryPickerInstruction").replace("{0}", "\\\\server").replace("{1}", "\\\\192.168.1.101"); + html += Globalize.translate("MessageDirectoryPickerInstruction", "\\\\server", "\\\\192.168.1.101"); if ("bsd" === systemInfo.OperatingSystem.toLowerCase()) { html += "
"; html += "
"; @@ -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() } diff --git a/src/components/dom.js b/src/components/dom.js index 3fe428732..a9ce5d53a 100644 --- a/src/components/dom.js +++ b/src/components/dom.js @@ -262,15 +262,17 @@ return _transitionEvent; } - export default { - parentWithAttribute: parentWithAttribute, - parentWithClass: parentWithClass, - parentWithTag: parentWithTag, - addEventListener: addEventListener, - removeEventListener: removeEventListener, - getWindowSize: getWindowSize, - getScreenWidth: getScreenWidth, - whichTransitionEvent: whichTransitionEvent, - whichAnimationEvent: whichAnimationEvent, - whichAnimationCancelEvent: whichAnimationCancelEvent - }; +/* eslint-enable indent */ + +export default { + parentWithAttribute: parentWithAttribute, + parentWithClass: parentWithClass, + parentWithTag: parentWithTag, + addEventListener: addEventListener, + removeEventListener: removeEventListener, + getWindowSize: getWindowSize, + getScreenWidth: getScreenWidth, + whichTransitionEvent: whichTransitionEvent, + whichAnimationEvent: whichAnimationEvent, + whichAnimationCancelEvent: whichAnimationCancelEvent +}; diff --git a/src/components/htmlvideoplayer/plugin.js b/src/components/htmlvideoplayer/plugin.js index 97056f80f..451b53aef 100644 --- a/src/components/htmlvideoplayer/plugin.js +++ b/src/components/htmlvideoplayer/plugin.js @@ -1048,11 +1048,12 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function renderSsaAss(videoElement, track, item) { var attachments = self._currentPlayOptions.mediaSource.MediaAttachments || []; + var apiClient = connectionManager.getApiClient(item); var options = { video: videoElement, subUrl: getTextTrackUrl(track, item), fonts: attachments.map(function (i) { - return i.DeliveryUrl; + return apiClient.getUrl(i.DeliveryUrl); }), workerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker.js", legacyWorkerUrl: appRouter.baseUrl() + "/libraries/subtitles-octopus-worker-legacy.js", diff --git a/src/components/humanedate.js b/src/components/humanedate.js deleted file mode 100644 index 26ce26d94..000000000 --- a/src/components/humanedate.js +++ /dev/null @@ -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 - }; -}); diff --git a/src/components/imagedownloader/imagedownloader.js b/src/components/imagedownloader/imagedownloader.js index f4fcd7091..9df083aea 100644 --- a/src/components/imagedownloader/imagedownloader.js +++ b/src/components/imagedownloader/imagedownloader.js @@ -109,7 +109,7 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image html += ''; var startAtDisplay = totalRecordCount ? startIndex + 1 : 0; - html += startAtDisplay + '-' + recordsEnd + ' of ' + totalRecordCount; + html += globalize.translate("ListPaging", startAtDisplay, recordsEnd, totalRecordCount); html += ''; diff --git a/src/components/itemidentifier/itemidentifier.js b/src/components/itemidentifier/itemidentifier.js index 2a779618f..9f89aef94 100644 --- a/src/components/itemidentifier/itemidentifier.js +++ b/src/components/itemidentifier/itemidentifier.js @@ -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 += ''; diff --git a/src/components/metadataeditor/metadataeditor.js b/src/components/metadataeditor/metadataeditor.js index 84b60b32a..8a64cac7e 100644 --- a/src/components/metadataeditor/metadataeditor.js +++ b/src/components/metadataeditor/metadataeditor.js @@ -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 += '
'; html += '
'; diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 2c3e45b63..c8a79a362 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -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 = [ diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 037ca5b05..6a626cd25 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -544,8 +544,10 @@ import layoutManager from "layoutManager"; }, {capture: true}); } - export default { - isEnabled: isEnabled, - scrollTo: scrollTo, - scrollToElement: scrollToElement - }; +/* eslint-enable indent */ + +export default { + isEnabled: isEnabled, + scrollTo: scrollTo, + scrollToElement: scrollToElement +}; diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index 2057deaf6..78f5cdca0 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -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 }; } diff --git a/src/controllers/dashboard/plugins/add.js b/src/controllers/dashboard/plugins/add.js index 72a7134fa..a05cac461 100644 --- a/src/controllers/dashboard/plugins/add.js +++ b/src/controllers/dashboard/plugins/add.js @@ -84,7 +84,7 @@ define(["jQuery", "loading", "libraryMenu", "globalize", "connectionManager", "e } if (installedPlugin) { - var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled").replace("{0}", "" + installedPlugin.Version + ""); + var currentVersionText = globalize.translate("MessageYouHaveVersionInstalled", "" + installedPlugin.Version + ""); $("#pCurrentVersion", page).show().html(currentVersionText); } else { $("#pCurrentVersion", page).hide().html(""); diff --git a/src/controllers/dashboard/plugins/available.js b/src/controllers/dashboard/plugins/available.js index 5526bd9ad..adccfa393 100644 --- a/src/controllers/dashboard/plugins/available.js +++ b/src/controllers/dashboard/plugins/available.js @@ -116,7 +116,7 @@ define(["loading", "libraryMenu", "globalize", "cardStyle", "emby-button", "emby return ip.Id == plugin.guid; })[0]; html += "
"; - html += installedPlugin ? globalize.translate("LabelVersionInstalled").replace("{0}", installedPlugin.Version) : " "; + html += installedPlugin ? globalize.translate("LabelVersionInstalled", installedPlugin.Version) : " "; html += "
"; html += "
"; html += "
"; diff --git a/src/controllers/dashboard/plugins/installed.js b/src/controllers/dashboard/plugins/installed.js index 026b58ce6..c381b2409 100644 --- a/src/controllers/dashboard/plugins/installed.js +++ b/src/controllers/dashboard/plugins/installed.js @@ -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({ diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtask.js b/src/controllers/dashboard/scheduledtasks/scheduledtask.js index 03eeeeb87..8a3cdf5d6 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtask.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtask.js @@ -75,17 +75,19 @@ define(["jQuery", "loading", "datetime", "dom", "globalize", "emby-input", "emby html += "
"; 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; diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js index 390fd1735..b91158d8b 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js @@ -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 += " (" + globalize.translate("LabelFailed") + ")"; } else if (task.LastExecutionResult.Status === "Cancelled") { diff --git a/src/controllers/devices.js b/src/controllers/devices.js index 3fd2be983..8dd665f7f 100644 --- a/src/controllers/devices.js +++ b/src/controllers/devices.js @@ -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 += " "; diff --git a/src/controllers/dlnaprofile.js b/src/controllers/dlnaprofile.js index fb4cdb425..ca0d3afdb 100644 --- a/src/controllers/dlnaprofile.js +++ b/src/controllers/dlnaprofile.js @@ -258,14 +258,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in html += "
"; html += ''; - html += "

" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "

"; + html += "

" + Globalize.translate("ValueContainer", profile.Container || allText) + "

"; if ("Video" == profile.Type) { - html += "

" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "

"; - html += "

" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "

"; + html += "

" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "

"; } else { if ("Audio" == profile.Type) { - html += "

" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "

"; } } @@ -319,14 +319,14 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in html += "
"; html += ''; html += "

Protocol: " + (profile.Protocol || "Http") + "

"; - html += "

" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "

"; + html += "

" + Globalize.translate("ValueContainer", profile.Container || allText) + "

"; if ("Video" == profile.Type) { - html += "

" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "

"; - html += "

" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "

"; + html += "

" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "

"; } else { if ("Audio" == profile.Type) { - html += "

" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "

"; } } @@ -404,11 +404,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in html += "
"; html += ''; - html += "

" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "

"; + html += "

" + Globalize.translate("ValueContainer", profile.Container || allText) + "

"; if (profile.Conditions && profile.Conditions.length) { html += "

"; - 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 += "

"; @@ -476,11 +476,11 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in html += "
"; html += ''; - html += "

" + Globalize.translate("ValueCodec").replace("{0}", profile.Codec || allText) + "

"; + html += "

" + Globalize.translate("ValueCodec", profile.Codec || allText) + "

"; if (profile.Conditions && profile.Conditions.length) { html += "

"; - 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 += "

"; @@ -547,20 +547,20 @@ define(["jQuery", "loading", "fnchecked", "emby-select", "emby-button", "emby-in html += "
"; html += ''; - html += "

" + Globalize.translate("ValueContainer").replace("{0}", profile.Container || allText) + "

"; + html += "

" + Globalize.translate("ValueContainer", profile.Container || allText) + "

"; if ("Video" == profile.Type) { - html += "

" + Globalize.translate("ValueVideoCodec").replace("{0}", profile.VideoCodec || allText) + "

"; - html += "

" + Globalize.translate("ValueAudioCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + Globalize.translate("ValueVideoCodec", profile.VideoCodec || allText) + "

"; + html += "

" + Globalize.translate("ValueAudioCodec", profile.AudioCodec || allText) + "

"; } else { if ("Audio" == profile.Type) { - html += "

" + Globalize.translate("ValueCodec").replace("{0}", profile.AudioCodec || allText) + "

"; + html += "

" + Globalize.translate("ValueCodec", profile.AudioCodec || allText) + "

"; } } if (profile.Conditions && profile.Conditions.length) { html += "

"; - 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 += "

"; diff --git a/src/controllers/itemdetailpage.js b/src/controllers/itemdetailpage.js index 23a672751..178419e28 100644 --- a/src/controllers/itemdetailpage.js +++ b/src/controllers/itemdetailpage.js @@ -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 = '
' + item.ProductionLocations[0] + ""; itemBirthLocation.classList.remove("hide"); - itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue").replace("{0}", gmap); + itemBirthLocation.innerHTML = globalize.translate("BirthPlaceValue", gmap); } else { itemBirthLocation.classList.add("hide"); } diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 7e19af4b9..98e087147 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -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; } diff --git a/src/controllers/userprofilespage.js b/src/controllers/userprofilespage.js index 2a2387ab6..180d0e62a 100644 --- a/src/controllers/userprofilespage.js +++ b/src/controllers/userprofilespage.js @@ -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 += "
"; return html + "
"; } - + // 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 ""; diff --git a/src/scripts/browser.js b/src/scripts/browser.js index f9e194232..19153bb19 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -292,7 +292,7 @@ define([], function () { } if (typeof document !== 'undefined') { - if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { + if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) { browser.touch = true; } } diff --git a/src/scripts/dfnshelper.js b/src/scripts/dfnshelper.js new file mode 100644 index 000000000..a593c42c0 --- /dev/null +++ b/src/scripts/dfnshelper.js @@ -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 +} diff --git a/src/scripts/librarybrowser.js b/src/scripts/librarybrowser.js index bd8980aed..bc8908fe6 100644 --- a/src/scripts/librarybrowser.js +++ b/src/scripts/librarybrowser.js @@ -83,7 +83,7 @@ define(["userSettings"], function (userSettings) { if (html += '
', showControls) { html += ''; - html += (totalRecordCount ? startIndex + 1 : 0) + "-" + recordsEnd + " of " + totalRecordCount; + html += Globalize.translate("ListPaging", (totalRecordCount ? startIndex + 1 : 0), recordsEnd, totalRecordCount); html += ""; } diff --git a/src/scripts/site.js b/src/scripts/site.js index 824d8d0ca..6ffe033fa 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -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", "classlist-polyfill" diff --git a/src/strings/ar.json b/src/strings/ar.json index 7fae30a08..524c245cd 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -919,7 +919,7 @@ "HeaderFavoriteArtists": "الفنانون المفضلون", "Shows": "الحلقات", "Books": "الكتب", - "ValueSpecialEpisodeName": "مميز - {0}", + "ValueSpecialEpisodeName": "خاص - {0}", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderAlbumArtists": "فناني الألبومات", "Genres": "الأنواع", diff --git a/src/strings/de.json b/src/strings/de.json index 3dab52c54..2adc5789e 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -17,7 +17,7 @@ "Albums": "Alben", "All": "Alle", "AllChannels": "Alle Kanäle", - "AllComplexFormats": "Alle komplexen Formate (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Alle komplexen Formate (ASS, SSA, VOBSUB, PGS, SUB/IDX)", "AllEpisodes": "Alle Folgen", "AllLanguages": "Alle Sprachen", "AllLibraries": "Alle Bibliotheken", @@ -32,7 +32,7 @@ "AllowSeasonalThemes": "Erlaube automatische Jahreszeitenmotive", "AllowSeasonalThemesHelp": "Wenn aktiviert, werden Jahreszeitenmotive von Zeit zu Zeit deine Motiveinstellungen überschreiben.", "AllowedRemoteAddressesHelp": "Kommagetrennte Liste von IP Adressen oder IP/Netzmasken für Netzwerke, für die externe Verbindungen erlaubt sind. Wenn leer, sind alle Adressen erlaubt.", - "AlwaysPlaySubtitles": "Untertitel immer einblenden", + "AlwaysPlaySubtitles": "Immer anzeigen", "AlwaysPlaySubtitlesHelp": "Untertitel die den Spracheinstellungen entsprechen werden unabhängig von der Tonspursprache geladen.", "AnyLanguage": "Jede Sprache", "Anytime": "Jederzeit", @@ -60,7 +60,7 @@ "BoxRear": "Box (Rückseite)", "Browse": "Blättern", "BrowsePluginCatalogMessage": "Durchsuche unsere Bibliothek, um alle verfügbaren Plugins anzuzeigen.", - "BurnSubtitlesHelp": "Legt fest, ob der Server die Untertitel basierend auf deren Format während der Videokonvertierung einbrennen soll. Die Vermeidung des Einbrennen von Untertiteln verbessert die Serverperformance. Wähle Auto, um Bildfomate (z.B. VOBSUB, PGS, SUB/IDX, etc.) sowie bestimmte ASS/SSA-Untertitel einbrennen zu lassen.", + "BurnSubtitlesHelp": "Legt fest, ob der Server die Untertitel während der Videotranskodierung einbrennen soll. Deaktivieren verbessert die Serverperformance immens. Wähle Auto, um bildbasierte Formate (z.B. VOBSUB, PGS, SUB, IDX) sowie bestimmte ASS- oder SSA-Untertitel einbrennen zu lassen.", "ButtonAdd": "Hinzufügen", "ButtonAddMediaLibrary": "Füge Medienbibliothek hinzu", "ButtonAddScheduledTaskTrigger": "Auslöser hinzufügen", @@ -185,7 +185,7 @@ "DisplayInOtherHomeScreenSections": "Zeige auf dem Homescreen Bereiche wie 'Neueste Medien' oder 'Weiterschauen'", "DisplayMissingEpisodesWithinSeasons": "Zeige fehlende Episoden innerhalb von Staffeln", "DisplayMissingEpisodesWithinSeasonsHelp": "Dies muss auch für Serienbibliotheken in den Servereinstellungen aktiviert sein.", - "DisplayModeHelp": "Bitte wähle den Typ des Bildschirms auf dem Du Jellyfin verwendest.", + "DisplayModeHelp": "Wähle das Layout welches du für die Oberfläche verwenden möchtest.", "DoNotRecord": "Nicht aufnehmen", "Down": "Runter", "DownloadsValue": "{0} Downloads", @@ -469,7 +469,7 @@ "Images": "Bilder", "ImportFavoriteChannelsHelp": "Wenn aktiviert, werden nur auf dem Tuner favorisierte Kanäle importiert.", "ImportMissingEpisodesHelp": "Wenn aktiviert, werden Informationen über fehlende Episoden in Deine Jellyfin Datenbank importiert und innerhalb von Staffeln angezeigt. Dies kann zu deutlich längeren Bibliothek Scans führen.", - "InstallingPackage": "Installiere {0}", + "InstallingPackage": "Installiere {0} (Version {1})", "InstantMix": "Schnellmix", "ItemCount": "{0} Einträge", "Items": "Einträge", @@ -908,15 +908,15 @@ "NoNextUpItemsMessage": "Es wurde nichts gefunden. Schau dir deine Shows an!", "NoPluginConfigurationMessage": "Dieses Plugin hat keine konfigurierbaren Einstellungen.", "NoSubtitleSearchResultsFound": "Keine Ergebnisse gefunden.", - "NoSubtitles": "Keine Untertitel", + "NoSubtitles": "Keine", "NoSubtitlesHelp": "Untertitel werden standardmäßig nicht geladen. Sie können aber während der Wiedergabe manuell aktiviert werden.", "None": "Keines", "NumLocationsValue": "{0} Verzeichnisse", "Off": "Aus", "OneChannel": "Ein Kanal", - "OnlyForcedSubtitles": "Nur erzwungene Untertitel", + "OnlyForcedSubtitles": "Nur Erzwungene", "OnlyForcedSubtitlesHelp": "Nur Untertitel, die als erzwungen markiert wurden, werden geladen.", - "OnlyImageFormats": "Nur Bildformate (VOBSUB, PGS, SUB, etc.)", + "OnlyImageFormats": "Nur Bildformate (VOBSUB, PGS, SUB)", "OptionAdminUsers": "Administratoren", "OptionAlbumArtist": "Album-Interpret", "OptionAllUsers": "Alle Benutzer", @@ -1036,9 +1036,9 @@ "OptionWeekly": "Wöchentlich", "OriginalAirDateValue": "Erstausstrahlung: {0}", "Overview": "Übersicht", - "PackageInstallCancelled": "{0} Installation abgebrochen.", - "PackageInstallCompleted": "{0} Installation abgeschlossen.", - "PackageInstallFailed": "{0} Installation fehlgeschlagen.", + "PackageInstallCancelled": "{0} (Version {1}) Installation abgebrochen.", + "PackageInstallCompleted": "{0} (Version {1}) Installation abgeschlossen.", + "PackageInstallFailed": "{0} (Version {1}) Installation fehlgeschlagen.", "ParentalRating": "Altersfreigabe", "PasswordMatchError": "Die Passwörter müssen übereinstimmen.", "PasswordResetComplete": "Das Passwort wurde zurückgesetzt.", @@ -1492,5 +1492,27 @@ "AllowFfmpegThrottling": "Transkodierung drosseln", "PlaybackErrorNoCompatibleStream": "Es gab ein Problem bei der Erkennung des Wiedergabeprofils des Clients und der Server sendet kein kompatibles Format.", "AllowFfmpegThrottlingHelp": "Wenn eine Transkodierung oder ein Remux weit genug über die aktuelle Abspielposition fortgeschritten ist, pausiere sie sodass weniger Ressourcen verbraucht werden. Dies ist am nützlichsten, wenn wenig geskippt wird. Bei Wiedergabeproblemen sollte diese Option deaktiviert werden.", - "ClientSettings": "Client Einstellungen" + "ClientSettings": "Client Einstellungen", + "OnApplicationStartup": "Beim Starten der Applikation", + "EveryXHours": "Alle {0} Stunden", + "EveryHour": "Jede Stunde", + "EveryXMinutes": "Alle {0} Minuten", + "OnWakeFromSleep": "Beim Aufwachen aus \"Energie sparen\"", + "WeeklyAt": "{0} um {1}", + "DailyAt": "Täglich um {0}", + "LastSeen": "Zuletzt gesehen {0}", + "PersonRole": "als {0}", + "ListPaging": "{0}-{1} von {2}", + "WriteAccessRequired": "Jellyfin Server benötigt Schreibrechte auf diesem Ordner. Bitte prüfe die Schreibrechte und versuche es erneut.", + "PathNotFound": "Der Pfad konnte nicht gefunden werden. Bitte versichere dich dass der Pfad korrekt ist und versuche es erneut.", + "Track": "Track", + "Season": "Staffel", + "ReleaseGroup": "Veröffentlichungs-Gruppe", + "Person": "Person", + "OtherArtist": "Andere Künstler", + "Movie": "Film", + "Episode": "Episode", + "Artist": "Künstler", + "AlbumArtist": "Album Künstler", + "Album": "Album" } diff --git a/src/strings/en-us.json b/src/strings/en-us.json index a62bb18aa..008c8565f 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -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" } diff --git a/src/strings/es-ar.json b/src/strings/es-ar.json index 6c7ed6f2d..93dfbbfc0 100644 --- a/src/strings/es-ar.json +++ b/src/strings/es-ar.json @@ -343,18 +343,18 @@ "EnableBackdrops": "Habilitar fondo", "EnableBackdropsHelp": "Si esta habilitado, los fondos van a ser mostrados en segundo plano de algunas paginas mientras estas en la biblioteca.", "EnableCinemaMode": "Habilitar modo Cine", - "EnableColorCodedBackgrounds": "Habilitar colores en el fondo del codigo", + "EnableColorCodedBackgrounds": "Habilitar colores en el fondo del código", "AuthProviderHelp": "Seleccione un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", "CriticRating": "Calificación de la crítica", "DefaultSubtitlesHelp": "Los subtítulos se cargan en base a los indicadores por defecto y los indicadores forzados en los metadatos embebidos. Las preferencias de idioma son consideradas cuando existe más de una opción.", "Dislike": "No me gusta", "EnableDebugLoggingHelp": "El registro de depuración debería activarse solo a fin de solucionar problemas. El incremento en el acceso al sistema de archivos podría prevenir que el servidor entre en modo de suspensión en algunos entornos.", "EnableDisplayMirroring": "Habilitar duplicación de la pantalla", - "EnableExternalVideoPlayers": "Habilitar reproductores de vídeo externos", + "EnableExternalVideoPlayers": "Habilitar reproductores de video externos", "EnableExternalVideoPlayersHelp": "Se mostrará un menú de reproductor externo al iniciar la reproducción de video", "EnableNextVideoInfoOverlay": "Habilitar vista de información del siguiente vídeo durante la reproducción", "EnableNextVideoInfoOverlayHelp": "Al finalizar un video, mostrar información sobre el siguiente vídeo en la lista de reproducción", - "EnablePhotos": "Habilitar fotos", + "EnablePhotos": "Mostrar fotos", "EnablePhotosHelp": "Las fotos serán detectadas y se mostrarán junto a otros archivos de medios", "EnableStreamLooping": "Repetir automáticamente transmisiones en vivo", "EnableStreamLoopingHelp": "Habilita esto sí las transmisiones en vivo sólo contienen unos cuantos segundos y es necesario solicitarlos continuamente. Habilitar esto cuando no es necesario puede causar problemas.", @@ -417,5 +417,43 @@ "ButtonAddImage": "Agregar imagen", "AskAdminToCreateLibrary": "Pregunte al administrador para crear una biblioteca.", "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o conversión avanza demasiado con respecto a la posición actual de la reproducción, se pausara el proceso para consumir menos recursos. esto es mas útil cuando no se hacen búsquedas de tiempo a menudo. Desactive esta opción si experimenta problemas en la reproducción.", - "AllowFfmpegThrottling": "Transcodificación Throttle" + "AllowFfmpegThrottling": "Transcodificación Throttle", + "HeaderCancelRecording": "Cancelar Grabación", + "HeaderBranding": "Marca", + "HeaderBooks": "Libros", + "HeaderBlockItemsWithNoRating": "Bloquear elementos con rating de información no reconocible:", + "HeaderAutomaticUpdates": "Actualizaciones Automáticas", + "HeaderAudioSettings": "Configuración del Audio", + "HeaderAudioBooks": "Audiolibros", + "HeaderAppearsOn": "Aparece en", + "HeaderApp": "Aplicación", + "HeaderApiKeysHelp": "Las aplicaciones externas requieren una llave API para poder comunicarse con el servidor Jellyfin. Las llaves se emiten iniciando sesión con una cuenta Jellyfin u otorgando manualmente una clave a la aplicación.", + "HeaderApiKeys": "Llaves API", + "HeaderApiKey": "Llave API", + "HeaderAllowMediaDeletionFrom": "Permitir el borrado de medios desde", + "HeaderAlert": "Alerta", + "HeaderAlbums": "Albumes", + "HeaderAdmin": "Admin", + "HeaderAdditionalParts": "Partes adicionales", + "HeaderAddUpdateImage": "Agregar/Actualizar imagen", + "HeaderAddToPlaylist": "Agregar a la lista de reproducción", + "HeaderAddToCollection": "Agregar a la Colección", + "HeaderAddScheduledTaskTrigger": "Agregar disparador", + "HeaderActivity": "Actividad", + "HeaderActiveRecordings": "Grabaciones activas", + "HeaderActiveDevices": "Dispositivos activos", + "HeaderAccessScheduleHelp": "Crear un calendario de acceso, para limitar el acceso en determinadas horas.", + "HeaderAccessSchedule": "Acceder al Calendario", + "HardwareAccelerationWarning": "Habilitar la aceleración de hardware puede causar inestabilidad en algunos entornos. Asegúrese de que su sistema operativo y los controladores de video estén completamente actualizados. Si tiene dificultades para reproducir el video después de habilitarlo, deberá volver a cambiar la configuración a \"Nada\".", + "HandledByProxy": "Manejado por un proxy reverso", + "HDPrograms": "Programas en HD", + "EncoderPresetHelp": "Elige un valor más rápido para mejorar la performance, o elige un valor más lento para mejorar la calidad.", + "FetchingData": "Obteniendo información adicional", + "Episode": "Episodio", + "Yesterday": "Ayer", + "ClientSettings": "Configuración del cliente", + "BoxSet": "Colección", + "Artist": "Artista", + "AlbumArtist": "Artista del Album", + "Album": "Album" } diff --git a/src/strings/es.json b/src/strings/es.json index 06318c22d..727fb2477 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -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" } diff --git a/src/strings/fr.json b/src/strings/fr.json index 77a0c37b2..d8451dc38 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -476,7 +476,7 @@ "Identify": "Identifier", "ImportFavoriteChannelsHelp": "Activez cette option pour n'importer que les chaînes ajoutées aux favoris sur le tuner.", "ImportMissingEpisodesHelp": "Les informations à propos des épisodes manquants seront importées dans votre base de donnée Jellyfin et affichées dans les saisons et séries. Cela peut accroître significativement la durée d'actualisation de la médiathèque.", - "InstallingPackage": "Installation de {0}", + "InstallingPackage": "Installation de {0} (version {1})", "InstantMix": "Mix instantané", "ItemCount": "{0} éléments", "Items": "Éléments", @@ -1058,7 +1058,7 @@ "OptionReportByteRangeSeekingWhenTranscodingHelp": "Nécessaire pour certains appareils qui ne sont pas capables d'effectuer une recherche dans le temps correctement.", "OptionRequirePerfectSubtitleMatch": "Télécharger uniquement les sous-titres qui correspondent parfaitement à mes fichiers vidéo", "OptionRequirePerfectSubtitleMatchHelp": "En activant cette option, seuls les sous-titres ayant été testés et vérifiés avec votre fichier vidéo seront téléchargés. En désactivant cette option, vous aurez plus de chance que des sous-titres soient téléchargés, mais ils risquent d'être décalés ou incorrects.", - "OptionResElement": "res element", + "OptionResElement": "Résolution d'élément", "OptionResumable": "Reprise possible", "OptionRuntime": "Durée", "OptionSaturday": "Samedi", @@ -1080,9 +1080,9 @@ "OptionWeekly": "Hebdomadaire", "OriginalAirDateValue": "Date de diffusion originale : {0}", "Overview": "Synopsis", - "PackageInstallCancelled": "L'installation de {0} a été annulée.", - "PackageInstallCompleted": "L'installation de {0} est terminée.", - "PackageInstallFailed": "L'installation de {0} a échoué.", + "PackageInstallCancelled": "L'installation de {0} (version {1}) a été annulée.", + "PackageInstallCompleted": "L'installation de {0} (version {1}) est terminée.", + "PackageInstallFailed": "L'installation de {0} (version {1}) a échoué.", "ParentalRating": "Classification parentale", "PasswordMatchError": "Le mot de passe et sa confirmation doivent correspondre.", "PasswordResetComplete": "Le mot de passe a été réinitialisé.", @@ -1482,5 +1482,17 @@ "BoxSet": "Coffret", "Artist": "Artiste", "AlbumArtist": "Artiste de l'Album", - "Album": "Album" + "Album": "Album", + "OnApplicationStartup": "Au démarrage de l'application", + "EveryXHours": "Toutes les {0} heures", + "EveryHour": "Toutes les heures", + "EveryXMinutes": "Toutes les {0} minutes", + "OnWakeFromSleep": "À la sortie de veille", + "WeeklyAt": "{0} à {1}", + "DailyAt": "Tous les jours à {0}", + "LastSeen": "Vu pour la dernière fois {0}", + "PersonRole": "en tant que {0}", + "ListPaging": "{0}-{1} de {2}", + "WriteAccessRequired": "Le serveur Jellyfin a besoin d'un accès en écriture à ce dossier. Merci de vérifier l’accès en écriture et réessayez.", + "PathNotFound": "Le chemin d'accès n'a pas pu être trouvé. Merci de le vérifier et de réessayer." } diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index ccd90e05b..f0d36b2aa 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -2,31 +2,31 @@ "AccessRestrictedTryAgainLater": "目前访问受限。请稍后再试。", "Actor": "演员", "Add": "添加", - "AddItemToCollectionHelp": "通过搜索并使用鼠标右键单击或点击菜单将项目添加到集合中, 将项目添加到集合中。", - "AddToCollection": "加入收藏", + "AddItemToCollectionHelp": "通过搜索项目并右键或轻触得到的弹出菜单来将项目添加到集合中。", + "AddToCollection": "加入集合", "AddToPlayQueue": "添加至播放队列", - "AddToPlaylist": "添加到播放列表", + "AddToPlaylist": "添加至播放列表", "AddedOnValue": "已添加 {0}", "AdditionalNotificationServices": "浏览插件目录来安装额外的通知访问服务。", "AirDate": "播出日期", - "Aired": "已发布", + "Aired": "已播出", "Albums": "专辑", "Alerts": "警告", "All": "全部", "AllChannels": "所有频道", - "AllComplexFormats": "所有高级特效格式字幕(ASS, SSA, VOBSUB, PGS, SUB/IDX 等)", + "AllComplexFormats": "所有复杂格式字幕 (ASS, SSA, VOBSUB, PGS, SUB, IDX 等)", "AllEpisodes": "所有剧集", "AllLanguages": "所有语言", "AllLibraries": "所有媒体库", "AllowHWTranscodingHelp": "允许调谐器即时转码。这可能有助于减少Jellyfin 媒体服务器的转码工作。", "AllowMediaConversion": "允许媒体转换", - "AllowMediaConversionHelp": "授予或拒绝访问转换媒体功能。", - "AllowOnTheFlySubtitleExtraction": "允许同时提取字幕", + "AllowMediaConversionHelp": "授予或拒绝访问媒体转换功能。", + "AllowOnTheFlySubtitleExtraction": "允许实时提取字幕", "AllowOnTheFlySubtitleExtractionHelp": "为避免视频转码,可以从视频中提取内置的字幕,并以纯文本的形式发送给客户端。在某些系统中这个提取的进程可能会花费较长时间并导致视频播放出现卡顿。如果禁用这个选项,当内置字幕不能被客户端设备原生支持时,字幕将通过视频转码被烧录进视频中。", "AllowRemoteAccess": "允许与此 Jellyfin 服务器进行远程连接。", "AllowRemoteAccessHelp": "如果取消勾选,所有的远程连接将会被阻止。", - "AllowedRemoteAddressesHelp": "将允许远程连接的网络的 ip 地址或 ip/网掩码条目的逗号分隔列表。如果留空, 则允许所有远程地址。", - "AlwaysPlaySubtitles": "总是显示字幕", + "AllowedRemoteAddressesHelp": "允许远程连接的 IP 地址或 IP/子网掩码 的列表,以逗号分隔。如果留空,则允许所有远程地址。", + "AlwaysPlaySubtitles": "总是显示", "AlwaysPlaySubtitlesHelp": "无论音频为何种语言,都将加载与语言偏好匹配的字幕。", "Anytime": "任何时间", "AroundTime": "{0} 左右", @@ -48,7 +48,7 @@ "Books": "书籍", "Browse": "浏览", "BrowsePluginCatalogMessage": "浏览我们的插件目录来查看现有插件。", - "BurnSubtitlesHelp": "根据字幕格式确定服务器在转换视频时是否应压制字幕。避免压制字幕会提高服务器性能。选择“自动”以压制基于图像的字幕格式(如 VOBSUB, PGS, SUB/IDX 等)和一些复杂的 ASS/SSA 字幕。", + "BurnSubtitlesHelp": "服务器在转换视频时是否应压制字幕。避免压制字幕会提高服务器性能。选择“自动”以压制基于图像的字幕格式(如 VOBSUB, PGS, SUB, IDX 等)和一些复杂的 ASS/SSA 字幕。", "ButtonAdd": "添加", "ButtonAddMediaLibrary": "添加媒体库", "ButtonAddScheduledTaskTrigger": "添加触发器", @@ -921,9 +921,9 @@ "Normal": "普通", "NumLocationsValue": "{0} 个文件夹", "OneChannel": "一个频道", - "OnlyForcedSubtitles": "只显示强制字幕", + "OnlyForcedSubtitles": "仅强制字幕", "OnlyForcedSubtitlesHelp": "只有被标记为“强制”的字幕会被加载。", - "OnlyImageFormats": "仅图像格式(VOBSUB, PGS, SUB等)", + "OnlyImageFormats": "仅图像格式(VOBSUB, PGS, SUB 等)", "OptionAdminUsers": "管理员", "OptionAlbum": "专辑", "OptionAlbumArtist": "专辑艺术家", @@ -1334,9 +1334,9 @@ "AuthProviderHelp": "选择用于验证此用户密码的身份验证提供者。", "ColorPrimaries": "基色", "ConfigureDateAdded": "在Jellyfin Server仪表板媒体库的设置里确认如何添加日期", - "DisplayModeHelp": "选择您正在运行Jellyfin的屏幕类型。", + "DisplayModeHelp": "选择您想要的界面布局风格。", "EnableColorCodedBackgrounds": "彩色背景", - "ErrorDeletingItem": "服务器删除项目时出错。请确认服务器具有对媒体文件夹的写入权限并重试。", + "ErrorDeletingItem": "从 Jellyfin Server 删除项目时出错。请确认 Jellyfin Server 是否拥有对媒体目录的写权限,然后重试。", "GroupBySeries": "按系列分组", "HeaderApp": "应用程序", "DirectStreamHelp1": "该媒体文件的分辨率和编码(H.264、AC3 等)与您的设备兼容,但容器格式(.mkv、.avi、.wmv 等)不受支持。因此,视频在串流至您的设备之前将会被即时封装为另一种格式。", @@ -1471,9 +1471,20 @@ "NoCreatedLibraries": "看上去您还未创建任何资料库。{0} 您想现在创建一个吗? {1}", "AskAdminToCreateLibrary": "请联系管理员以创建一个新的资料库。", "PlaybackErrorNoCompatibleStream": "客户端配置文件存在问题,服务器未发送兼容的媒体格式。", - "AllowFfmpegThrottlingHelp": "当转码或再封装的进度大幅超过当前播放位置时,暂停该进程,以使其消耗更少的资源。在观看时不经常调整播放进度的情况下,这将非常有用。如果遇到播放问题,请关闭此功能。", + "AllowFfmpegThrottlingHelp": "当转码或者再封装的进度距离当前播放进度足够远时,暂停这个过程以减少资源消耗。当观看视频时不经常调整播放进度的情况下,这个功能将非常有用。如果你遇到了播放问题,请关闭这个选项。", "AllowFfmpegThrottling": "限制转码速度", "PreferEmbeddedEpisodeInfosOverFileNames": "优先使用内置的剧集信息而不是文件名", "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "这将在内置元数据含剧集信息时使用内置信息。", - "ClientSettings": "客户端设置" + "ClientSettings": "客户端设置", + "Track": "音轨", + "Season": "季", + "ReleaseGroup": "发行组", + "Person": "人物", + "OtherArtist": "其他艺术家", + "Movie": "电影", + "Episode": "剧集", + "BoxSet": "套装", + "Artist": "艺术家", + "AlbumArtist": "专辑艺术家", + "Album": "专辑" } diff --git a/yarn.lock b/yarn.lock index eed2aa6b8..3c286a7d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1354,6 +1354,15 @@ array-flatten@^2.1.0: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== +array-includes@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" + array-initial@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.1.0.tgz#2fa74b26739371c3947bd7a7adc73be334b3d795" @@ -1405,6 +1414,14 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.flat@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + arraybuffer.slice@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" @@ -2669,6 +2686,11 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + content-disposition@0.5.3, content-disposition@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -3089,6 +3111,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" @@ -3103,7 +3130,7 @@ debug-fabulous@1.X: memoizee "0.4.X" object-assign "4.X" -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -3401,6 +3428,14 @@ dns-txt@^2.0.2: dependencies: buffer-indexof "^1.0.0" +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -3762,7 +3797,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: version "1.17.5" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== @@ -3844,6 +3879,53 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +eslint-import-resolver-node@^0.3.2: + version "0.3.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" + integrity sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-module-utils@^2.4.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-eslint-comments@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.1.2.tgz#4ef6c488dbe06aa1627fea107b3e5d059fc8a395" + integrity sha512-QexaqrNeteFfRTad96W+Vi4Zj1KFbkHHNMMaHZEYcovKav6gdomyGzaxSDSL3GoIyUOo078wRAdYlu1caiauIQ== + dependencies: + escape-string-regexp "^1.0.5" + ignore "^5.0.5" + +eslint-plugin-import@^2.20.2: + version "2.20.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" + integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== + dependencies: + array-includes "^3.0.3" + array.prototype.flat "^1.2.1" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.4.1" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.0" + read-pkg-up "^2.0.0" + resolve "^1.12.0" + +eslint-plugin-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" + integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -4428,7 +4510,7 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -find-up@^2.0.0: +find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= @@ -5648,7 +5730,7 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.4, ignore@^5.1.1, ignore@^5.1.4: +ignore@^5.0.4, ignore@^5.0.5, ignore@^5.1.1, ignore@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== @@ -6251,6 +6333,11 @@ is-stream@^1.0.0, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + is-supported-regexp-flag@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz#21ee16518d2c1dd3edd3e9a0d57e50207ac364ca" @@ -6647,6 +6734,16 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -8298,6 +8395,13 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -8363,6 +8467,13 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" @@ -9428,6 +9539,14 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -9454,6 +9573,15 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -9832,7 +9960,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.4.0: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==