implement new translation

This commit is contained in:
sk 2023-09-29 18:46:26 +02:00
parent 380e4ff77e
commit c261214e49
10 changed files with 295 additions and 179 deletions

View File

@ -1,11 +1,13 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.TranslatedStatus;
import org.joinmastodon.android.model.Translation;
public class TranslateStatus extends MastodonAPIRequest<TranslatedStatus> {
public TranslateStatus(String id) {
super(HttpMethod.POST, "/statuses/"+id+"/translate", TranslatedStatus.class);
setRequestBody(new Object());
import java.util.Map;
public class TranslateStatus extends MastodonAPIRequest<Translation>{
public TranslateStatus(String id, String lang){
super(HttpMethod.POST, "/statuses/"+id+"/translate", Translation.class);
setRequestBody(Map.of("lang", lang));
}
}

View File

@ -22,6 +22,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account;
@ -29,7 +30,9 @@ import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
@ -55,6 +58,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@ -772,6 +776,61 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
}
public void togglePostTranslation(Status status, String itemID){
switch(status.translationState){
case LOADING -> {
return;
}
case SHOWN -> {
status.translationState=Status.TranslationState.HIDDEN;
}
case HIDDEN -> {
if(status.translation!=null){
status.translationState=Status.TranslationState.SHOWN;
}else{
status.translationState=Status.TranslationState.LOADING;
new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage())
.setCallback(new Callback<>(){
@Override
public void onSuccess(Translation result){
if(getActivity()==null)
return;
status.translation=result;
status.translationState=Status.TranslationState.SHOWN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
status.translationState=Status.TranslationState.HIDDEN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
}
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.translation_failed)
.setPositiveButton(R.string.ok, null)
.show();
}
})
.exec(accountID);
}
}
}
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}
}
public void rebuildAllDisplayItems(){
displayItems.clear();
for(T item:data){

View File

@ -29,6 +29,7 @@ import android.text.TextWatcher;
import android.text.format.DateFormat;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@ -826,6 +827,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
publishButton = wrap.findViewById(R.id.publish_btn);
languageButton = wrap.findViewById(R.id.language_btn);
languageButton.setOnClickListener(v->showLanguageAlert());
languageButton.setOnLongClickListener(v->{
languageButton.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if(!getLocalPrefs().bottomEncoding){
getLocalPrefs().bottomEncoding=true;
getLocalPrefs().save();
}
return false;
});
publishButton.setOnClickListener(v -> {
if(GlobalUserPreferences.altTextReminders && editingStatus==null)

View File

@ -7,6 +7,8 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.github.bottomSoftwareFoundation.bottom.Bottom;
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
@ -15,16 +17,21 @@ import com.google.gson.JsonParseException;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.utils.StatusTextEncoder;
import org.parceler.Parcel;
import java.lang.reflect.Type;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;
@Parcel
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
@ -83,9 +90,9 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public transient boolean sensitiveRevealed;
public transient boolean textExpanded, textExpandable;
public transient boolean hasGapAfter;
public transient TranslatedStatus translation;
public transient boolean translationShown;
private transient String strippedText;
public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation;
public Status(){}
@ -201,6 +208,38 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
return (Status) super.clone();
}
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
public boolean isEligibleForTranslation(AccountSession session){
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
boolean translateEnabled = instanceInfo != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
instanceInfo.v2.configuration.translation.enabled;
try {
String bottomText = BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
if(bottomText==null || bottomText.length()==0 || bottomText.equals("\u0005")) bottomText=null;
if(bottomText!=null){
translation=new Translation();
translation.content=bottomText;
translation.detectedSourceLanguage="\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48";
translation.provider="bottom-java";
return true;
}
} catch (TranslationError ignored) {}
return translateEnabled && !TextUtils.isEmpty(content) && !TextUtils.isEmpty(language)
&& !Objects.equals(Locale.getDefault().getLanguage(), language)
&& (visibility==StatusPrivacy.PUBLIC || visibility==StatusPrivacy.UNLISTED);
}
public enum TranslationState{
HIDDEN,
SHOWN,
LOADING
}
public boolean isReblogPermitted(String accountID){
return visibility.isReblogPermitted(account.id.equals(
AccountSessionManager.getInstance().getAccount(accountID).self.id

View File

@ -1,7 +0,0 @@
package org.joinmastodon.android.model;
public class TranslatedStatus extends BaseModel {
public String content;
public String detectedSourceLanguage;
public String provider;
}

View File

@ -0,0 +1,10 @@
package org.joinmastodon.android.model;
import org.joinmastodon.android.api.AllFieldsAreRequired;
@AllFieldsAreRequired
public class Translation extends BaseModel{
public String content;
public String detectedSourceLanguage;
public String provider;
}

View File

@ -1,71 +1,52 @@
package org.joinmastodon.android.ui.displayitems;
import static org.joinmastodon.android.ui.utils.UiUtils.opacityIn;
import android.app.Activity;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.github.bottomSoftwareFoundation.bottom.Bottom;
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.TranslatedStatus;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.LinkedTextView;
import org.joinmastodon.android.utils.StatusTextEncoder;
import java.util.Locale;
import java.util.regex.Pattern;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.MovieDrawable;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence text;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private CharSequence translatedText;
private CustomEmojiHelper translationEmojiHelper=new CustomEmojiHelper();
public boolean textSelectable;
public boolean reduceTopPadding;
public boolean disableTranslate;
public final Status status;
public boolean disableTranslate, translationShown;
private AccountSession session;
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
super(parentID, parentFragment);
this.text=text;
this.status=status;
this.disableTranslate=disableTranslate;
this.translationShown=status.translationShown;
emojiHelper.setText(text);
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
}
public void setTranslationShown(boolean translationShown) {
this.translationShown = translationShown;
status.translationShown = translationShown;
}
@Override
@ -75,38 +56,47 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public int getImageCount(){
return emojiHelper.getImageCount();
return getCurrentEmojiHelper().getImageCount();
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return emojiHelper.getImageRequest(index);
return getCurrentEmojiHelper().getImageRequest(index);
}
public void setTranslatedText(String text){
Status statusForContent=status.getContentStatus();
translatedText=HtmlParser.parse(text, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, parentFragment.getAccountID());
translationEmojiHelper.setText(translatedText);
}
private CustomEmojiHelper getCurrentEmojiHelper(){
return status.translationState==Status.TranslationState.SHOWN ? translationEmojiHelper : emojiHelper;
}
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
private final LinkedTextView text;
private final TextView translateInfo, readMore;
private final View textWrap, translateWrap, translateProgress;
private final Button translateButton;
private final ScrollView textScrollView;
private final ViewStub translationFooterStub;
private View translationFooter, translationButtonWrap;
private TextView translationInfo;
private Button translationButton;
private ProgressBar translationProgress;
private final float textMaxHeight, textCollapsedHeight;
private final float textMaxHeight;
private final LinearLayout.LayoutParams collapseParams, wrapParams;
private final ViewGroup parent;
private final TextView readMore;
private final ScrollView textScrollView;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent);
this.parent=parent;
text=findViewById(R.id.text);
textWrap = (LinearLayout) itemView;
translateWrap=findViewById(R.id.translate_wrap);
translateButton=findViewById(R.id.translate_btn);
translateInfo=findViewById(R.id.translate_info);
translateProgress=findViewById(R.id.translate_progress);
translationFooterStub=findViewById(R.id.translation_info);
textScrollView=findViewById(R.id.text_scroll_view);
readMore=findViewById(R.id.read_more);
textMaxHeight=activity.getResources().getDimension(R.dimen.text_max_height);
textCollapsedHeight=activity.getResources().getDimension(R.dimen.text_collapsed_height);
float textCollapsedHeight=activity.getResources().getDimension(R.dimen.text_collapsed_height);
collapseParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) textCollapsedHeight);
wrapParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
readMore.setOnClickListener(v -> item.parentFragment.onToggleExpanded(item.status, getItemID()));
@ -114,83 +104,20 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(TextStatusDisplayItem item){
boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText);
text.setText(item.translationShown
? HtmlParser.parse(item.status.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
: item.text);
text.setTextIsSelectable(item.textSelectable);
if (item.textSelectable) {
textScrollView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
if(item.status.translationState==Status.TranslationState.SHOWN){
if(item.translatedText==null){
item.setTranslatedText(item.status.translation.content);
}
text.setText(item.translatedText);
}else{
text.setText(item.text);
}
text.setTextIsSelectable(item.textSelectable);
text.setInvalidateOnEveryFrame(false);
itemView.setClickable(false);
itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom());
text.setTextColor(UiUtils.getThemeColor(text.getContext(), item.inset ? R.attr.colorM3OnSurfaceVariant : R.attr.colorM3OnSurface));
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(item.session.domain);
boolean translateEnabled = !item.disableTranslate && instanceInfo != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
instanceInfo.v2.configuration.translation.enabled;
String bottomText = null;
try {
bottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
} catch (TranslationError ignored) {}
boolean translateVisible = (bottomText != null || (
translateEnabled &&
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
item.status.language != null &&
// todo: compare to mastodon locale instead (how do i query that?!)
!item.status.language.equalsIgnoreCase(Locale.getDefault().getLanguage())))
&& (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
translateWrap.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
translateButton.setText(item.translationShown ? R.string.sk_translate_show_original : R.string.sk_translate_post);
translateInfo.setText(item.translationShown ? itemView.getResources().getString(R.string.sk_translated_using, bottomText != null ? "bottom-java" : item.status.translation.provider) : "");
String finalBottomText = bottomText;
translateButton.setOnClickListener(v->{
if (item.status.translation == null) {
if (finalBottomText != null) {
try {
item.status.translation = new TranslatedStatus();
item.status.translation.content = finalBottomText;
item.setTranslationShown(true);
} catch (TranslationError err) {
item.status.translation = null;
Toast.makeText(itemView.getContext(), err.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
rebind();
return;
}
translateProgress.setVisibility(View.VISIBLE);
translateButton.setClickable(false);
translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
rebind();
}
@Override
public void onError(ErrorResponse error) {
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
error.showToast(itemView.getContext());
}
}).exec(item.parentFragment.getAccountID());
} else {
item.setTranslationShown(!item.translationShown);
rebind();
}
});
updateTranslation(false);
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
@ -224,18 +151,19 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
boolean tooBig = text.getMeasuredHeight() > textMaxHeight;
boolean expandable = tooBig && !hasSpoiler;
boolean expandable = tooBig && !item.status.hasSpoiler();
item.parentFragment.onEnableExpandable(Holder.this, expandable);
}
boolean expandButtonShown=item.status.textExpandable && !item.status.textExpanded;
translateWrap.setPadding(0, V.dp(expandButtonShown ? 0 : 4), 0, 0);
if(translationFooter!=null)
translationFooter.setPadding(0, V.dp(expandButtonShown ? 0 : 4), 0, 0);
readMore.setVisibility(expandButtonShown ? View.VISIBLE : View.GONE);
textScrollView.setLayoutParams(item.status.textExpandable && !item.status.textExpanded ? collapseParams : wrapParams);
// compensate for spoiler's bottom margin
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
params.setMargins(params.leftMargin, item.inset && hasSpoiler ? V.dp(-16) : 0,
params.setMargins(params.leftMargin, item.inset && item.status.hasSpoiler() ? V.dp(-16) : 0,
params.rightMargin, params.bottomMargin);
}
@ -259,5 +187,58 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private CustomEmojiHelper getEmojiHelper(){
return item.emojiHelper;
}
public void updateTranslation(boolean updateText){
if(item.status==null)
return;
boolean translateEnabled=!item.disableTranslate && item.status.isEligibleForTranslation(item.parentFragment.getSession());
if(translationFooter==null && translateEnabled){
translationFooter=translationFooterStub.inflate();
translationInfo=findViewById(R.id.translation_info_text);
translationButton=findViewById(R.id.translation_btn);
translationButtonWrap=findViewById(R.id.translation_btn_wrap);
translationProgress=findViewById(R.id.translation_progress);
translationButton.setOnClickListener(v->item.parentFragment.togglePostTranslation(item.status, item.parentID));
}
if(item.status.translationState==Status.TranslationState.HIDDEN){
if(updateText) text.setText(item.text);
if(translationFooter==null) return;
translationFooter.setVisibility(translateEnabled ? View.VISIBLE : View.GONE);
translationProgress.setVisibility(View.GONE);
Translation existingTrans=item.status.getContentStatus().translation;
String lang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null;
String displayLang=Locale.forLanguageTag(lang!=null ? lang : item.status.getContentStatus().language).getDisplayLanguage();
translationButton.setText(item.parentFragment.getString(R.string.translate_post, !displayLang.isBlank() ? displayLang : lang));
translationButton.setEnabled(true);
translationButton.setAlpha(1);
translationInfo.setVisibility(View.GONE);
UiUtils.beginLayoutTransition((ViewGroup) translationButtonWrap);
}else{
translationFooter.setVisibility(View.VISIBLE);
if(item.status.translationState==Status.TranslationState.SHOWN){
translationProgress.setVisibility(View.GONE);
translationButton.setText(R.string.translation_show_original);
translationButton.setEnabled(true);
translationButton.setAlpha(1);
translationInfo.setVisibility(View.VISIBLE);
translationButton.setVisibility(View.VISIBLE);
String displayLang=Locale.forLanguageTag(item.status.translation.detectedSourceLanguage).getDisplayLanguage();
translationInfo.setText(translationInfo.getContext().getString(R.string.post_translated, !displayLang.isBlank() ? displayLang : item.status.translation.detectedSourceLanguage, item.status.translation.provider));
UiUtils.beginLayoutTransition((ViewGroup) translationButtonWrap);
if(updateText){
if(item.translatedText==null){
item.setTranslatedText(item.status.translation.content);
}
text.setText(item.translatedText);
}
}else{ // LOADING
translationProgress.setVisibility(View.VISIBLE);
translationButton.setEnabled(false);
translationButton.startAnimation(opacityIn);
translationInfo.setVisibility(View.INVISIBLE);
UiUtils.beginLayoutTransition((ViewGroup) translationButton.getParent());
}
}
}
}
}

