瀏覽代碼

Translate the ISO-639-2/B codes to ISO-639-2/T. (#13068)

* Translate the ISO-639-2/B codes to ISO-639-2/T.

This enables 19 additional languages to be displayed correctly.

* Convert the 2-dimensional array to a dictionary

* Added the French language to the list of ISO-639-2/B codes

* Don't change the property, use a local variable instead.

* When creating the MediaStream in the MediaStreamRepository ensure that the ISO 639-2/T (f.e. deu) code is used for the language as that is the one the .NET culture info knows.
The other code is most likely the ISO 639-2/B code (f.e. ger) which is unknown to the .NET culture info and will result in just displaying the code instead of the display name.

* Move the substitution of ISO 639-2/B to /T to the localization manager.
Some language (like Chinese) have multiple entries in the iso6392.txt file (f.e. zho|chi|zh|..., zho|chi|zh-tw|...) but the conversation between /T and /B is the same so use .TryAdd.

* Change the method definition from GetISO6392TFromB to TryGetISO6392TFromB and return true if a case was found.

* Add unit tests for TryGetISO6392TFromB.
baka0815 1 月之前
父節點
當前提交
5fc1b1c862

+ 31 - 0
Emby.Server.Implementations/Localization/LocalizationManager.cs

@@ -1,6 +1,8 @@
 using System;
 using System.Collections.Concurrent;
+using System.Collections.Frozen;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
 using System.Reflection;
@@ -38,6 +40,8 @@ namespace Emby.Server.Implementations.Localization
 
         private List<CultureDto> _cultures = [];
 
+        private FrozenDictionary<string, string> _iso6392BtoT = null!;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="LocalizationManager" /> class.
         /// </summary>
@@ -100,6 +104,7 @@ namespace Emby.Server.Implementations.Localization
         private async Task LoadCultures()
         {
             List<CultureDto> list = [];
+            Dictionary<string, string> iso6392BtoTdict = new Dictionary<string, string>();
 
             using var stream = _assembly.GetManifestResourceStream(CulturesPath);
             if (stream is null)
@@ -142,12 +147,17 @@ namespace Emby.Server.Implementations.Localization
                     else
                     {
                         threeLetterNames = [parts[0], parts[1]];
+
+                        // In cases where there are two TLN the first one is ISO 639-2/T and the second one is ISO 639-2/B
+                        // We need ISO 639-2/T for the .NET cultures so we cultivate a dictionary for the translation B->T
+                        iso6392BtoTdict.TryAdd(parts[1], parts[0]);
                     }
 
                     list.Add(new CultureDto(name, name, twoCharName, threeLetterNames));
                 }
 
                 _cultures = list;
+                _iso6392BtoT = iso6392BtoTdict.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
             }
         }
 
@@ -505,5 +515,26 @@ namespace Emby.Server.Implementations.Localization
             yield return new LocalizationOption("漢語 (繁體字)", "zh-TW");
             yield return new LocalizationOption("廣東話 (香港)", "zh-HK");
         }
+
+        /// <inheritdoc />
+        public bool TryGetISO6392TFromB(string isoB, [NotNullWhen(true)] out string? isoT)
+        {
+            // Unlikely case the dictionary is not (yet) initialized properly
+            if (_iso6392BtoT == null)
+            {
+                isoT = null;
+                return false;
+            }
+
+            var result = _iso6392BtoT.TryGetValue(isoB, out isoT) && !string.IsNullOrEmpty(isoT);
+
+            // Ensure the ISO code being null if the result is false
+            if (!result)
+            {
+                isoT = null;
+            }
+
+            return result;
+        }
     }
 }

+ 12 - 1
Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs

@@ -100,7 +100,18 @@ public class MediaStreamRepository : IMediaStreamRepository
 
         dto.IsAVC = entity.IsAvc;
         dto.Codec = entity.Codec;
-        dto.Language = entity.Language;
+
+        var language = entity.Language;
+
+        // Check if the language has multiple three letter ISO codes
+        // if yes choose the first as that is the ISO 639-2/T code we're needing
+        if (language != null && _localization.TryGetISO6392TFromB(language, out string? isoT))
+        {
+            language = isoT;
+        }
+
+        dto.Language = language;
+
         dto.ChannelLayout = entity.ChannelLayout;
         dto.Profile = entity.Profile;
         dto.AspectRatio = entity.AspectRatio;

+ 3 - 2
MediaBrowser.Model/Entities/MediaStream.cs

@@ -2,6 +2,7 @@
 #pragma warning disable CS1591
 
 using System;
+using System.Collections.Frozen;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Globalization;
@@ -272,7 +273,7 @@ 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. Will not work for some languages which use ISO 639-2/B instead of /T codes.
+                            // Get full language string i.e. eng -> English.
                             string fullLanguage = CultureInfo
                                 .GetCultures(CultureTypes.NeutralCultures)
                                 .FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase))
@@ -375,7 +376,7 @@ namespace MediaBrowser.Model.Entities
 
                         if (!string.IsNullOrEmpty(Language))
                         {
-                            // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes.
+                            // Get full language string i.e. eng -> English.
                             string fullLanguage = CultureInfo
                                 .GetCultures(CultureTypes.NeutralCultures)
                                 .FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase))

+ 9 - 0
MediaBrowser.Model/Globalization/ILocalizationManager.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Model.Globalization;
@@ -61,4 +62,12 @@ public interface ILocalizationManager
     /// <param name="language">The language.</param>
     /// <returns>The correct <see cref="CultureDto" /> for the given language.</returns>
     CultureDto? FindLanguageInfo(string language);
+
+    /// <summary>
+    /// Returns the language in ISO 639-2/T when the input is ISO 639-2/B.
+    /// </summary>
+    /// <param name="isoB">The language in ISO 639-2/B.</param>
+    /// <param name="isoT">The language in ISO 639-2/T.</param>
+    /// <returns>Whether the language could be converted.</returns>
+    public bool TryGetISO6392TFromB(string isoB, [NotNullWhen(true)] out string? isoT);
 }

+ 25 - 0
tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Linq;
 using System.Threading.Tasks;
+using BitFaster.Caching;
 using Emby.Server.Implementations.Localization;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Model.Configuration;
@@ -51,6 +52,30 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
             Assert.Contains("ger", germany.ThreeLetterISOLanguageNames);
         }
 
+        [Fact]
+        public async Task TryGetISO6392TFromB_Success()
+        {
+            var localizationManager = Setup(new ServerConfiguration
+            {
+                UICulture = "de-DE"
+            });
+            await localizationManager.LoadAll();
+
+            string? isoT;
+
+            // Translation ger -> deu
+            Assert.True(localizationManager.TryGetISO6392TFromB("ger", out isoT));
+            Assert.Equal("deu", isoT);
+
+            // chi -> zho
+            Assert.True(localizationManager.TryGetISO6392TFromB("chi", out isoT));
+            Assert.Equal("zho", isoT);
+
+            // eng is already ISO 639-2/T
+            Assert.False(localizationManager.TryGetISO6392TFromB("eng", out isoT));
+            Assert.Null(isoT);
+        }
+
         [Theory]
         [InlineData("de")]
         [InlineData("deu")]