diff --git a/PKHeX.Core/Editing/BattleTemplate/Errors/BattleTemplateParseError.cs b/PKHeX.Core/Editing/BattleTemplate/Errors/BattleTemplateParseError.cs
new file mode 100644
index 000000000..64cdce07a
--- /dev/null
+++ b/PKHeX.Core/Editing/BattleTemplate/Errors/BattleTemplateParseError.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace PKHeX.Core;
+
+public readonly record struct BattleTemplateParseError(BattleTemplateParseErrorType Type, string Value)
+{
+ public string Humanize(BattleTemplateParseErrorLocalization localization) => Type.Humanize(localization, Value);
+}
diff --git a/PKHeX.Core/Editing/BattleTemplate/Errors/BattleTemplateParseErrorLocalization.cs b/PKHeX.Core/Editing/BattleTemplate/Errors/BattleTemplateParseErrorLocalization.cs
new file mode 100644
index 000000000..912645187
--- /dev/null
+++ b/PKHeX.Core/Editing/BattleTemplate/Errors/BattleTemplateParseErrorLocalization.cs
@@ -0,0 +1,55 @@
+using System.Text.Json.Serialization;
+
+namespace PKHeX.Core;
+
+///
+/// Localized strings for values.
+/// Each enum member maps 1:1 to a property for JSON (de)serialization.
+///
+public sealed class BattleTemplateParseErrorLocalization
+{
+ private static readonly BattleTemplateParseErrorLocalizationContext Context = new(LocalizationStorage.Options);
+ public static readonly LocalizationStorage Cache = new("setparse", Context.BattleTemplateParseErrorLocalization);
+ public static BattleTemplateParseErrorLocalization Get(string language = GameLanguage.DefaultLanguage) => Cache.Get(language);
+ public static BattleTemplateParseErrorLocalization Get(LanguageID language) => Cache.Get(language.GetLanguageCode());
+
+ // General / structural
+ public required string LineLength { get; init; } = "Line exceeded the maximum supported length: {0}";
+
+ // Token issues
+ public required string TokenUnknown { get; init; } = "Unrecognized: {0}";
+ public required string TokenFailParse { get; init; } = "Token could not be parsed: {0}";
+
+ // Move issues
+ public required string MoveCountTooMany { get; init; } = "Too many moves specified: {0}";
+ public required string MoveSlotAlreadyUsed { get; init; } = "Move slot already used: {0}";
+ public required string MoveDuplicate { get; init; } = "Duplicate move specified: {0}";
+ public required string MoveUnrecognized { get; init; } = "Move not recognized: {0}";
+
+ // Item
+ public required string ItemUnrecognized { get; init; } = "Held item not recognized: {0}";
+
+ // Ability
+ public required string AbilityDeclaration { get; init; } = "Ability already declared: {0}";
+ public required string AbilityUnrecognized { get; init; } = "Ability not recognized: {0}";
+ public required string AbilityAlreadySpecified { get; init; } = "Ability already specified: {0}";
+
+ // Nature
+ public required string NatureUnrecognized { get; init; } = "Nature not recognized: {0}";
+ public required string NatureAlreadySpecified { get; init; } = "Nature already specified: {0}";
+
+ // Hidden Power
+ public required string HiddenPowerUnknownType { get; init; } = "Hidden Power type not recognized: {0}";
+ public required string HiddenPowerIncompatibleIVs { get; init; } = "Hidden Power type incompatible with IVs: {0}";
+
+ // EffortValue Nature Amp (Stat modifiers with + / - )
+ public required string NatureEffortAmpDeclaration { get; init; } = "Nature / effort amp already declared: {0}";
+ public required string NatureEffortAmpUnknown { get; init; } = "Unknown nature effort amp token: {0}";
+ public required string NatureEffortAmpAlreadySpecified { get; init; } = "Nature effort amp already specified: {0}";
+ public required string NatureEffortAmpConflictNature { get; init; } = "Declared effort amp conflicts with previously specified nature.";
+ public required string NatureAmpNoPlus { get; init; } = "Missing '+' nature amp token.";
+ public required string NatureAmpNoMinus { get; init; } = "Missing '-' nature amp token.";
+}
+
+[JsonSerializable(typeof(BattleTemplateParseErrorLocalization))]
+public sealed partial class BattleTemplateParseErrorLocalizationContext : JsonSerializerContext;
diff --git a/PKHeX.Core/Editing/BattleTemplate/Errors/BattleTemplateParseErrorType.cs b/PKHeX.Core/Editing/BattleTemplate/Errors/BattleTemplateParseErrorType.cs
new file mode 100644
index 000000000..a2072f424
--- /dev/null
+++ b/PKHeX.Core/Editing/BattleTemplate/Errors/BattleTemplateParseErrorType.cs
@@ -0,0 +1,75 @@
+using System;
+using static PKHeX.Core.BattleTemplateParseErrorType;
+
+namespace PKHeX.Core;
+
+public enum BattleTemplateParseErrorType : byte
+{
+ None = 0,
+ LineLength,
+ TokenUnknown,
+ TokenFailParse,
+ MoveCountTooMany,
+ MoveSlotAlreadyUsed,
+ MoveDuplicate,
+ MoveUnrecognized,
+
+ ItemUnrecognized,
+ AbilityDeclaration,
+ AbilityUnrecognized,
+ AbilityAlreadySpecified,
+ NatureUnrecognized,
+ NatureAlreadySpecified,
+
+ HiddenPowerUnknownType,
+ HiddenPowerIncompatibleIVs,
+
+ NatureEffortAmpDeclaration,
+ NatureEffortAmpUnknown,
+ NatureEffortAmpAlreadySpecified,
+ NatureEffortAmpConflictNature,
+ NatureAmpNoPlus,
+ NatureAmpNoMinus,
+}
+
+public static class BattleTemplateParseErrorExtensions
+{
+ ///
+ /// Returns the localized string for the provided .
+ /// Falls back to the enum name if no mapping exists.
+ ///
+ public static string Humanize(this BattleTemplateParseErrorType type, BattleTemplateParseErrorLocalization localization, string value)
+ {
+ var template = GetTemplate(type, localization);
+ if (value.Length == 0)
+ return template;
+ return string.Format(template, value);
+ }
+
+ private static string GetTemplate(BattleTemplateParseErrorType type, BattleTemplateParseErrorLocalization localization) => type switch
+ {
+ None => "",
+ LineLength => localization.LineLength,
+ TokenUnknown => localization.TokenUnknown,
+ TokenFailParse => localization.TokenFailParse,
+ MoveCountTooMany => localization.MoveCountTooMany,
+ MoveSlotAlreadyUsed => localization.MoveSlotAlreadyUsed,
+ MoveDuplicate => localization.MoveDuplicate,
+ MoveUnrecognized => localization.MoveUnrecognized,
+ ItemUnrecognized => localization.ItemUnrecognized,
+ AbilityDeclaration => localization.AbilityDeclaration,
+ AbilityUnrecognized => localization.AbilityUnrecognized,
+ AbilityAlreadySpecified => localization.AbilityAlreadySpecified,
+ NatureUnrecognized => localization.NatureUnrecognized,
+ NatureAlreadySpecified => localization.NatureAlreadySpecified,
+ HiddenPowerUnknownType => localization.HiddenPowerUnknownType,
+ HiddenPowerIncompatibleIVs => localization.HiddenPowerIncompatibleIVs,
+ NatureEffortAmpDeclaration => localization.NatureEffortAmpDeclaration,
+ NatureEffortAmpUnknown => localization.NatureEffortAmpUnknown,
+ NatureEffortAmpAlreadySpecified => localization.NatureEffortAmpAlreadySpecified,
+ NatureEffortAmpConflictNature => localization.NatureEffortAmpConflictNature,
+ NatureAmpNoPlus => localization.NatureAmpNoPlus,
+ NatureAmpNoMinus => localization.NatureAmpNoMinus,
+ _ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
+ };
+}
diff --git a/PKHeX.Core/Editing/BattleTemplate/Showdown/ShowdownSet.cs b/PKHeX.Core/Editing/BattleTemplate/Showdown/ShowdownSet.cs
index 0f4b9e3c2..da8b33f84 100644
--- a/PKHeX.Core/Editing/BattleTemplate/Showdown/ShowdownSet.cs
+++ b/PKHeX.Core/Editing/BattleTemplate/Showdown/ShowdownSet.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Text;
using static PKHeX.Core.Species;
+using static PKHeX.Core.BattleTemplateParseErrorType;
namespace PKHeX.Core;
@@ -47,7 +48,10 @@ public sealed class ShowdownSet : IBattleTemplate
///
/// Any lines that failed to be parsed.
///
- public readonly List InvalidLines = new(0);
+ public readonly List InvalidLines = new(0);
+
+ private void LogError(BattleTemplateParseErrorType type, ReadOnlySpan text = default)
+ => InvalidLines.Add(new(type, text.ToString()));
///
/// Loads a new from the input string.
@@ -137,7 +141,7 @@ private void ParseLines(SpanLineEnumerator lines, BattleTemplateLocalization loc
first = false;
continue;
}
- InvalidLines.Add(line.ToString());
+ LogError(LineLength, line);
continue;
}
@@ -168,7 +172,7 @@ private void ParseLines(IEnumerable lines, BattleTemplateLocalization lo
first = false;
continue;
}
- InvalidLines.Add(line);
+ LogError(LineLength, line);
continue;
}
@@ -183,64 +187,84 @@ private void ParseLines(IEnumerable lines, BattleTemplateLocalization lo
}
}
- private void ParseLine(ReadOnlySpan line, ref int movectr, BattleTemplateLocalization localization)
+ private void ParseLine(ReadOnlySpan line, ref int countMoves, BattleTemplateLocalization localization)
{
- var moves = Moves.AsSpan();
+ // Check the first char, to see if it is a move input.
var firstChar = line[0];
if (firstChar is '-' or '–')
{
- if (movectr >= MaxMoveCount)
- {
- InvalidLines.Add($"Too many moves: {line}");
- return;
- }
- var moveString = ParseLineMove(line, localization.Strings);
- int move = StringUtil.FindIndexIgnoreCase(localization.Strings.movelist, moveString);
- if (move < 0)
- InvalidLines.Add($"Unknown Move: {moveString}");
- else if (moves.Contains((ushort)move))
- InvalidLines.Add($"Duplicate Move: {moveString}");
- else
- moves[movectr++] = (ushort)move;
+ ParseMoveLine(line, ref countMoves, localization);
return;
}
+ // Check if it is a directional move input.
var dirMove = BattleTemplateConfig.GetMoveDisplay(MoveDisplayStyle.Directional);
var dirMoveIndex = dirMove.IndexOf(firstChar);
if (dirMoveIndex != -1)
{
- if (moves[dirMoveIndex] != 0)
- {
- InvalidLines.Add($"Move slot already specified: {line}");
- return;
- }
- var moveString = ParseLineMove(line, localization.Strings);
- int move = StringUtil.FindIndexIgnoreCase(localization.Strings.movelist, moveString);
- if (move < 0)
- InvalidLines.Add($"Unknown Move: {moveString}");
- else if (moves.Contains((ushort)move))
- InvalidLines.Add($"Duplicate Move: {moveString}");
- else
- moves[dirMoveIndex] = (ushort)move;
- movectr++;
+ ParseMoveLineIndex(line, ref countMoves, localization, dirMoveIndex);
return;
}
- if (firstChar is '[' or '@') // Ability
+ // Check if it is an Ability selection/line
+ if (firstChar is '[' or '@')
{
ParseLineAbilityBracket(line, localization.Strings);
return;
}
+ // Otherwise, tokenized input line.
var token = localization.Config.TryParse(line, out var value);
if (token == BattleTemplateToken.None)
{
- InvalidLines.Add($"Unknown Token: {line}");
+ LogError(TokenUnknown, line);
return;
}
var valid = ParseEntry(token, value, localization);
if (!valid)
- InvalidLines.Add(line.ToString());
+ LogError(TokenFailParse, line);
+ }
+
+ private void ParseMoveLine(ReadOnlySpan line, ref int countMoves, BattleTemplateLocalization localization)
+ {
+ if (countMoves >= MaxMoveCount)
+ {
+ LogError(MoveCountTooMany, line);
+ return;
+ }
+ TryAddMoveAtIndex(line, ref countMoves, localization, countMoves);
+ }
+
+ private void ParseMoveLineIndex(ReadOnlySpan line, ref int countMoves, BattleTemplateLocalization localization, int index)
+ {
+ if (Moves[index] != 0)
+ {
+ LogError(MoveSlotAlreadyUsed, line);
+ return;
+ }
+ TryAddMoveAtIndex(line, ref countMoves, localization, index);
+ }
+
+ private void TryAddMoveAtIndex(ReadOnlySpan line, ref int countMoves, BattleTemplateLocalization localization, int index)
+ {
+ var strings = localization.Strings;
+ var movelist = strings.movelist;
+ var moveString = ParseLineMove(line, strings);
+ int move = StringUtil.FindIndexIgnoreCase(movelist, moveString);
+ var moves = Moves.AsSpan();
+ if (move < 0)
+ {
+ LogError(MoveUnrecognized, moveString);
+ }
+ else if (moves.Contains((ushort)move))
+ {
+ LogError(MoveDuplicate, moveString);
+ }
+ else
+ {
+ moves[index] = (ushort)move;
+ countMoves++;
+ }
}
private void ParseLineAbilityBracket(ReadOnlySpan line, GameStrings localizationStrings)
@@ -251,7 +275,7 @@ private void ParseLineAbilityBracket(ReadOnlySpan line, GameStrings locali
{
var itemName = line[(itemStart + 1)..].TrimStart();
if (!ParseItemName(itemName, localizationStrings))
- InvalidLines.Add($"Unknown Item: {itemName}");
+ LogError(ItemUnrecognized, itemName);
line = line[..itemStart];
}
@@ -259,7 +283,7 @@ private void ParseLineAbilityBracket(ReadOnlySpan line, GameStrings locali
var abilityEnd = line.IndexOf(']');
if (abilityEnd == -1 || line.Length == 1) // '[' should be present if ']' is; length check.
{
- InvalidLines.Add($"Invalid Ability declaration: {line}");
+ LogError(AbilityDeclaration, line);
return; // invalid line
}
@@ -267,7 +291,7 @@ private void ParseLineAbilityBracket(ReadOnlySpan line, GameStrings locali
var abilityIndex = StringUtil.FindIndexIgnoreCase(localizationStrings.abilitylist, abilityName);
if (abilityIndex < 0)
{
- InvalidLines.Add($"Unknown Ability: {abilityName}");
+ LogError(AbilityUnrecognized, abilityName);
return; // invalid line
}
Ability = abilityIndex;
@@ -296,12 +320,12 @@ private bool ParseLineAbility(ReadOnlySpan value, ReadOnlySpan abi
var index = StringUtil.FindIndexIgnoreCase(abilityNames, value);
if (index < 0)
{
- InvalidLines.Add($"Unknown Ability: {value}");
+ LogError(AbilityUnrecognized, value);
return false;
}
if (Ability != -1 && Ability != index)
{
- InvalidLines.Add($"Different ability already specified: {value}");
+ LogError(AbilityAlreadySpecified, value);
return false;
}
@@ -318,12 +342,12 @@ private bool ParseLineNature(ReadOnlySpan value, ReadOnlySpan natu
var nature = (Nature)index;
if (!nature.IsFixed())
{
- InvalidLines.Add($"Invalid Nature: {value}");
+ LogError(NatureUnrecognized, value);
return false;
}
if (Nature != Nature.Random && Nature != nature)
{
- InvalidLines.Add($"Different nature already specified: {value}");
+ LogError(NatureAlreadySpecified, value);
return false;
}
@@ -816,7 +840,7 @@ private void ParseFirstLine(ReadOnlySpan first, GameStrings strings)
var speciesName = first[..itemSplit].TrimEnd();
if (!ParseItemName(itemName, strings))
- InvalidLines.Add($"Unknown Item: {itemName}");
+ LogError(ItemUnrecognized, itemName);
ParseFirstLineNoItem(speciesName, strings);
}
else
@@ -1004,7 +1028,7 @@ private ReadOnlySpan ParseLineMove(ReadOnlySpan line, GameStrings st
if (IVs.AsSpan().ContainsAnyExcept(maxIV))
{
if (!HiddenPower.SetIVsForType(hpVal, IVs, Context))
- InvalidLines.Add($"Invalid IVs for Hidden Power Type: {type}");
+ LogError(HiddenPowerIncompatibleIVs, type);
}
else if (hpVal >= 0)
{
@@ -1012,7 +1036,7 @@ private ReadOnlySpan ParseLineMove(ReadOnlySpan line, GameStrings st
}
else
{
- InvalidLines.Add($"Invalid Hidden Power Type: {type}");
+ LogError(HiddenPowerUnknownType, type);
}
return hiddenPowerName;
}
@@ -1042,19 +1066,19 @@ private bool ParseLineEVs(ReadOnlySpan line, BattleTemplateLocalization lo
var end = natureName.IndexOf(')');
if (end == -1)
{
- InvalidLines.Add($"Invalid EV nature: {natureName}");
+ LogError(NatureEffortAmpDeclaration, natureName);
return false; // invalid line
}
natureName = natureName[..end].Trim();
var natureIndex = StringUtil.FindIndexIgnoreCase(localization.Strings.natures, natureName);
if (natureIndex == -1)
{
- InvalidLines.Add($"Invalid EV nature: {natureName}");
+ LogError(NatureEffortAmpUnknown, natureName);
return false; // invalid line
}
if (Nature != Nature.Random) // specified in a separate Nature line
- InvalidLines.Add($"EV nature ignored, specified previously: {natureName}");
+ LogError(NatureEffortAmpAlreadySpecified, natureName);
else
Nature = (Nature)natureIndex;
@@ -1074,7 +1098,7 @@ private bool ParseLineEVs(ReadOnlySpan line, BattleTemplateLocalization lo
success &= ampNature;
if (ampNature && currentNature != Nature.Random && currentNature != Nature)
{
- InvalidLines.Add($"EV +/- nature does not match specified nature: {currentNature}");
+ LogError(NatureEffortAmpConflictNature);
Nature = currentNature; // revert to original
}
return success;
@@ -1090,9 +1114,9 @@ private bool ParseLineIVs(ReadOnlySpan line, BattleTemplateConfig config)
private bool AdjustNature(sbyte plus, sbyte minus)
{
if (plus == StatParseResult.NoStatAmp)
- InvalidLines.Add("Invalid Nature adjustment, missing plus stat.");
+ LogError(NatureAmpNoPlus);
if (minus == StatParseResult.NoStatAmp)
- InvalidLines.Add("Invalid Nature adjustment, missing minus stat.");
+ LogError(NatureAmpNoMinus);
else
Nature = NatureAmp.CreateNatureFromAmps(plus, minus);
return true;
diff --git a/PKHeX.Core/Legality/Tables/SpeciesCategory.cs b/PKHeX.Core/Legality/Tables/SpeciesCategory.cs
index c685e5ca5..11485337e 100644
--- a/PKHeX.Core/Legality/Tables/SpeciesCategory.cs
+++ b/PKHeX.Core/Legality/Tables/SpeciesCategory.cs
@@ -70,6 +70,9 @@ public static bool IsParadox(ushort species) => species is (>= (int)GreatTusk an
or (int)WalkingWake or (int)IronLeaves
or (int)GougingFire or (int)RagingBolt or (int)IronBoulder or (int)IronCrown;
+ ///
+ /// Checks if the is a species that evolved from a bi-gendered species into a single-gendered species/form.
+ ///
public static bool IsFixedGenderFromDual(ushort currentSpecies) => currentSpecies switch
{
(int)Shedinja => true, // Genderless
@@ -84,7 +87,20 @@ public static bool IsParadox(ushort species) => species is (>= (int)GreatTusk an
(int)Meowstic => true, // (M/F) form specific
(int)Salazzle => true, // (F)
(int)Oinkologne => true, // (M/F) form specific
+ (int)Basculegion => true, // (M/F) form specific
_ => false,
};
+
+ ///
+ /// Checks if the is a species that has each gender as a different form.
+ ///
+ public static bool IsFormGenderSpecific(ushort currentSpecies) => currentSpecies switch
+ {
+ (int)Meowstic => true,
+ (int)Indeedee => true,
+ (int)Basculegion => true,
+ (int)Oinkologne => true,
+ _ => false,
+ };
}
diff --git a/PKHeX.Core/Resources/localize/battle/setparse_de.json b/PKHeX.Core/Resources/localize/battle/setparse_de.json
new file mode 100644
index 000000000..b5779a489
--- /dev/null
+++ b/PKHeX.Core/Resources/localize/battle/setparse_de.json
@@ -0,0 +1,23 @@
+{
+ "LineLength": "Line exceeded the maximum supported length: {0}",
+ "TokenUnknown": "Unrecognized: {0}",
+ "TokenFailParse": "Parse Failure: {0}",
+ "MoveCountTooMany": "Too many moves specified: {0}",
+ "MoveSlotAlreadyUsed": "Move slot already used: {0}",
+ "MoveDuplicate": "Duplicate move specified: {0}",
+ "MoveUnrecognized": "Move not recognized: {0}",
+ "ItemUnrecognized": "Held item not recognized: {0}",
+ "AbilityDeclaration": "Ability already declared: {0}",
+ "AbilityUnrecognized": "Ability not recognized: {0}",
+ "AbilityAlreadySpecified": "Ability already specified: {0}",
+ "NatureUnrecognized": "Nature not recognized: {0}",
+ "NatureAlreadySpecified": "Nature already specified: {0}",
+ "HiddenPowerUnknownType": "Hidden Power type not recognized: {0}",
+ "HiddenPowerIncompatibleIVs": "Hidden Power type incompatible with IVs: {0}",
+ "NatureEffortAmpDeclaration": "Nature / effort amp already declared: {0}",
+ "NatureEffortAmpUnknown": "Unknown nature effort amp: {0}",
+ "NatureEffortAmpAlreadySpecified": "Nature effort amp already specified: {0}",
+ "NatureEffortAmpConflictNature": "Declared effort amp conflicts with previously specified nature.",
+ "NatureAmpNoPlus": "Missing '+' nature amp.",
+ "NatureAmpNoMinus": "Missing '-' nature amp."
+}
diff --git a/PKHeX.Core/Resources/localize/battle/setparse_en.json b/PKHeX.Core/Resources/localize/battle/setparse_en.json
new file mode 100644
index 000000000..b5779a489
--- /dev/null
+++ b/PKHeX.Core/Resources/localize/battle/setparse_en.json
@@ -0,0 +1,23 @@
+{
+ "LineLength": "Line exceeded the maximum supported length: {0}",
+ "TokenUnknown": "Unrecognized: {0}",
+ "TokenFailParse": "Parse Failure: {0}",
+ "MoveCountTooMany": "Too many moves specified: {0}",
+ "MoveSlotAlreadyUsed": "Move slot already used: {0}",
+ "MoveDuplicate": "Duplicate move specified: {0}",
+ "MoveUnrecognized": "Move not recognized: {0}",
+ "ItemUnrecognized": "Held item not recognized: {0}",
+ "AbilityDeclaration": "Ability already declared: {0}",
+ "AbilityUnrecognized": "Ability not recognized: {0}",
+ "AbilityAlreadySpecified": "Ability already specified: {0}",
+ "NatureUnrecognized": "Nature not recognized: {0}",
+ "NatureAlreadySpecified": "Nature already specified: {0}",
+ "HiddenPowerUnknownType": "Hidden Power type not recognized: {0}",
+ "HiddenPowerIncompatibleIVs": "Hidden Power type incompatible with IVs: {0}",
+ "NatureEffortAmpDeclaration": "Nature / effort amp already declared: {0}",
+ "NatureEffortAmpUnknown": "Unknown nature effort amp: {0}",
+ "NatureEffortAmpAlreadySpecified": "Nature effort amp already specified: {0}",
+ "NatureEffortAmpConflictNature": "Declared effort amp conflicts with previously specified nature.",
+ "NatureAmpNoPlus": "Missing '+' nature amp.",
+ "NatureAmpNoMinus": "Missing '-' nature amp."
+}
diff --git a/PKHeX.Core/Resources/localize/battle/setparse_es.json b/PKHeX.Core/Resources/localize/battle/setparse_es.json
new file mode 100644
index 000000000..b5779a489
--- /dev/null
+++ b/PKHeX.Core/Resources/localize/battle/setparse_es.json
@@ -0,0 +1,23 @@
+{
+ "LineLength": "Line exceeded the maximum supported length: {0}",
+ "TokenUnknown": "Unrecognized: {0}",
+ "TokenFailParse": "Parse Failure: {0}",
+ "MoveCountTooMany": "Too many moves specified: {0}",
+ "MoveSlotAlreadyUsed": "Move slot already used: {0}",
+ "MoveDuplicate": "Duplicate move specified: {0}",
+ "MoveUnrecognized": "Move not recognized: {0}",
+ "ItemUnrecognized": "Held item not recognized: {0}",
+ "AbilityDeclaration": "Ability already declared: {0}",
+ "AbilityUnrecognized": "Ability not recognized: {0}",
+ "AbilityAlreadySpecified": "Ability already specified: {0}",
+ "NatureUnrecognized": "Nature not recognized: {0}",
+ "NatureAlreadySpecified": "Nature already specified: {0}",
+ "HiddenPowerUnknownType": "Hidden Power type not recognized: {0}",
+ "HiddenPowerIncompatibleIVs": "Hidden Power type incompatible with IVs: {0}",
+ "NatureEffortAmpDeclaration": "Nature / effort amp already declared: {0}",
+ "NatureEffortAmpUnknown": "Unknown nature effort amp: {0}",
+ "NatureEffortAmpAlreadySpecified": "Nature effort amp already specified: {0}",
+ "NatureEffortAmpConflictNature": "Declared effort amp conflicts with previously specified nature.",
+ "NatureAmpNoPlus": "Missing '+' nature amp.",
+ "NatureAmpNoMinus": "Missing '-' nature amp."
+}
diff --git a/PKHeX.Core/Resources/localize/battle/setparse_fr.json b/PKHeX.Core/Resources/localize/battle/setparse_fr.json
new file mode 100644
index 000000000..b5779a489
--- /dev/null
+++ b/PKHeX.Core/Resources/localize/battle/setparse_fr.json
@@ -0,0 +1,23 @@
+{
+ "LineLength": "Line exceeded the maximum supported length: {0}",
+ "TokenUnknown": "Unrecognized: {0}",
+ "TokenFailParse": "Parse Failure: {0}",
+ "MoveCountTooMany": "Too many moves specified: {0}",
+ "MoveSlotAlreadyUsed": "Move slot already used: {0}",
+ "MoveDuplicate": "Duplicate move specified: {0}",
+ "MoveUnrecognized": "Move not recognized: {0}",
+ "ItemUnrecognized": "Held item not recognized: {0}",
+ "AbilityDeclaration": "Ability already declared: {0}",
+ "AbilityUnrecognized": "Ability not recognized: {0}",
+ "AbilityAlreadySpecified": "Ability already specified: {0}",
+ "NatureUnrecognized": "Nature not recognized: {0}",
+ "NatureAlreadySpecified": "Nature already specified: {0}",
+ "HiddenPowerUnknownType": "Hidden Power type not recognized: {0}",
+ "HiddenPowerIncompatibleIVs": "Hidden Power type incompatible with IVs: {0}",
+ "NatureEffortAmpDeclaration": "Nature / effort amp already declared: {0}",
+ "NatureEffortAmpUnknown": "Unknown nature effort amp: {0}",
+ "NatureEffortAmpAlreadySpecified": "Nature effort amp already specified: {0}",
+ "NatureEffortAmpConflictNature": "Declared effort amp conflicts with previously specified nature.",
+ "NatureAmpNoPlus": "Missing '+' nature amp.",
+ "NatureAmpNoMinus": "Missing '-' nature amp."
+}
diff --git a/PKHeX.Core/Resources/localize/battle/setparse_it.json b/PKHeX.Core/Resources/localize/battle/setparse_it.json
new file mode 100644
index 000000000..b5779a489
--- /dev/null
+++ b/PKHeX.Core/Resources/localize/battle/setparse_it.json
@@ -0,0 +1,23 @@
+{
+ "LineLength": "Line exceeded the maximum supported length: {0}",
+ "TokenUnknown": "Unrecognized: {0}",
+ "TokenFailParse": "Parse Failure: {0}",
+ "MoveCountTooMany": "Too many moves specified: {0}",
+ "MoveSlotAlreadyUsed": "Move slot already used: {0}",
+ "MoveDuplicate": "Duplicate move specified: {0}",
+ "MoveUnrecognized": "Move not recognized: {0}",
+ "ItemUnrecognized": "Held item not recognized: {0}",
+ "AbilityDeclaration": "Ability already declared: {0}",
+ "AbilityUnrecognized": "Ability not recognized: {0}",
+ "AbilityAlreadySpecified": "Ability already specified: {0}",
+ "NatureUnrecognized": "Nature not recognized: {0}",
+ "NatureAlreadySpecified": "Nature already specified: {0}",
+ "HiddenPowerUnknownType": "Hidden Power type not recognized: {0}",
+ "HiddenPowerIncompatibleIVs": "Hidden Power type incompatible with IVs: {0}",
+ "NatureEffortAmpDeclaration": "Nature / effort amp already declared: {0}",
+ "NatureEffortAmpUnknown": "Unknown nature effort amp: {0}",
+ "NatureEffortAmpAlreadySpecified": "Nature effort amp already specified: {0}",
+ "NatureEffortAmpConflictNature": "Declared effort amp conflicts with previously specified nature.",
+ "NatureAmpNoPlus": "Missing '+' nature amp.",
+ "NatureAmpNoMinus": "Missing '-' nature amp."
+}
diff --git a/PKHeX.Core/Resources/localize/battle/setparse_ja.json b/PKHeX.Core/Resources/localize/battle/setparse_ja.json
new file mode 100644
index 000000000..b5779a489
--- /dev/null
+++ b/PKHeX.Core/Resources/localize/battle/setparse_ja.json
@@ -0,0 +1,23 @@
+{
+ "LineLength": "Line exceeded the maximum supported length: {0}",
+ "TokenUnknown": "Unrecognized: {0}",
+ "TokenFailParse": "Parse Failure: {0}",
+ "MoveCountTooMany": "Too many moves specified: {0}",
+ "MoveSlotAlreadyUsed": "Move slot already used: {0}",
+ "MoveDuplicate": "Duplicate move specified: {0}",
+ "MoveUnrecognized": "Move not recognized: {0}",
+ "ItemUnrecognized": "Held item not recognized: {0}",
+ "AbilityDeclaration": "Ability already declared: {0}",
+ "AbilityUnrecognized": "Ability not recognized: {0}",
+ "AbilityAlreadySpecified": "Ability already specified: {0}",
+ "NatureUnrecognized": "Nature not recognized: {0}",
+ "NatureAlreadySpecified": "Nature already specified: {0}",
+ "HiddenPowerUnknownType": "Hidden Power type not recognized: {0}",
+ "HiddenPowerIncompatibleIVs": "Hidden Power type incompatible with IVs: {0}",
+ "NatureEffortAmpDeclaration": "Nature / effort amp already declared: {0}",
+ "NatureEffortAmpUnknown": "Unknown nature effort amp: {0}",
+ "NatureEffortAmpAlreadySpecified": "Nature effort amp already specified: {0}",
+ "NatureEffortAmpConflictNature": "Declared effort amp conflicts with previously specified nature.",
+ "NatureAmpNoPlus": "Missing '+' nature amp.",
+ "NatureAmpNoMinus": "Missing '-' nature amp."
+}
diff --git a/PKHeX.Core/Resources/localize/battle/setparse_ko.json b/PKHeX.Core/Resources/localize/battle/setparse_ko.json
new file mode 100644
index 000000000..b5779a489
--- /dev/null
+++ b/PKHeX.Core/Resources/localize/battle/setparse_ko.json
@@ -0,0 +1,23 @@
+{
+ "LineLength": "Line exceeded the maximum supported length: {0}",
+ "TokenUnknown": "Unrecognized: {0}",
+ "TokenFailParse": "Parse Failure: {0}",
+ "MoveCountTooMany": "Too many moves specified: {0}",
+ "MoveSlotAlreadyUsed": "Move slot already used: {0}",
+ "MoveDuplicate": "Duplicate move specified: {0}",
+ "MoveUnrecognized": "Move not recognized: {0}",
+ "ItemUnrecognized": "Held item not recognized: {0}",
+ "AbilityDeclaration": "Ability already declared: {0}",
+ "AbilityUnrecognized": "Ability not recognized: {0}",
+ "AbilityAlreadySpecified": "Ability already specified: {0}",
+ "NatureUnrecognized": "Nature not recognized: {0}",
+ "NatureAlreadySpecified": "Nature already specified: {0}",
+ "HiddenPowerUnknownType": "Hidden Power type not recognized: {0}",
+ "HiddenPowerIncompatibleIVs": "Hidden Power type incompatible with IVs: {0}",
+ "NatureEffortAmpDeclaration": "Nature / effort amp already declared: {0}",
+ "NatureEffortAmpUnknown": "Unknown nature effort amp: {0}",
+ "NatureEffortAmpAlreadySpecified": "Nature effort amp already specified: {0}",
+ "NatureEffortAmpConflictNature": "Declared effort amp conflicts with previously specified nature.",
+ "NatureAmpNoPlus": "Missing '+' nature amp.",
+ "NatureAmpNoMinus": "Missing '-' nature amp."
+}
diff --git a/PKHeX.Core/Resources/localize/battle/setparse_zh-hans.json b/PKHeX.Core/Resources/localize/battle/setparse_zh-hans.json
new file mode 100644
index 000000000..b5779a489
--- /dev/null
+++ b/PKHeX.Core/Resources/localize/battle/setparse_zh-hans.json
@@ -0,0 +1,23 @@
+{
+ "LineLength": "Line exceeded the maximum supported length: {0}",
+ "TokenUnknown": "Unrecognized: {0}",
+ "TokenFailParse": "Parse Failure: {0}",
+ "MoveCountTooMany": "Too many moves specified: {0}",
+ "MoveSlotAlreadyUsed": "Move slot already used: {0}",
+ "MoveDuplicate": "Duplicate move specified: {0}",
+ "MoveUnrecognized": "Move not recognized: {0}",
+ "ItemUnrecognized": "Held item not recognized: {0}",
+ "AbilityDeclaration": "Ability already declared: {0}",
+ "AbilityUnrecognized": "Ability not recognized: {0}",
+ "AbilityAlreadySpecified": "Ability already specified: {0}",
+ "NatureUnrecognized": "Nature not recognized: {0}",
+ "NatureAlreadySpecified": "Nature already specified: {0}",
+ "HiddenPowerUnknownType": "Hidden Power type not recognized: {0}",
+ "HiddenPowerIncompatibleIVs": "Hidden Power type incompatible with IVs: {0}",
+ "NatureEffortAmpDeclaration": "Nature / effort amp already declared: {0}",
+ "NatureEffortAmpUnknown": "Unknown nature effort amp: {0}",
+ "NatureEffortAmpAlreadySpecified": "Nature effort amp already specified: {0}",
+ "NatureEffortAmpConflictNature": "Declared effort amp conflicts with previously specified nature.",
+ "NatureAmpNoPlus": "Missing '+' nature amp.",
+ "NatureAmpNoMinus": "Missing '-' nature amp."
+}
diff --git a/PKHeX.Core/Resources/localize/battle/setparse_zh-hant.json b/PKHeX.Core/Resources/localize/battle/setparse_zh-hant.json
new file mode 100644
index 000000000..b5779a489
--- /dev/null
+++ b/PKHeX.Core/Resources/localize/battle/setparse_zh-hant.json
@@ -0,0 +1,23 @@
+{
+ "LineLength": "Line exceeded the maximum supported length: {0}",
+ "TokenUnknown": "Unrecognized: {0}",
+ "TokenFailParse": "Parse Failure: {0}",
+ "MoveCountTooMany": "Too many moves specified: {0}",
+ "MoveSlotAlreadyUsed": "Move slot already used: {0}",
+ "MoveDuplicate": "Duplicate move specified: {0}",
+ "MoveUnrecognized": "Move not recognized: {0}",
+ "ItemUnrecognized": "Held item not recognized: {0}",
+ "AbilityDeclaration": "Ability already declared: {0}",
+ "AbilityUnrecognized": "Ability not recognized: {0}",
+ "AbilityAlreadySpecified": "Ability already specified: {0}",
+ "NatureUnrecognized": "Nature not recognized: {0}",
+ "NatureAlreadySpecified": "Nature already specified: {0}",
+ "HiddenPowerUnknownType": "Hidden Power type not recognized: {0}",
+ "HiddenPowerIncompatibleIVs": "Hidden Power type incompatible with IVs: {0}",
+ "NatureEffortAmpDeclaration": "Nature / effort amp already declared: {0}",
+ "NatureEffortAmpUnknown": "Unknown nature effort amp: {0}",
+ "NatureEffortAmpAlreadySpecified": "Nature effort amp already specified: {0}",
+ "NatureEffortAmpConflictNature": "Declared effort amp conflicts with previously specified nature.",
+ "NatureAmpNoPlus": "Missing '+' nature amp.",
+ "NatureAmpNoMinus": "Missing '-' nature amp."
+}
diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs
index 54cf07ece..d46ea4def 100644
--- a/PKHeX.WinForms/MainWindow/Main.cs
+++ b/PKHeX.WinForms/MainWindow/Main.cs
@@ -439,8 +439,16 @@ private void ClickShowdownImportPKM(object? sender, EventArgs e)
var invalid = set.InvalidLines;
if (invalid.Count != 0)
- WinFormsUtil.Alert(MsgSimulatorInvalid, string.Join(Environment.NewLine, invalid));
-
+ {
+ var localization = BattleTemplateParseErrorLocalization.Get(CurrentLanguage);
+ var sb = new System.Text.StringBuilder();
+ foreach (var line in invalid)
+ {
+ var error = line.Humanize(localization);
+ sb.AppendLine(error);
+ }
+ WinFormsUtil.Alert(MsgSimulatorInvalid, sb.ToString());
+ }
PKME_Tabs.LoadShowdownSet(set);
}