using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Npgsql; using PhoenixLib.Logging; using PhoenixLib.MultiLanguage; using WingsAPI.Data.GameData; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; namespace TranslationServer.Loader { public class GenericTranslationFileLoader : IResourceLoader { private static readonly INamingConvention __NamingConvention = UnderscoredNamingConvention.Instance; private static readonly IDeserializer __Deserializer = new DeserializerBuilder().WithNamingConvention(__NamingConvention).Build(); private readonly TranslationsFileLoaderOptions _options; private readonly List _translations = new(); public GenericTranslationFileLoader(TranslationsFileLoaderOptions options) => _options = options; public async Task> LoadAsync() { if (_translations.Any()) { return _translations; } Dictionary english = null; foreach (RegionLanguageType i in Enum.GetValues(typeof(RegionLanguageType))) { if (i == RegionLanguageType.RU) { continue; } string languageType = i.ToString().ToLowerInvariant(); var newTmp = new Dictionary(); string languageDirectory = Path.Combine(_options.TranslationsPath, $"{languageType}"); IEnumerable translationFiles = Enumerable.Empty(); if (Directory.Exists(languageDirectory)) { translationFiles = Directory.GetFiles(languageDirectory, "*.yml").Concat(Directory.GetFiles(languageDirectory, "*.yaml")); } foreach (string translationFile in translationFiles) { try { string fileContent = await File.ReadAllTextAsync(translationFile); IDeserializer deserializer = __Deserializer; Dictionary tmp = deserializer.Deserialize>(fileContent); foreach ((string s, string value) in tmp) { if (string.IsNullOrEmpty(value)) { continue; } if (value == $"#{s}" && english != null && english.TryGetValue(s, out string translated)) { newTmp[s] = translated; continue; } newTmp[s] = value; } } catch (Exception e) { Log.Error($"[RESOURCE_LOADER] {translationFile} {languageType}", e); } } if (!newTmp.Any()) { foreach ((string key, string value) in LoadFromResourceLangFiles(i)) { newTmp[key] = value; } if (!newTmp.Any()) { foreach ((string key, string value) in LoadFromDatabaseLangFiles(i)) { newTmp[key] = value; } } } if (i == RegionLanguageType.EN) { english = newTmp; } _translations.AddRange(newTmp.Select(s => new GenericTranslationDto { Key = s.Key, Value = s.Value, Language = i })); } Log.Info($"[RESOURCE_LOADER] {_translations.Count.ToString()} translations loaded"); return _translations; } private IEnumerable<(string key, string value)> LoadFromResourceLangFiles(RegionLanguageType lang) { var entries = new List<(string key, string value)>(); string marker = lang switch { RegionLanguageType.EN => "_uk_", RegionLanguageType.DE => "_de_", RegionLanguageType.FR => "_fr_", RegionLanguageType.IT => "_it_", RegionLanguageType.ES => "_es_", RegionLanguageType.CZ => "_cz_", RegionLanguageType.PL => "_pl_", RegionLanguageType.TR => "_tr_", _ => null }; if (marker == null) { return entries; } try { const string resourceLangPath = "/app/resources/lang"; if (!Directory.Exists(resourceLangPath)) { return entries; } foreach (string file in Directory.GetFiles(resourceLangPath, $"*{marker}*.txt", SearchOption.TopDirectoryOnly)) { string filePrefix = Path.GetFileNameWithoutExtension(file)?.Replace('.', '_') ?? "lang"; foreach (string line in File.ReadAllLines(file, Encoding.Latin1)) { string[] parts = line.Split('\t'); if (parts.Length < 2 || string.IsNullOrWhiteSpace(parts[0]) || string.IsNullOrWhiteSpace(parts[1])) { continue; } entries.Add(($"{filePrefix}.{parts[0]}", parts[1])); } } } catch (Exception ex) { Log.Error($"[RESOURCE_LOADER] Resource language fallback failed for {lang}", ex); } return entries; } private IEnumerable<(string key, string value)> LoadFromDatabaseLangFiles(RegionLanguageType lang) { var entries = new List<(string key, string value)>(); try { string marker = lang switch { RegionLanguageType.EN => "_uk_", RegionLanguageType.DE => "_de_", RegionLanguageType.FR => "_fr_", RegionLanguageType.IT => "_it_", RegionLanguageType.ES => "_es_", RegionLanguageType.CZ => "_cz_", RegionLanguageType.PL => "_pl_", RegionLanguageType.TR => "_tr_", _ => null }; if (marker == null) { return entries; } string host = Environment.GetEnvironmentVariable("DATABASE_IP") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP") ?? "127.0.0.1"; string port = Environment.GetEnvironmentVariable("DATABASE_PORT") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PORT") ?? "5432"; string db = Environment.GetEnvironmentVariable("DATABASE_NAME") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_NAME") ?? "game"; string user = Environment.GetEnvironmentVariable("DATABASE_USER") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER") ?? "postgres"; string pass = Environment.GetEnvironmentVariable("DATABASE_PASSWORD") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD") ?? "postgres"; using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}"); conn.Open(); using var cmd = new NpgsqlCommand("SELECT relative_path, content FROM resources.resource_files WHERE category='lang' AND lower(relative_path) LIKE lower(@needle);", conn); cmd.Parameters.AddWithValue("needle", $"%{marker}%"); using var reader = cmd.ExecuteReader(); while (reader.Read()) { string path = reader.GetString(0); string filePrefix = Path.GetFileNameWithoutExtension(path)?.Replace('.', '_') ?? "lang"; byte[] content = (byte[])reader[1]; string text = Encoding.Latin1.GetString(content); foreach (string line in text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries)) { string[] parts = line.Split('\t'); if (parts.Length < 2 || string.IsNullOrWhiteSpace(parts[0]) || string.IsNullOrWhiteSpace(parts[1])) { continue; } string key = $"{filePrefix}.{parts[0]}"; entries.Add((key, parts[1])); } } } catch (Exception ex) { Log.Error($"[RESOURCE_LOADER] DB language fallback failed for {lang}", ex); } return entries; } } }