Remove magic ftl file naming.

This commit is contained in:
Paul Chote
2024-10-20 18:48:15 +01:00
committed by Gustas
parent 5a0c8439fc
commit c09d7cbdea
7 changed files with 54 additions and 86 deletions

View File

@@ -13,7 +13,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using Linguini.Bundle; using Linguini.Bundle;
using Linguini.Bundle.Builder; using Linguini.Bundle.Builder;
using Linguini.Shared.Types.Bundle; using Linguini.Shared.Types.Bundle;
@@ -53,37 +52,30 @@ namespace OpenRA
{ {
readonly Linguini.Bundle.FluentBundle bundle; readonly Linguini.Bundle.FluentBundle bundle;
public FluentBundle(string language, string[] paths, IReadOnlyFileSystem fileSystem) public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem)
: this(language, paths, fileSystem, error => Log.Write("debug", error.Message)) { } : this(culture, paths, fileSystem, error => Log.Write("debug", error.Message)) { }
public FluentBundle(string language, string[] paths, IReadOnlyFileSystem fileSystem, string text) public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, string text)
: this(language, paths, fileSystem, text, error => Log.Write("debug", error.Message)) { } : this(culture, paths, fileSystem, text, error => Log.Write("debug", error.Message)) { }
public FluentBundle(string language, string[] paths, IReadOnlyFileSystem fileSystem, Action<ParseError> onError) public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, Action<ParseError> onError)
: this(language, paths, fileSystem, null, onError) { } : this(culture, paths, fileSystem, null, onError) { }
public FluentBundle(string language, string text, Action<ParseError> onError) public FluentBundle(string culture, string text, Action<ParseError> onError)
: this(language, null, null, text, onError) { } : this(culture, null, null, text, onError) { }
public FluentBundle(string language, string[] paths, IReadOnlyFileSystem fileSystem, string text, Action<ParseError> onError) public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, string text, Action<ParseError> onError)
{ {
bundle = LinguiniBuilder.Builder() bundle = LinguiniBuilder.Builder()
.CultureInfo(new CultureInfo(language)) .CultureInfo(new CultureInfo(culture))
.SkipResources() .SkipResources()
.SetUseIsolating(false) .SetUseIsolating(false)
.UseConcurrent() .UseConcurrent()
.UncheckedBuild(); .UncheckedBuild();
if (paths != null && paths.Length > 0) if (paths != null)
{ {
// Always load english strings to provide a fallback for missing translations. foreach (var path in paths)
// It is important to load the english files first so the chosen language's files can override them.
var resolvedPaths = paths.Where(t => t.EndsWith("en.ftl", StringComparison.Ordinal)).ToList();
foreach (var t in paths)
if (t.EndsWith($"{language}.ftl", StringComparison.Ordinal))
resolvedPaths.Add(t);
foreach (var path in resolvedPaths.Distinct())
{ {
var stream = fileSystem.Open(path); var stream = fileSystem.Open(path);
using (var reader = new StreamReader(stream)) using (var reader = new StreamReader(stream))

View File

@@ -26,7 +26,7 @@ namespace OpenRA
{ {
lock (SyncObject) lock (SyncObject)
{ {
modFluentBundle = new FluentBundle(Game.Settings.Player.Language, modData.Manifest.Translations, fileSystem); modFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, modData.Manifest.Translations, fileSystem);
if (fileSystem is Map map && map.FluentMessageDefinitions != null) if (fileSystem is Map map && map.FluentMessageDefinitions != null)
{ {
var files = Array.Empty<string>(); var files = Array.Empty<string>();
@@ -44,7 +44,7 @@ namespace OpenRA
text = builder.ToString(); text = builder.ToString();
} }
mapFluentBundle = new FluentBundle(Game.Settings.Player.Language, files, fileSystem, text); mapFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, files, fileSystem, text);
} }
} }
} }

View File

@@ -83,10 +83,13 @@ namespace OpenRA
public readonly string[] SpriteFormats = Array.Empty<string>(); public readonly string[] SpriteFormats = Array.Empty<string>();
public readonly string[] PackageFormats = Array.Empty<string>(); public readonly string[] PackageFormats = Array.Empty<string>();
public readonly string[] VideoFormats = Array.Empty<string>(); public readonly string[] VideoFormats = Array.Empty<string>();
public readonly bool AllowUnusedTranslationsInExternalPackages = true;
public readonly int FontSheetSize = 512; public readonly int FontSheetSize = 512;
public readonly int CursorSheetSize = 512; public readonly int CursorSheetSize = 512;
// TODO: This should be controlled by a user-selected translation bundle!
public readonly string FluentCulture = "en";
public readonly bool AllowUnusedTranslationsInExternalPackages = true;
readonly string[] reservedModuleNames = readonly string[] reservedModuleNames =
{ {
"Include", "Metadata", "FileSystem", "MapFolders", "Rules", "Include", "Metadata", "FileSystem", "MapFolders", "Rules",

View File

@@ -143,7 +143,7 @@ namespace OpenRA
text = builder.ToString(); text = builder.ToString();
} }
FluentBundle = new FluentBundle(Game.Settings.Player.Language, files, fileSystem, text); FluentBundle = new FluentBundle(modData.Manifest.FluentCulture, files, fileSystem, text);
} }
else else
FluentBundle = null; FluentBundle = null;

