/* * Copyright (C) 2015 Actor LLC. <https://actor.im> */ package im.actor.core.i18n; import com.google.j2objc.annotations.ObjectiveCName; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.List; import im.actor.core.entity.ContentType; import im.actor.core.entity.Dialog; import im.actor.core.entity.Message; import im.actor.core.entity.Notification; import im.actor.core.entity.PeerType; import im.actor.core.entity.Sex; import im.actor.core.entity.User; import im.actor.core.entity.content.ServiceCallEnded; import im.actor.core.entity.content.ServiceCallMissed; import im.actor.core.entity.content.ServiceContent; import im.actor.core.entity.content.ServiceGroupAvatarChanged; import im.actor.core.entity.content.ServiceGroupCreated; import im.actor.core.entity.content.ServiceGroupTitleChanged; import im.actor.core.entity.content.ServiceGroupTopicChanged; import im.actor.core.entity.content.ServiceGroupAboutChanged; import im.actor.core.entity.content.ServiceGroupUserInvited; import im.actor.core.entity.content.ServiceGroupUserJoined; import im.actor.core.entity.content.ServiceGroupUserKicked; import im.actor.core.entity.content.ServiceGroupUserLeave; import im.actor.core.entity.content.ServiceUserRegistered; import im.actor.core.entity.content.TextContent; import im.actor.core.modules.Modules; import im.actor.core.network.RpcException; import im.actor.core.util.JavaUtil; import im.actor.core.viewmodel.UserPresence; import im.actor.runtime.Assets; import im.actor.runtime.Runtime; import im.actor.runtime.intl.IntlEngine; import im.actor.runtime.json.JSONException; public class I18nEngine extends IntlEngine { private static final String TAG = "I18nEngine"; private static final String[] SUPPORTED_LOCALES = new String[]{"Ru", "Ar", "Zn", "Pt", "Es", "Fa"}; private static final String[] FEMALE = new String[]{"female", "other"}; private static final String[] MALE = new String[]{"male", "other"}; private static final String[] DEFAULT = new String[]{"other"}; private static final String[] YOU = new String[]{"you"}; public static I18nEngine create(Modules modules) { String currentLocale = Runtime.getLocaleRuntime().getCurrentLocale(); if (currentLocale != null) { if (JavaUtil.contains(SUPPORTED_LOCALES, currentLocale)) { try { return new I18nEngine( modules, Assets.loadAsset("AppText_" + currentLocale + ".json"), Assets.loadAsset("AppText.json")); } catch (JSONException e) { e.printStackTrace(); } } } try { return new I18nEngine( modules, Assets.loadAsset("AppText.json"), null); } catch (JSONException e) { // Shoud not happen throw new RuntimeException(e); } } private final Modules modules; @ObjectiveCName("initWithModules:withLocalization:withFallback:") public I18nEngine(Modules modules, String localization, String fallback) throws JSONException { super(localization, fallback); this.modules = modules; } @Override public String getAppName() { String appName = modules.getConfiguration().getCustomAppName(); if (appName != null) { return appName; } return super.getAppName(); } // // Typing // @ObjectiveCName("formatTyping") public String formatTyping() { return get("typing.simple"); } @ObjectiveCName("formatTypingWithName:") public String formatTyping(String name) { return get("typing.user") .replace("{user}", name); } @ObjectiveCName("formatTypingWithNames:") public String formatTyping(List<String> names) { if (names.size() == 1) { return formatTyping(names.get(0)); } return get("typing.group.sequenced") .replace("{users}", formatSequence(names)); } @ObjectiveCName("formatTypingWithCount:") public String formatTyping(int count) { return get("typing.group.many") .replace("{count}", "" + count); } // // Presence // @ObjectiveCName("formatPresence:withSex:") public String formatPresence(UserPresence value, Sex sex) { if (value == null) { return null; } String[] sexType = DEFAULT; if (sex == Sex.MALE) { sexType = MALE; } else if (sex == Sex.FEMALE) { sexType = FEMALE; } if (value.getState() == UserPresence.State.OFFLINE) { long currentTime = im.actor.runtime.Runtime.getCurrentSyncedTime() / 1000L; int delta = (int) (currentTime - value.getLastSeen()); if (delta < 60) { return get("presence.now", sexType); } else if (delta < 24 * 60 * 60) { String time = formatTime(value.getLastSeen() * 1000L); if (areSameDays(value.getLastSeen() * 1000L, new Date().getTime())) { return get("presence.today", sexType) .replace("{time}", time); } else { return get("presence.yesterday", sexType) .replace("{time}", time); } } else if (delta < 14 * 24 * 60 * 60) { String time = formatTime(value.getLastSeen() * 1000L); String date = formatDate(value.getLastSeen() * 1000L); return get("presence.at_day_time", sexType) .replace("{time}", time) .replace("{date}", date); } else if (delta < 6 * 30 * 24 * 60 * 60) { String date = formatDate(value.getLastSeen() * 1000L); return get("presence.at_day", sexType) .replace("{date}", date); } else { return get("presence.offline", sexType); } } else if (value.getState() == UserPresence.State.ONLINE) { return get("presence.online", sexType); } return null; } @ObjectiveCName("formatGroupOnline:") public String formatGroupOnline(int count) { return getPlural("presence.members", count) .replace("{count}", "" + count); } // // Group // /** * Formatting Group Members counter * * @param count number of members * @return formatted string like "12 members" */ @ObjectiveCName("formatGroupMembers:") public String formatGroupMembers(int count) { return getPlural("groups.members", count) .replace("{count}", "" + count); } // // Content // /** * Formatting Dialog List text. Deprecated: you need to manually format Content and append * performer if needed and highlight it * * @param dialog dialog to format * @return formatted content */ @Deprecated @ObjectiveCName("formatDialogText:") public String formatDialogText(Dialog dialog) { // Detecting if dialog is empty if (dialog.getSenderId() == 0) { return ""; } else { String contentText = formatContentText(dialog.getSenderId(), dialog.getMessageType(), dialog.getText(), dialog.getRelatedUid(), dialog.isChannel()); if (dialog.getPeer().getPeerType() == PeerType.GROUP && !dialog.isChannel()) { if (!isLargeDialogMessage(dialog.getMessageType())) { return formatPerformerName(dialog.getSenderId()) + ": " + contentText; } else { return contentText; } } else { return contentText; } } } /** * If Dialog List message need to be wide in group chat as it is already includes performer * in it's body. * * @param contentType Type of Content * @return true if content is wide */ @ObjectiveCName("isLargeDialogMessage:") public boolean isLargeDialogMessage(ContentType contentType) { switch (contentType) { case SERVICE: case SERVICE_AVATAR: case SERVICE_AVATAR_REMOVED: case SERVICE_CREATED: case SERVICE_TITLE: case SERVICE_LEAVE: case SERVICE_REGISTERED: case SERVICE_KICK: case SERVICE_ADD: case SERVICE_JOINED: case SERVICE_CALL_ENDED: case SERVICE_CALL_MISSED: case SERVICE_ABOUT: case SERVICE_TOPIC: return true; default: return false; } } /** * Formatting Pending notification text * * @param pendingNotification pending notification * @return formatted notification */ @ObjectiveCName("formatNotificationText:") public String formatNotificationText(Notification pendingNotification) { return formatContentText(pendingNotification.getSender(), pendingNotification.getContentDescription().getContentType(), pendingNotification.getContentDescription().getText(), pendingNotification.getContentDescription().getRelatedUser(), pendingNotification.isChannel()); } /** * Formatting content for Dialog List and Notifications * * @param senderId sender of message (used in service messages) * @param contentType type of content * @param text text of message * @param relatedUid optional related uid * @return formatted content */ @ObjectiveCName("formatContentTextWithSenderId:withContentType:withText:withRelatedUid:withIsChannel:") public String formatContentText(int senderId, ContentType contentType, String text, int relatedUid, boolean isChannel) { String groupKey = isChannel ? "channels" : "groups"; switch (contentType) { case TEXT: return text; case DOCUMENT: if (text == null || text.length() == 0) { return get("content.document"); } return text;// File name case DOCUMENT_PHOTO: return get("content.photo"); case DOCUMENT_VIDEO: return get("content.video"); case DOCUMENT_AUDIO: return get("content.audio"); case CONTACT: return get("content.contact"); case LOCATION: return get("content.location"); case STICKER: if (text != null && !"".equals(text)) { return text + " " + get("content.sticker"); } else { return get("content.sticker"); } case SERVICE: return text;// Should be service message case SERVICE_REGISTERED: return getTemplateNamed(senderId, "content.service.registered.compact") .replace("{app_name}", getAppName()); case SERVICE_CREATED: return getTemplateNamed(senderId, "content.service." + groupKey + ".created"); case SERVICE_ADD: return getTemplateNamed(senderId, "content.service." + groupKey + ".invited") .replace("{name_added}", getSubjectName(relatedUid)); case SERVICE_LEAVE: return getTemplateNamed(senderId, "content.service." + groupKey + ".left"); case SERVICE_KICK: return getTemplateNamed(senderId, "content.service." + groupKey + ".kicked") .replace("{name_kicked}", getSubjectName(relatedUid)); case SERVICE_AVATAR: return getTemplateNamed(senderId, "content.service." + groupKey + ".avatar_changed"); case SERVICE_AVATAR_REMOVED: return getTemplateNamed(senderId, "content.service." + groupKey + ".avatar_removed"); case SERVICE_TITLE: return getTemplateNamed(senderId, "content.service." + groupKey + ".title_changed.compact"); case SERVICE_TOPIC: return getTemplateNamed(senderId, "content.service." + groupKey + ".topic_changed.compact"); case SERVICE_ABOUT: return getTemplateNamed(senderId, "content.service." + groupKey + ".about_changed.compact"); case SERVICE_JOINED: return getTemplateNamed(senderId, "content.service." + groupKey + ".joined"); case SERVICE_CALL_ENDED: return get("content.service.calls.ended"); case SERVICE_CALL_MISSED: return get("content.service.calls.missed"); case NONE: return ""; default: case UNKNOWN_CONTENT: return get("content.unsupported"); } } /** * Formatting Service Content * * @param senderId sender of a message * @param content content of a message * @return formatted message */ @ObjectiveCName("formatFullServiceMessageWithSenderId:withContent:withIsChannel:") public String formatFullServiceMessage(int senderId, ServiceContent content, boolean isChannel) { String groupKey = isChannel ? "channels" : "groups"; if (content instanceof ServiceUserRegistered) { return getTemplateNamed(senderId, "content.service.registered.full") .replace("{app_name}", getAppName()); } else if (content instanceof ServiceGroupCreated) { return getTemplateNamed(senderId, "content.service." + groupKey + ".created"); } else if (content instanceof ServiceGroupUserInvited) { return getTemplateNamed(senderId, "content.service." + groupKey + ".invited") .replace("{name_added}", getSubjectName(((ServiceGroupUserInvited) content).getAddedUid())); } else if (content instanceof ServiceGroupUserKicked) { return getTemplateNamed(senderId, "content.service." + groupKey + ".kicked") .replace("{name_kicked}", getSubjectName(((ServiceGroupUserKicked) content).getKickedUid())); } else if (content instanceof ServiceGroupUserLeave) { return getTemplateNamed(senderId, "content.service." + groupKey + ".left"); } else if (content instanceof ServiceGroupTitleChanged) { return getTemplateNamed(senderId, "content.service." + groupKey + ".title_changed.full") .replace("{title}", ((ServiceGroupTitleChanged) content).getNewTitle()); } else if (content instanceof ServiceGroupTopicChanged) { return getTemplateNamed(senderId, "content.service." + groupKey + ".topic_changed.full") .replace("{topic}", ((ServiceGroupTopicChanged) content).getNewTopic()); } else if (content instanceof ServiceGroupAboutChanged) { return getTemplateNamed(senderId, "content.service." + groupKey + ".about_changed.full") .replace("{about}", ((ServiceGroupAboutChanged) content).getNewAbout()); } else if (content instanceof ServiceGroupAvatarChanged) { if (((ServiceGroupAvatarChanged) content).getNewAvatar() != null) { return getTemplateNamed(senderId, "content.service." + groupKey + ".avatar_changed"); } else { return getTemplateNamed(senderId, "content.service." + groupKey + ".avatar_removed"); } } else if (content instanceof ServiceGroupUserJoined) { return getTemplateNamed(senderId, "content.service." + groupKey + ".joined"); } else if (content instanceof ServiceCallEnded) { return get("content.service.calls.ended"); } else if (content instanceof ServiceCallMissed) { return get("content.service.calls.missed"); } return content.getCompatText(); } // // Formatting errors // @ObjectiveCName("formatErrorTextWithTag:") public String formatErrorText(String tag) { return get(Errors.mapError(tag)); } @ObjectiveCName("formatErrorTextWithError:") public String formatErrorText(Object o) { if (o instanceof RpcException) { RpcException e = (RpcException) o; String res = Errors.mapError(e.getTag(), null); if (res != null) { return get(res); } else { if (e.getMessage().equals("")) { return e.getTag(); } else { return e.getMessage(); } } } else if (o instanceof Exception) { return ((Exception) o).getMessage(); } else { return "" + o; } } // // Exporting messages // /** * Formatting messages for exporting * * @param messages messages to export * @return formatted text */ @ObjectiveCName("formatMessagesExport:") public String formatMessagesExport(Message[] messages) { String text = ""; Arrays.sort(messages, new Comparator<Message>() { int compare(long lhs, long rhs) { return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1); } @Override public int compare(Message lhs, Message rhs) { return compare(lhs.getEngineSort(), rhs.getEngineSort()); } }); if (messages.length == 1) { for (Message model : messages) { if (!(model.getContent() instanceof TextContent)) { continue; } text += ((TextContent) model.getContent()).getText(); } } else { for (Message model : messages) { if (!(model.getContent() instanceof TextContent)) { continue; } if (text.length() > 0) { text += "\n"; } text += getUser(model.getSenderId()).getName() + ": "; text += ((TextContent) model.getContent()).getText(); } } return text; } // // Tools // private String getTemplateNamed(int senderId, String baseString) { String newString = getTemplate(senderId, baseString) .replace("{name}", formatPerformerName(senderId)); // term with 'you' in persian language should be appended with suffix if (getLocaleName().equals("Fa")) { if (senderId == modules.getAuthModule().myUid()) { newString += "ید"; } } return newString; } private String getTemplate(int senderId, String baseString) { if (senderId == modules.getAuthModule().myUid()) { return get(baseString, YOU); } User u = getUser(senderId); String[] sexType = DEFAULT; if (u.getSex() == Sex.MALE) { sexType = MALE; } else if (u.getSex() == Sex.FEMALE) { sexType = FEMALE; } return get(baseString, sexType); } @ObjectiveCName("formatPerformerNameWithUid:") public String formatPerformerName(int uid) { if (uid == modules.getAuthModule().myUid()) { return getYouVerb(); } else { return getUser(uid).getName(); } } @ObjectiveCName("getSubjectNameWithUid:") public String getSubjectName(int uid) { if (uid == modules.getAuthModule().myUid()) { return getTheeVerb(); } else { User user = getUser(uid); return user != null ? user.getName() : ""; } } private User getUser(int uid) { return modules.getUsersModule().getUsersStorage().getValue(uid); } }