diff --git a/Emby.Naming/ExternalFiles/ExternalPathParser.cs b/Emby.Naming/ExternalFiles/ExternalPathParser.cs index 7a01b02f3c..3461b3c0d6 100644 --- a/Emby.Naming/ExternalFiles/ExternalPathParser.cs +++ b/Emby.Naming/ExternalFiles/ExternalPathParser.cs @@ -97,14 +97,18 @@ namespace Emby.Naming.ExternalFiles if (culture is not null && pathInfo.Language is null) { - pathInfo.Language = culture.ThreeLetterISOLanguageName; + pathInfo.Language = culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) + ? culture.Name + : culture.ThreeLetterISOLanguageName; extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); } else if (culture is not null && pathInfo.Language == "hin") { // Hindi language code "hi" collides with a hearing impaired flag - use as Hindi only if no other language is set pathInfo.IsHearingImpaired = true; - pathInfo.Language = culture.ThreeLetterISOLanguageName; + pathInfo.Language = culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) + ? culture.Name + : culture.ThreeLetterISOLanguageName; extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); } else if (_namingOptions.MediaHearingImpairedFlags.Any(s => currentSliceWithoutSeparator.Equals(s, StringComparison.OrdinalIgnoreCase))) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index ab30971e27..1e3b8ea760 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -379,7 +379,7 @@ namespace Emby.Server.Implementations.Library var culture = _localizationManager.FindLanguageInfo(language); if (culture is not null) { - return culture.ThreeLetterISOLanguageNames; + return culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) ? [culture.Name] : culture.ThreeLetterISOLanguageNames; } return [language]; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 242f2af565..b4c65ad85f 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -128,7 +128,8 @@ namespace Emby.Server.Implementations.Localization } string name = parts[3]; - if (string.IsNullOrWhiteSpace(name)) + string displayname = parts[3]; + if (string.IsNullOrWhiteSpace(displayname)) { continue; } @@ -138,6 +139,10 @@ namespace Emby.Server.Implementations.Localization { continue; } + else if (twoCharName.Contains('-', StringComparison.OrdinalIgnoreCase)) + { + name = twoCharName; + } string[] threeLetterNames; if (string.IsNullOrWhiteSpace(parts[1])) @@ -153,7 +158,7 @@ namespace Emby.Server.Implementations.Localization iso6392BtoTdict.TryAdd(parts[1], parts[0]); } - list.Add(new CultureDto(name, name, twoCharName, threeLetterNames)); + list.Add(new CultureDto(name, displayname, twoCharName, threeLetterNames)); } _cultures = list; diff --git a/Emby.Server.Implementations/Localization/iso6392.txt b/Emby.Server.Implementations/Localization/iso6392.txt index 97da674819..dc4b5d45aa 100644 --- a/Emby.Server.Implementations/Localization/iso6392.txt +++ b/Emby.Server.Implementations/Localization/iso6392.txt @@ -311,8 +311,8 @@ nia|||Nias|nias nic|||Niger-Kordofanian languages|nigéro-kordofaniennes, langues niu|||Niuean|niué nld|dut|nl|Dutch; Flemish|néerlandais; flamand -nno||nn|Norwegian Nynorsk; Nynorsk, Norwegian|norvégien nynorsk; nynorsk, norvégien -nob||nb|Bokmål, Norwegian; Norwegian Bokmål|norvégien bokmål +nno||nn|Norwegian (Nynorsk)|norvégien (nynorsk) +nob||nb|Norwegian (Bokmal)|norvégien (bokmål) nog|||Nogai|nogaï; nogay non|||Norse, Old|norrois, vieux nor||no|Norwegian|norvégien @@ -391,10 +391,10 @@ slv||sl|Slovenian|slovène sma|||Southern Sami|sami du Sud sme||se|Northern Sami|sami du Nord smi|||Sami languages|sames, langues -smj|||Lule Sami|sami de Lule -smn|||Inari Sami|sami d'Inari +smj|||Sami (Lule)|sami de Lule +smn|||Sami (Inari)|sami d'Inari smo||sm|Samoan|samoan -sms|||Skolt Sami|sami skolt +sms|||Sami (Skolt)|sami skolt sna||sn|Shona|shona snd||sd|Sindhi|sindhi snk|||Soninke|soninké @@ -483,9 +483,12 @@ zen|||Zenaga|zenaga zgh|||Standard Moroccan Tamazight|amazighe standard marocain zha||za|Zhuang; Chuang|zhuang; chuang zho|chi|zh|Chinese|chinois -zho|chi|ze|Chinese; Bilingual|chinois -zho|chi|zh-tw|Chinese; Traditional|chinois -zho|chi|zh-hk|Chinese; Hong Kong|chinois +zho|chi|ze|Chinese (Bilingual)|chinois +zho|chi|zh-cn|Chinese (Simplified)|chinois +zho|chi|zh-hans|Chinese (Simplified)|chinois +zho|chi|zh-tw|Chinese (Traditional)|chinois +zho|chi|zh-hant|Chinese (Traditional)|chinois +zho|chi|zh-hk|Chinese (Hong Kong)|chinois znd|||Zande languages|zandé, langues zul||zu|Zulu|zoulou zun|||Zuni|zuni diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 50eeaeac67..e1d9b6bba0 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -158,7 +158,10 @@ public class ItemUpdateController : BaseJellyfinApiController ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(), ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(), Countries = _localizationManager.GetCountries().ToArray(), - Cultures = _localizationManager.GetCultures().ToArray() + Cultures = _localizationManager.GetCultures() + .DistinctBy(c => c.DisplayName, StringComparer.OrdinalIgnoreCase) + .OrderBy(c => c.DisplayName) + .ToArray() }; if (!item.IsVirtualItem diff --git a/Jellyfin.Api/Controllers/LocalizationController.cs b/Jellyfin.Api/Controllers/LocalizationController.cs index bbce5a9e13..dd8f935dc1 100644 --- a/Jellyfin.Api/Controllers/LocalizationController.cs +++ b/Jellyfin.Api/Controllers/LocalizationController.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Linq; using MediaBrowser.Common.Api; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; @@ -34,7 +36,14 @@ public class LocalizationController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetCultures() { - return Ok(_localization.GetCultures()); + var allCultures = _localization.GetCultures(); + + var distinctCultures = allCultures + .DistinctBy(c => c.DisplayName, StringComparer.OrdinalIgnoreCase) + .OrderBy(c => c.DisplayName) + .AsEnumerable(); + + return Ok(distinctCultures); } /// diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 5c8f37fcdb..b1626e2c98 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -273,11 +273,28 @@ namespace MediaBrowser.Model.Entities // Do not display the language code in display titles if unset or set to a special code. Show it in all other cases (possibly expanded). if (!string.IsNullOrEmpty(Language) && !_specialCodes.Contains(Language, StringComparison.OrdinalIgnoreCase)) { - // Get full language string i.e. eng -> English. - string fullLanguage = CultureInfo - .GetCultures(CultureTypes.NeutralCultures) - .FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)) - ?.DisplayName; + // Get full language string i.e. eng -> English, zh-Hans -> Chinese (Simplified). + var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); + CultureInfo match = null; + if (Language.Contains('-', StringComparison.OrdinalIgnoreCase)) + { + match = cultures.FirstOrDefault(r => + r.Name.Equals(Language, StringComparison.OrdinalIgnoreCase)); + + if (match is null) + { + string baseLang = Language.AsSpan().LeftPart('-').ToString(); + match = cultures.FirstOrDefault(r => + r.TwoLetterISOLanguageName.Equals(baseLang, StringComparison.OrdinalIgnoreCase)); + } + } + else + { + match = cultures.FirstOrDefault(r => + r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)); + } + + string fullLanguage = match?.DisplayName; attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language)); } @@ -376,11 +393,28 @@ namespace MediaBrowser.Model.Entities if (!string.IsNullOrEmpty(Language)) { - // Get full language string i.e. eng -> English. - string fullLanguage = CultureInfo - .GetCultures(CultureTypes.NeutralCultures) - .FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)) - ?.DisplayName; + // Get full language string i.e. eng -> English, zh-Hans -> Chinese (Simplified). + var cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); + CultureInfo match = null; + if (Language.Contains('-', StringComparison.OrdinalIgnoreCase)) + { + match = cultures.FirstOrDefault(r => + r.Name.Equals(Language, StringComparison.OrdinalIgnoreCase)); + + if (match is null) + { + string baseLang = Language.AsSpan().LeftPart('-').ToString(); + match = cultures.FirstOrDefault(r => + r.TwoLetterISOLanguageName.Equals(baseLang, StringComparison.OrdinalIgnoreCase)); + } + } + else + { + match = cultures.FirstOrDefault(r => + r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)); + } + + string fullLanguage = match?.DisplayName; attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language)); } else diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs index a7a1e5e811..6d6bba4fc4 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -41,7 +41,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization await localizationManager.LoadAll(); var cultures = localizationManager.GetCultures().ToList(); - Assert.Equal(191, cultures.Count); + Assert.Equal(194, cultures.Count); var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal)); Assert.NotNull(germany);