/* * Copyright (c) 2025 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.Extensions.Localization; using ProtonVPN.Client.Localization.Building; using ProtonVPN.Client.Localization.Contracts; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.Common.Core.Extensions; using ProtonVPN.Logging.Contracts; using ProtonVPN.Logging.Contracts.Events.AppLogs; using ReswPlusLib; using WinUI3Localizer; namespace ProtonVPN.Client.Localization; public class LocalizationProvider : ILocalizationProvider, ILocationLocalizationSetter { private readonly ILogger _logger; private readonly ISettings _settings; private readonly ILocalizer _localizer; private readonly IStringLocalizer _stringLocalizer; private readonly Lazy> _fallbackLanguageDictionary; private Dictionary> _cityNameTranslations = []; private Dictionary> _stateNameTranslations = []; public LocalizationProvider( ILogger logger, ISettings settings, ILocalizerFactory localizerFactory) { _logger = logger; _settings = settings; _localizer = localizerFactory.GetOrCreate(); _stringLocalizer = new StringLocalizer(this); _fallbackLanguageDictionary = new Lazy>(CreateFallbackLanguageDictionary); ForceCurrentLanguageForPluralProvider(); } public void ForceCurrentLanguageForPluralProvider() { // The library does not allow forcing the language via public interface therefore using reflection. try { Type type = typeof(ResourceLoaderExtension); FieldInfo providerField = type.GetField("_pluralProvider", BindingFlags.NonPublic | BindingFlags.Static); providerField?.SetValue(null, null); MethodInfo method = type.GetMethod("CreatePluralProvider", BindingFlags.NonPublic | BindingFlags.Static); method?.Invoke(null, [_settings.Language]); } catch (Exception e) { _logger.Error("Failed to set language for plural provider.", e); } } // Only sets the fallback dictionary on Release so that missing translations are easier to detect when on Debug private Dictionary CreateFallbackLanguageDictionary() { return _settings.IsDebugModeEnabled ? [] : _localizer.GetLanguageDictionaries() .FirstOrDefault(ld => ld.Language.EqualsIgnoringCase(LocalizerFactory.DEFAULT_LANGUAGE))? .GetItems() .ToDictionary(i => i.Uid, i => i.Value) ?? new(); } public string Get(string resourceKey) { string result = _localizer.GetLocalizedString(resourceKey); if (result == resourceKey || string.IsNullOrEmpty(result)) { result = _fallbackLanguageDictionary.Value.TryGetValue(resourceKey, out string value) ? value : resourceKey; } return result.Replace("\\n", Environment.NewLine); } public string GetFormat(string resourceKey, object arg0) { return Safe(resourceKey, value => string.Format(value, arg0)); } public string GetFormat(string resourceKey, object arg0, object arg1) { return Safe(resourceKey, value => string.Format(value, arg0, arg1)); } public string GetFormat(string resourceKey, object arg0, object arg1, object arg2) { return Safe(resourceKey, value => string.Format(value, arg0, arg1, arg2)); } public string GetFormat(string resourceKey, params object[] args) { return Safe(resourceKey, value => string.Format(value, args)); } public string GetPlural(string resourceKey, long number) { return _stringLocalizer.GetPlural(resourceKey, number); } public string GetPluralFormat(string resourceKey, long number) { return string.Format(GetPlural(resourceKey, number), number); } private string Safe(string resourceKey, Func func) { string value = string.Empty; try { value = Get(resourceKey); return func(value); } catch (FormatException) { return value; } } public void SetCityNames(Dictionary> cities) { _cityNameTranslations = cities; } public void SetStateNames(Dictionary> states) { _stateNameTranslations = states; } public string GetCityName(string englishCityName, string countryCode) { return GetLocationName(_cityNameTranslations, englishCityName, countryCode); } public string GetStateName(string englishStateName, string countryCode) { return GetLocationName(_stateNameTranslations, englishStateName, countryCode); } private string GetLocationName(Dictionary> translationsContainer, string englishName, string countryCode) { if (string.IsNullOrEmpty(englishName)) { return string.Empty; } if (translationsContainer != null && translationsContainer.TryGetValue(countryCode, out Dictionary translations) && translations.TryGetValue(englishName, out string localizedName) && !string.IsNullOrEmpty(localizedName)) { return localizedName; } return englishName; } }