Add basic internationalization support

This commit is contained in:
davioooh 2018-04-18 17:14:32 +02:00 committed by Abbas Abou Daya
parent bcb9d7505b
commit d77887fd2c
5 changed files with 98 additions and 22 deletions

View File

@ -103,6 +103,7 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
protected static final String COMMANDS = "commands"; protected static final String COMMANDS = "commands";
// Messages // Messages
// TODO replace hardcoded messages...
protected static final String RECOVERY_MESSAGE = "I am ready to receive the backup file. Please reply to this message with the backup file attached."; protected static final String RECOVERY_MESSAGE = "I am ready to receive the backup file. Please reply to this message with the backup file attached.";
protected static final String RECOVER_SUCCESS = "I have successfully recovered."; protected static final String RECOVER_SUCCESS = "I have successfully recovered.";
@ -306,7 +307,7 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
}) })
.sorted() .sorted()
.reduce((a, b) -> format("%s%n%s", a, b)) .reduce((a, b) -> format("%s%n%s", a, b))
.orElse("No public commands found."); .orElse(getLocalizedMessage("ability.commands.notFound", ctx.user().locale()));
silent.send(commands, ctx.chatId()); silent.send(commands, ctx.chatId());
}) })
@ -371,11 +372,13 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
if (db.recover(backupData)) { if (db.recover(backupData)) {
silent.send(RECOVER_SUCCESS, chatId); silent.send(RECOVER_SUCCESS, chatId);
} else { } else {
silent.send("Oops, something went wrong during recovery.", chatId); silent.send(getLocalizedMessage("ability.recover.fail",
AbilityUtils.getUser(update).getLanguageCode()), chatId);
} }
} catch (Exception e) { } catch (Exception e) {
BotLogger.error("Could not recover DB from backup", TAG, e); BotLogger.error("Could not recover DB from backup", TAG, e);
silent.send("I have failed to recover.", chatId); silent.send(getLocalizedMessage("ability.recover.error",
AbilityUtils.getUser(update).getLanguageCode()), chatId);
} }
}, MESSAGE, DOCUMENT, REPLY, isReplyTo(RECOVERY_MESSAGE)) }, MESSAGE, DOCUMENT, REPLY, isReplyTo(RECOVERY_MESSAGE))
.build(); .build();
@ -411,10 +414,10 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
Set<Integer> blacklist = blacklist(); Set<Integer> blacklist = blacklist();
if (blacklist.contains(userId)) if (blacklist.contains(userId))
silent.sendMd(format("%s is already *banned*.", escape(bannedUser)), ctx.chatId()); silent.sendMd(getLocalizedMessage("ability.ban.alreadyBanned", ctx.user().locale(), escape(bannedUser)), ctx.chatId());
else { else {
blacklist.add(userId); blacklist.add(userId);
silent.sendMd(format("%s is now *banned*.", escape(bannedUser)), ctx.chatId()); silent.sendMd(getLocalizedMessage("ability.ban.banned", ctx.user().locale(), escape(bannedUser)), ctx.chatId());
} }
}) })
.post(commitTo(db)) .post(commitTo(db))
@ -439,9 +442,9 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
Set<Integer> blacklist = blacklist(); Set<Integer> blacklist = blacklist();
if (!blacklist.remove(userId)) if (!blacklist.remove(userId))
silent.sendMd(format("@%s is *not* on the *blacklist*.", escape(username)), ctx.chatId()); silent.sendMd(getLocalizedMessage("ability.unban.notBanned", ctx.user().locale(), escape(username)), ctx.chatId());
else { else {
silent.sendMd(format("@%s, your ban has been *lifted*.", escape(username)), ctx.chatId()); silent.sendMd(getLocalizedMessage("ability.unban.lifted", ctx.user().locale(), escape(username)), ctx.chatId());
} }
}) })
.post(commitTo(db)) .post(commitTo(db))
@ -463,10 +466,10 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
Set<Integer> admins = admins(); Set<Integer> admins = admins();
if (admins.contains(userId)) if (admins.contains(userId))
silent.sendMd(format("@%s is already an *admin*.", escape(username)), ctx.chatId()); silent.sendMd(getLocalizedMessage("ability.promote.alreadyPromoted", ctx.user().locale(), escape(username)), ctx.chatId());
else { else {
admins.add(userId); admins.add(userId);
silent.sendMd(format("@%s has been *promoted*.", escape(username)), ctx.chatId()); silent.sendMd(getLocalizedMessage("ability.promote.promoted", ctx.user().locale(), escape(username)), ctx.chatId());
} }
}).post(commitTo(db)) }).post(commitTo(db))
.build(); .build();
@ -487,9 +490,9 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
Set<Integer> admins = admins(); Set<Integer> admins = admins();
if (admins.remove(userId)) { if (admins.remove(userId)) {
silent.sendMd(format("@%s has been *demoted*.", escape(username)), ctx.chatId()); silent.sendMd(getLocalizedMessage("ability.demote.demoted", ctx.user().locale(), escape(username)), ctx.chatId());
} else { } else {
silent.sendMd(format("@%s is *not* an *admin*.", escape(username)), ctx.chatId()); silent.sendMd(getLocalizedMessage("ability.demote.alreadyDemoted", ctx.user().locale(), escape(username)), ctx.chatId());
} }
}) })
.post(commitTo(db)) .post(commitTo(db))
@ -514,10 +517,10 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
long chatId = ctx.chatId(); long chatId = ctx.chatId();
if (admins.contains(id)) if (admins.contains(id))
silent.send("You're already my master.", chatId); silent.send(getLocalizedMessage("ability.claim.alreadyClaimed", ctx.user().locale()), chatId);
else { else {
admins.add(id); admins.add(id);
silent.send("You're now my master.", chatId); silent.send(getLocalizedMessage("ability.claim.claimed", ctx.user().locale()), chatId);
} }
} else { } else {
// This is not a joke // This is not a joke
@ -615,7 +618,12 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
boolean isOk = abilityTokens == 0 || (tokens.length > 0 && tokens.length == abilityTokens); boolean isOk = abilityTokens == 0 || (tokens.length > 0 && tokens.length == abilityTokens);
if (!isOk) if (!isOk)
silent.send(format("Sorry, this feature requires %d additional %s.", abilityTokens, abilityTokens == 1 ? "input" : "inputs"), getChatId(trio.a())); silent.send(
getLocalizedMessage(
"checkInput.fail",
AbilityUtils.getUser(trio.a()).getLanguageCode(),
abilityTokens, abilityTokens == 1 ? "input" : "inputs"),
getChatId(trio.a()));
return isOk; return isOk;
} }
@ -627,7 +635,12 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
boolean isOk = abilityLocality == ALL || locality == abilityLocality; boolean isOk = abilityLocality == ALL || locality == abilityLocality;
if (!isOk) if (!isOk)
silent.send(format("Sorry, %s-only feature.", abilityLocality.toString().toLowerCase()), getChatId(trio.a())); silent.send(
getLocalizedMessage(
"checkLocality.fail",
AbilityUtils.getUser(trio.a()).getLanguageCode(),
abilityLocality.toString().toLowerCase()),
getChatId(trio.a()));
return isOk; return isOk;
} }
@ -642,8 +655,11 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
boolean isOk = privacy.compareTo(trio.b().privacy()) >= 0; boolean isOk = privacy.compareTo(trio.b().privacy()) >= 0;
if (!isOk) if (!isOk)
silent.send("Sorry, you don't have the required access level to do that.", getChatId(trio.a())); silent.send(
getLocalizedMessage(
"checkPrivacy.fail",
AbilityUtils.getUser(trio.a()).getLanguageCode()),
getChatId(trio.a()));
return isOk; return isOk;
} }

