Add language-aware article sorting

This commit is contained in:
ajp_anton
2026-07-19 01:35:02 +00:00
parent 6c33dc1d69
commit 6a81debc8c
8 changed files with 237 additions and 37 deletions
@@ -13,7 +13,7 @@ namespace Jellyfin.Plugin.Multilang.Services;
public readonly record struct ItemsProxyTransformResult(string Body, string[] ItemIds);
public readonly record struct ItemsProxyResolvedText(bool Change, string? Value);
public readonly record struct ItemsProxyResolvedText(bool Change, string? Value, string? Language = null);
public readonly record struct ItemsProxyResolvedAsset(bool Change, string? Value);
@@ -61,9 +61,9 @@ public sealed class ItemsProxyTransformer
if (ids.Length == 0)
return new ItemsProxyTransformResult(body, ids);
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
var sortLocale = ItemsProxySorting.ResolveSortLocale(rules.SortLocale, controls.ClientLocale);
var sortCulture = ItemsProxySorting.GetSortCulture(sortLocale);
var sortArticles = ItemsProxySorting.GetSortArticles(_articleCatalog, Plugin.Instance?.Configuration?.ArticleEntries ?? [], sortLocale);
var localGenreIds = rules.Enabled ? ItemsProxyRequestBuilder.ParseLocalGenreIds(controls.GenreIds) : [];
if (rules.Enabled || localGenreIds.Length > 0)
@@ -80,10 +80,19 @@ public sealed class ItemsProxyTransformer
ids = GetItemIds(items);
}
if (rules.Enabled)
ApplyRules(items, ids, rules, controls.ClientLocale, factsByItem);
var titleLanguages = rules.Enabled
? ApplyRules(items, ids, rules, controls.ClientLocale, factsByItem)
: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var jellyfinTitleLanguages = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
ItemsProxySorting.Apply(root, controls, sortCulture, sortArticles);
ItemsProxySorting.Apply(root, controls, sortCulture, item =>
{
var itemId = TryGetItemId(item);
var language = itemId is not null && titleLanguages.TryGetValue(itemId, out var translatedLanguage)
? translatedLanguage
: ResolveJellyfinTitleLanguage(itemId, config, jellyfinTitleLanguages);
return ItemsProxySorting.GetSortArticles(_articleCatalog, config.ArticleEntries, language, config.IgnoreArticlesWhenSorting);
});
return new ItemsProxyTransformResult(root.ToJsonString(new JsonSerializerOptions { WriteIndented = false }), ids);
}
@@ -230,12 +239,12 @@ public sealed class ItemsProxyTransformer
if (field.Equals(TitleField, StringComparison.OrdinalIgnoreCase) &&
action.Equals(OriginalAction, StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrWhiteSpace(facts.OriginalTitle))
return new ItemsProxyResolvedText(true, facts.OriginalTitle);
return new ItemsProxyResolvedText(true, facts.OriginalTitle, facts.OriginalLanguage);
if (action.Equals(OriginalAction, StringComparison.OrdinalIgnoreCase))
{
var originalValue = GetTranslatedField(byLang, OriginalAction, field);
if (!string.IsNullOrWhiteSpace(originalValue))
return new ItemsProxyResolvedText(true, originalValue);
return new ItemsProxyResolvedText(true, originalValue, facts.OriginalLanguage);
continue;
}
@@ -244,7 +253,7 @@ public sealed class ItemsProxyTransformer
var value = GetTranslatedField(byLang, action[LanguagePrefix.Length..], field);
if (!string.IsNullOrWhiteSpace(value))
return new ItemsProxyResolvedText(true, value);
return new ItemsProxyResolvedText(true, value, action[LanguagePrefix.Length..]);
}
return field.Equals(TitleField, StringComparison.OrdinalIgnoreCase)
@@ -347,7 +356,7 @@ public sealed class ItemsProxyTransformer
return string.Empty;
}
private void ApplyRules(
private Dictionary<string, string> ApplyRules(
IEnumerable<JsonObject> items,
string[] ids,
UserRulesDocument rules,
@@ -357,6 +366,7 @@ public sealed class ItemsProxyTransformer
var langs = GetNeededLanguages(rules);
var translations = _store.GetTranslations(ids, langs);
var assets = _store.GetAssets(ids, langs);
var titleLanguages = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var movieGenreNames = _store.GetGenreNames("movie", clientLocale);
var tvGenreNames = _store.GetGenreNames("tv", clientLocale);
@@ -373,6 +383,8 @@ public sealed class ItemsProxyTransformer
var title = ResolveField(TitleField, GetActionList(rules, category, TitleField), facts, byLang);
if (title.Change)
item["Name"] = title.Value ?? string.Empty;
if (!string.IsNullOrWhiteSpace(title.Language))
titleLanguages[itemId] = title.Language;
var overview = ResolveField(OverviewField, GetActionList(rules, category, OverviewField), facts, byLang);
if (overview.Change)
@@ -395,6 +407,27 @@ public sealed class ItemsProxyTransformer
ApplyBackdropAsset(item, ResolveAsset(BackdropKind, GetActionList(rules, category, BackdropKind), assetsByKind));
ApplyGenres(item, facts, movieGenreNames, tvGenreNames);
}
return titleLanguages;
}
private string ResolveJellyfinTitleLanguage(
string? itemId,
PluginConfiguration config,
Dictionary<string, string> resolvedLanguages)
{
if (string.IsNullOrWhiteSpace(itemId))
return config.JellyfinTitleLanguageFallback;
if (resolvedLanguages.TryGetValue(itemId, out var existing))
return existing;
var language = Guid.TryParse(itemId, out var itemGuid)
? _libraryManager.GetItemById(itemGuid)?.GetPreferredMetadataLanguage()
: null;
language = string.IsNullOrWhiteSpace(language) ? config.JellyfinTitleLanguageFallback : language;
resolvedLanguages[itemId] = language ?? string.Empty;
return resolvedLanguages[itemId];
}
private static void ApplyImageAsset(JsonObject item, string imageType, ItemsProxyResolvedAsset asset)