Préparation traduction en => fr Part1
This commit is contained in:
231
MareSynchronos/Localization/LocalizationService.cs
Normal file
231
MareSynchronos/Localization/LocalizationService.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Dalamud.Plugin;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Localization;
|
||||
|
||||
public class LocalizationService
|
||||
{
|
||||
private readonly ILogger<LocalizationService> _logger;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
private readonly MareConfigService _configService;
|
||||
private readonly Dictionary<LocalizationLanguage, Dictionary<string, string>> _translations = new();
|
||||
private readonly HashSet<string> _missingLocalizationsLogged = new(StringComparer.Ordinal);
|
||||
private readonly Lock _missingLocalizationsLock = new();
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
AllowTrailingCommas = true
|
||||
};
|
||||
|
||||
public static LocalizationService? Instance { get; private set; }
|
||||
|
||||
public LocalizationService(ILogger<LocalizationService> logger, IDalamudPluginInterface pluginInterface, MareConfigService configService)
|
||||
{
|
||||
_logger = logger;
|
||||
_pluginInterface = pluginInterface;
|
||||
_configService = configService;
|
||||
|
||||
Instance = this;
|
||||
|
||||
foreach (var language in Enum.GetValues<LocalizationLanguage>())
|
||||
{
|
||||
_translations[language] = LoadLanguage(language);
|
||||
}
|
||||
}
|
||||
|
||||
public LocalizationLanguage CurrentLanguage => _configService.Current.Language;
|
||||
|
||||
public static IEnumerable<LocalizationLanguage> SupportedLanguages => Enum.GetValues<LocalizationLanguage>();
|
||||
|
||||
public string GetString(string fallbackEnglish, params object[] formatArgs)
|
||||
{
|
||||
return GetStringInternal(null, fallbackEnglish, formatArgs);
|
||||
}
|
||||
|
||||
public string GetString(string key, string fallbackEnglish, params object[] formatArgs)
|
||||
{
|
||||
return GetStringInternal(key, fallbackEnglish, formatArgs);
|
||||
}
|
||||
|
||||
public string GetLanguageDisplayName(LocalizationLanguage language)
|
||||
{
|
||||
var fallback = language switch
|
||||
{
|
||||
LocalizationLanguage.French => "Français",
|
||||
LocalizationLanguage.English => "English",
|
||||
_ => language.ToString()
|
||||
};
|
||||
|
||||
return GetRawString($"Language.DisplayName.{language}", fallback);
|
||||
}
|
||||
|
||||
public void ReloadLanguage(LocalizationLanguage language)
|
||||
{
|
||||
_translations[language] = LoadLanguage(language);
|
||||
}
|
||||
|
||||
public void ReloadAll()
|
||||
{
|
||||
foreach (var language in Enum.GetValues<LocalizationLanguage>())
|
||||
{
|
||||
ReloadLanguage(language);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetStringInternal(string? key, string fallbackEnglish, params object[] formatArgs)
|
||||
{
|
||||
var usedKey = string.IsNullOrWhiteSpace(key) ? fallbackEnglish : key;
|
||||
var text = GetRawString(usedKey, fallbackEnglish);
|
||||
|
||||
if (formatArgs != null && formatArgs.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, text, formatArgs);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Localization format mismatch for key {Key} with text '{Text}'", usedKey, text);
|
||||
try
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, fallbackEnglish, formatArgs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return fallbackEnglish;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private string GetRawString(string key, string fallbackEnglish)
|
||||
{
|
||||
if (TryGetString(CurrentLanguage, key, out var localized))
|
||||
{
|
||||
return localized;
|
||||
}
|
||||
|
||||
LogMissingLocalization(CurrentLanguage, key, fallbackEnglish);
|
||||
|
||||
if (TryGetString(LocalizationLanguage.English, key, out var english))
|
||||
{
|
||||
return english;
|
||||
}
|
||||
|
||||
if (CurrentLanguage != LocalizationLanguage.English)
|
||||
{
|
||||
LogMissingLocalization(LocalizationLanguage.English, key, fallbackEnglish);
|
||||
}
|
||||
|
||||
return fallbackEnglish;
|
||||
}
|
||||
|
||||
private bool TryGetString(LocalizationLanguage language, string key, out string value)
|
||||
{
|
||||
var dictionary = GetDictionary(language);
|
||||
if (dictionary.TryGetValue(key, out var text) && !string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
value = text;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetDictionary(LocalizationLanguage language)
|
||||
{
|
||||
if (!_translations.TryGetValue(language, out var dictionary))
|
||||
{
|
||||
dictionary = LoadLanguage(language);
|
||||
_translations[language] = dictionary;
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> LoadLanguage(LocalizationLanguage language)
|
||||
{
|
||||
var baseDirectory = _pluginInterface.AssemblyLocation.DirectoryName ?? string.Empty;
|
||||
var localizationDirectory = Path.Combine(baseDirectory, "Localization");
|
||||
var languageCode = GetLanguageCode(language);
|
||||
var filePath = Path.Combine(localizationDirectory, $"{languageCode}.json");
|
||||
|
||||
Dictionary<string, string>? translations = null;
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead(filePath);
|
||||
translations = DeserializeTranslations(stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load localization data from {FilePath}", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (translations == null)
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var resourceName = $"{assembly.GetName().Name}.Localization.{languageCode}.json";
|
||||
using var resourceStream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (resourceStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
translations = DeserializeTranslations(resourceStream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load embedded localization resource {Resource}", resourceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (translations == null)
|
||||
{
|
||||
_logger.LogDebug("Localization data for {Language} not found on disk or embedded, using empty dictionary.", language);
|
||||
translations = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return translations;
|
||||
}
|
||||
|
||||
private void LogMissingLocalization(LocalizationLanguage language, string key, string fallback)
|
||||
{
|
||||
var marker = $"{language}:{key}";
|
||||
using var scope = _missingLocalizationsLock.EnterScope();
|
||||
if (_missingLocalizationsLogged.Contains(marker)) return;
|
||||
_missingLocalizationsLogged.Add(marker);
|
||||
|
||||
_logger.LogDebug("Missing localization for {Language} ({Key}). Using fallback '{Fallback}'.", language, key, fallback);
|
||||
}
|
||||
|
||||
private static string GetLanguageCode(LocalizationLanguage language) => language switch
|
||||
{
|
||||
LocalizationLanguage.French => "fr",
|
||||
LocalizationLanguage.English => "en",
|
||||
_ => language.ToString().ToLowerInvariant(),
|
||||
};
|
||||
|
||||
private static Dictionary<string, string>? DeserializeTranslations(Stream stream)
|
||||
{
|
||||
var translations = JsonSerializer.Deserialize<Dictionary<string, string>>(stream, JsonOptions);
|
||||
return translations != null
|
||||
? new Dictionary<string, string>(translations, StringComparer.OrdinalIgnoreCase)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user