82 lines
4.9 KiB
C#
82 lines
4.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Concurrent;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using MediaBrowser.Common.Extensions;
|
|
using MediaBrowser.Controller;
|
|
using MediaBrowser.Controller.Configuration;
|
|
|
|
namespace Jellyfin.Plugin.Enigma2;
|
|
|
|
/// <summary>One catalogue for web status, native messages and cached FFmpeg slates.</summary>
|
|
public sealed class StartupMessages
|
|
{
|
|
private static readonly Dictionary<string, Dictionary<string, string>> Catalogue = Load();
|
|
private readonly IServerConfigurationManager configuration;
|
|
private readonly IDisplayPreferencesManager preferences;
|
|
private readonly ConcurrentDictionary<(Guid User, string Device), (string Language, DateTime Seen)> clientLanguages = new();
|
|
public StartupMessages(IServerConfigurationManager configuration = null, IDisplayPreferencesManager preferences = null)
|
|
{ this.configuration = configuration; this.preferences = preferences; }
|
|
private static Dictionary<string, Dictionary<string, string>> Load()
|
|
{
|
|
using var stream = typeof(StartupMessages).Assembly.GetManifestResourceStream("Jellyfin.Plugin.Enigma2.Localization.startup-messages.json");
|
|
return JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(stream);
|
|
}
|
|
private static string Supported(string locale)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(locale) || locale.Length > 64) return null;
|
|
var language = locale.Trim().Replace('_', '-').Split('-')[0].ToLowerInvariant();
|
|
return Catalogue.ContainsKey(language) ? language : null;
|
|
}
|
|
public static string ResolveLanguage(string uiLanguage, string acceptLanguage, string serverLanguage)
|
|
{
|
|
var direct = Supported(uiLanguage);
|
|
if (direct != null) return direct;
|
|
// Honour quality ordering and q=0 exclusions; headers are untrusted hints,
|
|
// never file names or a reason to change metadata/audio preferences.
|
|
if (!string.IsNullOrEmpty(acceptLanguage) && acceptLanguage.Length <= 1024)
|
|
{
|
|
var candidates = acceptLanguage.Split(',').Take(32).Select((entry, order) =>
|
|
{
|
|
var parts = entry.Trim().Split(';'); var quality = 1.0;
|
|
foreach (var part in parts.Skip(1))
|
|
if (part.Trim().StartsWith("q=", StringComparison.OrdinalIgnoreCase)
|
|
&& (!double.TryParse(part.Trim().Substring(2), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out quality)
|
|
|| quality < 0 || quality > 1)) quality = 0;
|
|
return new { Language = Supported(parts[0]), Quality = quality, Order = order };
|
|
}).Where(x => x.Language != null && x.Quality > 0).OrderByDescending(x => x.Quality).ThenBy(x => x.Order);
|
|
var match = candidates.FirstOrDefault(); if (match != null) return match.Language;
|
|
}
|
|
return Supported(serverLanguage) ?? "en";
|
|
}
|
|
public string Resolve(string uiLanguage, string acceptLanguage) => ResolveLanguage(uiLanguage, acceptLanguage, configuration?.Configuration?.UICulture);
|
|
public string Resolve(Guid user, string uiLanguage, string acceptLanguage)
|
|
{
|
|
var direct = Supported(uiLanguage);
|
|
if (direct != null) return direct;
|
|
// Jellyfin Web uses usersettings/emby display preferences. Some clients
|
|
// save language there; current Web stores it locally. Read only the
|
|
// requesting user's preference, without creating or changing it.
|
|
try
|
|
{
|
|
var custom = preferences?.ListCustomItemDisplayPreferences(user, "usersettings".GetMD5(), "emby");
|
|
if (custom != null && custom.TryGetValue("language", out var value) && Supported(value) is { } saved) return saved;
|
|
}
|
|
catch { /* An optional preference lookup must not prevent playback. */ }
|
|
return Resolve(null, acceptLanguage);
|
|
}
|
|
public void RememberClientLanguage(Guid user, string device, string language)
|
|
{
|
|
if (user == Guid.Empty || string.IsNullOrEmpty(device) || Supported(language) is not { } supported) return;
|
|
foreach (var entry in clientLanguages.Where(x => DateTime.UtcNow - x.Value.Seen > TimeSpan.FromMinutes(2)).ToArray()) clientLanguages.TryRemove(entry.Key, out _);
|
|
if (clientLanguages.Count >= 512 && !clientLanguages.ContainsKey((user, device))) return;
|
|
clientLanguages[(user, device)] = (supported, DateTime.UtcNow);
|
|
}
|
|
public string ClientLanguage(Guid user, string device) => device != null && clientLanguages.TryGetValue((user, device), out var value)
|
|
&& DateTime.UtcNow - value.Seen < TimeSpan.FromMinutes(2) ? value.Language : null;
|
|
public IReadOnlyDictionary<string, string> Texts(string language) => Catalogue[Supported(language) ?? "en"];
|
|
public string Text(string language, string key) => Texts(language).TryGetValue(key, out var value) ? value : Catalogue["en"][key];
|
|
}
|