aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordyphire <qimoge@gmail.com>2025-07-30 22:15:37 +0800
committerdyphire <qimoge@gmail.com>2025-07-30 22:15:37 +0800
commit2007815fa6b592984a64a6d3d978d41ae8f7347b (patch)
tree4dac6c0b1c4e955b74fdcb9c20e597f4b556f34b
parentf9c4c9b3456d682901afbc747a12b45dd18f31a6 (diff)
Further refinement of BCP 47 language labeling support
-rw-r--r--Emby.Naming/ExternalFiles/ExternalPathParser.cs8
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs2
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs9
-rw-r--r--Emby.Server.Implementations/Localization/iso6392.txt20
-rw-r--r--Jellyfin.Api/Controllers/ItemUpdateController.cs5
-rw-r--r--Jellyfin.Api/Controllers/LocalizationController.cs11
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs54
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs2
8 files changed, 84 insertions, 27 deletions
diff --git a/Emby.Naming/ExternalFiles/ExternalPathParser.cs b/Emby.Naming/ExternalFiles/ExternalPathParser.cs
index 7a01b02f3..3461b3c0d 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 ab30971e2..1e3b8ea76 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 242f2af56..b4c65ad85 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 d6f8af617..5a1a2d559 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,10 +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-cn|Chinese; Simplified|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 50eeaeac6..e1d9b6bba 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 bbce5a9e1..dd8f935dc 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<IEnumerable<CultureDto>> 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);
}
/// <summary>
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 5c8f37fcd..b1626e2c9 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 a7a1e5e81..6d6bba4fc 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);