View File

@@ -249,7 +249,6 @@ namespace OpenRA
public Color Color = Color.FromArgb(200, 32, 32); public Color Color = Color.FromArgb(200, 32, 32);
public string LastServer = "localhost:1234"; public string LastServer = "localhost:1234";
public Color[] CustomColors = Array.Empty<Color>(); public Color[] CustomColors = Array.Empty<Color>();
public string Language = "en";
} }
public class GameSettings public class GameSettings

View File

@@ -26,9 +26,6 @@ namespace OpenRA
if (Game.EngineVersion != null) if (Game.EngineVersion != null)
Log.Write("exception", $"OpenRA engine version {Game.EngineVersion}"); Log.Write("exception", $"OpenRA engine version {Game.EngineVersion}");
if (Game.Settings != null && Game.Settings.Player != null && Game.Settings.Player.Language != null)
Log.Write("exception", $"OpenRA Language: {Game.Settings.Player.Language}");
if (Game.ModData != null) if (Game.ModData != null)
{ {
var manifest = Game.ModData.Manifest; var manifest = Game.ModData.Manifest;

View File

@@ -30,8 +30,6 @@ namespace OpenRA.Mods.Common.Lint
{ {
sealed class CheckFluentReferences : ILintPass, ILintMapPass sealed class CheckFluentReferences : ILintPass, ILintMapPass
{ {
static readonly Regex FilenameRegex = new(@"(?<language>[^\/\\]+)\.ftl$");
void ILintMapPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Map map) void ILintMapPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Map map)
{ {
if (map.FluentMessageDefinitions == null) if (map.FluentMessageDefinitions == null)
@@ -43,32 +41,27 @@ namespace OpenRA.Mods.Common.Lint
emitWarning($"Empty key in map ftl files required by {context}"); emitWarning($"Empty key in map ftl files required by {context}");
var mapTranslations = FieldLoader.GetValue<string[]>("value", map.FluentMessageDefinitions.Value); var mapTranslations = FieldLoader.GetValue<string[]>("value", map.FluentMessageDefinitions.Value);
var allModTranslations = modData.Manifest.Translations; var allModTranslations = modData.Manifest.Translations;
foreach (var language in GetModLanguages(allModTranslations))
// For maps we don't warn on unused keys. They might be unused on *this* map,
// but the mod or another map may use them and we don't have sight of that.
CheckKeys(allModTranslations.Concat(mapTranslations), map.Open, usedKeys,
_ => false, emitError, emitWarning);
var modFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, allModTranslations, modData.DefaultFileSystem, _ => { });
var mapFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, mapTranslations, map, error => emitError(error.Message));
foreach (var group in usedKeys.KeysWithContext)
{ {
// Check keys and variables are not missing across all language files. if (modFluentBundle.HasMessage(group.Key))
// But for maps we don't warn on unused keys. They might be unused on *this* map,
// but the mod or another map may use them and we don't have sight of that.
CheckKeys(
allModTranslations.Concat(mapTranslations), map.Open, usedKeys,
language, _ => false, emitError, emitWarning);
var modFluentBundle = new FluentBundle(language, allModTranslations, modData.DefaultFileSystem, _ => { });
var mapFluentBundle = new FluentBundle(language, mapTranslations, map, error => emitError(error.Message));
foreach (var group in usedKeys.KeysWithContext)
{ {
if (modFluentBundle.HasMessage(group.Key)) if (mapFluentBundle.HasMessage(group.Key))
{ emitWarning($"Key `{group.Key}` in map ftl files already exists in mod translations and will not be used.");
if (mapFluentBundle.HasMessage(group.Key)) }
emitWarning($"Key `{group.Key}` in `{language}` language in map ftl files already exists in mod translations and will not be used."); else if (!mapFluentBundle.HasMessage(group.Key))
} {
else if (!mapFluentBundle.HasMessage(group.Key)) foreach (var context in group)
{ emitWarning($"Missing key `{group.Key}` in map ftl files required by {context}");
foreach (var context in group)
emitWarning($"Missing key `{group.Key}` in `{language}` language in map ftl files required by {context}");
}
} }
} }
@@ -80,34 +73,30 @@ namespace OpenRA.Mods.Common.Lint
void ILintPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData) void ILintPass.Run(Action<string> emitError, Action<string> emitWarning, ModData modData)
{ {
Console.WriteLine("Testing Fluent references");
var (usedKeys, testedFields) = GetUsedFluentKeysInMod(modData); var (usedKeys, testedFields) = GetUsedFluentKeysInMod(modData);
foreach (var context in usedKeys.EmptyKeyContexts) foreach (var context in usedKeys.EmptyKeyContexts)
emitWarning($"Empty key in mod translation files required by {context}"); emitWarning($"Empty key in mod translation files required by {context}");
var allModTranslations = modData.Manifest.Translations.ToArray(); var allModTranslations = modData.Manifest.Translations.ToArray();
foreach (var language in GetModLanguages(allModTranslations)) CheckModWidgets(modData, usedKeys, testedFields);
// With the fully populated keys, check keys and variables are not missing and not unused across all language files.
var keyWithAttrs = CheckKeys(
allModTranslations, modData.DefaultFileSystem.Open, usedKeys,
file =>
!modData.Manifest.AllowUnusedTranslationsInExternalPackages ||
!modData.DefaultFileSystem.IsExternalFile(file),
emitError, emitWarning);
foreach (var group in usedKeys.KeysWithContext)
{ {
Console.WriteLine($"Testing language: {language}"); if (keyWithAttrs.Contains(group.Key))
CheckModWidgets(modData, usedKeys, testedFields); continue;
// With the fully populated keys, check keys and variables are not missing and not unused across all language files. foreach (var context in group)
var keyWithAttrs = CheckKeys( emitWarning($"Missing key `{group.Key}` in mod ftl files required by {context}");
allModTranslations, modData.DefaultFileSystem.Open, usedKeys,
language,
file =>
!modData.Manifest.AllowUnusedTranslationsInExternalPackages ||
!modData.DefaultFileSystem.IsExternalFile(file),
emitError, emitWarning);
foreach (var group in usedKeys.KeysWithContext)
{
if (keyWithAttrs.Contains(group.Key))
continue;
foreach (var context in group)
emitWarning($"Missing key `{group.Key}` in `{language}` language in mod ftl files required by {context}");
}
} }
// Check if we couldn't test any fields. // Check if we couldn't test any fields.
@@ -121,14 +110,6 @@ namespace OpenRA.Mods.Common.Lint
$"`{field.ReflectedType.Name}.{field.Name}` - previous warnings may be incorrect"); $"`{field.ReflectedType.Name}.{field.Name}` - previous warnings may be incorrect");
} }
static IEnumerable<string> GetModLanguages(IEnumerable<string> translations)
{
return translations
.Select(filename => FilenameRegex.Match(filename).Groups["language"].Value)
.Distinct()
.OrderBy(l => l);
}
static Keys GetUsedFluentKeysInRuleset(Ruleset rules) static Keys GetUsedFluentKeysInRuleset(Ruleset rules)
{ {
var usedKeys = new Keys(); var usedKeys = new Keys();
@@ -427,15 +408,11 @@ namespace OpenRA.Mods.Common.Lint
static HashSet<string> CheckKeys( static HashSet<string> CheckKeys(
IEnumerable<string> paths, Func<string, Stream> openFile, Keys usedKeys, IEnumerable<string> paths, Func<string, Stream> openFile, Keys usedKeys,
string language, Func<string, bool> checkUnusedKeysForFile, Func<string, bool> checkUnusedKeysForFile, Action<string> emitError, Action<string> emitWarning)
Action<string> emitError, Action<string> emitWarning)
{ {
var keyWithAttrs = new HashSet<string>(); var keyWithAttrs = new HashSet<string>();
foreach (var path in paths) foreach (var path in paths)
{ {
if (!path.EndsWith($"{language}.ftl", StringComparison.Ordinal))
continue;
var stream = openFile(path); var stream = openFile(path);
using (var reader = new StreamReader(stream)) using (var reader = new StreamReader(stream))
{ {