Prevent community mods from warning on unused translations in the common assets

Currently when linting translations, we check for any unused translation keys. This works fine for the default mods, which own the entire sets of translation files. For community mods, they often import the translation files from the common mod assets, but they may only use some of the translations provided. Currently, they would get warnings about not using translations from the common files they have imported.

Since the community mods don't own those translations, getting warnings about it is annoying. To solve this issue, introduce a AllowUnusedTranslationsInExternalPackages in the mod.yaml which defaults to true. This will prevent reporting of unused translation keys from external assets. Keys that are used for external assets will still be validated, and keys from the mod assets will be both validated and unused keys will be reported.

We default the new flag to true and don't provide an update rule. This means community mods will get the new behaviour. For the default mods, we do want to check the "external" assets, since we control those assets. So the default mods have their mod.yaml updated to disable the flag and retain the existing behaviour of checking everything.
This commit is contained in:
RoosterDragon
2024-07-27 22:56:20 +01:00
committed by Gustas
parent 0aac5885fb
commit bd809e5af7
11 changed files with 50 additions and 33 deletions

View File

@@ -23,7 +23,7 @@ namespace OpenRA.FileSystem
bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename);
bool TryOpen(string filename, out Stream s);
bool Exists(string filename);
bool IsExternalModFile(string filename);
bool IsExternalFile(string filename);
}
public class FileSystem : IReadOnlyFileSystem
@@ -270,21 +270,11 @@ namespace OpenRA.FileSystem
}
/// <summary>
/// Returns true if the given filename references an external mod via an explicit mount.
/// Returns true if the given filename references any file outside the mod mount.
/// </summary>
public bool IsExternalModFile(string filename)
public bool IsExternalFile(string filename)
{
var explicitSplit = filename.IndexOf('|');
if (explicitSplit < 0)
return false;
if (!explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
return false;
if (installedMods[modID].Package == explicitPackage)
return false;
return modPackages.Contains(explicitPackage);
return !filename.StartsWith($"{modID}|", StringComparison.Ordinal);
}
/// <summary>

View File

@@ -74,6 +74,7 @@ namespace OpenRA
public readonly string[] SpriteFormats = Array.Empty<string>();
public readonly string[] PackageFormats = Array.Empty<string>();
public readonly string[] VideoFormats = Array.Empty<string>();
public bool AllowUnusedTranslationsInExternalPackages = true;
readonly string[] reservedModuleNames =
{
@@ -81,7 +82,7 @@ namespace OpenRA
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
"ServerTraits", "LoadScreen", "DefaultOrderGenerator", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
"RequiresMods", "PackageFormats"
"RequiresMods", "PackageFormats", "AllowUnusedTranslationsInExternalPackages"
};
readonly TypeDictionary modules = new();
@@ -166,6 +167,10 @@ namespace OpenRA
if (yaml.TryGetValue("VideoFormats", out entry))
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", entry.Value);
if (yaml.TryGetValue("AllowUnusedTranslationsInExternalPackages", out entry))
AllowUnusedTranslationsInExternalPackages =
FieldLoader.GetValue<bool>("AllowUnusedTranslationsInExternalPackages", entry.Value);
}
public void LoadCustomData(ObjectCreator oc)

View File

@@ -1427,11 +1427,11 @@ namespace OpenRA
return modData.DefaultFileSystem.Exists(filename);
}
public bool IsExternalModFile(string filename)
public bool IsExternalFile(string filename)
{
// Explicit package paths never refer to a map
if (filename.Contains('|'))
return modData.DefaultFileSystem.IsExternalModFile(filename);
return modData.DefaultFileSystem.IsExternalFile(filename);
return false;
}

View File

@@ -654,11 +654,11 @@ namespace OpenRA
return modData.DefaultFileSystem.Exists(filename);
}
bool IReadOnlyFileSystem.IsExternalModFile(string filename)
bool IReadOnlyFileSystem.IsExternalFile(string filename)
{
// Explicit package paths never refer to a map
if (filename.Contains('|'))
return modData.DefaultFileSystem.IsExternalModFile(filename);
return modData.DefaultFileSystem.IsExternalFile(filename);
return false;
}

View File

