Merge remote-tracking branch 'upstream/master' into try-to-merge-upstream

This commit is contained in:
sk 2023-09-29 20:59:12 +02:00
commit 4ee229ea79
99 changed files with 1804 additions and 177 deletions

View File

@ -16,7 +16,7 @@ android {
minSdk 23
targetSdk 33
versionCode 99
versionName "2.0.3+fork.99"
versionName "2.1.4+fork.99"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
}

View File

@ -143,7 +143,7 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
}
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
new GetSearchResults(q, null, true)
new GetSearchResults(q, null, true, null, 0, 0)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){

View File

@ -176,6 +176,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
.map(type->{
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
channel.setLightColor(context.getColor(R.color.primary_700));
channel.enableLights(true);
channel.setGroup(accountID);
return channel;
})
@ -205,6 +207,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
.setShowWhen(true)
.setCategory(Notification.CATEGORY_SOCIAL)
.setAutoCancel(true)
.setLights(context.getColor(android.R.attr.colorAccent), 500, 1000)
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
if (!GlobalUserPreferences.uniformNotificationIcon) {

View File

@ -6,18 +6,19 @@ import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.StatusPrivacy;
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable){
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable, Boolean indexable){
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
setRequestBody(new Request(locked, discoverable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
setRequestBody(new Request(locked, discoverable, indexable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
}
private static class Request{
public Boolean locked, discoverable;
public Boolean locked, discoverable, indexable;
public RequestSource source;
public Request(Boolean locked, Boolean discoverable, RequestSource source){
public Request(Boolean locked, Boolean discoverable, Boolean indexable, RequestSource source){
this.locked=locked;
this.discoverable=discoverable;
this.indexable=indexable;
this.source=source;
}
}

View File

@ -6,6 +6,9 @@ import org.joinmastodon.android.model.FilterContext;
import java.util.EnumSet;
import java.util.List;
import androidx.annotation.Keep;
@Keep
class FilterRequest{
public String title;
public EnumSet<FilterContext> context;

View File

@ -4,13 +4,19 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.SearchResults;
public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
public GetSearchResults(String query, Type type, boolean resolve){
public GetSearchResults(String query, Type type, boolean resolve, String maxID, int offset, int count){
super(HttpMethod.GET, "/search", SearchResults.class);
addQueryParameter("q", query);
if(type!=null)
addQueryParameter("type", type.name().toLowerCase());
if(resolve)
addQueryParameter("resolve", "true");
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(offset>0)
addQueryParameter("offset", String.valueOf(offset));
if(count>0)
addQueryParameter("limit", String.valueOf(count));
}
public GetSearchResults limit(int limit){

View File

@ -0,0 +1,10 @@
package org.joinmastodon.android.api.requests.tags;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
public class GetTag extends MastodonAPIRequest<Hashtag>{
public GetTag(String tag){
super(HttpMethod.GET, "/tags/"+tag, Hashtag.class);
}
}

View File

@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.tags;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
public class SetTagFollowed extends MastodonAPIRequest<Hashtag>{
public SetTagFollowed(String tag, boolean followed){
super(HttpMethod.POST, "/tags/"+tag+(followed ? "/follow" : "/unfollow"), Hashtag.class);
setRequestBody(new Object());
}
}

View File

@ -219,7 +219,7 @@ public class AccountSession{
public void savePreferencesIfPending(){
if(preferencesNeedSaving){
new UpdateAccountCredentialsPreferences(preferences, null, null)
new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
@ -303,6 +303,10 @@ public class AccountSession{
});
}
public void updateAccountInfo(){
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
}
public Optional<Instance> getInstance() {
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
}

View File

@ -303,8 +303,7 @@ public class AccountSessionManager{
}
}
private void updateSessionLocalInfo(AccountSession session){
/*package*/ void updateSessionLocalInfo(AccountSession session){
new GetOwnAccount()
.setCallback(new Callback<>(){
@Override

View File

@ -1317,7 +1317,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
if(usePhotoPicker){
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1)
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
}else{
intent=new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);

View File

@ -45,8 +45,8 @@ public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag
}
@Override
public void onItemClick(String hashtag){
UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag, data.stream().filter(h -> Objects.equals(h.name, hashtag)).findAny().map(h -> h.following).orElse(null));
public void onItemClick(String id){
UiUtils.openHashtagTimeline(getActivity(), accountID, Objects.requireNonNull(findItemOfType(id, HashtagStatusDisplayItem.class)).tag);
}
@Override

View File

@ -121,7 +121,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
@Override
public void onClick() {
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name);
}
}
}

View File

@ -1,46 +1,64 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.view.HapticFeedbackConstants;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.tags.GetHashtag;
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.requests.tags.GetTag;
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
import org.joinmastodon.android.events.HashtagUpdatedEvent;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.text.SpacerSpan;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.joinmastodon.android.ui.views.ProgressBarButton;
import org.parceler.Parcels;
import java.util.List;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
public class HashtagTimelineFragment extends PinnableStatusListFragment {
private String hashtag;
public class HashtagTimelineFragment extends PinnableStatusListFragment{
private Hashtag hashtag;
private String hashtagName;
private TextView headerTitle, headerSubtitle;
private ProgressBarButton followButton;
private ProgressBar followProgress;
private MenuItem followMenuItem, pinMenuItem;
private boolean followRequestRunning;
private boolean toolbarContentVisible;
private List<String> any;
private List<String> all;
private List<String> none;
private boolean following;
private boolean localOnly;
private MenuItem followButton;
private Menu optionsMenu;
private MenuInflater optionsMenuInflater;
@Override
protected boolean wantsComposeButton() {
@ -50,73 +68,19 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
updateTitle(getArguments().getString("hashtag"));
following=getArguments().getBoolean("following", false);
localOnly=getArguments().getBoolean("localOnly", false);
any=getArguments().getStringArrayList("any");
all=getArguments().getStringArrayList("all");
none=getArguments().getStringArrayList("none");
setHasOptionsMenu(true);
}
private void updateTitle(String hashtagName) {
hashtag = hashtagName;
setTitle('#'+hashtag);
}
private void updateFollowingState(boolean newFollowing) {
this.following = newFollowing;
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
E.post(new HashtagUpdatedEvent(hashtag, following));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.hashtag_timeline, menu);
super.onCreateOptionsMenu(menu, inflater);
followButton = menu.findItem(R.id.follow_hashtag);
updateFollowingState(following);
new GetHashtag(hashtag).setCallback(new Callback<>() {
@Override
public void onSuccess(Hashtag hashtag) {
if (getActivity() == null) return;
updateTitle(hashtag.name);
updateFollowingState(hashtag.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.follow_hashtag) {
updateFollowingState(!following);
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
@Override
public void onSuccess(Hashtag i) {
if (getActivity() == null) return;
if (i.following == following) Toast.makeText(getActivity(), getString(i.following ? R.string.followed_user : R.string.unfollowed_user, "#" + i.name), Toast.LENGTH_SHORT).show();
updateFollowingState(i.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
updateFollowingState(!following);
}
}).exec(accountID);
return true;
if(getArguments().containsKey("hashtag")){
hashtag=Parcels.unwrap(getArguments().getParcelable("hashtag"));
hashtagName=hashtag.name;
}else{
hashtagName=getArguments().getString("hashtagName");
}
return false;
setTitle('#'+hashtagName);
setHasOptionsMenu(true);
}
@Override
@ -126,12 +90,10 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetHashtagTimeline(hashtagName, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if (getActivity() == null) return;
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty());
}
})
@ -146,15 +108,40 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
}
@Override
public boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
public void loadData(){
reloadTag();
super.loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
if(getParentFragment() instanceof HomeTabFragment) return;
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
View topChild=recyclerView.getChildAt(0);
int firstChildPos=recyclerView.getChildAdapterPosition(topChild);
float newAlpha=firstChildPos>0 ? 1f : Math.min(1f, -topChild.getTop()/(float)headerTitle.getHeight());
toolbarTitleView.setAlpha(newAlpha);
boolean newToolbarVisibility=newAlpha>0.5f;
if(newToolbarVisibility!=toolbarContentVisible){
toolbarContentVisible=newToolbarVisibility;
createOptionsMenu();
}
}
});
}
@Override
public void onFabClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("prefilledText", '#'+hashtag+' ');
args.putString("prefilledText", '#'+hashtagName+' ');
Nav.go(getActivity(), ComposeFragment.class, args);
}
@ -170,6 +157,203 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
@Override
public Uri getWebUri(Uri.Builder base) {
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build();
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags/") + hashtag).build();
}
@Override
protected RecyclerView.Adapter getAdapter(){
View header=getActivity().getLayoutInflater().inflate(R.layout.header_hashtag_timeline, list, false);
headerTitle=header.findViewById(R.id.title);
headerSubtitle=header.findViewById(R.id.subtitle);
followButton=header.findViewById(R.id.profile_action_btn);
followProgress=header.findViewById(R.id.action_progress);
headerTitle.setText("#"+hashtagName);
followButton.setVisibility(View.GONE);
followButton.setOnClickListener(v->{
if(hashtag==null)
return;
setFollowed(!hashtag.following);
});
followButton.setOnLongClickListener(v->{
if(hashtag==null) return false;
UiUtils.pickAccount(getActivity(), accountID, R.string.sk_follow_as, R.drawable.ic_fluent_person_add_28_regular, session -> {
new SetTagFollowed(hashtagName, true).setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag hashtag) {
Toast.makeText(
getActivity(),
getString(R.string.sk_followed_as, session.self.getShortUsername()),
Toast.LENGTH_SHORT
).show();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(session.getID());
}, null);
return true;
});
updateHeader();
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
if(!(getParentFragment() instanceof HomeTabFragment)){
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(header));
}
mergeAdapter.addAdapter(super.getAdapter());
return mergeAdapter;
}
@Override
protected int getMainAdapterOffset(){
return 1;
}
private void createOptionsMenu(){
optionsMenu.clear();
optionsMenuInflater.inflate(R.menu.hashtag_timeline, optionsMenu);
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
pinMenuItem=optionsMenu.findItem(R.id.pin);
followMenuItem.setVisible(toolbarContentVisible);
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
if(toolbarContentVisible){
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu);
}else{
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.pin);
}
}
@Override
public void updatePinButton(MenuItem pin){
super.updatePinButton(pin);
UiUtils.insetPopupMenuIcon(getContext(), pin);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.hashtag_timeline, menu);
super.onCreateOptionsMenu(menu, inflater);
optionsMenu=menu;
optionsMenuInflater=inflater;
createOptionsMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.follow_hashtag && hashtag!=null) {
setFollowed(!hashtag.following);
}
return true;
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
toolbarTitleView.setAlpha(toolbarContentVisible ? 1f : 0f);
createOptionsMenu();
}
private void updateHeader(){
if(hashtag==null)
return;
if(hashtag.history!=null && !hashtag.history.isEmpty()){
int weekPosts=hashtag.history.stream().mapToInt(h->h.uses).sum();
int todayPosts=hashtag.history.get(0).uses;
int numAccounts=hashtag.history.stream().mapToInt(h->h.accounts).sum();
int hSpace=V.dp(8);
SpannableStringBuilder ssb=new SpannableStringBuilder();
ssb.append(getResources().getQuantityString(R.plurals.x_posts, weekPosts, weekPosts));
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append('·');
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append(getResources().getQuantityString(R.plurals.x_participants, numAccounts, numAccounts));
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append('·');
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append(getResources().getQuantityString(R.plurals.x_posts_today, todayPosts, todayPosts));
headerSubtitle.setText(ssb);
}
int styleRes;
followButton.setVisibility(View.VISIBLE);
if(hashtag.following){
followButton.setText(R.string.button_following);
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal;
}else{
followButton.setText(R.string.button_follow);
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
}
TypedArray ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
followButton.setBackground(ta.getDrawable(0));
ta.recycle();
ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
followButton.setTextColor(ta.getColorStateList(0));
followProgress.setIndeterminateTintList(ta.getColorStateList(0));
ta.recycle();
followButton.setTextVisible(true);
followProgress.setVisibility(View.GONE);
if(followMenuItem!=null){
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), followMenuItem);
}
}
private void reloadTag(){
new GetTag(hashtagName)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag result){
hashtag=result;
updateHeader();
}
@Override
public void onError(ErrorResponse error){
}
})
.exec(accountID);
}
private void setFollowed(boolean followed){
if(followRequestRunning)
return;
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
followButton.setTextVisible(false);
followProgress.setVisibility(View.VISIBLE);
followRequestRunning=true;
new SetTagFollowed(hashtagName, followed)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag result){
if(getActivity()==null)
return;
hashtag=result;
updateHeader();
followRequestRunning=false;
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
if(error instanceof MastodonErrorResponse er && "Duplicate record".equals(er.error)){
hashtag.following=true;
}else{
error.showToast(getActivity());
}
updateHeader();
followRequestRunning=false;
}
})
.exec(accountID);
}
}

View File

@ -184,6 +184,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
});
}
}
tabBar.selectTab(currentTab);
return content;
}

View File

@ -524,9 +524,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) {
args.putString("hashtag", hashtag.name);
args.putBoolean("following", hashtag.following);
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
UiUtils.openHashtagTimeline(getContext(), accountID, hashtag);
}
return true;
}

View File