View File

@ -45,56 +45,10 @@
android:visibility="gone"
android:importantForAccessibility="no"/>
<org.joinmastodon.android.ui.views.AutoOrientationLinearLayout
android:id="@+id/translate_wrap"
<ViewStub
android:id="@+id/translation_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:gravity="center_vertical"
android:visibility="gone">
<FrameLayout
android:id="@+id/action_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:clipToPadding="false">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/translate_btn"
style="@style/Widget.Mastodon.M3.Button.Text"
android:textColor="?colorM3OnSurfaceVariant"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:minWidth="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/sk_translate_post"/>
<ProgressBar
android:id="@+id/translate_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
style="?android:progressBarStyleSmall"
android:elevation="10dp"
android:outlineProvider="none"
android:visibility="gone"/>
</FrameLayout>
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/translate_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:textColor="?colorM3OnSurfaceVariant"
android:textAlignment="textEnd"
tools:text="Translated using TranslateEngine" />
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
android:layout="@layout/footer_text_translation"/>
</LinearLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:gravity="center_vertical">
<FrameLayout
android:id="@+id/translation_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:clipToPadding="false">
<ProgressBar
android:id="@+id/translation_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:layout_marginEnd="8dp"
android:indeterminate="true"
style="?android:progressBarStyleSmall"
android:visibility="gone"/>
<Button
android:id="@+id/translation_btn"
style="@style/Widget.Mastodon.M3.Button.Text"
android:textColor="?colorM3OnSurfaceVariant"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/translation_show_original"/>
</FrameLayout>
<TextView
android:id="@+id/translation_info_text"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:maxLines="1"
android:ellipsize="end"
android:textAppearance="@style/m3_body_small"
android:textColor="?colorM3OnSurfaceVariant"
android:textAlignment="textEnd"
tools:text="Translated using TranslateEngine" />
</LinearLayout>

View File

@ -589,4 +589,23 @@
<string name="time_minutes_ago_short">%dm ago</string>
<string name="time_hours_ago_short">%dh ago</string>
<string name="time_days_ago_short">%dd ago</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Translate from %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Translated from %1$s using %2$s</string>
<string name="translation_show_original">Show original</string>
<string name="translation_failed">Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.</string>
<string name="settings_privacy">Privacy and reach</string>
<string name="settings_discoverable">Feature profile and posts in discovery algorithms</string>
<string name="settings_indexable">Include public posts in search results</string>
<plurals name="x_participants">
<item quantity="one">%,d participant</item>
<item quantity="other">%,d participants</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d post today</item>
<item quantity="other">%,d posts today</item>
</plurals>
</resources>