diff --git a/docker-compose.yml b/docker-compose.yml index 8f22509..cc7d6df 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -427,10 +427,16 @@ services: REDIS_PORT: 6379 MQTT_BROKER_ADDRESS: mqtt MQTT_BROKER_PORT: 1883 + DATABASE_IP: postgres + DATABASE_PORT: 5432 + DATABASE_NAME: game + DATABASE_USER: ${POSTGRES_USER} + DATABASE_PASSWORD: ${POSTGRES_PASSWORD} Logging__LogLevel__Microsoft.AspNetCore.Server.Kestrel: Error command: ["/app/TranslationsServer.dll"] volumes: - - ./translations:/app/translations:ro + - ./translations:/app/translations + - ./resources:/app/resources:ro ports: - "19999:19999" diff --git a/srcs/TranslationsServer/Loader/GenericTranslationFileLoader.cs b/srcs/TranslationsServer/Loader/GenericTranslationFileLoader.cs index 77ca3c6..d7d6d18 100644 --- a/srcs/TranslationsServer/Loader/GenericTranslationFileLoader.cs +++ b/srcs/TranslationsServer/Loader/GenericTranslationFileLoader.cs @@ -2,7 +2,9 @@ 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; @@ -41,7 +43,13 @@ namespace TranslationServer.Loader var newTmp = new Dictionary(); string languageDirectory = Path.Combine(_options.TranslationsPath, $"{languageType}"); - foreach (string translationFile in Directory.GetFiles(languageDirectory, "*.yml").Concat(Directory.GetFiles(languageDirectory, "*.yaml"))) + IEnumerable translationFiles = Enumerable.Empty(); + if (Directory.Exists(languageDirectory)) + { + translationFiles = Directory.GetFiles(languageDirectory, "*.yml").Concat(Directory.GetFiles(languageDirectory, "*.yaml")); + } + + foreach (string translationFile in translationFiles) { try { @@ -64,28 +72,158 @@ namespace TranslationServer.Loader newTmp[s] = value; } - - if (i == RegionLanguageType.EN) - { - english = newTmp; - } - - _translations.AddRange(newTmp.Select(s => new GenericTranslationDto - { - Key = s.Key, - Value = s.Value, - Language = i - })); } 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; + } } } \ No newline at end of file diff --git a/srcs/TranslationsServer/TranslationsServer.csproj b/srcs/TranslationsServer/TranslationsServer.csproj index 5b86013..505130a 100644 --- a/srcs/TranslationsServer/TranslationsServer.csproj +++ b/srcs/TranslationsServer/TranslationsServer.csproj @@ -9,6 +9,7 @@ +