@@ -44,7 +44,12 @@ namespace OpenRA.Mods.Common.Lint
foreach (var language in GetTranslationLanguages(modData))
{
CheckKeys(modData.Manifest.Translations.Concat(mapTranslations), map.Open, usedKeys, language, false, emitError, emitWarning);
// Check keys and variables are not missing across all language files.
// 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(
modData.Manifest.Translations.Concat(mapTranslations), map.Open, usedKeys,
language, _ => false, emitError, emitWarning);
var modTranslation = new Translation(language, modData.Manifest.Translations, modData.DefaultFileSystem, _ => { });
var mapTranslation = new Translation(language, mapTranslations, map, error => emitError(error.Message));
@@ -79,7 +84,13 @@ namespace OpenRA.Mods.Common.Lint
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(modData.Manifest.Translations, modData.DefaultFileSystem.Open, usedKeys, language, true, emitError, emitWarning);
var keyWithAttrs = CheckKeys(
modData.Manifest.Translations, modData.DefaultFileSystem.Open, usedKeys,
language,
file =>
!modData.Manifest.AllowUnusedTranslationsInExternalPackages ||
!modData.DefaultFileSystem.IsExternalFile(file),
emitError, emitWarning);
foreach (var group in usedKeys.KeysWithContext)
{
@@ -322,7 +333,10 @@ namespace OpenRA.Mods.Common.Lint
CheckChrome(n, translationReferencesByWidgetField, usedKeys);
}
static HashSet<string> CheckKeys(IEnumerable<string> translationFiles, Func<string, Stream> openFile, TranslationKeys usedKeys, string language, bool checkUnusedKeys, Action<string> emitError, Action<string> emitWarning)
static HashSet<string> CheckKeys(
IEnumerable<string> translationFiles, Func<string, Stream> openFile, TranslationKeys usedKeys,
string language, Func<string, bool> checkUnusedKeysForFile,
Action<string> emitError, Action<string> emitWarning)
{
var keyWithAttrs = new HashSet<string>();
foreach (var file in translationFiles)
@@ -351,7 +365,7 @@ namespace OpenRA.Mods.Common.Lint
foreach (var (node, attributeName) in nodeAndAttributeNames)
{
keyWithAttrs.Add(attributeName == null ? key : $"{key}.{attributeName}");
if (checkUnusedKeys)
if (checkUnusedKeysForFile(file))
CheckUnusedKey(key, attributeName, file, usedKeys, emitWarning);
CheckVariables(node, key, attributeName, file, usedKeys, emitError, emitWarning);
}

View File

@@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.UpdateRules
{
return FieldLoader.GetValue<string[]>("value", yaml.Value)
.Where(f => f.Contains('|'))
.SelectMany(f => LoadModYaml(modData, FilterExternalModFiles(modData, new[] { f }, externalFilenames)))
.SelectMany(f => LoadModYaml(modData, FilterExternalFiles(modData, new[] { f }, externalFilenames)))
.ToList();
}
@@ -182,11 +182,11 @@ namespace OpenRA.Mods.Common.UpdateRules
return MiniYaml.Merge(yaml).ConvertAll(n => new MiniYamlNodeBuilder(n));
}
public static IEnumerable<string> FilterExternalModFiles(ModData modData, IEnumerable<string> files, HashSet<string> externalFilenames)
public static IEnumerable<string> FilterExternalFiles(ModData modData, IEnumerable<string> files, HashSet<string> externalFilenames)
{
foreach (var f in files)
{
if (f.Contains('|') && modData.DefaultFileSystem.IsExternalModFile(f))
if (f.Contains('|') && modData.DefaultFileSystem.IsExternalFile(f))
{
externalFilenames.Add(f);
continue;
@@ -200,12 +200,12 @@ namespace OpenRA.Mods.Common.UpdateRules
{
var manualSteps = new List<string>();
var modRules = LoadModYaml(modData, FilterExternalModFiles(modData, modData.Manifest.Rules, externalFilenames));
var modWeapons = LoadModYaml(modData, FilterExternalModFiles(modData, modData.Manifest.Weapons, externalFilenames));
var modSequences = LoadModYaml(modData, FilterExternalModFiles(modData, modData.Manifest.Sequences, externalFilenames));
var modTilesets = LoadModYaml(modData, FilterExternalModFiles(modData, modData.Manifest.TileSets, externalFilenames));
var modChromeLayout = LoadModYaml(modData, FilterExternalModFiles(modData, modData.Manifest.ChromeLayout, externalFilenames));
var modChromeProvider = LoadModYaml(modData, FilterExternalModFiles(modData, modData.Manifest.Chrome, externalFilenames));
var modRules = LoadModYaml(modData, FilterExternalFiles(modData, modData.Manifest.Rules, externalFilenames));
var modWeapons = LoadModYaml(modData, FilterExternalFiles(modData, modData.Manifest.Weapons, externalFilenames));
var modSequences = LoadModYaml(modData, FilterExternalFiles(modData, modData.Manifest.Sequences, externalFilenames));
var modTilesets = LoadModYaml(modData, FilterExternalFiles(modData, modData.Manifest.TileSets, externalFilenames));
var modChromeLayout = LoadModYaml(modData, FilterExternalFiles(modData, modData.Manifest.ChromeLayout, externalFilenames));
var modChromeProvider = LoadModYaml(modData, FilterExternalFiles(modData, modData.Manifest.Chrome, externalFilenames));
// Find and add shared map includes
foreach (var package in modData.MapCache.EnumerateMapPackagesWithoutCaching())

View File

@@ -47,7 +47,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
.Where(t => t.Value.Length > 0)
.ToDictionary(t => t.Key, t => t.Value);
var modRules = UpdateUtils.LoadModYaml(modData, UpdateUtils.FilterExternalModFiles(modData, modData.Manifest.Rules, new HashSet<string>()));
var modRules = UpdateUtils.LoadModYaml(modData, UpdateUtils.FilterExternalFiles(modData, modData.Manifest.Rules, new HashSet<string>()));
// Include files referenced in maps.
foreach (var package in modData.MapCache.EnumerateMapPackagesWithoutCaching())

View File

@@ -149,6 +149,8 @@ Translations:
cnc|languages/chrome/en.ftl
cnc|languages/rules/en.ftl
AllowUnusedTranslationsInExternalPackages: false
Voices:
cnc|audio/voices.yaml

View File

@@ -130,6 +130,8 @@ Translations:
d2k|languages/chrome/en.ftl
d2k|languages/rules/en.ftl
AllowUnusedTranslationsInExternalPackages: false
Weapons:
d2k|weapons/debris.yaml
d2k|weapons/smallguns.yaml

View File

@@ -146,6 +146,8 @@ Translations:
ra|languages/chrome/en.ftl
ra|languages/rules/en.ftl
AllowUnusedTranslationsInExternalPackages: false
Weapons:
ra|weapons/explosions.yaml
ra|weapons/ballistics.yaml

View File

@@ -191,6 +191,8 @@ Translations:
ts|languages/chrome/en.ftl
ts|languages/rules/en.ftl
AllowUnusedTranslationsInExternalPackages: false
Voices:
ts|audio/voices.yaml