View File

@ -1,11 +1,14 @@
package org.telegram.abilitybots.api.objects; package org.telegram.abilitybots.api.objects;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import org.telegram.telegrambots.api.objects.User; import org.telegram.telegrambots.api.objects.User;
import java.io.Serializable; import java.io.Serializable;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.StringJoiner; import java.util.StringJoiner;
@ -27,12 +30,15 @@ public final class EndUser implements Serializable {
private final String lastName; private final String lastName;
@JsonProperty("username") @JsonProperty("username")
private final String username; private final String username;
@JsonIgnore
private Locale locale;
private EndUser(Integer id, String firstName, String lastName, String username) { private EndUser(Integer id, String firstName, String lastName, String username, Locale locale) {
this.id = id; this.id = id;
this.firstName = firstName; this.firstName = firstName;
this.lastName = lastName; this.lastName = lastName;
this.username = username; this.username = username;
this.locale = locale != null? locale : Locale.ENGLISH;
} }
@JsonCreator @JsonCreator
@ -40,7 +46,7 @@ public final class EndUser implements Serializable {
@JsonProperty("firstName") String firstName, @JsonProperty("firstName") String firstName,
@JsonProperty("lastName") String lastName, @JsonProperty("lastName") String lastName,
@JsonProperty("username") String username) { @JsonProperty("username") String username) {
return new EndUser(id, firstName, lastName, username); return new EndUser(id, firstName, lastName, username, null);
} }
/** /**
@ -50,7 +56,8 @@ public final class EndUser implements Serializable {
* @return an augmented end-user * @return an augmented end-user
*/ */
public static EndUser fromUser(User user) { public static EndUser fromUser(User user) {
return new EndUser(user.getId(), user.getFirstName(), user.getLastName(), user.getUserName()); Locale locale = Strings.isNullOrEmpty(user.getLanguageCode()) ? null : Locale.forLanguageTag(user.getLanguageCode());
return new EndUser(user.getId(), user.getFirstName(), user.getLastName(), user.getUserName(), locale);
} }
public int id() { public int id() {
@ -69,6 +76,8 @@ public final class EndUser implements Serializable {
return username; return username;
} }
public Locale locale() { return locale; }
/** /**
* The full name is identified as the concatenation of the first and last name, separated by a space. * The full name is identified as the concatenation of the first and last name, separated by a space.
* This method can return an empty name if both first and last name are empty. * This method can return an empty name if both first and last name are empty.
@ -118,12 +127,13 @@ public final class EndUser implements Serializable {
return Objects.equals(id, endUser.id) && return Objects.equals(id, endUser.id) &&
Objects.equals(firstName, endUser.firstName) && Objects.equals(firstName, endUser.firstName) &&
Objects.equals(lastName, endUser.lastName) && Objects.equals(lastName, endUser.lastName) &&
Objects.equals(username, endUser.username); Objects.equals(username, endUser.username) &&
Objects.equals(locale, endUser.locale);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, firstName, lastName, username); return Objects.hash(id, firstName, lastName, username, locale);
} }
@Override @Override
@ -133,6 +143,7 @@ public final class EndUser implements Serializable {
.add("firstName", firstName) .add("firstName", firstName)
.add("lastName", lastName) .add("lastName", lastName)
.add("username", username) .add("username", username)
.add("locale", locale.toString())
.toString(); .toString();
} }
} }

View File

@ -1,10 +1,15 @@
package org.telegram.abilitybots.api.util; package org.telegram.abilitybots.api.util;
import com.google.common.base.Strings;
import org.telegram.abilitybots.api.db.DBContext; import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.MessageContext; import org.telegram.abilitybots.api.objects.MessageContext;
import org.telegram.telegrambots.api.objects.Update; import org.telegram.telegrambots.api.objects.Update;
import org.telegram.telegrambots.api.objects.User; import org.telegram.telegrambots.api.objects.User;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -172,4 +177,25 @@ public final class AbilityUtils {
public static Predicate<Update> isReplyTo(String msg) { public static Predicate<Update> isReplyTo(String msg) {
return update -> update.getMessage().getReplyToMessage().getText().equals(msg); return update -> update.getMessage().getReplyToMessage().getText().equals(msg);
} }
public static String getLocalizedMessage(String messageCode, Locale locale, Object...arguments) {
ResourceBundle bundle;
if(locale == null){
bundle = ResourceBundle.getBundle("default_messages");
}else {
try {
bundle = ResourceBundle.getBundle("messages", locale);
} catch (MissingResourceException e) {
bundle = ResourceBundle.getBundle("default_messages");
}
}
String message = bundle.getString(messageCode);
return MessageFormat.format(message, arguments);
}
public static String getLocalizedMessage(String messageCode, String languageCode, Object...arguments){
Locale locale = Strings.isNullOrEmpty(languageCode) ? null : Locale.forLanguageTag(languageCode);
return getLocalizedMessage(messageCode, locale, arguments);
}
} }

View File

@ -0,0 +1,22 @@
ability.commands.notFound=No public commands found.
ability.recover.fail=Oops, something went wrong during recovery.
ability.recover.error=I have failed to recover.
ability.ban.alreadyBanned={0} is already *banned*.
ability.ban.banned={0} is now *banned*.
ability.unban.notBanned=@{0} is *not* on the *blacklist*.
ability.unban.lifted=@{0}, your ban has been *lifted*.
ability.promote.alreadyPromoted=@{0} is already an *admin*.
ability.promote.promoted=@{0} has been *promoted*.
ability.demote.alreadyDemoted=@{0} is *not* an *admin*.
ability.demote.demoted=@{0} has been *demoted*.
ability.claim.alreadyClaimed=You''re already my master.
ability.claim.claimed=You''re now my master.
checkInput.fail=Sorry, this feature requires {0,number,integer} additional {1}.
checkLocality.fail=Sorry, {0}-only feature.
checkPrivacy.fail=Sorry, you don''t have the required access level to do that.

View File

@ -556,6 +556,7 @@ public class AbilityBotTest {
when(message.hasText()).thenReturn(true); when(message.hasText()).thenReturn(true);
MessageContext context = mock(MessageContext.class); MessageContext context = mock(MessageContext.class);
when(context.chatId()).thenReturn(GROUP_ID); when(context.chatId()).thenReturn(GROUP_ID);
when(context.user()).thenReturn(MUSER);
bot.reportCommands().action().accept(context); bot.reportCommands().action().accept(context);