diff --git a/src/main/java/net/dv8tion/jda/api/exceptions/LocalizationException.java b/src/main/java/net/dv8tion/jda/api/exceptions/LocalizationException.java new file mode 100644 index 0000000000..298ca914ca --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/exceptions/LocalizationException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.api.exceptions; + +import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; + +/** + * Exception indicating that an error occurred while localizing an application command. + * + *

They are usually caused by invalid strings, + * or exceptions in a {@link net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction LocalizationFunction}. + * + * @see net.dv8tion.jda.api.interactions.commands.build.CommandData#setLocalizationFunction(LocalizationFunction) CommandData.setLocalizationFunction(LocalizationFunction) + */ +public class LocalizationException extends RuntimeException +{ + public LocalizationException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/src/main/java/net/dv8tion/jda/internal/interactions/CommandDataImpl.java b/src/main/java/net/dv8tion/jda/internal/interactions/CommandDataImpl.java index 88bc202595..f3e774ef8d 100644 --- a/src/main/java/net/dv8tion/jda/internal/interactions/CommandDataImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/interactions/CommandDataImpl.java @@ -94,9 +94,9 @@ public void checkDescription(@Nonnull String description) @Override public DataObject toData() { - DataArray options = DataArray.fromCollection(this.options); + if (localizationMapper != null) localizationMapper.localizeCommand(this); - if (localizationMapper != null) localizationMapper.localizeCommand(this, options); + DataArray options = DataArray.fromCollection(this.options); DataObject json = DataObject.empty() .put("type", type.getId()) diff --git a/src/main/java/net/dv8tion/jda/internal/interactions/command/localization/LocalizationMapper.java b/src/main/java/net/dv8tion/jda/internal/interactions/command/localization/LocalizationMapper.java index d7ab4b9638..57d78e2b35 100644 --- a/src/main/java/net/dv8tion/jda/internal/interactions/command/localization/LocalizationMapper.java +++ b/src/main/java/net/dv8tion/jda/internal/interactions/command/localization/LocalizationMapper.java @@ -16,18 +16,15 @@ package net.dv8tion.jda.internal.interactions.command.localization; +import net.dv8tion.jda.api.exceptions.LocalizationException; import net.dv8tion.jda.api.interactions.DiscordLocale; import net.dv8tion.jda.api.interactions.commands.Command; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.CommandData; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; +import net.dv8tion.jda.api.interactions.commands.build.*; import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; import net.dv8tion.jda.api.interactions.commands.localization.LocalizationMap; -import net.dv8tion.jda.api.utils.data.DataArray; -import net.dv8tion.jda.api.utils.data.DataObject; -import net.dv8tion.jda.internal.utils.Checks; import javax.annotation.Nonnull; +import java.util.List; import java.util.Map; import java.util.Stack; import java.util.StringJoiner; @@ -69,7 +66,7 @@ public static LocalizationMapper fromFunction(@Nonnull LocalizationFunction loca return new LocalizationMapper(localizationFunction); } - public void localizeCommand(CommandData commandData, DataArray optionArray) + public void localizeCommand(CommandData commandData) { final TranslationContext ctx = new TranslationContext(); ctx.withKey(commandData.getName(), () -> @@ -79,59 +76,58 @@ public void localizeCommand(CommandData commandData, DataArray optionArray) { final SlashCommandData slashCommandData = (SlashCommandData) commandData; ctx.trySetTranslation(slashCommandData.getDescriptionLocalizations(), "description"); - localizeOptionArray(optionArray, ctx); + + localizeOptions(ctx, slashCommandData.getOptions()); + localizeSubcommands(ctx, slashCommandData.getSubcommands()); + ctx.forEach(slashCommandData.getSubcommandGroups(), SubcommandGroupData::getName, subcommandGroup -> + { + ctx.trySetTranslation(subcommandGroup.getNameLocalizations(), "name"); + ctx.trySetTranslation(subcommandGroup.getDescriptionLocalizations(), "description"); + + localizeSubcommands(ctx, subcommandGroup.getSubcommands()); + }); } }); } - private void localizeOptionArray(DataArray optionArray, TranslationContext ctx) + private static void localizeSubcommands(TranslationContext ctx, List subcommands) { - ctx.forObjects(optionArray, o -> o.getString("name"), obj -> + ctx.forEach(subcommands, SubcommandData::getName, subcommand -> { - if (obj.hasKey("name_localizations")) - ctx.trySetTranslation(obj.getObject("name_localizations"), "name"); - if (obj.hasKey("description_localizations")) - ctx.trySetTranslation(obj.getObject("description_localizations"), "description"); - if (obj.hasKey("options")) - localizeOptionArray(obj.getArray("options"), ctx); - if (obj.hasKey("choices")) - //Puts "choices" between the option name and the choice name - // This makes it more distinguishable in tree structures - ctx.withKey("choices", () -> localizeOptionArray(obj.getArray("choices"), ctx)); + ctx.trySetTranslation(subcommand.getNameLocalizations(), "name"); + ctx.trySetTranslation(subcommand.getDescriptionLocalizations(), "description"); + localizeOptions(ctx, subcommand.getOptions()); }); } + private static void localizeOptions(TranslationContext ctx, List options) + { + // .options..(name|description) + ctx.withKey("options", () -> + ctx.forEach(options, OptionData::getName, option -> + { + ctx.trySetTranslation(option.getNameLocalizations(), "name"); + ctx.trySetTranslation(option.getDescriptionLocalizations(), "description"); + + // .options..choices..name + ctx.withKey("choices", () -> + ctx.forEach(option.getChoices(), + Command.Choice::getName, + choice -> ctx.trySetTranslation(choice.getNameLocalizations(), "name") + ) + ); + }) + ); + } + private class TranslationContext { private final Stack keyComponents = new Stack<>(); - private void forObjects(DataArray source, Function keyExtractor, Consumer consumer) + private void forEach(List list, Function keyExtractor, Consumer consumer) { - for (int i = 0; i < source.length(); i++) - { - final DataObject item = source.getObject(i); - final Runnable runnable = () -> - { - final String key = keyExtractor.apply(item); - keyComponents.push(key); - consumer.accept(item); - keyComponents.pop(); - }; - - //We need to differentiate subcommands/groups from options before inserting the "options" separator - final OptionType type = OptionType.fromKey(item.getInt("type", -1)); //-1 when the object isn't an option - final boolean isOption = type != OptionType.SUB_COMMAND && type != OptionType.SUB_COMMAND_GROUP && type != OptionType.UNKNOWN; - if (isOption) { - //At this point the key should look like "path.to.command", - // we can insert "options", and the keyExtractor would give option names - - //Put "options" between the command name and the option name - // This makes it more distinguishable in tree structures - withKey("options", runnable); - } else { - runnable.run(); - } - } + for (E e : list) + withKey(keyExtractor.apply(e), () -> consumer.accept(e)); } private void withKey(String key, Runnable runnable) @@ -160,26 +156,7 @@ private void trySetTranslation(LocalizationMap localizationMap, String finalComp } catch (Exception e) { - throw new RuntimeException("An uncaught exception occurred while using a LocalizationFunction, localization key: '" + key + "'", e); - } - } - - private void trySetTranslation(DataObject localizationMap, String finalComponent) - { - final String key = getKey(finalComponent); - try - { - final Map data = localizationFunction.apply(key); - data.forEach((locale, localizedValue) -> - { - Checks.check(locale != DiscordLocale.UNKNOWN, "Localization function returned a map with an 'UNKNOWN' DiscordLocale"); - - localizationMap.put(locale.getLocale(), localizedValue); - }); - } - catch (Exception e) - { - throw new RuntimeException("An uncaught exception occurred while using a LocalizationFunction, localization key: '" + key + "'", e); + throw new LocalizationException("Unable to set translations from '" + localizationFunction.getClass().getName() + "' with key '" + key + "'", e); } } } diff --git a/src/test/java/net/dv8tion/jda/test/interactions/LocalizationTest.java b/src/test/java/net/dv8tion/jda/test/interactions/LocalizationTest.java index 3f9c09cf9f..8f66340fa3 100644 --- a/src/test/java/net/dv8tion/jda/test/interactions/LocalizationTest.java +++ b/src/test/java/net/dv8tion/jda/test/interactions/LocalizationTest.java @@ -56,15 +56,6 @@ static void setup() new Command.Choice("7 Days", "7"), new Command.Choice("14 Days", "14") ) - ), - new SubcommandData("temp", "Bans a user temporarily").addOptions( - new OptionData(OptionType.STRING, "user", "The user to ban"), - new OptionData(OptionType.INTEGER, "del_days", "The amount of days to delete messages") - .addChoices( - new Command.Choice("1 Day", "1"), - new Command.Choice("7 Days", "7"), - new Command.Choice("14 Days", "14") - ) ) ) ).setLocalizationFunction(localizationFunction);