diff --git a/docs/translations.md b/docs/translations.md
new file mode 100644
index 00000000..bec2c4dc
--- /dev/null
+++ b/docs/translations.md
@@ -0,0 +1,39 @@
+
+
+
+# AuthMe Translations
+The following translations are available in AuthMe. Use the code in your
+config.yml to use the language file.
+
+Code | Language | Translated |
+---- | -------- | ---------: | ---
+en | English | 100% |
+bg | Bulgarian | 73% |
+br | Brazilian | 100% |
+cz | Czech | 91% |
+de | German | 97% |
+es | Spanish | 100% |
+eu | Basque | 66% |
+fi | Finnish | 70% |
+fr | French | 97% |
+gl | Galician | 74% |
+hu | Hungarian | 97% |
+id | Indonesian | 74% |
+it | Italian | 100% |
+ko | Korean | 76% |
+lt | Latvian | 57% |
+nl | Dutch | 80% |
+pl | Polish | 95% |
+pt | Portuguese | 91% |
+ru | Russian | 97% |
+sk | Slovakian | 50% |
+tr | Turkish | 85% |
+uk | Ukrainian | 97% |
+vn | Vietnamese | 85% |
+zhcn | Chinese (China) | 85% |
+zhhk | Chinese (Hong Kong) | 85% |
+zhtw | Chinese (Taiwan) | 85% |
+
+---
+
+This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Oct 08 22:53:13 CEST 2016
diff --git a/src/test/java/tools/docs/UpdateDocsTask.java b/src/test/java/tools/docs/UpdateDocsTask.java
index 86ba85ba..cfdb69ef 100644
--- a/src/test/java/tools/docs/UpdateDocsTask.java
+++ b/src/test/java/tools/docs/UpdateDocsTask.java
@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableSet;
import tools.docs.commands.CommandPageCreater;
import tools.docs.hashmethods.HashAlgorithmsDescriptionTask;
import tools.docs.permissions.PermissionsListWriter;
+import tools.docs.translations.TranslationPageGenerator;
import tools.utils.AutoToolTask;
import tools.utils.ToolTask;
@@ -17,7 +18,8 @@ import java.util.function.Consumer;
public class UpdateDocsTask implements AutoToolTask {
private static final Set> TASKS = ImmutableSet
- .of(CommandPageCreater.class, HashAlgorithmsDescriptionTask.class, PermissionsListWriter.class);
+ .of(CommandPageCreater.class, HashAlgorithmsDescriptionTask.class,
+ PermissionsListWriter.class, TranslationPageGenerator.class);
@Override
public String getTaskName() {
diff --git a/src/test/java/tools/docs/translations/TranslationPageGenerator.java b/src/test/java/tools/docs/translations/TranslationPageGenerator.java
new file mode 100644
index 00000000..e7785bd7
--- /dev/null
+++ b/src/test/java/tools/docs/translations/TranslationPageGenerator.java
@@ -0,0 +1,143 @@
+package tools.docs.translations;
+
+import com.google.common.collect.ImmutableMap;
+import tools.docs.translations.TranslationsGatherer.TranslationInfo;
+import tools.utils.AutoToolTask;
+import tools.utils.FileUtils;
+import tools.utils.TagValue.NestedTagValue;
+import tools.utils.TagValueHolder;
+import tools.utils.ToolsConstants;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Objects.firstNonNull;
+
+/**
+ * Generates the translations page in docs.
+ */
+public class TranslationPageGenerator implements AutoToolTask {
+
+ private static final String DOCS_PAGE = ToolsConstants.DOCS_FOLDER + "translations.md";
+ private static final String TEMPLATE_FILE = ToolsConstants.TOOLS_SOURCE_ROOT + "docs/translations/translations.tpl.md";
+ private static final Map LANGUAGE_NAMES = buildLanguageNames();
+
+ // Color configuration for the bars shown next to translation percentage
+ /**
+ * Percentage threshold under which the color will be computed from COLOR_0 to COLOR_1;
+ * above which COLOR_1 to COLOR_2 is used.
+ */
+ private static final int COLOR_1_PERCENTAGE = 75;
+ // Colors are in RGB format, displayed as an int array of three values whose entries are in the range [0, 15].
+ private static final int[] COLOR_0 = { 9, 0, 0};
+ private static final int[] COLOR_1 = {12, 9, 0};
+ private static final int[] COLOR_2 = { 6, 15, 6};
+
+ private final TranslationsGatherer gatherer = new TranslationsGatherer();
+
+ @Override
+ public String getTaskName() {
+ return "updateTranslations";
+ }
+
+ @Override
+ public void execute(Scanner scanner) {
+ executeDefault();
+ }
+
+ @Override
+ public void executeDefault() {
+ NestedTagValue translationValuesHolder = new NestedTagValue();
+
+ for (TranslationInfo translation : gatherer.getTranslationInfo()) {
+ int percentage = (int) Math.round(translation.percentTranslated * 100);
+ String name = firstNonNull(LANGUAGE_NAMES.get(translation.code), "?");
+ TagValueHolder valueHolder = TagValueHolder.create()
+ .put("code", translation.code)
+ .put("name", name)
+ .put("percentage", Integer.toString(percentage))
+ .put("color", computeColor(percentage));
+ translationValuesHolder.add(valueHolder);
+ }
+
+ TagValueHolder tags = TagValueHolder.create().put("languages", translationValuesHolder);
+ FileUtils.generateFileFromTemplate(TEMPLATE_FILE, DOCS_PAGE, tags);
+ }
+
+ /**
+ * Returns the color for the given percentage as a 6-digit hex color code.
+ *
+ * @param percentage the percentage to generate a color for
+ * @return the color
+ */
+ private String computeColor(int percentage) {
+ int[] color;
+ if (percentage < COLOR_1_PERCENTAGE) {
+ color = computeColor(percentage, COLOR_0, COLOR_1, 0, COLOR_1_PERCENTAGE);
+ } else {
+ color = computeColor(percentage, COLOR_1, COLOR_2, COLOR_1_PERCENTAGE, 100);
+ }
+
+ return Arrays.stream(color)
+ .mapToObj(i -> Integer.toString(i, 16))
+ .map(s -> s + s)
+ .collect(Collectors.joining());
+ }
+
+ /**
+ * Computes the color as the transition between two given colors.
+ *
+ * @param percentage the percentage to compute the color for
+ * @param colorA the color at the start of the range
+ * @param colorB the color at the end of the range
+ * @param rangeMin range start
+ * @param rangeMax range end
+ * @return color for the given percentage
+ */
+ private static int[] computeColor(int percentage, int[] colorA, int[] colorB, int rangeMin, int rangeMax) {
+ double max = rangeMax - rangeMin;
+ double n = percentage - rangeMin;
+
+ return new int[]{
+ (int) (colorA[0] + n / max * (colorB[0] - colorA[0])),
+ (int) (colorA[1] + n / max * (colorB[1] - colorA[1])),
+ (int) (colorA[2] + n / max * (colorB[2] - colorA[2]))
+ };
+ }
+
+ /**
+ * @return map of language code -> language name
+ */
+ private static Map buildLanguageNames() {
+ return ImmutableMap.builder()
+ .put("bg", "Bulgarian")
+ .put("br", "Brazilian")
+ .put("cz", "Czech")
+ .put("de", "German")
+ .put("en", "English")
+ .put("es", "Spanish")
+ .put("eu", "Basque")
+ .put("fi", "Finnish")
+ .put("fr", "French")
+ .put("gl", "Galician")
+ .put("hu", "Hungarian")
+ .put("id", "Indonesian")
+ .put("it", "Italian")
+ .put("ko", "Korean")
+ .put("lt", "Latvian")
+ .put("nl", "Dutch")
+ .put("pl", "Polish")
+ .put("pt", "Portuguese")
+ .put("ru", "Russian")
+ .put("sk", "Slovakian")
+ .put("tr", "Turkish")
+ .put("uk", "Ukrainian")
+ .put("vn", "Vietnamese")
+ .put("zhcn", "Chinese (China)")
+ .put("zhhk", "Chinese (Hong Kong)")
+ .put("zhtw", "Chinese (Taiwan)")
+ .build();
+ }
+}
diff --git a/src/test/java/tools/docs/translations/TranslationsGatherer.java b/src/test/java/tools/docs/translations/TranslationsGatherer.java
new file mode 100644
index 00000000..233b9332
--- /dev/null
+++ b/src/test/java/tools/docs/translations/TranslationsGatherer.java
@@ -0,0 +1,86 @@
+package tools.docs.translations;
+
+import fr.xephi.authme.message.MessageKey;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+import tools.utils.ToolsConstants;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Gathers all available translations of AuthMe.
+ */
+public class TranslationsGatherer {
+
+ private static final Pattern MESSAGES_PATTERN = Pattern.compile("messages_([a-z]{2,4})\\.yml");
+ private static final String MESSAGES_FOLDER = ToolsConstants.MAIN_RESOURCES_ROOT + "messages/";
+
+ private List translationInfo = new ArrayList<>();
+
+ public TranslationsGatherer() {
+ gatherTranslations();
+ translationInfo.sort((e1, e2) -> getCode(e1).compareTo(getCode(e2)));
+ }
+
+ public List getTranslationInfo() {
+ return translationInfo;
+ }
+
+ private void gatherTranslations() {
+ File[] files = new File(MESSAGES_FOLDER).listFiles();
+ if (files == null) {
+ throw new IllegalStateException("Cannot read files of '" + MESSAGES_FOLDER + "'");
+ }
+ for (File file : files) {
+ String code = getLanguageCode(file.getName());
+ if (code != null) {
+ processMessagesFile(code, file);
+ }
+ }
+ }
+
+ private void processMessagesFile(String code, File file) {
+ FileConfiguration configuration = YamlConfiguration.loadConfiguration(file);
+ int availableMessages = 0;
+ for (MessageKey key : MessageKey.values()) {
+ if (configuration.contains(key.getKey())) {
+ ++availableMessages;
+ }
+ }
+ translationInfo.add(new TranslationInfo(code, (double) availableMessages / MessageKey.values().length));
+ }
+
+ private String getLanguageCode(String messagesFile) {
+ Matcher matcher = MESSAGES_PATTERN.matcher(messagesFile);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ return null;
+ }
+
+ public static final class TranslationInfo {
+ public final String code;
+ public final double percentTranslated;
+
+ TranslationInfo(String code, double percentTranslated) {
+ this.code = code;
+ this.percentTranslated = percentTranslated;
+ }
+ }
+
+ /**
+ * Returns the language code from the translation info for sorting purposes.
+ * Returns "a" for "en" language code to sort English on top.
+ *
+ * @param info the translation info
+ * @return the language code for sorting
+ */
+ private static String getCode(TranslationInfo info) {
+ return "en".equals(info.code) ? "a" : info.code;
+ }
+
+}
diff --git a/src/test/java/tools/docs/translations/translations.tpl.md b/src/test/java/tools/docs/translations/translations.tpl.md
new file mode 100644
index 00000000..faad36b5
--- /dev/null
+++ b/src/test/java/tools/docs/translations/translations.tpl.md
@@ -0,0 +1,14 @@
+
+
+
+# AuthMe Translations
+The following translations are available in AuthMe. Use the code in your
+config.yml to use the language file.
+
+Code | Language | Translated |
+---- | -------- | ---------: | ---
+[#languages]
+{code} | {name} | {percentage}% |
+[/#languages]
+
+{gen_footer}