@ -304,6 +304,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
tabbar.setTabTextSize(V.dp(14));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, (tab, position)->tab.setText(switch(position){
case 0 -> R.string.profile_featured;
case 1 -> R.string.profile_timeline;
case 2 -> R.string.profile_about;
default -> throw new IllegalStateException();
}));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
@ -317,6 +323,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if (position == 4) tab.view.setVisibility(View.GONE);
}
});
tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onTabSelected(TabLayout.Tab tab){}
@Override
public void onTabUnselected(TabLayout.Tab tab){}
@Override
public void onTabReselected(TabLayout.Tab tab){
if(getFragmentForPage(tab.getPosition()) instanceof ScrollableToTop stt)
stt.scrollToTop();
}
});
cover.setOutlineProvider(new ViewOutlineProvider(){
@Override
@ -1304,9 +1323,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
FrameLayout view=new FrameLayout(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@ -1314,8 +1331,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
Fragment fragment=getFragmentForPage(position);
FrameLayout fragmentView=tabViews[position];
fragmentView.setVisibility(View.VISIBLE);
if(fragmentView.getParent() instanceof ViewGroup parent)
parent.removeView(fragmentView);
((FrameLayout)holder.itemView).addView(fragmentView);
if(!fragment.isAdded()){
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit();
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){

View File

@ -24,6 +24,7 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
@ -105,6 +106,12 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
text.textSelectable=true;
else if(item instanceof FooterStatusDisplayItem footer)
footer.hideCounts=true;
else if(item instanceof SpoilerStatusDisplayItem spoiler){
for(StatusDisplayItem subItem:spoiler.contentItems){
if(subItem instanceof TextStatusDisplayItem text)
text.textSelectable=true;
}
}
}
}

View File

@ -48,7 +48,7 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
@Override
protected void doLoadData(int offset, int count){
refreshing=true;
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false)
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(SearchResults result){

View File

@ -289,15 +289,19 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
FrameLayout view=new FrameLayout(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
FrameLayout view=tabViews[position];
if(view.getParent() instanceof ViewGroup parent)
parent.removeView(view);
view.setVisibility(View.VISIBLE);
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
@Override
public int getItemCount(){

View File

@ -23,7 +23,6 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
@ -31,7 +30,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
@ -97,7 +96,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
args.putParcelable("profileAccount", Parcels.wrap(res.account));
Nav.go(getActivity(), ProfileFragment.class, args);
}
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
case STATUS -> {
Status status=res.status.getContentStatus();
Bundle args=new Bundle();
@ -113,7 +112,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
}
@Override
protected void doLoadData(int offset, int count){
protected void doLoadData(int _offset, int count){
GetSearchResults.Type type;
if(currentFilter.size()==1){
type=switch(currentFilter.iterator().next()){
@ -128,7 +127,21 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
dataLoaded();
return;
}
currentRequest=new GetSearchResults(currentQuery, type, true)
String maxID=null;
// TODO server-side bug
/*int offset=0;
if(_offset>0){
if(type==GetSearchResults.Type.STATUSES){
if(!preloadedData.isEmpty())
maxID=preloadedData.get(preloadedData.size()-1).status.id;
else if(!data.isEmpty())
maxID=data.get(data.size()-1).status.id;
}else{
offset=_offset;
}
}*/
int offset=_offset;
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
@ -142,12 +155,15 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
results.add(new SearchResult(tag));
}
if(result.statuses!=null){
for(Status status:result.statuses)
results.add(new SearchResult(status));
Set<String> alreadyLoadedStatuses=data.stream().filter(r->r.type==SearchResult.Type.STATUS).map(r->r.status.id).collect(Collectors.toSet());
for(Status status:result.statuses){
if(!alreadyLoadedStatuses.contains(status.id))
results.add(new SearchResult(status));
}
}
prevDisplayItems=new ArrayList<>(displayItems);
unfilteredResults=results;
onDataLoaded(filterSearchResults(results), false);
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
}
@Override

View File

@ -122,7 +122,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
recentsHeader.setVisible(!data.isEmpty());
});
}else{
currentRequest=new GetSearchResults(currentQuery, null, false)
currentRequest=new GetSearchResults(currentQuery, null, false, null, 0, 0)
.limit(2)
.setCallback(new SimpleCallback<>(this){
@Override
@ -378,7 +378,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
private void openHashtag(SearchResult res){
wrapSuicideDialog(()->{
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
});
}
@ -424,6 +424,8 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}
private void onSearchViewEnter(){
if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty())
return;
wrapSuicideDialog(()->deliverResult(currentQuery, null));
}
@ -436,7 +438,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
String q=searchViewHelper.getQuery();
if(q.startsWith("#"))
q=q.substring(1);
UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
UiUtils.openHashtagTimeline(getActivity(), accountID, q);
});
}

View File

@ -105,7 +105,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
@Override
public void onClick(){
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
UiUtils.openHashtagTimeline(getActivity(), accountID, item);
}
}
}

View File

@ -271,7 +271,7 @@ public class SignupFragment extends ToolbarFragment{
@Override
public void tail(Node node, int depth){
if(node instanceof Element){
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}

View File

@ -33,7 +33,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
setTitle(getString(R.string.about_app, getString(R.string.sk_app_name)));
AccountSession s=AccountSessionManager.get(accountID);
onDataLoaded(List.of(
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag), null)),
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag))),
new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),

View File

@ -55,6 +55,8 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
onDataLoaded(List.of(
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick),
new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick),
new ListItem<>(R.string.settings_privacy, 0, R.drawable.ic_privacy_tip_24px, this::onPrivacyClick),
new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_filter_alt_24px, this::onFiltersClick),
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick),
new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick),
new ListItem<>(getString(R.string.about_app, getString(R.string.sk_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true),
@ -69,7 +71,9 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
}
account.reloadPreferences(null);
AccountSession session=AccountSessionManager.get(accountID);
session.reloadPreferences(null);
session.updateAccountInfo();
E.register(this);
}
@ -133,6 +137,10 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
}
private void onPrivacyClick(){
Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
}
private void onFiltersClick(){
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
}

View File

@ -0,0 +1,41 @@
package org.joinmastodon.android.fragments.settings;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import java.util.List;
public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> discoverableItem, indexableItem;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.settings_privacy);
Account self=AccountSessionManager.get(accountID).self;
onDataLoaded(List.of(
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_thumbs_up_down_24px, ()->toggleCheckableItem(discoverableItem)),
indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_search_24px, ()->toggleCheckableItem(indexableItem))
));
if(self.source.indexable==null)
indexableItem.isEnabled=false;
}
@Override
protected void doLoadData(int offset, int count){}
@Override
public void onPause(){
super.onPause();
Account self=AccountSessionManager.get(accountID).self;
if(self.discoverable!=discoverableItem.checked || (self.source.indexable!=null && self.source.indexable!=indexableItem.checked)){
self.discoverable=discoverableItem.checked;
self.source.indexable=indexableItem.checked;
AccountSessionManager.get(accountID).savePreferencesLater();
}
}
}

View File

@ -153,18 +153,21 @@ public class SettingsServerFragment extends AppKitFragment{
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
FrameLayout view=new FrameLayout(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
FrameLayout view=tabViews[position];
if(view.getParent() instanceof ViewGroup parent)
parent.removeView(view);
view.setVisibility(View.VISIBLE);
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Fragment fragment=getFragmentForPage(position);
if(!fragment.isAdded()){
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
getChildFragmentManager().beginTransaction().add(view.getId(), fragment).commit();
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){

View File

@ -139,6 +139,7 @@ public class Account extends BaseModel implements Searchable{
* When a timed mute will expire, if applicable.
*/
public Instant muteExpiresAt;
public boolean noindex;
public List<Role> roles;
@ -238,6 +239,7 @@ public class Account extends BaseModel implements Searchable{
", source="+source+
", suspended="+suspended+
", muteExpiresAt="+muteExpiresAt+
", noindex="+noindex+
'}';
}
}

View File

@ -23,6 +23,7 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
", following="+following+
", history="+history+
", statusesCount="+statusesCount+
", following="+following+
'}';
}
@ -30,4 +31,19 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
public String getID(){
return name;
}
@Override
public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass()!=o.getClass()) return false;
Hashtag hashtag=(Hashtag) o;
return name.equals(hashtag.name);
}
@Override
public int hashCode(){
return name.hashCode();
}
}

View File

@ -20,4 +20,22 @@ public class Mention extends BaseModel{
", url='"+url+'\''+
'}';
}
@Override
public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass()!=o.getClass()) return false;
Mention mention=(Mention) o;
if(!id.equals(mention.id)) return false;
return url.equals(mention.url);
}
@Override
public int hashCode(){
int result=id.hashCode();
result=31*result+url.hashCode();
return result;
}
}

View File

@ -37,6 +37,8 @@ public class Source extends BaseModel{
* The number of pending follow requests.
*/
public int followRequestCount;
public Boolean indexable;
public boolean hideCollections;
@Override
public void postprocess() throws ObjectValidationException{
@ -54,6 +56,8 @@ public class Source extends BaseModel{
", sensitive="+sensitive+
", language='"+language+'\''+
", followRequestCount="+followRequestCount+
", indexable="+indexable+
", hideCollections="+hideCollections+
'}';
}
}

View File

@ -5,6 +5,17 @@ import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDese
import android.text.TextUtils;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import androidx.annotation.NonNull;
import com.github.bottomSoftwareFoundation.bottom.Bottom;

View File

@ -219,7 +219,7 @@ public class TimelineDefinition {
args.putString("listID", listId);
args.putBoolean("listIsExclusive", listIsExclusive);
} else if (type == TimelineType.HASHTAG) {
args.putString("hashtag", hashtagName);
args.putString("hashtagName", hashtagName);
args.putBoolean("localOnly", hashtagLocalOnly);
args.putStringArrayList("any", hashtagAny == null ? new ArrayList<>() : new ArrayList<>(hashtagAny));
args.putStringArrayList("all", hashtagAll == null ? new ArrayList<>() : new ArrayList<>(hashtagAll));

View File

@ -15,6 +15,7 @@ import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Status;
@ -53,7 +54,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText, Status status) {
super(parentID, parentFragment);
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
HtmlParser.parseCustomEmoji(ssb, emojis);
if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames)
HtmlParser.parseCustomEmoji(ssb, emojis);
this.text=ssb;
emojiHelper.setText(ssb);
this.icon=icon;

View File

@ -180,10 +180,8 @@ public abstract class StatusDisplayItem{
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
parentID, fragment, hashtag.name, List.of(),
R.drawable.ic_fluent_number_symbol_20sp_filled, null,
i -> {
args.putString("hashtag", hashtag.name);
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
}, status
i->UiUtils.openHashtagTimeline(fragment.getActivity(), accountID, hashtag),
status
)));
}

View File

@ -878,6 +878,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height){
if(width<=0 || height<=0)
return;
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams();
params.width=width;
params.height=height;

View File

@ -119,6 +119,9 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
int width=right-left;
int height=bottom-top;
if(width==0 || height==0)
return;
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
minScale=scale;
maxScale=Math.max(3f, height/(float)child.getHeight());
@ -306,8 +309,6 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
}, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA));
}
}else{
if(animatingTransition)
Log.w(TAG, "updateViewTransform: ", new Throwable().fillInStackTrace());
child.setScaleX(matrixValues[Matrix.MSCALE_X]);
child.setScaleY(matrixValues[Matrix.MSCALE_Y]);
child.setTranslationX(matrixValues[Matrix.MTRANS_X]);

View File

@ -100,9 +100,10 @@ public class HtmlParser{
}
}
Map<String, String> idsByUrl=mentions.stream().collect(Collectors.toMap(m->m.url, m->m.id));
Map<String, String> idsByUrl=mentions.stream().distinct().collect(Collectors.toMap(m->m.url, m->m.id));
// Hashtags in remote posts have remote URLs, these have local URLs so they don't match.
// Map<String, String> tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name));
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
final SpannableStringBuilder ssb=new SpannableStringBuilder();
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
@ -115,6 +116,7 @@ public class HtmlParser{
}else if(node instanceof Element el){
switch(el.nodeName()){
case "a" -> {
Object linkObject=null;
String href=el.attr("href");
LinkSpan.Type linkType;
String text=el.text();
@ -122,6 +124,7 @@ public class HtmlParser{
if(text.startsWith("#")){
linkType=LinkSpan.Type.HASHTAG;
href=text.substring(1);
linkObject=tagsByTag.get(text.substring(1).toLowerCase());
}else{
linkType=LinkSpan.Type.URL;
}
@ -136,7 +139,7 @@ public class HtmlParser{
}else{
linkType=LinkSpan.Type.URL;
}
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID), ssb.length(), el));
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID, linkObject, text), ssb.length(), el));
}
case "br" -> ssb.append('\n');
case "span" -> {
@ -271,7 +274,7 @@ public class HtmlParser{
String url=matcher.group(3);
if(TextUtils.isEmpty(matcher.group(4)))
url="http://"+url;
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null), matcher.start(3), matcher.end(3), 0);
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null, null, null), matcher.start(3), matcher.end(3), 0);
}while(matcher.find()); // Find more URLs
return ssb;
}

View File

@ -5,6 +5,7 @@ import android.text.TextPaint;
import android.text.style.CharacterStyle;
import android.view.View;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.ui.utils.UiUtils;
public class LinkSpan extends CharacterStyle {
@ -14,17 +15,15 @@ public class LinkSpan extends CharacterStyle {
private String link;
private Type type;
private String accountID;
private Object linkObject;
private String text;
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID){
this(link, listener, type, accountID, null);
}
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, String text){
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, Object linkObject, String text){
this.listener=listener;
this.link=link;
this.type=type;
this.accountID=accountID;
this.linkObject=linkObject;
this.text=text;
}
@ -42,7 +41,12 @@ public class LinkSpan extends CharacterStyle {
switch(getType()){
case URL -> UiUtils.openURL(context, accountID, link);
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link, null);
case HASHTAG -> {
if(linkObject instanceof Hashtag ht)
UiUtils.openHashtagTimeline(context, accountID, ht);
else
UiUtils.openHashtagTimeline(context, accountID, text);
}
case CUSTOM -> listener.onLinkClick(this);
}
}

View File

