Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor LocalizationMapper to enable more checks #2676

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(), () ->
Expand All @@ -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<SubcommandData> 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<OptionData> options)
{
// <my.command.path>.options.<option_name>.(name|description)
ctx.withKey("options", () ->
ctx.forEach(options, OptionData::getName, option ->
{
ctx.trySetTranslation(option.getNameLocalizations(), "name");
ctx.trySetTranslation(option.getDescriptionLocalizations(), "description");

// <my.command.path>.options.<option_name>.choices.<choice_name>.name
ctx.withKey("choices", () ->
ctx.forEach(option.getChoices(),
Command.Choice::getName,
choice -> ctx.trySetTranslation(choice.getNameLocalizations(), "name")
)
);
})
);
}

private class TranslationContext
{
private final Stack<String> keyComponents = new Stack<>();

private void forObjects(DataArray source, Function<DataObject, String> keyExtractor, Consumer<DataObject> consumer)
private <E> void forEach(List<E> list, Function<E, String> keyExtractor, Consumer<E> 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)
Expand Down Expand Up @@ -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<DiscordLocale, String> 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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading