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 3 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
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 @@ -18,16 +18,12 @@

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 +65,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 +75,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 +155,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 RuntimeException("Unable to set translations from '" + localizationFunction.getClass().getName() + "' with key '" + key + "'", e);
freya022 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
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