@ -104,6 +104,7 @@ import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.SearchResults;
@ -445,12 +446,18 @@ public class UiUtils {
Nav.go((Activity) context, ProfileFragment.class, args);
}
public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following) {
Bundle args = new Bundle();
public static void openHashtagTimeline(Context context, String accountID, Hashtag hashtag){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("hashtag", hashtag);
if (following != null) args.putBoolean("following", following);
Nav.go((Activity) context, HashtagTimelineFragment.class, args);
args.putParcelable("hashtag", Parcels.wrap(hashtag));
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
}
public static void openHashtagTimeline(Context context, String accountID, String hashtag){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("hashtagName", hashtag);
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
}
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed) {
@ -1157,7 +1164,7 @@ public class UiUtils {
return Optional.empty();
}
return Optional.of(new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() {
return Optional.of(new GetSearchResults(query.getQuery(), type, true, null, 0, 0).setCallback(new Callback<>() {
@Override
public void onSuccess(SearchResults results) {
Optional<T> result = extractResult.apply(results);
@ -1254,7 +1261,7 @@ public class UiUtils {
}
public static MastodonAPIRequest<SearchResults> lookupAccountHandle(Context context, String accountID, Pair<String, Optional<String>> queryHandle, BiConsumer<Class<? extends Fragment>, Bundle> go) {
String fullHandle = ("@" + queryHandle.first) + (queryHandle.second.map(domain -> "@" + domain).orElse(""));
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true)
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true, null, 0, 0)
.setCallback(new Callback<>() {
@Override
public void onSuccess(SearchResults results) {
@ -1317,7 +1324,7 @@ public class UiUtils {
})
.execNoAuth(uri.getHost()));
} else if (looksLikeFediverseUrl(url)) {
return Optional.of(new GetSearchResults(url, null, true)
return Optional.of(new GetSearchResults(url, null, true, null, 0, 0)
.setCallback(new Callback<>() {
@Override
public void onSuccess(SearchResults results) {

View File

@ -15,15 +15,12 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.SearchResults;
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FilterChipView;
@ -96,6 +93,24 @@ public class ComposeAutocompleteViewController{
outRect.right=V.dp(8);
}
});
// Set empty adapter to prevent NPEs
list.setAdapter(new RecyclerView.Adapter<>(){
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
throw new UnsupportedOperationException();
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position){
}
@Override
public int getItemCount(){
return 0;
}
});
contentView.addView(list, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
emptyButton=new FilterChipView(activity);
@ -222,11 +237,13 @@ public class ComposeAutocompleteViewController{
}
private void doSearchUsers(){
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false)
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
currentRequest=null;
if(mode!=Mode.USERS)
return;
List<AccountViewModel> oldList=users;
users=result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList());
if(isLoading){
@ -256,7 +273,7 @@ public class ComposeAutocompleteViewController{
}
private void doSearchHashtags(){
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false)
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false, null, 0, 0)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){

View File

@ -75,14 +75,14 @@ public class ComposePollViewController{
Instance instance=fragment.instance;
if (!instance.isAkkoma()) {
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
maxPollOptions=instance.configuration.polls.maxOptions;
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
maxPollOptionLength=instance.configuration.polls.maxCharactersPerOption;
} else {
if (instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
maxPollOptions=instance.pollLimits.maxOptions;
if(instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
maxPollOptionLength=instance.pollLimits.maxOptionChars;
}

View File

@ -15,7 +15,7 @@ public class MediaGridLayout extends ViewGroup{
private static final int GAP=2; // dp
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
private int[] columnStarts, columnEnds, rowStarts, rowEnds;
public MediaGridLayout(Context context){
this(context, null);
@ -41,6 +41,14 @@ public class MediaGridLayout extends ViewGroup{
width=Math.round(width*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH));
}
if(rowStarts==null || rowStarts.length<tiledLayout.rowSizes.length){
rowStarts=new int[tiledLayout.rowSizes.length];
rowEnds=new int[tiledLayout.rowSizes.length];
}
if(columnStarts==null || columnStarts.length<tiledLayout.columnSizes.length){
columnStarts=new int[tiledLayout.columnSizes.length];
columnEnds=new int[tiledLayout.columnSizes.length];
}
int offset=0;
for(int i=0;i<tiledLayout.columnSizes.length;i++){
columnStarts[i]=offset;
@ -73,7 +81,7 @@ public class MediaGridLayout extends ViewGroup{
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b){
if(tiledLayout==null)
if(tiledLayout==null || rowStarts==null)
return;
int maxWidth=UiUtils.MAX_WIDTH;

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,22Q8.525,21.125 6.263,18.012Q4,14.9 4,11.1V5L12,2L20,5V11.1Q20,14.9 17.738,18.012Q15.475,21.125 12,22ZM12,19.9Q14.6,19.075 16.3,16.6Q18,14.125 18,11.1V6.375L12,4.125L6,6.375V11.1Q6,14.125 7.7,16.6Q9.4,19.075 12,19.9ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M2,14Q1.175,14 0.588,13.412Q0,12.825 0,12V6Q0,5.7 0.125,5.425Q0.25,5.15 0.45,4.95L5.4,0L6.15,0.75Q6.3,0.9 6.4,1.137Q6.5,1.375 6.5,1.6V1.8L5.8,5H11Q11.425,5 11.713,5.287Q12,5.575 12,6V7.25Q12,7.4 11.975,7.537Q11.95,7.675 11.9,7.8L9.65,13.1Q9.475,13.525 9.088,13.762Q8.7,14 8.25,14ZM7.95,12 L10,7.15V7Q10,7 10,7Q10,7 10,7H3.35L3.95,4.3L2,6.2V12Q2,12 2,12Q2,12 2,12ZM18.6,24 L17.85,23.25Q17.7,23.1 17.6,22.863Q17.5,22.625 17.5,22.4V22.2L18.2,19H13Q12.575,19 12.288,18.712Q12,18.425 12,18V16.75Q12,16.6 12.025,16.462Q12.05,16.325 12.1,16.2L14.35,10.9Q14.55,10.475 14.925,10.238Q15.3,10 15.75,10H22Q22.825,10 23.413,10.587Q24,11.175 24,12V18Q24,18.3 23.888,18.562Q23.775,18.825 23.55,19.05ZM16.05,12 L14,16.85V17Q14,17 14,17Q14,17 14,17H20.65L20.05,19.7L22,17.8V12Q22,12 22,12Q22,12 22,12ZM2,12V6.2V7Q2,7 2,7Q2,7 2,7V7.15V12Q2,12 2,12Q2,12 2,12ZM22,12V17.8V17Q22,17 22,17Q22,17 22,17V16.85V12Q22,12 22,12Q22,12 22,12Z"/>
</vector>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:paddingHorizontal="16dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/follow_btn_wrap"
android:layout_marginEnd="8dp"
android:textAppearance="@style/m3_headline_small"
android:textColor="?colorM3OnSurface"
android:maxLines="4"
android:ellipsize="end"
android:minHeight="48dp"
android:gravity="center_vertical"
tools:text="#CatsOfMastodonButLong"/>
<FrameLayout
android:id="@+id/follow_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/title"
android:layout_alignBottom="@id/title"
android:layout_alignParentEnd="true">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/profile_action_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="center"
style="@style/Widget.Mastodon.M3.Button.Filled"
android:paddingHorizontal="16dp"
tools:text="@string/button_follow" />
<ProgressBar
android:id="@+id/action_progress"
style="?android:progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:elevation="10dp"
android:indeterminate="true"
android:outlineProvider="none"
android:visibility="gone" />
</FrameLayout>
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_marginTop="4dp"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3OnSurfaceVariant"
tools:text="123 posts"/>
</RelativeLayout>

View File

@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/pin"
android:title="@string/sk_pin_timeline"
android:icon="@drawable/ic_fluent_pin_24_regular"
android:showAsAction="always" />
<item
android:id="@+id/pin"
android:title="@string/sk_pin_timeline"
android:showAsAction="always"
android:icon="@drawable/ic_fluent_pin_24_regular" />
<item
android:id="@+id/follow_hashtag"
android:icon="@drawable/ic_fluent_person_add_24_regular"
android:showAsAction="always"
android:title="@string/button_follow"/>
</menu>

View File

@ -8,7 +8,7 @@
<string name="ok">حسنًا</string>
<string name="preparing_auth">جَارٍ الإعدَادُ لِلمُصادَقَة…</string>
<string name="finishing_auth">يُنهي المصادقة…</string>
<string name="user_boosted">%s إعادة نشر</string>
<string name="user_boosted">قام %s بإعادة نشر</string>
<string name="in_reply_to">ردًا على %s</string>
<string name="notifications">الإشعارات</string>
<string name="user_followed_you">%s بَدَأ بِمُتابَعَتِك</string>
@ -242,7 +242,7 @@
<string name="skip">تخطى</string>
<string name="notification_type_follow">متابعُون جُدُد</string>
<string name="notification_type_favorite">المفضلة</string>
<string name="notification_type_reblog">المشاركات</string>
<string name="notification_type_reblog">المعاد نشرها</string>
<string name="notification_type_mention">الإشارات</string>
<string name="notification_type_poll">استطلاع رأي</string>
<string name="choose_account">اختر حسابًا</string>
@ -277,7 +277,7 @@
<string name="more_options">مزيد من الخيارات</string>
<string name="new_post">منشور جديد</string>
<string name="button_reply">ردّ</string>
<string name="button_reblog">شارك</string>
<string name="button_reblog">إعادة النشر</string>
<string name="button_favorite">فضّل</string>
<string name="button_share">شارك</string>
<string name="media_no_description">وسائط بدون وصف</string>
@ -292,8 +292,8 @@
<string name="followed_user">أنت تتابع الآن %s</string>
<string name="following_user_requested">طَلَبَ %s مُتابَعتك</string>
<string name="open_in_browser">افتح في المتصفح</string>
<string name="hide_boosts_from_user">اخف مشاركات %s</string>
<string name="show_boosts_from_user">أظهر مشاركات %s</string>
<string name="hide_boosts_from_user">أخفِ المعاد نشرها مِن %s</string>
<string name="show_boosts_from_user">أظهر ما أعاد %s نشرَه</string>
<string name="signup_reason">لماذا تريد الانضمام؟</string>
<string name="signup_reason_note">هذا سوف يساعدنا في مراجعة تطبيقك.</string>
<string name="clear">امسح</string>
@ -346,10 +346,10 @@
<item quantity="other">%,d تفضيل</item>
</plurals>
<plurals name="x_reblogs">
<item quantity="zero">%,d إعادة نشر</item>
<item quantity="zero">لم يُعد نشره</item>
<item quantity="one">إعادة نشر واحدة</item>
<item quantity="two">أعيد نشره مرّتان</item>
<item quantity="few">أعيد نشره %,d مرة</item>
<item quantity="few">أعيد نشره %,d مرات</item>
<item quantity="many">أعيد نشره %,d مرات</item>
<item quantity="other">أعيد نشره %,d مرات</item>
</plurals>
@ -698,4 +698,18 @@
<string name="time_minutes_ago_short">مُنذُ %dد</string>
<string name="time_hours_ago_short">مُنذُ %dسا</string>
<string name="time_days_ago_short">مُنذُ %d أيام</string>
<!-- %s is the name of the post language -->
<string name="translate_post">تُرجِم مِن %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">مُترجَم مِن %1$s باستخدام %2$s</string>
<string name="translation_show_original">إظهار الأصل</string>
<string name="translation_failed">فشِلَت الترجَمة. قد لم يتمكّن مدير الخادم من تفعيل الترجمات على هذا الخادم أو أنّ هذا الخادم يُشغِّل نسخة قديمة من ماستدون حيث الترجمات غير مدعومة بعد.</string>
<plurals name="x_participants">
<item quantity="zero">لا مُشارِك</item>
<item quantity="one">مشارِك واحد</item>
<item quantity="two">مشاركَيْنِ</item>
<item quantity="few">مشاركين</item>
<item quantity="many">مُشارِكًا</item>
<item quantity="other">مُشارك</item>
</plurals>
</resources>

View File

@ -527,4 +527,6 @@
<string name="time_minutes_ago_short">%d хв таму</string>
<string name="time_hours_ago_short">%d г таму</string>
<string name="time_days_ago_short">%d дз таму</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -199,4 +199,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -182,4 +182,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -293,4 +293,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -639,4 +639,6 @@
<string name="time_minutes_ago_short">Před %dm</string>
<string name="time_hours_ago_short">Před %dh</string>
<string name="time_days_ago_short">Před %dd</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -503,4 +503,6 @@
<string name="search_open_url">Åbn URL i Mastodon</string>
<string name="posts_matching_hashtag">Indlæg med “%s”</string>
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -582,4 +582,6 @@
<string name="time_minutes_ago_short">vor %d Minuten</string>
<string name="time_hours_ago_short">vor %d Stunden</string>
<string name="time_days_ago_short">vor %d Tagen</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -387,6 +387,7 @@
<string name="welcome_to_mastodon">Καλώς ήρθες στο Mastodon</string>
<string name="welcome_paragraph1">Το Mastodon είναι ένα αποκεντρωμένο κοινωνικό δίκτυο που σημαίνει ότι καμία εταιρεία δεν το ελέγχει. Αποτελείται από πολλούς ανεξάρτητους διακομιστές, όλοι συνδεδεμένοι μαζί.</string>
<string name="what_are_servers">Τι είναι οι διακομιστές;</string>
<string name="welcome_paragraph2">Κάθε λογαριασμός Mastodon φιλοξενείται σε ένα διακομιστή - ο καθένας με τις δικές του αξίες, κανόνες &amp; διαχειριστές. Ανεξάρτητα από το ποιον μπορεί να επιλέξεις, μπορείς να ακολουθήσεις και να αλληλεπιδράσεις με άτομα από οποιονδήποτε διακομιστή.</string>
<string name="opening_link">Άνοιγμα συνδέσμου…</string>
<string name="link_not_supported">Αυτός ο σύνδεσμος δεν υποστηρίζεται στην εφαρμογή</string>
<string name="log_out_all_accounts">Αποσύνδεση από όλους τους λογαριασμούς</string>
@ -581,4 +582,21 @@
<string name="time_minutes_ago_short">%dλ πριν</string>
<string name="time_hours_ago_short">%dώ πριν</string>
<string name="time_days_ago_short">%dημ πριν</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Μετάφραση από %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Μεταφράστηκε από %1$s χρησιμοποιώντας %2$s</string>
<string name="translation_show_original">Εμφάνιση αρχικού</string>
<string name="translation_failed">Η μετάφραση απέτυχε. Ίσως ο διαχειριστής δεν έχει ενεργοποιήσει μεταφράσεις σε αυτόν τον διακομιστή ή αυτός ο διακομιστής εκτελεί μια παλαιότερη έκδοση του Mastodon όπου οι μεταφράσεις δεν υποστηρίζονται ακόμα.</string>
<string name="settings_privacy">Ιδιωτικότητα και προσιτότητα</string>
<string name="settings_discoverable">Παροχή προφίλ και δημοσιεύσεων σε αλγορίθμους ανακάλυψης</string>
<string name="settings_indexable">Συμπερίληψη δημόσιων αναρτήσεων στα αποτελέσματα αναζήτησης</string>
<plurals name="x_participants">
<item quantity="one">%,d συμμετέχων</item>
<item quantity="other">%,d συμμετέχοντες</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d ανάρτηση σήμερα</item>
<item quantity="other">%,d αναρτήσεις σήμερα</item>
</plurals>
</resources>

View File

@ -573,4 +573,6 @@
<string name="time_minutes_ago_short">hace %dm</string>
<string name="time_hours_ago_short">hace %dh</string>
<string name="time_days_ago_short">hace %dd</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -441,4 +441,6 @@
<string name="clear_all">Garbitu dena</string>
<string name="search_open_url">Ireki URLa Mastodonen</string>
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -387,6 +387,7 @@
<string name="welcome_to_mastodon">به ماستودون خوش آمدید</string>
<string name="welcome_paragraph1">ماستودون یک شبکه اجتماعی غیر متمرکز است،به این معنی که هیچ شرکتی آن را کنترل نمی کند. این از بسیاری از کارسازهای مستقل تشکیل شده است که همه به هم متصل هستند.</string>
<string name="what_are_servers">کارساز شما کجاست؟</string>
<string name="welcome_paragraph2">هر حساب ماستودون بر روی یک سرور میزبانی می شود — هر کدام با مقادیر، قوانین، &amp; مدیران خاص خود. مهم نیست کدام یک را انتخاب می کنید، می توانید افراد را در هر کارسازی دنبال کنید و با آنها تعامل داشته باشید.</string>
<string name="opening_link">باز کردن پیوند…</string>
<string name="link_not_supported">این پیوند در کاره پشتیبانی نمی شود</string>
<string name="log_out_all_accounts">از همه حساب‌ها خارج شوید</string>
@ -581,4 +582,21 @@
<string name="time_minutes_ago_short">%d؜دقيقه پيش</string>
<string name="time_hours_ago_short">%dساعت پيش</string>
<string name="time_days_ago_short">%dروز پیش</string>
<!-- %s is the name of the post language -->
<string name="translate_post">ترجمه از %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">ترجمه از %1$s با %2$s</string>
<string name="translation_show_original">نمایش اصلی</string>
<string name="translation_failed">ترجمه ناموفق بود. شاید مدیر ترجمه‌ها را در این کارساز فعال نکرده باشد یا این کارساز نسخه قدیمی ماستودون را اجرا می کند که در آن ترجمه‌ها هنوز پشتیبانی نمی شوند.</string>
<string name="settings_privacy">محرمانگی و دسترسی</string>
<string name="settings_discoverable">مشخص کردن مشخصات و فرسته‌ها در الگوریتم‌های اکتشاف</string>
<string name="settings_indexable">قرار دادن فرسته‌های عمومی در نتایج جستجو</string>
<plurals name="x_participants">
<item quantity="one">%,d شرکت کننده</item>
<item quantity="other">%,d شرکت‌کننده</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d فرسته امروز</item>
<item quantity="other">%,d فرسته امروز</item>
</plurals>
</resources>

View File

@ -2,13 +2,167 @@
<resources>
<string name="log_in">Kirjaudu sisään</string>
<string name="next">Seuraava</string>
<string name="loading_instance">Haetaan palvelimen tietoja…</string>
<string name="error">Virhe</string>
<string name="not_a_mastodon_instance">%s ei näytä olevan Mastodonin palvelin.</string>
<string name="ok">OK</string>
<string name="preparing_auth">Valmistellaan todennusta…</string>
<string name="finishing_auth">Viimeistellään todennusta…</string>
<string name="user_boosted">%s tehosti</string>
<string name="in_reply_to">Vastauksessa %s</string>
<string name="notifications">Ilmoitukset</string>
<string name="user_followed_you">%s seurasi sinua</string>
<string name="user_sent_follow_request">%s lähetti sinulle seurauspyynnön</string>
<string name="user_favorited">%s tykkäsi julkaisustasi</string>
<string name="notification_boosted">%s tehosti viestiäsi</string>
<string name="poll_ended">Katso tulokset äänestyksestä johon osallistuit</string>
<string name="share_toot_title">Jaa</string>
<string name="settings">Asetukset</string>
<string name="publish">Julkaise</string>
<string name="discard_draft">Hylkää luonnos?</string>
<string name="discard">Hylkää</string>
<string name="cancel">Kumoa</string>
<plurals name="followers">
<item quantity="one">seuraaja</item>
<item quantity="other">seuraajat</item>
</plurals>
<plurals name="following">
<item quantity="one">seurataan</item>
<item quantity="other">seurataan</item>
</plurals>
<string name="posts">Viestit</string>
<string name="posts_and_replies">Viestit ja vastaukset</string>
<string name="media">Media</string>
<string name="profile_about">Tietoja</string>
<string name="button_follow">Seuraa</string>
<string name="button_following">Seurataan</string>
<string name="edit_profile">Muokkaa profiilia</string>
<string name="share_user">Jaa profiili</string>
<string name="mute_user">Mykistä %s</string>
<string name="unmute_user">Poista mykistys tililtä %s</string>
<string name="block_user">Estä %s</string>
<string name="unblock_user">Poista käyttäjän %s esto</string>
<string name="report_user">Raportoi %s</string>
<string name="block_domain">Estä %s</string>
<string name="unblock_domain">Poista käyttäjän %s esto</string>
<plurals name="x_posts">
<item quantity="one">%,d julkaisu</item>
<item quantity="other">%,d julkaisua</item>
</plurals>
<string name="profile_joined">Liittynyt</string>
<string name="done">Valmis</string>
<string name="loading">Ladataan…</string>
<string name="field_label">Nimi</string>
<string name="field_content">Sisältö</string>
<string name="saving">Tallennetaan…</string>
<string name="post_from_user">Julkaisu tililtä %s</string>
<string name="poll_option_hint">Vaihtoehto %d</string>
<plurals name="x_minutes">
<item quantity="one">%d minuutti</item>
<item quantity="other">%d minuuttia</item>
</plurals>
<plurals name="x_hours">
<item quantity="one">%d tunti</item>
<item quantity="other">%d tuntia</item>
</plurals>
<plurals name="x_days">
<item quantity="one">%d päivä</item>
<item quantity="other">%d päivää</item>
</plurals>
<plurals name="x_seconds_left">
<item quantity="one">%d sekunti jäljellä</item>
<item quantity="other">%d sekunttia jäljellä</item>
</plurals>
<plurals name="x_minutes_left">
<item quantity="one">%d minuutti jäljellä</item>
<item quantity="other">%d minuuttia jäljellä</item>
</plurals>
<plurals name="x_hours_left">
<item quantity="one">%d tunti jäljellä</item>
<item quantity="other">%d tuntia jäljellä</item>
</plurals>
<plurals name="x_days_left">
<item quantity="one">%d päivä jäljellä</item>
<item quantity="other">%d päivää jäljellä</item>
</plurals>
<plurals name="x_votes">
<item quantity="one">%d ääni</item>
<item quantity="other">%d ääntä</item>
</plurals>
<string name="poll_closed">Suljettu</string>
<string name="confirm_mute_title">Mykistä tili</string>
<string name="confirm_mute">Vahvista käyttäjän %s mykistys</string>
<string name="do_mute">Mykistä</string>
<string name="confirm_unmute_title">Poista tilin mykistys</string>
<string name="confirm_unmute">Vahvista, että haluat poistaa mykistyksen tililtä %s</string>
<string name="do_unmute">Poista mykistys</string>
<string name="confirm_block_title">Estä tili</string>
<string name="confirm_block_domain_title">Estä verkkotunnus</string>
<string name="confirm_block">Vahvista käyttäjän %s esto</string>
<string name="do_block">Estä</string>
<string name="confirm_unblock_title">Poista tilin esto</string>
<string name="confirm_unblock_domain_title">Poista verkkotunnuksen esto</string>
<string name="confirm_unblock">Vahvista, että haluat poistaa tilin %s eston</string>
<string name="do_unblock">Poista esto</string>
<string name="button_blocked">Estetty</string>
<string name="action_vote">Äänestä</string>
<string name="delete">Poista</string>
<string name="confirm_delete_title">Poista julkaisu</string>
<string name="confirm_delete">Haluatko varmasti poistaa tämän julkaisun?</string>
<string name="deleting">Poistetaan…</string>
<string name="notification_channel_audio_player">Äänen toisto</string>
<string name="play">Toista</string>
<string name="pause">Tauko</string>
<string name="log_out">Kirjaudu ulos</string>
<string name="add_account">Lisää tili</string>
<string name="search_hint">Haku</string>
<string name="hashtags">Aihetunnisteet</string>
<string name="news">Uutiset</string>
<string name="for_you">Sinulle</string>
<string name="all_notifications">Kaikki</string>
<string name="mentions">Maininnat</string>
<plurals name="x_people_talking">
<item quantity="one">%d henkilö puhuu</item>
<item quantity="other">%d henkilöä puhuu</item>
</plurals>
<string name="report_title">Raportoi %s</string>
<string name="report_choose_reason">Mikä on väärin tässä julkaisussa?</string>
<string name="report_choose_reason_account">Mikä on vialla käyttäjässä %s?</string>
<string name="report_choose_reason_subtitle">Valitse se, mikä sopii parhaiten</string>
<string name="report_reason_personal">En pidä siitä</string>
<string name="report_reason_personal_subtitle">Tätä ei halua nähdä</string>
<string name="report_reason_spam">Se on roskapostia</string>
<string name="report_reason_spam_subtitle">Haitalliset linkit, väärennetyt sitoutumiset tai toistuvat vastaukset</string>
<string name="report_reason_violation">Se rikkoo palvelimen sääntöjä</string>
<string name="report_reason_violation_subtitle">Tiedät, että se rikkoo tiettyjä sääntöjä</string>
<string name="report_reason_other">Jotain muuta</string>
<string name="report_reason_other_subtitle">Ongelma ei sovi muihin kategorioihin</string>
<string name="report_choose_rule">Mitä sääntöjä rikotaan?</string>
<string name="report_choose_rule_subtitle">Valitse kaikki sopivat</string>
<string name="report_choose_posts">Onko julkaisuja, jotka tukevat tätä raporttia?</string>
<string name="report_choose_posts_subtitle">Valitse kaikki sopivat</string>
<string name="report_comment_title">Olisiko jotain muuta, mitä meidän pitäisi tietää?</string>
<string name="report_comment_hint">Lisäkommentit</string>
<string name="sending_report">Lähetetään raporttia…</string>
<string name="report_sent_title">Kiitos raportista, tutkimme asiaa.</string>
<string name="report_sent_subtitle">Sillä välin kun tarkistamme tätä, voit ryhtyä toimenpiteisiin käyttäjää @%s vastaan:</string>
<string name="unfollow_user">Lopeta käyttäjän %s seuraaminen</string>
<string name="unfollow">Lopeta seuraaminen</string>
<string name="mute_user_explain">Et näe hänen viestejään. Hän voi silti seurata sinua ja nähdä viestisi. Hän ei tiedä, että on mykistetty.</string>
<string name="block_user_explain">Et näe hänen viestejään, eikä hän voi nähdä viestejäsi tai seurata sinua. Hän näkevät, että olet estänyt hänet.</string>
<string name="report_personal_title">Etkö halua nähdä tätä?</string>
<string name="report_personal_subtitle">Tässä on vaihtoehtosi hallita näkemääsi Mastodonissa:</string>
<string name="back">Takaisin</string>
<string name="search_communities">Palvelimen nimi tai URL-osoite</string>
<string name="instance_rules_title">Palvelimen säännöt</string>
<string name="instance_rules_subtitle">Jatkamalla sitoudut noudattamaan seuraavia sääntöjä, jotka %s moderaattorit ovat asettaneet ja valvoneet.</string>
<string name="signup_title">Luo tili</string>
<string name="display_name">Nimi</string>
<string name="username">Käyttäjätunnus</string>
<string name="email">Sähköposti</string>
<string name="password">Salasana</string>
<string name="confirm_password">Vahvista salasana</string>
<string name="password_note">Sisällytä suuraakkoset, erikoismerkit, ja numerot, jotta voit lisätä salasanan voimaa.</string>
<string name="category_academia">Akateeminen</string>
<string name="category_activism">Aktivismi</string>
<string name="category_all">Kaikki</string>
@ -22,39 +176,424 @@
<string name="category_music">Musiikki</string>
<string name="category_regional">Alueellinen</string>
<string name="category_tech">Teknologia</string>
<string name="confirm_email_title">Tarkista sähköpostisi</string>
<!-- %s is the email address -->
<string name="confirm_email_subtitle">Napauta lähettämäämme linkkiä vahvistaaksesi tunnuksen %s. Odotamme täällä.</string>
<string name="confirm_email_didnt_get">Etkö saanut linkkiä?</string>
<string name="resend">Lähetä uudelleen</string>
<string name="open_email_app">Avaa sähköpostiohjelma</string>
<string name="resent_email">Vahvistusviesti lähetetty</string>
<string name="compose_hint">Kirjoita tai liitä mitä mietit</string>
<string name="content_warning">Sisältövaroitus</string>
<string name="save">Tallenna</string>
<string name="add_alt_text">Lisää selitys</string>
<string name="visibility_public">Julkinen</string>
<string name="visibility_followers_only">Vain seuraajat</string>
<string name="visibility_private">Vain mainitsemani tilit</string>
<string name="recent_searches">Viimeisimmät</string>
<string name="skip">Ohita</string>
<string name="notification_type_follow">Uudet seuraajat</string>
<string name="notification_type_favorite">Suosikit</string>
<string name="notification_type_reblog">Tehostukset</string>
<string name="notification_type_mention">Maininnat</string>
<string name="notification_type_poll">Kyselyt</string>
<string name="choose_account">Valitse tili</string>
<string name="err_not_logged_in">Kirjaudu ensin Mastodoniin</string>
<plurals name="cant_add_more_than_x_attachments">
<item quantity="one">Et voi lisätä enempää kuin %d medialiitteen</item>
<item quantity="other">Et voi lisätä enempää kuin %d medialiitettä</item>
</plurals>
<string name="media_attachment_unsupported_type">Tiedosto %s on tyyppiä jota ei tueta</string>
<string name="media_attachment_too_big">Tiedosto %1$s ylittää %2$s MB kokorajan</string>
<string name="settings_theme">Ulkoasu</string>
<string name="theme_auto">Käytä laitteen ulkoasua</string>
<string name="theme_light">Vaalea</string>
<string name="theme_dark">Tumma</string>
<string name="settings_behavior">Toiminnot</string>
<string name="settings_gif">Toista animoidut käyttäjäkuvat ja emojit</string>
<string name="settings_custom_tabs">Käytä sovelluksen sisäistä selainta</string>
<string name="settings_notifications">Ilmoitukset</string>
<string name="settings_contribute">Osallistu Mastodoniin</string>
<string name="settings_tos">Käyttöehdot</string>
<string name="settings_privacy_policy">Tietosuojakäytäntö</string>
<string name="settings_clear_cache">Tyhjennä median välimuisti</string>
<string name="settings_app_version">Mastodon Android v%1$s (%2$d)</string>
<string name="media_cache_cleared">Median välimuisti tyhjennetty</string>
<string name="confirm_log_out">Kirjaudu ulos %s?</string>
<string name="sensitive_content_explain">Kirjoittaja merkitsi tämän median arkaluontoiseksi.</string>
<string name="avatar_description">Avaa profiili %s</string>
<string name="more_options">Lisää asetuksia</string>
<string name="new_post">Uusi julkaisu</string>
<string name="button_reply">Vastaa</string>
<string name="button_reblog">Tehosta</string>
<string name="button_favorite">Suosikki</string>
<string name="button_share">Jaa</string>
<string name="media_no_description">Kuva ilma kuvausta</string>
<string name="add_media">Lisää mediatiedosto</string>
<string name="add_poll">Lisää kysely</string>
<string name="emoji">Emoji</string>
<string name="home_timeline">Kotiaikajana</string>
<string name="my_profile">Oma profiili</string>
<string name="media_viewer">Median katselin</string>
<string name="follow_user">Follow %s</string>
<string name="unfollowed_user">Käyttäjän %s seuraaminen lopetettu</string>
<string name="followed_user">Seuraat nyt käyttäjää %s</string>
<string name="following_user_requested">Käyttäjän %s seuraamista pyydetty</string>
<string name="open_in_browser">Avaa selaimessa</string>
<string name="hide_boosts_from_user">Piilota käyttäjän @%s tehostukset</string>
<string name="show_boosts_from_user">Näytä tehostukset käyttäjältä @%s</string>
<string name="signup_reason">Miksi haluat liittyä?</string>
<string name="signup_reason_note">Tämä auttaa meitä arvioimaan hakemustasi.</string>
<string name="clear">Tyhjennä</string>
<string name="profile_header">Otsikon kuva</string>
<string name="profile_picture">Profiilikuva</string>
<string name="reorder">Järjestä uudelleen</string>
<string name="download">Lataa</string>
<string name="permission_required">Käyttöoikeus vaaditaan</string>
<string name="storage_permission_to_download">Sovellus tarvitsee pääsyn tallennustilaan, jotta voit tallentaa tämän tiedoston.</string>
<string name="open_settings">Avaa asetukset</string>
<string name="error_saving_file">Virhe tallennettaessa tiedostoa</string>
<string name="file_saved">Tiedosto tallennettu</string>
<string name="downloading">Ladataan…</string>
<string name="no_app_to_handle_action">Tätä toimintoa käsittelevää sovellusta ei ole</string>
<string name="local_timeline">Paikallinen</string>
<string name="trending_posts_info_banner">Nämä julkaisut ovat saamassa vetoa eri puolilla Mastodonia.</string>
<string name="trending_links_info_banner">Näistä uutisista puhutaan Mastodonissa.</string>
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">Nämä ovat viestit kaikilta palvelimesi (%s) käyttäjiltä.</string>
<string name="recommended_accounts_info_banner">Muiden seuraamiesi perusteella saattaisit pitää näistä tileistä.</string>
<string name="see_new_posts">Uusia julkaisuja</string>
<string name="load_missing_posts">Lataa puuttuvat julkaisut</string>
<string name="follow_back">Seuraa takaisin</string>
<string name="button_follow_pending">Pyydetty</string>
<string name="follows_you">Seuraa sinua</string>
<string name="manually_approves_followers">Hyväksyy seuraajat käsin</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<plurals name="x_followers">
<item quantity="one">%d seuraaja</item>
<item quantity="other">%d seuraajaa</item>
</plurals>
<plurals name="x_following">
<item quantity="one">%d seurattu</item>
<item quantity="other">%d seurattua</item>
</plurals>
<plurals name="x_favorites">
<item quantity="one">%,d suosikki</item>
<item quantity="other">%,d suosikkia</item>
</plurals>
<plurals name="x_reblogs">
<item quantity="one">%,d tehostus</item>
<item quantity="other">%,d tehostusta</item>
</plurals>
<string name="timestamp_via_app">%1$s sovelluksella %2$s</string>
<string name="time_now">nyt</string>
<string name="edit_history">Muokkaushistoria</string>
<string name="last_edit_at_x">Muokattiin viimeksi %s</string>
<string name="time_just_now">juuri nyt</string>
<plurals name="x_seconds_ago">
<item quantity="one">%d sekunti sitten</item>
<item quantity="other">%d sekuntia sitten</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="one">%d minuutti sitten</item>
<item quantity="other">%d minuuttia sitten</item>
</plurals>
<string name="edited_timestamp">muokattu %s</string>
<string name="edit_original_post">Alkuperäinen viesti</string>
<string name="edit_text_edited">Tekstiä muokattu</string>
<string name="edit_spoiler_added">Sisältövaroitus</string>
<string name="edit_spoiler_edited">Sisältövaroitus muokattu</string>
<string name="edit_spoiler_removed">Sisältövaroitus poistettu</string>
<string name="edit_poll_added">Kysely lisätty</string>
<string name="edit_poll_edited">Kyselyä muokattu</string>
<string name="edit_poll_removed">Kysely poistettu</string>
<string name="edit_media_added">Mediatiedosto lisätty</string>
<string name="edit_media_removed">Mediatiedosto poistettu</string>
<string name="edit_media_reordered">Mediatiedostoja järjestetty</string>
<string name="edit_marked_sensitive">Merkitty arkaluontoiseksi</string>
<string name="edit_marked_not_sensitive">Merkitty ei arkaluontoiseksi</string>
<string name="edit_multiple_changed">Julkaisu muokattu</string>
<string name="edit">Muokkaa</string>
<string name="discard_changes">Hylätäänkö muutokset?</string>
<string name="upload_failed">Lataus epäonnistui</string>
<string name="file_size_bytes">%d tavua</string>
<string name="file_size_kb">%.2f KB</string>
<string name="file_size_mb">%.2f MB</string>
<string name="file_size_gb">%.2f GB</string>
<string name="upload_processing">Käsitellään…</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
<string name="download_update">Lataa (%s)</string>
<string name="install_update">Asenna</string>
<string name="privacy_policy_title">Yksityisyytesi</string>
<string name="privacy_policy_subtitle">Vaikka Mastodon-sovellus ei kerää mitään tietoja, palvelimella, jonka olet rekisteröitynyt, voi olla eri käytäntö.\n\nJos olet eri mieltä käytännöstä %s, voit palata ja valita eri palvelin.</string>
<string name="i_agree">Hyväksyn</string>
<string name="empty_list">Luettelo on tyhjä</string>
<string name="instance_signup_closed">Tämä palvelin ei hyväksy uusia rekisteröintejä.</string>
<string name="text_copied">Kopioitu leikepöydälle</string>
<string name="add_bookmark">Kirjanmerkki</string>
<string name="remove_bookmark">Poista kirjanmerkki</string>
<string name="bookmarks">Kirjanmerkit</string>
<string name="your_favorites">Omat suosikit</string>
<string name="login_title">Tervetuloa takaisin</string>
<string name="login_subtitle">Kirjaudu sisään palvelimella, jossa olet luonut tilisi.</string>
<string name="server_url">Palvelimen URL-osoite</string>
<string name="signup_random_server_explain">Palvelin valitaan kielesi perusteella, jos jatkat ilman valintaa.</string>
<string name="server_filter_any_language">Millä tahansa kielellä</string>
<string name="server_filter_instant_signup">Välitön rekisteröityminen</string>
<string name="server_filter_manual_review">Manuaalinen hyväksyntä</string>
<string name="server_filter_any_signup_speed">Mikä tahansa rekisteröintinopeus</string>
<string name="server_filter_region_europe">Eurooppa</string>
<string name="server_filter_region_north_america">Pohjois-Amerikka</string>
<string name="server_filter_region_south_america">Etelä-Amerikka</string>
<string name="server_filter_region_africa">Afrikka</string>
<string name="server_filter_region_asia">Aasia</string>
<string name="server_filter_region_oceania">Oseania</string>
<string name="not_accepting_new_members">Ei hyväksy uusia jäseniä</string>
<string name="category_special_interests">Erityiset Kiinnostukset</string>
<string name="signup_passwords_dont_match">Salasanat eivät täsmää</string>
<string name="pick_server_for_me">Valitse minulle</string>
<string name="profile_add_row">Lisää rivi</string>
<string name="profile_setup">Profiilin asetukset</string>
<string name="profile_setup_subtitle">Voit myös täyttää tämän myöhemmin Profiili-välilehdellä.</string>
<string name="profile_setup_explanation">Voit lisätä enintään neljä profiilikenttää joissa on mitä haluat. Sijainti, linkit, pronominit — taivas on rajana.</string>
<string name="popular_on_mastodon">Suosittua Mastodonissa</string>
<string name="follow_all">Seuraa kaikkia</string>
<string name="server_rules_disagree">Eri mieltä</string>
<string name="privacy_policy_explanation">TL;DR: Emme kerää tai käsittele mitään.</string>
<!-- %s is server domain -->
<string name="server_policy_disagree">Eri mieltä tästä %s</string>
<string name="profile_bio">Kuvaus</string>
<!-- Shown in a progress dialog when you tap "follow all" -->
<string name="sending_follows">Seurataan käyttäjiä…</string>
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
<string name="signup_email_domain_blocked">%1$s ei salli ilmoittautumisia osoitteesta %2$s. Kokeile toista osoitetta tai &lt;a&gt;valitse toinen palvelin&lt;/a&gt;.</string>
<string name="spoiler_show">Näytä joka tapauksessa</string>
<string name="spoiler_hide">Piilota uudelleen</string>
<string name="poll_multiple_choice">Valitse yksi tai useampi</string>
<string name="save_changes">Tallenna muutokset</string>
<string name="profile_featured">Suositellut</string>
<string name="profile_timeline">Aikajana</string>
<string name="view_all">Näytä kaikki</string>
<string name="profile_endorsed_accounts">Tilit</string>
<string name="verified_link">Vahvistettu linkki</string>
<string name="show">Näytä</string>
<string name="hide">Piilota</string>
<string name="join_default_server">Liity palvelimelle %s</string>
<string name="pick_server">Valitse toinen palvelin</string>
<string name="signup_or_login">tai</string>
<string name="learn_more">Lue lisää</string>
<string name="welcome_to_mastodon">Tervetuloa Mastodoniin</string>
<string name="welcome_paragraph1">Mastodon on hajautettu sosiaalinen verkosto, joka tarkoittaa sitä, ettei sitä hallitse mikään yksittäinen yritys. Se koostuu monista itsenäisesti ylläpidetyistä palvelimista, jotka on liitetty yhteen.</string>
<string name="what_are_servers">Mitä palvelimet ovat?</string>
<string name="welcome_paragraph2">Jokainen Mastodon tili isännöi palvelimella - kullakin on omat arvot, säännöt, &amp; ylläpitäjät. Riippumatta siitä, minkä valitset, voit seurata ja olla vuorovaikutuksessa ihmisten kanssa millä tahansa palvelimella.</string>
<string name="opening_link">Avataan linkki…</string>
<string name="link_not_supported">Tämä linkki ei ole tuettu sovelluksessa</string>
<string name="log_out_all_accounts">Kirjaudu ulos kaikista tileistä</string>
<string name="confirm_log_out_all_accounts">Kirjaudu ulos kaikista tileistä?</string>
<string name="retry">Yritä uudelleen</string>
<string name="post_failed">Viestin lähettäminen epäonnistui</string>
<!-- %s is formatted file size ("467 KB image") -->
<string name="attachment_description_image">%s kuva</string>
<string name="attachment_description_video">%s video</string>
<string name="attachment_description_audio">%s ääni</string>
<string name="attachment_description_unknown">%s tiedosto</string>
<string name="attachment_type_image">Kuva</string>
<string name="attachment_type_video">Video</string>
<string name="attachment_type_audio">Ääni</string>
<string name="attachment_type_gif">GIF</string>
<string name="attachment_type_unknown">Tiedosto</string>
<string name="attachment_x_percent_uploaded">%d%% ladattu</string>
<string name="add_poll_option">Lisää kyselyyn vaihtoehto</string>
<string name="poll_length">Kyselyn kesto</string>
<string name="poll_style">Tyyli</string>
<string name="compose_poll_single_choice">Valitse yksi</string>
<string name="compose_poll_multiple_choice">Monivalinta</string>
<string name="delete_poll_option">Poista kyselyn vaihtoehto</string>
<string name="poll_style_title">Kyselyn tyyli</string>
<string name="alt_text">Selitys</string>
<string name="help">Ohje</string>
<string name="what_is_alt_text">Mikä on selitys?</string>
<string name="alt_text_help">Kuvaselitys auttaa ihmisiä, joilla on näkövamma, hidas yhteys, tai jotka tarvitsevat lisäkontekstia. \n\nVoit parantaa saavutettavuutta ja kaikkien mahdollisuutta ymmärtää kirjoittamalla selkeän, lyhyen ja objektiivisen selityksen. \n\n<ul><li>Mainitse tärkeät elementit</li>\n<li>Anna tiivistelmä kuvissa olevista teksteistä</li>\n<li>Käytä normaalia lauserakennetta</li>\n<li>Vältä turhaa toistoa</li>\n<li>Keskity monimutkaisissa kuvioissa (kuten kartoissa ja taulukoissa) trendeihin ja tärkeimpiin tietoihin</li></ul></string>
<string name="edit_post">Muokkaa julkaisua</string>
<string name="no_verified_link">Ei todennettua linkkiä</string>
<string name="compose_autocomplete_emoji_empty">Selaa emojeita</string>
<string name="compose_autocomplete_users_empty">Löydä etsimäsi henkilöt</string>
<string name="no_search_results">Näille hakusanoille ei löytynyt mitään</string>
<string name="language">Kieli</string>
<string name="language_default">Oletus</string>
<string name="language_system">Järjestelmä</string>
<string name="language_detecting">Tunnista kieli</string>
<string name="language_cant_detect">Ei voi tunnistaa kieltä</string>
<string name="language_detected">Tunnistettu</string>
<string name="media_hidden">Media piilotettu</string>
<string name="post_hidden">Julkaisu piilotettu</string>
<string name="report_title_post">Raportoi julkaisu</string>
<string name="forward_report_explanation">Tämä tili on toisella palvelimella. Haluatko lähettää nimettömän raportin myös sinne?</string>
<!-- %s is the server domain -->
<string name="forward_report_to_server">Välitä kohteeseen %s</string>
<!-- Shown on the "stamp" on the screen that appears after you report a post/user. Please keep the translation short, preferably a single word -->
<string name="reported">Raportoitu</string>
<string name="report_unfollow_explanation">Jos et enää halua nähdä tämän käyttäjän julkaisuja kotiaikajanallasi, lopeta seuraaminen.</string>
<string name="muted_user">Käyttäjä %s mykistetty</string>
<string name="report_sent_already_blocked">Olet jo estänyt tämän käyttäjän, sinun ei tarvitse tehdä muuta sillä aikaa kun tarkastamme raporttisi.</string>
<string name="report_personal_already_blocked">Olet jo estänyt tämän käyttäjän, joten sinun ei tarvitse tehdä mitään muuta.\n\nKiitos, että autat pitämään Mastodonin turvallisena paikkana kaikille!</string>
<string name="blocked_user">Estetty %s</string>
<string name="mark_all_notifications_read">Merkitse kaikki luetuksi</string>
<string name="settings_display">Näyttö</string>
<string name="settings_filters">Suodattimet</string>
<string name="settings_server_explanation">Yleiskatsaus, säännöt, valvojat</string>
<!-- %s is the app name (Mastodon, key app_name). I made it a placeholder so everything Just Works™ with forks -->
<string name="about_app">Tietoa: %s</string>
<string name="default_post_language">Julkaisun oletuskieli</string>
<string name="settings_alt_text_reminders">Lisää muistutus kuvaselityksestä</string>
<string name="settings_confirm_unfollow">Kysy ennen kuin käyttäjän seuraaminen lopetetaan</string>
<string name="settings_confirm_boost">Kysy ennen tehostusta</string>
<string name="settings_confirm_delete_post">Kysy ennen julkaisujen poistamista</string>
<string name="pause_all_notifications">Keskeytä kaikki</string>
<string name="pause_notifications_off">Pois Päältä</string>
<string name="notifications_policy_anyone">Kuka tahansa</string>
<string name="notifications_policy_followed">Seuraajasi</string>
<string name="notifications_policy_follower">Seuraamasi henkilöt</string>
<string name="notifications_policy_no_one">Ei kukaan</string>
<string name="settings_notifications_policy">Ota vastaan ilmoituksia käyttäjältä</string>
<string name="notification_type_mentions_and_replies">Maininnat ja vastaukset</string>
<string name="pause_all_notifications_title">Keskeytä kaikki ilmoitukset</string>
<plurals name="x_weeks">
<item quantity="one">%d viikko</item>
<item quantity="other">%d viikkoa</item>
</plurals>
<!-- %1$s is the date (may be relative, e.g. "today" or "yesterday"), %2$s is the time. You can reorder these placeholders if that works better for your language -->
<string name="date_at_time">%1$s klo %2$s</string>
<string name="today">tänään</string>
<string name="yesterday">eilen</string>
<string name="tomorrow">huomenna</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="pause_notifications_ends">Päättyy %s</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="pause_notifications_banner">Ilmoitukset jatkuvat %s.</string>
<string name="resume_notifications_now">Jatka nyt</string>
<string name="open_system_notification_settings">Siirry ilmoitusasetuksiin</string>
<string name="about_server">Tietoja</string>
<string name="server_rules">Säännöt</string>
<string name="server_administrator">Ylläpitäjä</string>
<string name="send_email_to_server_admin">Viestin ylläpitäjä</string>
<string name="notifications_disabled_in_system">Ota ilmoitukset käyttöön laitteesi asetuksista nähdäksesi päivityksiä mistä tahansa.</string>
<string name="settings_even_more">Vielä enemmän asetuksia</string>
<string name="settings_show_cws">Näytä sisältövaroitukset</string>
<string name="settings_hide_sensitive_media">Piilota arkaluontoiseksi merkitty media</string>
<string name="settings_show_interaction_counts">Näytä reaktiolaskurit</string>
<string name="settings_show_emoji_in_names">Mukautetut emojit näyttönimissä</string>
<plurals name="in_x_seconds">
<item quantity="one">%d sekunnin kuluttua</item>
<item quantity="other">%d sekunnin kuluttua</item>
</plurals>
<plurals name="in_x_minutes">
<item quantity="one">%d minuutin kuluttua</item>
<item quantity="other">%d minuutin kuluttua</item>
</plurals>
<plurals name="in_x_hours">
<item quantity="one">%d tunnin kuluttua</item>
<item quantity="other">%d tunnin kuluttua</item>
</plurals>
<plurals name="x_hours_ago">
<item quantity="one">%d tunti sitten</item>
<item quantity="other">%d tuntia sitten</item>
</plurals>
<string name="alt_text_reminder_title">Mediasta puuttuu selitysteksti</string>
<plurals name="alt_text_reminder_x_images">
<item quantity="one">%s kuvastasi puuttuu selitysteksti. Julkaistaanko silti?</item>
<item quantity="other">%s kuvastasi puuttuu selitysteksti. Julkaistaanko silti?</item>
</plurals>
<plurals name="alt_text_reminder_x_attachments">
<item quantity="one">%s mediatiedostostasi puuttuu selitysteksti. Julkaistaanko silti?</item>
<item quantity="other">%s mediatiedostostasi puuttuu selitysteksti. Julkaistaanko silti?</item>
</plurals>
<string name="count_one">Yksi</string>
<string name="count_two">Kaksi</string>
<string name="count_three">Kolme</string>
<string name="count_four">Neljä</string>
<string name="alt_text_reminder_post_anyway">Julkaise</string>
<!-- %s is the username -->
<string name="unfollow_confirmation">Lopeta käyttäjän %s seuraaminen?</string>
<string name="filter_active">Aktiivinen</string>
<string name="filter_inactive">Ei käytössä</string>
<string name="settings_add_filter">Lisää suodatin</string>
<string name="settings_edit_filter">Muokkaa suodatinta</string>
<string name="settings_filter_duration">Kesto</string>
<string name="settings_filter_muted_words">Mykistetyt sanat</string>
<string name="settings_filter_context">Mykistä alkaen</string>
<string name="settings_filter_show_cw">Näytä sisältövaroituksella</string>
<string name="settings_filter_show_cw_explanation">Näytä vielä viestejä, jotka täsmäävät tähän suodattimeen, mutta sisällönvaroituksen takana</string>
<string name="settings_delete_filter">Poista suodatin</string>
<string name="filter_duration_forever">Ikuisesti</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="settings_filter_ends">Päättyy %s</string>
<plurals name="settings_x_muted_words">
<item quantity="one">%d mykistetty sana tai lause</item>
<item quantity="other">%d mykistettyä sanaa tai lauseita</item>
</plurals>
<string name="selection_2_options">%1$s ja %2$s</string>
<string name="selection_3_options">%1$s, %2$s ja %3$s</string>
<string name="selection_4_or_more">%1$s, %2$s, ja %3$d lisää</string>
<string name="filter_context_home_lists">Koti &amp; listat</string>
<string name="filter_context_notifications">Ilmoitukset</string>
<string name="filter_context_public_timelines">Julkiset aikajanat</string>
<string name="filter_context_threads_replies">Langat &amp; vastaukset</string>
<string name="filter_context_profiles">Profiilit</string>
<string name="settings_filter_title">Otsikko</string>
<string name="settings_delete_filter_title">Poistetaanko suodatin “%s”?</string>
<string name="settings_delete_filter_confirmation">Tämä suodatin poistetaan tililtäsi kaikissa laitteissa.</string>
<string name="add_muted_word">Lisää mykistetty sana</string>
<string name="edit_muted_word">Muokkaa mykistettyä sanaa</string>
<string name="add">Lisää</string>
<string name="filter_word_or_phrase">Sana tai lause</string>
<string name="filter_add_word_help">Sanat ovat tapauskohtaisia ja vastaavat vain kokonaisia sanoja.\n\nJos suodattaa avainsana “Apple”, se piilottaa viestit sisältävät “omena” tai “aPPLe” mutta ei “ananas\".</string>
<string name="settings_delete_filter_word">Poista sana “%s”?</string>
<string name="enter_selection_mode">Valitse</string>
<string name="select_all">Valitse kaikki</string>
<string name="settings_filter_duration_title">Suodattimen kesto</string>
<string name="filter_duration_custom">Mukautettu</string>
<plurals name="settings_delete_x_filter_words">
<item quantity="one">Poista %d sana?</item>
<item quantity="other">Poista %d sanaa?</item>
</plurals>
<plurals name="x_items_selected">
<item quantity="one">%d valittu</item>
<item quantity="other">%d valittu</item>
</plurals>
<string name="required_form_field_blank">Tätä ei voi jättää tyhjäksi</string>
<string name="filter_word_already_in_list">Jo luettelossa</string>
<string name="app_update_ready">Sovelluksen päivitys valmis</string>
<string name="app_update_version">Versio %s</string>
<string name="downloading_update">Ladataan (%d%%)</string>
<!-- Shown like a content warning, %s is the name of the filter -->
<string name="post_matches_filter_x">Sopii suodattimeen ”%s”</string>
<string name="search_mastodon">Etsi Mastodonista</string>
<string name="clear_all">Tyhjennä kaikki</string>
<string name="search_open_url">Avaa URL-osoite Mastodonissa</string>
<string name="posts_matching_hashtag">Julkaisut joissa on \"%s\"</string>
<string name="search_go_to_account">Siirry tiliin %s</string>
<string name="posts_matching_string">Julkaisut joissa on \"%s\"</string>
<string name="accounts_matching_string">Henkilöt jossa on \"%s\"</string>
<!-- Shown in the post header. Please keep it short -->
<string name="time_seconds_ago_short">%ds sitten</string>
<string name="time_minutes_ago_short">%dm sitten</string>
<string name="time_hours_ago_short">%dh sitten</string>
<string name="time_days_ago_short">%dd sitten</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Käännetty kielestä %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Käännetty kielestä %1$s käyttäen %2$s</string>
<string name="translation_show_original">Näytä alkuperäinen</string>
<string name="translation_failed">Käännös epäonnistui. Ehkä järjestelmänvalvoja ei ole ottanut käyttöön käännöksiä tällä palvelimella tai tällä palvelimella on käynnissä vanhempi versio Mastodonista, jossa käännöksiä ei vielä tueta.</string>
<plurals name="x_participants">
<item quantity="one">%d osallistuja</item>
<item quantity="other">%d osallistujaa</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d viesti tänään</item>
<item quantity="other">%,d viestiä tänään</item>
</plurals>
</resources>

View File

@ -211,12 +211,12 @@
<item quantity="other">%,d tagasunod</item>
</plurals>
<plurals name="x_following">
<item quantity="one">%, d Sumusunod</item>
<item quantity="other">%, d Sumusunod</item>
<item quantity="one">%,d Sumusunod</item>
<item quantity="other">%,d Sumusunod</item>
</plurals>
<plurals name="x_favorites">
<item quantity="one">%, d Paborito</item>
<item quantity="other">%, d paborito</item>
<item quantity="one">%,d Paborito</item>
<item quantity="other">%,d paborito</item>
</plurals>
<string name="timestamp_via_app">%1$s sa pamamagitan ng %2$s</string>
<string name="time_now">ngayon</string>
@ -284,4 +284,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -582,4 +582,18 @@
<string name="time_minutes_ago_short">il y a %dm</string>
<string name="time_hours_ago_short">Il y a %dh</string>
<string name="time_days_ago_short">Il y a %dj</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Traduire depuis %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Traduit depuis %1$s via %2$s</string>
<string name="translation_show_original">Afficher loriginal</string>
<string name="translation_failed">La traduction a échoué. Peut-être que ladministrateur na pas activé les traductions sur ce serveur ou que ce serveur utilise une ancienne version de Mastodon où les traductions ne sont pas encore prises en charge.</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 message aujourdhui</item>
<item quantity="other">%,d messages aujourdhui</item>
</plurals>
</resources>

View File

@ -20,4 +20,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -640,4 +640,6 @@
<string name="time_minutes_ago_short">%dm air ais</string>
<string name="time_hours_ago_short">%du air ais</string>
<string name="time_days_ago_short">%dl air ais</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -4,12 +4,14 @@
<string name="next">Seguinte</string>
<string name="loading_instance">Obtendo info do servidor…</string>
<string name="error">Erro</string>
<string name="not_a_mastodon_instance">%s non semella ser un servidor Mastodon.</string>
<string name="ok">OK</string>
<string name="preparing_auth">Preparándose para a autenticación…</string>
<string name="finishing_auth">Rematando coa autenticación…</string>
<string name="user_boosted">%s promoveu</string>
<string name="in_reply_to">Como resposta a %s</string>
<string name="notifications">Notificacións</string>
<string name="user_followed_you">%s comezou a seguirte</string>
<string name="share_toot_title">Compartir</string>
<string name="settings">Axustes</string>
<string name="publish">Publicar</string>
@ -347,4 +349,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -47,4 +47,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -227,4 +227,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -372,4 +372,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -382,4 +382,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -20,4 +20,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -368,6 +368,7 @@
<string name="welcome_to_mastodon">Selamat datang di Mastodon</string>
<string name="welcome_paragraph1">Mastodon adalah jejaring sosial terdesentralisasi, tidak ada satu pun perusahaan yang mengontrol. Semua dijalankan oleh server independen, terkoneksi bersama.</string>
<string name="what_are_servers">Apa itu server?</string>
<string name="welcome_paragraph2">Semua akun Mastodon berada pada sebuah server — dengan nilai, aturan, &amp; admin masing-masing. Mana pun yang Anda pilih, Anda dapat mengikuti dan berinteraksi dengan server mana pun.</string>
<string name="opening_link">Membuka tautan…</string>
<string name="link_not_supported">Tautan ini tidak didukung dalam aplikasi</string>
<string name="log_out_all_accounts">Keluar dari semua akun</string>
@ -552,4 +553,6 @@
<string name="time_minutes_ago_short">%dm yang lalu</string>
<string name="time_hours_ago_short">%dj yang lalu</string>
<string name="time_days_ago_short">%dh yang lalu</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -582,4 +582,21 @@
<string name="time_minutes_ago_short">fyrir %dm síðan</string>
<string name="time_hours_ago_short">fyrir %dh síðan</string>
<string name="time_days_ago_short">fyrir %dd síðan</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Þýða úr %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Þýtt úr %1$s með %2$s</string>
<string name="translation_show_original">Sýna upprunalegt</string>
<string name="translation_failed">Þýðing mistókst. Mögulega hefur kerfisstjórinn ekki virkjað þýðingar á þessum netþjóni, eða að netþjónninn sé keyrður á eldri útgáfu Mastodon þar sem þýðingar séu ekki studdar.</string>
<string name="settings_privacy">Gagnaleynd og útbreiðsla</string>
<string name="settings_discoverable">Hafa notandasnið og færslur með í reikniritum leitar</string>
<string name="settings_indexable">Hafa opinberar færslur með í leitarniðurstöðum</string>
<plurals name="x_participants">
<item quantity="one">%,d þátttakandi</item>
<item quantity="other">%,d þátttakendur</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d færsla í dag</item>
<item quantity="other">%,d færslur í dag</item>
</plurals>
</resources>

View File

@ -582,4 +582,19 @@
<string name="time_minutes_ago_short">%dmin fa</string>
<string name="time_hours_ago_short">%do fa</string>
<string name="time_days_ago_short">%dg fa</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Traduci da %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Tradotto da %1$s utilizzando %2$s</string>
<string name="translation_show_original">Mostra originale</string>
<string name="translation_failed">Traduzione fallita. Forse l\'amministratore non ha abilitato le traduzioni su questo server, o su questo server è in esecuzione una versione precedente di Mastodon in cui le traduzioni non sono ancora supportate.</string>
<string name="settings_indexable">Includi i post pubblici nei risultati di ricerca</string>
<plurals name="x_participants">
<item quantity="one">%,d participante</item>
<item quantity="other">%,d partecipanti</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d post oggi</item>
<item quantity="other">%,d post oggi</item>
</plurals>
</resources>

View File

@ -88,4 +88,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -553,4 +553,19 @@
<string name="time_minutes_ago_short">%d 分前</string>
<string name="time_hours_ago_short">%d 時間前</string>
<string name="time_days_ago_short">%d 日前</string>
<!-- %s is the name of the post language -->
<string name="translate_post">%s から翻訳</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">%2$s による %1$s からの翻訳</string>
<string name="translation_show_original">原文を表示</string>
<string name="translation_failed">翻訳に失敗しました。このサーバーは翻訳機能を有効にしていないか、翻訳機能のない旧バージョンの Mastodon を実行しているのかもしれません。</string>
<string name="settings_privacy">プライバシーとつながりやすさ</string>
<string name="settings_discoverable">アカウントを見つけやすくする</string>
<string name="settings_indexable">公開投稿を検索できるようにする</string>
<plurals name="x_participants">
<item quantity="other">参加者 %d 人</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="other">今日の投稿 %,d 件</item>
</plurals>
</resources>

View File

@ -242,4 +242,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -300,4 +300,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -180,4 +180,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -85,6 +85,10 @@
<item quantity="one">%d dag resterend</item>
<item quantity="other">%d dagen resterend</item>
</plurals>
<plurals name="x_votes">
<item quantity="one">%,d stem</item>
<item quantity="other">%,d stemmen</item>
</plurals>
<string name="poll_closed">Gesloten</string>
<string name="confirm_mute_title">Account negeren</string>
<string name="confirm_mute">Het negeren van %s bevestigen</string>
@ -383,6 +387,7 @@
<string name="welcome_to_mastodon">Welkom bij Mastodon</string>
<string name="welcome_paragraph1">Mastodon is een gedecentraliseerd sociaal netwerk, wat betekent dat geen enkel bedrijf het controleert. Het bestaat uit veel onafhankelijk opererende servers, allemaal met elkaar verbonden.</string>
<string name="what_are_servers">Wat zijn servers?</string>
<string name="welcome_paragraph2">Elk Mastodon-account wordt gehost op een server elk met diens eigen waarden, regels en beheerders. Het maakt niet uit welke server je kiest, je kunt mensen op elke server volgen en ermee communiceren.</string>
<string name="opening_link">Koppeling aan het openen…</string>
<string name="link_not_supported">Deze koppeling wordt niet ondersteund in de app</string>
<string name="log_out_all_accounts">Bij alle accounts afmelden</string>
@ -401,6 +406,7 @@
<string name="attachment_type_unknown">Bestand</string>
<string name="attachment_x_percent_uploaded">%d%% geüpload</string>
<string name="add_poll_option">Enquete-optie toevoegen</string>
<string name="poll_length">Lengte enquête</string>
<string name="poll_style">Stijl</string>
<string name="compose_poll_single_choice">Kies een</string>
<string name="compose_poll_multiple_choice">Meerkeuze</string>
@ -409,9 +415,38 @@
<string name="alt_text">Alternatieve tekst</string>
<string name="help">Hulp</string>
<string name="what_is_alt_text">Wat is alternatieve tekst?</string>
<string name="alt_text_help">Alt tekst biedt afbeeldingsbeschrijvingen voor mensen met een visiuele beperking en verbindingen met een lage bandbreedte of voor mensen die naar extra context zoeken.\n\nJe kunt de toegankelijkheid en het begrip voor iedereen verbeteren door duidelijk, beknopt en objectief te schrijven.\n\n<ul><li>Beschrijf belangrijke elementen</li>\n<li>Vat tekst in afbeeldingen samen</li>\n<li>Gebruik de reguliere zinsopbouw</li>\n<li>Vermijd overbodige informatie</li>\n<li>Focus op trends en belangrijke bevindingen in complexe beelden (zoals diagrammen of kaarten)</li></ul> \n</string>
<string name="edit_post">Bericht bewerken</string>
<string name="no_verified_link">Geen geverifieerde koppeling</string>
<string name="compose_autocomplete_emoji_empty">Emoji doorzoeken</string>
<string name="compose_autocomplete_users_empty">Vind wie je zoekt</string>
<string name="no_search_results">Deze zoektermen leveren geen resultaat op</string>
<string name="language">Taal</string>
<string name="language_default">Standaard</string>
<string name="language_system">Systeem</string>
<string name="language_detecting">Taal detecteren</string>
<string name="language_cant_detect">Kan taal niet detecteren</string>
<string name="language_detected">Gedetecteerd</string>
<string name="media_hidden">Media verborgen</string>
<string name="post_hidden">Bericht verborgen</string>
<string name="report_title_post">Bericht rapporteren</string>
<string name="forward_report_explanation">De account bevindt zich op een andere server. Wil je daar eveneens een geanonimiseerde kopie van dit rapport naar toesturen?</string>
<!-- %s is the server domain -->
<string name="forward_report_to_server">Doorsturen naar %s</string>
<!-- Shown on the "stamp" on the screen that appears after you report a post/user. Please keep the translation short, preferably a single word -->
<string name="reported">Gerapporteerd</string>
<string name="report_unfollow_explanation">Als je hun berichten niet meer in je home feed wilt zien, moet je deze persoon niet meer volgen.</string>
<string name="muted_user">%s gedempt</string>
<string name="report_sent_already_blocked">Je hebt deze gebruiker al geblokkeerd, dus je hoeft niets meer te doen terwijl we je rapport beoordelen.</string>
<string name="report_personal_already_blocked">Je hebt deze gebruiker al geblokkeerd, dus er is niets anders dat je hoeft te doen.\n\nBedankt voor het helpen om Mastodon een veilige plek voor iedereen te behouden!</string>
<string name="blocked_user">%s geblokkeerd</string>
<string name="mark_all_notifications_read">Alles als gelezen markeren</string>
<string name="settings_display">Weergave</string>
<string name="settings_filters">Filters</string>
<string name="settings_server_explanation">Overzicht, regels, moderators</string>
<!-- %s is the app name (Mastodon, key app_name). I made it a placeholder so everything Just Works™ with forks -->
<string name="about_app">Over %s</string>
<string name="default_post_language">Standaard taal bericht</string>
<string name="settings_alt_text_reminders">Alt-tekst-herinneringen toevoegen</string>
<string name="settings_confirm_unfollow">Vraag voor ontvolgen iemand</string>
<string name="settings_confirm_boost">Vragen voor boosten</string>
@ -437,6 +472,44 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="pause_notifications_ends">Eindigt %s</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="pause_notifications_banner">Notificaties worden hervat %s.</string>
<string name="resume_notifications_now">Nu hervatten</string>
<string name="open_system_notification_settings">Ga naar de meldingsinstellingen</string>
<string name="about_server">Over</string>
<string name="server_rules">Regels</string>
<string name="server_administrator">Beheerder</string>
<string name="send_email_to_server_admin">Bericht aan beheerder</string>
<string name="notifications_disabled_in_system">Schakel meldingen vanuit instellingen van uw apparaat in om van overal updates te zien.</string>
<string name="settings_even_more">Nog meer instellingen</string>
<string name="settings_show_cws">Inhoudswaarschuwingen tonen</string>
<string name="settings_hide_sensitive_media">Als gevoelig gemarkeerde media verbergen</string>
<string name="settings_show_interaction_counts">Aantal berichtinteracties</string>
<string name="settings_show_emoji_in_names">Aangepaste emoji in weergavenamen</string>
<plurals name="in_x_seconds">
<item quantity="one">in %d seconde</item>
<item quantity="other">in %d seconden</item>
</plurals>
<plurals name="in_x_minutes">
<item quantity="one">in %d minuut</item>
<item quantity="other">in %d minuten</item>
</plurals>
<plurals name="in_x_hours">
<item quantity="one">in %d uur</item>
<item quantity="other">in %d uur</item>
</plurals>
<plurals name="x_hours_ago">
<item quantity="one">%d uur geleden</item>
<item quantity="other">%d uur geleden</item>
</plurals>
<string name="alt_text_reminder_title">Media zonder alt-tekst</string>
<plurals name="alt_text_reminder_x_images">
<item quantity="one">%s van je afbeeldingen mist alt-tekst. Toch plaatsen?</item>
<item quantity="other">%s van je afbeeldingen missen alt-tekst. Toch plaatsen?</item>
</plurals>
<plurals name="alt_text_reminder_x_attachments">
<item quantity="one">%s van je mediabijlagen mist alt-tekst. Toch plaatsen?</item>
<item quantity="other">%s van je mediabijlagen missen alt-tekst. Toch plaatsen?</item>
</plurals>
<string name="count_one">Een</string>
<string name="count_two">Twee</string>
<string name="count_three">Drie</string>
@ -444,7 +517,83 @@
<string name="alt_text_reminder_post_anyway">Plaatsen</string>
<!-- %s is the username -->
<string name="unfollow_confirmation">%s ontvolgen?</string>
<string name="filter_active">Actief</string>
<string name="filter_inactive">Inactief</string>
<string name="settings_add_filter">Filter toevoegen</string>
<string name="settings_edit_filter">Filter bewerken</string>
<string name="settings_filter_duration">Duur</string>
<string name="settings_filter_muted_words">Gedempte woorden</string>
<string name="settings_filter_context">Gedempt van</string>
<string name="settings_filter_show_cw">Toon met inhoudswaarschuwing</string>
<string name="settings_filter_show_cw_explanation">Berichten die met dit filter overeenkomen, maar achter een inhoudswaarschuwing zitten toch tonen</string>
<string name="settings_delete_filter">Filter verwijderen</string>
<string name="filter_duration_forever">Voor altijd</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="settings_filter_ends">Eindigt %s</string>
<plurals name="settings_x_muted_words">
<item quantity="one">%d gedempt woord of zin</item>
<item quantity="other">%d gedempte woorden of zinnen</item>
</plurals>
<string name="selection_2_options">%1$s en %2$s</string>
<string name="selection_3_options">%1$s, %2$s en %3$s</string>
<string name="selection_4_or_more">%1$s, %2$s en %3$d meer</string>
<string name="filter_context_home_lists">Startpagina &amp; lijsten</string>
<string name="filter_context_notifications">Meldingen</string>
<string name="filter_context_public_timelines">Openbare tijdlijnen</string>
<string name="filter_context_threads_replies">Threads &amp; antwoorden</string>
<string name="filter_context_profiles">Profielen</string>
<string name="settings_filter_title">Titel</string>
<string name="settings_delete_filter_title">Filter %s verwijderen?</string>
<string name="settings_delete_filter_confirmation">Dit filter zal op al uw apparaten uit uw account worden verwijderd.</string>
<string name="add_muted_word">Gedempte woord toevoegen</string>
<string name="edit_muted_word">Gedempte woord bewerken</string>
<string name="add">Toevoegen</string>
<string name="filter_word_or_phrase">Woord of zin</string>
<string name="filter_add_word_help">Woorden zijn niet hoofdlettergevoelig en komen alleen overeen met volledige woorden.\n\nAls je op de term Apple filtert, verbergt het berichten die apple of aPpLe bevatten, maar niet ananas.</string>
<string name="settings_delete_filter_word">%s verwijderen?</string>
<string name="enter_selection_mode">Selecteren</string>
<string name="select_all">Alles selecteren</string>
<string name="settings_filter_duration_title">Filter tijdsduur</string>
<string name="filter_duration_custom">Aangepast</string>
<plurals name="settings_delete_x_filter_words">
<item quantity="one">%d woord verwijderen?</item>
<item quantity="other">%d woorden verwijderen?</item>
</plurals>
<plurals name="x_items_selected">
<item quantity="one">%d geselecteerd</item>
<item quantity="other">%d geselecteerd</item>
</plurals>
<string name="required_form_field_blank">Mag niet leeg zijn</string>
<string name="filter_word_already_in_list">Al in de lijst</string>
<string name="app_update_ready">App-update voltooid</string>
<string name="app_update_version">Versie %s</string>
<string name="downloading_update">Downloaden (%d%%)</string>
<!-- Shown like a content warning, %s is the name of the filter -->
<string name="post_matches_filter_x">Komt overeen met filter %s</string>
<string name="search_mastodon">Mastodon doorzoeken</string>
<string name="clear_all">Alles wissen</string>
<string name="search_open_url">URL in Mastodon openen</string>
<string name="posts_matching_hashtag">Berichten met %s</string>
<string name="search_go_to_account">Ga naar %s</string>
<string name="posts_matching_string">Berichten met %s</string>
<string name="accounts_matching_string">Personen met %s</string>
<!-- Shown in the post header. Please keep it short -->
<string name="time_seconds_ago_short">%ds geleden</string>
<string name="time_minutes_ago_short">%dm geleden</string>
<string name="time_hours_ago_short">%du geleden</string>
<string name="time_days_ago_short">%d dagen geleden</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Vanuit het %s vertalen</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Vertaald vanuit het %1$s door %2$s</string>
<string name="translation_show_original">Origineel bekijken</string>
<string name="translation_failed">Vertaling mislukt. Mogelijk heeft de serverbeheerder vertalingen niet ingeschakeld op deze server, of draait deze server een oude versie van Mastodon waar vertalingen nog niet worden ondersteund.</string>
<plurals name="x_participants">
<item quantity="one">%d deelnemer</item>
<item quantity="other">%d deelnemers</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d bericht vandaag</item>
<item quantity="other">%,d berichten vandaag</item>
</plurals>
</resources>

View File

@ -581,4 +581,6 @@
<string name="time_minutes_ago_short">%dm siden</string>
<string name="time_hours_ago_short">%dt siden</string>
<string name="time_days_ago_short">%dd siden</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -79,4 +79,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -457,4 +457,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -8,7 +8,7 @@
<string name="ok">OK</string>
<string name="preparing_auth">Preparando para autenticação…</string>
<string name="finishing_auth">Finalizando autenticação…</string>
<string name="user_boosted">%s impulsionado</string>
<string name="user_boosted">%s impulsionou</string>
<string name="in_reply_to">Em resposta à %s</string>
<string name="notifications">Notificações</string>
<string name="user_followed_you">%s seguiu você</string>
@ -571,4 +571,6 @@
<string name="time_minutes_ago_short">%dm atrás</string>
<string name="time_hours_ago_short">%dh atrás</string>
<string name="time_days_ago_short">%dd atrás</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -234,4 +234,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -48,4 +48,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -640,4 +640,25 @@
<string name="time_minutes_ago_short">%d мин. назад</string>
<string name="time_hours_ago_short">%d ч. назад</string>
<string name="time_days_ago_short">%d д. назад</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Перевести %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">%1$s переведён с помощью %2$s</string>
<string name="translation_show_original">Показать оригинал</string>
<string name="translation_failed">Перевод не удался. Возможно, администратор не включил переводы на этом сервере или на этом сервере установлена ​​более старая версия Mastodon, где переводы еще не поддерживаются.</string>
<string name="settings_privacy">Приватность и доступ</string>
<string name="settings_discoverable">Показывать профиль и посты в алгоритмах рекомендаций</string>
<string name="settings_indexable">Включить публичные посты в результаты поиска</string>
<plurals name="x_participants">
<item quantity="one">%,d участник</item>
<item quantity="few">%,d участника</item>
<item quantity="many">%,d участников</item>
<item quantity="other">%,d участников</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d пост сегодня</item>
<item quantity="few">%,d поста сегодня</item>
<item quantity="many">%,d постов сегодня</item>
<item quantity="other">%,d постов сегодня</item>
</plurals>
</resources>

View File

@ -75,4 +75,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -274,6 +274,8 @@
<string name="trending_posts_info_banner">To so objave, ki plenijo pozornost po Mastodonu.</string>
<string name="trending_links_info_banner">To so novice, o katerih se govori na Mastodonu.</string>
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">To so vse objave vseh uporabnikov na vašem strežniku (%s).</string>
<string name="recommended_accounts_info_banner">Glede na to, komu sledite, vam bodo ti računi všeč.</string>
<string name="see_new_posts">Pokaži nove objave</string>
<string name="load_missing_posts">Naloži manjkajoče objave</string>
<string name="follow_back">Sledijo nazaj</string>
@ -455,8 +457,11 @@
<!-- %s is the app name (Mastodon, key app_name). I made it a placeholder so everything Just Works™ with forks -->
<string name="about_app">O programu %s</string>
<string name="default_post_language">Privzeti jezik objave</string>
<string name="settings_confirm_delete_post">Vprašaj pred brisanjem objav</string>
<string name="pause_all_notifications">Premor za vse</string>
<string name="pause_notifications_off">Izklopljeno</string>
<string name="notifications_policy_anyone">Kdor koli</string>
<string name="notifications_policy_followed">Ljudje, ki vam sledijo</string>
<string name="notifications_policy_follower">Ljudje, ki jim sledite</string>
<string name="notifications_policy_no_one">Nihče</string>
<string name="notification_type_mentions_and_replies">Omembe in odgovori</string>
@ -504,6 +509,7 @@
<string name="filter_context_profiles">Profili</string>
<string name="settings_filter_title">Naslov</string>
<string name="settings_delete_filter_title">Ali želite izbrisati filter »%s«?</string>
<string name="settings_delete_filter_confirmation">Ta filter bo izbrisan iz vašega računa na vseh vaših napravah.</string>
<string name="add_muted_word">Dodaj utišano besedo</string>
<string name="edit_muted_word">Uredi utišano besedo</string>
<string name="add">Dodaj</string>
@ -532,4 +538,13 @@
<string name="time_minutes_ago_short">pred %d meseci</string>
<string name="time_hours_ago_short">pred %dh urami</string>
<string name="time_days_ago_short">pred %d dnemi</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Prevedi iz jezika: %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Prevedeno iz %1$s s pomočjo %2$s</string>
<string name="translation_show_original">Pokaži izvirnik</string>
<string name="translation_failed">Prevod je spodletel. Morda skrbnik ni omogočil prevajanja na tem strežniku ali pa strežnik teče na starejši različici Masotodona, na kateri prevajanje še ni podprto.</string>
<string name="settings_privacy">Zasebnost in dosegljivost</string>
<string name="settings_discoverable">Izpostavljaj profile in objave v algoritmih odkrivanja</string>
<string name="settings_indexable">Med zadetke iskanja vključi javne objave</string>
</resources>

View File

@ -12,6 +12,7 @@
<string name="in_reply_to">Som svar på %s</string>
<string name="notifications">Notiser</string>
<string name="user_followed_you">%s följde dig</string>
<string name="user_favorited">%s favoritmarkerade ditt inlägg</string>
<string name="share_toot_title">Dela</string>
<string name="settings">Inställningar</string>
<string name="publish">Publicera</string>
@ -229,6 +230,7 @@
<string name="file_saved">Filen sparad</string>
<string name="downloading">Laddar ner…</string>
<!-- %s is the server domain -->
<string name="recommended_accounts_info_banner">Du kanske gillar dessa konton baserat på andra konton du följer.</string>
<string name="see_new_posts">Se nya inlägg</string>
<string name="load_missing_posts">Ladda saknade inlägg</string>
<string name="follow_back">Följ tillbaka</string>
@ -353,6 +355,8 @@
<string name="attachment_description_video">%s video</string>
<string name="attachment_description_audio">%s ljud</string>
<string name="attachment_description_unknown">%s fil</string>
<string name="attachment_type_image">Bild</string>
<string name="attachment_type_video">Video</string>
<string name="attachment_type_audio">Ljud</string>
<string name="attachment_type_gif">GIF</string>
<string name="attachment_type_unknown">Fil</string>
@ -383,6 +387,7 @@
<string name="about_server">Om</string>
<string name="server_rules">Regler</string>
<string name="server_administrator">Administratör</string>
<string name="settings_show_emoji_in_names">Anpassad emoji i visningsnamn</string>
<plurals name="in_x_hours">
<item quantity="one">om %d timme</item>
<item quantity="other">om %d timmar</item>
@ -391,6 +396,9 @@
<item quantity="one">%d timme sedan</item>
<item quantity="other">%d timmar sedan</item>
</plurals>
<string name="count_two">Två</string>
<string name="count_three">Tre</string>
<string name="count_four">Fyra</string>
<!-- %s is the username -->
<string name="filter_active">Aktiv</string>
<string name="filter_inactive">Inaktiv</string>
@ -401,12 +409,19 @@
<string name="selection_2_options">%1$s och %2$s</string>
<string name="selection_3_options">%1$s, %2$s och %3$s</string>
<string name="filter_context_profiles">Profiler</string>
<string name="settings_delete_filter_confirmation">Detta filter kommer raderas från ditt konto på alla dina enheter.</string>
<string name="add">Lägg till</string>
<string name="filter_word_or_phrase">Ord eller fras</string>
<string name="enter_selection_mode">Välj</string>
<string name="select_all">Välj alla</string>
<plurals name="settings_delete_x_filter_words">
<item quantity="one">Radera %d ord?</item>
<item quantity="other">Radera %d ord?</item>
</plurals>
<string name="app_update_version">Version %s</string>
<!-- Shown like a content warning, %s is the name of the filter -->
<string name="post_matches_filter_x">Matchar filter \"%s\"</string>
<string name="search_mastodon">Sök i Mastodon</string>
<string name="clear_all">Rensa alla</string>
<string name="search_open_url">Öppna URL i Mastodon</string>
<string name="posts_matching_hashtag">Inlägg med \"%s\"</string>
@ -416,4 +431,7 @@
<string name="time_minutes_ago_short">%dm sedan</string>
<string name="time_hours_ago_short">%dt sedan</string>
<string name="time_days_ago_short">%dd sedan</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Översätt från %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -553,4 +553,19 @@
<string name="time_minutes_ago_short">%d นาทีที่แล้ว</string>
<string name="time_hours_ago_short">%d ชั่วโมงที่แล้ว</string>
<string name="time_days_ago_short">%d วันที่แล้ว</string>
<!-- %s is the name of the post language -->
<string name="translate_post">แปลจาก %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">แปลจาก %1$s โดยใช้ %2$s</string>
<string name="translation_show_original">แสดงดั้งเดิม</string>
<string name="translation_failed">การแปลล้มเหลว บางทีผู้ดูแลอาจไม่ได้เปิดใช้งานการแปลในเซิร์ฟเวอร์นี้หรือเซิร์ฟเวอร์นี้กำลังใช้ Mastodon รุ่นเก่ากว่าที่ยังไม่รองรับการแปล</string>
<string name="settings_privacy">ความเป็นส่วนตัวและการเข้าถึง</string>
<string name="settings_discoverable">แสดงโปรไฟล์และโพสต์ในอัลกอริทึมการค้นพบ</string>
<string name="settings_indexable">รวมโพสต์สาธารณะในผลลัพธ์การค้นหา</string>
<plurals name="x_participants">
<item quantity="other">%,d ผู้มีส่วนร่วม</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="other">%,d โพสต์วันนี้</item>
</plurals>
</resources>

View File

@ -387,6 +387,7 @@
<string name="welcome_to_mastodon">Mastodon\'a hoş geldiniz</string>
<string name="welcome_paragraph1">Mastodon merkezi olmayan bir sosyal ağdır, yani onu tek bir şirket kontrol etmiyor. Hepsi birbirine bağlı, bağımsız olarak çalışan birçok sunucudan oluşur.</string>
<string name="what_are_servers">Sunucular nedir?</string>
<string name="welcome_paragraph2">Her Mastodon hesabı bir sunucuda barındırılır - her birinin kendi değerleri, kuralları ve yöneticileri vardır. Hangisini seçerseniz seçin, herhangi bir sunucudaki insanları takip edebilir ve onlarla etkileşime geçebilirsiniz.</string>
<string name="opening_link">Bağlantıılıyor…</string>
<string name="link_not_supported">Bu bağlantı uygulamada desteklenmiyor</string>
<string name="log_out_all_accounts">Tüm hesaplardan çıkış yap</string>
@ -581,4 +582,21 @@
<string name="time_minutes_ago_short">%ddk önce</string>
<string name="time_hours_ago_short">%dsa önce</string>
<string name="time_days_ago_short">%dg önce</string>
<!-- %s is the name of the post language -->
<string name="translate_post">%s dilinden çevrildi</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">%2$s kullanılarak %1$s dilinden çevrildi</string>
<string name="translation_show_original">Özgün içeriği göster</string>
<string name="translation_failed">Çeviri başarısız oldu. Yönetici bu sunucuda çevirileri etkinleştirmemiş olabilir veya bu sunucu çevirilerin henüz desteklenmediği eski bir Mastodon sürümünü çalıştırıyor olabilir.</string>
<string name="settings_privacy">Gizlilik ve erişim</string>
<string name="settings_discoverable">Profil ve gönderileri keşif algoritmalarında kullan</string>
<string name="settings_indexable">Herkese açık gönderileri arama sonuçlarına ekle</string>
<plurals name="x_participants">
<item quantity="one">%d katılımcılar</item>
<item quantity="other">%d katılımcılar</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d bunu gönder</item>
<item quantity="other">%,d bunları gönder</item>
</plurals>
</resources>

View File

@ -425,6 +425,7 @@
<string name="welcome_to_mastodon">Вітаємо у Mastodon</string>
<string name="welcome_paragraph1">Mastodon - це децентралізована соціальна мережа, тобто жодна компанія не контролює її. Вона складається з багатьох незалежних серверів, які з\'єднані між собою.</string>
<string name="what_are_servers">Що таке сервери?</string>
<string name="welcome_paragraph2">Кожен обліковий запис Mastodon розміщений на сервері - кожен сервер має свої цінності, правила, й &amp; адмінів. Немає різниці, який ви оберете, ви зможете підписуватися та спілкуватися з користувачами з будь-якого.</string>
<string name="opening_link">Відкриття посилання…</string>
<string name="link_not_supported">Це посилання не підтримується застосунком</string>
<string name="log_out_all_accounts">Вийти з усіх акаунтів</string>
@ -639,4 +640,25 @@
<string name="time_minutes_ago_short">%dхв. тому</string>
<string name="time_hours_ago_short">%dгод. тому</string>
<string name="time_days_ago_short">%dд. тому</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Перекласти з %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Перекладено з %1$s за допомогою %2$s</string>
<string name="translation_show_original">Показати оригінал</string>
<string name="translation_failed">Не вдалося виконати переклад. Можливо, адміністратор не активував переклади на цьому сервері або цей сервер використовує стару версію Mastodon, де переклади ще не підтримуються.</string>
<string name="settings_privacy">Приватність і досяжність</string>
<string name="settings_discoverable">Враховувати профіль та дописи в алгоритмах пошуку</string>
<string name="settings_indexable">Включити загальнодоступні дописи в результати пошуку</string>
<plurals name="x_participants">
<item quantity="one">%,d учасник</item>
<item quantity="few">%,d учасники</item>
<item quantity="many">%,d учасників</item>
<item quantity="other">%,d учасника</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d допис сьогодні</item>
<item quantity="few">%,d дописи сьогодні</item>
<item quantity="many">%,d дописів сьогодні</item>
<item quantity="other">%,d дописа сьогодні</item>
</plurals>
</resources>

View File

@ -20,4 +20,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@ -266,7 +266,7 @@
<item quantity="other">%,d lượt thích</item>
</plurals>
<plurals name="x_reblogs">
<item quantity="other">%,đăng lại</item>
<item quantity="other">%,d đăng lại</item>
</plurals>
<string name="timestamp_via_app">%1$s qua %2$s</string>
<string name="time_now">vừa xong</string>
@ -553,4 +553,16 @@
<string name="time_minutes_ago_short">%dp</string>
<string name="time_hours_ago_short">%dh</string>
<string name="time_days_ago_short">%d ngày</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Dịch từ %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Dịch từ %1$s bằng %2$s</string>
<string name="translation_show_original">Bản gốc</string>
<string name="translation_failed">Dịch không thành công. Có thể quản trị viên chưa bật dịch trên máy chủ này hoặc máy chủ này đang chạy phiên bản cũ hơn của Mastodon chưa hỗ trợ dịch.</string>
<plurals name="x_participants">
<item quantity="other">%,d người thảo luận</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="other">%,d tút hôm nay</item>
</plurals>
</resources>

View File

@ -552,4 +552,13 @@
<string name="time_minutes_ago_short">%d 分钟前</string>
<string name="time_hours_ago_short">%d 小时前</string>
<string name="time_days_ago_short">%d 天前</string>
<!-- %s is the name of the post language -->
<string name="translate_post">从 %s 翻译</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">从 %1$s 翻译成 %2$s</string>
<string name="translation_show_original">显示原文</string>
<string name="translation_failed">翻译失败。此服务器运行的 Mastodon 版本不支持翻译功能,或管理员未将翻译功能开启。</string>
<string name="settings_privacy">隐私与可达性</string>
<string name="settings_discoverable">在发现算法中展示您的个人资料和嘟文</string>
<string name="settings_indexable">将您的公开嘟文纳入搜索范围</string>
</resources>

View File

@ -553,4 +553,19 @@
<string name="time_minutes_ago_short">%d 分鐘前</string>
<string name="time_hours_ago_short">%d 小時前</string>
<string name="time_days_ago_short">%d 天前</string>
<!-- %s is the name of the post language -->
<string name="translate_post">翻譯自 %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">透過 %2$s 翻譯 %1$s</string>
<string name="translation_show_original">顯示原文</string>
<string name="translation_failed">翻譯失敗。也許管理員未於此伺服器啟用翻譯功能,或此伺服器為未支援翻譯功能之舊版本 Mastodon。</string>
<string name="settings_privacy">隱私權及觸及</string>
<string name="settings_discoverable">於探索演算法中推薦個人檔案及嘟文</string>
<string name="settings_indexable">允許公開嘟文顯示於搜尋結果中</string>
<plurals name="x_participants">
<item quantity="other">%,d 位參與者</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="other">本日共 %,d 則嘟文</item>
</plurals>
</resources>

View File

@ -36,6 +36,7 @@
<item name="android:windowActionModeOverlay">true</item>
<item name="android:actionModeBackground">?colorM3PrimaryContainer</item>
<item name="android:actionModeCloseDrawable">@drawable/ic_fluent_dismiss_24_regular</item>
<!-- <item name="appkitEmptyTextAppearance">@style/empty_text</item>-->
<item name="android:statusBarColor">?colorM3Background</item>
<item name="android:navigationBarColor">?colorM3Background</item>
@ -72,6 +73,7 @@
<item name="android:windowActionModeOverlay">true</item>
<item name="android:actionModeBackground">?colorM3PrimaryContainer</item>
<item name="android:actionModeCloseDrawable">@drawable/ic_fluent_dismiss_24_regular</item>
<!-- <item name="appkitEmptyTextAppearance">@style/empty_text</item>-->
<item name="colorM3DisabledBackground">#1FE3E3E3</item>
<item name="colorM3Error">#F2B8B5</item>
@ -381,4 +383,8 @@
<style name="window_fade_out">
<item name="android:windowExitAnimation">@anim/fade_out_fast</item>
</style>
<style name="empty_text" parent="m3_body_large">
<item name="android:textColor">?colorM3OnSurfaceVariant</item>
</style>
</resources>

View File

@ -0,0 +1,116 @@
// run: java tools/VerifyTranslatedStringFormatting.java
// Reads all localized strings and makes sure they contain valid formatting placeholders matching the original English strings to avoid crashes.
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
public class VerifyTranslatedStringFormatting{
// %[argument_index$][flags][width][.precision][t]conversion
private static final String formatSpecifier="%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
private static final Pattern fsPattern=Pattern.compile(formatSpecifier);
private static HashMap<String, List<String>> placeholdersInStrings=new HashMap<>();
private static int errorCount=0;
public static void main(String[] args) throws Exception{
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc;
try(FileInputStream in=new FileInputStream("mastodon/src/main/res/values/strings.xml")){
doc=builder.parse(in);
}
NodeList list=doc.getDocumentElement().getChildNodes(); // why does this stupid NodeList thing exist at all?
for(int i=0;i<list.getLength();i++){
if(list.item(i) instanceof Element el){
String name=el.getAttribute("name");
String value;
if("string".equals(el.getTagName())){
value=el.getTextContent();
}else if("plurals".equals(el.getTagName())){
value=el.getElementsByTagName("item").item(0).getTextContent();
}else{
System.out.println("Warning: unexpected tag "+name);
continue;
}
ArrayList<String> placeholders=new ArrayList<>();
Matcher matcher=fsPattern.matcher(value);
while(matcher.find()){
placeholders.add(matcher.group());
}
placeholdersInStrings.put(name, placeholders);
}
}
for(File file:new File("mastodon/src/main/res").listFiles()){
if(file.getName().startsWith("values-")){
File stringsXml=new File(file, "strings.xml");
if(stringsXml.exists()){
processFile(stringsXml);
}
}
}
if(errorCount>0){
System.err.println("Found "+errorCount+" problems in localized strings");
System.exit(1);
}
}
private static void processFile(File file) throws Exception{
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc;
try(FileInputStream in=new FileInputStream(file)){
doc=builder.parse(in);
}
NodeList list=doc.getDocumentElement().getChildNodes();
for(int i=0;i<list.getLength();i++){
if(list.item(i) instanceof Element el){
String name=el.getAttribute("name");
String value;
if("string".equals(el.getTagName())){
value=el.getTextContent();
if(!verifyString(value, placeholdersInStrings.get(name))){
errorCount++;
System.out.println(file+": string "+name+" is missing placeholders");
}
}else if("plurals".equals(el.getTagName())){
NodeList items=el.getElementsByTagName("item");
for(int j=0;j<items.getLength();j++){
Element item=(Element)items.item(j);
value=item.getTextContent();
String quantity=item.getAttribute("quantity");
if(!verifyString(value, placeholdersInStrings.get(name))){
// Some languages use zero/one/two for just these numbers so they may skip the placeholder
// still make sure that there's no '%' characters to avoid crashes
if(List.of("zero", "one", "two").contains(quantity) && !value.contains("%")){
continue;
}
errorCount++;
System.out.println(file+": string "+name+"["+quantity+"] is missing placeholders");
}
}
}else{
System.out.println("Warning: unexpected tag "+name);
continue;
}
}
}
}
private static boolean verifyString(String str, List<String> placeholders){
for(String placeholder:placeholders){
if(placeholder.equals("%,d")){
// %,d and %d are interchangeable but %,d provides nicer formatting
if(!str.contains(placeholder) && !str.contains("%d"))
return false;
}else if(!str.contains(placeholder)){
return false;
}
}
return true;
}
}