From f398b6d08b46544f61523c6871624201a2b54dfc Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 26 Jun 2026 08:20:55 +0200 Subject: Fix localization lookup --- .../Localization/LocalizationManager.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations/Localization/LocalizationManager.cs') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 843e35afcc..6971431155 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -566,11 +566,15 @@ namespace Emby.Server.Implementations.Localization private static string GetResourceFilename(string culture) { - var parts = culture.Split('-'); + // Region codes may use a '-' (BCP-47, e.g. "pt-BR") or '_' (e.g. "es_419", "ar_SA") separator. + // Normalize the casing (lower-case language, upper-case region) while preserving the separator + // so the result matches the embedded resource file name, which is case-sensitive. + var separatorIndex = culture.IndexOfAny(['-', '_']); - if (parts.Length == 2) + if (separatorIndex > 0) { - culture = parts[0].ToLowerInvariant() + "-" + parts[1].ToUpperInvariant(); + var separator = culture[separatorIndex]; + culture = culture[..separatorIndex].ToLowerInvariant() + separator + culture[(separatorIndex + 1)..].ToUpperInvariant(); } else { -- cgit v1.2.3 From 2528bc10326e4b842899b55ae2d023414f5fc53f Mon Sep 17 00:00:00 2001 From: theguymadmax Date: Sat, 4 Jul 2026 17:53:55 -0400 Subject: Fix parental rating lookup for multi-rating entries --- .../Localization/LocalizationManager.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'Emby.Server.Implementations/Localization/LocalizationManager.cs') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 6971431155..98f629d31c 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -356,6 +356,27 @@ namespace Emby.Server.Implementations.Localization { ArgumentException.ThrowIfNullOrEmpty(rating); + // Some providers may list multiple ratings separated by '/' (e.g. "SE:15 / SE:15+ / SE:Från 15 år"). + // Try each one in order and use the first that resolves. + var ratingValues = rating.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (var ratingValue in ratingValues) + { + var score = GetSingleRatingScore(ratingValue, countryCode); + if (score is not null) + { + return score; + } + } + + return null; + } + + /// + /// Resolves a single rating value to a score. + /// + private ParentalRatingScore? GetSingleRatingScore(string rating, string? countryCode) + { // Handle unrated content if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase)) { -- cgit v1.2.3 From 8bf1710e0771eaebfa2933d0f7849484566f2bd5 Mon Sep 17 00:00:00 2001 From: theguymadmax Date: Wed, 8 Jul 2026 20:09:49 -0400 Subject: Check numeric rating value after splitting country code --- .../Localization/LocalizationManager.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Localization/LocalizationManager.cs') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 98f629d31c..c3e77bb87f 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -385,7 +385,7 @@ namespace Emby.Server.Implementations.Localization // Convert ints directly // This may override some of the locale specific age ratings (but those always map to the same age) - if (int.TryParse(rating, out var ratingAge)) + if (TryParseRatingAsScore(rating, out var ratingAge)) { return new(ratingAge, null); } @@ -487,6 +487,13 @@ namespace Emby.Server.Implementations.Localization return true; } + // If it's not a recognized rating string, fall back to using the number as the score + if (TryParseRatingAsScore(ratingPart, out var numericScore)) + { + result = new ParentalRatingScore(numericScore, null); + return true; + } + _logger.LogWarning( "Rating '{Rating}' not found in the '{CountryCode}' rating system, treating as unrated", rating, @@ -501,6 +508,18 @@ namespace Emby.Server.Implementations.Localization return true; } + /// + /// Tries to parse a rating as a number, allowing an optional trailing '+' (e.g. "16" or "18+"). + /// + /// Rating value to parse. + /// Parsed score. + /// Returns true if parsing was successful. + private static bool TryParseRatingAsScore(string ratingValue, out int score) + { + var trimmed = ratingValue.TrimEnd('+'); + return int.TryParse(trimmed, out score); + } + /// public string GetLocalizedString(string phrase) { -- cgit v1.2.3 From 2aae53bc152682ddf845d6ba17e4cb109d39bca8 Mon Sep 17 00:00:00 2001 From: theguymadmax Date: Sun, 12 Jul 2026 11:54:56 -0400 Subject: Apply review feedback --- Emby.Server.Implementations/Localization/LocalizationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Localization/LocalizationManager.cs') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index c3e77bb87f..e7dd984ec4 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -514,7 +514,7 @@ namespace Emby.Server.Implementations.Localization /// Rating value to parse. /// Parsed score. /// Returns true if parsing was successful. - private static bool TryParseRatingAsScore(string ratingValue, out int score) + private static bool TryParseRatingAsScore(ReadOnlySpan ratingValue, out int score) { var trimmed = ratingValue.TrimEnd('+'); return int.TryParse(trimmed, out score); -- cgit v1.2.3 From 2cd2f36fe4912cb465cf5e78a055463f8a5c44a9 Mon Sep 17 00:00:00 2001 From: 854562 <44002186+854562@users.noreply.github.com> Date: Mon, 20 Jul 2026 22:00:10 +0200 Subject: Extract truncation logic to helper and add tests --- .../Localization/LocalizationManager.cs | 18 ++++++++++++ .../Item/MediaStreamRepository.cs | 6 +--- .../Probing/ProbeResultNormalizer.cs | 8 ++--- .../Globalization/ILocalizationManager.cs | 8 +++++ .../Localization/LocalizationManagerTests.cs | 34 ++++++++++++++++++++++ 5 files changed, 63 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations/Localization/LocalizationManager.cs') diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 843e35afcc..068dde639e 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -261,6 +261,24 @@ namespace Emby.Server.Implementations.Localization _cultures); } + /// + public string? GetLanguageDisplayName(string language) + { + if (string.IsNullOrEmpty(language)) + { + return null; + } + + var displayName = FindLanguageInfo(language)?.DisplayName; + if (displayName is null) + { + return null; + } + + // Truncate at the first delimiter to avoid cluttered display names + return displayName.Split([';', ','], StringSplitOptions.None)[0].Trim(); + } + /// public IReadOnlyList GetCountries() { diff --git a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs index 17cfdffe84..a25629132b 100644 --- a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs +++ b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs @@ -172,11 +172,7 @@ public class MediaStreamRepository : IMediaStreamRepository if (!string.IsNullOrEmpty(dto.Language)) { - var culture = _localization.FindLanguageInfo(dto.Language); - // Truncate at the first delimiter to avoid cluttered display names - dto.LocalizedLanguage = culture?.DisplayName is { } name - ? name.Split([';', ','])[0].Trim() - : null; + dto.LocalizedLanguage = _localization.GetLanguageDisplayName(dto.Language); } if (dto.Type is MediaStreamType.Audio) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index a9c37c0025..449c18a306 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -732,9 +732,7 @@ namespace MediaBrowser.MediaEncoding.Probing stream.LocalizedOriginal = _localization.GetLocalizedString("Original"); if (!string.IsNullOrEmpty(stream.Language)) { - stream.LocalizedLanguage = _localization.FindLanguageInfo(stream.Language)?.DisplayName is { } name - ? name.Split([';', ','])[0].Trim() - : null; + stream.LocalizedLanguage = _localization.GetLanguageDisplayName(stream.Language); } stream.Channels = streamInfo.Channels; @@ -776,9 +774,7 @@ namespace MediaBrowser.MediaEncoding.Probing stream.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired"); if (!string.IsNullOrEmpty(stream.Language)) { - stream.LocalizedLanguage = _localization.FindLanguageInfo(stream.Language)?.DisplayName is { } name - ? name.Split([';', ','])[0].Trim() - : null; + stream.LocalizedLanguage = _localization.GetLanguageDisplayName(stream.Language); } if (string.IsNullOrEmpty(stream.Title)) diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index 7ad240abfb..0fff70c4e0 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -72,6 +72,14 @@ public interface ILocalizationManager /// The correct for the given language. CultureDto? FindLanguageInfo(string language); + /// + /// Gets a human-readable display name for the given language code. + /// Truncates at the first semicolon or comma to avoid cluttered ISO-639-2 names. + /// + /// An ISO language code. + /// The display name, or null if not found. + string? GetLanguageDisplayName(string language); + /// /// Returns the language in ISO 639-2/T when the input is ISO 639-2/B. /// diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs index 3b8fe5ca60..b608d3ea49 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -119,6 +119,40 @@ namespace Jellyfin.Server.Implementations.Tests.Localization Assert.Equal(code, culture.ThreeLetterISOLanguageName); } + [Theory] + [InlineData("ell", "Greek")] // Comma truncation + [InlineData("nld", "Dutch")] // Semicolon truncation + [InlineData("ron", "Romanian")] // Semicolon truncation, multiple + [InlineData("eng", "English")] // No truncation + [InlineData("zh-CN", "Chinese (Simplified)")] // No truncation, with parentheses + public async Task GetLanguageDisplayName_DelimitedName_ReturnsTruncatedName(string language, string expected) + { + var localizationManager = Setup(new ServerConfiguration + { + UICulture = "en-US" + }); + await localizationManager.LoadAll(); + + var result = localizationManager.GetLanguageDisplayName(language); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("xyz")] + public async Task GetLanguageDisplayName_InvalidInput_ReturnsNull(string? language) + { + var localizationManager = Setup(new ServerConfiguration + { + UICulture = "en-US" + }); + await localizationManager.LoadAll(); + + var result = localizationManager.GetLanguageDisplayName(language!); + Assert.Null(result); + } + [Fact] public async Task GetParentalRatings_Default_Success() { -- cgit v1.2.3