/
LocalizationMapper.java
165 lines (150 loc) · 6.77 KB
/
LocalizationMapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*
* 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.internal.interactions.command.localization;
import net.dv8tion.jda.api.interactions.DiscordLocale;
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.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.Map;
import java.util.Stack;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Function;
/*
* Utility class which maps user-provided translations (from a resource bundle for example) to the command data as well as everything contained in it.
* This class essentially wraps a {@link LocalizationFunction} to ask the localization function for translations based on command definitions defined in code.
* The real brain of this system lies in the localization function.
* The localization function is where the developer can define <i>how</i> to get translations for various parts of commands.
* The LocalizationMapper is effectively just the context organizer between command definitions and getting their translations.
*
* <p>You can find a prebuilt localization function that uses {@link java.util.ResourceBundle ResourceBundles} at {@link ResourceBundleLocalizationFunction}.
*
* @see LocalizationFunction
* @see ResourceBundleLocalizationFunction
*/
public class LocalizationMapper
{
private final LocalizationFunction localizationFunction;
private LocalizationMapper(LocalizationFunction localizationFunction) {
this.localizationFunction = localizationFunction;
}
/*
* Creates a new {@link LocalizationMapper} from the given {@link LocalizationFunction}
*
* @param localizationFunction
* The {@link LocalizationFunction} to use
*
* @return The {@link LocalizationMapper} instance
*
* @see ResourceBundleLocalizationFunction
*/
@Nonnull
public static LocalizationMapper fromFunction(@Nonnull LocalizationFunction localizationFunction) {
return new LocalizationMapper(localizationFunction);
}
public void localizeCommand(CommandData commandData, DataArray optionArray)
{
final TranslationContext ctx = new TranslationContext();
ctx.withKey(commandData.getName(), () ->
{
ctx.trySetTranslation(commandData.getNameLocalizations(), "name");
if (commandData instanceof SlashCommandData)
{
final SlashCommandData slashCommandData = (SlashCommandData) commandData;
ctx.trySetTranslation(slashCommandData.getDescriptionLocalizations(), "description");
localizeOptionArray(optionArray, ctx);
}
});
}
private void localizeOptionArray(DataArray optionArray, TranslationContext ctx)
{
ctx.forObjects(optionArray, o -> o.getString("name"), obj ->
{
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"))
localizeOptionArray(obj.getArray("choices"), ctx);
});
}
private class TranslationContext
{
private final Stack<String> keyComponents = new Stack<>();
private void forObjects(DataArray source, Function<DataObject, String> keyExtractor, Consumer<DataObject> consumer)
{
for (int i = 0; i < source.length(); i++)
{
final DataObject item = source.getObject(i);
final String key = keyExtractor.apply(item);
keyComponents.push(key);
consumer.accept(item);
keyComponents.pop();
}
}
private void withKey(String key, Runnable runnable)
{
keyComponents.push(key);
runnable.run();
keyComponents.pop();
}
private String getKey(String finalComponent)
{
final StringJoiner joiner = new StringJoiner(".");
for (String keyComponent : keyComponents)
joiner.add(keyComponent.replace(" ", "_")); //Context commands can have spaces, we need to replace them
joiner.add(finalComponent.replace(" ", "_"));
return joiner.toString().toLowerCase();
}
private void trySetTranslation(LocalizationMap localizationMap, String finalComponent)
{
final String key = getKey(finalComponent);
try
{
final Map<DiscordLocale, String> data = localizationFunction.apply(key);
localizationMap.setTranslations(data);
}
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);
}
}
}
}