Merge pull request #14410 from dyphire/language

Further refinement of BCP 47 language labeling support
This commit is contained in:
Joshua M. Boniface 2025-08-03 17:29:40 -04:00 committed by GitHub
commit 1262ac31dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 84 additions and 26 deletions

View File

@ -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)))

View File

@ -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];

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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);