Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The best way to discover and request Movies and TV Shows by using Seerr directly
- **Bulk request from Letterboxd**: Select and request multiple movies from your Letterboxd watchlist at once
- **Display customizations**: Customize logos, backdrops, poster style, and colors for all carousels/cards
- **Native Jellyfin UI**: Optional native look for carousels, grid pages, and search results that matches Jellyfin's own UI style (should work with most themes)
- **Automatic localization**: English and French interfaces follow each user's Jellyfin display language, with English as the fallback


## Installation
Expand Down
22 changes: 19 additions & 3 deletions src/Jellyfin.Plugin.SeerrFin/Configuration/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -1737,6 +1737,10 @@ <h3 id="bstCustomizeModalTitle" class="bst-modal-title"></h3>
const customTabsPluginId = 'fbacd0b6-fd46-4a05-b0a4-2045d6a135b0';
const form = document.querySelector('.seerrFinConfigurationForm');

function t(source) {
return window.seerrFinI18n ? window.seerrFinI18n.t(source) : source;
}

function switchBstPage(pageId) {
document.querySelectorAll('.bst-page-nav-btn[data-bst-panel]').forEach(function (btn) {
btn.classList.toggle('is-active', btn.getAttribute('data-bst-panel') === pageId);
Expand Down Expand Up @@ -1794,7 +1798,17 @@ <h3 id="bstCustomizeModalTitle" class="bst-modal-title"></h3>

function resolveTabTitle(id, title) {
const value = String(title || '').trim();
return value || TAB_TITLES[id] || id;
const defaultTitle = TAB_TITLES[id] || id;
if (!value || value === defaultTitle || value === t(defaultTitle)) {
return t(defaultTitle);
}
return value;
}

function canonicalizeTabTitle(id, title) {
const value = String(title || '').trim();
const defaultTitle = TAB_TITLES[id] || id;
return !value || value === defaultTitle || value === t(defaultTitle) ? defaultTitle : value;
}

let tabSettings = createDefaultTabs();
Expand Down Expand Up @@ -1954,12 +1968,14 @@ <h3 id="bstCustomizeModalTitle" class="bst-modal-title"></h3>
renderTabOrderList();
}

document.addEventListener('seerrfinlanguagechange', syncTabCardsFromState);

function buildTabsPayload() {
return tabSettings.map(function (tab) {
return {
Id: tab.Id,
Enabled: tab.Enabled !== false,
Title: resolveTabTitle(tab.Id, tab.Title)
Title: canonicalizeTabTitle(tab.Id, tab.Title)
};
});
}
Expand Down Expand Up @@ -2600,7 +2616,7 @@ <h3 id="bstCustomizeModalTitle" class="bst-modal-title"></h3>
activeCustomizeKey = key;
const style = displayCustomizations[item.configKey] || defaultDisplayItemStyle(item);

customizeTitle.textContent = 'Customize ' + item.label;
customizeTitle.textContent = t('Customize') + ' ' + t(item.label);
customizeDuotone.checked = style.DuotoneEnabled === true;
customizeDuotoneLight.value = toColorInputValue(style.DuotoneLight, 'ffffff');
customizeDuotoneDark.value = toColorInputValue(style.DuotoneDark, '969696');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ private void SetCacheHeaders()
Response.Headers.ETag = $"\"v{version}-c{config.CacheBustCounter}\"";
}

[HttpGet("seerrfin-i18n.js")]
[Produces("application/javascript")]
public ActionResult GetLocalizationScript() => ServeEmbedded("Inject.seerrfin-i18n.js", "application/javascript");

[HttpGet("seerrfin-tabs.js")]
[Produces("application/javascript")]
public ActionResult GetScript() => ServeEmbedded("Inject.seerrfin-tabs.js", "application/javascript");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static string IndexHtml(PatchRequestPayload payload)
$"<link rel=\"stylesheet\" href=\"../SeerrFin/seerrfin-requests.css{cacheParam}\" />" +
$"<link rel=\"stylesheet\" href=\"../SeerrFin/seerrfin-letterboxd.css{cacheParam}\" />";
string scripts =
$"<script defer src=\"../SeerrFin/seerrfin-i18n.js{cacheParam}\"></script>" +
$"<script defer src=\"../SeerrFin/seerrfin-modal.js{cacheParam}\"></script>" +
$"<script defer src=\"../SeerrFin/seerrfin-nativeui.js{cacheParam}\"></script>" +
$"<script defer src=\"../SeerrFin/seerrfin-tabs.js{cacheParam}\"></script>" +
Expand Down
636 changes: 636 additions & 0 deletions src/Jellyfin.Plugin.SeerrFin/Inject/seerrfin-i18n.js

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/Jellyfin.Plugin.SeerrFin/Inject/seerrfin-letterboxd.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ window.seerrFinLog = window.seerrFinLog || {
return 'Unknown';
}

return date.toLocaleString();
const detectedLocale = window.seerrFinI18n ? window.seerrFinI18n.getLocale() : 'en-US';
const locale = /^(fr|en)(-|$)/i.test(detectedLocale) ? detectedLocale : 'en-US';
return date.toLocaleString(locale);
}

function getSyncButtonLabel() {
Expand Down
24 changes: 14 additions & 10 deletions src/Jellyfin.Plugin.SeerrFin/Inject/seerrfin-nativeui.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ window.seerrFinLog = window.seerrFinLog || {
return;
}

function t(source) {
return window.seerrFinI18n ? window.seerrFinI18n.t(source) : source;
}

const log = window.seerrFinLog;

window.seerrFinNativeUi = {
Expand Down Expand Up @@ -332,17 +336,17 @@ window.seerrFinLog = window.seerrFinLog || {
getGridMenuOptions: function (type) {
if (type === 'view') {
return [
{ value: 'Poster', label: 'Poster' },
{ value: 'PosterCard', label: 'Poster Card' },
{ value: 'Thumb', label: 'Thumb' },
{ value: 'ThumbCard', label: 'Thumb Card' }
{ value: 'Poster', label: t('Poster') },
{ value: 'PosterCard', label: t('Poster Card') },
{ value: 'Thumb', label: t('Thumb') },
{ value: 'ThumbCard', label: t('Thumb Card') }
];
}
if (type === 'sort') {
return [
{ value: 'title', label: 'Title' },
{ value: 'year', label: 'Release date' },
{ value: 'rating', label: 'Community rating' }
{ value: 'title', label: t('Title') },
{ value: 'year', label: t('Release date') },
{ value: 'rating', label: t('Community rating') }
];
}
return [];
Expand Down Expand Up @@ -387,9 +391,9 @@ window.seerrFinLog = window.seerrFinLog || {
button: button,
query: query,
items: [
{ name: 'Name', id: 'SortName' },
{ name: 'Community rating', id: 'CommunityRating,SortName' },
{ name: 'Release date', id: 'PremiereDate,SortName' }
{ name: t('Name'), id: 'SortName' },
{ name: t('Community rating'), id: 'CommunityRating,SortName' },
{ name: t('Release date'), id: 'PremiereDate,SortName' }
],
callback: function () {
gridView.dataset.gridSortBy = query.SortBy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

<ItemGroup>
<EmbeddedResource Include="Configuration\config.html" />
<EmbeddedResource Include="Inject\seerrfin-i18n.js" />
<EmbeddedResource Include="Inject\seerrfin-nativeui.js" />
<EmbeddedResource Include="Inject\seerrfin-tabs.js" />
<EmbeddedResource Include="Inject\seerrfin-tabs.css" />
Expand Down