diff --git a/AppShared/Info.plist b/AppShared/Info.plist index 73f11cd2..920eaff8 100644 --- a/AppShared/Info.plist +++ b/AppShared/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.3.1 CFBundleVersion - 109 + 113 diff --git a/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings b/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings index cde27dc9..49183a43 100644 --- a/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/ar_SA/Intents.strings @@ -1,4 +1,4 @@ -"16wxgf" = "النَشر على ماستودون"; +"16wxgf" = "النَّشرُ عَلَى مَاستودُون"; "751xkl" = "محتوى نصي"; @@ -14,7 +14,7 @@ "RxSqsb" = "مَنشور"; -"WCIR3D" = "نَشر ${content} على ماستودون"; +"WCIR3D" = "نَشرُ ${content} عَلَى مَاستودُون"; "ZKJSNu" = "مَنشور"; @@ -32,9 +32,9 @@ "ayoYEb-ehFLjY" = "${content}، المُتابِعُون فقط"; -"dUyuGg" = "النشر على ماستدون"; +"dUyuGg" = "النَّشرُ عَلَى مَاستودُون"; -"dYQ5NN" = "للعامة"; +"dYQ5NN" = "لِلعَامَّة"; "ehFLjY" = "لمتابعيك فقط"; diff --git a/Localization/StringsConvertor/Intents/input/it_IT/Intents.strings b/Localization/StringsConvertor/Intents/input/it_IT/Intents.strings index 6877490b..d26aef14 100644 --- a/Localization/StringsConvertor/Intents/input/it_IT/Intents.strings +++ b/Localization/StringsConvertor/Intents/input/it_IT/Intents.strings @@ -1,51 +1,51 @@ -"16wxgf" = "Post on Mastodon"; +"16wxgf" = "Pubblica su Mastodon"; -"751xkl" = "Text Content"; +"751xkl" = "Contenuto testuale"; -"CsR7G2" = "Post on Mastodon"; +"CsR7G2" = "Pubblica su Mastodon"; -"HZSGTr" = "What content to post?"; +"HZSGTr" = "Quale contenuto postare?"; -"HdGikU" = "Posting failed"; +"HdGikU" = "Pubblicazione non riuscita"; -"KDNTJ4" = "Failure Reason"; +"KDNTJ4" = "Motivo del fallimento"; -"RHxKOw" = "Send Post with text content"; +"RHxKOw" = "Invia post con contenuto testuale"; -"RxSqsb" = "Post"; +"RxSqsb" = "Pubblica"; -"WCIR3D" = "Post ${content} on Mastodon"; +"WCIR3D" = "Pubblica ${content} su Mastodon"; -"ZKJSNu" = "Post"; +"ZKJSNu" = "Pubblica"; "ZS1XaK" = "${content}"; -"ZbSjzC" = "Visibility"; +"ZbSjzC" = "Visibilità"; -"Zo4jgJ" = "Post Visibility"; +"Zo4jgJ" = "Visibilità del post"; -"apSxMG-dYQ5NN" = "There are ${count} options matching ‘Public’."; +"apSxMG-dYQ5NN" = "Ci sono ${count} opzioni corrispondenti a 'Pubblico'."; -"apSxMG-ehFLjY" = "There are ${count} options matching ‘Followers Only’."; +"apSxMG-ehFLjY" = "Ci sono ${count} opzioni corrispondenti a ‘Solo Seguaci’."; -"ayoYEb-dYQ5NN" = "${content}, Public"; +"ayoYEb-dYQ5NN" = "${content}, Pubblico"; -"ayoYEb-ehFLjY" = "${content}, Followers Only"; +"ayoYEb-ehFLjY" = "${content}, Solo seguaci"; -"dUyuGg" = "Post on Mastodon"; +"dUyuGg" = "Pubblica su Mastodon"; -"dYQ5NN" = "Public"; +"dYQ5NN" = "Pubblico"; -"ehFLjY" = "Followers Only"; +"ehFLjY" = "Solo i seguaci"; -"gfePDu" = "Posting failed. ${failureReason}"; +"gfePDu" = "Pubblicazione fallita. ${failureReason}"; -"k7dbKQ" = "Post was sent successfully."; +"k7dbKQ" = "Post inviato con successo."; -"oGiqmY-dYQ5NN" = "Just to confirm, you wanted ‘Public’?"; +"oGiqmY-dYQ5NN" = "Solo per confermare, volevi ‘Pubblico’?"; -"oGiqmY-ehFLjY" = "Just to confirm, you wanted ‘Followers Only’?"; +"oGiqmY-ehFLjY" = "Solo per confermare, volevi 'Solo seguaci'?"; "rM6dvp" = "URL"; -"ryJLwG" = "Post was sent successfully. "; +"ryJLwG" = "Post inviato con successo. "; diff --git a/Localization/StringsConvertor/Intents/input/it_IT/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/it_IT/Intents.stringsdict index 18422c77..75d98a9e 100644 --- a/Localization/StringsConvertor/Intents/input/it_IT/Intents.stringsdict +++ b/Localization/StringsConvertor/Intents/input/it_IT/Intents.stringsdict @@ -5,7 +5,7 @@ There are ${count} options matching ‘${content}’. - 2 NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${content}’. + Ci sono %#@count_option@ corrispondenti a «${content}». count_option NSStringFormatSpecTypeKey @@ -13,15 +13,15 @@ NSStringFormatValueTypeKey %ld one - 1 option + 1 opzione other - %ld options + %ld opzioni There are ${count} options matching ‘${visibility}’. NSStringLocalizedFormatKey - There are %#@count_option@ matching ‘${visibility}’. + Ci sono %#@count_option@ corrispondenti a «${visibility}». count_option NSStringFormatSpecTypeKey @@ -29,9 +29,9 @@ NSStringFormatValueTypeKey %ld one - 1 option + 1 opzione other - %ld options + %ld opzioni diff --git a/Localization/StringsConvertor/Intents/input/vi_VN/Intents.strings b/Localization/StringsConvertor/Intents/input/vi_VN/Intents.strings new file mode 100644 index 00000000..a9533731 --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/vi_VN/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Đăng lên Mastodon"; + +"751xkl" = "Nội dung văn bản"; + +"CsR7G2" = "Đăng lên Mastodon"; + +"HZSGTr" = "Đăng loại nội dung nào?"; + +"HdGikU" = "Không thể đăng"; + +"KDNTJ4" = "Lý do không thể đăng"; + +"RHxKOw" = "Gửi tút với nội dung là chữ"; + +"RxSqsb" = "Tút"; + +"WCIR3D" = "Đăng ${content} lên Mastodon"; + +"ZKJSNu" = "Tút"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Hiển thị"; + +"Zo4jgJ" = "Thay đổi quyền riêng tư"; + +"apSxMG-dYQ5NN" = "Có ${count} lựa chọn khớp với ‘Công khai’."; + +"apSxMG-ehFLjY" = "Có ${count} lựa chọn khớp với ‘Riêng tư’."; + +"ayoYEb-dYQ5NN" = "${content}, Công khai"; + +"ayoYEb-ehFLjY" = "${content}, Riêng tư"; + +"dUyuGg" = "Đăng lên Mastodon"; + +"dYQ5NN" = "Công khai"; + +"ehFLjY" = "Riêng tư"; + +"gfePDu" = "Không thể đăng. ${failureReason}"; + +"k7dbKQ" = "Đã đăng tút thành công."; + +"oGiqmY-dYQ5NN" = "Xin xác nhận, bạn muốn ‘Công khai’?"; + +"oGiqmY-ehFLjY" = "Xin xác nhận, bạn muốn ‘Riêng tư’?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Đã đăng tút thành công. "; diff --git a/Localization/StringsConvertor/Intents/input/vi_VN/Intents.stringsdict b/Localization/StringsConvertor/Intents/input/vi_VN/Intents.stringsdict new file mode 100644 index 00000000..a4bb783d --- /dev/null +++ b/Localization/StringsConvertor/Intents/input/vi_VN/Intents.stringsdict @@ -0,0 +1,34 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + Có %#@count_option@ khớp với ‘${content}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + other + %ld lựa chọn + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + Có %#@count_option@ khớp với ‘${visibility}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + other + %ld lựa chọn + + + + diff --git a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift index 14266a45..606a9520 100644 --- a/Localization/StringsConvertor/Sources/StringsConvertor/main.swift +++ b/Localization/StringsConvertor/Sources/StringsConvertor/main.swift @@ -56,6 +56,7 @@ private func map(language: String) -> String? { case "fr_FR": return "fr" // French case "de_DE": return "de" // German case "ja_JP": return "ja" // Japanese + case "kab_KAB": return "kab" // Kabyle case "kmr_TR": return "ku" // Kurmanji (Kurdish) case "ru_RU": return "ru" // Russian case "gd_GB": return "gd-GB" // Scottish Gaelic @@ -63,6 +64,7 @@ private func map(language: String) -> String? { case "es_AR": return "es-419" // Spanish, Argentina case "sv_FI": return "sv_FI" // Swedish, Finland case "th_TH": return "th" // Thai + case "vi_VN": return "vi" // Vietnamese default: return nil } } diff --git a/Localization/StringsConvertor/input/ar_SA/Localizable.stringsdict b/Localization/StringsConvertor/input/ar_SA/Localizable.stringsdict index 0b28c577..c2f64172 100644 --- a/Localization/StringsConvertor/input/ar_SA/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ar_SA/Localizable.stringsdict @@ -170,6 +170,30 @@ %ld إعادة تدوين + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + لا رَدّ + one + رَدٌّ واحِد + two + رَدَّانِ اِثنان + few + %ld رُدُود + many + %ld رَدًّا + other + %ld رَدّ + + plural.count.vote NSStringLocalizedFormatKey @@ -447,15 +471,15 @@ zero مُنذُ لَحظة one - مُنذُ سنة + مُنذُ %ldع two - مُنذُ سنتين + مُنذُ %ldع few - مُنذُ %ld سنين + مُنذُ %ldع many - مُنذُ %ld سنةً + مُنذُ %ldع other - مُنذُ %ld سنة + مُنذُ %ldع date.month.ago.abbr @@ -471,15 +495,15 @@ zero مُنذُ لَحظة one - مُنذُ شهر + مُنذُ %ldش two - مُنذُ شهرين + مُنذُ %ldش few - مُنذُ %ld أشهُر + مُنذُ %ldش many - مُنذُ %ld شهرًا + مُنذُ %ldش other - مُنذُ %ld شهر + مُنذُ %ldش date.day.ago.abbr @@ -495,15 +519,15 @@ zero مُنذُ لَحظة one - مُنذُ يوم + مُنذُ %ldي two - مُنذُ يومين + مُنذُ %ldي few - مُنذُ %ld أيام + مُنذُ %ldي many - مُنذُ %ld يومًا + مُنذُ %ldي other - مُنذُ %ld يوم + مُنذُ %ldي date.hour.ago.abbr @@ -519,15 +543,15 @@ zero مُنذُ لَحظة one - مُنذُ ساعة + مُنذُ %ldس two - مُنذُ ساعتين + مُنذُ %ldس few - مُنذُ %ld ساعات + مُنذُ %ldس many - مُنذُ %ld ساعةًَ + مُنذُ %ldس other - مُنذُ %ld ساعة + مُنذُ %ldس date.minute.ago.abbr @@ -543,15 +567,15 @@ zero مُنذُ لَحظة one - مُنذُ دقيقة + مُنذُ %ldد two - مُنذُ دقيقتان + مُنذُ %ldد few - مُنذُ %ld دقائق + مُنذُ %ldد many - مُنذُ %ld دقيقةً + مُنذُ %ldد other - مُنذُ %ld دقيقة + مُنذُ %ldد date.second.ago.abbr @@ -567,15 +591,15 @@ zero مُنذُ لَحظة one - مُنذُ ثانية + مُنذُ %ldث two - مُنذُ ثانيتين + مُنذُ %ldث few - مُنذُ %ld ثوان + مُنذُ %ldث many - مُنذُ %ld ثانية + مُنذُ %ldث other - مُنذُ %ld ثانية + مُنذُ %ldث diff --git a/Localization/StringsConvertor/input/ar_SA/app.json b/Localization/StringsConvertor/input/ar_SA/app.json index 51b33472..d9712536 100644 --- a/Localization/StringsConvertor/input/ar_SA/app.json +++ b/Localization/StringsConvertor/input/ar_SA/app.json @@ -32,9 +32,9 @@ "message": "يتعذَّر تعديل الملف التعريفي. يُرجى المُحاولة مرة أُخرى." }, "sign_out": { - "title": "تسجيل الخروج", + "title": "تَسجيلُ الخُروج", "message": "هل أنت متأكد من رغبتك في تسجيل الخُروج؟", - "confirm": "تسجيل الخروج" + "confirm": "تَسجيلُ الخُروج" }, "block_domain": { "title": "هل أنتَ مُتأكِّدٌ حقًا مِن رغبتك في حظر %s بالكامل؟ في معظم الحالات، يكون مِنَ الكافي والمُفَضَّل استهداف عدد محدود للحظر أو الكتم. لن ترى محتوى من هذا النطاق وسوف يُزال جميع متابعيك المتواجدين فيه.", @@ -49,8 +49,8 @@ "message": "هَل أنتَ مُتأكِدٌ مِن رَغبتِكَ فِي حَذفِ هَذَا المَنشُور؟" }, "clean_cache": { - "title": "مَحو ذاكرة التخزين المؤقت", - "message": "تمَّ مَحو %s مِن ذاكرة التخزين المؤقت بنجاح." + "title": "مَحوُ ذاكِرَةِ التَّخزينِ المُؤقَّت", + "message": "مُحِيَ ما مَساحَتُهُ %s مِن ذاكِرَةِ التَّخزينِ المُؤقَّت بِنجاح." } }, "controls": { @@ -61,7 +61,7 @@ "open": "فتح", "add": "إضافة", "remove": "حذف", - "edit": "تحرير", + "edit": "تَحرير", "save": "حفظ", "ok": "حسنًا", "done": "تمّ", @@ -79,7 +79,7 @@ "see_more": "عرض المزيد", "preview": "مُعاينة", "share": "المُشارك", - "share_user": "مُشاركة %s", + "share_user": "مُشارَكَةُ %s", "share_post": "مشارك المنشور", "open_in_safari": "الفَتحُ في Safari", "open_in_browser": "الفَتحُ في المُتَصَفِّح", @@ -87,7 +87,7 @@ "manually_search": "البحث يدويًا بدلًا من ذلك", "skip": "تخطي", "reply": "الرَّد", - "report_user": "الإبلاغ عن %s", + "report_user": "الإبلاغُ عَن %s", "block_domain": "حظر %s", "unblock_domain": "رفع الحظر عن %s", "settings": "الإعدادات", @@ -103,7 +103,7 @@ "common": { "switch_to_tab": "التبديل إلى %s", "compose_new_post": "تأليف منشور جديد", - "show_favorites": "إظهار المُفضَّلة", + "show_favorites": "أظْهِر المُفضَّلة", "open_settings": "فَتحُ الإعدادات" }, "timeline": { @@ -124,12 +124,13 @@ } }, "status": { - "user_reblogged": "أعادَ %s تدوينها", + "user_reblogged": "أعادَ %s تَدوينَها", "user_replied_to": "رَدًا على %s", - "show_post": "إظهار منشور", - "show_user_profile": "إظهار الملف التعريفي للمُستخدِم", + "show_post": "أظْهِر مَنشور", + "show_user_profile": "أظْهِر المِلَفَّ التَّعريفِيَّ لِلمُستَخدِم", "content_warning": "تحذير المُحتوى", - "media_content_warning": "انقر للكشف", + "media_content_warning": "اُنقُر لِلكَشف", + "tap_to_reveal": "اُنقُر لِلكَشف", "poll": { "vote": "صَوِّت", "closed": "انتهى" @@ -141,14 +142,18 @@ "favorite": "التفضيل", "unfavorite": "إزالة التفضيل", "menu": "القائمة", - "hide": "إخفاء" + "hide": "إخفاء", + "show_image": "أظْهِرِ الصُّورَة", + "show_gif": "أظْهِر GIF", + "show_video_player": "أظْهِر مُشَغِّلَ المَقاطِعِ المَرئِيَّة", + "tap_then_hold_to_show_menu": "اُنقُر مُطَوَّلًا لِإظْهَارِ القائِمَة" }, "tag": { "url": "عنوان URL", "mention": "إشارة", "link": "رابط", "hashtag": "وسم", - "email": "بريد إلكتروني", + "email": "بَريدٌ إلِكتُرُونِيّ", "emoji": "رمز تعبيري" }, "visibility": { @@ -164,17 +169,17 @@ "request": "إرسال طَلَب", "pending": "قيد المُراجعة", "block": "حظر", - "block_user": "حظر %s", + "block_user": "حَظرُ %s", "block_domain": "حظر %s", "unblock": "رفع الحَظر", "unblock_user": "رفع الحَظر عن %s", "blocked": "محظور", "mute": "كَتم", - "mute_user": "كَتم %s", + "mute_user": "كَتمُ %s", "unmute": "رفع الكتم", "unmute_user": "رفع الكتم عن %s", "muted": "مكتوم", - "edit_info": "تعديل المعلومات" + "edit_info": "تَحريرُ المَعلُومات" }, "timeline": { "filtered": "مُصفَّى", @@ -184,7 +189,7 @@ "loader": { "load_missing_posts": "تحميل المَنشورات المَفقودَة", "loading_missing_posts": "يَجري تحميل المَنشورات المَفقودَة...", - "show_more_replies": "إظهار مَزيد مِنَ الرُّدود" + "show_more_replies": "أظْهِر مَزيدًا مِنَ الرُّدود" }, "header": { "no_status_found": "لَم يُعْثَر على مَنشورات", @@ -249,14 +254,14 @@ "delete": "حذف" }, "username": { - "placeholder": "اِسم مُستَخدِم", + "placeholder": "اِسمُ مُستَخدِم", "duplicate_prompt": "اِسم المُستَخدِم هذا مأخوذٌ بالفعل." }, "display_name": { - "placeholder": "اِسم العَرض" + "placeholder": "اِسمُ عَرض" }, "email": { - "placeholder": "بريد إلكتروني" + "placeholder": "بَريدٌ إلِكتُرُونِيّ" }, "password": { "placeholder": "رمز سري", @@ -266,7 +271,7 @@ "checked": "مُتَحَققٌ مِنه", "unchecked": "غيرُ مُتَحَققٍ مِنه" }, - "hint": "يجب أن يكون رمزك السري مكوَّن من ثمان خانات على الأقل" + "hint": "يَجِبُ أن يَحتَوي رَمزُكَ السِرِّيَ علَى ثَمانِ خاناتٍ أقلًا" }, "invite": { "registration_user_invite_request": "لماذا ترغب في الانضمام؟" @@ -274,7 +279,7 @@ }, "error": { "item": { - "username": "اِسم المُستَخدِم", + "username": "اِسمُ المُستَخدِم", "email": "البريد الإلكتروني", "password": "الرمز السري", "agreement": "الاِتِّفاقيَّة", @@ -345,7 +350,7 @@ }, "compose": { "title": { - "new_post": "منشور جديد", + "new_post": "مَنشُورٌ جَديد", "new_reply": "رَدٌّ جديد" }, "media_selection": { @@ -377,10 +382,10 @@ "placeholder": "اكتب تَحذيرًا دَقيقًا هُنا..." }, "visibility": { - "public": "للعامة", + "public": "لِلعَامَّة", "unlisted": "غير مُدرَج", "private": "للمُتابِعينَ فقط", - "direct": "للأشخاص المُشار إليهم فقط" + "direct": "لِمَن أشرتُ إليهِم فَقَط" }, "auto_complete": { "space_to_add": "انقر على مساحة لإضافتِها" @@ -407,7 +412,7 @@ "dashboard": { "posts": "مَنشورات", "following": "مُتابَع", - "followers": "متابِع" + "followers": "مُتابِع" }, "fields": { "add_row": "إضافة صف", @@ -419,7 +424,7 @@ "segmented_control": { "posts": "مَنشورات", "replies": "رُدُود", - "posts_and_replies": "المَنشوراتُ وَالرُدود", + "posts_and_replies": "مَنشُوراتٌ وَرُدُود", "media": "وَسائِط", "about": "حَول" }, @@ -440,6 +445,12 @@ "title": "رَفعُ الحَظرِ عَنِ الحِساب", "message": "تأكيدُ رَفع الحَظرِ عَن %s" } + }, + "accessibility": { + "show_avatar_image": "أظْهِر الصُّورَةَ الرَّمزِيَّة", + "edit_avatar_image": "تَحريرُ الصُّورَةِ الرَّمزِيَّة", + "show_banner_image": "أظْهِر صُورَةَ الرَّايَة", + "double_tap_to_open_the_list": "اُنقُر نَقرًا مُزدَوَجًا لِفَتحِ القائِمَة" } }, "follower": { @@ -449,15 +460,15 @@ "footer": "لا يُمكِن عَرض المُتابَعات مِنَ الخوادم الأُخرى." }, "search": { - "title": "البحث", + "title": "البَحث", "search_bar": { - "placeholder": "البحث عن وسوم أو مستخدمين", + "placeholder": "اِبحَث عَن وُسُومٍ أو مُستَخدِمين", "cancel": "إلغاء" }, "recommend": { "button_text": "إظهار الكُل", "hash_tag": { - "title": "ذُو شعبيَّة على ماستودون", + "title": "ذُو شَعبِيَّةٍ عَلَى مَاستودُون", "description": "الوُسُومُ الَّتي تَحظى بقدرٍ كبيرٍ مِنَ الاِهتمام", "people_talking": "%s أشخاص يتحدَّثوا" }, @@ -470,9 +481,9 @@ "searching": { "segment": { "all": "الكُل", - "people": "الأشخاص", - "hashtags": "الوُسُوم", - "posts": "المَنشورات" + "people": "أشخاص", + "hashtags": "وُسُوم", + "posts": "مَنشُورات" }, "empty_state": { "no_results": "لا تُوجَدُ نتائِج" @@ -481,6 +492,14 @@ "clear": "مَحو" } }, + "discovery": { + "tabs": { + "posts": "المنشورات", + "hashtags": "الوسوم", + "news": "الأخبار", + "for_you": "For You" + } + }, "favorite": { "title": "مُفضَّلَتُك" }, @@ -498,8 +517,8 @@ "poll_has_ended": "انتهى استطلاعُ الرأي" }, "keyobard": { - "show_everything": "إظهار كل شيء", - "show_mentions": "إظهار الإشارات" + "show_everything": "أظْهِر كُلَّ شَيء", + "show_mentions": "أظْهِر الإشارَات" } }, "thread": { @@ -541,7 +560,8 @@ "true_black_dark_mode": "النَّمَطُ الأسوَدُ الداكِنُ الحَقيقي", "disable_avatar_animation": "تَعطيلُ الصوَرِ الرمزيَّةِ المُتحرِّكَة", "disable_emoji_animation": "تَعطيلُ الرُموزِ التَّعبيريَّةِ المُتحرِّكَة", - "using_default_browser": "اِستِخدامُ المُتصفِّحِ الاِفتراضي لِفتحِ الرَّوابِط" + "using_default_browser": "اِستِخدامُ المُتصفِّحِ الاِفتراضي لِفتحِ الرَّوابِط", + "open_links_in_mastodon": "فَتحُ الرَّوابِطِ فِي مَاستودُون" }, "boring_zone": { "title": "المنطِقَةُ المُملَّة", @@ -551,7 +571,7 @@ }, "spicy_zone": { "title": "المنطِقَةُ اللَّاذِعَة", - "clear": "مَحوُ ذاكِرَةُ التَّخزينِ المُؤقت لِلوسائِط", + "clear": "مَحوُ ذاكِرَةِ التَّخزينِ المُؤقَّتِ لِلوسائِط", "signout": "تَسجيلُ الخُروج" } }, @@ -578,12 +598,12 @@ "preview": { "keyboard": { "close_preview": "إغلاق المُعايَنَة", - "show_next": "إظهار التالي", - "show_previous": "إظهار السابق" + "show_next": "أظْهِر التَّالي", + "show_previous": "أظْهِر السَّابِق" } }, "account_list": { - "tab_bar_hint": "المِلف المُحدَّد حاليًا: %s. انقر نقرًا مزدوجًا مع الاستمرار لإظهار مُبدِّل الحِساب", + "tab_bar_hint": "المِلَفُّ المُحدَّدُ حالِيًّا: %s. اُنقُر نَقرًا مُزدَوَجًا مَعَ الاِستِمرارِ لِإظهارِ مُبدِّلِ الحِساب", "dismiss_account_switcher": "تجاهُل مبدِّل الحِساب", "add_account": "إضافَةُ حِساب" }, diff --git a/Localization/StringsConvertor/input/ar_SA/ios-infoPlist.json b/Localization/StringsConvertor/input/ar_SA/ios-infoPlist.json index 22fb2868..1535679c 100644 --- a/Localization/StringsConvertor/input/ar_SA/ios-infoPlist.json +++ b/Localization/StringsConvertor/input/ar_SA/ios-infoPlist.json @@ -1,6 +1,6 @@ { "NSCameraUsageDescription": "يُستخدم لالتقاط الصورة عِندَ نشر الحالات", "NSPhotoLibraryAddUsageDescription": "يُستخدم لحِفظ الصورة في مكتبة الصور", - "NewPostShortcutItemTitle": "منشور جديد", + "NewPostShortcutItemTitle": "مَنشُورٌ جَديد", "SearchShortcutItemTitle": "البحث" } diff --git a/Localization/StringsConvertor/input/ca_ES/Localizable.stringsdict b/Localization/StringsConvertor/input/ca_ES/Localizable.stringsdict index 140185ba..dfbd38c0 100644 --- a/Localization/StringsConvertor/input/ca_ES/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ca_ES/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld impulsos + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 Resposta + other + %ld respostes + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ca_ES/app.json b/Localization/StringsConvertor/input/ca_ES/app.json index c3aac1e5..c3484260 100644 --- a/Localization/StringsConvertor/input/ca_ES/app.json +++ b/Localization/StringsConvertor/input/ca_ES/app.json @@ -41,11 +41,11 @@ "block_entire_domain": "Bloquejar Domini" }, "save_photo_failure": { - "title": "Desa l'Error de la Foto", + "title": "Error al Desar la Foto", "message": "Activa el permís d'accés a la biblioteca de fotos per desar-la." }, "delete_post": { - "title": "Estàs segur que vols suprimir aquesta publicació?", + "title": "Esborrar Publicació", "message": "Estàs segur que vols suprimir aquesta publicació?" }, "clean_cache": { @@ -83,7 +83,7 @@ "share_post": "Compartir Publicació", "open_in_safari": "Obrir a Safari", "open_in_browser": "Obre al navegador", - "find_people": "Busca persones per seguir", + "find_people": "Busca persones a seguir", "manually_search": "Cerca manualment a canvi", "skip": "Omet", "reply": "Respon", @@ -110,26 +110,27 @@ "previous_status": "Publicació anterior", "next_status": "Publicació següent", "open_status": "Obre la publicació", - "open_author_profile": "Obre el perfil de l'autor", - "open_reblogger_profile": "Obre el perfil del impulsor", - "reply_status": "Respon a la publicació", - "toggle_reblog": "Commuta l'impuls de la publicació", - "toggle_favorite": "Commuta el Favorit de la publicació", + "open_author_profile": "Obre el Perfil de l'Autor", + "open_reblogger_profile": "Obre el Perfil del Impulsor", + "reply_status": "Respon a la Publicació", + "toggle_reblog": "Commuta l'Impuls de la Publicació", + "toggle_favorite": "Commuta el Favorit de la Publicació", "toggle_content_warning": "Commuta l'Avís de Contingut", "preview_image": "Vista prèvia de l'Imatge" }, "segmented_control": { - "previous_section": "Secció anterior", - "next_section": "Secció següent" + "previous_section": "Secció Anterior", + "next_section": "Secció Següent" } }, "status": { "user_reblogged": "%s ha impulsat", "user_replied_to": "Ha respòs a %s", - "show_post": "Mostra la publicació", + "show_post": "Mostra la Publicació", "show_user_profile": "Mostra el perfil de l'usuari", "content_warning": "Advertència de Contingut", - "media_content_warning": "Toca qualsevol lloc per mostrar", + "media_content_warning": "Toca qualsevol lloc per a mostrar", + "tap_to_reveal": "Toca per a mostrar", "poll": { "vote": "Vota", "closed": "Finalitzada" @@ -141,7 +142,11 @@ "favorite": "Favorit", "unfavorite": "Desfer Favorit", "menu": "Menú", - "hide": "Amaga" + "hide": "Amaga", + "show_image": "Mostra la imatge", + "show_gif": "Mostra el GIF", + "show_video_player": "Mostra el reproductor de vídeo", + "tap_then_hold_to_show_menu": "Toca i manté per a veure el menú" }, "tag": { "url": "URL", @@ -182,15 +187,15 @@ "now": "Ara" }, "loader": { - "load_missing_posts": "Carrega les publicacions que falten", - "loading_missing_posts": "Carregant les publicacions que falten...", + "load_missing_posts": "Carrega les publicacions faltants", + "loading_missing_posts": "Carregant les publicacions faltants...", "show_more_replies": "Mostra més respostes" }, "header": { "no_status_found": "No s'ha trobat cap publicació", "blocking_warning": "No pots veure el perfil d'aquest usuari\n fins que el desbloquegis.\nEl teu perfil els sembla així.", "user_blocking_warning": "No pots veure el perfil de %s\n fins que el desbloquegis.\nEl teu perfil els sembla així.", - "blocked_warning": "No pots veure el perfil d'aquest usuari\n fins que et desbloquegi.", + "blocked_warning": "No pots veure el perfil d'aquest usuari\nfins que et desbloquegi.", "user_blocked_warning": "No pots veure el perfil de %s\n fins que et desbloquegi.", "suspended_warning": "Aquest usuari ha estat suspès.", "user_suspended_warning": "El compte de %s ha estat suspès." @@ -205,7 +210,7 @@ "log_in": "Inicia sessió" }, "server_picker": { - "title": "Tria un servidor,\nqualsevol servidor.", + "title": "Mastodon està fet d'usuaris en diferents comunitats.", "subtitle": "Tria una comunitat segons els teus interessos, regió o una de propòsit general.", "subtitle_extend": "Tria una comunitat segons els teus interessos, regió o una de propòsit general. Cada comunitat és operada per una organització totalment independent o individualment.", "button": { @@ -266,7 +271,7 @@ "checked": "verificat", "unchecked": "no verificat" }, - "hint": "La teva contrasenya ha de tenir com a mínim buit caràcters" + "hint": "La teva contrasenya ha de tenir com a mínim vuit caràcters" }, "invite": { "registration_user_invite_request": "Perquè vols unir-te?" @@ -294,7 +299,7 @@ "inclusion": "%s no és un valor suportat" }, "special": { - "username_invalid": "El nom d'usuari només ha de contenir caràcters alfanumèrics i guions baixos", + "username_invalid": "El nom d'usuari ha de contenir només caràcters alfanumèrics i guions baixos", "username_too_long": "El nom d'usuari és massa llarg (no pot ser més llarg de 30 caràcters)", "email_invalid": "Aquesta no és una adreça de correu electrònic vàlida", "password_too_short": "La contrasenya és massa curta (ha de tenir 8 caràcters com a mínim)" @@ -313,7 +318,7 @@ }, "confirm_email": { "title": "Una última cosa.", - "subtitle": "Acabem d'enviar un correu electrònic a %s,\ntoca l'enllaç per a confirmar el teu compte.", + "subtitle": "Toca l'enllaç del correu electrònic que t'hem enviat per a confirmar el teu compte.", "button": { "open_email_app": "Obre l'aplicació de correu", "resend": "Reenvia" @@ -440,6 +445,12 @@ "title": "Desbloqueja el Compte", "message": "Confirma per a desbloquejar %s" } + }, + "accessibility": { + "show_avatar_image": "Mostra l'imatge del avatar", + "edit_avatar_image": "Edita l'imatge del avatar", + "show_banner_image": "Mostra l'imatge del bàner", + "double_tap_to_open_the_list": "Doble toc per a veure la llista" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Neteja" } }, + "discovery": { + "tabs": { + "posts": "Publicacions", + "hashtags": "Etiquetes", + "news": "Notícies", + "for_you": "Per a tu" + } + }, "favorite": { "title": "Els teus Favorits" }, @@ -532,7 +551,7 @@ "anyone": "algú", "follower": "un seguidor", "follow": "a qualsevol que segueixi", - "noone": "algú", + "noone": "ningú", "title": "Notifica'm quan" } }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Mode negre fosc autèntic", "disable_avatar_animation": "Desactiva avatars animats", "disable_emoji_animation": "Desactiva emojis animats", - "using_default_browser": "Utilitza el navegador predeterminat per a obrir enllaços" + "using_default_browser": "Utilitza el navegador predeterminat per a obrir enllaços", + "open_links_in_mastodon": "Obre enllaços a Mastodon" }, "boring_zone": { "title": "La Zona Avorrida", diff --git a/Localization/StringsConvertor/input/cy_GB/Localizable.stringsdict b/Localization/StringsConvertor/input/cy_GB/Localizable.stringsdict index e6b0d5f9..e0f1e0f2 100644 --- a/Localization/StringsConvertor/input/cy_GB/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/cy_GB/Localizable.stringsdict @@ -170,6 +170,30 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + %ld replies + one + 1 reply + two + %ld replies + few + %ld replies + many + %ld replies + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/cy_GB/app.json b/Localization/StringsConvertor/input/cy_GB/app.json index ad99e178..548c5ada 100644 --- a/Localization/StringsConvertor/input/cy_GB/app.json +++ b/Localization/StringsConvertor/input/cy_GB/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Show user profile", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Closed" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/StringsConvertor/input/da_DK/Localizable.stringsdict b/Localization/StringsConvertor/input/da_DK/Localizable.stringsdict index 730e2902..503ff9db 100644 --- a/Localization/StringsConvertor/input/da_DK/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/da_DK/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/da_DK/app.json b/Localization/StringsConvertor/input/da_DK/app.json index ad99e178..548c5ada 100644 --- a/Localization/StringsConvertor/input/da_DK/app.json +++ b/Localization/StringsConvertor/input/da_DK/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Show user profile", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Closed" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/StringsConvertor/input/de_DE/Localizable.stringsdict b/Localization/StringsConvertor/input/de_DE/Localizable.stringsdict index 66b7f2a2..20e8b615 100644 --- a/Localization/StringsConvertor/input/de_DE/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/de_DE/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld Reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 Antwort + other + %ld Antworten + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/de_DE/app.json b/Localization/StringsConvertor/input/de_DE/app.json index f62f9f95..522b3f77 100644 --- a/Localization/StringsConvertor/input/de_DE/app.json +++ b/Localization/StringsConvertor/input/de_DE/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Benutzerprofil anzeigen", "content_warning": "Inhaltswarnung", "media_content_warning": "Tippe irgendwo zum Anzeigen", + "tap_to_reveal": "Zum Anzeigen tippen", "poll": { "vote": "Abstimmen", "closed": "Beendet" @@ -141,7 +142,11 @@ "favorite": "Favorit", "unfavorite": "Aus Favoriten entfernen", "menu": "Menü", - "hide": "Verstecken" + "hide": "Verstecken", + "show_image": "Bild anzeigen", + "show_gif": "GIF anzeigen", + "show_video_player": "Zeige Video-Player", + "tap_then_hold_to_show_menu": "Halte gedrückt um das Menü anzuzeigen" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Konto entsperren", "message": "Bestätige %s zu entsperren" } + }, + "accessibility": { + "show_avatar_image": "Profilbild anzeigen", + "edit_avatar_image": "Profilbild bearbeiten", + "show_banner_image": "Bannerbild anzeigen", + "double_tap_to_open_the_list": "Doppeltippen, um die Liste zu öffnen" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Zurücksetzen" } }, + "discovery": { + "tabs": { + "posts": "Beiträge", + "hashtags": "Hashtags", + "news": "Nachrichten", + "for_you": "Für dich" + } + }, "favorite": { "title": "Deine Favoriten" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Vollständig dunkler Dunkelmodus", "disable_avatar_animation": "Animierte Profilbilder deaktivieren", "disable_emoji_animation": "Animierte Emojis deaktivieren", - "using_default_browser": "Standardbrowser zum Öffnen von Links verwenden" + "using_default_browser": "Standardbrowser zum Öffnen von Links verwenden", + "open_links_in_mastodon": "Links in Mastodon öffnen" }, "boring_zone": { "title": "Der langweilige Bereich", diff --git a/Localization/StringsConvertor/input/en_US/Localizable.stringsdict b/Localization/StringsConvertor/input/en_US/Localizable.stringsdict index 730e2902..503ff9db 100644 --- a/Localization/StringsConvertor/input/en_US/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/en_US/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/en_US/app.json b/Localization/StringsConvertor/input/en_US/app.json index ad99e178..548c5ada 100644 --- a/Localization/StringsConvertor/input/en_US/app.json +++ b/Localization/StringsConvertor/input/en_US/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Show user profile", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Closed" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/StringsConvertor/input/es_AR/Localizable.stringsdict b/Localization/StringsConvertor/input/es_AR/Localizable.stringsdict index f4f0097e..9d1fdadb 100644 --- a/Localization/StringsConvertor/input/es_AR/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/es_AR/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld adhesiones + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 respuesta + other + %ld respuestas + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/es_AR/app.json b/Localization/StringsConvertor/input/es_AR/app.json index 106ffb64..581ccd83 100644 --- a/Localization/StringsConvertor/input/es_AR/app.json +++ b/Localization/StringsConvertor/input/es_AR/app.json @@ -45,7 +45,7 @@ "message": "Por favor, habilitá el permiso de acceso a la biblioteca de fotos para guardar la imagen." }, "delete_post": { - "title": "¿Estás seguro que querés eliminar este mensaje?", + "title": "Eliminar mensaje", "message": "¿Estás seguro que querés eliminar este mensaje?" }, "clean_cache": { @@ -130,6 +130,7 @@ "show_user_profile": "Mostrar perfil de usuario", "content_warning": "Advertencia de contenido", "media_content_warning": "Toca en cualquier lugar para mostrar", + "tap_to_reveal": "Tocá para mostrar", "poll": { "vote": "Votar", "closed": "Cerrada" @@ -141,7 +142,11 @@ "favorite": "Marcar como favorito", "unfavorite": "Dejar de marcar como favorito", "menu": "Menú", - "hide": "Ocultar" + "hide": "Ocultar", + "show_image": "Mostrar imagen", + "show_gif": "Mostrar GIF", + "show_video_player": "Mostrar reproductor de video", + "tap_then_hold_to_show_menu": "Tocá y mantené presionado para mostrar el menú" }, "tag": { "url": "Dirección web", @@ -440,6 +445,12 @@ "title": "Desbloquear cuenta", "message": "Confirmá para desbloquear a %s" } + }, + "accessibility": { + "show_avatar_image": "Mostrar imagen de avatar", + "edit_avatar_image": "Editar imagen de avatar", + "show_banner_image": "Mostrar imagen de banner", + "double_tap_to_open_the_list": "Tocá dos veces para abrir la lista" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Limpiar" } }, + "discovery": { + "tabs": { + "posts": "Mensajes", + "hashtags": "Etiquetas", + "news": "Novedades", + "for_you": "Para vos" + } + }, "favorite": { "title": "Tus favoritos" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Modo negro oscuro real", "disable_avatar_animation": "Deshabilitar avatares animados", "disable_emoji_animation": "Deshabilitar emojis animados", - "using_default_browser": "Usar navegador web predeterminado para abrir enlaces" + "using_default_browser": "Usar navegador web predeterminado para abrir enlaces", + "open_links_in_mastodon": "Abrir enlaces en Mastodon" }, "boring_zone": { "title": "La zona aburrida", diff --git a/Localization/StringsConvertor/input/es_ES/Localizable.stringsdict b/Localization/StringsConvertor/input/es_ES/Localizable.stringsdict index 186218af..24e407d0 100644 --- a/Localization/StringsConvertor/input/es_ES/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/es_ES/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogueos + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/es_ES/app.json b/Localization/StringsConvertor/input/es_ES/app.json index cfebae26..d4215ecb 100644 --- a/Localization/StringsConvertor/input/es_ES/app.json +++ b/Localization/StringsConvertor/input/es_ES/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Mostrar perfil del usuario", "content_warning": "Advertencia de Contenido", "media_content_warning": "Pulsa en cualquier sitio para mostrar", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vota", "closed": "Cerrado" @@ -141,7 +142,11 @@ "favorite": "Favorito", "unfavorite": "No favorito", "menu": "Menú", - "hide": "Ocultar" + "hide": "Ocultar", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Desbloquear cuenta", "message": "Confirmar para desbloquear a %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Borrar" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Tus Favoritos" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Modo oscuro negro real", "disable_avatar_animation": "Deshabilitar avatares animados", "disable_emoji_animation": "Deshabilitar emojis animados", - "using_default_browser": "Usar navegador predeterminado para abrir los enlaces" + "using_default_browser": "Usar navegador predeterminado para abrir los enlaces", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "La Zona Aburrida", diff --git a/Localization/StringsConvertor/input/eu_ES/Localizable.stringsdict b/Localization/StringsConvertor/input/eu_ES/Localizable.stringsdict index 817e8372..871fb10b 100644 --- a/Localization/StringsConvertor/input/eu_ES/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/eu_ES/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld bultzada + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Erantzun bat + other + %ld erantzun + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/eu_ES/app.json b/Localization/StringsConvertor/input/eu_ES/app.json index 39d06227..8547ae26 100644 --- a/Localization/StringsConvertor/input/eu_ES/app.json +++ b/Localization/StringsConvertor/input/eu_ES/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Erakutsi erabiltzailearen profila", "content_warning": "Edukiaren abisua", "media_content_warning": "Ukitu edonon bistaratzeko", + "tap_to_reveal": "Sakatu erakusteko", "poll": { "vote": "Bozkatu", "closed": "Itxita" @@ -141,7 +142,11 @@ "favorite": "Gogokoa", "unfavorite": "Kendu gogokoa", "menu": "Menua", - "hide": "Ezkutatu" + "hide": "Ezkutatu", + "show_image": "Erakutsi irudia", + "show_gif": "Erakutsi GIFa", + "show_video_player": "Erakutsi bideo-erreproduzigailua", + "tap_then_hold_to_show_menu": "Sakatu eta eutsi menua erakusteko" }, "tag": { "url": "URLa", @@ -440,6 +445,12 @@ "title": "Desblokeatu kontua", "message": "Berretsi %s desblokeatzea" } + }, + "accessibility": { + "show_avatar_image": "Erakutsi abatarra", + "edit_avatar_image": "Editatu abatarra", + "show_banner_image": "Erakutsi banner irudia", + "double_tap_to_open_the_list": "Sakatu birritan zerrenda irekitzeko" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Garbitu" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Zure gogokoak" }, @@ -491,8 +510,8 @@ }, "notification_description": { "followed_you": "zu jarraitzen hasi da", - "favorited_your_post": "erabiltzaileak zure bidalketa gogoko du", - "reblogged_your_post": "erabiltzaileak bultzada eman dio zure bidalketari", + "favorited_your_post": "(e)k zure bidalketa gogoko du", + "reblogged_your_post": "(e)k bultzada eman dio zure bidalketari", "mentioned_you": "erabiltzaileak aipatu zaitu", "request_to_follow_you": "erabiltzaileak zu jarraitzea eskatu du", "poll_has_ended": "inkesta amaitu da" @@ -541,7 +560,8 @@ "true_black_dark_mode": "Benetako modu beltz iluna", "disable_avatar_animation": "Desgaitu abatar animatuak", "disable_emoji_animation": "Desgaitu emoji animatuak", - "using_default_browser": "Erabili nabigatzaile lehenetsia estekak irekitzeko" + "using_default_browser": "Erabili nabigatzaile lehenetsia estekak irekitzeko", + "open_links_in_mastodon": "Ireki estekak Mastodonen" }, "boring_zone": { "title": "Eremu aspergarria", diff --git a/Localization/StringsConvertor/input/fr_FR/Localizable.stringsdict b/Localization/StringsConvertor/input/fr_FR/Localizable.stringsdict index 37f07e67..5c2b1497 100644 --- a/Localization/StringsConvertor/input/fr_FR/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/fr_FR/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 réponse + other + %ld réponses + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/fr_FR/app.json b/Localization/StringsConvertor/input/fr_FR/app.json index 9941ff99..642d2c37 100644 --- a/Localization/StringsConvertor/input/fr_FR/app.json +++ b/Localization/StringsConvertor/input/fr_FR/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Montrer le profil de l’utilisateur·rice", "content_warning": "Avertissement de contenu", "media_content_warning": "Tapotez n’importe où pour révéler la publication", + "tap_to_reveal": "Appuyer pour afficher", "poll": { "vote": "Voter", "closed": "Fermé" @@ -141,7 +142,11 @@ "favorite": "Favori", "unfavorite": "Retirer des favoris", "menu": "Menu", - "hide": "Cacher" + "hide": "Cacher", + "show_image": "Afficher l’image", + "show_gif": "Afficher le GIF", + "show_video_player": "Afficher le lecteur vidéo", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Débloquer le compte", "message": "Confirmer le déblocage de %s" } + }, + "accessibility": { + "show_avatar_image": "Afficher l’avatar", + "edit_avatar_image": "Modifier l’avatar", + "show_banner_image": "Afficher l’image de la bannière", + "double_tap_to_open_the_list": "Appuyer deux fois pour ouvrir la liste" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Effacer" } }, + "discovery": { + "tabs": { + "posts": "Messages", + "hashtags": "Hashtags", + "news": "Actualité", + "for_you": "Pour vous" + } + }, "favorite": { "title": "Vos favoris" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Vrai mode sombre", "disable_avatar_animation": "Désactiver les avatars animés", "disable_emoji_animation": "Désactiver les émojis animées", - "using_default_browser": "Utiliser le navigateur par défaut pour ouvrir les liens" + "using_default_browser": "Utiliser le navigateur par défaut pour ouvrir les liens", + "open_links_in_mastodon": "Ouvrir les liens dans Mastodon" }, "boring_zone": { "title": "La zone ennuyante", diff --git a/Localization/StringsConvertor/input/gd_GB/Localizable.stringsdict b/Localization/StringsConvertor/input/gd_GB/Localizable.stringsdict index 7a54f553..b149323e 100644 --- a/Localization/StringsConvertor/input/gd_GB/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/gd_GB/Localizable.stringsdict @@ -142,6 +142,26 @@ %ld brosnachadh + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld fhreagairt + two + %ld fhreagairt + few + %ld freagairtean + other + %ld freagairt + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/gd_GB/app.json b/Localization/StringsConvertor/input/gd_GB/app.json index 520293d4..7053105d 100644 --- a/Localization/StringsConvertor/input/gd_GB/app.json +++ b/Localization/StringsConvertor/input/gd_GB/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Seall pròifil a’ chleachdaiche", "content_warning": "Rabhadh susbainte", "media_content_warning": "Thoir gnogag àite sam bith gus a nochdadh", + "tap_to_reveal": "Thoir gnogag gus a nochdadh", "poll": { "vote": "Cuir bhòt", "closed": "Dùinte" @@ -141,7 +142,11 @@ "favorite": "Cuir ris na h-annsachdan", "unfavorite": "Thoir air falbh o na h-annsachdan", "menu": "Clàr-taice", - "hide": "Falaich" + "hide": "Falaich", + "show_image": "Seall an dealbh", + "show_gif": "Seall an GIF", + "show_video_player": "Seall cluicheadair video", + "tap_then_hold_to_show_menu": "Thoir gnogag ’s cùm sìos a shealltainn a’ chlàir-thaice" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Dì-bhac an cunntas", "message": "Dearbh dì-bhacadh %s" } + }, + "accessibility": { + "show_avatar_image": "Seall dealbh an avatar", + "edit_avatar_image": "Deasaich dealbh an avatar", + "show_banner_image": "Seall dealbh brataich", + "double_tap_to_open_the_list": "Thoir gnogag dhùbailte a dh’fhosgladh na liosta" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Falamhaich" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Na h-annsachdan agad" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Modh dubh dorcha", "disable_avatar_animation": "Cuir beothachadh nan avataran à comas", "disable_emoji_animation": "Cuir beothachadh nan Emojis à comas", - "using_default_browser": "Cleachd am brabhsair bunaiteach airson ceanglaichean fhosgladh" + "using_default_browser": "Cleachd am brabhsair bunaiteach airson ceanglaichean fhosgladh", + "open_links_in_mastodon": "Fosgail ceanglaichean ann am Mastodon" }, "boring_zone": { "title": "An earrann ràsanach", diff --git a/Localization/StringsConvertor/input/hi_IN/Localizable.stringsdict b/Localization/StringsConvertor/input/hi_IN/Localizable.stringsdict index 730e2902..503ff9db 100644 --- a/Localization/StringsConvertor/input/hi_IN/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/hi_IN/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/hi_IN/app.json b/Localization/StringsConvertor/input/hi_IN/app.json index ad99e178..846a3f66 100644 --- a/Localization/StringsConvertor/input/hi_IN/app.json +++ b/Localization/StringsConvertor/input/hi_IN/app.json @@ -2,8 +2,8 @@ "common": { "alerts": { "common": { - "please_try_again": "Please try again.", - "please_try_again_later": "Please try again later." + "please_try_again": "कृपया फिर से प्रयास करें।", + "please_try_again_later": "बाद में फिर से प्रयास करें।" }, "sign_up_failure": { "title": "Sign Up Failure" @@ -45,7 +45,7 @@ "message": "Please enable the photo library access permission to save the photo." }, "delete_post": { - "title": "Delete Post", + "title": "पोस्ट को हटाएं", "message": "Are you sure you want to delete this post?" }, "clean_cache": { @@ -130,6 +130,7 @@ "show_user_profile": "Show user profile", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Closed" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/StringsConvertor/input/id_ID/Localizable.stringsdict b/Localization/StringsConvertor/input/id_ID/Localizable.stringsdict index 88c0fac9..a4a0f493 100644 --- a/Localization/StringsConvertor/input/id_ID/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/id_ID/Localizable.stringsdict @@ -100,6 +100,20 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/id_ID/app.json b/Localization/StringsConvertor/input/id_ID/app.json index c6af04e0..c6fe5610 100644 --- a/Localization/StringsConvertor/input/id_ID/app.json +++ b/Localization/StringsConvertor/input/id_ID/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Tampilkan Profil Pengguna", "content_warning": "Peringatan Konten", "media_content_warning": "Ketuk di mana saja untuk melihat", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Ditutup" @@ -141,7 +142,11 @@ "favorite": "Favorit", "unfavorite": "Unfavorite", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Hapus" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "Zona Membosankan", diff --git a/Localization/StringsConvertor/input/it_IT/Localizable.stringsdict b/Localization/StringsConvertor/input/it_IT/Localizable.stringsdict index 730e2902..71098060 100644 --- a/Localization/StringsConvertor/input/it_IT/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/it_IT/Localizable.stringsdict @@ -13,15 +13,15 @@ NSStringFormatValueTypeKey ld one - 1 unread notification + 1 notifica non letta other - %ld unread notification + %ld notifiche non lette a11y.plural.count.input_limit_exceeds NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ + Il limite di input supera %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -29,15 +29,15 @@ NSStringFormatValueTypeKey ld one - 1 character + 1 carattere other - %ld characters + %ld caratteri a11y.plural.count.input_limit_remains NSStringLocalizedFormatKey - Input limit remains %#@character_count@ + Il limite di input rimane %#@character_count@ character_count NSStringFormatSpecTypeKey @@ -45,9 +45,9 @@ NSStringFormatValueTypeKey ld one - 1 character + 1 carattere other - %ld characters + %ld caratteri plural.count.metric_formatted.post @@ -63,7 +63,7 @@ one post other - posts + post plural.count.post @@ -79,7 +79,7 @@ one 1 post other - %ld posts + %ld post plural.count.favorite @@ -93,9 +93,9 @@ NSStringFormatValueTypeKey ld one - 1 favorite + 1 preferito other - %ld favorites + %ld preferiti plural.count.reblog @@ -109,9 +109,25 @@ NSStringFormatValueTypeKey ld one - 1 reblog + 1 condivisione other - %ld reblogs + %ld condivisioni + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 risposta + other + %ld risposte plural.count.vote @@ -125,9 +141,9 @@ NSStringFormatValueTypeKey ld one - 1 vote + 1 voto other - %ld votes + %ld voti plural.count.voter @@ -141,9 +157,9 @@ NSStringFormatValueTypeKey ld one - 1 voter + 1 votante other - %ld voters + %ld votanti plural.people_talking @@ -157,9 +173,9 @@ NSStringFormatValueTypeKey ld one - 1 people talking + 1 persona ne parla other - %ld people talking + %ld persone ne parlano plural.count.following @@ -175,7 +191,7 @@ one 1 following other - %ld following + %ld stanno seguendo plural.count.follower @@ -189,9 +205,9 @@ NSStringFormatValueTypeKey ld one - 1 follower + 1 seguace other - %ld followers + %ld seguaci date.year.left @@ -205,9 +221,9 @@ NSStringFormatValueTypeKey ld one - 1 year left + 1 anno rimasto other - %ld years left + %ld anni rimasti date.month.left @@ -221,9 +237,9 @@ NSStringFormatValueTypeKey ld one - 1 months left + 1 mese rimasto other - %ld months left + %ld mesi rimasti date.day.left @@ -237,9 +253,9 @@ NSStringFormatValueTypeKey ld one - 1 day left + 1 giorno rimasto other - %ld days left + %ld giorni rimasti date.hour.left @@ -253,9 +269,9 @@ NSStringFormatValueTypeKey ld one - 1 hour left + 1 ora rimasta other - %ld hours left + %ld ore rimaste date.minute.left @@ -269,9 +285,9 @@ NSStringFormatValueTypeKey ld one - 1 minute left + 1 minuto rimasto other - %ld minutes left + %ld minuti rimasti date.second.left @@ -285,9 +301,9 @@ NSStringFormatValueTypeKey ld one - 1 second left + 1 secondo rimasto other - %ld seconds left + %ld secondi rimasti date.year.ago.abbr @@ -301,9 +317,9 @@ NSStringFormatValueTypeKey ld one - 1y ago + 1 anno fa other - %ldy ago + %ld anni fa date.month.ago.abbr @@ -317,9 +333,9 @@ NSStringFormatValueTypeKey ld one - 1M ago + 1 mese fa other - %ldM ago + %ld mesi fa date.day.ago.abbr @@ -333,9 +349,9 @@ NSStringFormatValueTypeKey ld one - 1d ago + 1 giorno fa other - %ldd ago + %ld giorni fa date.hour.ago.abbr @@ -349,9 +365,9 @@ NSStringFormatValueTypeKey ld one - 1h ago + 1 ora fa other - %ldh ago + %ld ore fa date.minute.ago.abbr @@ -365,9 +381,9 @@ NSStringFormatValueTypeKey ld one - 1m ago + 1 minuto fa other - %ldm ago + %ld minuti fa date.second.ago.abbr @@ -381,9 +397,9 @@ NSStringFormatValueTypeKey ld one - 1s ago + 1 secondo fa other - %lds ago + %ld secondi fa diff --git a/Localization/StringsConvertor/input/it_IT/app.json b/Localization/StringsConvertor/input/it_IT/app.json index ad99e178..24044016 100644 --- a/Localization/StringsConvertor/input/it_IT/app.json +++ b/Localization/StringsConvertor/input/it_IT/app.json @@ -2,595 +2,615 @@ "common": { "alerts": { "common": { - "please_try_again": "Please try again.", - "please_try_again_later": "Please try again later." + "please_try_again": "Per favore riprova.", + "please_try_again_later": "Per favore, riprova più tardi." }, "sign_up_failure": { - "title": "Sign Up Failure" + "title": "Iscrizione fallita" }, "server_error": { - "title": "Server Error" + "title": "Errore del server" }, "vote_failure": { - "title": "Vote Failure", - "poll_ended": "The poll has ended" + "title": "Voto fallito", + "poll_ended": "Il sondaggio è terminato" }, "discard_post_content": { - "title": "Discard Draft", - "message": "Confirm to discard composed post content." + "title": "Elimina bozza", + "message": "Confermare di scartare il contenuto del post composto." }, "publish_post_failure": { - "title": "Publish Failure", - "message": "Failed to publish the post.\nPlease check your internet connection.", + "title": "Pubblicazione fallita", + "message": "Pubblicazione del post fallita.\nPer favore verifica la tua connessione internet.", "attachments_message": { - "video_attach_with_photo": "Cannot attach a video to a post that already contains images.", - "more_than_one_video": "Cannot attach more than one video." + "video_attach_with_photo": "Impossibile allegare un filmato a un post che contiene già immagini.", + "more_than_one_video": "Impossibile allegare più di un filmato." } }, "edit_profile_failure": { - "title": "Edit Profile Error", - "message": "Cannot edit profile. Please try again." + "title": "Errore nella modifica del profilo", + "message": "Impossibile modificare il profilo. Per favore, riprova." }, "sign_out": { - "title": "Sign Out", - "message": "Are you sure you want to sign out?", - "confirm": "Sign Out" + "title": "Esci", + "message": "Vuoi davvero scollegarti?", + "confirm": "Esci" }, "block_domain": { - "title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain and any of your followers from that domain will be removed.", - "block_entire_domain": "Block Domain" + "title": "Vuoi davvero bloccare %s completamente? Nella maggioranza dei casi, è preferibile e sufficiente bloccare o silenziare pochi account in modo mirato. Non vedrai i contenuti di quel dominio e tutti i tuoi follower da quel dominio verranno rimossi.", + "block_entire_domain": "Blocca il dominio" }, "save_photo_failure": { - "title": "Save Photo Failure", - "message": "Please enable the photo library access permission to save the photo." + "title": "Salvataggio foto fallito", + "message": "Si prega di abilitare l'autorizzazione di accesso alla galleria immagini per salvare la foto." }, "delete_post": { - "title": "Delete Post", - "message": "Are you sure you want to delete this post?" + "title": "Cancella il post", + "message": "Vuoi veramente eliminare questo post?" }, "clean_cache": { - "title": "Clean Cache", - "message": "Successfully cleaned %s cache." + "title": "Pulisci la cache", + "message": "Cache %s pulita con successo." } }, "controls": { "actions": { - "back": "Back", - "next": "Next", - "previous": "Previous", - "open": "Open", - "add": "Add", - "remove": "Remove", - "edit": "Edit", - "save": "Save", + "back": "Indietro", + "next": "Avanti", + "previous": "Precedente", + "open": "Apri", + "add": "Aggiungi", + "remove": "Rimuovi", + "edit": "Modifica", + "save": "Salva", "ok": "OK", - "done": "Done", - "confirm": "Confirm", - "continue": "Continue", - "compose": "Compose", - "cancel": "Cancel", - "discard": "Discard", - "try_again": "Try Again", - "take_photo": "Take Photo", - "save_photo": "Save Photo", - "copy_photo": "Copy Photo", - "sign_in": "Sign In", - "sign_up": "Sign Up", - "see_more": "See More", - "preview": "Preview", - "share": "Share", - "share_user": "Share %s", - "share_post": "Share Post", - "open_in_safari": "Open in Safari", - "open_in_browser": "Open in Browser", - "find_people": "Find people to follow", - "manually_search": "Manually search instead", - "skip": "Skip", - "reply": "Reply", - "report_user": "Report %s", - "block_domain": "Block %s", - "unblock_domain": "Unblock %s", - "settings": "Settings", - "delete": "Delete" + "done": "Fatto", + "confirm": "Conferma", + "continue": "Continua", + "compose": "Scrivi", + "cancel": "Annulla", + "discard": "Abbandona", + "try_again": "Riprova", + "take_photo": "Scatta foto", + "save_photo": "Salva foto", + "copy_photo": "Copia foto", + "sign_in": "Accedi", + "sign_up": "Registrati", + "see_more": "Visualizza altro", + "preview": "Anteprima", + "share": "Condividi", + "share_user": "Condividi %s", + "share_post": "Condividi il post", + "open_in_safari": "Apri su Safari", + "open_in_browser": "Apri nel browser", + "find_people": "Trova persone da seguire", + "manually_search": "Cerca manualmente invece", + "skip": "Salta", + "reply": "Rispondi", + "report_user": "Segnala %s", + "block_domain": "Blocca %s", + "unblock_domain": "Sblocca %s", + "settings": "Impostazioni", + "delete": "Elimina" }, "tabs": { - "home": "Home", - "search": "Search", - "notification": "Notification", - "profile": "Profile" + "home": "Inizio", + "search": "Cerca", + "notification": "Notifiche", + "profile": "Profilo" }, "keyboard": { "common": { - "switch_to_tab": "Switch to %s", - "compose_new_post": "Compose New Post", - "show_favorites": "Show Favorites", - "open_settings": "Open Settings" + "switch_to_tab": "Passa a %s", + "compose_new_post": "Componi un nuovo post", + "show_favorites": "Mostra preferiti", + "open_settings": "Apri Impostazioni" }, "timeline": { - "previous_status": "Previous Post", - "next_status": "Next Post", - "open_status": "Open Post", - "open_author_profile": "Open Author's Profile", - "open_reblogger_profile": "Open Reblogger's Profile", - "reply_status": "Reply to Post", - "toggle_reblog": "Toggle Reblog on Post", - "toggle_favorite": "Toggle Favorite on Post", - "toggle_content_warning": "Toggle Content Warning", - "preview_image": "Preview Image" + "previous_status": "Post precedente", + "next_status": "Post successivo", + "open_status": "Apri il post", + "open_author_profile": "Apri il profilo dell'autore", + "open_reblogger_profile": "Apri il profilo di chi ha condiviso", + "reply_status": "Rispondi al post", + "toggle_reblog": "Attiva/Disattiva condivisione sul post", + "toggle_favorite": "Attiva/Disattiva preferito nel post", + "toggle_content_warning": "Attiva/Disattiva avvertimento contenuti", + "preview_image": "Anteprima immagine" }, "segmented_control": { - "previous_section": "Previous Section", - "next_section": "Next Section" + "previous_section": "Sezione precedente", + "next_section": "Sezione successiva" } }, "status": { - "user_reblogged": "%s reblogged", - "user_replied_to": "Replied to %s", - "show_post": "Show Post", - "show_user_profile": "Show user profile", - "content_warning": "Content Warning", - "media_content_warning": "Tap anywhere to reveal", + "user_reblogged": "%s hanno condiviso", + "user_replied_to": "Rispondi a %s", + "show_post": "Mostra il post", + "show_user_profile": "Mostra il profilo dell'utente", + "content_warning": "Avviso sul contenuto", + "media_content_warning": "Tocca ovunque per rivelare", + "tap_to_reveal": "Tocca per rivelare", "poll": { - "vote": "Vote", - "closed": "Closed" + "vote": "Vota", + "closed": "Chiuso" }, "actions": { - "reply": "Reply", - "reblog": "Reblog", - "unreblog": "Undo reblog", - "favorite": "Favorite", - "unfavorite": "Unfavorite", - "menu": "Menu", - "hide": "Hide" + "reply": "Rispondi", + "reblog": "Condivisione", + "unreblog": "Annulla condivisione", + "favorite": "Preferito", + "unfavorite": "Non preferito", + "menu": "Menù", + "hide": "Nascondi", + "show_image": "Mostra immagine", + "show_gif": "Mostra GIF", + "show_video_player": "Mostra lettore video", + "tap_then_hold_to_show_menu": "Tocca quindi tieni premuto per mostrare il menu" }, "tag": { "url": "URL", - "mention": "Mention", - "link": "Link", - "hashtag": "Hashtag", + "mention": "Menzione", + "link": "Collegamento", + "hashtag": "Etichetta", "email": "Email", "emoji": "Emoji" }, "visibility": { - "unlisted": "Everyone can see this post but not display in the public timeline.", - "private": "Only their followers can see this post.", - "private_from_me": "Only my followers can see this post.", - "direct": "Only mentioned user can see this post." + "unlisted": "Tutti possono vedere questo post ma non mostrare nella cronologia pubblica.", + "private": "Solo i loro seguaci possono vedere questo post.", + "private_from_me": "Solo i miei seguaci possono vedere questo post.", + "direct": "Solo l'utente menzionato può vedere questo post." } }, "friendship": { - "follow": "Follow", - "following": "Following", - "request": "Request", - "pending": "Pending", - "block": "Block", - "block_user": "Block %s", - "block_domain": "Block %s", - "unblock": "Unblock", - "unblock_user": "Unblock %s", - "blocked": "Blocked", - "mute": "Mute", - "mute_user": "Mute %s", - "unmute": "Unmute", - "unmute_user": "Unmute %s", - "muted": "Muted", - "edit_info": "Edit Info" + "follow": "Segui", + "following": "Stai seguendo", + "request": "Richiesta", + "pending": "In attesa", + "block": "Blocca", + "block_user": "Blocca %s", + "block_domain": "Blocca %s", + "unblock": "Sblocca", + "unblock_user": "Sblocca %s", + "blocked": "Bloccato", + "mute": "Silenzia", + "mute_user": "Silenzia %s", + "unmute": "Riattiva", + "unmute_user": "Riattiva %s", + "muted": "Silenziato", + "edit_info": "Modifica info" }, "timeline": { - "filtered": "Filtered", + "filtered": "Filtrato", "timestamp": { - "now": "Now" + "now": "Ora" }, "loader": { - "load_missing_posts": "Load missing posts", - "loading_missing_posts": "Loading missing posts...", - "show_more_replies": "Show more replies" + "load_missing_posts": "Carica i post mancanti", + "loading_missing_posts": "Caricamento post mancanti...", + "show_more_replies": "Mostra più risposte" }, "header": { - "no_status_found": "No Post Found", - "blocking_warning": "You can’t view this user's profile\nuntil you unblock them.\nYour profile looks like this to them.", - "user_blocking_warning": "You can’t view %s’s profile\nuntil you unblock them.\nYour profile looks like this to them.", - "blocked_warning": "You can’t view this user’s profile\nuntil they unblock you.", - "user_blocked_warning": "You can’t view %s’s profile\nuntil they unblock you.", - "suspended_warning": "This user has been suspended.", - "user_suspended_warning": "%s’s account has been suspended." + "no_status_found": "Nessun post trovato", + "blocking_warning": "Non puoi visualizzare il profilo di questo utente\nfinché non li sblocchi.\nIl tuo profilo sembra questo per loro.", + "user_blocking_warning": "Non puoi visualizzare il profilo di %s\nfinché non li sblocchi.\nIl tuo profilo sembra questo per loro.", + "blocked_warning": "Non puoi visualizzare il profilo di questo utente\nfino a quando non ti sbloccano.", + "user_blocked_warning": "Non puoi visualizzare il profilo di %s\nfino a quando non ti sbloccano.", + "suspended_warning": "Questo utente è stato sospeso.", + "user_suspended_warning": "L'account di %s è stato sospeso." } } } }, "scene": { "welcome": { - "slogan": "Social networking\nback in your hands.", - "get_started": "Get Started", - "log_in": "Log In" + "slogan": "Il social network, di nuovo nelle tue mani.", + "get_started": "Inizia", + "log_in": "Accedi" }, "server_picker": { - "title": "Mastodon is made of users in different communities.", - "subtitle": "Pick a community based on your interests, region, or a general purpose one.", - "subtitle_extend": "Pick a community based on your interests, region, or a general purpose one. Each community is operated by an entirely independent organization or individual.", + "title": "Mastodon è fatto di utenti in diverse comunità.", + "subtitle": "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale.", + "subtitle_extend": "Scegli una comunità basata sui tuoi interessi, regione o uno scopo generale. Ogni comunità è gestita da un'organizzazione completamente indipendente o individuale.", "button": { "category": { - "all": "All", - "all_accessiblity_description": "Category: All", - "academia": "academia", - "activism": "activism", - "food": "food", - "furry": "furry", - "games": "games", - "general": "general", - "journalism": "journalism", + "all": "Tutti", + "all_accessiblity_description": "Categoria: Tutti", + "academia": "accademia", + "activism": "attivismo", + "food": "cibo", + "furry": "peloso", + "games": "giochi", + "general": "generale", + "journalism": "giornalismo", "lgbt": "lgbt", - "regional": "regional", - "art": "art", - "music": "music", - "tech": "tech" + "regional": "locale", + "art": "arte", + "music": "musica", + "tech": "tecnologia" }, - "see_less": "See Less", - "see_more": "See More" + "see_less": "Vedi meno", + "see_more": "Vedi di più" }, "label": { - "language": "LANGUAGE", - "users": "USERS", - "category": "CATEGORY" + "language": "LINGUA", + "users": "UTENTI", + "category": "CATEGORIA" }, "input": { - "placeholder": "Search communities" + "placeholder": "Cerca comunità" }, "empty_state": { - "finding_servers": "Finding available servers...", - "bad_network": "Something went wrong while loading the data. Check your internet connection.", - "no_results": "No results" + "finding_servers": "Ricerca server disponibili...", + "bad_network": "Qualcosa è andato storto durante il caricamento dei dati. Controlla la tua connessione internet.", + "no_results": "Nessun risultato" } }, "register": { - "title": "Let’s get you set up on %s", + "title": "Facciamo in modo che sia configurato il %s", "input": { "avatar": { - "delete": "Delete" + "delete": "Elimina" }, "username": { - "placeholder": "username", - "duplicate_prompt": "This username is taken." + "placeholder": "nome utente", + "duplicate_prompt": "Questo nome utente è già stato preso." }, "display_name": { - "placeholder": "display name" + "placeholder": "visualizza nome" }, "email": { "placeholder": "email" }, "password": { "placeholder": "password", - "require": "Your password needs at least:", - "character_limit": "8 characters", + "require": "La tua password ha bisogno di almeno:", + "character_limit": "8 caratteri", "accessibility": { - "checked": "checked", - "unchecked": "unchecked" + "checked": "verificato", + "unchecked": "non verificato" }, - "hint": "Your password needs at least eight characters" + "hint": "La tua password deve essere di almeno 8 caratteri" }, "invite": { - "registration_user_invite_request": "Why do you want to join?" + "registration_user_invite_request": "Perché vuoi unirti?" } }, "error": { "item": { - "username": "Username", + "username": "Nome utente", "email": "Email", "password": "Password", - "agreement": "Agreement", + "agreement": "Accordo", "locale": "Locale", - "reason": "Reason" + "reason": "Motivo" }, "reason": { - "blocked": "%s contains a disallowed email provider", - "unreachable": "%s does not seem to exist", - "taken": "%s is already in use", - "reserved": "%s is a reserved keyword", - "accepted": "%s must be accepted", - "blank": "%s is required", - "invalid": "%s is invalid", - "too_long": "%s is too long", - "too_short": "%s is too short", - "inclusion": "%s is not a supported value" + "blocked": "%s contiene un provider email non consentito", + "unreachable": "%s non sembra esistere", + "taken": "%s è già in uso", + "reserved": "%s è una parola chiave riservata", + "accepted": "%s deve essere accettato", + "blank": "%s è richiesto", + "invalid": "%s non è valido", + "too_long": "%s è troppo lungo", + "too_short": "%s è troppo corto", + "inclusion": "%s non è un valore supportato" }, "special": { - "username_invalid": "Username must only contain alphanumeric characters and underscores", - "username_too_long": "Username is too long (can’t be longer than 30 characters)", - "email_invalid": "This is not a valid email address", - "password_too_short": "Password is too short (must be at least 8 characters)" + "username_invalid": "Il nome utente deve contenere solo caratteri alfanumerici e trattini bassi", + "username_too_long": "Il nome utente è troppo lungo (non può essere più lungo di 30 caratteri)", + "email_invalid": "Questo non è un indirizzo email valido", + "password_too_short": "La password è troppo corta (deve contenere almeno 8 caratteri)" } } }, "server_rules": { - "title": "Some ground rules.", - "subtitle": "These are set and enforced by the %s moderators.", - "prompt": "By continuing, you’re subject to the terms of service and privacy policy for %s.", - "terms_of_service": "terms of service", + "title": "Alcune regole di base.", + "subtitle": "Questi sono impostati e applicati dai moderatori %s.", + "prompt": "Continuando, sei soggetto alle condizioni di servizio e all'informativa sulla privacy per %s.", + "terms_of_service": "condizioni del servizio", "privacy_policy": "privacy policy", "button": { - "confirm": "I Agree" + "confirm": "Accetto" } }, "confirm_email": { - "title": "One last thing.", - "subtitle": "Tap the link we emailed to you to verify your account.", + "title": "Un'ultima cosa.", + "subtitle": "Tocca il link che ti abbiamo inviato per verificare il tuo account.", "button": { - "open_email_app": "Open Email App", - "resend": "Resend" + "open_email_app": "Apri l'app Email", + "resend": "Invia di nuovo" }, "dont_receive_email": { - "title": "Check your email", - "description": "Check if your email address is correct as well as your junk folder if you haven’t.", - "resend_email": "Resend Email" + "title": "Controlla la tua e-mail", + "description": "Controlla se il tuo indirizzo email sia corretto, così come la tua cartella spazzatura se non ce l'hai.", + "resend_email": "Invia e-mail di nuovo" }, "open_email_app": { - "title": "Check your inbox.", - "description": "We just sent you an email. Check your junk folder if you haven’t.", - "mail": "Mail", - "open_email_client": "Open Email Client" + "title": "Controlla la tua posta in arrivo.", + "description": "Ti abbiamo appena inviato un'email. Controlla la tua cartella spazzatura se non ce l'hai.", + "mail": "Posta", + "open_email_client": "Apri client Email" } }, "home_timeline": { - "title": "Home", + "title": "Inizio", "navigation_bar_state": { - "offline": "Offline", - "new_posts": "See new posts", - "published": "Published!", - "Publishing": "Publishing post..." + "offline": "Non in linea", + "new_posts": "Vedi nuovi post", + "published": "Pubblicato!", + "Publishing": "Pubblicazione post..." } }, "suggestion_account": { - "title": "Find People to Follow", - "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + "title": "Trova alcune persone da seguire", + "follow_explain": "Quando segui qualcuno, vedrai i loro post nella tua home feed." }, "compose": { "title": { - "new_post": "New Post", - "new_reply": "New Reply" + "new_post": "Nuovo post", + "new_reply": "Nuova risposta" }, "media_selection": { - "camera": "Take Photo", - "photo_library": "Photo Library", - "browse": "Browse" + "camera": "Scatta foto", + "photo_library": "Libreria foto", + "browse": "Sfoglia" }, - "content_input_placeholder": "Type or paste what’s on your mind", - "compose_action": "Publish", - "replying_to_user": "replying to %s", + "content_input_placeholder": "Digita o incolla quello che hai in mente", + "compose_action": "Pubblica", + "replying_to_user": "rispondendo a %s", "attachment": { - "photo": "photo", - "video": "video", - "attachment_broken": "This %s is broken and can’t be\nuploaded to Mastodon.", - "description_photo": "Describe the photo for the visually-impaired...", - "description_video": "Describe the video for the visually-impaired..." + "photo": "foto", + "video": "filmato", + "attachment_broken": "Questo %s è rotto e non può essere\ncaricato su Mastodon.", + "description_photo": "Descrivi la foto per gli utenti ipovedenti...", + "description_video": "Descrivi il filmato per gli utenti ipovedenti..." }, "poll": { - "duration_time": "Duration: %s", - "thirty_minutes": "30 minutes", - "one_hour": "1 Hour", - "six_hours": "6 Hours", - "one_day": "1 Day", - "three_days": "3 Days", - "seven_days": "7 Days", - "option_number": "Option %ld" + "duration_time": "Durata: %s", + "thirty_minutes": "30 minuti", + "one_hour": "1 ora", + "six_hours": "6 ore", + "one_day": "1 giorno", + "three_days": "3 giorni", + "seven_days": "7 giorni", + "option_number": "Opzione %ld" }, "content_warning": { - "placeholder": "Write an accurate warning here..." + "placeholder": "Scrivi un avviso accurato qui..." }, "visibility": { - "public": "Public", - "unlisted": "Unlisted", - "private": "Followers only", - "direct": "Only people I mention" + "public": "Pubblico", + "unlisted": "Non elencato", + "private": "Solo i seguaci", + "direct": "Solo le persone che menziono" }, "auto_complete": { - "space_to_add": "Space to add" + "space_to_add": "Spazio da aggiungere" }, "accessibility": { - "append_attachment": "Add Attachment", - "append_poll": "Add Poll", - "remove_poll": "Remove Poll", - "custom_emoji_picker": "Custom Emoji Picker", - "enable_content_warning": "Enable Content Warning", - "disable_content_warning": "Disable Content Warning", - "post_visibility_menu": "Post Visibility Menu" + "append_attachment": "Aggiungi allegato", + "append_poll": "Aggiungi sondaggio", + "remove_poll": "Elimina sondaggio", + "custom_emoji_picker": "Selettore Emoji personalizzato", + "enable_content_warning": "Abilita avvertimento contenuti", + "disable_content_warning": "Disabilita avviso di contenuti", + "post_visibility_menu": "Menu di visibilità del post" }, "keyboard": { - "discard_post": "Discard Post", - "publish_post": "Publish Post", - "toggle_poll": "Toggle Poll", - "toggle_content_warning": "Toggle Content Warning", - "append_attachment_entry": "Add Attachment - %s", - "select_visibility_entry": "Select Visibility - %s" + "discard_post": "Scarta post", + "publish_post": "Pubblica il post", + "toggle_poll": "Attiva/Disattiva Sondaggio", + "toggle_content_warning": "Attiva/Disattiva avviso contenuti", + "append_attachment_entry": "Aggiungi allegato - %s", + "select_visibility_entry": "Seleziona visibilità - %s" } }, "profile": { "dashboard": { - "posts": "posts", - "following": "following", - "followers": "followers" + "posts": "post", + "following": "seguendo", + "followers": "seguaci" }, "fields": { - "add_row": "Add Row", + "add_row": "Aggiungi riga", "placeholder": { - "label": "Label", - "content": "Content" + "label": "Etichetta", + "content": "Contenuto" } }, "segmented_control": { - "posts": "Posts", - "replies": "Replies", - "posts_and_replies": "Posts and Replies", + "posts": "Post", + "replies": "Risposte", + "posts_and_replies": "Post e risposte", "media": "Media", - "about": "About" + "about": "Info su" }, "relationship_action_alert": { "confirm_mute_user": { - "title": "Mute Account", - "message": "Confirm to mute %s" + "title": "Silenzia account", + "message": "Confermi di silenziare %s" }, "confirm_unmute_user": { - "title": "Unmute Account", - "message": "Confirm to unmute %s" + "title": "Riattiva account", + "message": "Confermi di riattivare %s" }, "confirm_block_user": { - "title": "Block Account", - "message": "Confirm to block %s" + "title": "Blocca account", + "message": "Confermi di bloccare %s" }, "confirm_unblock_user": { - "title": "Unblock Account", - "message": "Confirm to unblock %s" + "title": "Sblocca account", + "message": "Conferma per sbloccare %s" } + }, + "accessibility": { + "show_avatar_image": "Mostra immagine avatar", + "edit_avatar_image": "Modifica immagine avatar", + "show_banner_image": "Mostra immagine banner", + "double_tap_to_open_the_list": "Doppio tocco per aprire la lista" } }, "follower": { - "footer": "Followers from other servers are not displayed." + "footer": "I seguaci da altri server non vengono visualizzati." }, "following": { - "footer": "Follows from other servers are not displayed." + "footer": "I follow da altri server non vengono visualizzati." }, "search": { - "title": "Search", + "title": "Cerca", "search_bar": { - "placeholder": "Search hashtags and users", - "cancel": "Cancel" + "placeholder": "Cerca hashtag e utenti", + "cancel": "Annulla" }, "recommend": { - "button_text": "See All", + "button_text": "Vedi tutto", "hash_tag": { - "title": "Trending on Mastodon", - "description": "Hashtags that are getting quite a bit of attention", - "people_talking": "%s people are talking" + "title": "Di tendenza su Mastodon", + "description": "Hashtag che stanno ottenendo un bel po' di attenzione", + "people_talking": "%s persone ne parlano" }, "accounts": { - "title": "Accounts you might like", - "description": "You may like to follow these accounts", - "follow": "Follow" + "title": "Account che potrebbero piacerti", + "description": "Potresti voler seguire questi account", + "follow": "Segui" } }, "searching": { "segment": { - "all": "All", - "people": "People", + "all": "Tutto", + "people": "Persone", "hashtags": "Hashtags", - "posts": "Posts" + "posts": "Post" }, "empty_state": { - "no_results": "No results" + "no_results": "Nessun risultato" }, - "recent_search": "Recent searches", - "clear": "Clear" + "recent_search": "Ricerche recenti", + "clear": "Cancella" + } + }, + "discovery": { + "tabs": { + "posts": "Post", + "hashtags": "Hashtag", + "news": "Notizie", + "for_you": "Per Te" } }, "favorite": { - "title": "Your Favorites" + "title": "I tuoi preferiti" }, "notification": { "title": { - "Everything": "Everything", - "Mentions": "Mentions" + "Everything": "Tutto", + "Mentions": "Menzioni" }, "notification_description": { - "followed_you": "followed you", - "favorited_your_post": "favorited your post", - "reblogged_your_post": "reblogged your post", - "mentioned_you": "mentioned you", - "request_to_follow_you": "request to follow you", - "poll_has_ended": "poll has ended" + "followed_you": "ti ha seguito", + "favorited_your_post": "ha apprezzato il tuo post", + "reblogged_your_post": "ha ripostato il tuo post", + "mentioned_you": "ti ha menzionato", + "request_to_follow_you": "richiesta di seguirti", + "poll_has_ended": "sondaggio terminato" }, "keyobard": { - "show_everything": "Show Everything", - "show_mentions": "Show Mentions" + "show_everything": "Mostra Tutto", + "show_mentions": "Mostra Menzioni" } }, "thread": { "back_title": "Post", - "title": "Post from %s" + "title": "Post da %s" }, "settings": { - "title": "Settings", + "title": "Impostazioni", "section": { "appearance": { - "title": "Appearance", - "automatic": "Automatic", - "light": "Always Light", - "dark": "Always Dark" + "title": "Aspetto", + "automatic": "Automatico", + "light": "Sempre chiaro", + "dark": "Sempre scuro" }, "look_and_feel": { "title": "Look and Feel", - "use_system": "Use System", - "really_dark": "Really Dark", - "sorta_dark": "Sorta Dark", - "light": "Light" + "use_system": "Predefinito di sistema", + "really_dark": "Davvero scuro", + "sorta_dark": "Un po' scuro", + "light": "Chiaro" }, "notifications": { - "title": "Notifications", - "favorites": "Favorites my post", - "follows": "Follows me", - "boosts": "Reblogs my post", - "mentions": "Mentions me", + "title": "Notifiche", + "favorites": "Apprezza i miei post", + "follows": "Mi segue", + "boosts": "Condivide i miei post", + "mentions": "Mi menziona", "trigger": { - "anyone": "anyone", - "follower": "a follower", - "follow": "anyone I follow", - "noone": "no one", - "title": "Notify me when" + "anyone": "chiunque", + "follower": "un seguace", + "follow": "chiunque io segua", + "noone": "nessuno", + "title": "Avvisami quando" } }, "preference": { - "title": "Preferences", - "true_black_dark_mode": "True black dark mode", - "disable_avatar_animation": "Disable animated avatars", - "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "title": "Preferenze", + "true_black_dark_mode": "Modalità molto scura", + "disable_avatar_animation": "Disabilita avatar animati", + "disable_emoji_animation": "Disabilita emoji animate", + "using_default_browser": "Usa browser predefinito per aprire i collegamenti", + "open_links_in_mastodon": "Apri i link in Mastodon" }, "boring_zone": { - "title": "The Boring Zone", - "account_settings": "Account Settings", - "terms": "Terms of Service", - "privacy": "Privacy Policy" + "title": "La zona boring", + "account_settings": "Impostazioni account", + "terms": "Termini di servizio", + "privacy": "Politica sulla Privacy" }, "spicy_zone": { - "title": "The Spicy Zone", - "clear": "Clear Media Cache", - "signout": "Sign Out" + "title": "La zona piccante", + "clear": "Cancella la cache multimediale", + "signout": "Esci" } }, "footer": { - "mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)" + "mastodon_description": "Mastodon è un software open source. Puoi segnalare problemi su GitHub a %s (%s)" }, "keyboard": { - "close_settings_window": "Close Settings Window" + "close_settings_window": "Chiudi la finestra Impostazioni" } }, "report": { - "title_report": "Report", - "title": "Report %s", - "step1": "Step 1 of 2", - "step2": "Step 2 of 2", - "content1": "Are there any other posts you’d like to add to the report?", - "content2": "Is there anything the moderators should know about this report?", - "report_sent_title": "Thanks for reporting, we’ll look into this.", - "send": "Send Report", - "skip_to_send": "Send without comment", - "text_placeholder": "Type or paste additional comments", - "reported": "REPORTED" + "title_report": "Segnala", + "title": "Segnala %s", + "step1": "Fase 1 di 2", + "step2": "Fase 2 di 2", + "content1": "Ci sono altri post che vorresti aggiungere alla segnalazione?", + "content2": "C'è qualcosa che i moderatori dovrebbero sapere su questa segnalazione?", + "report_sent_title": "Grazie per la segnalazione, esamineremo questo aspetto.", + "send": "Invia segnalazione", + "skip_to_send": "Invia senza commento", + "text_placeholder": "Digita o incolla commenti aggiuntivi", + "reported": "SEGNALATO" }, "preview": { "keyboard": { - "close_preview": "Close Preview", - "show_next": "Show Next", - "show_previous": "Show Previous" + "close_preview": "Chiudi anteprima", + "show_next": "Mostra successivo", + "show_previous": "Mostra precedente" } }, "account_list": { - "tab_bar_hint": "Current selected profile: %s. Double tap then hold to show account switcher", - "dismiss_account_switcher": "Dismiss Account Switcher", - "add_account": "Add Account" + "tab_bar_hint": "Profilo corrente selezionato: %s. Doppio tocco e tieni premuto per mostrare il cambio account", + "dismiss_account_switcher": "Ignora il cambio account", + "add_account": "Aggiungi account" }, "wizard": { - "new_in_mastodon": "New in Mastodon", - "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", - "accessibility_hint": "Double tap to dismiss this wizard" + "new_in_mastodon": "Nuovo su Mastodon", + "multiple_account_switch_intro_description": "Passa tra più account tenendo premuto il pulsante del profilo.", + "accessibility_hint": "Doppio tocco per eliminare questa procedura guidata" } } } \ No newline at end of file diff --git a/Localization/StringsConvertor/input/it_IT/ios-infoPlist.json b/Localization/StringsConvertor/input/it_IT/ios-infoPlist.json index c6db73de..bca6817f 100644 --- a/Localization/StringsConvertor/input/it_IT/ios-infoPlist.json +++ b/Localization/StringsConvertor/input/it_IT/ios-infoPlist.json @@ -1,6 +1,6 @@ { - "NSCameraUsageDescription": "Used to take photo for post status", - "NSPhotoLibraryAddUsageDescription": "Used to save photo into the Photo Library", - "NewPostShortcutItemTitle": "New Post", - "SearchShortcutItemTitle": "Search" + "NSCameraUsageDescription": "Usato per scattare foto per lo stato del post", + "NSPhotoLibraryAddUsageDescription": "Utilizzato per salvare la foto nella galleria immagini", + "NewPostShortcutItemTitle": "Nuovo post", + "SearchShortcutItemTitle": "Cerca" } diff --git a/Localization/StringsConvertor/input/ja_JP/Localizable.stringsdict b/Localization/StringsConvertor/input/ja_JP/Localizable.stringsdict index f1c5e6e2..8dfc9507 100644 --- a/Localization/StringsConvertor/input/ja_JP/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ja_JP/Localizable.stringsdict @@ -100,6 +100,20 @@ %ld ブースト + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ja_JP/app.json b/Localization/StringsConvertor/input/ja_JP/app.json index 7ddfa51c..fb16cf4b 100644 --- a/Localization/StringsConvertor/input/ja_JP/app.json +++ b/Localization/StringsConvertor/input/ja_JP/app.json @@ -130,6 +130,7 @@ "show_user_profile": "プロフィールを見る", "content_warning": "コンテンツ警告", "media_content_warning": "どこかをタップして表示", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "投票", "closed": "クローズド" @@ -141,7 +142,11 @@ "favorite": "お気に入り", "unfavorite": "お気に入り登録を取り消す", "menu": "メニュー", - "hide": "非表示" + "hide": "非表示", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "クリア" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "お気に入り" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "真っ黒なダークテーマを使用する", "disable_avatar_animation": "アバターのアニメーションを無効化する", "disable_emoji_animation": "絵文字のアニメーションを無効化する", - "using_default_browser": "既定のブラウザでリンクを開く" + "using_default_browser": "既定のブラウザでリンクを開く", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "アプリについて", diff --git a/Localization/StringsConvertor/input/kab_KAB/Localizable.stringsdict b/Localization/StringsConvertor/input/kab_KAB/Localizable.stringsdict index 8a2bac9e..c3d72dda 100644 --- a/Localization/StringsConvertor/input/kab_KAB/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/kab_KAB/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld n uɛiwed n usuffeɣ + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/kab_KAB/app.json b/Localization/StringsConvertor/input/kab_KAB/app.json index 74c16795..08de0aef 100644 --- a/Localization/StringsConvertor/input/kab_KAB/app.json +++ b/Localization/StringsConvertor/input/kab_KAB/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Ssken-d amaɣnu n useqdac", "content_warning": "Alɣu n ugbur", "media_content_warning": "Sit anida tebɣiḍ i wakken ad twaliḍ", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Dɣeṛ", "closed": "Ifukk" @@ -141,7 +142,11 @@ "favorite": "Anurif", "unfavorite": "Kkes seg yismenyifen", "menu": "Umuɣ", - "hide": "Ffer" + "hide": "Ffer", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Kkes asewḥel i umiḍan", "message": "Sentem tukksa n usgugem i %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Sfeḍ" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Ismenyifen-ik·im" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Askar aberkan n tidet", "disable_avatar_animation": "Sens ivaṭaren yettembiwilen", "disable_emoji_animation": "Sens imujiten yettembiwilen", - "using_default_browser": "Seqdec iminig amezwer i twaledyawt n yiseɣwan" + "using_default_browser": "Seqdec iminig amezwer i twaledyawt n yiseɣwan", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "Tamnaḍt yessefcalen", diff --git a/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict index 8ae1b812..0fa7d821 100644 --- a/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/kmr_TR/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld ji nû ve nivîsandin + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 bersiv + other + %ld bersiv + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/kmr_TR/app.json b/Localization/StringsConvertor/input/kmr_TR/app.json index 5d1d70fb..61fb7033 100644 --- a/Localization/StringsConvertor/input/kmr_TR/app.json +++ b/Localization/StringsConvertor/input/kmr_TR/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Profîla bikarhêner nîşan bide", "content_warning": "Hişyariya naverokê", "media_content_warning": "Ji bo eşkerekirinê li derekî bitikîne", + "tap_to_reveal": "Ji bo dîtinê bitikîne", "poll": { "vote": "Deng bide", "closed": "Girtî" @@ -141,7 +142,11 @@ "favorite": "Bijarte", "unfavorite": "Nebijarte", "menu": "Kulîn", - "hide": "Veşêre" + "hide": "Veşêre", + "show_image": "Wêneyê nîşan bide", + "show_gif": "GIF nîşan bide", + "show_video_player": "Lêdera vîdyoyê nîşan bide", + "tap_then_hold_to_show_menu": "Ji bo nîşandana menuyê dirêj bitikîne" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Astengiyê li ser ajimêr rake", "message": "Ji bo rakirina astengkirinê %s bipejirîne" } + }, + "accessibility": { + "show_avatar_image": "Wêneya avatar nîşan bide", + "edit_avatar_image": "Wêneya avatar serrast bike", + "show_banner_image": "Wêneya paşrûyê nîşan bide", + "double_tap_to_open_the_list": "Ducaran bitikîne bo vekirina listeyê" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Pak bike" } }, + "discovery": { + "tabs": { + "posts": "Şandî", + "hashtags": "Hashtag", + "news": "Nûçe", + "for_you": "Ji bo te" + } + }, "favorite": { "title": "Bijarteyên te" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Moda tarî ya reş a rastîn", "disable_avatar_animation": "Avatarên anîmasyonî neçalak bike", "disable_emoji_animation": "Emojiyên anîmasyonî neçalak bike", - "using_default_browser": "Ji bo vekirina girêdanan geroka berdest bi kar bîne" + "using_default_browser": "Ji bo vekirina girêdanan geroka berdest bi kar bîne", + "open_links_in_mastodon": "Girêdanan di Mastodon de veke" }, "boring_zone": { "title": "Devera acizker", diff --git a/Localization/StringsConvertor/input/ko_KR/Localizable.stringsdict b/Localization/StringsConvertor/input/ko_KR/Localizable.stringsdict index 7c990671..2af4c9ce 100644 --- a/Localization/StringsConvertor/input/ko_KR/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ko_KR/Localizable.stringsdict @@ -100,6 +100,20 @@ %ld 개의 리블로그 + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ko_KR/app.json b/Localization/StringsConvertor/input/ko_KR/app.json index 3f9f4c19..ab079573 100644 --- a/Localization/StringsConvertor/input/ko_KR/app.json +++ b/Localization/StringsConvertor/input/ko_KR/app.json @@ -130,6 +130,7 @@ "show_user_profile": "사용자 프로필 보기", "content_warning": "열람 주의", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "투표", "closed": "마감" @@ -141,7 +142,11 @@ "favorite": "즐겨찾기", "unfavorite": "즐겨찾기 해제", "menu": "메뉴", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "트루 블랙 어두운 모드", "disable_avatar_animation": "움직이는 아바타 비활성화", "disable_emoji_animation": "움직이는 에모지 비활성화", - "using_default_browser": "기본 브라우저로 링크 열기" + "using_default_browser": "기본 브라우저로 링크 열기", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "지루한 영역", diff --git a/Localization/StringsConvertor/input/nl_NL/Localizable.stringsdict b/Localization/StringsConvertor/input/nl_NL/Localizable.stringsdict index 8b6ab05c..5ae33cbe 100644 --- a/Localization/StringsConvertor/input/nl_NL/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/nl_NL/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld gedeelde berichten + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/nl_NL/app.json b/Localization/StringsConvertor/input/nl_NL/app.json index ae8f2d2d..7a0e37a2 100644 --- a/Localization/StringsConvertor/input/nl_NL/app.json +++ b/Localization/StringsConvertor/input/nl_NL/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Toon Gebruikersprofiel", "content_warning": "Inhoudswaarschuwing", "media_content_warning": "Tap hier om te tonen", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Stemmen", "closed": "Gesloten" @@ -141,7 +142,11 @@ "favorite": "Toevoegen aan Favorieten", "unfavorite": "Verwijderen uit Favorieten", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Wissen" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Uw favorieten" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Echt zwarte donker uiterlijk", "disable_avatar_animation": "Geanimeerde avatars uitschakelen", "disable_emoji_animation": "Geanimeerde emojis uitschakelen", - "using_default_browser": "Gebruik de standaard browser om links te openen" + "using_default_browser": "Gebruik de standaard browser om links te openen", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "De Saaie Instellingen", diff --git a/Localization/StringsConvertor/input/pt_BR/Localizable.stringsdict b/Localization/StringsConvertor/input/pt_BR/Localizable.stringsdict index 730e2902..503ff9db 100644 --- a/Localization/StringsConvertor/input/pt_BR/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/pt_BR/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/pt_BR/app.json b/Localization/StringsConvertor/input/pt_BR/app.json index ad99e178..548c5ada 100644 --- a/Localization/StringsConvertor/input/pt_BR/app.json +++ b/Localization/StringsConvertor/input/pt_BR/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Show user profile", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Closed" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/StringsConvertor/input/pt_PT/Localizable.stringsdict b/Localization/StringsConvertor/input/pt_PT/Localizable.stringsdict index 730e2902..503ff9db 100644 --- a/Localization/StringsConvertor/input/pt_PT/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/pt_PT/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/pt_PT/app.json b/Localization/StringsConvertor/input/pt_PT/app.json index ad99e178..548c5ada 100644 --- a/Localization/StringsConvertor/input/pt_PT/app.json +++ b/Localization/StringsConvertor/input/pt_PT/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Show user profile", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Closed" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/StringsConvertor/input/ro_RO/Localizable.stringsdict b/Localization/StringsConvertor/input/ro_RO/Localizable.stringsdict index 8cda4bbd..2acc3276 100644 --- a/Localization/StringsConvertor/input/ro_RO/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ro_RO/Localizable.stringsdict @@ -128,6 +128,24 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + few + %ld replies + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ro_RO/app.json b/Localization/StringsConvertor/input/ro_RO/app.json index b9ef116d..1b776e79 100644 --- a/Localization/StringsConvertor/input/ro_RO/app.json +++ b/Localization/StringsConvertor/input/ro_RO/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Show user profile", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Closed" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/StringsConvertor/input/ru_RU/Localizable.stringsdict b/Localization/StringsConvertor/input/ru_RU/Localizable.stringsdict index 96afce4e..626a10d7 100644 --- a/Localization/StringsConvertor/input/ru_RU/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ru_RU/Localizable.stringsdict @@ -142,6 +142,26 @@ %ld продвинули + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + few + %ld replies + many + %ld replies + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/ru_RU/app.json b/Localization/StringsConvertor/input/ru_RU/app.json index 58cedfc7..ca040eeb 100644 --- a/Localization/StringsConvertor/input/ru_RU/app.json +++ b/Localization/StringsConvertor/input/ru_RU/app.json @@ -46,7 +46,7 @@ }, "delete_post": { "title": "Вы уверены, что хотите удалить этот пост?", - "message": "Are you sure you want to delete this post?" + "message": "Вы уверены, что хотите удалить этот пост?" }, "clean_cache": { "title": "Очистка кэша", @@ -130,6 +130,7 @@ "show_user_profile": "Показать профиль пользователя", "content_warning": "Предупреждение о содержании", "media_content_warning": "Нажмите в любом месте, чтобы показать", + "tap_to_reveal": "Нажмите, чтобы показать", "poll": { "vote": "Проголосовать", "closed": "Завершён" @@ -141,7 +142,11 @@ "favorite": "Добавить в избранное", "unfavorite": "Убрать из избранного", "menu": "Меню", - "hide": "Hide" + "hide": "Скрыть", + "show_image": "Показать изображение", + "show_gif": "Показать GIF", + "show_video_player": "Показать видеопроигрыватель", + "tap_then_hold_to_show_menu": "Нажмите и удерживайте, чтобы показать меню" }, "tag": { "url": "Ссылка", @@ -170,7 +175,7 @@ "unblock_user": "Разблокировать %s", "blocked": "В заблокированных", "mute": "Игнорировать", - "mute_user": "Добавить %s в игнорируемые", + "mute_user": "Игнорировать %s", "unmute": "Убрать из игнорируемых", "unmute_user": "Убрать %s из игнорируемых", "muted": "В игнорируемых", @@ -201,7 +206,7 @@ "scene": { "welcome": { "slogan": "Социальная сеть\nпод вашим контролем.", - "get_started": "Get Started", + "get_started": "Присоединиться", "log_in": "Вход" }, "server_picker": { @@ -316,7 +321,7 @@ "subtitle": "Мы только что отправили письмо на\n%s.\nНажмите на ссылку в нём, чтобы\nподтвердить свою учётную запись.", "button": { "open_email_app": "Открыть приложение почты", - "resend": "Resend" + "resend": "Отправить заново" }, "dont_receive_email": { "title": "Проверьте свой e-mail адрес", @@ -419,7 +424,7 @@ "segmented_control": { "posts": "Посты", "replies": "Ответы", - "posts_and_replies": "Posts and Replies", + "posts_and_replies": "Посты и ответы", "media": "Медиа", "about": "About" }, @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Очистить" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Ваше избранное" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Полноценно чёрный режим", "disable_avatar_animation": "Отключить анимацию аватарок", "disable_emoji_animation": "Отключить анимацию эмодзи", - "using_default_browser": "Использовать браузер по умолчанию для открытия ссылок" + "using_default_browser": "Использовать браузер по умолчанию для открытия ссылок", + "open_links_in_mastodon": "Открывать ссылки в Мастодоне" }, "boring_zone": { "title": "Зона скукотищи", @@ -563,7 +583,7 @@ } }, "report": { - "title_report": "Report", + "title_report": "Жалоба", "title": "Пожаловаться на %s", "step1": "Шаг 1 из 2", "step2": "Шаг 2 из 2", @@ -588,7 +608,7 @@ "add_account": "Add Account" }, "wizard": { - "new_in_mastodon": "New in Mastodon", + "new_in_mastodon": "Новое в Мастодоне", "multiple_account_switch_intro_description": "Switch between multiple accounts by holding the profile button.", "accessibility_hint": "Double tap to dismiss this wizard" } diff --git a/Localization/StringsConvertor/input/sv_FI/Localizable.stringsdict b/Localization/StringsConvertor/input/sv_FI/Localizable.stringsdict index eec977a6..43231214 100644 --- a/Localization/StringsConvertor/input/sv_FI/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/sv_FI/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld edelleen jakoa + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/sv_FI/app.json b/Localization/StringsConvertor/input/sv_FI/app.json index 669ee437..769aadc5 100644 --- a/Localization/StringsConvertor/input/sv_FI/app.json +++ b/Localization/StringsConvertor/input/sv_FI/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Näytä tili", "content_warning": "Sisältövaroitus", "media_content_warning": "Napauta mistä tahansa paljastaaksesi", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Suljettu" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Valikko", - "hide": "Dölj" + "hide": "Dölj", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Tyhjennä" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Omat suosikit" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "Todellinen mustan tumma tila", "disable_avatar_animation": "Poista käytöstä animoidut avatarit", "disable_emoji_animation": "Poista käytöstä animoidut emojit", - "using_default_browser": "Käytä oletusselainta linkkien avaamiseen" + "using_default_browser": "Käytä oletusselainta linkkien avaamiseen", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "Tylsä alue", diff --git a/Localization/StringsConvertor/input/sv_SE/Localizable.stringsdict b/Localization/StringsConvertor/input/sv_SE/Localizable.stringsdict index f8da5e39..50eab95b 100644 --- a/Localization/StringsConvertor/input/sv_SE/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/sv_SE/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/sv_SE/app.json b/Localization/StringsConvertor/input/sv_SE/app.json index 59ad0d6e..f18ce2dc 100644 --- a/Localization/StringsConvertor/input/sv_SE/app.json +++ b/Localization/StringsConvertor/input/sv_SE/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Show user profile", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "Vote", "closed": "Closed" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Meny", - "hide": "Dölj" + "hide": "Dölj", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Rensa" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Inaktivera animerade avatarer", "disable_emoji_animation": "Inaktivera animerade emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/StringsConvertor/input/th_TH/Localizable.stringsdict b/Localization/StringsConvertor/input/th_TH/Localizable.stringsdict index 8971821f..8ae8feb7 100644 --- a/Localization/StringsConvertor/input/th_TH/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/th_TH/Localizable.stringsdict @@ -100,6 +100,20 @@ %ld การดัน + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld การตอบกลับ + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/th_TH/app.json b/Localization/StringsConvertor/input/th_TH/app.json index 001075b1..b771f951 100644 --- a/Localization/StringsConvertor/input/th_TH/app.json +++ b/Localization/StringsConvertor/input/th_TH/app.json @@ -130,6 +130,7 @@ "show_user_profile": "แสดงโปรไฟล์ผู้ใช้", "content_warning": "คำเตือนเนื้อหา", "media_content_warning": "แตะที่ใดก็ตามเพื่อเปิดเผย", + "tap_to_reveal": "แตะเพื่อเปิดเผย", "poll": { "vote": "ลงคะแนน", "closed": "ปิดแล้ว" @@ -141,7 +142,11 @@ "favorite": "ชื่นชอบ", "unfavorite": "เลิกชื่นชอบ", "menu": "เมนู", - "hide": "ซ่อน" + "hide": "ซ่อน", + "show_image": "แสดงภาพ", + "show_gif": "แสดง GIF", + "show_video_player": "แสดงตัวเล่นวิดีโอ", + "tap_then_hold_to_show_menu": "แตะค้างไว้เพื่อแสดงเมนู" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "เลิกปิดกั้นบัญชี", "message": "ยืนยันเพื่อเลิกปิดกั้น %s" } + }, + "accessibility": { + "show_avatar_image": "แสดงภาพประจำตัว", + "edit_avatar_image": "แก้ไขภาพประจำตัว", + "show_banner_image": "แสดงภาพแบนเนอร์", + "double_tap_to_open_the_list": "แตะสองครั้งเพื่อเปิดรายการ" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "ล้าง" } }, + "discovery": { + "tabs": { + "posts": "โพสต์", + "hashtags": "แฮชแท็ก", + "news": "ข่าว", + "for_you": "สำหรับคุณ" + } + }, "favorite": { "title": "รายการโปรดของคุณ" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "โหมดมืดดำสนิท", "disable_avatar_animation": "ปิดใช้งานภาพประจำตัวแบบเคลื่อนไหว", "disable_emoji_animation": "ปิดใช้งานอีโมจิแบบเคลื่อนไหว", - "using_default_browser": "ใช้เบราว์เซอร์เริ่มต้นเพื่อเปิดลิงก์" + "using_default_browser": "ใช้เบราว์เซอร์เริ่มต้นเพื่อเปิดลิงก์", + "open_links_in_mastodon": "เปิดลิงก์ใน Mastodon" }, "boring_zone": { "title": "โซนน่าเบื่อ", diff --git a/Localization/StringsConvertor/input/vi_VN/Localizable.stringsdict b/Localization/StringsConvertor/input/vi_VN/Localizable.stringsdict new file mode 100644 index 00000000..71ba1951 --- /dev/null +++ b/Localization/StringsConvertor/input/vi_VN/Localizable.stringsdict @@ -0,0 +1,356 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld thông báo chưa đọc + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Giới hạn nhập tối đa %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ký tự + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Giới hạn nhập còn lại %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ký tự + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + tút + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld tút + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld lượt thích + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld đăng lại + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld trả lời + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld bình chọn + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld người bình chọn + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld người đang thảo luận + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld đang theo dõi + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld người theo dõi + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld năm còn lại + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld tháng còn lại + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ngày còn lại + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld giờ còn lại + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld phút còn lại + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld giây còn lại + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld năm trước + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld tháng trước + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ngày trước + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ldh + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ldm + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %lds + + + + diff --git a/Localization/StringsConvertor/input/vi_VN/app.json b/Localization/StringsConvertor/input/vi_VN/app.json new file mode 100644 index 00000000..5aa16c95 --- /dev/null +++ b/Localization/StringsConvertor/input/vi_VN/app.json @@ -0,0 +1,616 @@ +{ + "common": { + "alerts": { + "common": { + "please_try_again": "Vui lòng thử lại.", + "please_try_again_later": "Vui lòng thử lại sau." + }, + "sign_up_failure": { + "title": "Đăng ký không thành công" + }, + "server_error": { + "title": "Lỗi máy chủ" + }, + "vote_failure": { + "title": "Bình chọn không thành công", + "poll_ended": "Cuộc bình chọn đã kết thúc" + }, + "discard_post_content": { + "title": "Hủy bản nháp", + "message": "Xác nhận bỏ qua nội dung tút đã viết." + }, + "publish_post_failure": { + "title": "Đăng tút không thành công", + "message": "Không thể đăng tút.\nVui lòng kiểm tra kết nối mạng.", + "attachments_message": { + "video_attach_with_photo": "Không thể đính kèm video cùng với hình ảnh.", + "more_than_one_video": "Không thể đính kèm nhiều video." + } + }, + "edit_profile_failure": { + "title": "Lỗi chỉnh sửa hồ sơ", + "message": "Không thể chỉnh sửa hồ sơ. Vui lòng thử lại." + }, + "sign_out": { + "title": "Đăng xuất", + "message": "Bạn có chắc muốn đăng xuất không?", + "confirm": "Đăng xuất" + }, + "block_domain": { + "title": "Bạn thật sự muốn ẩn toàn bộ nội dung từ %s? Sẽ hợp lý hơn nếu bạn chỉ chặn hoặc ẩn một vài tài khoản cụ thể. Ẩn toàn bộ nội dung từ máy chủ sẽ khiến bạn không còn thấy nội dung từ máy chủ đó ở bất kỳ nơi nào, kể cả thông báo. Người theo dõi bạn từ máy chủ đó cũng sẽ bị xóa luôn.", + "block_entire_domain": "Chặn máy chủ" + }, + "save_photo_failure": { + "title": "Lưu hình ảnh không thành công", + "message": "Vui lòng cho phép quyền truy cập thư viện hình ảnh để lưu hình ảnh về máy." + }, + "delete_post": { + "title": "Xóa tút", + "message": "Bạn có chắc muốn xóa tút này không?" + }, + "clean_cache": { + "title": "Xóa bộ nhớ đệm", + "message": "Đã xóa %s bộ nhớ đệm." + } + }, + "controls": { + "actions": { + "back": "Quay lại", + "next": "Kế tiếp", + "previous": "Trước đó", + "open": "Mở", + "add": "Thêm", + "remove": "Xóa", + "edit": "Sửa", + "save": "Lưu", + "ok": "OK", + "done": "Xong", + "confirm": "Xác nhận", + "continue": "Tiếp tục", + "compose": "Viết tút", + "cancel": "Hủy bỏ", + "discard": "Bỏ qua", + "try_again": "Thử lại", + "take_photo": "Chụp ảnh", + "save_photo": "Lưu ảnh", + "copy_photo": "Sao chép ảnh", + "sign_in": "Đăng nhập", + "sign_up": "Đăng ký", + "see_more": "Xem thêm", + "preview": "Xem trước", + "share": "Chia sẻ", + "share_user": "Chia sẻ %s", + "share_post": "Chia sẻ tút", + "open_in_safari": "Mở bằng Safari", + "open_in_browser": "Mở trong trình duyệt", + "find_people": "Đề xuất theo dõi", + "manually_search": "Tự tìm kiếm thủ công", + "skip": "Bỏ qua", + "reply": "Trả lời", + "report_user": "Báo cáo %s", + "block_domain": "Chặn %s", + "unblock_domain": "Bỏ chặn %s", + "settings": "Cài đặt", + "delete": "Xóa" + }, + "tabs": { + "home": "Bảng tin", + "search": "Tìm kiếm", + "notification": "Thông báo", + "profile": "Trang hồ sơ" + }, + "keyboard": { + "common": { + "switch_to_tab": "Chuyển thành %s", + "compose_new_post": "Viết tút mới", + "show_favorites": "Hiện lượt thích", + "open_settings": "Mở cài đặt" + }, + "timeline": { + "previous_status": "Tút trước", + "next_status": "Tút sau", + "open_status": "Mở tút", + "open_author_profile": "Mở trang người viết tút", + "open_reblogger_profile": "Mở trang người đăng lại tút", + "reply_status": "Trả lời tút", + "toggle_reblog": "Chọn đăng lại tút", + "toggle_favorite": "Chọn thích tút", + "toggle_content_warning": "Chọn nội dung ẩn", + "preview_image": "Xem trước hình ảnh" + }, + "segmented_control": { + "previous_section": "Tới phần trước", + "next_section": "Tới phần tiếp theo" + } + }, + "status": { + "user_reblogged": "%s đăng lại", + "user_replied_to": "Trả lời %s", + "show_post": "Xem tút", + "show_user_profile": "Xem trang hồ sơ", + "content_warning": "Nội dung ẩn", + "media_content_warning": "Nhấn để hiển thị", + "tap_to_reveal": "Nhấn để hiển thị", + "poll": { + "vote": "Bình chọn", + "closed": "Kết thúc" + }, + "actions": { + "reply": "Trả lời", + "reblog": "Đăng lại", + "unreblog": "Hủy đăng lại", + "favorite": "Thích", + "unfavorite": "Bỏ thích", + "menu": "Menu", + "hide": "Ẩn", + "show_image": "Hiển thị hình ảnh", + "show_gif": "Hiển thị GIF", + "show_video_player": "Hiện trình phát video", + "tap_then_hold_to_show_menu": "Nhấn giữ để hiện menu" + }, + "tag": { + "url": "URL", + "mention": "Nhắc đến", + "link": "Liên kết", + "hashtag": "Hashtag", + "email": "Email", + "emoji": "Emoji" + }, + "visibility": { + "unlisted": "Ai cũng thấy tút này nhưng không hiện trên bảng tin máy chủ.", + "private": "Chỉ người theo dõi của họ có thể thấy tút này.", + "private_from_me": "Chỉ người theo dõi tôi có thể thấy tút này.", + "direct": "Chỉ người được nhắc đến có thể thấy tút." + } + }, + "friendship": { + "follow": "Theo dõi", + "following": "Đang theo dõi", + "request": "Yêu cầu", + "pending": "Đang chờ", + "block": "Chặn", + "block_user": "Chặn %s", + "block_domain": "Chặn %s", + "unblock": "Bỏ chặn", + "unblock_user": "Bỏ chặn %s", + "blocked": "Đã chặn", + "mute": "Ẩn", + "mute_user": "Ẩn %s", + "unmute": "Bỏ ẩn", + "unmute_user": "Bỏ ẩn %s", + "muted": "Đã ẩn", + "edit_info": "Chỉnh sửa" + }, + "timeline": { + "filtered": "Bộ lọc", + "timestamp": { + "now": "Vừa xong" + }, + "loader": { + "load_missing_posts": "Tải tút chưa đọc", + "loading_missing_posts": "Đang tải tút chưa đọc...", + "show_more_replies": "Xem lượt trả lời" + }, + "header": { + "no_status_found": "Không tìm thấy tút", + "blocking_warning": "Bạn không thể xem trang người này\ncho tới khi bạn bỏ chặn họ.\nHọ sẽ thấy trang của bạn như thế này.", + "user_blocking_warning": "Bạn không thể xem trang %s\ncho tới khi bạn bỏ chặn họ.\nHọ sẽ thấy trang của bạn như thế này.", + "blocked_warning": "Bạn không thể xem trang người này\ncho tới khi họ bỏ chặn bạn.", + "user_blocked_warning": "Bạn không thể xem trang %s\ncho tới khi họ bỏ chặn bạn.", + "suspended_warning": "Người dùng đã bị vô hiệu hóa.", + "user_suspended_warning": "%s đã bị vô hiệu hóa." + } + } + } + }, + "scene": { + "welcome": { + "slogan": "Mạng xã hội\ndo bạn kiểm soát.", + "get_started": "Bắt đầu", + "log_in": "Đăng nhập" + }, + "server_picker": { + "title": "Mastodon gồm nhiều cộng đồng với nhiều thành viên khác nhau.", + "subtitle": "Chọn một cộng đồng dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn.", + "subtitle_extend": "Chọn một cộng đồng dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn. Mỗi cộng đồng có thể được vận hành bởi một tổ chức độc lập hoặc một cá nhân.", + "button": { + "category": { + "all": "Toàn bộ", + "all_accessiblity_description": "Phân loại: Toàn bộ", + "academia": "học thuật", + "activism": "hoạt động xã hội", + "food": "ăn uống", + "furry": "furry", + "games": "trò chơi", + "general": "chung", + "journalism": "tin tức", + "lgbt": "lgbt", + "regional": "khu vực", + "art": "nghệ thuật", + "music": "âm nhạc", + "tech": "công nghệ" + }, + "see_less": "Ẩn bớt", + "see_more": "Nhiều hơn" + }, + "label": { + "language": "NGÔN NGỮ", + "users": "NGƯỜI DÙNG", + "category": "PHÂN LOẠI" + }, + "input": { + "placeholder": "Tìm cộng đồng" + }, + "empty_state": { + "finding_servers": "Đang tìm máy chủ hoạt động...", + "bad_network": "Đã xảy ra lỗi. Hãy thử lại hoặc kiểm tra kết nối internet của bạn.", + "no_results": "Không có kết quả" + } + }, + "register": { + "title": "Hãy để tôi đăng ký trên %s", + "input": { + "avatar": { + "delete": "Xóa" + }, + "username": { + "placeholder": "tên người dùng", + "duplicate_prompt": "Tên người dùng đã tồn tại." + }, + "display_name": { + "placeholder": "tên hiển thị" + }, + "email": { + "placeholder": "email" + }, + "password": { + "placeholder": "mật khẩu", + "require": "Mật khẩu phải tối thiểu:", + "character_limit": "8 ký tự", + "accessibility": { + "checked": "đã ổn", + "unchecked": "chưa ổn" + }, + "hint": "Mật khẩu của bạn phải dài tối thiểu 8 ký tự" + }, + "invite": { + "registration_user_invite_request": "Vì sao bạn muốn tham gia?" + } + }, + "error": { + "item": { + "username": "Tên người dùng", + "email": "Email", + "password": "Mật khẩu", + "agreement": "Thoả thuận", + "locale": "Cục bộ", + "reason": "Lý do" + }, + "reason": { + "blocked": "%s dùng dịch vụ email bị cấm", + "unreachable": "%s không tồn tại", + "taken": "%s đã tồn tại", + "reserved": "%s là một từ khóa hạn chế", + "accepted": "%s phải được đồng ý", + "blank": "%s là bắt buộc", + "invalid": "%s không hợp lệ", + "too_long": "%s quá dài", + "too_short": "%s quá ngắn", + "inclusion": "%s chứa ký tự không được hỗ trợ" + }, + "special": { + "username_invalid": "Tên người dùng chỉ có thể chứa các ký tự chữ và số và dấu gạch dưới", + "username_too_long": "Tên người dùng không thể dài hơn 30 ký tự", + "email_invalid": "Đây không phải là một địa chỉ email khả dụng", + "password_too_short": "Mật khẩu của bạn quá ngắn, phải có ít nhất 8 ký tự" + } + } + }, + "server_rules": { + "title": "Quy tắc máy chủ.", + "subtitle": "Được ban hành và áp dụng bởi quản trị viên %s", + "prompt": "Tiếp tục nghĩa là bạn đồng ý điều khoản dịch vụ và chính sách bảo mật của %s.", + "terms_of_service": "điều khoản dịch vụ", + "privacy_policy": "chính sách bảo mật", + "button": { + "confirm": "Tôi đồng ý" + } + }, + "confirm_email": { + "title": "Còn điều này nữa.", + "subtitle": "Nhấn vào liên kết chúng tôi gửi qua email để xác thực tài khoản.", + "button": { + "open_email_app": "Mở ứng dụng email", + "resend": "Gửi lại" + }, + "dont_receive_email": { + "title": "Kiểm tra email", + "description": "Kiểm tra địa chỉ email của bạn đúng chưa hoặc có bị chuyển vào thư rác.", + "resend_email": "Gửi lại email" + }, + "open_email_app": { + "title": "Kiểm tra hộp thư của bạn.", + "description": "Chúng tôi vừa gửi email cho bạn. Kiểm tra thư rác nếu bạn không thấy.", + "mail": "Email", + "open_email_client": "Mở ứng dụng email" + } + }, + "home_timeline": { + "title": "Bảng tin", + "navigation_bar_state": { + "offline": "Ngoại tuyến", + "new_posts": "Đọc những tút mới", + "published": "Đã đăng!", + "Publishing": "Đang đăng tút..." + } + }, + "suggestion_account": { + "title": "Đề xuất theo dõi", + "follow_explain": "Khi theo dõi ai đó, bạn sẽ thấy tút của họ trong bảng tin." + }, + "compose": { + "title": { + "new_post": "Viết tút", + "new_reply": "Viết trả lời" + }, + "media_selection": { + "camera": "Chụp ảnh", + "photo_library": "Thư viện hình ảnh", + "browse": "Chọn" + }, + "content_input_placeholder": "Cho thế giới biết bạn đang nghĩ gì", + "compose_action": "Đăng", + "replying_to_user": "trả lời đến %s", + "attachment": { + "photo": "ảnh", + "video": "video", + "attachment_broken": "%s này bị lỗi và không thể\ntải lên Mastodon.", + "description_photo": "Mô tả hình ảnh cho người khiếm thị...", + "description_video": "Mô tả video cho người khiếm thị..." + }, + "poll": { + "duration_time": "Thời gian: %s", + "thirty_minutes": "30 phút", + "one_hour": "1 giờ", + "six_hours": "6 giờ", + "one_day": "1 ngày", + "three_days": "3 ngày", + "seven_days": "7 ngày", + "option_number": "Lựa chọn %ld" + }, + "content_warning": { + "placeholder": "Viết nội dung ẩn của bạn ở đây..." + }, + "visibility": { + "public": "Công khai", + "unlisted": "Hạn chế", + "private": "Riêng tư", + "direct": "Chỉ người được nhắc đến" + }, + "auto_complete": { + "space_to_add": "Khoảng cách để thêm" + }, + "accessibility": { + "append_attachment": "Thêm media", + "append_poll": "Tạo bình chọn", + "remove_poll": "Xóa bình chọn", + "custom_emoji_picker": "Chọn emoji", + "enable_content_warning": "Bật nội dung ẩn", + "disable_content_warning": "Tắt nội dung ẩn", + "post_visibility_menu": "Menu hiển thị tút" + }, + "keyboard": { + "discard_post": "Hủy đăng tút", + "publish_post": "Đăng tút", + "toggle_poll": "Mở bình chọn", + "toggle_content_warning": "Mở nội dung ẩn", + "append_attachment_entry": "Thêm media - %s", + "select_visibility_entry": "Thay đổi quyền riêng tư - %s" + } + }, + "profile": { + "dashboard": { + "posts": "tút", + "following": "theo dõi", + "followers": "người theo dõi" + }, + "fields": { + "add_row": "Thêm hàng", + "placeholder": { + "label": "Nhãn", + "content": "Nội dung" + } + }, + "segmented_control": { + "posts": "Tút", + "replies": "Trả lời", + "posts_and_replies": "Tút và trả lời", + "media": "Media", + "about": "Giới thiệu" + }, + "relationship_action_alert": { + "confirm_mute_user": { + "title": "Ẩn người dùng", + "message": "Xác nhận ẩn %s" + }, + "confirm_unmute_user": { + "title": "Bỏ ẩn người dùng", + "message": "Xác nhận bỏ ẩn %s" + }, + "confirm_block_user": { + "title": "Chặn người dùng", + "message": "Xác nhận chặn %s" + }, + "confirm_unblock_user": { + "title": "Bỏ chặn người dùng", + "message": "Xác nhận bỏ chặn %s" + } + }, + "accessibility": { + "show_avatar_image": "Hiển thị ảnh đại diện", + "edit_avatar_image": "Sửa ảnh đại diện", + "show_banner_image": "Hiển thị ảnh bìa", + "double_tap_to_open_the_list": "Nhấn hai lần để mở danh sách" + } + }, + "follower": { + "footer": "Không hiển thị người theo dõi từ máy chủ khác." + }, + "following": { + "footer": "Không hiển thị người bạn theo dõi từ máy chủ khác." + }, + "search": { + "title": "Tìm kiếm", + "search_bar": { + "placeholder": "Tìm hashtag và người dùng", + "cancel": "Hủy bỏ" + }, + "recommend": { + "button_text": "Xem tất cả", + "hash_tag": { + "title": "Xu hướng trên Mastodon", + "description": "Những hashtag đang được sử dụng nhiều nhất", + "people_talking": "%s người đang thảo luận" + }, + "accounts": { + "title": "Những người bạn có thể thích", + "description": "Bạn có thể muốn theo dõi những người này", + "follow": "Theo dõi" + } + }, + "searching": { + "segment": { + "all": "Tất cả", + "people": "Người dùng", + "hashtags": "Hashtag", + "posts": "Tút" + }, + "empty_state": { + "no_results": "Không có kết quả" + }, + "recent_search": "Tìm kiếm gần đây", + "clear": "Xóa" + } + }, + "discovery": { + "tabs": { + "posts": "Tút", + "hashtags": "Hashtag", + "news": "Tin tức", + "for_you": "Dành cho bạn" + } + }, + "favorite": { + "title": "Lượt thích" + }, + "notification": { + "title": { + "Everything": "Mọi thứ", + "Mentions": "Lượt nhắc đến" + }, + "notification_description": { + "followed_you": "đã theo dõi bạn", + "favorited_your_post": "thích tút của bạn", + "reblogged_your_post": "đăng lại tút của bạn", + "mentioned_you": "nhắc đến bạn", + "request_to_follow_you": "yêu cầu theo dõi bạn", + "poll_has_ended": "cuộc bình chọn đã kết thúc" + }, + "keyobard": { + "show_everything": "Hiện mọi thứ", + "show_mentions": "Hiện lượt nhắc" + } + }, + "thread": { + "back_title": "Tút", + "title": "Tút của %s" + }, + "settings": { + "title": "Cài đặt", + "section": { + "appearance": { + "title": "Giao diện", + "automatic": "Tự động", + "light": "Sáng", + "dark": "Tối" + }, + "look_and_feel": { + "title": "Giao diện", + "use_system": "Mặc định hệ thống", + "really_dark": "Tối Mạnh", + "sorta_dark": "Tối Nhẹ", + "light": "Sáng" + }, + "notifications": { + "title": "Thông báo", + "favorites": "Thích tút của tôi", + "follows": "Theo dõi tôi", + "boosts": "Đăng lại tút của tôi", + "mentions": "Nhắc đến tôi", + "trigger": { + "anyone": "ai đó", + "follower": "người theo dõi", + "follow": "người tôi theo dõi", + "noone": "không một ai", + "title": "Thông báo khi" + } + }, + "preference": { + "title": "Chung", + "true_black_dark_mode": "Chế độ tối chân thật", + "disable_avatar_animation": "Tắt ảnh đại diện GIF", + "disable_emoji_animation": "Tắt emoji dạng GIF", + "using_default_browser": "Dùng trình duyệt mặc định", + "open_links_in_mastodon": "Mở liên kết trong Mastodon" + }, + "boring_zone": { + "title": "Nhàm chán", + "account_settings": "Cài đặt tài khoản", + "terms": "Điều khoản dịch vụ", + "privacy": "Chính sách bảo mật" + }, + "spicy_zone": { + "title": "Thú vị", + "clear": "Xóa bộ nhớ đệm", + "signout": "Đăng xuất" + } + }, + "footer": { + "mastodon_description": "Mastodon là phần mềm mã nguồn mở. Bạn có thể báo lỗi trên GitHub tại %s (%s)" + }, + "keyboard": { + "close_settings_window": "Đóng cửa sổ cài đặt" + } + }, + "report": { + "title_report": "Báo cáo", + "title": "Báo cáo %s", + "step1": "Bước 1 trong 2", + "step2": "Bước 2 trong 2", + "content1": "Bạn muốn thêm tút nào vào báo cáo nữa không?", + "content2": "Kiểm duyệt viên cần biết gì về báo cáo này?", + "report_sent_title": "Cảm ơn đã báo cáo, chúng tôi sẽ xem xét kỹ.", + "send": "Gửi báo cáo", + "skip_to_send": "Gửi không ghi chú", + "text_placeholder": "Nhập hoặc bổ sung chú thích", + "reported": "ĐÃ BÁO CÁO" + }, + "preview": { + "keyboard": { + "close_preview": "Đóng xem trước", + "show_next": "Hiện kế tiếp", + "show_previous": "Hiện trước đó" + } + }, + "account_list": { + "tab_bar_hint": "Đang dùng tài khoản: %s. Nhấn hai lần và giữ để đổi sang tài khoản khác", + "dismiss_account_switcher": "Bỏ qua chuyển đổi tài khoản", + "add_account": "Thêm tài khoản" + }, + "wizard": { + "new_in_mastodon": "Mới trên Mastodon", + "multiple_account_switch_intro_description": "Chuyển đổi giữa nhiều tài khoản bằng cách đè giữ nút tài khoản.", + "accessibility_hint": "Nhấn hai lần để bỏ qua" + } + } +} \ No newline at end of file diff --git a/Localization/StringsConvertor/input/vi_VN/ios-infoPlist.json b/Localization/StringsConvertor/input/vi_VN/ios-infoPlist.json new file mode 100644 index 00000000..2170219a --- /dev/null +++ b/Localization/StringsConvertor/input/vi_VN/ios-infoPlist.json @@ -0,0 +1,6 @@ +{ + "NSCameraUsageDescription": "Được sử dụng để chụp ảnh cho tút", + "NSPhotoLibraryAddUsageDescription": "Được sử dụng để lưu ảnh vào Thư viện ảnh", + "NewPostShortcutItemTitle": "Viết tút", + "SearchShortcutItemTitle": "Tìm kiếm" +} diff --git a/Localization/StringsConvertor/input/zh_CN/Localizable.stringsdict b/Localization/StringsConvertor/input/zh_CN/Localizable.stringsdict index 12b8b5f6..6c2661ee 100644 --- a/Localization/StringsConvertor/input/zh_CN/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/zh_CN/Localizable.stringsdict @@ -100,6 +100,20 @@ %ld 条转发 + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 条回复 + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/zh_CN/app.json b/Localization/StringsConvertor/input/zh_CN/app.json index 74ea0529..db871d5a 100644 --- a/Localization/StringsConvertor/input/zh_CN/app.json +++ b/Localization/StringsConvertor/input/zh_CN/app.json @@ -130,6 +130,7 @@ "show_user_profile": "查看用户个人资料", "content_warning": "内容警告", "media_content_warning": "点击任意位置显示", + "tap_to_reveal": "点击以显示", "poll": { "vote": "投票", "closed": "已关闭" @@ -141,7 +142,11 @@ "favorite": "喜欢", "unfavorite": "取消喜欢", "menu": "菜单", - "hide": "隐藏" + "hide": "隐藏", + "show_image": "显示图片", + "show_gif": "显示 GIF", + "show_video_player": "显示视频播放器", + "tap_then_hold_to_show_menu": "长按以显示菜单" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "解除屏蔽帐户", "message": "确认取消屏蔽 %s" } + }, + "accessibility": { + "show_avatar_image": "显示头像", + "edit_avatar_image": "编辑头像", + "show_banner_image": "显示顶部横幅图片", + "double_tap_to_open_the_list": "双击打开列表" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "清除" } }, + "discovery": { + "tabs": { + "posts": "帖子", + "hashtags": "话题", + "news": "新闻", + "for_you": "为你推荐" + } + }, "favorite": { "title": "你的喜欢" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "纯黑模式", "disable_avatar_animation": "禁用动画头像", "disable_emoji_animation": "禁用动画表情", - "using_default_browser": "使用默认浏览器打开链接" + "using_default_browser": "使用默认浏览器打开链接", + "open_links_in_mastodon": "在 Mastodon 中打开链接" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/StringsConvertor/input/zh_TW/Localizable.stringsdict b/Localization/StringsConvertor/input/zh_TW/Localizable.stringsdict index dafab129..c69fd562 100644 --- a/Localization/StringsConvertor/input/zh_TW/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/zh_TW/Localizable.stringsdict @@ -100,6 +100,20 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/Localization/StringsConvertor/input/zh_TW/app.json b/Localization/StringsConvertor/input/zh_TW/app.json index be2442e4..909bd7c6 100644 --- a/Localization/StringsConvertor/input/zh_TW/app.json +++ b/Localization/StringsConvertor/input/zh_TW/app.json @@ -130,6 +130,7 @@ "show_user_profile": "Show user profile", "content_warning": "Content Warning", "media_content_warning": "Tap anywhere to reveal", + "tap_to_reveal": "Tap to reveal", "poll": { "vote": "投票", "closed": "Closed" @@ -141,7 +142,11 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "menu": "Menu", - "hide": "Hide" + "hide": "Hide", + "show_image": "Show image", + "show_gif": "Show GIF", + "show_video_player": "Show video player", + "tap_then_hold_to_show_menu": "Tap then hold to show menu" }, "tag": { "url": "URL", @@ -440,6 +445,12 @@ "title": "Unblock Account", "message": "Confirm to unblock %s" } + }, + "accessibility": { + "show_avatar_image": "Show avatar image", + "edit_avatar_image": "Edit avatar image", + "show_banner_image": "Show banner image", + "double_tap_to_open_the_list": "Double tap to open the list" } }, "follower": { @@ -481,6 +492,14 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + } + }, "favorite": { "title": "Your Favorites" }, @@ -541,7 +560,8 @@ "true_black_dark_mode": "True black dark mode", "disable_avatar_animation": "Disable animated avatars", "disable_emoji_animation": "Disable animated emojis", - "using_default_browser": "Use default browser to open links" + "using_default_browser": "Use default browser to open links", + "open_links_in_mastodon": "Open links in Mastodon" }, "boring_zone": { "title": "The Boring Zone", diff --git a/Localization/app.json b/Localization/app.json index f0dc0ebf..a8fc9031 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -129,6 +129,7 @@ "show_post": "Show Post", "show_user_profile": "Show user profile", "content_warning": "Content Warning", + "sensitive_content": "Sensitive Content", "media_content_warning": "Tap anywhere to reveal", "tap_to_reveal": "Tap to reveal", "poll": { @@ -492,6 +493,15 @@ "clear": "Clear" } }, + "discovery": { + "tabs": { + "posts": "Posts", + "hashtags": "Hashtags", + "news": "News", + "for_you": "For You" + }, + "intro": "These are the posts gaining traction in your corner of Mastodon." + }, "favorite": { "title": "Your Favorites" }, diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 69596a20..8b212674 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B9125F60EA700143C56 /* UIControl.swift */; }; 2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */; }; 2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */; }; - 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAB925CB9B0500C9ED86 /* UIView.swift */; }; 2D34D9D126148D9E0081BFC0 /* APIService+Recommend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */; }; 2D34D9DB261494120081BFC0 /* APIService+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9DA261494120081BFC0 /* APIService+Search.swift */; }; 2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D35237926256D920031AF25 /* NotificationSection.swift */; }; @@ -116,9 +115,7 @@ DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; }; DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; }; - DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; }; DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; }; - DB01E23326A98F0900C3965B /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB01E23226A98F0900C3965B /* MastodonMeta */; }; DB01E23526A98F0900C3965B /* MetaTextKit in Frameworks */ = {isa = PBXBuildFile; productRef = DB01E23426A98F0900C3965B /* MetaTextKit */; }; DB023D26279FFB0A005AC798 /* ShareActivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023D25279FFB0A005AC798 /* ShareActivityProvider.swift */; }; DB023D2827A0FABD005AC798 /* NotificationTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023D2727A0FABD005AC798 /* NotificationTableViewCellDelegate.swift */; }; @@ -131,6 +128,8 @@ DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; }; DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; }; DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; }; + DB02EA0B280D180D00E751C5 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = DB02EA0A280D180D00E751C5 /* KeychainAccess */; }; + DB02EA0D280D184B00E751C5 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB02EA0C280D184B00E751C5 /* CommonOSLog */; }; DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03A792272A7E5700EE37C5 /* SidebarListHeaderView.swift */; }; DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03A794272A981400EE37C5 /* ContentSplitViewController.swift */; }; DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; }; @@ -149,6 +148,7 @@ DB0618072785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */; }; DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */; }; DB084B5725CBC56C00F898ED /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Status.swift */; }; + DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */; }; DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; }; DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB0C946426A6FD4D0088FB11 /* AlamofireImage */; }; DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */; }; @@ -215,7 +215,6 @@ DB336F3F278E668C0031E64B /* StatusTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB336F3E278E668C0031E64B /* StatusTableViewCell+ViewModel.swift */; }; DB336F41278E68480031E64B /* StatusView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB336F40278E68480031E64B /* StatusView+Configuration.swift */; }; DB336F43278EB1690031E64B /* MediaView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB336F42278EB1680031E64B /* MediaView+Configuration.swift */; }; - DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */; }; DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */; }; DB36679F268ABAF20027D07F /* ComposeStatusAttachmentSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */; }; DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */; }; @@ -223,6 +222,19 @@ DB3667A6268AE2620027D07F /* ComposeStatusPollSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */; }; DB3667A8268AE2900027D07F /* ComposeStatusPollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */; }; DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; }; + DB3E6FDD2806A40F00B035AE /* DiscoveryHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FDC2806A40F00B035AE /* DiscoveryHashtagsViewController.swift */; }; + DB3E6FE02806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FDF2806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift */; }; + DB3E6FE22806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FE12806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift */; }; + DB3E6FE42806A5B800B035AE /* DiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FE32806A5B800B035AE /* DiscoverySection.swift */; }; + DB3E6FE72806A7A200B035AE /* DiscoveryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FE62806A7A200B035AE /* DiscoveryItem.swift */; }; + DB3E6FE92806BD2200B035AE /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FE82806BD2200B035AE /* ThemeService.swift */; }; + DB3E6FEC2806D7F100B035AE /* DiscoveryNewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FEB2806D7F100B035AE /* DiscoveryNewsViewController.swift */; }; + DB3E6FEF2806D82600B035AE /* DiscoveryNewsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FEE2806D82600B035AE /* DiscoveryNewsViewModel.swift */; }; + DB3E6FF12806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF02806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift */; }; + DB3E6FF32806D97400B035AE /* DiscoveryNewsViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF22806D97400B035AE /* DiscoveryNewsViewModel+State.swift */; }; + DB3E6FF52807C40300B035AE /* DiscoveryForYouViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF42807C40300B035AE /* DiscoveryForYouViewController.swift */; }; + DB3E6FF82807C45300B035AE /* DiscoveryForYouViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF72807C45300B035AE /* DiscoveryForYouViewModel.swift */; }; + DB3E6FFA2807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3E6FF92807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift */; }; DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; }; DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; }; DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDB25BAA00100D1B89D /* Main.storyboard */; }; @@ -336,7 +348,6 @@ DB6804872637CD4C00430867 /* AppShared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DB6804D12637CE4700430867 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804D02637CE4700430867 /* UserDefaults.swift */; }; DB6804FD2637CFEC00430867 /* AppSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804FC2637CFEC00430867 /* AppSecret.swift */; }; - DB6805102637D0F800430867 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = DB68050F2637D0F800430867 /* KeychainAccess */; }; DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */; }; DB68A04A25E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68A04925E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift */; }; DB68A05D25E9055900CFDF14 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DB68A05C25E9055900CFDF14 /* Settings.bundle */; }; @@ -361,7 +372,6 @@ DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */; }; DB6B750427300B4000C70B6E /* TimelineFooterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */; }; DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */; }; - DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */; }; DB6D1B44263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */; }; DB6D9F3526351B7A008423CD /* NotificationService+Decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */; }; DB6D9F42263527CE008423CD /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB6D9F41263527CE008423CD /* AlamofireImage */; }; @@ -376,8 +386,6 @@ DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F9626367249008423CD /* SettingsViewController.swift */; }; DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */; }; DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */; }; - DB71C7CB271D5A0300BE3819 /* LineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71C7CA271D5A0300BE3819 /* LineChartView.swift */; }; - DB71C7CD271D7F4300BE3819 /* CurveAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */; }; DB71FD5225F8CCAA00512AE1 /* APIService+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */; }; DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */; }; DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; }; @@ -495,8 +503,6 @@ DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525552611EDCA002F1F29 /* UserTimelineViewModel.swift */; }; DBB5255E2611F07A002F1F29 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */; }; DBB525642612C988002F1F29 /* MeProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525632612C988002F1F29 /* MeProfileViewModel.swift */; }; - DBB5256E2612D5A1002F1F29 /* ProfileStatusDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */; }; - DBB525852612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */; }; DBB8AB4626AECDE200F6D281 /* SendPostIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */; }; DBB8AB4826AED09C00F6D281 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DBB8AB4726AED09C00F6D281 /* MastodonSDK */; }; DBB8AB4A26AED0B500F6D281 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB8AB4926AED0B500F6D281 /* APIService.swift */; }; @@ -508,19 +514,9 @@ DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24A926A5301B00398BB9 /* MastodonSDK */; }; DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */; }; DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24B726A5421800398BB9 /* CommonOSLog */; }; - DBBC24BC26A542F500398BB9 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BB26A542F500398BB9 /* ThemeService.swift */; }; - DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */; }; - DBBC24C126A5443100398BB9 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BF26A5443100398BB9 /* SystemTheme.swift */; }; - DBBC24C426A544B900398BB9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24C326A544B900398BB9 /* Theme.swift */; }; - DBBC24C626A5456000398BB9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24C326A544B900398BB9 /* Theme.swift */; }; - DBBC24C726A5456400398BB9 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BF26A5443100398BB9 /* SystemTheme.swift */; }; - DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BB26A542F500398BB9 /* ThemeService.swift */; }; - DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */; }; DBBC24CB26A546C000398BB9 /* ThemePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */; }; - DBBC24CF26A547AE00398BB9 /* ThemeService+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */; }; DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; }; DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */; }; - DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */; }; DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; }; DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */; }; DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */; }; @@ -550,6 +546,13 @@ DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */; }; DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */; }; DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */; }; + DBDFF1902805543100557A48 /* DiscoveryPostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF18F2805543100557A48 /* DiscoveryPostsViewController.swift */; }; + DBDFF1932805554900557A48 /* DiscoveryPostsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF1922805554900557A48 /* DiscoveryPostsViewModel.swift */; }; + DBDFF1952805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF1942805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift */; }; + DBDFF197280556D900557A48 /* DiscoveryPostsViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF196280556D900557A48 /* DiscoveryPostsViewModel+State.swift */; }; + DBDFF19A28055A1400557A48 /* DiscoveryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF19928055A1400557A48 /* DiscoveryViewController.swift */; }; + DBDFF19C28055BD600557A48 /* DiscoveryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF19B28055BD600557A48 /* DiscoveryViewModel.swift */; }; + DBDFF19E2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF19D2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift */; }; DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */; }; DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */; }; DBE3CA6827A39CAB00AFE27B /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; @@ -589,7 +592,6 @@ DBFEF05F26A57715006D7ED1 /* StatusAuthorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */; }; DBFEF06026A57715006D7ED1 /* StatusAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */; }; DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */; }; - DBFEF06926A67E45006D7ED1 /* AppearancePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */; }; DBFEF06D26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF06C26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift */; }; DBFEF06F26A690C4006D7ED1 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; }; DBFEF07326A6913D006D7ED1 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF07226A6913D006D7ED1 /* APIService.swift */; }; @@ -728,7 +730,6 @@ 2D206B9125F60EA700143C56 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = ""; }; 2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Gesture.swift"; sourceTree = ""; }; 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = ""; }; - 2D32EAB925CB9B0500C9ED86 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; 2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Recommend.swift"; sourceTree = ""; }; 2D34D9DA261494120081BFC0 /* APIService+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Search.swift"; sourceTree = ""; }; 2D35237926256D920031AF25 /* NotificationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSection.swift; sourceTree = ""; }; @@ -870,6 +871,7 @@ DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewModel+Diffable.swift"; sourceTree = ""; }; DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterAvatarTableViewCell.swift; sourceTree = ""; }; DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; }; + DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryIntroBannerView.swift; sourceTree = ""; }; DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = ""; }; DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = ""; }; DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListCollectionViewCell.swift; sourceTree = ""; }; @@ -941,7 +943,6 @@ DB336F3E278E668C0031E64B /* StatusTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusTableViewCell+ViewModel.swift"; sourceTree = ""; }; DB336F40278E68480031E64B /* StatusView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusView+Configuration.swift"; sourceTree = ""; }; DB336F42278EB1680031E64B /* MediaView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaView+Configuration.swift"; sourceTree = ""; }; - DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRelationshipActionButton.swift; sourceTree = ""; }; DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentTableViewCell.swift; sourceTree = ""; }; DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentSection.swift; sourceTree = ""; }; DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentItem.swift; sourceTree = ""; }; @@ -949,6 +950,19 @@ DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollSection.swift; sourceTree = ""; }; DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollItem.swift; sourceTree = ""; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = ""; }; + DB3E6FDC2806A40F00B035AE /* DiscoveryHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryHashtagsViewController.swift; sourceTree = ""; }; + DB3E6FDF2806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryHashtagsViewModel.swift; sourceTree = ""; }; + DB3E6FE12806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryHashtagsViewModel+Diffable.swift"; sourceTree = ""; }; + DB3E6FE32806A5B800B035AE /* DiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverySection.swift; sourceTree = ""; }; + DB3E6FE62806A7A200B035AE /* DiscoveryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryItem.swift; sourceTree = ""; }; + DB3E6FE82806BD2200B035AE /* ThemeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeService.swift; sourceTree = ""; }; + DB3E6FEB2806D7F100B035AE /* DiscoveryNewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryNewsViewController.swift; sourceTree = ""; }; + DB3E6FEE2806D82600B035AE /* DiscoveryNewsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryNewsViewModel.swift; sourceTree = ""; }; + DB3E6FF02806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryNewsViewModel+Diffable.swift"; sourceTree = ""; }; + DB3E6FF22806D97400B035AE /* DiscoveryNewsViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryNewsViewModel+State.swift"; sourceTree = ""; }; + DB3E6FF42807C40300B035AE /* DiscoveryForYouViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryForYouViewController.swift; sourceTree = ""; }; + DB3E6FF72807C45300B035AE /* DiscoveryForYouViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryForYouViewModel.swift; sourceTree = ""; }; + DB3E6FF92807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryForYouViewModel+Diffable.swift"; sourceTree = ""; }; DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB427DD525BAA00100D1B89D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DB427DD725BAA00100D1B89D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -1105,7 +1119,6 @@ DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTableViewCell.swift; sourceTree = ""; }; DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFooterTableViewCell.swift; sourceTree = ""; }; DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error.swift"; sourceTree = ""; }; - DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePreference.swift; sourceTree = ""; }; DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+API+Subscriptions+Policy.swift"; sourceTree = ""; }; DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationService+Decrypt.swift"; sourceTree = ""; }; DB6D9F4826353FD6008423CD /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; @@ -1119,8 +1132,6 @@ DB6D9F9626367249008423CD /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewController.swift; sourceTree = ""; }; DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTopChevronView.swift; sourceTree = ""; }; - DB71C7CA271D5A0300BE3819 /* LineChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartView.swift; sourceTree = ""; }; - DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurveAlgorithm.swift; sourceTree = ""; }; DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Status.swift"; sourceTree = ""; }; DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = ""; }; DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = ""; }; @@ -1249,20 +1260,12 @@ DBB525552611EDCA002F1F29 /* UserTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTimelineViewModel.swift; sourceTree = ""; }; DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; }; DBB525632612C988002F1F29 /* MeProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeProfileViewModel.swift; sourceTree = ""; }; - DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStatusDashboardView.swift; sourceTree = ""; }; - DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStatusDashboardMeterView.swift; sourceTree = ""; }; DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendPostIntentHandler.swift; sourceTree = ""; }; DBB8AB4926AED0B500F6D281 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; DBB9759B262462E1004620BD /* ThreadMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMetaView.swift; sourceTree = ""; }; DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = ""; }; DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = ""; }; - DBBC24BB26A542F500398BB9 /* ThemeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeService.swift; sourceTree = ""; }; - DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonTheme.swift; sourceTree = ""; }; - DBBC24BF26A5443100398BB9 /* SystemTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemTheme.swift; sourceTree = ""; }; - DBBC24C326A544B900398BB9 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; - DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeService+Appearance.swift"; sourceTree = ""; }; DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = ""; }; - DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = ""; }; DBBC50C0278ED49200AF0CC6 /* MastodonAuthenticationBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationBox.swift; sourceTree = ""; }; DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = ""; }; DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = ""; }; @@ -1293,6 +1296,13 @@ DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+TableViewControllerNavigateable.swift"; sourceTree = ""; }; DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+StatusTableViewControllerNavigateable.swift"; sourceTree = ""; }; DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Onboarding.swift"; sourceTree = ""; }; + DBDFF18F2805543100557A48 /* DiscoveryPostsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryPostsViewController.swift; sourceTree = ""; }; + DBDFF1922805554900557A48 /* DiscoveryPostsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryPostsViewModel.swift; sourceTree = ""; }; + DBDFF1942805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryPostsViewModel+Diffable.swift"; sourceTree = ""; }; + DBDFF196280556D900557A48 /* DiscoveryPostsViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryPostsViewModel+State.swift"; sourceTree = ""; }; + DBDFF19928055A1400557A48 /* DiscoveryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryViewController.swift; sourceTree = ""; }; + DBDFF19B28055BD600557A48 /* DiscoveryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryViewModel.swift; sourceTree = ""; }; + DBDFF19D2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoveryPostsViewController+DataSourceProvider.swift"; sourceTree = ""; }; DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewController.swift; sourceTree = ""; }; DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewModel.swift; sourceTree = ""; }; DBE3CDBA261C427900430CC6 /* TimelineHeaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineHeaderTableViewCell.swift; sourceTree = ""; }; @@ -1317,6 +1327,12 @@ DBF3B7402733EB9400E21627 /* MastodonLocalCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLocalCode.swift; sourceTree = ""; }; DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Mastodon.xctestplan; path = Mastodon/Mastodon.xctestplan; sourceTree = ""; }; DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MastodonSDK.xctestplan; sourceTree = ""; }; + DBF81C7427F68F5A00004A56 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/Intents.strings; sourceTree = ""; }; + DBF81C7527F68F5A00004A56 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/InfoPlist.strings; sourceTree = ""; }; + DBF81C7627F68F5A00004A56 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = kab; path = kab.lproj/Intents.stringsdict; sourceTree = ""; }; + DBF81C7727F6913300004A56 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Intents.strings; sourceTree = ""; }; + DBF81C7827F6913300004A56 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/InfoPlist.strings; sourceTree = ""; }; + DBF81C7927F6913300004A56 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = vi.lproj/Intents.stringsdict; sourceTree = ""; }; DBF8AE13263293E400C9C23C /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; DBF8AE15263293E400C9C23C /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; DBF8AE17263293E400C9C23C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1357,7 +1373,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */, DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */, 2D939AC825EE14620076FA61 /* CropViewController in Frameworks */, DBB525082611EAC0002F1F29 /* Tabman in Frameworks */, @@ -1367,10 +1382,10 @@ DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */, DB552D4F26BBD10C00E481F6 /* OrderedCollections in Frameworks */, 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */, - DB01E23326A98F0900C3965B /* MastodonMeta in Frameworks */, DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */, DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */, DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */, + DB02EA0D280D184B00E751C5 /* CommonOSLog in Frameworks */, 2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */, 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */, DBF7A0FC26830C33004176A2 /* FPSIndicator in Frameworks */, @@ -1400,7 +1415,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DB6805102637D0F800430867 /* KeychainAccess in Frameworks */, + DB02EA0B280D180D00E751C5 /* KeychainAccess in Frameworks */, EE93E8E8F9E0C39EAAEBD92F /* Pods_AppShared.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1648,7 +1663,6 @@ 2D5A3D0125CF8640002347D6 /* Vender */ = { isa = PBXGroup; children = ( - DB71C7CC271D7F4300BE3819 /* CurveAlgorithm.swift */, 2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */, DB51D170262832380062B7A1 /* BlurHashDecode.swift */, DB51D171262832380062B7A1 /* BlurHashEncode.swift */, @@ -1667,7 +1681,6 @@ DB45FB0425CA87B4005A8AC7 /* APIService */, DB49A61925FF327D00B98345 /* EmojiService */, DB9A489B26036E19008B817C /* MastodonAttachmentService */, - DBBC24BD26A5441A00398BB9 /* ThemeService */, DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */, 2DA6054625F716A2006356F9 /* PlaybackState.swift */, DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */, @@ -1711,6 +1724,7 @@ DB4F097626A0398000D62E92 /* Compose */, DB0617F727855B010030EE79 /* Notification */, DB4F097726A039A200D62E92 /* Search */, + DB3E6FE52806A5BA00B035AE /* Discovery */, DB0617FA27855B660030EE79 /* Settings */, DBCBED2226132E1D00B49291 /* FetchedResultsController */, ); @@ -1797,7 +1811,6 @@ isa = PBXGroup; children = ( 2DCB73FC2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift */, - DB71C7CA271D5A0300BE3819 /* LineChartView.swift */, ); path = View; sourceTree = ""; @@ -2012,6 +2025,14 @@ path = CoreDataStack; sourceTree = ""; }; + DB0A322F280EEA00001729D2 /* View */ = { + isa = PBXGroup; + children = ( + DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */, + ); + path = View; + sourceTree = ""; + }; DB0C947826A7FE950088FB11 /* Button */ = { isa = PBXGroup; children = ( @@ -2091,6 +2112,54 @@ path = Resources; sourceTree = ""; }; + DB3E6FDE2806A41200B035AE /* Hashtags */ = { + isa = PBXGroup; + children = ( + DB3E6FDC2806A40F00B035AE /* DiscoveryHashtagsViewController.swift */, + DB3E6FDF2806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift */, + DB3E6FE12806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift */, + ); + path = Hashtags; + sourceTree = ""; + }; + DB3E6FE52806A5BA00B035AE /* Discovery */ = { + isa = PBXGroup; + children = ( + DB3E6FE32806A5B800B035AE /* DiscoverySection.swift */, + DB3E6FE62806A7A200B035AE /* DiscoveryItem.swift */, + ); + path = Discovery; + sourceTree = ""; + }; + DB3E6FEA2806BD2500B035AE /* MastodonUI */ = { + isa = PBXGroup; + children = ( + DB3E6FE82806BD2200B035AE /* ThemeService.swift */, + ); + path = MastodonUI; + sourceTree = ""; + }; + DB3E6FED2806D7FC00B035AE /* News */ = { + isa = PBXGroup; + children = ( + DB3E6FEB2806D7F100B035AE /* DiscoveryNewsViewController.swift */, + DB3E6FEE2806D82600B035AE /* DiscoveryNewsViewModel.swift */, + DB3E6FF02806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift */, + DB3E6FF22806D97400B035AE /* DiscoveryNewsViewModel+State.swift */, + ); + path = News; + sourceTree = ""; + }; + DB3E6FF62807C40500B035AE /* ForYou */ = { + isa = PBXGroup; + children = ( + DB3E6FF42807C40300B035AE /* DiscoveryForYouViewController.swift */, + DB3E6FF72807C45300B035AE /* DiscoveryForYouViewModel.swift */, + DB3E6FF92807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift */, + ); + path = ForYou; + sourceTree = ""; + }; DB427DC925BAA00100D1B89D = { isa = PBXGroup; children = ( @@ -2322,7 +2391,6 @@ children = ( DBA465942696E387002B41DB /* AppPreference.swift */, DB647C5826F1EA2700F7F82C /* WizardPreference.swift */, - DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */, DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */, DB1D842F26566512000346B3 /* KeyboardPreference.swift */, DBCBCC0C2680B908000F5B51 /* HomeTimelinePreference.swift */, @@ -2676,6 +2744,7 @@ 2DAC9E36262FC20B0062E1A6 /* SuggestionAccount */, DB9D6C0825E4F5A60051B173 /* Profile */, DB9D6BEE25E4F5370051B173 /* Search */, + DBDFF1912805544800557A48 /* Discovery */, 5B90C455262599800002E742 /* Settings */, ); path = Scene; @@ -2685,6 +2754,7 @@ isa = PBXGroup; children = ( DB084B5125CBC56300F898ED /* CoreDataStack */, + DB3E6FEA2806BD2500B035AE /* MastodonUI */, DB6C8C0525F0921200AAA452 /* MastodonSDK */, 2DF123A625C3B0210020F248 /* ActiveLabel.swift */, 5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */, @@ -2702,7 +2772,6 @@ DBD376B1269302A4007FEC24 /* UITableViewCell.swift */, 0FAA101B25E10E760017CCDE /* UIFont.swift */, 2D206B9125F60EA700143C56 /* UIControl.swift */, - 2D32EAB925CB9B0500C9ED86 /* UIView.swift */, 5DA732CB2629CEF500A92342 /* UIView+Remove.swift */, 2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */, DB8AF55C25C138B7002E6C99 /* UIViewController.swift */, @@ -2983,9 +3052,6 @@ isa = PBXGroup; children = ( DBB5254F2611ED6D002F1F29 /* ProfileHeaderView.swift */, - DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */, - DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */, - DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */, DBF98149265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift */, ); path = View; @@ -2999,24 +3065,11 @@ path = Service; sourceTree = ""; }; - DBBC24BD26A5441A00398BB9 /* ThemeService */ = { - isa = PBXGroup; - children = ( - DBBC24C326A544B900398BB9 /* Theme.swift */, - DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */, - DBBC24BF26A5443100398BB9 /* SystemTheme.swift */, - DBBC24BB26A542F500398BB9 /* ThemeService.swift */, - DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */, - ); - path = ThemeService; - sourceTree = ""; - }; DBBC24D526A54BCB00398BB9 /* Helper */ = { isa = PBXGroup; children = ( DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */, DBBC50C0278ED49200AF0CC6 /* MastodonAuthenticationBox.swift */, - DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */, DBF3B7402733EB9400E21627 /* MastodonLocalCode.swift */, ); path = Helper; @@ -3062,6 +3115,32 @@ path = FetchedResultsController; sourceTree = ""; }; + DBDFF1912805544800557A48 /* Discovery */ = { + isa = PBXGroup; + children = ( + DB0A322F280EEA00001729D2 /* View */, + DBDFF19828055A0900557A48 /* Posts */, + DB3E6FDE2806A41200B035AE /* Hashtags */, + DB3E6FED2806D7FC00B035AE /* News */, + DB3E6FF62807C40500B035AE /* ForYou */, + DBDFF19928055A1400557A48 /* DiscoveryViewController.swift */, + DBDFF19B28055BD600557A48 /* DiscoveryViewModel.swift */, + ); + path = Discovery; + sourceTree = ""; + }; + DBDFF19828055A0900557A48 /* Posts */ = { + isa = PBXGroup; + children = ( + DBDFF18F2805543100557A48 /* DiscoveryPostsViewController.swift */, + DBDFF19D2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift */, + DBDFF1922805554900557A48 /* DiscoveryPostsViewModel.swift */, + DBDFF1942805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift */, + DBDFF196280556D900557A48 /* DiscoveryPostsViewModel+State.swift */, + ); + path = Posts; + sourceTree = ""; + }; DBE0821A25CD382900FD6BBD /* Register */ = { isa = PBXGroup; children = ( @@ -3233,7 +3312,6 @@ DB3D0FF225BAA61700EAA174 /* AlamofireImage */, 5D526FE125BE9AC400460CB9 /* MastodonSDK */, 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */, - DB0140BC25C40D7500F9F3CF /* CommonOSLog */, 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */, 2D939AC725EE14620076FA61 /* CropViewController */, DB9A487D2603456B008B817C /* UITextView+Placeholder */, @@ -3242,10 +3320,10 @@ DBAC649D267DFE43007FE9FD /* DiffableDataSources */, DBAC64A0267E6D02007FE9FD /* Fuzi */, DBF7A0FB26830C33004176A2 /* FPSIndicator */, - DB01E23226A98F0900C3965B /* MastodonMeta */, DB01E23426A98F0900C3965B /* MetaTextKit */, DB552D4E26BBD10C00E481F6 /* OrderedCollections */, DBA5A52E26F07ED800CACBAA /* PanModal */, + DB02EA0C280D184B00E751C5 /* CommonOSLog */, ); productName = Mastodon; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; @@ -3306,7 +3384,7 @@ ); name = AppShared; packageProductDependencies = ( - DB68050F2637D0F800430867 /* KeychainAccess */, + DB02EA0A280D180D00E751C5 /* KeychainAccess */, ); productName = AppShared; productReference = DB68047F2637CD4C00430867 /* AppShared.framework */; @@ -3441,6 +3519,8 @@ "eu-ES", "sv-FI", ku, + kab, + vi, ); mainGroup = DB427DC925BAA00100D1B89D; packageReferences = ( @@ -3789,6 +3869,7 @@ buildActionMask = 2147483647; files = ( DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */, + DBDFF19E2805703700557A48 /* DiscoveryPostsViewController+DataSourceProvider.swift in Sources */, DB6180EB26391C140018D199 /* MediaPreviewTransitionItem.swift in Sources */, DB63F74727990B0600455B82 /* DataSourceFacade+Hashtag.swift in Sources */, DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */, @@ -3815,6 +3896,7 @@ DB336F43278EB1690031E64B /* MediaView+Configuration.swift in Sources */, DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */, DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */, + DB3E6FE02806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift in Sources */, 2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */, DB447681260B3ED600B66B82 /* CustomEmojiPickerSection.swift in Sources */, DB0FCB7427956939006C02E2 /* DataSourceFacade+Status.swift in Sources */, @@ -3850,6 +3932,7 @@ DB697DD6278F4C29004EF2F7 /* DataSourceProvider.swift in Sources */, DB0FCB8E2796C0B7006C02E2 /* TrendCollectionViewCell.swift in Sources */, 0F1E2D0B2615C39400C38565 /* DoubleTitleLabelNavigationBarTitleView.swift in Sources */, + DBDFF1902805543100557A48 /* DiscoveryPostsViewController.swift in Sources */, DB697DD9278F4CED004EF2F7 /* HomeTimelineViewController+DataSourceProvider.swift in Sources */, DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */, DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */, @@ -3870,11 +3953,13 @@ DB297B1B2679FAE200704C90 /* PlaceholderImageCacheService.swift in Sources */, DB0FCB8C2796BF8D006C02E2 /* SearchViewModel+Diffable.swift in Sources */, 2D8FCA082637EABB00137F46 /* APIService+FollowRequest.swift in Sources */, + DBDFF1952805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift in Sources */, DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */, - DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */, + DBDFF19C28055BD600557A48 /* DiscoveryViewModel.swift in Sources */, DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */, DBB45B6227B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift in Sources */, DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */, + DB3E6FF32806D97400B035AE /* DiscoveryNewsViewModel+State.swift in Sources */, DB6746ED278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift in Sources */, DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */, DB63F76B279A5ED300455B82 /* NotificationTimelineViewModel+LoadOldestState.swift in Sources */, @@ -3897,12 +3982,12 @@ DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */, 2D34D9DB261494120081BFC0 /* APIService+Search.swift in Sources */, 5B90C48B26259C120002E742 /* APIService+CoreData+Subscriptions.swift in Sources */, - DBB5256E2612D5A1002F1F29 /* ProfileStatusDashboardView.swift in Sources */, DBA9443E265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift in Sources */, DB025B93278D6501002F581E /* Persistence.swift in Sources */, 2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */, DBFEEC9D279C12C1004F81DD /* ProfileFieldEditCollectionViewCell.swift in Sources */, DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */, + DB3E6FEC2806D7F100B035AE /* DiscoveryNewsViewController.swift in Sources */, DBA088DF26958164003EB4B2 /* UserFetchedResultsController.swift in Sources */, DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */, 0F202213261351F5000C64BF /* APIService+HashtagTimeline.swift in Sources */, @@ -3944,7 +4029,6 @@ 2D4AD8A826316D3500613EFC /* SelectedAccountItem.swift in Sources */, DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */, DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */, - DBBC24C126A5443100398BB9 /* SystemTheme.swift in Sources */, DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */, 2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */, 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */, @@ -3971,6 +4055,7 @@ DB63F76227996B6600455B82 /* SearchHistoryViewController+DataSourceProvider.swift in Sources */, DB73BF4927140BA300781945 /* UICollectionViewDiffableDataSource.swift in Sources */, DBA5E7AB263BD3F5004598BB /* TimelineTableViewCellContextMenuConfiguration.swift in Sources */, + DB3E6FE92806BD2200B035AE /* ThemeService.swift in Sources */, DB73B490261F030A002E9E9F /* SafariActivity.swift in Sources */, DB63F7492799126300455B82 /* FollowerListViewController+DataSourceProvider.swift in Sources */, DB6D1B44263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift in Sources */, @@ -3988,11 +4073,11 @@ DB697DD4278F4927004EF2F7 /* StatusTableViewCellDelegate.swift in Sources */, DB0FCB902796C5EB006C02E2 /* APIService+Trend.swift in Sources */, DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */, + DB3E6FF52807C40300B035AE /* DiscoveryForYouViewController.swift in Sources */, DB9D7C21269824B80054B3DF /* APIService+Filter.swift in Sources */, 2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */, DB0FCB842796B2A2006C02E2 /* FavoriteViewController+DataSourceProvider.swift in Sources */, DBCC3B36261440BA0045B23D /* UINavigationController.swift in Sources */, - DBB525852612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift in Sources */, DB0FCB68279507EF006C02E2 /* DataSourceFacade+Meta.swift in Sources */, DB63F75C279956D000455B82 /* Persistence+Tag.swift in Sources */, 2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */, @@ -4004,11 +4089,14 @@ DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */, DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */, 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, + DBDFF1932805554900557A48 /* DiscoveryPostsViewModel.swift in Sources */, 2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */, DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */, + DB3E6FE72806A7A200B035AE /* DiscoveryItem.swift in Sources */, DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */, DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */, DB647C5926F1EA2700F7F82C /* WizardPreference.swift in Sources */, + DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */, 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */, 5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */, 5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */, @@ -4028,10 +4116,10 @@ 5B90C462262599800002E742 /* SettingsSectionHeader.swift in Sources */, DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */, 5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */, + DB3E6FEF2806D82600B035AE /* DiscoveryNewsViewModel.swift in Sources */, DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */, DB84811727883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift in Sources */, 2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */, - DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */, DB98EB6727B216560082E365 /* ReportResultViewModel+Diffable.swift in Sources */, DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */, DB025B95278D6530002F581E /* Persistence+MastodonUser.swift in Sources */, @@ -4061,6 +4149,7 @@ 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */, DB6B74F6272FBCDB00C70B6E /* FollowerListViewModel+State.swift in Sources */, DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */, + DBDFF197280556D900557A48 /* DiscoveryPostsViewModel+State.swift in Sources */, DB336F2C278D6FC30031E64B /* Persistence+Status.swift in Sources */, DB336F2A278D6F2B0031E64B /* MastodonField.swift in Sources */, DB0FCB7A279576A2006C02E2 /* DataSourceFacade+Thread.swift in Sources */, @@ -4088,7 +4177,7 @@ DB4AA6B327BA34B6009EC082 /* CellFrameCacheContainer.swift in Sources */, DB0FCB942797E2B0006C02E2 /* SearchResultViewModel+Diffable.swift in Sources */, DB63F752279944AA00455B82 /* SearchHistorySectionHeaderCollectionReusableView.swift in Sources */, - DBBC24C426A544B900398BB9 /* Theme.swift in Sources */, + DB3E6FDD2806A40F00B035AE /* DiscoveryHashtagsViewController.swift in Sources */, DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */, DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */, DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */, @@ -4096,6 +4185,7 @@ DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */, DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */, DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */, + DB3E6FFA2807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift in Sources */, DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */, DBB45B6027B50A4F002DC5A7 /* RecommendAccountItem.swift in Sources */, 0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */, @@ -4107,7 +4197,6 @@ DBBF1DC5265251C300E5B703 /* AutoCompleteViewModel+Diffable.swift in Sources */, DB603111279EB38500A935FE /* DataSourceFacade+Mute.swift in Sources */, DB68A04A25E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift in Sources */, - DBBC24BC26A542F500398BB9 /* ThemeService.swift in Sources */, DB336F38278D7AAF0031E64B /* Poll+Property.swift in Sources */, 0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */, DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */, @@ -4120,7 +4209,6 @@ DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */, DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */, DBF156E22702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m in Sources */, - 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */, 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */, DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */, DB697DE1278F5296004EF2F7 /* DataSourceFacade+Model.swift in Sources */, @@ -4128,7 +4216,6 @@ DB4F097526A037F500D62E92 /* SearchHistoryViewModel.swift in Sources */, DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */, DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */, - DB71C7CB271D5A0300BE3819 /* LineChartView.swift in Sources */, DB98EB5627B0FF1B0082E365 /* ReportViewControllerAppearance.swift in Sources */, DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */, 2D206B8625F5FB0900143C56 /* Double.swift in Sources */, @@ -4150,6 +4237,7 @@ DBD376AC2692ECDB007FEC24 /* ThemePreference.swift in Sources */, DB4F097D26A03A5B00D62E92 /* SearchHistoryItem.swift in Sources */, DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */, + DB3E6FE22806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift in Sources */, DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */, DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */, DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */, @@ -4196,7 +4284,6 @@ DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */, DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */, 2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */, - DB71C7CD271D7F4300BE3819 /* CurveAlgorithm.swift in Sources */, DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */, DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */, DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */, @@ -4213,31 +4300,33 @@ 0F2021FB2613262F000C64BF /* HashtagTimelineViewController.swift in Sources */, DB697DDD278F521D004EF2F7 /* DataSourceFacade.swift in Sources */, DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */, + DB3E6FE42806A5B800B035AE /* DiscoverySection.swift in Sources */, DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */, DB697DDB278F4DE3004EF2F7 /* DataSourceProvider+StatusTableViewCellDelegate.swift in Sources */, DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */, 2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */, DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */, DBB45B5627B39FC9002DC5A7 /* MediaPreviewVideoViewController.swift in Sources */, - DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */, DB0FCB8027968F70006C02E2 /* MastodonStatusThreadViewModel.swift in Sources */, DB0FCB6E27950E6B006C02E2 /* MastodonMention.swift in Sources */, DB67D08627312E67006A36CF /* WizardViewController.swift in Sources */, DB6746EB278ED8B0008A6B94 /* PollOptionView+Configuration.swift in Sources */, DB9A489026035963008B817C /* APIService+Media.swift in Sources */, DBFEEC99279BDCDE004F81DD /* ProfileAboutViewModel.swift in Sources */, - DBBC24CF26A547AE00398BB9 /* ThemeService+Appearance.swift in Sources */, 2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */, DB4F097B26A039FF00D62E92 /* SearchHistorySection.swift in Sources */, DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */, DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */, 2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */, + DB3E6FF12806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift in Sources */, + DB3E6FF82807C45300B035AE /* DiscoveryForYouViewModel.swift in Sources */, DB49A62525FF334C00B98345 /* EmojiService+CustomEmojiViewModel+LoadState.swift in Sources */, DB4924E226312AB200E9DB22 /* NotificationService.swift in Sources */, DB6D9F6F2635807F008423CD /* Setting.swift in Sources */, DB6F5E38264E994A009108F4 /* AutoCompleteTopChevronView.swift in Sources */, DB6746F0278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift in Sources */, DBB525412611ED54002F1F29 /* ProfileHeaderViewController.swift in Sources */, + DBDFF19A28055A1400557A48 /* DiscoveryViewController.swift in Sources */, DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */, DB63F756279949BD00455B82 /* Persistence+SearchHistory.swift in Sources */, 2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */, @@ -4247,7 +4336,6 @@ DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */, DB63F74B279914A000455B82 /* FollowingListViewController+DataSourceProvider.swift in Sources */, DBB525362611ECEB002F1F29 /* UserTimelineViewController.swift in Sources */, - DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */, DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */, DB98EB4927B0F0CD0082E365 /* ReportStatusTableViewCell.swift in Sources */, DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */, @@ -4338,17 +4426,12 @@ DBFEF07526A69192006D7ED1 /* APIService+Media.swift in Sources */, DBFEF06F26A690C4006D7ED1 /* APIService+APIError.swift in Sources */, DBFEF05C26A57715006D7ED1 /* StatusEditorView.swift in Sources */, - DBBC24C726A5456400398BB9 /* SystemTheme.swift in Sources */, - DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */, - DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */, DBFEF07C26A6BD0A006D7ED1 /* APIService+Status+Publish.swift in Sources */, DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */, DB6746E8278ED639008A6B94 /* MastodonAuthenticationBox.swift in Sources */, DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */, DBFEF05B26A57715006D7ED1 /* ComposeViewModel.swift in Sources */, - DBBC24C626A5456000398BB9 /* Theme.swift in Sources */, DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */, - DBFEF06926A67E45006D7ED1 /* AppearancePreference.swift in Sources */, DBC6461526A170AB00B0E31B /* ShareViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4442,6 +4525,8 @@ DB126A4C278C063F005726EE /* eu-ES */, DB126A56278C088D005726EE /* sv-FI */, DBEB19E927E4F37B00B0E80E /* ku */, + DBF81C7427F68F5A00004A56 /* kab */, + DBF81C7727F6913300004A56 /* vi */, ); name = Intents.intentdefinition; sourceTree = ""; @@ -4465,6 +4550,8 @@ DB126A4F278C063F005726EE /* eu-ES */, DB126A59278C088D005726EE /* sv-FI */, DBEB19EA27E4F37B00B0E80E /* ku */, + DBF81C7527F68F5A00004A56 /* kab */, + DBF81C7827F6913300004A56 /* vi */, ); name = InfoPlist.strings; sourceTree = ""; @@ -4504,6 +4591,8 @@ DB126A50278C063F005726EE /* eu-ES */, DB126A5A278C088D005726EE /* sv-FI */, DBEB19EB27E4F37B00B0E80E /* ku */, + DBF81C7627F68F5A00004A56 /* kab */, + DBF81C7927F6913300004A56 /* vi */, ); name = Intents.stringsdict; sourceTree = ""; @@ -4648,7 +4737,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4677,7 +4766,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4785,11 +4874,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 109; + DYLIB_CURRENT_VERSION = 113; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4816,11 +4905,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 109; + DYLIB_CURRENT_VERSION = 113; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4845,7 +4934,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4870,7 +4959,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4895,7 +4984,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4920,7 +5009,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5006,7 +5095,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5073,11 +5162,11 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5Z4GVSS33P; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 109; + DYLIB_CURRENT_VERSION = 113; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AppShared/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5102,7 +5191,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5126,7 +5215,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5151,7 +5240,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5176,7 +5265,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5200,7 +5289,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 109; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5341,7 +5430,7 @@ repositoryURL = "https://github.com/TwidereProject/MetaTextKit.git"; requirement = { kind = exactVersion; - version = 2.2.1; + version = 2.2.2; }; }; DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */ = { @@ -5459,21 +5548,21 @@ package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; productName = CommonOSLog; }; - DB0140BC25C40D7500F9F3CF /* CommonOSLog */ = { - isa = XCSwiftPackageProductDependency; - package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; - productName = CommonOSLog; - }; - DB01E23226A98F0900C3965B /* MastodonMeta */ = { - isa = XCSwiftPackageProductDependency; - package = DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */; - productName = MastodonMeta; - }; DB01E23426A98F0900C3965B /* MetaTextKit */ = { isa = XCSwiftPackageProductDependency; package = DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */; productName = MetaTextKit; }; + DB02EA0A280D180D00E751C5 /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; + DB02EA0C280D184B00E751C5 /* CommonOSLog */ = { + isa = XCSwiftPackageProductDependency; + package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; + productName = CommonOSLog; + }; DB0C946426A6FD4D0088FB11 /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; @@ -5493,11 +5582,6 @@ package = DB552D4D26BBD10C00E481F6 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = OrderedCollections; }; - DB68050F2637D0F800430867 /* KeychainAccess */ = { - isa = XCSwiftPackageProductDependency; - package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */; - productName = KeychainAccess; - }; DB6D9F41263527CE008423CD /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index a90323e9..7d790003 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,7 +9,7 @@ isShown orderHint - 3 + 4 CoreDataStack.xcscheme_^#shared#^_ @@ -19,7 +19,7 @@ Mastodon - RTL.xcscheme_^#shared#^_ orderHint - 18 + 19 Mastodon - Release.xcscheme_^#shared#^_ @@ -31,6 +31,11 @@ orderHint 2 + Mastodon - ar.xcscheme + + orderHint + 3 + Mastodon - ar.xcscheme_^#shared#^_ orderHint @@ -104,7 +109,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 49 + 30 MastodonIntents.xcscheme_^#shared#^_ @@ -119,12 +124,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 51 + 31 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 50 + 23 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 11d45388..53f6a3a4 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/Alamofire/Alamofire.git", "state": { "branch": null, - "revision": "f82c23a8a7ef8dc1a49a8bfc6a96883e79121864", - "version": "5.5.0" + "revision": "354dda32d89fc8cd4f5c46487f64957d355f53d8", + "version": "5.6.1" } }, { @@ -55,6 +55,15 @@ "version": "1.2.0" } }, + { + "package": "FaviconFinder", + "repositoryURL": "https://github.com/will-lumley/FaviconFinder.git", + "state": { + "branch": null, + "revision": "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", + "version": "3.3.0" + } + }, { "package": "FLAnimatedImage", "repositoryURL": "https://github.com/Flipboard/FLAnimatedImage.git", @@ -96,8 +105,8 @@ "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git", "state": { "branch": null, - "revision": "3ea336d3de7938dc112084c596a646e697b0feee", - "version": "2.2.1" + "revision": "8074400b3819ef0395550082e6e8e960ef22e1f3", + "version": "2.2.2" } }, { @@ -105,8 +114,8 @@ "repositoryURL": "https://github.com/kean/Nuke.git", "state": { "branch": null, - "revision": "0db18dd34998cca18e9a28bcee136f84518007a0", - "version": "10.4.1" + "revision": "78fa963b8491fc520791d8c2a509f1b8593d8aae", + "version": "10.7.1" } }, { @@ -141,8 +150,8 @@ "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", "state": { "branch": null, - "revision": "2c53f531f1bedd253f55d85105409c28ed4a922c", - "version": "5.12.3" + "revision": "2e63d0061da449ad0ed130768d05dceb1496de44", + "version": "5.12.5" } }, { @@ -172,13 +181,22 @@ "version": "1.0.0" } }, + { + "package": "SwiftSoup", + "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", + "state": { + "branch": null, + "revision": "41e7c263fb8c277e980ebcb9b0b5f6031d3d4886", + "version": "2.4.2" + } + }, { "package": "Introspect", "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", "state": { "branch": null, - "revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2", - "version": "0.1.3" + "revision": "f2616860a41f9d9932da412a8978fec79c06fe24", + "version": "0.1.4" } }, { diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index c8ce4acb..1f803001 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -144,7 +144,7 @@ extension SceneCoordinator { case popover(sourceView: UIView) case panModal case custom(transitioningDelegate: UIViewControllerTransitioningDelegate) - case customPush + case customPush(animated: Bool) case safariPresent(animated: Bool, completion: (() -> Void)? = nil) case alertController(animated: Bool, completion: (() -> Void)? = nil) case activityViewControllerPresent(animated: Bool, completion: (() -> Void)? = nil) @@ -339,10 +339,10 @@ extension SceneCoordinator { viewController.transitioningDelegate = transitioningDelegate (splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil) - case .customPush: + case .customPush(let animated): // set delegate in view controller assert(sender?.navigationController?.delegate != nil) - sender?.navigationController?.pushViewController(viewController, animated: true) + sender?.navigationController?.pushViewController(viewController, animated: animated) case .safariPresent(let animated, let completion): if UserDefaults.shared.preferredUsingDefaultBrowser, case let .safari(url) = scene { diff --git a/Mastodon/Diffiable/Discovery/DiscoveryItem.swift b/Mastodon/Diffiable/Discovery/DiscoveryItem.swift new file mode 100644 index 00000000..024c4a2d --- /dev/null +++ b/Mastodon/Diffiable/Discovery/DiscoveryItem.swift @@ -0,0 +1,17 @@ +// +// DiscoveryItem.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation +import MastodonSDK +import CoreDataStack + +enum DiscoveryItem: Hashable { + case hashtag(Mastodon.Entity.Tag) + case link(Mastodon.Entity.Link) + case user(ManagedObjectRecord) + case bottomLoader +} diff --git a/Mastodon/Diffiable/Discovery/DiscoverySection.swift b/Mastodon/Diffiable/Discovery/DiscoverySection.swift new file mode 100644 index 00000000..cab2eb82 --- /dev/null +++ b/Mastodon/Diffiable/Discovery/DiscoverySection.swift @@ -0,0 +1,74 @@ +// +// DiscoverySection.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import MastodonUI + +enum DiscoverySection: CaseIterable { + // case posts + case hashtags + case news + case forYou +} + +extension DiscoverySection { + + static let logger = Logger(subsystem: "DiscoverySection", category: "logic") + + class Configuration { + weak var profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? + + public init(profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? = nil) { + self.profileCardTableViewCellDelegate = profileCardTableViewCellDelegate + } + } + + static func diffableDataSource( + tableView: UITableView, + context: AppContext, + configuration: Configuration + ) -> UITableViewDiffableDataSource { + tableView.register(TrendTableViewCell.self, forCellReuseIdentifier: String(describing: TrendTableViewCell.self)) + tableView.register(NewsTableViewCell.self, forCellReuseIdentifier: String(describing: NewsTableViewCell.self)) + tableView.register(ProfileCardTableViewCell.self, forCellReuseIdentifier: String(describing: ProfileCardTableViewCell.self)) + tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) + + return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in + switch item { + case .hashtag(let tag): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TrendTableViewCell.self), for: indexPath) as! TrendTableViewCell + cell.trendView.configure(tag: tag) + return cell + case .link(let link): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NewsTableViewCell.self), for: indexPath) as! NewsTableViewCell + cell.newsView.configure(link: link) + return cell + case .user(let record): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ProfileCardTableViewCell.self), for: indexPath) as! ProfileCardTableViewCell + context.managedObjectContext.performAndWait { + guard let user = record.object(in: context.managedObjectContext) else { return } + cell.configure( + tableView: tableView, + user: user, + profileCardTableViewCellDelegate: configuration.profileCardTableViewCellDelegate + ) + } + context.authenticationService.activeMastodonAuthentication + .map { $0?.user } + .assign(to: \.me, on: cell.profileCardView.viewModel.relationshipViewModel) + .store(in: &cell.disposeBag) + return cell + case .bottomLoader: + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell + cell.activityIndicatorView.startAnimating() + return cell + } + } + } + +} diff --git a/Mastodon/Diffiable/Search/SearchHistorySection.swift b/Mastodon/Diffiable/Search/SearchHistorySection.swift index dba1dc18..557b49f2 100644 --- a/Mastodon/Diffiable/Search/SearchHistorySection.swift +++ b/Mastodon/Diffiable/Search/SearchHistorySection.swift @@ -69,10 +69,10 @@ extension SearchHistorySection { let trendHeaderRegister = UICollectionView.SupplementaryRegistration(elementKind: UICollectionView.elementKindSectionHeader) { [weak dataSource] supplementaryView, elementKind, indexPath in supplementaryView.delegate = configuration.searchHistorySectionHeaderCollectionReusableViewDelegate - guard let dataSource = dataSource else { return } - let sections = dataSource.snapshot().sectionIdentifiers - guard indexPath.section < sections.count else { return } - let section = sections[indexPath.section] + guard let _ = dataSource else { return } + // let sections = dataSource.snapshot().sectionIdentifiers + // guard indexPath.section < sections.count else { return } + // let section = sections[indexPath.section] } dataSource.supplementaryViewProvider = { (collectionView: UICollectionView, elementKind: String, indexPath: IndexPath) in diff --git a/Mastodon/Diffiable/Search/SearchSection.swift b/Mastodon/Diffiable/Search/SearchSection.swift index 21f1d479..4f550abf 100644 --- a/Mastodon/Diffiable/Search/SearchSection.swift +++ b/Mastodon/Diffiable/Search/SearchSection.swift @@ -21,26 +21,7 @@ extension SearchSection { ) -> UICollectionViewDiffableDataSource { let trendCellRegister = UICollectionView.CellRegistration { cell, indexPath, item in - let primaryLabelText = "#" + item.name - let secondaryLabelText = L10n.Scene.Search.Recommend.HashTag.peopleTalking(item.talkingPeopleCount ?? 0) - cell.primaryLabel.text = primaryLabelText - cell.secondaryLabel.text = secondaryLabelText - - cell.lineChartView.data = (item.history ?? []) - .sorted(by: { $0.day < $1.day }) // latest last - .map { entry in - guard let point = Int(entry.accounts) else { - return .zero - } - return CGFloat(point) - } - - cell.isAccessibilityElement = true - cell.accessibilityLabel = [ - primaryLabelText, - secondaryLabelText - ].joined(separator: ", ") } let dataSource = UICollectionViewDiffableDataSource( diff --git a/Mastodon/Extension/CoreDataStack/MastodonUser.swift b/Mastodon/Extension/CoreDataStack/MastodonUser.swift index 02a98368..bc5f159d 100644 --- a/Mastodon/Extension/CoreDataStack/MastodonUser.swift +++ b/Mastodon/Extension/CoreDataStack/MastodonUser.swift @@ -9,53 +9,6 @@ import Foundation import CoreDataStack import MastodonSDK -extension MastodonUser { - - public var displayNameWithFallback: String { - return !displayName.isEmpty ? displayName : username - } - - public var acctWithDomain: String { - if !acct.contains("@") { - // Safe concat due to username cannot contains "@" - return username + "@" + domain - } else { - return acct - } - } - - public var domainFromAcct: String { - if !acct.contains("@") { - return domain - } else { - let domain = acct.split(separator: "@").last - return String(domain!) - } - } - -} - -extension MastodonUser { - - public func headerImageURL() -> URL? { - return URL(string: header) - } - - public func headerImageURLWithFallback(domain: String) -> URL { - return URL(string: header) ?? URL(string: "https://\(domain)/headers/original/missing.png")! - } - - public func avatarImageURL() -> URL? { - let string = UserDefaults.shared.preferredStaticAvatar ? avatarStatic ?? avatar : avatar - return URL(string: string) - } - - public func avatarImageURLWithFallback(domain: String) -> URL { - return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")! - } - -} - extension MastodonUser { public var profileURL: URL { diff --git a/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Tag.swift b/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Tag.swift index 2d0be696..e217d3a8 100644 --- a/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Tag.swift +++ b/Mastodon/Extension/MastodonSDK/Mastodon+Entity+Tag.swift @@ -17,14 +17,3 @@ extension Mastodon.Entity.Tag: Hashable { } } -extension Mastodon.Entity.Tag { - - /// the sum of recent 2 days - public var talkingPeopleCount: Int? { - return history? - .prefix(2) - .compactMap { Int($0.accounts) } - .reduce(0, +) - } - -} diff --git a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift b/Mastodon/Extension/MastodonUI/ThemeService.swift similarity index 97% rename from Mastodon/Service/ThemeService/ThemeService+Appearance.swift rename to Mastodon/Extension/MastodonUI/ThemeService.swift index 896ed888..5fe213d0 100644 --- a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift +++ b/Mastodon/Extension/MastodonUI/ThemeService.swift @@ -1,11 +1,13 @@ // -// ThemeService+Appearance.swift +// ThemeService.swift // Mastodon // -// Created by MainasuK Cirno on 2021-7-19. +// Created by MainasuK on 2022-4-13. // import UIKit +import MastodonCommon +import MastodonUI extension ThemeService { func set(themeName: ThemeName) { diff --git a/Mastodon/Extension/UIView.swift b/Mastodon/Extension/UIView.swift deleted file mode 100644 index d4814b7e..00000000 --- a/Mastodon/Extension/UIView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// UIView.swift -// Mastodon -// -// Created by sxiaojian on 2021/2/4. -// - -import UIKit - -// MARK: - Convenience view creation method -extension UIView { - - static let separatorColor: UIColor = { - UIColor(dynamicProvider: { collection in - switch collection.userInterfaceStyle { - case .dark: - return ThemeService.shared.currentTheme.value.separator - default: - return .separator - } - }) - }() - - static var separatorLine: UIView { - let line = UIView() - line.backgroundColor = UIView.separatorColor - return line - } - - static func separatorLineHeight(of view: UIView) -> CGFloat { - return 1.0 / view.traitCollection.displayScale - } - -} - -// MARK: - Convenience view appearance modification method -extension UIView { - @discardableResult - func applyCornerRadius(radius: CGFloat) -> Self { - layer.masksToBounds = true - layer.cornerRadius = radius - layer.cornerCurve = .continuous - return self - } - - @discardableResult - func applyShadow( - color: UIColor, - alpha: Float, - x: CGFloat, - y: CGFloat, - blur: CGFloat, - spread: CGFloat = 0) -> Self - { - layer.masksToBounds = false - layer.shadowColor = color.cgColor - layer.shadowOpacity = alpha - layer.shadowOffset = CGSize(width: x, height: y) - layer.shadowRadius = blur / 2.0 - if spread == 0 { - layer.shadowPath = nil - } else { - let dx = -spread - let rect = bounds.insetBy(dx: dx, dy: dx) - layer.shadowPath = UIBezierPath(rect: rect).cgPath - } - return self - } -} - diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index 311ee390..0bd11878 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -30,7 +30,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.3.1 CFBundleURLTypes @@ -43,7 +43,7 @@ CFBundleVersion - 109 + 113 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/Mastodon/Persistence/Extension/MastodonEmoji.swift b/Mastodon/Persistence/Extension/MastodonEmoji.swift index e9274a24..2ea23c67 100644 --- a/Mastodon/Persistence/Extension/MastodonEmoji.swift +++ b/Mastodon/Persistence/Extension/MastodonEmoji.swift @@ -22,13 +22,3 @@ extension MastodonEmoji { ) } } - -extension Collection where Element == MastodonEmoji { - public var asDictionary: MastodonContent.Emojis { - var dictionary: MastodonContent.Emojis = [:] - for emoji in self { - dictionary[emoji.code] = emoji.url - } - return dictionary - } -} diff --git a/Mastodon/Preference/ThemePreference.swift b/Mastodon/Preference/ThemePreference.swift index 62404779..5465cb22 100644 --- a/Mastodon/Preference/ThemePreference.swift +++ b/Mastodon/Preference/ThemePreference.swift @@ -5,17 +5,3 @@ // Created by MainasuK Cirno on 2021-7-5. // -import UIKit -import MastodonExtension - -extension UserDefaults { - - @objc dynamic var currentThemeNameRawValue: String { - get { - register(defaults: [#function: ThemeName.mastodon.rawValue]) - return string(forKey: #function) ?? ThemeName.mastodon.rawValue - } - set { self[#function] = newValue } - } - -} diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift index bf54f70a..7e376ed0 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift @@ -38,11 +38,15 @@ extension DataSourceFacade { meta: Meta ) async { switch meta { + // note: + // some server mark the normal url as "u-url" class. highlighted content is a URL case .url(_, _, let url, _), .mention(_, let url, _) where url.lowercased().hasPrefix("http"): - // note: - // some server mark the normal url as "u-url" class. highlighted content is a URL - guard let url = URL(string: url) else { return } + // fix non-ascii character URL link can not open issue + guard let url = URL(string: url) ?? URL(string: url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? url) else { + assertionFailure() + return + } if let domain = provider.context.authenticationService.activeMastodonAuthenticationBox.value?.domain, url.host == domain, url.pathComponents.count >= 4, url.pathComponents[0] == "/", diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift index 36eaab62..66259a09 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift @@ -122,12 +122,12 @@ extension DataSourceFacade { let barButtonItem: UIBarButtonItem? } - @MainActor - static func createProfileActionMenu( - dependency: NeedsDependency, - user: ManagedObjectRecord - ) -> UIMenu { - var children: [UIMenuElement] = [] +// @MainActor +// static func createProfileActionMenu( +// dependency: NeedsDependency, +// user: ManagedObjectRecord +// ) -> UIMenu { +// var children: [UIMenuElement] = [] // let name = mastodonUser.displayNameWithFallback // // if let shareUser = shareUser { @@ -339,9 +339,9 @@ extension DataSourceFacade { // } // children.append(deleteAction) // } - - return UIMenu(title: "", options: [], children: children) - } +// +// return UIMenu(title: "", options: [], children: children) +// } static func createActivityViewController( dependency: NeedsDependency, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index eab85e95..36ceb6dd 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -286,24 +286,8 @@ extension DataSourceFacade { try await dependency.context.managedObjectContext.perform { guard let _status = status.object(in: dependency.context.managedObjectContext) else { return } let status = _status.reblog ?? _status - - let allToggled = status.isContentSensitiveToggled && status.isMediaSensitiveToggled - - status.update(isContentSensitiveToggled: !allToggled) - status.update(isMediaSensitiveToggled: !allToggled) + status.update(isSensitiveToggled: !status.isSensitiveToggled) } } -// static func responseToToggleMediaSensitiveAction( -// dependency: NeedsDependency, -// status: ManagedObjectRecord -// ) async throws { -// try await dependency.context.managedObjectContext.perform { -// guard let _status = status.object(in: dependency.context.managedObjectContext) else { return } -// let status = _status.reblog ?? _status -// -// status.update(isMediaSensitiveToggled: !status.isMediaSensitiveToggled) -// } -// } - } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift index 0924028f..f6e58673 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift @@ -135,7 +135,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Med let status = _status.reblog ?? _status return NotificationMediaTransitionContext( status: .init(objectID: status.objectID), - needsToggleMediaSensitive: status.isMediaSensitiveToggled ? !status.sensitive : status.sensitive + needsToggleMediaSensitive: status.isSensitiveToggled ? !status.sensitive : status.sensitive ) } @@ -187,7 +187,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Med let status = _status.reblog ?? _status return NotificationMediaTransitionContext( status: .init(objectID: status.objectID), - needsToggleMediaSensitive: status.isMediaSensitiveToggled ? !status.sensitive : status.sensitive + needsToggleMediaSensitive: status.isMediaSensitive ? !status.isSensitiveToggled : false ) } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift index d14b5c34..9e5838e7 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift @@ -143,12 +143,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & MediaPrev return } - let managedObjectContext = self.context.managedObjectContext - let needsToggleMediaSensitive: Bool = try await managedObjectContext.perform { - guard let _status = status.object(in: managedObjectContext) else { return false } - let status = _status.reblog ?? _status - return status.isMediaSensitiveToggled ? !status.sensitive : status.sensitive - } + let needsToggleMediaSensitive = await !statusView.viewModel.isMediaReveal guard !needsToggleMediaSensitive else { try await DataSourceFacade.responseToToggleSensitiveAction( diff --git a/Mastodon/Resources/kab.lproj/InfoPlist.strings b/Mastodon/Resources/kab.lproj/InfoPlist.strings new file mode 100644 index 00000000..71086557 --- /dev/null +++ b/Mastodon/Resources/kab.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Used to take photo for post status"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; +"NewPostShortcutItemTitle" = "New Post"; +"SearchShortcutItemTitle" = "Search"; \ No newline at end of file diff --git a/Mastodon/Resources/vi.lproj/InfoPlist.strings b/Mastodon/Resources/vi.lproj/InfoPlist.strings new file mode 100644 index 00000000..71086557 --- /dev/null +++ b/Mastodon/Resources/vi.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +"NSCameraUsageDescription" = "Used to take photo for post status"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; +"NewPostShortcutItemTitle" = "New Post"; +"SearchShortcutItemTitle" = "Search"; \ No newline at end of file diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift index 7ea43f15..c1869669 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusPollOptionCollectionViewCell.swift @@ -10,6 +10,7 @@ import UIKit import Combine import MastodonAsset import MastodonLocalization +import MastodonUI protocol ComposeStatusPollOptionCollectionViewCellDelegate: AnyObject { func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textFieldDidBeginEditing textField: UITextField) diff --git a/Mastodon/Scene/Discovery/DiscoveryViewController.swift b/Mastodon/Scene/Discovery/DiscoveryViewController.swift new file mode 100644 index 00000000..8e3aab64 --- /dev/null +++ b/Mastodon/Scene/Discovery/DiscoveryViewController.swift @@ -0,0 +1,128 @@ +// +// DiscoveryViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import os.log +import UIKit +import Combine +import Tabman +import MastodonAsset +import MastodonUI + +public class DiscoveryViewController: TabmanViewController, NeedsDependency { + + public static let containerViewMarginForRegularHorizontalSizeClass: CGFloat = 64 + public static let containerViewMarginForCompactHorizontalSizeClass: CGFloat = 16 + + var disposeBag = Set() + + let logger = Logger(subsystem: "DiscoveryViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + private(set) lazy var viewModel = DiscoveryViewModel( + context: context, + coordinator: coordinator + ) + + private(set) lazy var buttonBar: TMBar.ButtonBar = { + let buttonBar = TMBar.ButtonBar() + buttonBar.backgroundView.style = .custom(view: buttonBarBackgroundView) + buttonBar.layout.interButtonSpacing = 0 + buttonBar.layout.contentInset = .zero + buttonBar.indicator.backgroundColor = Asset.Colors.Label.primary.color + buttonBar.indicator.weight = .custom(value: 2) + return buttonBar + }() + + let buttonBarBackgroundView: UIView = { + let view = UIView() + let barBottomLine = UIView.separatorLine + barBottomLine.backgroundColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.5) + barBottomLine.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(barBottomLine) + NSLayoutConstraint.activate([ + barBottomLine.leadingAnchor.constraint(equalTo: view.leadingAnchor), + barBottomLine.trailingAnchor.constraint(equalTo: view.trailingAnchor), + barBottomLine.bottomAnchor.constraint(equalTo: view.bottomAnchor), + barBottomLine.heightAnchor.constraint(equalToConstant: 2).priority(.required - 1), + ]) + return view + }() + + func customizeButtonBarAppearance() { + // The implmention use CATextlayer. Adapt for Dark Mode without dynamic colors + // Needs trigger update when `userInterfaceStyle` chagnes + let userInterfaceStyle = traitCollection.userInterfaceStyle + buttonBar.buttons.customize { button in + switch userInterfaceStyle { + case .dark: + // Asset.Colors.Label.primary.color + button.selectedTintColor = UIColor(red: 238.0/255.0, green: 238.0/255.0, blue: 238.0/255.0, alpha: 1.0) + // Asset.Colors.Label.secondary.color + button.tintColor = UIColor(red: 151.0/255.0, green: 157.0/255.0, blue: 173.0/255.0, alpha: 1.0) + default: + // Asset.Colors.Label.primary.color + button.selectedTintColor = UIColor(red: 40.0/255.0, green: 44.0/255.0, blue: 55.0/255.0, alpha: 1.0) + // Asset.Colors.Label.secondary.color + button.tintColor = UIColor(red: 60.0/255.0, green: 60.0/255.0, blue: 67.0/255.0, alpha: 0.6) + } + + button.backgroundColor = .clear + button.contentInset = UIEdgeInsets(top: 12, left: 26, bottom: 12, right: 26) + } + } + +} + +extension DiscoveryViewController { + + public override func viewDidLoad() { + super.viewDidLoad() + + setupAppearance(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupAppearance(theme: theme) + } + .store(in: &disposeBag) + + dataSource = viewModel + addBar( + buttonBar, + dataSource: viewModel, + at: .top + ) + customizeButtonBarAppearance() + + viewModel.$viewControllers + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.reloadData() + } + .store(in: &disposeBag) + } + + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + customizeButtonBarAppearance() + } + +} + +extension DiscoveryViewController { + + private func setupAppearance(theme: Theme) { + view.backgroundColor = theme.secondarySystemBackgroundColor + buttonBarBackgroundView.backgroundColor = theme.systemBackgroundColor + } + +} diff --git a/Mastodon/Scene/Discovery/DiscoveryViewModel.swift b/Mastodon/Scene/Discovery/DiscoveryViewModel.swift new file mode 100644 index 00000000..ae31797e --- /dev/null +++ b/Mastodon/Scene/Discovery/DiscoveryViewModel.swift @@ -0,0 +1,156 @@ +// +// DiscoveryViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import UIKit +import Combine +import Tabman +import Pageboy + +final class DiscoveryViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let discoveryPostsViewController: DiscoveryPostsViewController + let discoveryHashtagsViewController: DiscoveryHashtagsViewController + let discoveryNewsViewController: DiscoveryNewsViewController + let discoveryForYouViewController: DiscoveryForYouViewController + + @Published var viewControllers: [ScrollViewContainer & PageViewController] + + init(context: AppContext, coordinator: SceneCoordinator) { + func setupDependency(_ needsDependency: NeedsDependency) { + needsDependency.context = context + needsDependency.coordinator = coordinator + } + + self.context = context + discoveryPostsViewController = { + let viewController = DiscoveryPostsViewController() + setupDependency(viewController) + viewController.viewModel = DiscoveryPostsViewModel(context: context) + return viewController + }() + discoveryHashtagsViewController = { + let viewController = DiscoveryHashtagsViewController() + setupDependency(viewController) + viewController.viewModel = DiscoveryHashtagsViewModel(context: context) + return viewController + }() + discoveryNewsViewController = { + let viewController = DiscoveryNewsViewController() + setupDependency(viewController) + viewController.viewModel = DiscoveryNewsViewModel(context: context) + return viewController + }() + discoveryForYouViewController = { + let viewController = DiscoveryForYouViewController() + setupDependency(viewController) + viewController.viewModel = DiscoveryForYouViewModel(context: context) + return viewController + }() + self.viewControllers = [ + discoveryPostsViewController, + discoveryHashtagsViewController, + discoveryNewsViewController, + discoveryForYouViewController, + ] + // end init + + discoveryPostsViewController.viewModel.$isServerSupportEndpoint + .receive(on: DispatchQueue.main) + .sink { [weak self] isServerSupportEndpoint in + guard let self = self else { return } + if !isServerSupportEndpoint { + self.viewControllers.removeAll(where: { + $0 === self.discoveryPostsViewController || $0 === self.discoveryPostsViewController + }) + } + } + .store(in: &disposeBag) + + discoveryNewsViewController.viewModel.$isServerSupportEndpoint + .receive(on: DispatchQueue.main) + .sink { [weak self] isServerSupportEndpoint in + guard let self = self else { return } + if !isServerSupportEndpoint { + self.viewControllers.removeAll(where: { $0 === self.discoveryNewsViewController }) + } + } + .store(in: &disposeBag) + } + +} + + +// MARK: - PageboyViewControllerDataSource +extension DiscoveryViewModel: PageboyViewControllerDataSource { + + func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int { + return viewControllers.count + } + + func viewController(for pageboyViewController: PageboyViewController, at index: PageboyViewController.PageIndex) -> UIViewController? { + return viewControllers[index] + } + + func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? { + return .first + } + +} + +// MARK: - TMBarDataSource +extension DiscoveryViewModel: TMBarDataSource { + func barItem(for bar: TMBar, at index: Int) -> TMBarItemable { + guard !viewControllers.isEmpty, index < viewControllers.count else { + assertionFailure() + return TMBarItem(title: "") + } + return viewControllers[index].tabItem + } +} + +protocol PageViewController: UIViewController { + var tabItemTitle: String { get } + var tabItem: TMBarItemable { get } +} + +// MARK: - PageViewController +extension DiscoveryPostsViewController: PageViewController { + var tabItemTitle: String { "Posts" } + var tabItem: TMBarItemable { + return TMBarItem(title: tabItemTitle) + } +} + + +// MARK: - PageViewController +extension DiscoveryHashtagsViewController: PageViewController { + var tabItemTitle: String { "Hashtags" } + var tabItem: TMBarItemable { + + return TMBarItem(title: tabItemTitle) + } +} + +// MARK: - PageViewController +extension DiscoveryNewsViewController: PageViewController { + var tabItemTitle: String { "News" } + var tabItem: TMBarItemable { + return TMBarItem(title: tabItemTitle) + } +} + +// MARK: - PageViewController +extension DiscoveryForYouViewController: PageViewController { + var tabItemTitle: String { "For You" } + var tabItem: TMBarItemable { + return TMBarItem(title: tabItemTitle) + } +} diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift new file mode 100644 index 00000000..b8b8f25a --- /dev/null +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift @@ -0,0 +1,146 @@ +// +// DiscoveryForYouViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-14. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +final class DiscoveryForYouViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + let logger = Logger(subsystem: "DiscoveryForYouViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: DiscoveryForYouViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 100 + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + let refreshControl = UIRefreshControl() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryForYouViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.view.backgroundColor = theme.secondarySystemBackgroundColor + } + .store(in: &disposeBag) + + tableView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(tableView) + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: view.topAnchor), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + tableView.delegate = self + viewModel.setupDiffableDataSource( + tableView: tableView, + profileCardTableViewCellDelegate: self + ) + + tableView.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(DiscoveryForYouViewController.refreshControlValueChanged(_:)), for: .valueChanged) + viewModel.$isFetching + .receive(on: DispatchQueue.main) + .sink { [weak self] isFetching in + guard let self = self else { return } + if !isFetching { + self.refreshControl.endRefreshing() + } + } + .store(in: &disposeBag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + refreshControl.endRefreshing() + tableView.deselectRow(with: transitionCoordinator, animated: animated) + } + +} + +extension DiscoveryForYouViewController { + + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { + Task { + try await viewModel.fetch() + } + } + +} + +// MARK: - UITableViewDelegate +extension DiscoveryForYouViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)") + guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + guard let user = record.object(in: context.managedObjectContext) else { return } + let profileViewModel = CachedProfileViewModel( + context: context, + mastodonUser: user + ) + coordinator.present( + scene: .profile(viewModel: profileViewModel), + from: self, + transition: .show + ) + } + +} + +// MARK: - ProfileCardTableViewCellDelegate +extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate { + func profileCardTableViewCell(_ cell: ProfileCardTableViewCell, profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) { + guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { return } + guard let indexPath = tableView.indexPath(for: cell) else { return } + guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + + Task { + try await DataSourceFacade.responseToUserFollowAction( + dependency: self, + user: record, + authenticationBox: authenticationBox + ) + } // end Task + } +} + +// MARK: ScrollViewContainer +extension DiscoveryForYouViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + tableView + } +} + diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift new file mode 100644 index 00000000..fe53cf22 --- /dev/null +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift @@ -0,0 +1,47 @@ +// +// DiscoveryForYouViewModel+Diffable.swift +// Mastodon +// +// Created by MainasuK on 2022-4-14. +// + +import UIKit +import Combine +import MastodonUI + +extension DiscoveryForYouViewModel { + + func setupDiffableDataSource( + tableView: UITableView, + profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate + ) { + diffableDataSource = DiscoverySection.diffableDataSource( + tableView: tableView, + context: context, + configuration: DiscoverySection.Configuration( + profileCardTableViewCellDelegate: profileCardTableViewCellDelegate + ) + ) + + Task { + try await fetch() + } + + userFetchedResultsController.$records + .receive(on: DispatchQueue.main) + .sink { [weak self] records in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.forYou]) + + let items = records.map { DiscoveryItem.user($0) } + snapshot.appendItems(items, toSection: .forYou) + + diffableDataSource.applySnapshot(snapshot, animated: false) + } + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift new file mode 100644 index 00000000..7be4aeba --- /dev/null +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift @@ -0,0 +1,75 @@ +// +// DiscoveryForYouViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-14. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryForYouViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let userFetchedResultsController: UserFetchedResultsController + @Published var isFetching = false + + // output + var diffableDataSource: UITableViewDiffableDataSource? + let didLoadLatest = PassthroughSubject() + + init(context: AppContext) { + self.context = context + self.userFetchedResultsController = UserFetchedResultsController( + managedObjectContext: context.managedObjectContext, + domain: nil, + additionalPredicate: nil + ) + // end init + + context.authenticationService.activeMastodonAuthenticationBox + .map { $0?.domain } + .assign(to: \.domain, on: userFetchedResultsController) + .store(in: &disposeBag) + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryForYouViewModel { + func fetch() async throws { + guard !isFetching else { return } + isFetching = true + defer { isFetching = false } + + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + + do { + let response = try await context.apiService.suggestionAccountV2( + query: nil, + authenticationBox: authenticationBox + ) + let userIDs = response.value.map { $0.account.id } + userFetchedResultsController.userIDs = userIDs + } catch { + // fallback V1 + let response2 = try await context.apiService.suggestionAccount( + query: nil, + authenticationBox: authenticationBox + ) + let userIDs = response2.value.map { $0.id } + userFetchedResultsController.userIDs = userIDs + } + } +} diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift new file mode 100644 index 00000000..91ab5036 --- /dev/null +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift @@ -0,0 +1,133 @@ +// +// DiscoveryHashtagsViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +final class DiscoveryHashtagsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + let logger = Logger(subsystem: "TrendPostsViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: DiscoveryHashtagsViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 100 + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + let refreshControl = UIRefreshControl() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryHashtagsViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.view.backgroundColor = theme.secondarySystemBackgroundColor + } + .store(in: &disposeBag) + + tableView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(tableView) + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: view.topAnchor), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + tableView.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(DiscoveryHashtagsViewController.refreshControlValueChanged(_:)), for: .valueChanged) + + tableView.delegate = self + viewModel.setupDiffableDataSource( + tableView: tableView + ) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + tableView.deselectRow(with: transitionCoordinator, animated: animated) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + viewModel.viewDidAppeared.send() + } + +} + +extension DiscoveryHashtagsViewController { + + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { + Task { @MainActor in + do { + try await viewModel.fetch() + } catch { + // do nothing + } + sender.endRefreshing() + } // end Task + } + +} + +// MARK: - UITableViewDelegate +extension DiscoveryHashtagsViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)") + guard case let .hashtag(tag) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: tag.name) + coordinator.present( + scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), + from: self, + transition: .show + ) + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + guard let cell = cell as? TrendTableViewCell else { return } + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } + + if let lastItem = diffableDataSource.snapshot().itemIdentifiers.last, item == lastItem { + cell.configureSeparator(style: .edge) + } + } +} + +// MARK: ScrollViewContainer +extension DiscoveryHashtagsViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + tableView + } +} diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift new file mode 100644 index 00000000..0370f3f5 --- /dev/null +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift @@ -0,0 +1,42 @@ +// +// DiscoveryHashtagsViewModel+Diffable.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit + +extension DiscoveryHashtagsViewModel { + + func setupDiffableDataSource( + tableView: UITableView + ) { + diffableDataSource = DiscoverySection.diffableDataSource( + tableView: tableView, + context: context, + configuration: DiscoverySection.Configuration() + ) + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.hashtags]) + diffableDataSource?.apply(snapshot) + + $hashtags + .receive(on: DispatchQueue.main) + .sink { [weak self] hashtags in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.hashtags]) + + let items = hashtags.map { DiscoveryItem.hashtag($0) } + snapshot.appendItems(items, toSection: .hashtags) + + diffableDataSource.apply(snapshot) + } + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift new file mode 100644 index 00000000..bb30ec9e --- /dev/null +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift @@ -0,0 +1,76 @@ +// +// DiscoveryHashtagsViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryHashtagsViewModel { + + let logger = Logger(subsystem: "DiscoveryHashtagsViewModel", category: "ViewModel") + + var disposeBag = Set() + + // input + let context: AppContext + let viewDidAppeared = PassthroughSubject() + + // output + var diffableDataSource: UITableViewDiffableDataSource? + @Published var hashtags: [Mastodon.Entity.Tag] = [] + + init(context: AppContext) { + self.context = context + // end init + + Publishers.CombineLatest( + context.authenticationService.activeMastodonAuthenticationBox, + viewDidAppeared + ) + .compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in + return authenticationBox + } + .throttle(for: 3, scheduler: DispatchQueue.main, latest: true) + .asyncMap { authenticationBox in + try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil) + } + .retry(3) + .map { response in Result, Error> { response } } + .catch { error in Just(Result, Error> { throw error }) } + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let response): + self.hashtags = response.value.filter { !$0.name.isEmpty } + case .failure: + break + } + } + .store(in: &disposeBag) + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryHashtagsViewModel { + + @MainActor + func fetch() async throws { + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + let response = try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil) + hashtags = response.value.filter { !$0.name.isEmpty } + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch tags: \(response.value.count)") + } +} diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift new file mode 100644 index 00000000..4042e2cd --- /dev/null +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewController.swift @@ -0,0 +1,133 @@ +// +// DiscoveryNewsViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +final class DiscoveryNewsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + let logger = Logger(subsystem: "TrendPostsViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: DiscoveryNewsViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 100 + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + let refreshControl = UIRefreshControl() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryNewsViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.view.backgroundColor = theme.secondarySystemBackgroundColor + } + .store(in: &disposeBag) + + tableView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(tableView) + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: view.topAnchor), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + tableView.delegate = self + viewModel.setupDiffableDataSource( + tableView: tableView + ) + + tableView.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(DiscoveryNewsViewController.refreshControlValueChanged(_:)), for: .valueChanged) + viewModel.didLoadLatest + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.refreshControl.endRefreshing() + } + .store(in: &disposeBag) + + // setup batch fetch + viewModel.listBatchFetchViewModel.setup(scrollView: tableView) + viewModel.listBatchFetchViewModel.shouldFetch + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + guard self.view.window != nil else { return } + self.viewModel.stateMachine.enter(DiscoveryNewsViewModel.State.Loading.self) + } + .store(in: &disposeBag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + refreshControl.endRefreshing() + tableView.deselectRow(with: transitionCoordinator, animated: animated) + } + +} + +extension DiscoveryNewsViewController { + + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { + guard viewModel.stateMachine.enter(DiscoveryNewsViewModel.State.Reloading.self) else { + sender.endRefreshing() + return + } + } + +} + +// MARK: - UITableViewDelegate +extension DiscoveryNewsViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)") + guard case let .link(link) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + guard let url = URL(string: link.url) else { return } + coordinator.present( + scene: .safari(url: url), + from: self, + transition: .safariPresent(animated: true, completion: nil) + ) + } + +} + +// MARK: ScrollViewContainer +extension DiscoveryNewsViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + tableView + } +} diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift new file mode 100644 index 00000000..ab3634a3 --- /dev/null +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift @@ -0,0 +1,60 @@ +// +// DiscoveryNewsViewModel+Diffable.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit +import Combine + +extension DiscoveryNewsViewModel { + + func setupDiffableDataSource( + tableView: UITableView + ) { + diffableDataSource = DiscoverySection.diffableDataSource( + tableView: tableView, + context: context, + configuration: DiscoverySection.Configuration() + ) + + stateMachine.enter(State.Reloading.self) + + $links + .receive(on: DispatchQueue.main) + .sink { [weak self] links in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.news]) + + let items = links.map { DiscoveryItem.link($0) } + snapshot.appendItems(items, toSection: .news) + + if let currentState = self.stateMachine.currentState { + switch currentState { + case is State.Initial, + is State.Loading, + is State.Idle, + is State.Fail: + if !items.isEmpty { + snapshot.appendItems([.bottomLoader], toSection: .news) + } + case is State.Reloading: + break + case is State.NoMore: + break + default: + assertionFailure() + break + } + } + + diffableDataSource.applySnapshot(snapshot, animated: false) + } + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift new file mode 100644 index 00000000..8da975de --- /dev/null +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift @@ -0,0 +1,213 @@ +// +// DiscoveryNewsViewModel+State.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import Foundation +import GameplayKit +import MastodonSDK + +extension DiscoveryNewsViewModel { + class State: GKState, NamingState { + + let logger = Logger(subsystem: "DiscoveryNewsViewModel.State", category: "StateMachine") + + let id = UUID() + + var name: String { + String(describing: Self.self) + } + + weak var viewModel: DiscoveryNewsViewModel? + + init(viewModel: DiscoveryNewsViewModel) { + self.viewModel = viewModel + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + let previousState = previousState as? DiscoveryNewsViewModel.State + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "")") + } + + @MainActor + func enter(state: State.Type) { + stateMachine?.enter(state) + } + + deinit { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)") + } + } +} + +extension DiscoveryNewsViewModel.State { + class Initial: DiscoveryNewsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type: + return true + default: + return false + } + } + } + + class Reloading: DiscoveryNewsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Loading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + stateMachine.enter(Loading.self) + } + } + + class Fail: DiscoveryNewsViewModel.State { + + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Loading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let _ = viewModel, let stateMachine = stateMachine else { return } + + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading 3s later…", ((#file as NSString).lastPathComponent), #line, #function) + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading", ((#file as NSString).lastPathComponent), #line, #function) + stateMachine.enter(Loading.self) + } + } + } + + class Idle: DiscoveryNewsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type, is Loading.Type: + return true + default: + return false + } + } + } + + class Loading: DiscoveryNewsViewModel.State { + + var offset: Int? + + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Fail.Type: + return true + case is Idle.Type: + return true + case is NoMore.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + + switch previousState { + case is Reloading: + offset = nil + default: + break + } + + guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { + stateMachine.enter(Fail.self) + return + } + + let offset = self.offset + let isReloading = offset == nil + + Task { + do { + let response = try await viewModel.context.apiService.trendLinks( + domain: authenticationBox.domain, + query: Mastodon.API.Trends.StatusQuery( + offset: offset, + limit: nil + ) + ) + let newOffset: Int? = { + guard let offset = response.link?.offset else { return nil } + return self.offset.flatMap { max($0, offset) } ?? offset + }() + + let hasMore: Bool = { + guard let newOffset = newOffset else { return false } + return newOffset != self.offset // not the same one + }() + + self.offset = newOffset + + var hasNewItemsAppend = false + var links = isReloading ? [] : viewModel.links + for link in response.value { + guard !links.contains(link) else { continue } + links.append(link) + hasNewItemsAppend = true + } + + if hasNewItemsAppend, hasMore { + await enter(state: Idle.self) + } else { + await enter(state: NoMore.self) + } + viewModel.links = links + viewModel.didLoadLatest.send() + } catch { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch news fail: \(error.localizedDescription)") + if let error = error as? Mastodon.API.Error, error.httpResponseStatus.code == 404 { + viewModel.isServerSupportEndpoint = false + await enter(state: NoMore.self) + } else { + await enter(state: Fail.self) + } + + viewModel.didLoadLatest.send() + } + } // end Task + } // end func + } + + class NoMore: DiscoveryNewsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + } + } +} diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift new file mode 100644 index 00000000..2c4d89dc --- /dev/null +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift @@ -0,0 +1,74 @@ +// +// DiscoveryNewsViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-13. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryNewsViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let listBatchFetchViewModel = ListBatchFetchViewModel() + + // output + @Published var links: [Mastodon.Entity.Link] = [] + var diffableDataSource: UITableViewDiffableDataSource? + private(set) lazy var stateMachine: GKStateMachine = { + let stateMachine = GKStateMachine(states: [ + State.Initial(viewModel: self), + State.Reloading(viewModel: self), + State.Fail(viewModel: self), + State.Idle(viewModel: self), + State.Loading(viewModel: self), + State.NoMore(viewModel: self), + ]) + stateMachine.enter(State.Initial.self) + return stateMachine + }() + + let didLoadLatest = PassthroughSubject() + @Published var isServerSupportEndpoint = true + + init(context: AppContext) { + self.context = context + // end init + + Task { + await checkServerEndpoint() + } // end Task + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + + +extension DiscoveryNewsViewModel { + func checkServerEndpoint() async { + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + + do { + _ = try await context.apiService.trendLinks( + domain: authenticationBox.domain, + query: .init(offset: nil, limit: nil) + ) + } catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 { + isServerSupportEndpoint = false + } catch { + // do nothing + } + } +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController+DataSourceProvider.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController+DataSourceProvider.swift new file mode 100644 index 00000000..c3495b24 --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController+DataSourceProvider.swift @@ -0,0 +1,34 @@ +// +// DiscoveryPostsViewController+DataSourceProvider.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import UIKit + +extension DiscoveryPostsViewController: DataSourceProvider { + func item(from source: DataSourceItem.Source) async -> DataSourceItem? { + var _indexPath = source.indexPath + if _indexPath == nil, let cell = source.tableViewCell { + _indexPath = await self.indexPath(for: cell) + } + guard let indexPath = _indexPath else { return nil } + + guard let item = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { + return nil + } + + switch item { + case .status(let record): + return .status(record: record) + default: + return nil + } + } + + @MainActor + private func indexPath(for cell: UITableViewCell) async -> IndexPath? { + return tableView.indexPath(for: cell) + } +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift new file mode 100644 index 00000000..259b21d3 --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift @@ -0,0 +1,173 @@ +// +// DiscoveryPostsViewController.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +final class DiscoveryPostsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { + + let logger = Logger(subsystem: "TrendPostsViewController", category: "ViewController") + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var disposeBag = Set() + var viewModel: DiscoveryPostsViewModel! + + let mediaPreviewTransitionController = MediaPreviewTransitionController() + + lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 100 + tableView.separatorStyle = .none + tableView.backgroundColor = .clear + return tableView + }() + + let refreshControl = UIRefreshControl() + + let discoveryIntroBannerView = DiscoveryIntroBannerView() + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryPostsViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.view.backgroundColor = theme.secondarySystemBackgroundColor + } + .store(in: &disposeBag) + + tableView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(tableView) + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: view.topAnchor), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + discoveryIntroBannerView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(discoveryIntroBannerView) + NSLayoutConstraint.activate([ + discoveryIntroBannerView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + discoveryIntroBannerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + discoveryIntroBannerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + ]) + + discoveryIntroBannerView.delegate = self + discoveryIntroBannerView.isHidden = UserDefaults.shared.discoveryIntroBannerNeedsHidden + UserDefaults.shared.publisher(for: \.discoveryIntroBannerNeedsHidden) + .receive(on: DispatchQueue.main) + .assign(to: \.isHidden, on: discoveryIntroBannerView) + .store(in: &disposeBag) + + tableView.delegate = self + viewModel.setupDiffableDataSource( + tableView: tableView, + statusTableViewCellDelegate: self + ) + + tableView.refreshControl = refreshControl + refreshControl.addTarget(self, action: #selector(DiscoveryPostsViewController.refreshControlValueChanged(_:)), for: .valueChanged) + viewModel.didLoadLatest + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.refreshControl.endRefreshing() + } + .store(in: &disposeBag) + + // setup batch fetch + viewModel.listBatchFetchViewModel.setup(scrollView: tableView) + viewModel.listBatchFetchViewModel.shouldFetch + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + guard self.view.window != nil else { return } + self.viewModel.stateMachine.enter(DiscoveryPostsViewModel.State.Loading.self) + } + .store(in: &disposeBag) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + refreshControl.endRefreshing() + tableView.deselectRow(with: transitionCoordinator, animated: animated) + } + +} + +extension DiscoveryPostsViewController { + + @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { + guard viewModel.stateMachine.enter(DiscoveryPostsViewModel.State.Reloading.self) else { + sender.endRefreshing() + return + } + } + +} + +// MARK: - UITableViewDelegate +extension DiscoveryPostsViewController: UITableViewDelegate, AutoGenerateTableViewDelegate { + // sourcery:inline:DiscoveryPostsViewController.AutoGenerateTableViewDelegate + + // Generated using Sourcery + // DO NOT EDIT + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + aspectTableView(tableView, didSelectRowAt: indexPath) + } + + func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + return aspectTableView(tableView, contextMenuConfigurationForRowAt: indexPath, point: point) + } + + func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { + return aspectTableView(tableView, previewForHighlightingContextMenuWithConfiguration: configuration) + } + + func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { + return aspectTableView(tableView, previewForDismissingContextMenuWithConfiguration: configuration) + } + + func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { + aspectTableView(tableView, willPerformPreviewActionForMenuWith: configuration, animator: animator) + } + // sourcery:end +} + +// MARK: - StatusTableViewCellDelegate +extension DiscoveryPostsViewController: StatusTableViewCellDelegate { } + +// MARK: ScrollViewContainer +extension DiscoveryPostsViewController: ScrollViewContainer { + var scrollView: UIScrollView? { + tableView + } +} + +// MARK: - DiscoveryIntroBannerViewDelegate +extension DiscoveryPostsViewController: DiscoveryIntroBannerViewDelegate { + func discoveryIntroBannerView(_ bannerView: DiscoveryIntroBannerView, closeButtonDidPressed button: UIButton) { + UserDefaults.shared.discoveryIntroBannerNeedsHidden = true + } +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift new file mode 100644 index 00000000..5c82384c --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift @@ -0,0 +1,65 @@ +// +// DiscoveryPostsViewModel+Diffable.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import UIKit +import Combine + +extension DiscoveryPostsViewModel { + + func setupDiffableDataSource( + tableView: UITableView, + statusTableViewCellDelegate: StatusTableViewCellDelegate + ) { + diffableDataSource = StatusSection.diffableDataSource( + tableView: tableView, + context: context, + configuration: StatusSection.Configuration( + statusTableViewCellDelegate: statusTableViewCellDelegate, + timelineMiddleLoaderTableViewCellDelegate: nil, + filterContext: .none, + activeFilters: nil + ) + ) + + stateMachine.enter(State.Reloading.self) + + statusFetchedResultsController.$records + .receive(on: DispatchQueue.main) + .sink { [weak self] records in + guard let self = self else { return } + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + + let items = records.map { StatusItem.status(record: $0) } + snapshot.appendItems(items, toSection: .main) + + if let currentState = self.stateMachine.currentState { + switch currentState { + case is State.Initial, + is State.Reloading, + is State.Loading, + is State.Idle, + is State.Fail: + if !items.isEmpty { + snapshot.appendItems([.bottomLoader], toSection: .main) + } + case is State.NoMore: + break + default: + assertionFailure() + break + } + } + + diffableDataSource.applySnapshot(snapshot, animated: false) + } + .store(in: &disposeBag) + } + +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift new file mode 100644 index 00000000..d91c5adc --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift @@ -0,0 +1,213 @@ +// +// DiscoveryPostsViewModel+State.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import os.log +import Foundation +import GameplayKit +import MastodonSDK + +extension DiscoveryPostsViewModel { + class State: GKState, NamingState { + + let logger = Logger(subsystem: "DiscoveryPostsViewModel.State", category: "StateMachine") + + let id = UUID() + + var name: String { + String(describing: Self.self) + } + + weak var viewModel: DiscoveryPostsViewModel? + + init(viewModel: DiscoveryPostsViewModel) { + self.viewModel = viewModel + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + let previousState = previousState as? DiscoveryPostsViewModel.State + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] enter \(self.name), previous: \(previousState?.name ?? "")") + } + + @MainActor + func enter(state: State.Type) { + stateMachine?.enter(state) + } + + deinit { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [\(self.id.uuidString)] \(self.name)") + } + } +} + +extension DiscoveryPostsViewModel.State { + class Initial: DiscoveryPostsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type: + return true + default: + return false + } + } + } + + class Reloading: DiscoveryPostsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Loading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let _ = viewModel, let stateMachine = stateMachine else { return } + + stateMachine.enter(Loading.self) + } + } + + class Fail: DiscoveryPostsViewModel.State { + + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Loading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let _ = viewModel, let stateMachine = stateMachine else { return } + + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading 3s later…", ((#file as NSString).lastPathComponent), #line, #function) + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading", ((#file as NSString).lastPathComponent), #line, #function) + stateMachine.enter(Loading.self) + } + } + } + + class Idle: DiscoveryPostsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type, is Loading.Type: + return true + default: + return false + } + } + } + + class Loading: DiscoveryPostsViewModel.State { + + var offset: Int? + + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Fail.Type: + return true + case is Idle.Type: + return true + case is NoMore.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + + switch previousState { + case is Reloading: + offset = nil + default: + break + } + + guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { + stateMachine.enter(Fail.self) + return + } + + let offset = self.offset + let isReloading = offset == nil + + Task { + do { + let response = try await viewModel.context.apiService.trendStatuses( + domain: authenticationBox.domain, + query: Mastodon.API.Trends.StatusQuery( + offset: offset, + limit: nil + ) + ) + let newOffset: Int? = { + guard let offset = response.link?.offset else { return nil } + return self.offset.flatMap { max($0, offset) } ?? offset + }() + + let hasMore: Bool = { + guard let newOffset = newOffset else { return false } + return newOffset != self.offset // not the same one + }() + + self.offset = newOffset + + var hasNewStatusesAppend = false + var statusIDs = isReloading ? [] : viewModel.statusFetchedResultsController.statusIDs.value + for status in response.value { + guard !statusIDs.contains(status.id) else { continue } + statusIDs.append(status.id) + hasNewStatusesAppend = true + } + + if hasNewStatusesAppend, hasMore { + await enter(state: Idle.self) + } else { + await enter(state: NoMore.self) + } + viewModel.statusFetchedResultsController.statusIDs.value = statusIDs + viewModel.didLoadLatest.send() +// } catch let error as? + } catch { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch posts fail: \(error.localizedDescription)") + if let error = error as? Mastodon.API.Error, error.httpResponseStatus.code == 404 { + viewModel.isServerSupportEndpoint = false + await enter(state: NoMore.self) + } else { + await enter(state: Fail.self) + } + + viewModel.didLoadLatest.send() + } + } // end Task + } // end func + } + + class NoMore: DiscoveryPostsViewModel.State { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + switch stateClass { + case is Reloading.Type: + return true + default: + return false + } + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + } + } +} diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift new file mode 100644 index 00000000..c001bb7b --- /dev/null +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift @@ -0,0 +1,83 @@ +// +// DiscoveryPostsViewModel.swift +// Mastodon +// +// Created by MainasuK on 2022-4-12. +// + +import os.log +import UIKit +import Combine +import GameplayKit +import CoreData +import CoreDataStack +import MastodonSDK + +final class DiscoveryPostsViewModel { + + var disposeBag = Set() + + // input + let context: AppContext + let statusFetchedResultsController: StatusFetchedResultsController + let listBatchFetchViewModel = ListBatchFetchViewModel() + + // output + var diffableDataSource: UITableViewDiffableDataSource? + private(set) lazy var stateMachine: GKStateMachine = { + let stateMachine = GKStateMachine(states: [ + State.Initial(viewModel: self), + State.Reloading(viewModel: self), + State.Fail(viewModel: self), + State.Idle(viewModel: self), + State.Loading(viewModel: self), + State.NoMore(viewModel: self), + ]) + stateMachine.enter(State.Initial.self) + return stateMachine + }() + + let didLoadLatest = PassthroughSubject() + @Published var isServerSupportEndpoint = true + + init(context: AppContext) { + self.context = context + self.statusFetchedResultsController = StatusFetchedResultsController( + managedObjectContext: context.managedObjectContext, + domain: nil, + additionalTweetPredicate: nil + ) + // end init + + context.authenticationService.activeMastodonAuthentication + .map { $0?.domain } + .assign(to: \.value, on: statusFetchedResultsController.domain) + .store(in: &disposeBag) + + Task { + await checkServerEndpoint() + } // end Task + } + + deinit { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + +} + +extension DiscoveryPostsViewModel { + func checkServerEndpoint() async { + guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + + do { + _ = try await context.apiService.trendStatuses( + domain: authenticationBox.domain, + query: .init(offset: nil, limit: nil) + ) + } catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 { + isServerSupportEndpoint = false + } catch { + // do nothing + } + } +} diff --git a/Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift b/Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift new file mode 100644 index 00000000..e3e1c454 --- /dev/null +++ b/Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift @@ -0,0 +1,101 @@ +// +// DiscoveryIntroBannerView.swift +// Mastodon +// +// Created by MainasuK on 2022-4-19. +// + +import os.log +import UIKit +import Combine +import MastodonAsset + +public protocol DiscoveryIntroBannerViewDelegate: AnyObject { + func discoveryIntroBannerView(_ bannerView: DiscoveryIntroBannerView, closeButtonDidPressed button: UIButton) +} + +public final class DiscoveryIntroBannerView: UIView { + + let logger = Logger(subsystem: "DiscoveryIntroBannerView", category: "View") + + var _disposeBag = Set() + + public weak var delegate: DiscoveryIntroBannerViewDelegate? + + let label: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 16, weight: .regular)) + label.textColor = Asset.Colors.Label.primary.color + label.text = "These are the posts gaining traction in your corner of Mastodon." // TODO: i18n + label.numberOfLines = 0 + return label + }() + + let closeButton: HitTestExpandedButton = { + let button = HitTestExpandedButton(type: .system) + button.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + button.tintColor = Asset.Colors.Label.secondary.color + return button + }() + + public override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension DiscoveryIntroBannerView { + private func _init() { + preservesSuperviewLayoutMargins = true + + setupAppearance(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupAppearance(theme: theme) + } + .store(in: &_disposeBag) + + closeButton.translatesAutoresizingMaskIntoConstraints = false + addSubview(closeButton) + NSLayoutConstraint.activate([ + closeButton.topAnchor.constraint(equalTo: topAnchor, constant: 16).priority(.required - 1), + layoutMarginsGuide.trailingAnchor.constraint(equalTo: closeButton.trailingAnchor), + closeButton.heightAnchor.constraint(equalToConstant: 20).priority(.required - 1), + closeButton.widthAnchor.constraint(equalToConstant: 20).priority(.required - 1), + ]) + + label.translatesAutoresizingMaskIntoConstraints = false + addSubview(label) + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: topAnchor, constant: 16).priority(.required - 1), + label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + closeButton.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 10), + bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 16).priority(.required - 1), + ]) + + closeButton.addTarget(self, action: #selector(DiscoveryIntroBannerView.closeButtonDidPressed(_:)), for: .touchUpInside) + } +} + +extension DiscoveryIntroBannerView { + @objc private func closeButtonDidPressed(_ sender: UIButton) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + delegate?.discoveryIntroBannerView(self, closeButtonDidPressed: sender) + } +} + +extension DiscoveryIntroBannerView { + + private func setupAppearance(theme: Theme) { + backgroundColor = theme.systemBackgroundColor + } + +} diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 7b7f35e5..54955272 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -17,6 +17,7 @@ import AlamofireImage import StoreKit import MastodonAsset import MastodonLocalization +import MastodonUI final class HomeTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { @@ -291,7 +292,7 @@ extension HomeTimelineViewController { tableView.deselectRow(with: transitionCoordinator, animated: animated) // needs trigger manually after onboarding dismiss - setNeedsStatusBarAppearanceUpdate() + setNeedsStatusBarAppearanceUpdate() } override func viewDidAppear(_ animated: Bool) { diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift index bdb4d05c..16130251 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift @@ -39,6 +39,8 @@ final class NotificationTimelineViewController: UIViewController, NeedsDependenc return tableView }() + let cellFrameCache = NSCache() + deinit { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) } @@ -122,6 +124,16 @@ extension NotificationTimelineViewController { } +// MARK: - CellFrameCacheContainer +extension NotificationTimelineViewController: CellFrameCacheContainer { + func keyForCache(tableView: UITableView, indexPath: IndexPath) -> NSNumber? { + guard let diffableDataSource = viewModel.diffableDataSource else { return nil } + guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return nil } + let key = NSNumber(value: item.hashValue) + return key + } +} + extension NotificationTimelineViewController { @objc private func refreshControlValueChanged(_ sender: UIRefreshControl) { @@ -162,6 +174,13 @@ extension NotificationTimelineViewController: UITableViewDelegate, AutoGenerateT // sourcery:end + func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + guard let frame = retrieveCellFrame(tableView: tableView, indexPath: indexPath) else { + return 300 + } + return ceil(frame.height) + } + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard let item = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return @@ -172,6 +191,10 @@ extension NotificationTimelineViewController: UITableViewDelegate, AutoGenerateT await viewModel.loadMore(item: item) } } + + func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { + cacheCellFrame(tableView: tableView, didEndDisplaying: cell, forRowAt: indexPath) + } } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift index 2d43faa5..b86c952a 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift @@ -12,6 +12,7 @@ import GameController import AuthenticationServices import MastodonAsset import MastodonLocalization +import MastodonUI final class MastodonPickServerViewController: UIViewController, NeedsDependency { @@ -144,6 +145,13 @@ extension MastodonPickServerViewController { pickServerServerSectionTableHeaderViewDelegate: self, pickServerCellDelegate: self ) + + KeyboardResponderService + .configure( + scrollView: tableView, + layoutNeedsUpdate: viewModel.viewDidAppear.eraseToAnyPublisher() + ) + .store(in: &disposeBag) viewModel .selectedServer @@ -238,6 +246,7 @@ extension MastodonPickServerViewController { super.viewDidAppear(animated) tableView.flashScrollIndicators() + viewModel.viewDidAppear.send() } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -416,28 +425,6 @@ extension MastodonPickServerViewController: UITableViewDelegate { viewModel.selectedServer.send(nil) } - func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - guard let diffableDataSource = viewModel.diffableDataSource else { return } - guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } - - switch item { -// case .categoryPicker: -// guard let cell = cell as? PickServerCategoriesCell else { return } -// guard let diffableDataSource = cell.diffableDataSource else { return } -// let snapshot = diffableDataSource.snapshot() -// -// let item = viewModel.selectCategoryItem.value -// guard let section = snapshot.indexOfSection(.main), -// let row = snapshot.indexOfItem(item) else { return } -// cell.collectionView.selectItem(at: IndexPath(item: row, section: section), animated: false, scrollPosition: .centeredHorizontally) -// case .search: -// guard let cell = cell as? PickServerSearchCell else { return } -// cell.searchTextField.text = viewModel.searchText.value - default: - break - } - } - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { guard let diffableDataSource = viewModel.diffableDataSource else { return nil } let snapshot = diffableDataSource.snapshot() diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift index af38b110..59008c53 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift @@ -45,7 +45,8 @@ class MastodonPickServerViewModel: NSObject { let indexedServers = CurrentValueSubject<[Mastodon.Entity.Server], Never>([]) let unindexedServers = CurrentValueSubject<[Mastodon.Entity.Server]?, Never>([]) // set nil when loading let viewWillAppear = PassthroughSubject() - + let viewDidAppear = CurrentValueSubject(Void()) + // output var diffableDataSource: UITableViewDiffableDataSource? private(set) lazy var loadIndexedServerStateMachine: GKStateMachine = { diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift index b2269b9c..894cbdbd 100644 --- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift +++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerServerSectionTableHeaderView.swift @@ -185,12 +185,12 @@ extension PickServerServerSectionTableHeaderView { override func accessibilityElementCount() -> Int { guard let diffableDataSource = diffableDataSource else { return 0 } - return diffableDataSource.snapshot().itemIdentifiers.count + return diffableDataSource.snapshot().itemIdentifiers.count + 1 } override func accessibilityElement(at index: Int) -> Any? { - guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil } - return item + if let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) { return item } + return searchTextField } } diff --git a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift index 3daa2eb1..15f23483 100644 --- a/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift +++ b/Mastodon/Scene/Onboarding/Register/Cell/MastodonRegisterTextFieldTableViewCell.swift @@ -115,6 +115,7 @@ extension MastodonRegisterTextFieldTableViewCell { label.font = MastodonRegisterTextFieldTableViewCell.textFieldLabelFont label.textColor = Asset.Colors.Label.primary.color label.text = text + label.lineBreakMode = .byTruncatingMiddle label.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(label) @@ -123,6 +124,7 @@ extension MastodonRegisterTextFieldTableViewCell { label.leadingAnchor.constraint(equalTo: paddingView.trailingAnchor), containerView.trailingAnchor.constraint(equalTo: label.trailingAnchor, constant: 16), label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + label.widthAnchor.constraint(lessThanOrEqualToConstant: 180).priority(.required - 1), ]) return containerView }() diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift index bd2db3d4..26060c2c 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift @@ -11,6 +11,7 @@ import MastodonSDK import os.log import PhotosUI import UIKit +import MastodonUI import MastodonAsset import MastodonLocalization @@ -132,12 +133,12 @@ extension MastodonRegisterViewController { viewModel.setupDiffableDataSource(tableView: tableView) -// KeyboardResponderService -// .configure( -// scrollView: tableView, -// layoutNeedsUpdate: viewModel.viewDidAppear.eraseToAnyPublisher() -// ) -// .store(in: &disposeBag) + KeyboardResponderService + .configure( + scrollView: tableView, + layoutNeedsUpdate: viewModel.viewDidAppear.eraseToAnyPublisher() + ) + .store(in: &disposeBag) // gesture view.addGestureRecognizer(tapGestureRecognizer) @@ -403,65 +404,3 @@ extension MastodonRegisterViewController { } } - -extension MastodonRegisterViewController: UITextFieldDelegate { -// func textFieldDidBeginEditing(_ textField: UITextField) { -// let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" -// -// switch textField { -// case usernameTextField: -// viewModel.username.value = text -// case displayNameTextField: -// viewModel.displayName.value = text -// case emailTextField: -// viewModel.email.value = text -// case passwordTextField: -// viewModel.password.value = text -// case reasonTextField: -// viewModel.reason.value = text -// default: -// break -// } -// } -// -// func textFieldDidEndEditing(_ textField: UITextField) { -// let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" -// -// switch textField { -// case usernameTextField: -// viewModel.username.value = text -// case displayNameTextField: -// viewModel.displayName.value = text -// case emailTextField: -// viewModel.email.value = text -// case passwordTextField: -// viewModel.password.value = text -// case reasonTextField: -// viewModel.reason.value = text -// default: -// break -// } -// } -// -// func textFieldShouldReturn(_ textField: UITextField) -> Bool { -// switch textField { -// case usernameTextField: -// displayNameTextField.becomeFirstResponder() -// case displayNameTextField: -// emailTextField.becomeFirstResponder() -// case emailTextField: -// passwordTextField.becomeFirstResponder() -// case passwordTextField: -// if viewModel.approvalRequired { -// reasonTextField.becomeFirstResponder() -// } else { -// passwordTextField.resignFirstResponder() -// } -// case reasonTextField: -// reasonTextField.resignFirstResponder() -// default: -// break -// } -// return true -// } -} diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index a890505e..55a952b0 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -402,6 +402,7 @@ extension ProfileViewController { } extension ProfileViewController { + private func updateBarButtonInsets() { let margin: CGFloat = { switch traitCollection.userInterfaceIdiom { diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 403437da..ac8c12e9 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -13,10 +13,11 @@ import MastodonSDK import MastodonMeta import MastodonAsset import MastodonLocalization +import MastodonUI // please override this base class class ProfileViewModel: NSObject { - + let logger = Logger(subsystem: "ProfileViewModel", category: "ViewModel") typealias UserID = String @@ -372,101 +373,6 @@ extension ProfileViewModel { } -extension ProfileViewModel { - - enum RelationshipAction: Int, CaseIterable { - case none // set hide from UI - case follow - case request - case pending - case following - case muting - case blocked - case blocking - case suspended - case edit - case editing - case updating - - var option: RelationshipActionOptionSet { - return RelationshipActionOptionSet(rawValue: 1 << rawValue) - } - } - - // construct option set on the enum for safe iterator - struct RelationshipActionOptionSet: OptionSet { - let rawValue: Int - - static let none = RelationshipAction.none.option - static let follow = RelationshipAction.follow.option - static let request = RelationshipAction.request.option - static let pending = RelationshipAction.pending.option - static let following = RelationshipAction.following.option - static let muting = RelationshipAction.muting.option - static let blocked = RelationshipAction.blocked.option - static let blocking = RelationshipAction.blocking.option - static let suspended = RelationshipAction.suspended.option - static let edit = RelationshipAction.edit.option - static let editing = RelationshipAction.editing.option - static let updating = RelationshipAction.updating.option - - static let editOptions: RelationshipActionOptionSet = [.edit, .editing, .updating] - - func highPriorityAction(except: RelationshipActionOptionSet) -> RelationshipAction? { - let set = subtracting(except) - for action in RelationshipAction.allCases.reversed() where set.contains(action.option) { - return action - } - - return nil - } - - var title: String { - guard let highPriorityAction = self.highPriorityAction(except: []) else { - assertionFailure() - return " " - } - switch highPriorityAction { - case .none: return " " - case .follow: return L10n.Common.Controls.Friendship.follow - case .request: return L10n.Common.Controls.Friendship.request - case .pending: return L10n.Common.Controls.Friendship.pending - case .following: return L10n.Common.Controls.Friendship.following - case .muting: return L10n.Common.Controls.Friendship.muted - case .blocked: return L10n.Common.Controls.Friendship.follow // blocked by user - case .blocking: return L10n.Common.Controls.Friendship.blocked - case .suspended: return L10n.Common.Controls.Friendship.follow - case .edit: return L10n.Common.Controls.Friendship.editInfo - case .editing: return L10n.Common.Controls.Actions.done - case .updating: return " " - } - } - - @available(*, deprecated, message: "") - var backgroundColor: UIColor { - guard let highPriorityAction = self.highPriorityAction(except: []) else { - assertionFailure() - return Asset.Colors.brandBlue.color - } - switch highPriorityAction { - case .none: return Asset.Colors.brandBlue.color - case .follow: return Asset.Colors.brandBlue.color - case .request: return Asset.Colors.brandBlue.color - case .pending: return Asset.Colors.brandBlue.color - case .following: return Asset.Colors.brandBlue.color - case .muting: return Asset.Colors.alertYellow.color - case .blocked: return Asset.Colors.brandBlue.color - case .blocking: return Asset.Colors.danger.color - case .suspended: return Asset.Colors.brandBlue.color - case .edit: return Asset.Colors.brandBlue.color - case .editing: return Asset.Colors.brandBlue.color - case .updating: return Asset.Colors.brandBlue.color - } - } - - } -} - extension ProfileViewModel { func updateProfileInfo( headerProfileInfo: ProfileHeaderViewModel.ProfileInfo, diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift index 12925ca4..d9e52a8c 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift @@ -36,7 +36,7 @@ final class UserTimelineViewController: UIViewController, NeedsDependency, Media let cellFrameCache = NSCache() deinit { - os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) } } diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index db50565a..f470082f 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -11,6 +11,7 @@ import Combine import SafariServices import MastodonAsset import MastodonLocalization +import MastodonUI class MainTabBarController: UITabBarController { diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index 6568ab0c..7ac0f6e5 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -9,6 +9,7 @@ import os.log import UIKit import Combine import CoreDataStack +import MastodonUI protocol SidebarViewControllerDelegate: AnyObject { func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) diff --git a/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift b/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift index a43d65df..379cba70 100644 --- a/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift +++ b/Mastodon/Scene/Search/Search/Cell/TrendCollectionViewCell.swift @@ -9,45 +9,13 @@ import UIKit import Combine import MetaTextKit import MastodonAsset +import MastodonUI final class TrendCollectionViewCell: UICollectionViewCell { var _disposeBag = Set() - let container: UIStackView = { - let stackView = UIStackView() - stackView.axis = .horizontal - stackView.spacing = 16 - return stackView - }() - - let infoContainer: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - return stackView - }() - - let lineChartContainer: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - return stackView - }() - - let primaryLabel: UILabel = { - let label = UILabel() - label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) - label.textColor = Asset.Colors.Label.primary.color - return label - }() - - let secondaryLabel: UILabel = { - let label = UILabel() - label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) - label.textColor = Asset.Colors.Label.secondary.color - return label - }() - - let lineChartView = LineChartView() + let trendView = TrendView() override func prepareForReuse() { super.prepareForReuse() @@ -77,44 +45,13 @@ extension TrendCollectionViewCell { } .store(in: &_disposeBag) - container.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(container) + trendView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(trendView) NSLayoutConstraint.activate([ - container.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11), - container.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - container.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 11), - ]) - - container.layoutMargins = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) - container.isLayoutMarginsRelativeArrangement = true - - // container: H - [ info container | padding | line chart container ] - container.addArrangedSubview(infoContainer) - - // info container: V - [ primary | secondary ] - infoContainer.addArrangedSubview(primaryLabel) - infoContainer.addArrangedSubview(secondaryLabel) - - // padding - let padding = UIView() - container.addArrangedSubview(padding) - - // line chart - container.addArrangedSubview(lineChartContainer) - - let lineChartViewTopPadding = UIView() - let lineChartViewBottomPadding = UIView() - lineChartViewTopPadding.translatesAutoresizingMaskIntoConstraints = false - lineChartViewBottomPadding.translatesAutoresizingMaskIntoConstraints = false - lineChartView.translatesAutoresizingMaskIntoConstraints = false - lineChartContainer.addArrangedSubview(lineChartViewTopPadding) - lineChartContainer.addArrangedSubview(lineChartView) - lineChartContainer.addArrangedSubview(lineChartViewBottomPadding) - NSLayoutConstraint.activate([ - lineChartView.widthAnchor.constraint(equalToConstant: 50), - lineChartView.heightAnchor.constraint(equalToConstant: 26), - lineChartViewTopPadding.heightAnchor.constraint(equalTo: lineChartViewBottomPadding.heightAnchor), + trendView.topAnchor.constraint(equalTo: contentView.topAnchor), + trendView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + trendView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + trendView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), ]) } diff --git a/Mastodon/Scene/Search/Search/SearchViewController.swift b/Mastodon/Scene/Search/Search/SearchViewController.swift index d1bed948..982844f5 100644 --- a/Mastodon/Scene/Search/Search/SearchViewController.swift +++ b/Mastodon/Scene/Search/Search/SearchViewController.swift @@ -15,7 +15,7 @@ import MastodonLocalization final class HeightFixedSearchBar: UISearchBar { override var intrinsicContentSize: CGSize { - return CGSize(width: CGFloat.greatestFiniteMagnitude, height: 44) + return CGSize(width: CGFloat.greatestFiniteMagnitude, height: 36) } } @@ -35,19 +35,26 @@ final class SearchViewController: UIViewController, NeedsDependency { // layout alongside with split mode button (on iPad) let titleViewContainer = UIView() let searchBar = HeightFixedSearchBar() - - let collectionView: UICollectionView = { - var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) - configuration.backgroundColor = .clear - configuration.headerMode = .supplementary - let layout = UICollectionViewCompositionalLayout.list(using: configuration) - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) - collectionView.backgroundColor = .clear - return collectionView - }() + +// let collectionView: UICollectionView = { +// var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) +// configuration.backgroundColor = .clear +// configuration.headerMode = .supplementary +// let layout = UICollectionViewCompositionalLayout.list(using: configuration) +// let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) +// collectionView.backgroundColor = .clear +// return collectionView +// }() let searchBarTapPublisher = PassthroughSubject() + private(set) lazy var discoveryViewController: DiscoveryViewController = { + let viewController = DiscoveryViewController() + viewController.context = context + viewController.coordinator = coordinator + return viewController + }() + deinit { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) } @@ -71,19 +78,31 @@ extension SearchViewController { setupSearchBar() - collectionView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(collectionView) +// collectionView.translatesAutoresizingMaskIntoConstraints = false +// view.addSubview(collectionView) +// NSLayoutConstraint.activate([ +// collectionView.topAnchor.constraint(equalTo: view.topAnchor), +// collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), +// collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), +// collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), +// ]) +// +// collectionView.delegate = self +// viewModel.setupDiffableDataSource( +// collectionView: collectionView +// ) + + addChild(discoveryViewController) + discoveryViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(discoveryViewController.view) NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: view.topAnchor), - collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + discoveryViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + discoveryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + discoveryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + discoveryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) - collectionView.delegate = self - viewModel.setupDiffableDataSource( - collectionView: collectionView - ) +// discoveryViewController.view.isHidden = true } override func viewDidAppear(_ animated: Bool) { @@ -113,7 +132,10 @@ extension SearchViewController { searchBar.trailingAnchor.constraint(equalTo: titleViewContainer.trailingAnchor), searchBar.bottomAnchor.constraint(equalTo: titleViewContainer.bottomAnchor), ]) + searchBar.setContentHuggingPriority(.required, for: .horizontal) + searchBar.setContentHuggingPriority(.required, for: .vertical) navigationItem.titleView = titleViewContainer +// navigationItem.titleView = searchBar searchBarTapPublisher .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false) @@ -123,7 +145,10 @@ extension SearchViewController { let searchDetailViewModel = SearchDetailViewModel() searchDetailViewModel.needsBecomeFirstResponder = true self.navigationController?.delegate = self.searchTransitionController - self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush) + // FIXME: + // use `.customPush(animated: false)` false to disable navigation bar animation for searchBar layout + // but that should be a fade transition whe fixed size searchBar + self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush(animated: false)) } .store(in: &disposeBag) } @@ -151,21 +176,21 @@ extension SearchViewController: UISearchControllerDelegate { } // MARK: - UICollectionViewDelegate -extension SearchViewController: UICollectionViewDelegate { - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select item at: \(indexPath.debugDescription)") - - defer { - collectionView.deselectItem(at: indexPath, animated: true) - } - - guard let diffableDataSource = viewModel.diffableDataSource else { return } - guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } - - switch item { - case .trend(let hashtag): - let viewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag.name) - coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: self, transition: .show) - } - } -} +//extension SearchViewController: UICollectionViewDelegate { +// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { +// logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select item at: \(indexPath.debugDescription)") +// +// defer { +// collectionView.deselectItem(at: indexPath, animated: true) +// } +// +// guard let diffableDataSource = viewModel.diffableDataSource else { return } +// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } +// +// switch item { +// case .trend(let hashtag): +// let viewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag.name) +// coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: self, transition: .show) +// } +// } +//} diff --git a/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift b/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift index ca741b7f..3f448289 100644 --- a/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift +++ b/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift @@ -8,35 +8,35 @@ import UIKit import MastodonSDK -extension SearchViewModel { - - func setupDiffableDataSource( - collectionView: UICollectionView - ) { - diffableDataSource = SearchSection.diffableDataSource( - collectionView: collectionView, - context: context - ) - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.trend]) - diffableDataSource?.apply(snapshot) - - $hashtags - .receive(on: DispatchQueue.main) - .sink { [weak self] hashtags in - guard let self = self else { return } - guard let diffableDataSource = self.diffableDataSource else { return } - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.trend]) - - let trendItems = hashtags.map { SearchItem.trend($0) } - snapshot.appendItems(trendItems, toSection: .trend) - - diffableDataSource.apply(snapshot) - } - .store(in: &disposeBag) - } - -} +//extension SearchViewModel { +// +// func setupDiffableDataSource( +// collectionView: UICollectionView +// ) { +// diffableDataSource = SearchSection.diffableDataSource( +// collectionView: collectionView, +// context: context +// ) +// +// var snapshot = NSDiffableDataSourceSnapshot() +// snapshot.appendSections([.trend]) +// diffableDataSource?.apply(snapshot) +// +// $hashtags +// .receive(on: DispatchQueue.main) +// .sink { [weak self] hashtags in +// guard let self = self else { return } +// guard let diffableDataSource = self.diffableDataSource else { return } +// +// var snapshot = NSDiffableDataSourceSnapshot() +// snapshot.appendSections([.trend]) +// +// let trendItems = hashtags.map { SearchItem.trend($0) } +// snapshot.appendItems(trendItems, toSection: .trend) +// +// diffableDataSource.apply(snapshot) +// } +// .store(in: &disposeBag) +// } +// +//} diff --git a/Mastodon/Scene/Search/Search/SearchViewModel.swift b/Mastodon/Scene/Search/Search/SearchViewModel.swift index 2776713d..b47bc2e8 100644 --- a/Mastodon/Scene/Search/Search/SearchViewModel.swift +++ b/Mastodon/Scene/Search/Search/SearchViewModel.swift @@ -29,31 +29,31 @@ final class SearchViewModel: NSObject { self.context = context super.init() - Publishers.CombineLatest( - context.authenticationService.activeMastodonAuthenticationBox, - viewDidAppeared - ) - .compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in - return authenticationBox - } - .throttle(for: 3, scheduler: DispatchQueue.main, latest: true) - .asyncMap { authenticationBox in - try await context.apiService.trends(domain: authenticationBox.domain, query: nil) - } - .retry(3) - .map { response in Result, Error> { response } } - .catch { error in Just(Result, Error> { throw error }) } - .receive(on: DispatchQueue.main) - .sink { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let response): - self.hashtags = response.value - case .failure: - break - } - } - .store(in: &disposeBag) +// Publishers.CombineLatest( +// context.authenticationService.activeMastodonAuthenticationBox, +// viewDidAppeared +// ) +// .compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in +// return authenticationBox +// } +// .throttle(for: 3, scheduler: DispatchQueue.main, latest: true) +// .asyncMap { authenticationBox in +// try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil) +// } +// .retry(3) +// .map { response in Result, Error> { response } } +// .catch { error in Just(Result, Error> { throw error }) } +// .receive(on: DispatchQueue.main) +// .sink { [weak self] result in +// guard let self = self else { return } +// switch result { +// case .success(let response): +// self.hashtags = response.value +// case .failure: +// break +// } +// } +// .store(in: &disposeBag) } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift index 5e143a33..ecc1c0c0 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift @@ -12,6 +12,14 @@ import Pageboy import MastodonAsset import MastodonLocalization +final class CustomSearchController: UISearchController { + + let customSearchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: 300, height: 100)) + + override var searchBar: UISearchBar { customSearchBar } + +} + // Fake search bar not works on iPad with UISplitViewController // check device and fallback to standard UISearchController final class SearchDetailViewController: PageboyViewController, NeedsDependency { @@ -48,8 +56,8 @@ final class SearchDetailViewController: PageboyViewController, NeedsDependency { return navigationBar }() - let searchController: UISearchController = { - let searchController = UISearchController() + let searchController: CustomSearchController = { + let searchController = CustomSearchController() searchController.automaticallyShowsScopeBar = false searchController.dimsBackgroundDuringPresentation = false return searchController @@ -235,11 +243,17 @@ extension SearchDetailViewController { if isPhoneDevice { searchBar.setShowsCancelButton(true, animated: animated) - searchBar.becomeFirstResponder() + UIView.performWithoutAnimation { + self.searchBar.becomeFirstResponder() + } } else { - searchController.isActive = true - DispatchQueue.main.asyncAfter(deadline: .now() + 0.33) { - self.searchController.searchBar.becomeFirstResponder() + searchController.searchBar.setShowsCancelButton(true, animated: false) + searchController.searchBar.setShowsScope(true, animated: false) + UIView.performWithoutAnimation { + self.searchController.isActive = true + } + DispatchQueue.main.async { + self.searchController.searchBar.becomeFirstResponder() } } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/View/SearchHistoryTableHeaderView.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/View/SearchHistoryTableHeaderView.swift index 8ac661b1..fc41bdf2 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/View/SearchHistoryTableHeaderView.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/View/SearchHistoryTableHeaderView.swift @@ -10,6 +10,7 @@ import UIKit import Combine import MastodonAsset import MastodonLocalization +import MastodonUI protocol SearchHistoryTableHeaderViewDelegate: AnyObject { func searchHistoryTableHeaderView(_ searchHistoryTableHeaderView: SearchHistoryTableHeaderView, clearSearchHistoryButtonDidPressed button: UIButton) diff --git a/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift b/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift index 3760fd8e..44b6b39c 100644 --- a/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift +++ b/Mastodon/Scene/Settings/Cell/SettingsAppearanceTableViewCell.swift @@ -85,6 +85,9 @@ class SettingsAppearanceTableViewCell: UITableViewCell { subview.removeFromSuperview() } } + + // remove grouped style table corner radius + layer.cornerRadius = 0 } } diff --git a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift index 78c5462f..8300f865 100644 --- a/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift +++ b/Mastodon/Scene/Share/View/Content/ContentWarningOverlayView.swift @@ -11,6 +11,7 @@ import Combine import UIKit import MastodonAsset import MastodonLocalization +import MastodonUI protocol ContentWarningOverlayViewDelegate: AnyObject { func contentWarningOverlayViewDidPressed(_ contentWarningOverlayView: ContentWarningOverlayView) diff --git a/Mastodon/Scene/Share/View/Content/MediaView+Configuration.swift b/Mastodon/Scene/Share/View/Content/MediaView+Configuration.swift index ad2fa398..02f9ad5a 100644 --- a/Mastodon/Scene/Share/View/Content/MediaView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/MediaView+Configuration.swift @@ -58,18 +58,18 @@ extension MediaView { }() if let previewURL = configuration.previewURL, - let url = URL(string: previewURL) + let url = URL(string: previewURL) { let placeholder = UIImage.placeholder(color: .systemGray6) let request = URLRequest(url: url) - ImageDownloader.default.download(request) { response in + ImageDownloader.default.download(request, completion: { response in switch response.result { case .success(let image): configuration.previewImage = image - case .failure(let error): + case .failure: configuration.previewImage = placeholder } - } + }) } if let assetURL = configuration.assetURL, @@ -84,7 +84,7 @@ extension MediaView { .store(in: &configuration.blurhashImageDisposeBag) } - configuration.isReveal = status.sensitive ? status.isMediaSensitiveToggled : true + configuration.isReveal = status.isMediaSensitive ? status.isSensitiveToggled : true return configuration } diff --git a/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift b/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift index 1a90c69a..ab4fea1a 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift @@ -156,7 +156,6 @@ extension StatusView { .map { _ in author.avatarImageURL() } .assign(to: \.authorAvatarImageURL, on: viewModel) .store(in: &disposeBag) - // author name Publishers.CombineLatest( author.publisher(for: \.displayName), @@ -268,25 +267,19 @@ extension StatusView { .assign(to: \.visibility, on: viewModel) .store(in: &disposeBag) // sensitive - status.publisher(for: \.isContentSensitiveToggled) - .assign(to: \.isContentSensitiveToggled, on: viewModel) + viewModel.isContentSensitive = status.isContentSensitive + status.publisher(for: \.isSensitiveToggled) + .assign(to: \.isSensitiveToggled, on: viewModel) .store(in: &disposeBag) - - -// viewModel.source = status.source } private func configureMedia(status: Status) { let status = status.reblog ?? status - viewModel.isMediaSensitive = status.sensitive && !status.attachments.isEmpty // some servers set media sensitive even empty attachments + viewModel.isMediaSensitive = status.isMediaSensitive let configurations = MediaView.configuration(status: status) viewModel.mediaViewConfigurations = configurations - - status.publisher(for: \.isMediaSensitiveToggled) - .assign(to: \.isMediaSensitiveToggled, on: viewModel) - .store(in: &disposeBag) } private func configurePoll(status: Status) { diff --git a/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift b/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift index c339654f..0953feec 100644 --- a/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift +++ b/Mastodon/Scene/Share/View/Content/ThreadMetaView.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonUI final class ThreadMetaView: UIView { diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index a1033f05..a3315211 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -61,7 +61,7 @@ extension StatusTableViewCell { statusView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), containerViewLeadingLayoutConstraint, containerViewTrailingLayoutConstraint, - statusView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + contentView.bottomAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 10), ]) statusView.setup(style: .inline) updateContainerViewMarginConstraints() diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift index e27cc2dd..11517580 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift @@ -112,7 +112,7 @@ extension StatusThreadRootTableViewCell { statusView.statusMetricView ] - if !statusView.viewModel.isSensitive { + if !statusView.viewModel.isMediaSensitive { elements.removeAll(where: { $0 === statusView.contentSensitiveeToggleButton }) } diff --git a/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift b/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift index f06d04d9..e060acc8 100644 --- a/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift +++ b/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift @@ -46,14 +46,17 @@ extension SearchToSearchDetailViewControllerAnimatedTransitioning { let toViewEndFrame = transitionContext.finalFrame(for: toVC) transitionContext.containerView.addSubview(toView) toView.frame = toViewEndFrame + toView.setNeedsLayout() + toView.layoutIfNeeded() + toVC.searchBar.setNeedsLayout() + toVC.searchBar.layoutIfNeeded() toView.alpha = 0 let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: curve) animator.addAnimations { - + toView.alpha = 1 } animator.addCompletion { position in - toView.alpha = 1 transitionContext.completeTransition(true) } return animator diff --git a/Mastodon/Service/APIService/APIService+Recommend.swift b/Mastodon/Service/APIService/APIService+Recommend.swift index cb195b60..6e457ad0 100644 --- a/Mastodon/Service/APIService/APIService+Recommend.swift +++ b/Mastodon/Service/APIService/APIService+Recommend.swift @@ -13,12 +13,13 @@ import CoreDataStack import OSLog extension APIService { + func suggestionAccount( query: Mastodon.API.Suggestions.Query?, authenticationBox: MastodonAuthenticationBox ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> { - let response = try await Mastodon.API.Suggestions.get( + let response = try await Mastodon.API.Suggestions.accounts( session: session, domain: authenticationBox.domain, query: query, @@ -47,7 +48,7 @@ extension APIService { query: Mastodon.API.Suggestions.Query?, authenticationBox: MastodonAuthenticationBox ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.V2.SuggestionAccount]> { - let response = try await Mastodon.API.V2.Suggestions.get( + let response = try await Mastodon.API.V2.Suggestions.accounts( session: session, domain: authenticationBox.domain, query: query, diff --git a/Mastodon/Service/APIService/APIService+Trend.swift b/Mastodon/Service/APIService/APIService+Trend.swift index 0ce2a86a..47dda6bd 100644 --- a/Mastodon/Service/APIService/APIService+Trend.swift +++ b/Mastodon/Service/APIService/APIService+Trend.swift @@ -9,11 +9,12 @@ import Foundation import MastodonSDK extension APIService { - func trends( + + func trendHashtags( domain: String, - query: Mastodon.API.Trends.Query? + query: Mastodon.API.Trends.HashtagQuery? ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Tag]> { - let response = try await Mastodon.API.Trends.get( + let response = try await Mastodon.API.Trends.hashtags( session: session, domain: domain, query: query @@ -21,4 +22,48 @@ extension APIService { return response } + + func trendStatuses( + domain: String, + query: Mastodon.API.Trends.StatusQuery + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Status]> { + let response = try await Mastodon.API.Trends.statuses( + session: session, + domain: domain, + query: query + ).singleOutput() + + let managedObjectContext = backgroundManagedObjectContext + try await managedObjectContext.performChanges { + for entity in response.value { + _ = Persistence.Status.createOrMerge( + in: managedObjectContext, + context: Persistence.Status.PersistContext( + domain: domain, + entity: entity, + me: nil, + statusCache: nil, + userCache: nil, + networkDate: response.networkDate + ) + ) + } // end for … in + } + + return response + } + + func trendLinks( + domain: String, + query: Mastodon.API.Trends.LinkQuery + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Link]> { + let response = try await Mastodon.API.Trends.links( + session: session, + domain: domain, + query: query + ).singleOutput() + + return response + } + } diff --git a/Mastodon/Service/SettingService.swift b/Mastodon/Service/SettingService.swift index 1e8022c5..bd571d8f 100644 --- a/Mastodon/Service/SettingService.swift +++ b/Mastodon/Service/SettingService.swift @@ -12,6 +12,7 @@ import CoreDataStack import MastodonSDK import MastodonAsset import MastodonLocalization +import MastodonCommon final class SettingService { @@ -190,18 +191,6 @@ extension SettingService { extension SettingService { static func updatePreference(setting: Setting) { - // set appearance -// let userInterfaceStyle: UIUserInterfaceStyle = { -// switch setting.appearance { -// case .automatic: return .unspecified -// case .light: return .light -// case .dark: return .dark -// } -// }() -// if UserDefaults.shared.customUserInterfaceStyle != userInterfaceStyle { -// UserDefaults.shared.customUserInterfaceStyle = userInterfaceStyle -// } - // set theme let themeName: ThemeName = setting.preferredTrueBlackDarkMode ? .system : .mastodon if UserDefaults.shared.currentThemeNameRawValue != themeName.rawValue { @@ -223,6 +212,6 @@ extension SettingService { if UserDefaults.shared.preferredUsingDefaultBrowser != setting.preferredUsingDefaultBrowser { UserDefaults.shared.preferredUsingDefaultBrowser = setting.preferredUsingDefaultBrowser } - } + } diff --git a/MastodonIntent/Info.plist b/MastodonIntent/Info.plist index 02385f4e..0d11fe1f 100644 --- a/MastodonIntent/Info.plist +++ b/MastodonIntent/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.3.1 CFBundleVersion - 109 + 113 NSExtension NSExtensionAttributes diff --git a/MastodonIntent/ar.lproj/Intents.strings b/MastodonIntent/ar.lproj/Intents.strings index cde27dc9..49183a43 100644 --- a/MastodonIntent/ar.lproj/Intents.strings +++ b/MastodonIntent/ar.lproj/Intents.strings @@ -1,4 +1,4 @@ -"16wxgf" = "النَشر على ماستودون"; +"16wxgf" = "النَّشرُ عَلَى مَاستودُون"; "751xkl" = "محتوى نصي"; @@ -14,7 +14,7 @@ "RxSqsb" = "مَنشور"; -"WCIR3D" = "نَشر ${content} على ماستودون"; +"WCIR3D" = "نَشرُ ${content} عَلَى مَاستودُون"; "ZKJSNu" = "مَنشور"; @@ -32,9 +32,9 @@ "ayoYEb-ehFLjY" = "${content}، المُتابِعُون فقط"; -"dUyuGg" = "النشر على ماستدون"; +"dUyuGg" = "النَّشرُ عَلَى مَاستودُون"; -"dYQ5NN" = "للعامة"; +"dYQ5NN" = "لِلعَامَّة"; "ehFLjY" = "لمتابعيك فقط"; diff --git a/MastodonIntent/kab.lproj/Intents.strings b/MastodonIntent/kab.lproj/Intents.strings new file mode 100644 index 00000000..532c822f --- /dev/null +++ b/MastodonIntent/kab.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Asuffeɣ deg Matodon"; + +"751xkl" = "Agbur n uḍris"; + +"CsR7G2" = "Asuffeɣ deg Matodon"; + +"HZSGTr" = "Anwa agbur ara d-yettwasuffɣen?"; + +"HdGikU" = "Yecceḍ usuffeɣ"; + +"KDNTJ4" = "Ssebba n tuccḍa"; + +"RHxKOw" = "Azen tasuffeɣt s ugbur n uḍris"; + +"RxSqsb" = "Tasuffeɣt"; + +"WCIR3D" = "Suffeɣ ${content} deg Mastodon"; + +"ZKJSNu" = "Tasuffeɣt"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Abani"; + +"Zo4jgJ" = "Abani n tsuffeɣt"; + +"apSxMG-dYQ5NN" = "Yella ${count} n textiṛiyin yemṣadan d 'Uzayaz'."; + +"apSxMG-ehFLjY" = "Yella ${count} n textiṛiyin yemṣadan d 'Yineḍfaren kan'."; + +"ayoYEb-dYQ5NN" = "${content}, azayaz"; + +"ayoYEb-ehFLjY" = "${content}, ineḍfaren kan"; + +"dUyuGg" = "Asuffeɣ deg Maṣṭudun"; + +"dYQ5NN" = "Azayez"; + +"ehFLjY" = "Imeḍfaṛen kan"; + +"gfePDu" = "Asuffeɣ yecceḍ. ${failureReason}"; + +"k7dbKQ" = "Tasuffeɣt tettwazen akken iwata."; + +"oGiqmY-dYQ5NN" = "I usentem kan, tebɣiḍ 'Azayaz'?"; + +"oGiqmY-ehFLjY" = "I usentem kan, tebɣiḍ 'Ineḍfaren kan'?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Tasuffeɣt tettwazen akken iwata. "; diff --git a/MastodonIntent/kab.lproj/Intents.stringsdict b/MastodonIntent/kab.lproj/Intents.stringsdict new file mode 100644 index 00000000..5a39d5e6 --- /dev/null +++ b/MastodonIntent/kab.lproj/Intents.stringsdict @@ -0,0 +1,54 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${content}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${visibility}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + + diff --git a/MastodonIntent/vi.lproj/Intents.strings b/MastodonIntent/vi.lproj/Intents.strings new file mode 100644 index 00000000..a9533731 --- /dev/null +++ b/MastodonIntent/vi.lproj/Intents.strings @@ -0,0 +1,51 @@ +"16wxgf" = "Đăng lên Mastodon"; + +"751xkl" = "Nội dung văn bản"; + +"CsR7G2" = "Đăng lên Mastodon"; + +"HZSGTr" = "Đăng loại nội dung nào?"; + +"HdGikU" = "Không thể đăng"; + +"KDNTJ4" = "Lý do không thể đăng"; + +"RHxKOw" = "Gửi tút với nội dung là chữ"; + +"RxSqsb" = "Tút"; + +"WCIR3D" = "Đăng ${content} lên Mastodon"; + +"ZKJSNu" = "Tút"; + +"ZS1XaK" = "${content}"; + +"ZbSjzC" = "Hiển thị"; + +"Zo4jgJ" = "Thay đổi quyền riêng tư"; + +"apSxMG-dYQ5NN" = "Có ${count} lựa chọn khớp với ‘Công khai’."; + +"apSxMG-ehFLjY" = "Có ${count} lựa chọn khớp với ‘Riêng tư’."; + +"ayoYEb-dYQ5NN" = "${content}, Công khai"; + +"ayoYEb-ehFLjY" = "${content}, Riêng tư"; + +"dUyuGg" = "Đăng lên Mastodon"; + +"dYQ5NN" = "Công khai"; + +"ehFLjY" = "Riêng tư"; + +"gfePDu" = "Không thể đăng. ${failureReason}"; + +"k7dbKQ" = "Đã đăng tút thành công."; + +"oGiqmY-dYQ5NN" = "Xin xác nhận, bạn muốn ‘Công khai’?"; + +"oGiqmY-ehFLjY" = "Xin xác nhận, bạn muốn ‘Riêng tư’?"; + +"rM6dvp" = "URL"; + +"ryJLwG" = "Đã đăng tút thành công. "; diff --git a/MastodonIntent/vi.lproj/Intents.stringsdict b/MastodonIntent/vi.lproj/Intents.stringsdict new file mode 100644 index 00000000..5a39d5e6 --- /dev/null +++ b/MastodonIntent/vi.lproj/Intents.stringsdict @@ -0,0 +1,54 @@ + + + + + There are ${count} options matching ‘${content}’. - 2 + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${content}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + There are ${count} options matching ‘${visibility}’. + + NSStringLocalizedFormatKey + There are %#@count_option@ matching ‘${visibility}’. + count_option + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + %ld + zero + 0 options + one + 1 option + two + 2 options + few + %ld options + many + %ld options + other + %ld options + + + + diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift index af27091f..8b007c2a 100644 --- a/MastodonSDK/Package.swift +++ b/MastodonSDK/Package.swift @@ -32,12 +32,13 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"), .package(url: "https://github.com/kean/Nuke.git", from: "10.3.1"), .package(url: "https://github.com/Flipboard/FLAnimatedImage.git", from: "1.0.0"), - .package(url: "https://github.com/TwidereProject/MetaTextKit.git", .exact("2.2.1")), + .package(url: "https://github.com/TwidereProject/MetaTextKit.git", .exact("2.2.2")), .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.4.0"), .package(url: "https://github.com/Alamofire/AlamofireImage.git", from: "4.1.0"), .package(name: "NukeFLAnimatedImagePlugin", url: "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", from: "8.0.0"), .package(name: "UITextView+Placeholder", url: "https://github.com/MainasuK/UITextView-Placeholder.git", from: "1.4.1"), - .package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.3") + .package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.3"), + .package(name: "FaviconFinder", url: "https://github.com/will-lumley/FaviconFinder.git", from: "3.2.2"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -60,7 +61,9 @@ let package = Package( ), .target( name: "MastodonCommon", - dependencies: [] + dependencies: [ + "MastodonExtension" + ] ), .target( name: "MastodonExtension", @@ -93,6 +96,7 @@ let package = Package( .product(name: "AlamofireImage", package: "AlamofireImage"), .product(name: "MetaTextKit", package: "MetaTextKit"), .product(name: "FLAnimatedImage", package: "FLAnimatedImage"), + .product(name: "FaviconFinder", package: "FaviconFinder"), ] ), .testTarget( diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents index a6f0ee0c..16a1c7c8 100644 --- a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents +++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 3.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -185,8 +185,7 @@ - - + @@ -262,7 +261,7 @@ - + diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift index d17d1c61..0c729191 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift @@ -42,9 +42,7 @@ public final class Status: NSManagedObject { @NSManaged public private(set) var spoilerText: String? // sourcery: autoUpdatableObject - @NSManaged public private(set) var isContentSensitiveToggled: Bool - // sourcery: autoUpdatableObject - @NSManaged public private(set) var isMediaSensitiveToggled: Bool + @NSManaged public private(set) var isSensitiveToggled: Bool @NSManaged public private(set) var application: Application? @@ -432,14 +430,9 @@ extension Status: AutoUpdatableObject { self.spoilerText = spoilerText } } - public func update(isContentSensitiveToggled: Bool) { - if self.isContentSensitiveToggled != isContentSensitiveToggled { - self.isContentSensitiveToggled = isContentSensitiveToggled - } - } - public func update(isMediaSensitiveToggled: Bool) { - if self.isMediaSensitiveToggled != isMediaSensitiveToggled { - self.isMediaSensitiveToggled = isMediaSensitiveToggled + public func update(isSensitiveToggled: Bool) { + if self.isSensitiveToggled != isSensitiveToggled { + self.isSensitiveToggled = isSensitiveToggled } } public func update(reblogsCount: Int64) { diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/primary.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/primary.colorset/Contents.json index a36ab82c..0c0c8af0 100644 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/primary.colorset/Contents.json +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/primary.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.216", - "green" : "0.173", - "red" : "0.157" + "blue" : "55", + "green" : "44", + "red" : "40" } }, "idiom" : "universal" diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json index cd123376..b23080b6 100644 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Label/secondary.colorset/Contents.json @@ -4,10 +4,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "0.600", - "blue" : "67", - "green" : "60", - "red" : "60" + "alpha" : "1.000", + "blue" : "133", + "green" : "112", + "red" : "102" } }, "idiom" : "universal" diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/Contents.json new file mode 100644 index 00000000..6e965652 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json new file mode 100644 index 00000000..1fc21a87 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "251", + "green" : "250", + "red" : "249" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "4", + "green" : "5", + "red" : "6" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift index 26e54900..3e7fa5c1 100644 --- a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift +++ b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift @@ -103,6 +103,9 @@ public enum Asset { public static let star = ImageAsset(name: "ObjectsAndTools/star") } public enum Scene { + public enum Discovery { + public static let profileCardBackground = ColorAsset(name: "Scene/Discovery/profile.card.background") + } public enum Onboarding { public static let avatarPlaceholder = ImageAsset(name: "Scene/Onboarding/avatar.placeholder") public static let background = ColorAsset(name: "Scene/Onboarding/background") diff --git a/Mastodon/Preference/AppearancePreference.swift b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Appearance.swift similarity index 81% rename from Mastodon/Preference/AppearancePreference.swift rename to MastodonSDK/Sources/MastodonCommon/Preference/Preference+Appearance.swift index 034bf965..713c926b 100644 --- a/Mastodon/Preference/AppearancePreference.swift +++ b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Appearance.swift @@ -9,7 +9,7 @@ import UIKit extension UserDefaults { - @objc dynamic var customUserInterfaceStyle: UIUserInterfaceStyle { + @objc public dynamic var customUserInterfaceStyle: UIUserInterfaceStyle { get { register(defaults: [#function: UIUserInterfaceStyle.unspecified.rawValue]) return UIUserInterfaceStyle(rawValue: integer(forKey: #function)) ?? .unspecified @@ -17,7 +17,7 @@ extension UserDefaults { set { self[#function] = newValue.rawValue } } - @objc dynamic var preferredStaticAvatar: Bool { + @objc public dynamic var preferredStaticAvatar: Bool { get { // default false // without set register to profile timeline performance @@ -26,7 +26,7 @@ extension UserDefaults { set { self[#function] = newValue } } - @objc dynamic var preferredStaticEmoji: Bool { + @objc public dynamic var preferredStaticEmoji: Bool { get { // default false // without set register to profile timeline performance diff --git a/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift new file mode 100644 index 00000000..0c6a9c54 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift @@ -0,0 +1,19 @@ +// +// Preference+Discovery.swift +// +// +// Created by MainasuK on 2022-4-19. +// + +import Foundation + +extension UserDefaults { + + @objc public dynamic var discoveryIntroBannerNeedsHidden: Bool { + get { + return bool(forKey: #function) + } + set { self[#function] = newValue } + } + +} diff --git a/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Theme.swift b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Theme.swift new file mode 100644 index 00000000..a87a8da7 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Theme.swift @@ -0,0 +1,26 @@ +// +// Preference+Theme.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit +import MastodonExtension + +public enum ThemeName: String, CaseIterable { + case system + case mastodon +} + +extension UserDefaults { + + @objc public dynamic var currentThemeNameRawValue: String { + get { + register(defaults: [#function: ThemeName.mastodon.rawValue]) + return string(forKey: #function) ?? ThemeName.mastodon.rawValue + } + set { self[#function] = newValue } + } + +} diff --git a/MastodonSDK/Sources/MastodonExtension/UIView.swift b/MastodonSDK/Sources/MastodonExtension/UIView.swift index 5466c464..f96d1618 100644 --- a/MastodonSDK/Sources/MastodonExtension/UIView.swift +++ b/MastodonSDK/Sources/MastodonExtension/UIView.swift @@ -12,3 +12,37 @@ extension UIView { return UIScreen.main.scale != UIScreen.main.nativeScale } } + +extension UIView { + @discardableResult + public func applyCornerRadius(radius: CGFloat) -> Self { + layer.masksToBounds = true + layer.cornerRadius = radius + layer.cornerCurve = .continuous + return self + } + + @discardableResult + public func applyShadow( + color: UIColor, + alpha: Float, + x: CGFloat, + y: CGFloat, + blur: CGFloat, + spread: CGFloat = 0 + ) -> Self { + layer.masksToBounds = false + layer.shadowColor = color.cgColor + layer.shadowOpacity = alpha + layer.shadowOffset = CGSize(width: x, height: y) + layer.shadowRadius = blur / 2.0 + if spread == 0 { + layer.shadowPath = nil + } else { + let dx = -spread + let rect = bounds.insetBy(dx: dx, dy: dx) + layer.shadowPath = UIBezierPath(rect: rect).cgPath + } + return self + } +} diff --git a/MastodonSDK/Sources/MastodonExtension/UInt64.swift b/MastodonSDK/Sources/MastodonExtension/UInt64.swift new file mode 100644 index 00000000..03dfbaca --- /dev/null +++ b/MastodonSDK/Sources/MastodonExtension/UInt64.swift @@ -0,0 +1,12 @@ +// +// UInt64.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation + +extension UInt64 { + public static let second: UInt64 = 1_000_000_000 +} diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 69e15f2d..f2bec6b0 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -274,6 +274,8 @@ public enum L10n { public static let showPost = L10n.tr("Localizable", "Common.Controls.Status.ShowPost") /// Show user profile public static let showUserProfile = L10n.tr("Localizable", "Common.Controls.Status.ShowUserProfile") + /// Tap to reveal + public static let tapToReveal = L10n.tr("Localizable", "Common.Controls.Status.TapToReveal") /// %@ reblogged public static func userReblogged(_ p1: Any) -> String { return L10n.tr("Localizable", "Common.Controls.Status.UserReblogged", String(describing: p1)) @@ -293,6 +295,14 @@ public enum L10n { public static let reblog = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reblog") /// Reply public static let reply = L10n.tr("Localizable", "Common.Controls.Status.Actions.Reply") + /// Show GIF + public static let showGif = L10n.tr("Localizable", "Common.Controls.Status.Actions.ShowGif") + /// Show image + public static let showImage = L10n.tr("Localizable", "Common.Controls.Status.Actions.ShowImage") + /// Show video player + public static let showVideoPlayer = L10n.tr("Localizable", "Common.Controls.Status.Actions.ShowVideoPlayer") + /// Tap then hold to show menu + public static let tapThenHoldToShowMenu = L10n.tr("Localizable", "Common.Controls.Status.Actions.TapThenHoldToShowMenu") /// Unfavorite public static let unfavorite = L10n.tr("Localizable", "Common.Controls.Status.Actions.Unfavorite") /// Undo reblog @@ -533,6 +543,18 @@ public enum L10n { public static let title = L10n.tr("Localizable", "Scene.ConfirmEmail.OpenEmailApp.Title") } } + public enum Discovery { + public enum Tabs { + /// For You + public static let forYou = L10n.tr("Localizable", "Scene.Discovery.Tabs.ForYou") + /// Hashtags + public static let hashtags = L10n.tr("Localizable", "Scene.Discovery.Tabs.Hashtags") + /// News + public static let news = L10n.tr("Localizable", "Scene.Discovery.Tabs.News") + /// Posts + public static let posts = L10n.tr("Localizable", "Scene.Discovery.Tabs.Posts") + } + } public enum Favorite { /// Your Favorites public static let title = L10n.tr("Localizable", "Scene.Favorite.Title") @@ -598,6 +620,16 @@ public enum L10n { } } public enum Profile { + public enum Accessibility { + /// Double tap to open the list + public static let doubleTapToOpenTheList = L10n.tr("Localizable", "Scene.Profile.Accessibility.DoubleTapToOpenTheList") + /// Edit avatar image + public static let editAvatarImage = L10n.tr("Localizable", "Scene.Profile.Accessibility.EditAvatarImage") + /// Show avatar image + public static let showAvatarImage = L10n.tr("Localizable", "Scene.Profile.Accessibility.ShowAvatarImage") + /// Show banner image + public static let showBannerImage = L10n.tr("Localizable", "Scene.Profile.Accessibility.ShowBannerImage") + } public enum Dashboard { /// followers public static let followers = L10n.tr("Localizable", "Scene.Profile.Dashboard.Followers") @@ -1014,6 +1046,8 @@ public enum L10n { public static let disableAvatarAnimation = L10n.tr("Localizable", "Scene.Settings.Section.Preference.DisableAvatarAnimation") /// Disable animated emojis public static let disableEmojiAnimation = L10n.tr("Localizable", "Scene.Settings.Section.Preference.DisableEmojiAnimation") + /// Open links in Mastodon + public static let openLinksInMastodon = L10n.tr("Localizable", "Scene.Settings.Section.Preference.OpenLinksInMastodon") /// Preferences public static let title = L10n.tr("Localizable", "Scene.Settings.Section.Preference.Title") /// True black dark mode @@ -1185,6 +1219,10 @@ public enum L10n { public static func reblog(_ p1: Int) -> String { return L10n.tr("Localizable", "plural.count.reblog", p1) } + /// Plural format key: "%#@reply_count@" + public static func reply(_ p1: Int) -> String { + return L10n.tr("Localizable", "plural.count.reply", p1) + } /// Plural format key: "%#@vote_count@" public static func vote(_ p1: Int) -> String { return L10n.tr("Localizable", "plural.count.vote", p1) diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings index 668c7788..bf6ab09e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.strings @@ -1,7 +1,7 @@ "Common.Alerts.BlockDomain.BlockEntireDomain" = "حظر النِّطاق"; "Common.Alerts.BlockDomain.Title" = "هل أنتَ مُتأكِّدٌ حقًا مِن رغبتك في حظر %@ بالكامل؟ في معظم الحالات، يكون مِنَ الكافي والمُفَضَّل استهداف عدد محدود للحظر أو الكتم. لن ترى محتوى من هذا النطاق وسوف يُزال جميع متابعيك المتواجدين فيه."; -"Common.Alerts.CleanCache.Message" = "تمَّ مَحو %@ مِن ذاكرة التخزين المؤقت بنجاح."; -"Common.Alerts.CleanCache.Title" = "مَحو ذاكرة التخزين المؤقت"; +"Common.Alerts.CleanCache.Message" = "مُحِيَ ما مَساحَتُهُ %@ مِن ذاكِرَةِ التَّخزينِ المُؤقَّت بِنجاح."; +"Common.Alerts.CleanCache.Title" = "مَحوُ ذاكِرَةِ التَّخزينِ المُؤقَّت"; "Common.Alerts.Common.PleaseTryAgain" = "يُرجى المُحاولة مرة أُخرى."; "Common.Alerts.Common.PleaseTryAgainLater" = "يُرجى المُحاولة مرة أُخرى لاحقًا."; "Common.Alerts.DeletePost.Message" = "هَل أنتَ مُتأكِدٌ مِن رَغبتِكَ فِي حَذفِ هَذَا المَنشُور؟"; @@ -18,9 +18,9 @@ "Common.Alerts.SavePhotoFailure.Message" = "يُرجى إتاحة إذن الوصول إلى مكتبة الصور لحفظ الصورة."; "Common.Alerts.SavePhotoFailure.Title" = "إخفاق في حفظ الصورة"; "Common.Alerts.ServerError.Title" = "خطأ في الخادم"; -"Common.Alerts.SignOut.Confirm" = "تسجيل الخروج"; +"Common.Alerts.SignOut.Confirm" = "تَسجيلُ الخُروج"; "Common.Alerts.SignOut.Message" = "هل أنت متأكد من رغبتك في تسجيل الخُروج؟"; -"Common.Alerts.SignOut.Title" = "تسجيل الخروج"; +"Common.Alerts.SignOut.Title" = "تَسجيلُ الخُروج"; "Common.Alerts.SignUpFailure.Title" = "إخفاق في التسجيل"; "Common.Alerts.VoteFailure.PollEnded" = "انتهى استطلاع الرأي"; "Common.Alerts.VoteFailure.Title" = "إخفاق في التصويت"; @@ -35,7 +35,7 @@ "Common.Controls.Actions.Delete" = "حذف"; "Common.Controls.Actions.Discard" = "تجاهُل"; "Common.Controls.Actions.Done" = "تمّ"; -"Common.Controls.Actions.Edit" = "تحرير"; +"Common.Controls.Actions.Edit" = "تَحرير"; "Common.Controls.Actions.FindPeople" = "ابحث عن أشخاص لِمُتابعتهم"; "Common.Controls.Actions.ManuallySearch" = "البحث يدويًا بدلًا من ذلك"; "Common.Controls.Actions.Next" = "التالي"; @@ -47,14 +47,14 @@ "Common.Controls.Actions.Previous" = "السابق"; "Common.Controls.Actions.Remove" = "حذف"; "Common.Controls.Actions.Reply" = "الرَّد"; -"Common.Controls.Actions.ReportUser" = "الإبلاغ عن %@"; +"Common.Controls.Actions.ReportUser" = "الإبلاغُ عَن %@"; "Common.Controls.Actions.Save" = "حفظ"; "Common.Controls.Actions.SavePhoto" = "حفظ الصورة"; "Common.Controls.Actions.SeeMore" = "عرض المزيد"; "Common.Controls.Actions.Settings" = "الإعدادات"; "Common.Controls.Actions.Share" = "المُشارك"; "Common.Controls.Actions.SharePost" = "مشارك المنشور"; -"Common.Controls.Actions.ShareUser" = "مُشاركة %@"; +"Common.Controls.Actions.ShareUser" = "مُشارَكَةُ %@"; "Common.Controls.Actions.SignIn" = "تسجيل الدخول"; "Common.Controls.Actions.SignUp" = "إنشاء حِساب"; "Common.Controls.Actions.Skip" = "تخطي"; @@ -63,13 +63,13 @@ "Common.Controls.Actions.UnblockDomain" = "رفع الحظر عن %@"; "Common.Controls.Friendship.Block" = "حظر"; "Common.Controls.Friendship.BlockDomain" = "حظر %@"; -"Common.Controls.Friendship.BlockUser" = "حظر %@"; +"Common.Controls.Friendship.BlockUser" = "حَظرُ %@"; "Common.Controls.Friendship.Blocked" = "محظور"; -"Common.Controls.Friendship.EditInfo" = "تعديل المعلومات"; +"Common.Controls.Friendship.EditInfo" = "تَحريرُ المَعلُومات"; "Common.Controls.Friendship.Follow" = "مُتابَعَة"; "Common.Controls.Friendship.Following" = "مُتابَع"; "Common.Controls.Friendship.Mute" = "كَتم"; -"Common.Controls.Friendship.MuteUser" = "كَتم %@"; +"Common.Controls.Friendship.MuteUser" = "كَتمُ %@"; "Common.Controls.Friendship.Muted" = "مكتوم"; "Common.Controls.Friendship.Pending" = "قيد المُراجعة"; "Common.Controls.Friendship.Request" = "إرسال طَلَب"; @@ -79,7 +79,7 @@ "Common.Controls.Friendship.UnmuteUser" = "رفع الكتم عن %@"; "Common.Controls.Keyboard.Common.ComposeNewPost" = "تأليف منشور جديد"; "Common.Controls.Keyboard.Common.OpenSettings" = "فَتحُ الإعدادات"; -"Common.Controls.Keyboard.Common.ShowFavorites" = "إظهار المُفضَّلة"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "أظْهِر المُفضَّلة"; "Common.Controls.Keyboard.Common.SwitchToTab" = "التبديل إلى %@"; "Common.Controls.Keyboard.SegmentedControl.NextSection" = "القسم التالي"; "Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "القسم السابق"; @@ -98,21 +98,26 @@ "Common.Controls.Status.Actions.Menu" = "القائمة"; "Common.Controls.Status.Actions.Reblog" = "إعادة النشر"; "Common.Controls.Status.Actions.Reply" = "الرَّد"; +"Common.Controls.Status.Actions.ShowGif" = "أظْهِر GIF"; +"Common.Controls.Status.Actions.ShowImage" = "أظْهِرِ الصُّورَة"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "أظْهِر مُشَغِّلَ المَقاطِعِ المَرئِيَّة"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "اُنقُر مُطَوَّلًا لِإظْهَارِ القائِمَة"; "Common.Controls.Status.Actions.Unfavorite" = "إزالة التفضيل"; "Common.Controls.Status.Actions.Unreblog" = "التراجُع عن إعادة النشر"; "Common.Controls.Status.ContentWarning" = "تحذير المُحتوى"; -"Common.Controls.Status.MediaContentWarning" = "انقر للكشف"; +"Common.Controls.Status.MediaContentWarning" = "اُنقُر لِلكَشف"; "Common.Controls.Status.Poll.Closed" = "انتهى"; "Common.Controls.Status.Poll.Vote" = "صَوِّت"; -"Common.Controls.Status.ShowPost" = "إظهار منشور"; -"Common.Controls.Status.ShowUserProfile" = "إظهار الملف التعريفي للمُستخدِم"; -"Common.Controls.Status.Tag.Email" = "بريد إلكتروني"; +"Common.Controls.Status.ShowPost" = "أظْهِر مَنشور"; +"Common.Controls.Status.ShowUserProfile" = "أظْهِر المِلَفَّ التَّعريفِيَّ لِلمُستَخدِم"; +"Common.Controls.Status.Tag.Email" = "بَريدٌ إلِكتُرُونِيّ"; "Common.Controls.Status.Tag.Emoji" = "رمز تعبيري"; "Common.Controls.Status.Tag.Hashtag" = "وسم"; "Common.Controls.Status.Tag.Link" = "رابط"; "Common.Controls.Status.Tag.Mention" = "إشارة"; "Common.Controls.Status.Tag.Url" = "عنوان URL"; -"Common.Controls.Status.UserReblogged" = "أعادَ %@ تدوينها"; +"Common.Controls.Status.TapToReveal" = "اُنقُر لِلكَشف"; +"Common.Controls.Status.UserReblogged" = "أعادَ %@ تَدوينَها"; "Common.Controls.Status.UserRepliedTo" = "رَدًا على %@"; "Common.Controls.Status.Visibility.Direct" = "المُستخدمِونَ المُشارِ إليهم فَقَطْ مَن يُمكِنُهُم رُؤيَةُ هَذَا المَنشُور."; "Common.Controls.Status.Visibility.Private" = "فَقَطْ مُتابِعينَهُم مَن يُمكِنُهُم رُؤيَةُ هَذَا المَنشُور."; @@ -138,11 +143,11 @@ "Common.Controls.Timeline.Header.UserSuspendedWarning" = "لقد أُوقِفَ حِساب %@."; "Common.Controls.Timeline.Loader.LoadMissingPosts" = "تحميل المَنشورات المَفقودَة"; "Common.Controls.Timeline.Loader.LoadingMissingPosts" = "يَجري تحميل المَنشورات المَفقودَة..."; -"Common.Controls.Timeline.Loader.ShowMoreReplies" = "إظهار مَزيد مِنَ الرُّدود"; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "أظْهِر مَزيدًا مِنَ الرُّدود"; "Common.Controls.Timeline.Timestamp.Now" = "الآن"; "Scene.AccountList.AddAccount" = "إضافَةُ حِساب"; "Scene.AccountList.DismissAccountSwitcher" = "تجاهُل مبدِّل الحِساب"; -"Scene.AccountList.TabBarHint" = "المِلف المُحدَّد حاليًا: %@. انقر نقرًا مزدوجًا مع الاستمرار لإظهار مُبدِّل الحِساب"; +"Scene.AccountList.TabBarHint" = "المِلَفُّ المُحدَّدُ حالِيًّا: %@. اُنقُر نَقرًا مُزدَوَجًا مَعَ الاِستِمرارِ لِإظهارِ مُبدِّلِ الحِساب"; "Scene.Compose.Accessibility.AppendAttachment" = "إضافة مُرفَق"; "Scene.Compose.Accessibility.AppendPoll" = "اضافة استطلاع رأي"; "Scene.Compose.Accessibility.CustomEmojiPicker" = "منتقي الرموز التعبيرية المُخصَّص"; @@ -178,11 +183,11 @@ "Scene.Compose.Poll.ThirtyMinutes" = "ثلاثون دقيقة"; "Scene.Compose.Poll.ThreeDays" = "ثلاثةُ أيام"; "Scene.Compose.ReplyingToUser" = "رَدًا على %@"; -"Scene.Compose.Title.NewPost" = "منشور جديد"; +"Scene.Compose.Title.NewPost" = "مَنشُورٌ جَديد"; "Scene.Compose.Title.NewReply" = "رَدٌّ جديد"; -"Scene.Compose.Visibility.Direct" = "للأشخاص المُشار إليهم فقط"; +"Scene.Compose.Visibility.Direct" = "لِمَن أشرتُ إليهِم فَقَط"; "Scene.Compose.Visibility.Private" = "للمُتابِعينَ فقط"; -"Scene.Compose.Visibility.Public" = "للعامة"; +"Scene.Compose.Visibility.Public" = "لِلعَامَّة"; "Scene.Compose.Visibility.Unlisted" = "غير مُدرَج"; "Scene.ConfirmEmail.Button.OpenEmailApp" = "فتح تطبيق البريد الإلكتروني"; "Scene.ConfirmEmail.Button.Resend" = "إعادَةُ الإرسال"; @@ -196,6 +201,10 @@ "Scene.ConfirmEmail.Subtitle" = "لقد أرسلنا للتو بريد إلكتروني إلى %@، انقر على الرابط لتأكيد حسابك."; "Scene.ConfirmEmail.Title" = "شيءٌ أخير."; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "الوسوم"; +"Scene.Discovery.Tabs.News" = "الأخبار"; +"Scene.Discovery.Tabs.Posts" = "المنشورات"; "Scene.Favorite.Title" = "مُفضَّلَتُك"; "Scene.Follower.Footer" = "لا يُمكِن عَرض المُتابِعين مِنَ الخوادم الأُخرى."; "Scene.Following.Footer" = "لا يُمكِن عَرض المُتابَعات مِنَ الخوادم الأُخرى."; @@ -204,8 +213,8 @@ "Scene.HomeTimeline.NavigationBarState.Published" = "تمَّ النَّشر!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "يَجري نَشر المُشارَكَة..."; "Scene.HomeTimeline.Title" = "الرَّئِيسَة"; -"Scene.Notification.Keyobard.ShowEverything" = "إظهار كل شيء"; -"Scene.Notification.Keyobard.ShowMentions" = "إظهار الإشارات"; +"Scene.Notification.Keyobard.ShowEverything" = "أظْهِر كُلَّ شَيء"; +"Scene.Notification.Keyobard.ShowMentions" = "أظْهِر الإشارَات"; "Scene.Notification.NotificationDescription.FavoritedYourPost" = "فَضَّلَ مَنشُورَك"; "Scene.Notification.NotificationDescription.FollowedYou" = "بَدَأ بِمُتابَعَتِك"; "Scene.Notification.NotificationDescription.MentionedYou" = "أشارَ إليك"; @@ -215,9 +224,13 @@ "Scene.Notification.Title.Everything" = "كُلُّ شيء"; "Scene.Notification.Title.Mentions" = "الإشارات"; "Scene.Preview.Keyboard.ClosePreview" = "إغلاق المُعايَنَة"; -"Scene.Preview.Keyboard.ShowNext" = "إظهار التالي"; -"Scene.Preview.Keyboard.ShowPrevious" = "إظهار السابق"; -"Scene.Profile.Dashboard.Followers" = "متابِع"; +"Scene.Preview.Keyboard.ShowNext" = "أظْهِر التَّالي"; +"Scene.Preview.Keyboard.ShowPrevious" = "أظْهِر السَّابِق"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "اُنقُر نَقرًا مُزدَوَجًا لِفَتحِ القائِمَة"; +"Scene.Profile.Accessibility.EditAvatarImage" = "تَحريرُ الصُّورَةِ الرَّمزِيَّة"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "أظْهِر الصُّورَةَ الرَّمزِيَّة"; +"Scene.Profile.Accessibility.ShowBannerImage" = "أظْهِر صُورَةَ الرَّايَة"; +"Scene.Profile.Dashboard.Followers" = "مُتابِع"; "Scene.Profile.Dashboard.Following" = "مُتابَع"; "Scene.Profile.Dashboard.Posts" = "مَنشورات"; "Scene.Profile.Fields.AddRow" = "إضافة صف"; @@ -234,14 +247,14 @@ "Scene.Profile.SegmentedControl.About" = "حَول"; "Scene.Profile.SegmentedControl.Media" = "وَسائِط"; "Scene.Profile.SegmentedControl.Posts" = "مَنشورات"; -"Scene.Profile.SegmentedControl.PostsAndReplies" = "المَنشوراتُ وَالرُدود"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "مَنشُوراتٌ وَرُدُود"; "Scene.Profile.SegmentedControl.Replies" = "رُدُود"; "Scene.Register.Error.Item.Agreement" = "الاِتِّفاقيَّة"; "Scene.Register.Error.Item.Email" = "البريد الإلكتروني"; "Scene.Register.Error.Item.Locale" = "اللغة المحلية"; "Scene.Register.Error.Item.Password" = "الرمز السري"; "Scene.Register.Error.Item.Reason" = "السبب"; -"Scene.Register.Error.Item.Username" = "اِسم المُستَخدِم"; +"Scene.Register.Error.Item.Username" = "اِسمُ المُستَخدِم"; "Scene.Register.Error.Reason.Accepted" = "يجب أن يُقبل %@"; "Scene.Register.Error.Reason.Blank" = "%@ مَطلوب"; "Scene.Register.Error.Reason.Blocked" = "يحتوي %@ على موفِّر خدمة بريد إلكتروني غير مسموح به"; @@ -257,17 +270,17 @@ "Scene.Register.Error.Special.UsernameInvalid" = "يُمكِن أن يحتوي اسم المستخدم على أحرف أبجدية، أرقام وشرطات سفلية فقط"; "Scene.Register.Error.Special.UsernameTooLong" = "اِسم المُستَخدِم طويل جداً (يَجِبُ ألّا يكون أطول من ثلاثين خانة)"; "Scene.Register.Input.Avatar.Delete" = "حذف"; -"Scene.Register.Input.DisplayName.Placeholder" = "اِسم العَرض"; -"Scene.Register.Input.Email.Placeholder" = "بريد إلكتروني"; +"Scene.Register.Input.DisplayName.Placeholder" = "اِسمُ عَرض"; +"Scene.Register.Input.Email.Placeholder" = "بَريدٌ إلِكتُرُونِيّ"; "Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "لماذا ترغب في الانضمام؟"; "Scene.Register.Input.Password.Accessibility.Checked" = "مُتَحَققٌ مِنه"; "Scene.Register.Input.Password.Accessibility.Unchecked" = "غيرُ مُتَحَققٍ مِنه"; "Scene.Register.Input.Password.CharacterLimit" = "ثمانيةُ خانات"; -"Scene.Register.Input.Password.Hint" = "يجب أن يكون رمزك السري مكوَّن من ثمان خانات على الأقل"; +"Scene.Register.Input.Password.Hint" = "يَجِبُ أن يَحتَوي رَمزُكَ السِرِّيَ علَى ثَمانِ خاناتٍ أقلًا"; "Scene.Register.Input.Password.Placeholder" = "رمز سري"; "Scene.Register.Input.Password.Require" = "رمز المرور الخاص بك يجب أن يحتوي على الأقل:"; "Scene.Register.Input.Username.DuplicatePrompt" = "اِسم المُستَخدِم هذا مأخوذٌ بالفعل."; -"Scene.Register.Input.Username.Placeholder" = "اِسم مُستَخدِم"; +"Scene.Register.Input.Username.Placeholder" = "اِسمُ مُستَخدِم"; "Scene.Register.Title" = "أخبرنا عن نفسك."; "Scene.Report.Content1" = "هل ترغب في إضافة أي منشورات أُخرى إلى البلاغ؟"; "Scene.Report.Content2" = "هل هناك أي شيء يجب أن يعرفه المُراقبين حول هذا البلاغ؟"; @@ -286,17 +299,17 @@ "Scene.Search.Recommend.ButtonText" = "إظهار الكُل"; "Scene.Search.Recommend.HashTag.Description" = "الوُسُومُ الَّتي تَحظى بقدرٍ كبيرٍ مِنَ الاِهتمام"; "Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ أشخاص يتحدَّثوا"; -"Scene.Search.Recommend.HashTag.Title" = "ذُو شعبيَّة على ماستودون"; +"Scene.Search.Recommend.HashTag.Title" = "ذُو شَعبِيَّةٍ عَلَى مَاستودُون"; "Scene.Search.SearchBar.Cancel" = "إلغاء"; -"Scene.Search.SearchBar.Placeholder" = "البحث عن وسوم أو مستخدمين"; +"Scene.Search.SearchBar.Placeholder" = "اِبحَث عَن وُسُومٍ أو مُستَخدِمين"; "Scene.Search.Searching.Clear" = "مَحو"; "Scene.Search.Searching.EmptyState.NoResults" = "لا تُوجَدُ نتائِج"; "Scene.Search.Searching.RecentSearch" = "عَمَليَّاُت البَحثِ الأخيرَة"; "Scene.Search.Searching.Segment.All" = "الكُل"; -"Scene.Search.Searching.Segment.Hashtags" = "الوُسُوم"; -"Scene.Search.Searching.Segment.People" = "الأشخاص"; -"Scene.Search.Searching.Segment.Posts" = "المَنشورات"; -"Scene.Search.Title" = "البحث"; +"Scene.Search.Searching.Segment.Hashtags" = "وُسُوم"; +"Scene.Search.Searching.Segment.People" = "أشخاص"; +"Scene.Search.Searching.Segment.Posts" = "مَنشُورات"; +"Scene.Search.Title" = "البَحث"; "Scene.ServerPicker.Button.Category.Academia" = "أكاديمي"; "Scene.ServerPicker.Button.Category.Activism" = "النشطاء"; "Scene.ServerPicker.Button.Category.All" = "الكل"; @@ -357,10 +370,11 @@ "Scene.Settings.Section.Notifications.Trigger.Title" = "أشعِرني عِندما يَقومُ"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "تَعطيلُ الصوَرِ الرمزيَّةِ المُتحرِّكَة"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "تَعطيلُ الرُموزِ التَّعبيريَّةِ المُتحرِّكَة"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "فَتحُ الرَّوابِطِ فِي مَاستودُون"; "Scene.Settings.Section.Preference.Title" = "التَّفضيلات"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "النَّمَطُ الأسوَدُ الداكِنُ الحَقيقي"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "اِستِخدامُ المُتصفِّحِ الاِفتراضي لِفتحِ الرَّوابِط"; -"Scene.Settings.Section.SpicyZone.Clear" = "مَحوُ ذاكِرَةُ التَّخزينِ المُؤقت لِلوسائِط"; +"Scene.Settings.Section.SpicyZone.Clear" = "مَحوُ ذاكِرَةِ التَّخزينِ المُؤقَّتِ لِلوسائِط"; "Scene.Settings.Section.SpicyZone.Signout" = "تَسجيلُ الخُروج"; "Scene.Settings.Section.SpicyZone.Title" = "المنطِقَةُ اللَّاذِعَة"; "Scene.Settings.Title" = "الإعدادات"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict index 0b28c577..c2f64172 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict @@ -170,6 +170,30 @@ %ld إعادة تدوين + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + لا رَدّ + one + رَدٌّ واحِد + two + رَدَّانِ اِثنان + few + %ld رُدُود + many + %ld رَدًّا + other + %ld رَدّ + + plural.count.vote NSStringLocalizedFormatKey @@ -447,15 +471,15 @@ zero مُنذُ لَحظة one - مُنذُ سنة + مُنذُ %ldع two - مُنذُ سنتين + مُنذُ %ldع few - مُنذُ %ld سنين + مُنذُ %ldع many - مُنذُ %ld سنةً + مُنذُ %ldع other - مُنذُ %ld سنة + مُنذُ %ldع date.month.ago.abbr @@ -471,15 +495,15 @@ zero مُنذُ لَحظة one - مُنذُ شهر + مُنذُ %ldش two - مُنذُ شهرين + مُنذُ %ldش few - مُنذُ %ld أشهُر + مُنذُ %ldش many - مُنذُ %ld شهرًا + مُنذُ %ldش other - مُنذُ %ld شهر + مُنذُ %ldش date.day.ago.abbr @@ -495,15 +519,15 @@ zero مُنذُ لَحظة one - مُنذُ يوم + مُنذُ %ldي two - مُنذُ يومين + مُنذُ %ldي few - مُنذُ %ld أيام + مُنذُ %ldي many - مُنذُ %ld يومًا + مُنذُ %ldي other - مُنذُ %ld يوم + مُنذُ %ldي date.hour.ago.abbr @@ -519,15 +543,15 @@ zero مُنذُ لَحظة one - مُنذُ ساعة + مُنذُ %ldس two - مُنذُ ساعتين + مُنذُ %ldس few - مُنذُ %ld ساعات + مُنذُ %ldس many - مُنذُ %ld ساعةًَ + مُنذُ %ldس other - مُنذُ %ld ساعة + مُنذُ %ldس date.minute.ago.abbr @@ -543,15 +567,15 @@ zero مُنذُ لَحظة one - مُنذُ دقيقة + مُنذُ %ldد two - مُنذُ دقيقتان + مُنذُ %ldد few - مُنذُ %ld دقائق + مُنذُ %ldد many - مُنذُ %ld دقيقةً + مُنذُ %ldد other - مُنذُ %ld دقيقة + مُنذُ %ldد date.second.ago.abbr @@ -567,15 +591,15 @@ zero مُنذُ لَحظة one - مُنذُ ثانية + مُنذُ %ldث two - مُنذُ ثانيتين + مُنذُ %ldث few - مُنذُ %ld ثوان + مُنذُ %ldث many - مُنذُ %ld ثانية + مُنذُ %ldث other - مُنذُ %ld ثانية + مُنذُ %ldث diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings index 8213aa3d..4e10ce16 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.strings @@ -5,7 +5,7 @@ "Common.Alerts.Common.PleaseTryAgain" = "Si us plau intenta-ho de nou."; "Common.Alerts.Common.PleaseTryAgainLater" = "Si us plau, prova-ho més tard."; "Common.Alerts.DeletePost.Message" = "Estàs segur que vols suprimir aquesta publicació?"; -"Common.Alerts.DeletePost.Title" = "Estàs segur que vols suprimir aquesta publicació?"; +"Common.Alerts.DeletePost.Title" = "Esborrar Publicació"; "Common.Alerts.DiscardPostContent.Message" = "Confirma per a descartar el contingut de la publicació composta."; "Common.Alerts.DiscardPostContent.Title" = "Descarta l'esborrany"; "Common.Alerts.EditProfileFailure.Message" = "No es pot editar el perfil. Si us plau torna-ho a provar."; @@ -16,7 +16,7 @@ Comprova la teva connexió a Internet."; "Common.Alerts.PublishPostFailure.Title" = "Error de Publicació"; "Common.Alerts.SavePhotoFailure.Message" = "Activa el permís d'accés a la biblioteca de fotos per desar-la."; -"Common.Alerts.SavePhotoFailure.Title" = "Desa l'Error de la Foto"; +"Common.Alerts.SavePhotoFailure.Title" = "Error al Desar la Foto"; "Common.Alerts.ServerError.Title" = "Error del Servidor"; "Common.Alerts.SignOut.Confirm" = "Tancar Sessió"; "Common.Alerts.SignOut.Message" = "Estàs segur que vols tancar la sessió?"; @@ -36,7 +36,7 @@ Comprova la teva connexió a Internet."; "Common.Controls.Actions.Discard" = "Descarta"; "Common.Controls.Actions.Done" = "Fet"; "Common.Controls.Actions.Edit" = "Edita"; -"Common.Controls.Actions.FindPeople" = "Busca persones per seguir"; +"Common.Controls.Actions.FindPeople" = "Busca persones a seguir"; "Common.Controls.Actions.ManuallySearch" = "Cerca manualment a canvi"; "Common.Controls.Actions.Next" = "Següent"; "Common.Controls.Actions.Ok" = "D'acord"; @@ -81,30 +81,34 @@ Comprova la teva connexió a Internet."; "Common.Controls.Keyboard.Common.OpenSettings" = "Obre la configuració"; "Common.Controls.Keyboard.Common.ShowFavorites" = "Mostra els Favorits"; "Common.Controls.Keyboard.Common.SwitchToTab" = "Canviar a %@"; -"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Secció següent"; -"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Secció anterior"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Secció Següent"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Secció Anterior"; "Common.Controls.Keyboard.Timeline.NextStatus" = "Publicació següent"; -"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Obre el perfil de l'autor"; -"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Obre el perfil del impulsor"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Obre el Perfil de l'Autor"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Obre el Perfil del Impulsor"; "Common.Controls.Keyboard.Timeline.OpenStatus" = "Obre la publicació"; "Common.Controls.Keyboard.Timeline.PreviewImage" = "Vista prèvia de l'Imatge"; "Common.Controls.Keyboard.Timeline.PreviousStatus" = "Publicació anterior"; -"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Respon a la publicació"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Respon a la Publicació"; "Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Commuta l'Avís de Contingut"; -"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Commuta el Favorit de la publicació"; -"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Commuta l'impuls de la publicació"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Commuta el Favorit de la Publicació"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Commuta l'Impuls de la Publicació"; "Common.Controls.Status.Actions.Favorite" = "Favorit"; "Common.Controls.Status.Actions.Hide" = "Amaga"; "Common.Controls.Status.Actions.Menu" = "Menú"; "Common.Controls.Status.Actions.Reblog" = "Impuls"; "Common.Controls.Status.Actions.Reply" = "Respon"; +"Common.Controls.Status.Actions.ShowGif" = "Mostra el GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Mostra la imatge"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Mostra el reproductor de vídeo"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Toca i manté per a veure el menú"; "Common.Controls.Status.Actions.Unfavorite" = "Desfer Favorit"; "Common.Controls.Status.Actions.Unreblog" = "Desfer l'impuls"; "Common.Controls.Status.ContentWarning" = "Advertència de Contingut"; -"Common.Controls.Status.MediaContentWarning" = "Toca qualsevol lloc per mostrar"; +"Common.Controls.Status.MediaContentWarning" = "Toca qualsevol lloc per a mostrar"; "Common.Controls.Status.Poll.Closed" = "Finalitzada"; "Common.Controls.Status.Poll.Vote" = "Vota"; -"Common.Controls.Status.ShowPost" = "Mostra la publicació"; +"Common.Controls.Status.ShowPost" = "Mostra la Publicació"; "Common.Controls.Status.ShowUserProfile" = "Mostra el perfil de l'usuari"; "Common.Controls.Status.Tag.Email" = "Correu electrònic"; "Common.Controls.Status.Tag.Emoji" = "Emoji"; @@ -112,6 +116,7 @@ Comprova la teva connexió a Internet."; "Common.Controls.Status.Tag.Link" = "Enllaç"; "Common.Controls.Status.Tag.Mention" = "Menciona"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Toca per a mostrar"; "Common.Controls.Status.UserReblogged" = "%@ ha impulsat"; "Common.Controls.Status.UserRepliedTo" = "Ha respòs a %@"; "Common.Controls.Status.Visibility.Direct" = "Només l'usuari mencionat pot veure aquesta publicació."; @@ -124,7 +129,7 @@ Comprova la teva connexió a Internet."; "Common.Controls.Tabs.Search" = "Cerca"; "Common.Controls.Timeline.Filtered" = "Filtrat"; "Common.Controls.Timeline.Header.BlockedWarning" = "No pots veure el perfil d'aquest usuari - fins que et desbloquegi."; +fins que et desbloquegi."; "Common.Controls.Timeline.Header.BlockingWarning" = "No pots veure el perfil d'aquest usuari fins que el desbloquegis. El teu perfil els sembla així."; @@ -136,8 +141,8 @@ El teu perfil els sembla així."; fins que el desbloquegis. El teu perfil els sembla així."; "Common.Controls.Timeline.Header.UserSuspendedWarning" = "El compte de %@ ha estat suspès."; -"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Carrega les publicacions que falten"; -"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Carregant les publicacions que falten..."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Carrega les publicacions faltants"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Carregant les publicacions faltants..."; "Common.Controls.Timeline.Loader.ShowMoreReplies" = "Mostra més respostes"; "Common.Controls.Timeline.Timestamp.Now" = "Ara"; "Scene.AccountList.AddAccount" = "Afegir compte"; @@ -193,9 +198,12 @@ carregat a Mastodon."; "Scene.ConfirmEmail.OpenEmailApp.Mail" = "Correu electrònic"; "Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Obre el Client de Correu electrònic"; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Comprova la teva safata d'entrada."; -"Scene.ConfirmEmail.Subtitle" = "Acabem d'enviar un correu electrònic a %@, -toca l'enllaç per a confirmar el teu compte."; +"Scene.ConfirmEmail.Subtitle" = "Toca l'enllaç del correu electrònic que t'hem enviat per a confirmar el teu compte."; "Scene.ConfirmEmail.Title" = "Una última cosa."; +"Scene.Discovery.Tabs.ForYou" = "Per a tu"; +"Scene.Discovery.Tabs.Hashtags" = "Etiquetes"; +"Scene.Discovery.Tabs.News" = "Notícies"; +"Scene.Discovery.Tabs.Posts" = "Publicacions"; "Scene.Favorite.Title" = "Els teus Favorits"; "Scene.Follower.Footer" = "Els seguidors d'altres servidors no son mostrats."; "Scene.Following.Footer" = "Els seguits d'altres servidors no son mostrats."; @@ -217,6 +225,10 @@ toca l'enllaç per a confirmar el teu compte."; "Scene.Preview.Keyboard.ClosePreview" = "Tanca la Vista Prèvia"; "Scene.Preview.Keyboard.ShowNext" = "Mostrar Següent"; "Scene.Preview.Keyboard.ShowPrevious" = "Mostrar Anterior"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Doble toc per a veure la llista"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Edita l'imatge del avatar"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Mostra l'imatge del avatar"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Mostra l'imatge del bàner"; "Scene.Profile.Dashboard.Followers" = "seguidors"; "Scene.Profile.Dashboard.Following" = "seguint"; "Scene.Profile.Dashboard.Posts" = "publicacions"; @@ -254,7 +266,7 @@ toca l'enllaç per a confirmar el teu compte."; "Scene.Register.Error.Reason.Unreachable" = "%@ sembla que no existeix"; "Scene.Register.Error.Special.EmailInvalid" = "Aquesta no és una adreça de correu electrònic vàlida"; "Scene.Register.Error.Special.PasswordTooShort" = "La contrasenya és massa curta (ha de tenir 8 caràcters com a mínim)"; -"Scene.Register.Error.Special.UsernameInvalid" = "El nom d'usuari només ha de contenir caràcters alfanumèrics i guions baixos"; +"Scene.Register.Error.Special.UsernameInvalid" = "El nom d'usuari ha de contenir només caràcters alfanumèrics i guions baixos"; "Scene.Register.Error.Special.UsernameTooLong" = "El nom d'usuari és massa llarg (no pot ser més llarg de 30 caràcters)"; "Scene.Register.Input.Avatar.Delete" = "Suprimeix"; "Scene.Register.Input.DisplayName.Placeholder" = "nom visible"; @@ -263,7 +275,7 @@ toca l'enllaç per a confirmar el teu compte."; "Scene.Register.Input.Password.Accessibility.Checked" = "verificat"; "Scene.Register.Input.Password.Accessibility.Unchecked" = "no verificat"; "Scene.Register.Input.Password.CharacterLimit" = "8 caràcters"; -"Scene.Register.Input.Password.Hint" = "La teva contrasenya ha de tenir com a mínim buit caràcters"; +"Scene.Register.Input.Password.Hint" = "La teva contrasenya ha de tenir com a mínim vuit caràcters"; "Scene.Register.Input.Password.Placeholder" = "contrasenya"; "Scene.Register.Input.Password.Require" = "La teva contrasenya com a mínim necessita:"; "Scene.Register.Input.Username.DuplicatePrompt" = "Aquest nom d'usuari ja està en ús."; @@ -322,8 +334,7 @@ toca l'enllaç per a confirmar el teu compte."; "Scene.ServerPicker.Label.Users" = "USUARIS"; "Scene.ServerPicker.Subtitle" = "Tria una comunitat segons els teus interessos, regió o una de propòsit general."; "Scene.ServerPicker.SubtitleExtend" = "Tria una comunitat segons els teus interessos, regió o una de propòsit general. Cada comunitat és operada per una organització totalment independent o individualment."; -"Scene.ServerPicker.Title" = "Tria un servidor, -qualsevol servidor."; +"Scene.ServerPicker.Title" = "Mastodon està fet d'usuaris en diferents comunitats."; "Scene.ServerRules.Button.Confirm" = "Hi estic d'acord"; "Scene.ServerRules.PrivacyPolicy" = "política de privadesa"; "Scene.ServerRules.Prompt" = "Al continuar, estàs subjecte als termes de servei i a la política de privacitat de %@."; @@ -353,10 +364,11 @@ qualsevol servidor."; "Scene.Settings.Section.Notifications.Trigger.Anyone" = "algú"; "Scene.Settings.Section.Notifications.Trigger.Follow" = "a qualsevol que segueixi"; "Scene.Settings.Section.Notifications.Trigger.Follower" = "un seguidor"; -"Scene.Settings.Section.Notifications.Trigger.Noone" = "algú"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "ningú"; "Scene.Settings.Section.Notifications.Trigger.Title" = "Notifica'm quan"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Desactiva avatars animats"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Desactiva emojis animats"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Obre enllaços a Mastodon"; "Scene.Settings.Section.Preference.Title" = "Preferències"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Mode negre fosc autèntic"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Utilitza el navegador predeterminat per a obrir enllaços"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.stringsdict index 140185ba..dfbd38c0 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ca.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld impulsos + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 Resposta + other + %ld respostes + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings index 8808e8d9..06f5eedf 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.strings @@ -98,6 +98,10 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Status.Actions.Menu" = "Menü"; "Common.Controls.Status.Actions.Reblog" = "Teilen"; "Common.Controls.Status.Actions.Reply" = "Antworten"; +"Common.Controls.Status.Actions.ShowGif" = "GIF anzeigen"; +"Common.Controls.Status.Actions.ShowImage" = "Bild anzeigen"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Zeige Video-Player"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Halte gedrückt um das Menü anzuzeigen"; "Common.Controls.Status.Actions.Unfavorite" = "Aus Favoriten entfernen"; "Common.Controls.Status.Actions.Unreblog" = "Nicht mehr teilen"; "Common.Controls.Status.ContentWarning" = "Inhaltswarnung"; @@ -112,6 +116,7 @@ Bitte überprüfe deine Internetverbindung."; "Common.Controls.Status.Tag.Link" = "Link"; "Common.Controls.Status.Tag.Mention" = "Erwähnung"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Zum Anzeigen tippen"; "Common.Controls.Status.UserReblogged" = "%@ teilte"; "Common.Controls.Status.UserRepliedTo" = "Antwortet auf %@"; "Common.Controls.Status.Visibility.Direct" = "Nur erwähnte Benutzer können diesen Beitrag sehen."; @@ -196,6 +201,10 @@ kann nicht auf Mastodon hochgeladen werden."; "Scene.ConfirmEmail.Subtitle" = "Wir haben gerade eine E-Mail an %@ gesendet, tippe darin auf den Link, um Dein Konto zu bestätigen."; "Scene.ConfirmEmail.Title" = "Noch eine letzte Sache."; +"Scene.Discovery.Tabs.ForYou" = "Für dich"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "Nachrichten"; +"Scene.Discovery.Tabs.Posts" = "Beiträge"; "Scene.Favorite.Title" = "Deine Favoriten"; "Scene.Follower.Footer" = "Follower von anderen Servern werden nicht angezeigt."; "Scene.Following.Footer" = "Wem das Konto folgt wird von anderen Servern werden nicht angezeigt."; @@ -217,6 +226,10 @@ tippe darin auf den Link, um Dein Konto zu bestätigen."; "Scene.Preview.Keyboard.ClosePreview" = "Vorschau schließen"; "Scene.Preview.Keyboard.ShowNext" = "Nächstes anzeigen"; "Scene.Preview.Keyboard.ShowPrevious" = "Vorheriges anzeigen"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Doppeltippen, um die Liste zu öffnen"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Profilbild bearbeiten"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Profilbild anzeigen"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Bannerbild anzeigen"; "Scene.Profile.Dashboard.Followers" = "Folger"; "Scene.Profile.Dashboard.Following" = "Gefolgte"; "Scene.Profile.Dashboard.Posts" = "Beiträge"; @@ -357,6 +370,7 @@ beliebigen Server."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Benachrichtige mich, wenn"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Animierte Profilbilder deaktivieren"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Animierte Emojis deaktivieren"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Links in Mastodon öffnen"; "Scene.Settings.Section.Preference.Title" = "Präferenzen"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Vollständig dunkler Dunkelmodus"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Standardbrowser zum Öffnen von Links verwenden"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict index 66b7f2a2..20e8b615 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/de.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld Reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 Antwort + other + %ld Antworten + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings index 1a03cd56..4edf4702 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings @@ -98,6 +98,10 @@ Please check your internet connection."; "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Reblog"; "Common.Controls.Status.Actions.Reply" = "Reply"; +"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Show image"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; "Common.Controls.Status.Actions.Unfavorite" = "Unfavorite"; "Common.Controls.Status.Actions.Unreblog" = "Undo reblog"; "Common.Controls.Status.ContentWarning" = "Content Warning"; @@ -112,6 +116,7 @@ Please check your internet connection."; "Common.Controls.Status.Tag.Link" = "Link"; "Common.Controls.Status.Tag.Mention" = "Mention"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Tap to reveal"; "Common.Controls.Status.UserReblogged" = "%@ reblogged"; "Common.Controls.Status.UserRepliedTo" = "Replied to %@"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; @@ -195,6 +200,10 @@ uploaded to Mastodon."; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Check your inbox."; "Scene.ConfirmEmail.Subtitle" = "Tap the link we emailed to you to verify your account."; "Scene.ConfirmEmail.Title" = "One last thing."; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "Scene.Favorite.Title" = "Your Favorites"; "Scene.Follower.Footer" = "Followers from other servers are not displayed."; "Scene.Following.Footer" = "Follows from other servers are not displayed."; @@ -216,6 +225,10 @@ uploaded to Mastodon."; "Scene.Preview.Keyboard.ClosePreview" = "Close Preview"; "Scene.Preview.Keyboard.ShowNext" = "Show Next"; "Scene.Preview.Keyboard.ShowPrevious" = "Show Previous"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; "Scene.Profile.Dashboard.Followers" = "followers"; "Scene.Profile.Dashboard.Following" = "following"; "Scene.Profile.Dashboard.Posts" = "posts"; @@ -355,6 +368,7 @@ uploaded to Mastodon."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Notify me when"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Disable animated avatars"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Disable animated emojis"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; "Scene.Settings.Section.Preference.Title" = "Preferences"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "True black dark mode"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Use default browser to open links"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict index 730e2902..503ff9db 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.strings index d149865a..0782206f 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.strings @@ -5,7 +5,7 @@ "Common.Alerts.Common.PleaseTryAgain" = "Por favor, intentá de nuevo."; "Common.Alerts.Common.PleaseTryAgainLater" = "Por favor, intentá de nuevo más tarde."; "Common.Alerts.DeletePost.Message" = "¿Estás seguro que querés eliminar este mensaje?"; -"Common.Alerts.DeletePost.Title" = "¿Estás seguro que querés eliminar este mensaje?"; +"Common.Alerts.DeletePost.Title" = "Eliminar mensaje"; "Common.Alerts.DiscardPostContent.Message" = "Confirmá para descartar el contenido del mensaje redactado."; "Common.Alerts.DiscardPostContent.Title" = "Descartar borrador"; "Common.Alerts.EditProfileFailure.Message" = "No se pudo editar el perfil. Por favor, intentá de nuevo."; @@ -98,6 +98,10 @@ Por favor, revisá tu conexión a Internet."; "Common.Controls.Status.Actions.Menu" = "Menú"; "Common.Controls.Status.Actions.Reblog" = "Adherir"; "Common.Controls.Status.Actions.Reply" = "Responder"; +"Common.Controls.Status.Actions.ShowGif" = "Mostrar GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Mostrar imagen"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Mostrar reproductor de video"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tocá y mantené presionado para mostrar el menú"; "Common.Controls.Status.Actions.Unfavorite" = "Dejar de marcar como favorito"; "Common.Controls.Status.Actions.Unreblog" = "Deshacer adhesión"; "Common.Controls.Status.ContentWarning" = "Advertencia de contenido"; @@ -112,6 +116,7 @@ Por favor, revisá tu conexión a Internet."; "Common.Controls.Status.Tag.Link" = "Enlace"; "Common.Controls.Status.Tag.Mention" = "Mención"; "Common.Controls.Status.Tag.Url" = "Dirección web"; +"Common.Controls.Status.TapToReveal" = "Tocá para mostrar"; "Common.Controls.Status.UserReblogged" = "%@ adhirió"; "Common.Controls.Status.UserRepliedTo" = "Respondió a %@"; "Common.Controls.Status.Visibility.Direct" = "Sólo el usuario mencionado puede ver este mensaje."; @@ -196,6 +201,10 @@ y no se puede subir a Mastodon."; "Scene.ConfirmEmail.Subtitle" = "Acabamos de enviar un correo electrónico a %@, pulsá en el enlace para confirmar tu cuenta."; "Scene.ConfirmEmail.Title" = "Una última cosa."; +"Scene.Discovery.Tabs.ForYou" = "Para vos"; +"Scene.Discovery.Tabs.Hashtags" = "Etiquetas"; +"Scene.Discovery.Tabs.News" = "Novedades"; +"Scene.Discovery.Tabs.Posts" = "Mensajes"; "Scene.Favorite.Title" = "Tus favoritos"; "Scene.Follower.Footer" = "No se muestran los seguidores de otros servidores."; "Scene.Following.Footer" = "No se muestran las cuentas de otros servidores que seguís."; @@ -217,6 +226,10 @@ pulsá en el enlace para confirmar tu cuenta."; "Scene.Preview.Keyboard.ClosePreview" = "Cerrar previsualización"; "Scene.Preview.Keyboard.ShowNext" = "Mostrar siguiente"; "Scene.Preview.Keyboard.ShowPrevious" = "Mostrar anterior"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Tocá dos veces para abrir la lista"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Editar imagen de avatar"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Mostrar imagen de avatar"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Mostrar imagen de banner"; "Scene.Profile.Dashboard.Followers" = "seguidores"; "Scene.Profile.Dashboard.Following" = "siguiendo"; "Scene.Profile.Dashboard.Posts" = "mensajes"; @@ -357,6 +370,7 @@ el que quieras."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Notificarme cuando"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Deshabilitar avatares animados"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Deshabilitar emojis animados"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Abrir enlaces en Mastodon"; "Scene.Settings.Section.Preference.Title" = "Configuración"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Modo negro oscuro real"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Usar navegador web predeterminado para abrir enlaces"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.stringsdict index f4f0097e..9d1fdadb 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/es-419.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld adhesiones + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 respuesta + other + %ld respuestas + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings index 09814c91..544a0b86 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.strings @@ -98,6 +98,10 @@ Por favor, revise su conexión a internet."; "Common.Controls.Status.Actions.Menu" = "Menú"; "Common.Controls.Status.Actions.Reblog" = "Rebloguear"; "Common.Controls.Status.Actions.Reply" = "Responder"; +"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Show image"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; "Common.Controls.Status.Actions.Unfavorite" = "No favorito"; "Common.Controls.Status.Actions.Unreblog" = "Deshacer reblogueo"; "Common.Controls.Status.ContentWarning" = "Advertencia de Contenido"; @@ -112,6 +116,7 @@ Por favor, revise su conexión a internet."; "Common.Controls.Status.Tag.Link" = "Enlace"; "Common.Controls.Status.Tag.Mention" = "Mención"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Tap to reveal"; "Common.Controls.Status.UserReblogged" = "%@ lo reblogueó"; "Common.Controls.Status.UserRepliedTo" = "En respuesta a %@"; "Common.Controls.Status.Visibility.Direct" = "Sólo el usuario mencionado puede ver este mensaje."; @@ -196,6 +201,10 @@ subirse a Mastodon."; "Scene.ConfirmEmail.Subtitle" = "Te acabamos de enviar un correo a %@, pulsa en el enlace para confirmar tu cuenta."; "Scene.ConfirmEmail.Title" = "Una última cosa."; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "Scene.Favorite.Title" = "Tus Favoritos"; "Scene.Follower.Footer" = "No se muestran los seguidores de otros servidores."; "Scene.Following.Footer" = "No se muestran los seguidos de otros servidores."; @@ -217,6 +226,10 @@ pulsa en el enlace para confirmar tu cuenta."; "Scene.Preview.Keyboard.ClosePreview" = "Cerrar Previsualización"; "Scene.Preview.Keyboard.ShowNext" = "Mostrar Siguiente"; "Scene.Preview.Keyboard.ShowPrevious" = "Mostrar Anterior"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; "Scene.Profile.Dashboard.Followers" = "seguidores"; "Scene.Profile.Dashboard.Following" = "siguiendo"; "Scene.Profile.Dashboard.Posts" = "publicaciones"; @@ -357,6 +370,7 @@ cualquier servidor."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Recibir notificación cuando"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Deshabilitar avatares animados"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Deshabilitar emojis animados"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; "Scene.Settings.Section.Preference.Title" = "Preferencias"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Modo oscuro negro real"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Usar navegador predeterminado para abrir los enlaces"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict index 186218af..24e407d0 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/es.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogueos + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.strings index 7feec0a7..42b3bfb6 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.strings @@ -98,6 +98,10 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Status.Actions.Menu" = "Menua"; "Common.Controls.Status.Actions.Reblog" = "Bultzada"; "Common.Controls.Status.Actions.Reply" = "Erantzun"; +"Common.Controls.Status.Actions.ShowGif" = "Erakutsi GIFa"; +"Common.Controls.Status.Actions.ShowImage" = "Erakutsi irudia"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Erakutsi bideo-erreproduzigailua"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Sakatu eta eutsi menua erakusteko"; "Common.Controls.Status.Actions.Unfavorite" = "Kendu gogokoa"; "Common.Controls.Status.Actions.Unreblog" = "Desegin bultzada"; "Common.Controls.Status.ContentWarning" = "Edukiaren abisua"; @@ -112,6 +116,7 @@ Egiaztatu Interneteko konexioa."; "Common.Controls.Status.Tag.Link" = "Esteka"; "Common.Controls.Status.Tag.Mention" = "Aipatu"; "Common.Controls.Status.Tag.Url" = "URLa"; +"Common.Controls.Status.TapToReveal" = "Sakatu erakusteko"; "Common.Controls.Status.UserReblogged" = "%@ erabiltzaileak bultzada eman dio"; "Common.Controls.Status.UserRepliedTo" = "%@(r)i erantzuten"; "Common.Controls.Status.Visibility.Direct" = "Aipatutako erabiltzaileek soilik ikus dezakete bidalketa hau."; @@ -196,6 +201,10 @@ Mastodonera igo."; "Scene.ConfirmEmail.Subtitle" = "Eposta bat bidali dizugu %@ helbidera, sakatu kontua berresteko esteka."; "Scene.ConfirmEmail.Title" = "Eta azkenik..."; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "Scene.Favorite.Title" = "Zure gogokoak"; "Scene.Follower.Footer" = "Beste zerbitzarietako jarraitzaileak ez dira bistaratzen."; "Scene.Following.Footer" = "Beste zerbitzarietan jarraitutakoak ez dira bistaratzen."; @@ -206,17 +215,21 @@ sakatu kontua berresteko esteka."; "Scene.HomeTimeline.Title" = "Hasiera"; "Scene.Notification.Keyobard.ShowEverything" = "Erakutsi guztia"; "Scene.Notification.Keyobard.ShowMentions" = "Erakutsi aipamenak"; -"Scene.Notification.NotificationDescription.FavoritedYourPost" = "erabiltzaileak zure bidalketa gogoko du"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "(e)k zure bidalketa gogoko du"; "Scene.Notification.NotificationDescription.FollowedYou" = "zu jarraitzen hasi da"; "Scene.Notification.NotificationDescription.MentionedYou" = "erabiltzaileak aipatu zaitu"; "Scene.Notification.NotificationDescription.PollHasEnded" = "inkesta amaitu da"; -"Scene.Notification.NotificationDescription.RebloggedYourPost" = "erabiltzaileak bultzada eman dio zure bidalketari"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "(e)k bultzada eman dio zure bidalketari"; "Scene.Notification.NotificationDescription.RequestToFollowYou" = "erabiltzaileak zu jarraitzea eskatu du"; "Scene.Notification.Title.Everything" = "Dena"; "Scene.Notification.Title.Mentions" = "Aipamenak"; "Scene.Preview.Keyboard.ClosePreview" = "Itxi aurrebista"; "Scene.Preview.Keyboard.ShowNext" = "Erakutsi hurrengoa"; "Scene.Preview.Keyboard.ShowPrevious" = "Erakutsi aurrekoa"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Sakatu birritan zerrenda irekitzeko"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Editatu abatarra"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Erakutsi abatarra"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Erakutsi banner irudia"; "Scene.Profile.Dashboard.Followers" = "jarraitzaile"; "Scene.Profile.Dashboard.Following" = "jarraitzen"; "Scene.Profile.Dashboard.Posts" = "bidalketa"; @@ -357,6 +370,7 @@ edozein zerbitzari."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Noiz jakinarazi:"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Desgaitu abatar animatuak"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Desgaitu emoji animatuak"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Ireki estekak Mastodonen"; "Scene.Settings.Section.Preference.Title" = "Hobespenak"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Benetako modu beltz iluna"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Erabili nabigatzaile lehenetsia estekak irekitzeko"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.stringsdict index 817e8372..871fb10b 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/eu-ES.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld bultzada + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Erantzun bat + other + %ld erantzun + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings index ffcd2846..0ed3a271 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings @@ -98,6 +98,10 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Rebloguer"; "Common.Controls.Status.Actions.Reply" = "Répondre"; +"Common.Controls.Status.Actions.ShowGif" = "Afficher le GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Afficher l’image"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Afficher le lecteur vidéo"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; "Common.Controls.Status.Actions.Unfavorite" = "Retirer des favoris"; "Common.Controls.Status.Actions.Unreblog" = "Annuler le reblog"; "Common.Controls.Status.ContentWarning" = "Avertissement de contenu"; @@ -112,6 +116,7 @@ Veuillez vérifier votre accès à Internet."; "Common.Controls.Status.Tag.Link" = "Lien"; "Common.Controls.Status.Tag.Mention" = "Mention"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Appuyer pour afficher"; "Common.Controls.Status.UserReblogged" = "%@ a reblogué"; "Common.Controls.Status.UserRepliedTo" = "À répondu à %@"; "Common.Controls.Status.Visibility.Direct" = "Seul·e l’utilisateur·rice mentionnée peut voir ce message."; @@ -196,6 +201,10 @@ téléversé sur Mastodon."; "Scene.ConfirmEmail.Subtitle" = "Nous venons d’envoyer un courriel à %@, tapotez le lien pour confirmer votre compte."; "Scene.ConfirmEmail.Title" = "Une dernière chose."; +"Scene.Discovery.Tabs.ForYou" = "Pour vous"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "Actualité"; +"Scene.Discovery.Tabs.Posts" = "Messages"; "Scene.Favorite.Title" = "Vos favoris"; "Scene.Follower.Footer" = "Les abonné·e·s issus des autres serveurs ne sont pas affiché·e·s."; "Scene.Following.Footer" = "Les abonnés issus des autres serveurs ne sont pas affichés."; @@ -217,6 +226,10 @@ tapotez le lien pour confirmer votre compte."; "Scene.Preview.Keyboard.ClosePreview" = "Fermer l'aperçu"; "Scene.Preview.Keyboard.ShowNext" = "Afficher le suivant"; "Scene.Preview.Keyboard.ShowPrevious" = "Afficher le précédent"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Appuyer deux fois pour ouvrir la liste"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Modifier l’avatar"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Afficher l’avatar"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Afficher l’image de la bannière"; "Scene.Profile.Dashboard.Followers" = "abonnés"; "Scene.Profile.Dashboard.Following" = "abonnements"; "Scene.Profile.Dashboard.Posts" = "publications"; @@ -357,6 +370,7 @@ n'importe quel serveur."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Me notifier lorsque"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Désactiver les avatars animés"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Désactiver les émojis animées"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Ouvrir les liens dans Mastodon"; "Scene.Settings.Section.Preference.Title" = "Préférences"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Vrai mode sombre"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Utiliser le navigateur par défaut pour ouvrir les liens"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict index 37f07e67..5c2b1497 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld reblogs + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 réponse + other + %ld réponses + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.strings index be6ea23a..91bc1373 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.strings @@ -98,6 +98,10 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Status.Actions.Menu" = "Clàr-taice"; "Common.Controls.Status.Actions.Reblog" = "Brosnaich"; "Common.Controls.Status.Actions.Reply" = "Freagair"; +"Common.Controls.Status.Actions.ShowGif" = "Seall an GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Seall an dealbh"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Seall cluicheadair video"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Thoir gnogag ’s cùm sìos a shealltainn a’ chlàir-thaice"; "Common.Controls.Status.Actions.Unfavorite" = "Thoir air falbh o na h-annsachdan"; "Common.Controls.Status.Actions.Unreblog" = "Na brosnaich tuilleadh"; "Common.Controls.Status.ContentWarning" = "Rabhadh susbainte"; @@ -112,6 +116,7 @@ Thoir sùil air a’ cheangal agad ris an eadar-lìon."; "Common.Controls.Status.Tag.Link" = "Ceangal"; "Common.Controls.Status.Tag.Mention" = "Iomradh"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Thoir gnogag gus a nochdadh"; "Common.Controls.Status.UserReblogged" = "Tha %@ ’ga bhrosnachadh"; "Common.Controls.Status.UserRepliedTo" = "Air %@ fhreagairt"; "Common.Controls.Status.Visibility.Direct" = "Chan fhaic ach an cleachdaiche air an dugadh iomradh am post seo."; @@ -196,6 +201,10 @@ a luchdadh suas gu Mastodon."; "Scene.ConfirmEmail.Subtitle" = "Tha sinn air post-d a chur gu %@, thoir gnogag air a’ chunntas a dhearbhadh a’ chunntais agad."; "Scene.ConfirmEmail.Title" = "Aon rud eile."; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "Scene.Favorite.Title" = "Na h-annsachdan agad"; "Scene.Follower.Footer" = "Cha dèid luchd-leantainn o fhrithealaichean eile a shealltainn."; "Scene.Following.Footer" = "Cha dèid cò air a leanas tu air frithealaichean eile a shealltainn."; @@ -217,6 +226,10 @@ thoir gnogag air a’ chunntas a dhearbhadh a’ chunntais agad."; "Scene.Preview.Keyboard.ClosePreview" = "Dùin an ro-shealladh"; "Scene.Preview.Keyboard.ShowNext" = "Air adhart"; "Scene.Preview.Keyboard.ShowPrevious" = "Air ais"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Thoir gnogag dhùbailte a dh’fhosgladh na liosta"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Deasaich dealbh an avatar"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Seall dealbh an avatar"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Seall dealbh brataich"; "Scene.Profile.Dashboard.Followers" = "luchd-leantainn"; "Scene.Profile.Dashboard.Following" = "a’ leantainn"; "Scene.Profile.Dashboard.Posts" = "postaichean"; @@ -356,6 +369,7 @@ thoir gnogag air a’ chunntas a dhearbhadh a’ chunntais agad."; "Scene.Settings.Section.Notifications.Trigger.Title" = " "; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Cuir beothachadh nan avataran à comas"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Cuir beothachadh nan Emojis à comas"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Fosgail ceanglaichean ann am Mastodon"; "Scene.Settings.Section.Preference.Title" = "Roghainnean"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Modh dubh dorcha"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Cleachd am brabhsair bunaiteach airson ceanglaichean fhosgladh"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.stringsdict index 7a54f553..b149323e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/gd-GB.lproj/Localizable.stringsdict @@ -142,6 +142,26 @@ %ld brosnachadh + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + %ld fhreagairt + two + %ld fhreagairt + few + %ld freagairtean + other + %ld freagairt + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings index cb037936..339e23f1 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.strings @@ -98,6 +98,10 @@ "Common.Controls.Status.Actions.Menu" = "メニュー"; "Common.Controls.Status.Actions.Reblog" = "ブースト"; "Common.Controls.Status.Actions.Reply" = "リプライ"; +"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Show image"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; "Common.Controls.Status.Actions.Unfavorite" = "お気に入り登録を取り消す"; "Common.Controls.Status.Actions.Unreblog" = "ブーストを戻す"; "Common.Controls.Status.ContentWarning" = "コンテンツ警告"; @@ -112,6 +116,7 @@ "Common.Controls.Status.Tag.Link" = "リンク"; "Common.Controls.Status.Tag.Mention" = "メンション"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Tap to reveal"; "Common.Controls.Status.UserReblogged" = "%@がブースト"; "Common.Controls.Status.UserRepliedTo" = "%@がリプライ"; "Common.Controls.Status.Visibility.Direct" = "この投稿はメンションされたユーザーに限り見ることができます。"; @@ -190,6 +195,10 @@ "Scene.ConfirmEmail.OpenEmailApp.Title" = "メールを確認"; "Scene.ConfirmEmail.Subtitle" = "先程 %@ にメールを送信しました。リンクをタップしてアカウントを確認してください。"; "Scene.ConfirmEmail.Title" = "さいごにもうひとつ。"; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "Scene.Favorite.Title" = "お気に入り"; "Scene.Follower.Footer" = "他のサーバーからのフォロワーは表示されません。"; "Scene.Following.Footer" = "他のサーバーにいるフォローは表示されません。"; @@ -211,6 +220,10 @@ "Scene.Preview.Keyboard.ClosePreview" = "プレビューを閉じる"; "Scene.Preview.Keyboard.ShowNext" = "次を見る"; "Scene.Preview.Keyboard.ShowPrevious" = "前を見る"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; "Scene.Profile.Dashboard.Followers" = "フォロワー"; "Scene.Profile.Dashboard.Following" = "フォロー"; "Scene.Profile.Dashboard.Posts" = "投稿"; @@ -350,6 +363,7 @@ "Scene.Settings.Section.Notifications.Trigger.Title" = "通知を受け取る"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "アバターのアニメーションを無効化する"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "絵文字のアニメーションを無効化する"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; "Scene.Settings.Section.Preference.Title" = "環境設定"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "真っ黒なダークテーマを使用する"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "既定のブラウザでリンクを開く"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict index f1c5e6e2..8dfc9507 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ja.lproj/Localizable.stringsdict @@ -100,6 +100,20 @@ %ld ブースト + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings new file mode 100644 index 00000000..792cf368 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.strings @@ -0,0 +1,389 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Sewḥel taɣult"; +"Common.Alerts.BlockDomain.Title" = "D tidet, d tidet tebɣiḍ ad tesweḥleḍ %@ akken ma yella? Deg tuget, kra n yisewḥal d ugdal ad yili d ayen iwulmen, yettwafernen. Ur tettwaliḍ ara agbur seg taɣult-nni neɣ kra seg yineḍfaren-ik i tt-yesseqdacen."; +"Common.Alerts.CleanCache.Message" = "Yettwasfeḍ %@ n tkatut tuffirt akken iwata."; +"Common.Alerts.CleanCache.Title" = "Sfeḍ tuffirt"; +"Common.Alerts.Common.PleaseTryAgain" = "Ttxil εreḍ tikelt-nniḍen."; +"Common.Alerts.Common.PleaseTryAgainLater" = "Ttxil εreḍ tikelt-nniḍen ticki."; +"Common.Alerts.DeletePost.Message" = "Tebɣiḍ s tidet ad tekkseḍ tasuffeɣt-agi?"; +"Common.Alerts.DeletePost.Title" = "Tebɣiḍ s tidet ad tekkseḍ tasuffeɣt-agi?"; +"Common.Alerts.DiscardPostContent.Message" = "Sentem i wakken ad yettusefsax ugbur n tsuffeɣt."; +"Common.Alerts.DiscardPostContent.Title" = "Kkes arewway"; +"Common.Alerts.EditProfileFailure.Message" = "Yegguma ad yettwaẓreg umaɣnu. Ɛreḍ tikkelt-nniḍen."; +"Common.Alerts.EditProfileFailure.Title" = "Ẓreg tuccḍa n umaɣnu"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Ur tezmireḍ ara ad ugar n tvidyut."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Ur tezmireḍ ara ad tsedduḍ tavidyut deg tsuffeɣt ideg llant yakan tugniwin."; +"Common.Alerts.PublishPostFailure.Message" = "Yecceḍ usuffeɣ n tsuffeɣt. +Ma ulac aɣilif, senqed tuqqna-inek internet."; +"Common.Alerts.PublishPostFailure.Title" = "Yecceḍ usuffeɣ"; +"Common.Alerts.SavePhotoFailure.Message" = "Ma ulac aɣilif, rmed tasiregt n unekcum ɣer temkarḍit n tewlafin i usekles n tewlaft."; +"Common.Alerts.SavePhotoFailure.Title" = "Tuccḍa deg usekles n tewlaft"; +"Common.Alerts.ServerError.Title" = "Tuccḍa n uqeddac"; +"Common.Alerts.SignOut.Confirm" = "Ffeɣ"; +"Common.Alerts.SignOut.Message" = "Tebɣiḍ ad teffɣeḍ?"; +"Common.Alerts.SignOut.Title" = "Ffeɣ"; +"Common.Alerts.SignUpFailure.Title" = "Tuccḍa deg unekcum"; +"Common.Alerts.VoteFailure.PollEnded" = "Tafrant tfuk"; +"Common.Alerts.VoteFailure.Title" = "Tuccḍa deg ufran"; +"Common.Controls.Actions.Add" = "Rnu"; +"Common.Controls.Actions.Back" = "Tuɣalin"; +"Common.Controls.Actions.BlockDomain" = "Sewḥel %@"; +"Common.Controls.Actions.Cancel" = "Sefsex"; +"Common.Controls.Actions.Compose" = "Sudes"; +"Common.Controls.Actions.Confirm" = "Sentem"; +"Common.Controls.Actions.Continue" = "Kemmel"; +"Common.Controls.Actions.CopyPhoto" = "Nɣel tawlaft"; +"Common.Controls.Actions.Delete" = "Kkes"; +"Common.Controls.Actions.Discard" = "Sefsex"; +"Common.Controls.Actions.Done" = "Immed"; +"Common.Controls.Actions.Edit" = "Ẓreg"; +"Common.Controls.Actions.FindPeople" = "Af imdanen ara tḍefreḍ"; +"Common.Controls.Actions.ManuallySearch" = "Anadi s ufus deg wadeg-is"; +"Common.Controls.Actions.Next" = "Uḍfir"; +"Common.Controls.Actions.Ok" = "IH"; +"Common.Controls.Actions.Open" = "Ldi"; +"Common.Controls.Actions.OpenInBrowser" = "Ldi deg yiminig"; +"Common.Controls.Actions.OpenInSafari" = "Ldi deg Safari"; +"Common.Controls.Actions.Preview" = "Taskant"; +"Common.Controls.Actions.Previous" = "Uzwir"; +"Common.Controls.Actions.Remove" = "Kkes"; +"Common.Controls.Actions.Reply" = "Err"; +"Common.Controls.Actions.ReportUser" = "Cetki ɣef %@"; +"Common.Controls.Actions.Save" = "Sekles"; +"Common.Controls.Actions.SavePhoto" = "Sekles tawlaft"; +"Common.Controls.Actions.SeeMore" = "Wali ugar"; +"Common.Controls.Actions.Settings" = "Iɣewwaṛen"; +"Common.Controls.Actions.Share" = "Bḍu"; +"Common.Controls.Actions.SharePost" = "Bḍu tasuffeɣt"; +"Common.Controls.Actions.ShareUser" = "Bḍu %@"; +"Common.Controls.Actions.SignIn" = "Qqen"; +"Common.Controls.Actions.SignUp" = "Jerred amiḍan"; +"Common.Controls.Actions.Skip" = "Zgel"; +"Common.Controls.Actions.TakePhoto" = "Ṭṭef tawlaft"; +"Common.Controls.Actions.TryAgain" = "Ɛreḍ tikkelt-nniḍen"; +"Common.Controls.Actions.UnblockDomain" = "Serreḥ i %@"; +"Common.Controls.Friendship.Block" = "Sewḥel"; +"Common.Controls.Friendship.BlockDomain" = "Sewḥel %@"; +"Common.Controls.Friendship.BlockUser" = "Sewḥel %@"; +"Common.Controls.Friendship.Blocked" = "Yettusewḥel"; +"Common.Controls.Friendship.EditInfo" = "Ẓreg talɣut"; +"Common.Controls.Friendship.Follow" = "Ḍfeṛ"; +"Common.Controls.Friendship.Following" = "Yettwaḍfar"; +"Common.Controls.Friendship.Mute" = "Sgugem"; +"Common.Controls.Friendship.MuteUser" = "Sgugem %@"; +"Common.Controls.Friendship.Muted" = "Yettwasgugem"; +"Common.Controls.Friendship.Pending" = "Yegguni"; +"Common.Controls.Friendship.Request" = "Tuttra"; +"Common.Controls.Friendship.Unblock" = "Serreḥ"; +"Common.Controls.Friendship.UnblockUser" = "Serreḥ i %@"; +"Common.Controls.Friendship.Unmute" = "Kkes asgugem"; +"Common.Controls.Friendship.UnmuteUser" = "Kkes asgugem ɣef %@"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "Aru tasuffeɣt tamaynut"; +"Common.Controls.Keyboard.Common.OpenSettings" = "Ldi iɣewwaren"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "Sken-d ismenyifen"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "Ddu ɣer %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Tigezmi tuḍfirt"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Tafrant tuzwirt"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "Amagrad uḍfir"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Ldi amaɣnu n umeskar"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Ldi amaɣnu n win i yulsen asuffeɣ"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "Ldi tasuffeɣt"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "Asenqed n tugna"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Amagrad uzwir"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Err ɣef tsuffeɣt"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Beddel alɣu n ugbur"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Abeddel n usmenyaf i tsuffeɣt"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Abeddel n unallas n tsuffeɣt"; +"Common.Controls.Status.Actions.Favorite" = "Anurif"; +"Common.Controls.Status.Actions.Hide" = "Ffer"; +"Common.Controls.Status.Actions.Menu" = "Umuɣ"; +"Common.Controls.Status.Actions.Reblog" = "Aɛiwed n usuffeɣ"; +"Common.Controls.Status.Actions.Reply" = "Err"; +"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Show image"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; +"Common.Controls.Status.Actions.Unfavorite" = "Kkes seg yismenyifen"; +"Common.Controls.Status.Actions.Unreblog" = "Sefsex allus n usuffeɣ"; +"Common.Controls.Status.ContentWarning" = "Alɣu n ugbur"; +"Common.Controls.Status.MediaContentWarning" = "Sit anida tebɣiḍ i wakken ad twaliḍ"; +"Common.Controls.Status.Poll.Closed" = "Ifukk"; +"Common.Controls.Status.Poll.Vote" = "Dɣeṛ"; +"Common.Controls.Status.ShowPost" = "Sken-d tasuffeɣt"; +"Common.Controls.Status.ShowUserProfile" = "Ssken-d amaɣnu n useqdac"; +"Common.Controls.Status.Tag.Email" = "Imayl"; +"Common.Controls.Status.Tag.Emoji" = "Emuji"; +"Common.Controls.Status.Tag.Hashtag" = "Ahacṭag"; +"Common.Controls.Status.Tag.Link" = "Aseɣwen"; +"Common.Controls.Status.Tag.Mention" = "Tabdart"; +"Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Tap to reveal"; +"Common.Controls.Status.UserReblogged" = "Tettwasuffeɣ-d %@ i tikkelt-nniḍen"; +"Common.Controls.Status.UserRepliedTo" = "Yerra ɣef %@"; +"Common.Controls.Status.Visibility.Direct" = "D ineḍfaren-is kan i izemren ad walin tsuffeɣ-a."; +"Common.Controls.Status.Visibility.Private" = "D ineḍfaren-is kan i izemren ad walin tsuffeɣ-a."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "D ineḍfaren-is kan i izemren ad walin tsuffeɣ-a."; +"Common.Controls.Status.Visibility.Unlisted" = "Yal wa yezmer ad iwali tsuffeɣt-a maca ur d-tettwaskaneḍ ara deg yizirig n wakud azayaz."; +"Common.Controls.Tabs.Home" = "Agejdan"; +"Common.Controls.Tabs.Notification" = "Tilɣa"; +"Common.Controls.Tabs.Profile" = "Amaɣnu"; +"Common.Controls.Tabs.Search" = "Nadi"; +"Common.Controls.Timeline.Filtered" = "Yettwasizdeg"; +"Common.Controls.Timeline.Header.BlockedWarning" = "Ur tezmireḍ ara ad twaliḍ amaɣnu n useqdac-a +Akka i as-d-yettban umaɣnu-inek."; +"Common.Controls.Timeline.Header.BlockingWarning" = "Ur tezmireḍ ara ad twaliḍ amaɣnu n useqdac-a +alamma tekkseḍ-as asewḥel. +Akka i as-d-yettban umaɣnu-inek."; +"Common.Controls.Timeline.Header.NoStatusFound" = "Ulac tasuffeɣt yettwafen"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "Yettwaseḥbes useqdac-a."; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "Ur tezmireḍ ara ad twaliḍ amaɣnu n %@ +Akka i as-d-yettban umaɣnu-inek."; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "Ur tezmireḍ ara ad twaliḍ amaɣnu n %@ +alamma tekkseḍ-as asewḥel. +Akka i as-d-yettban umaɣnu-inek."; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "Yettwaseḥbes umiḍan n %@."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Sali tisuffaɣ i iruḥen"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Asali n tsuffaɣ i iruḥen..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "Ssken-d ugar n tririyin"; +"Common.Controls.Timeline.Timestamp.Now" = "Tura"; +"Scene.AccountList.AddAccount" = "Rnu amiḍan"; +"Scene.AccountList.DismissAccountSwitcher" = "Sefsex abeddel n umiḍan"; +"Scene.AccountList.TabBarHint" = "Amaɣnu amiran yettwafernen: %@. Sit berdayen syen teǧǧeḍ aḍad-ik·im i uskan abeddel n umiḍan"; +"Scene.Compose.Accessibility.AppendAttachment" = "Rnu taceqquft yeddan"; +"Scene.Compose.Accessibility.AppendPoll" = "Rnu asenqed"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "Amefran n yimujiten udmawanen"; +"Scene.Compose.Accessibility.DisableContentWarning" = "Sens alɣu n ugbur"; +"Scene.Compose.Accessibility.EnableContentWarning" = "Rmed alɣu n ugbur"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "Umuɣ n ubani n tsuffeɣt"; +"Scene.Compose.Accessibility.RemovePoll" = "Kkes asenqed"; +"Scene.Compose.Attachment.AttachmentBroken" = "%@-a yerreẓ, ur yezmir ara +Ad d-yettwasali ɣef Mastodon."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Glem-d tawlaft i wid yesɛan ugur deg yiẓri..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Glem-d tavidyut i wid yesɛan ugur deg yiẓri..."; +"Scene.Compose.Attachment.Photo" = "tawlaft"; +"Scene.Compose.Attachment.Video" = "tavidyutt"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Tallunt ara yettwarnun"; +"Scene.Compose.ComposeAction" = "Sufeɣ"; +"Scene.Compose.ContentInputPlaceholder" = "Aru neɣ senteḍ ayen yellan deg wallaɣ-ik"; +"Scene.Compose.ContentWarning.Placeholder" = "Aru alɣu-inek s telqeyt da..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Rnu taceqquft yeddan - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "Sefsex tasuffeɣt"; +"Scene.Compose.Keyboard.PublishPost" = "Suffeɣ tasuffeɣt"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Fren timeẓriwt - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "Beddel alɣu n ugbur"; +"Scene.Compose.Keyboard.TogglePoll" = "Beddel asenqed"; +"Scene.Compose.MediaSelection.Browse" = "Snirem"; +"Scene.Compose.MediaSelection.Camera" = "Ṭṭef tawlaft"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "Tanedlist n tewlaft"; +"Scene.Compose.Poll.DurationTime" = "Tangazt: %@"; +"Scene.Compose.Poll.OneDay" = "1 n wass"; +"Scene.Compose.Poll.OneHour" = "1 n wesrag"; +"Scene.Compose.Poll.OptionNumber" = "Taxtiṛt %ld"; +"Scene.Compose.Poll.SevenDays" = "7 n wussan"; +"Scene.Compose.Poll.SixHours" = "6 n yisragen"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 n tesdatin"; +"Scene.Compose.Poll.ThreeDays" = "3 n wussan"; +"Scene.Compose.ReplyingToUser" = "tiririt ɣef %@"; +"Scene.Compose.Title.NewPost" = "Tasuffeɣt tamaynut"; +"Scene.Compose.Title.NewReply" = "Tiririt tamaynut"; +"Scene.Compose.Visibility.Direct" = "Imdanen i d-bedreɣ kan"; +"Scene.Compose.Visibility.Private" = "Imeḍfaṛen kan"; +"Scene.Compose.Visibility.Public" = "Azayez"; +"Scene.Compose.Visibility.Unlisted" = "War tabdert"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "Ldi asnas n yimayl"; +"Scene.ConfirmEmail.Button.Resend" = "Ales tuzna"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Senqed ma yella tansa-inek n imayl d tameɣut akked uspam ma yella ur t-tufiḍ ara."; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Ales tuzna n yimayl"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "Senqed imayl-ik·im"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "Akken kan i ak-n-nuzen imayl. Sefqed aspam ma yella ur t-tufiḍ ara."; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Imayl"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Ldi amsaɣ n yimayl"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "Sefqed Tanaka-inek."; +"Scene.ConfirmEmail.Subtitle" = "Sit ɣef useɣwen i ak-n-uznen i wakken ad tesneqdeḍ amiḍan-ik."; +"Scene.ConfirmEmail.Title" = "Taɣawsa taneggarut."; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; +"Scene.Favorite.Title" = "Ismenyifen-ik·im"; +"Scene.Follower.Footer" = "Ineḍfaren seg yiqeddacen-nniḍen ur d-ttwaskanen ara."; +"Scene.Following.Footer" = "Ineḍfaren seg yiqeddacen-nniḍen ur d-ttwaskanen ara."; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "Tissufaɣ timaynutin"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "Beṛṛa n tuqqna"; +"Scene.HomeTimeline.NavigationBarState.Published" = "Yettwasuffeɣ!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Asuffeɣ tasuffeɣt..."; +"Scene.HomeTimeline.Title" = "Agejdan"; +"Scene.Notification.Keyobard.ShowEverything" = "Sken yal taɣawsa"; +"Scene.Notification.Keyobard.ShowMentions" = "Sken tisedmirin"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "yesmenyef tasuffeɣt-ik·im"; +"Scene.Notification.NotificationDescription.FollowedYou" = "iṭṭafar-ik·ikem"; +"Scene.Notification.NotificationDescription.MentionedYou" = "yebder-ik·ikem-id"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "asenqed iffuk"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "iɛawed-as asuffeɣ i tsuffeɣt-ik·im"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "issuter aḍfar-inek"; +"Scene.Notification.Title.Everything" = "Akk"; +"Scene.Notification.Title.Mentions" = "Abdar"; +"Scene.Preview.Keyboard.ClosePreview" = "Mdel timeẓri"; +"Scene.Preview.Keyboard.ShowNext" = "Sken uḍfir"; +"Scene.Preview.Keyboard.ShowPrevious" = "Sken udfir"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Dashboard.Followers" = "imeḍfaren"; +"Scene.Profile.Dashboard.Following" = "iṭafaṛ"; +"Scene.Profile.Dashboard.Posts" = "tisuffaɣ"; +"Scene.Profile.Fields.AddRow" = "Rnu izirig"; +"Scene.Profile.Fields.Placeholder.Content" = "Agbur"; +"Scene.Profile.Fields.Placeholder.Label" = "Tabzimt"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Sentem asewḥel n %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Sewḥel amiḍan"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Sentem asgugem i %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Sgugem amiḍan"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Sentem tukksa n usgugem i %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Kkes asewḥel i umiḍan"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Sentem tukksa n usgugem i %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Kkes asgugem i umiḍan"; +"Scene.Profile.SegmentedControl.About" = "Ɣef"; +"Scene.Profile.SegmentedControl.Media" = "Amidya"; +"Scene.Profile.SegmentedControl.Posts" = "Imagraden"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Tisuffaɣ d tririyin"; +"Scene.Profile.SegmentedControl.Replies" = "Tiririyin"; +"Scene.Register.Error.Item.Agreement" = "Amtawa"; +"Scene.Register.Error.Item.Email" = "Imayl"; +"Scene.Register.Error.Item.Locale" = "Tadigant"; +"Scene.Register.Error.Item.Password" = "Awal uffir"; +"Scene.Register.Error.Item.Reason" = "Taɣẓint"; +"Scene.Register.Error.Item.Username" = "Isem n useqdac"; +"Scene.Register.Error.Reason.Accepted" = "%@ ilaq ad yettwaqbal"; +"Scene.Register.Error.Reason.Blank" = "isra %@"; +"Scene.Register.Error.Reason.Blocked" = "%@ deg-s asaǧǧăw n yimayl ur nettusireg ara"; +"Scene.Register.Error.Reason.Inclusion" = "%@ mačči d azal yettusefraken"; +"Scene.Register.Error.Reason.Invalid" = "%@ d arameɣtu"; +"Scene.Register.Error.Reason.Reserved" = "%@ d awal uffir yettwaḥarren"; +"Scene.Register.Error.Reason.Taken" = "%@ yettwaseqdec yakan"; +"Scene.Register.Error.Reason.TooLong" = "%@ ɣezzif aṭas"; +"Scene.Register.Error.Reason.TooShort" = "%@ wezzil aṭas"; +"Scene.Register.Error.Reason.Unreachable" = "%@ ur yettban ara yella"; +"Scene.Register.Error.Special.EmailInvalid" = "Tagi mačči d tansa n yimayl tameɣtut"; +"Scene.Register.Error.Special.PasswordTooShort" = "Awal uffir wezzil aṭas (ilaq ad yesɛu ma drus 8 yisekkilen)"; +"Scene.Register.Error.Special.UsernameInvalid" = "Isem n useqdac ilaq ad yesɛu kan isekkilen igmumḍinen d wid yettujerrden"; +"Scene.Register.Error.Special.UsernameTooLong" = "Isem n useqdac ɣezzif aṭas (ur ilaq ara ad iɛeddi nnig 30 yisekkilen)"; +"Scene.Register.Input.Avatar.Delete" = "Kkes"; +"Scene.Register.Input.DisplayName.Placeholder" = "isem ara d-yettwaskanen"; +"Scene.Register.Input.Email.Placeholder" = "imayl"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Acimi tebγiḍ ad ternuḍ iman-ik?"; +"Scene.Register.Input.Password.Accessibility.Checked" = "yettwasenqed"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "ur yettwasenqed ara"; +"Scene.Register.Input.Password.CharacterLimit" = "8 n yisekkilen"; +"Scene.Register.Input.Password.Hint" = "Awal-ik uffir yesra ma drus ṭam n yisekkilen"; +"Scene.Register.Input.Password.Placeholder" = "awal uffir"; +"Scene.Register.Input.Password.Require" = "Awal-ik uffir yesra ma drus:"; +"Scene.Register.Input.Username.DuplicatePrompt" = "Isem-ayi n umseqdac yettwaṭṭef yakan."; +"Scene.Register.Input.Username.Placeholder" = "isem n useqdac"; +"Scene.Register.Title" = "Aha ad nebdu asbadu ɣef %@"; +"Scene.Report.Content1" = "Tebɣiḍ ad ternuḍ tisuffaɣ-nniḍen ɣer uneqqis?"; +"Scene.Report.Content2" = "Yella wayen i ilaqen ad teẓren yimḍebbren ɣef uneqqis-a?"; +"Scene.Report.ReportSentTitle" = "Tanemmirt ɣef uneqqis, ad nwali deg waya."; +"Scene.Report.Reported" = "YETTWAMMEL"; +"Scene.Report.Send" = "Azen aneqis"; +"Scene.Report.SkipToSend" = "Azen s war awennit"; +"Scene.Report.Step1" = "Aḥric 1 seg 2"; +"Scene.Report.Step2" = "Aḥric 2 seg 2"; +"Scene.Report.TextPlaceholder" = "Aru neɣ senteḍ iwenniten-nniḍen"; +"Scene.Report.Title" = "Aneqqis %@"; +"Scene.Report.TitleReport" = "Aneqqis"; +"Scene.Search.Recommend.Accounts.Description" = "Ahat tebɣiḍ ad tḍefreḍ imiḍanen-a"; +"Scene.Search.Recommend.Accounts.Follow" = "Ḍfeṛ"; +"Scene.Search.Recommend.Accounts.Title" = "Imiḍanen i tzemreḍ ad tḥemmleḍ"; +"Scene.Search.Recommend.ButtonText" = "Wali akk"; +"Scene.Search.Recommend.HashTag.Description" = "Hashtags i d-ijebbden aṭas lwelha"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ yimdanen i yettmeslayen"; +"Scene.Search.Recommend.HashTag.Title" = "Ayen mucaɛen ɣef Mastodon"; +"Scene.Search.SearchBar.Cancel" = "Sefsex"; +"Scene.Search.SearchBar.Placeholder" = "Nadi hashtags d yiseqdacen"; +"Scene.Search.Searching.Clear" = "Sfeḍ"; +"Scene.Search.Searching.EmptyState.NoResults" = "Ulac igemmaḍ"; +"Scene.Search.Searching.RecentSearch" = "Inadiyen imaynuten"; +"Scene.Search.Searching.Segment.All" = "Akk"; +"Scene.Search.Searching.Segment.Hashtags" = "Ihacṭagen"; +"Scene.Search.Searching.Segment.People" = "Imdanen"; +"Scene.Search.Searching.Segment.Posts" = "Tisuffaɣ"; +"Scene.Search.Title" = "Nadi"; +"Scene.ServerPicker.Button.Category.Academia" = "akadimi"; +"Scene.ServerPicker.Button.Category.Activism" = "tinuɣmest"; +"Scene.ServerPicker.Button.Category.All" = "Akk"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "Taggayt: Akk"; +"Scene.ServerPicker.Button.Category.Art" = "taẓuri"; +"Scene.ServerPicker.Button.Category.Food" = "učči"; +"Scene.ServerPicker.Button.Category.Furry" = "furry"; +"Scene.ServerPicker.Button.Category.Games" = "uraren"; +"Scene.ServerPicker.Button.Category.General" = "amatu"; +"Scene.ServerPicker.Button.Category.Journalism" = "taɣamsa"; +"Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; +"Scene.ServerPicker.Button.Category.Music" = "aẓawan"; +"Scene.ServerPicker.Button.Category.Regional" = "amnaḍan"; +"Scene.ServerPicker.Button.Category.Tech" = "atiknikan"; +"Scene.ServerPicker.Button.SeeLess" = "Sken cwiṭ"; +"Scene.ServerPicker.Button.SeeMore" = "Wali ugar"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "Tella-d tuccḍa lawan n usali n yisefka. Senqed tuqqna-ink internet."; +"Scene.ServerPicker.EmptyState.FindingServers" = "Tifin n yiqeddacen yellan..."; +"Scene.ServerPicker.EmptyState.NoResults" = "Ulac igemmaḍ"; +"Scene.ServerPicker.Input.Placeholder" = "Nadi timɣiwnin"; +"Scene.ServerPicker.Label.Category" = "TAGGAYT"; +"Scene.ServerPicker.Label.Language" = "TUTLAYT"; +"Scene.ServerPicker.Label.Users" = "ISEQDACEN"; +"Scene.ServerPicker.Subtitle" = "Fren tamɣiwent almend n wayen tḥemmleḍ, n tmurt-ik neɣ n yiswi-inek amatu."; +"Scene.ServerPicker.SubtitleExtend" = "Fren tamɣiwent almend n wayen tḥemmleḍ, n tmurt-ik neɣ n yiswi-inek amatu. Yal tamɣiwent tsedday-itt tkebbanit neɣ amdan ilelliyen."; +"Scene.ServerPicker.Title" = "Mastodon yettwaxdem i yiseqdacen deg waṭas n temɣiwnin."; +"Scene.ServerRules.Button.Confirm" = "Qebleɣ"; +"Scene.ServerRules.PrivacyPolicy" = "tasertit tabaḍnit"; +"Scene.ServerRules.Prompt" = "Mi ara tkemmleḍ, ilaq ad tqebleḍ tiwtilin n yimeẓla d tsertit tabaḍnit n %@."; +"Scene.ServerRules.Subtitle" = "Ilugan-a ttusbadun sɣur inedbalen n %@."; +"Scene.ServerRules.TermsOfService" = "tiwetlin n useqdec"; +"Scene.ServerRules.Title" = "Kra n yilugan igejdanen."; +"Scene.Settings.Footer.MastodonDescription" = "Maṣṭudun d aseɣzan s uɣbalu yeldin. Tzemreḍ ad temmleḍ uguren deg GitHub %@ (%@)"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "Mdel asfaylu n iɣewwaṛen"; +"Scene.Settings.Section.Appearance.Automatic" = "Awurman"; +"Scene.Settings.Section.Appearance.Dark" = "Yezga d aberkan"; +"Scene.Settings.Section.Appearance.Light" = "Yezga d aceεlal"; +"Scene.Settings.Section.Appearance.Title" = "Apparence"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Iɣewwaṛen n umiḍan"; +"Scene.Settings.Section.BoringZone.Privacy" = "Tasertit tabaḍnit"; +"Scene.Settings.Section.BoringZone.Terms" = "Tiwtilin n useqdec"; +"Scene.Settings.Section.BoringZone.Title" = "Tamnaḍt yessefcalen"; +"Scene.Settings.Section.LookAndFeel.Light" = "Aceɛlal"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "D aberkan s tidet"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "D aberkan cwiya"; +"Scene.Settings.Section.LookAndFeel.Title" = "Wali, tḥalfuḍ"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "Seqdec anagraw"; +"Scene.Settings.Section.Notifications.Boosts" = "Yules asuffeɣ n tduffeɣt-iw"; +"Scene.Settings.Section.Notifications.Favorites" = "Yerna tasuffeɣt-iw ɣer yismenyafen-ines"; +"Scene.Settings.Section.Notifications.Follows" = "Yeṭṭafar-iyi"; +"Scene.Settings.Section.Notifications.Mentions" = "Ibder-iyi-d"; +"Scene.Settings.Section.Notifications.Title" = "Tilɣa"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "yal yiwen"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "yal win ara ḍefreɣ"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "ameḍfar"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "ula yiwen"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "Selɣu-yi-d mi ara"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Sens ivaṭaren yettembiwilen"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Sens imujiten yettembiwilen"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; +"Scene.Settings.Section.Preference.Title" = "Imenyafen"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Askar aberkan n tidet"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Seqdec iminig amezwer i twaledyawt n yiseɣwan"; +"Scene.Settings.Section.SpicyZone.Clear" = "Sfeḍ takatut tuffirt n umidyat"; +"Scene.Settings.Section.SpicyZone.Signout" = "Senser"; +"Scene.Settings.Section.SpicyZone.Title" = "Tamnaḍt tamihawt"; +"Scene.Settings.Title" = "Iɣewwaṛen"; +"Scene.SuggestionAccount.FollowExplain" = "Mi ara teṭṭafareḍ albaɛḍ, ad twaliḍ tisuffaɣ-is deg usuddem-inek agejdan."; +"Scene.SuggestionAccount.Title" = "Af imdanen ara tḍefreḍ"; +"Scene.Thread.BackTitle" = "Amagrad"; +"Scene.Thread.Title" = "Tasuffeɣt sɣur %@"; +"Scene.Welcome.GetStarted" = "Aha bdu tura"; +"Scene.Welcome.LogIn" = "Qqen"; +"Scene.Welcome.Slogan" = "Izeḍwa inmettiyen +uɣalen-d ɣer ufus-ik."; +"Scene.Wizard.AccessibilityHint" = "Sin isitiyen i usefsex n umarag-a"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Beddel gar waṭas n yimiḍanen s tussda ɣezzifen ɣef tqeffalt n umaɣnu."; +"Scene.Wizard.NewInMastodon" = "Amaynut deg Maṣṭudun"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict new file mode 100644 index 00000000..c3d72dda --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/kab.lproj/Localizable.stringsdict @@ -0,0 +1,406 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 wulɣu ur nettwaɣra + other + %ld yilɣa ur nettwaɣra + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Talast n unekcum tɛedda %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 usekkil + other + %ld yisekkilen + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Talast n unekcum yeqqim-d seg-s %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 usekkil + other + %ld yisekkilen + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + tasuffeɣt + other + tisuffaɣ + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 tsuffeɣt + other + %ld n tsuffaɣ + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1unurif + other + %ld yinurifen + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1uɛiwed n usuffeɣ + other + %ld n uɛiwed n usuffeɣ + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 tefrant + other + %ld tefranin + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1umefran + other + %ld imefranen + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 umdan i yettmeslayen + other + %ld yimdanen i yettmeslayen + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 uneḍfar + other + %ld yineḍfaren + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 uneḍfar + other + %ld yineḍfaren + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Yeqqim-d 1 useggas + other + Qqimen-d %ld yiseggasen + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 wayyur i d-yeqqimen + other + %ld wayyuren i d-yeqqimen + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Yeqqim-d 1 wass + other + Qqimen-d %ld wussan + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Yeqqim-d 1 usrag + other + Qqimen-d %ld yisragen + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 tesdat i d-yeqqimen + other + %ld tesdatin i d-yeqqimen + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 tasint i d-yeqqimen + other + %ld tsinin i d-yeqqimen + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 useggas aya + other + %ld yiseggasen aya + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 wayyur aya + other + %ld wayyuren aya + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 wass aya + other + %ld wussan aya + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 usrag aya + other + %ld yisragen aya + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 tesdat aya + other + %ld tesdatin aya + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 tasint aya + other + %ld tsinin aya + + + + diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings index 02c8be66..dcc8dae6 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings @@ -98,6 +98,10 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Status.Actions.Menu" = "Kulîn"; "Common.Controls.Status.Actions.Reblog" = "Ji nû ve nivîsandin"; "Common.Controls.Status.Actions.Reply" = "Bersivê bide"; +"Common.Controls.Status.Actions.ShowGif" = "GIF nîşan bide"; +"Common.Controls.Status.Actions.ShowImage" = "Wêneyê nîşan bide"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Lêdera vîdyoyê nîşan bide"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Ji bo nîşandana menuyê dirêj bitikîne"; "Common.Controls.Status.Actions.Unfavorite" = "Nebijarte"; "Common.Controls.Status.Actions.Unreblog" = "Ji nû ve nivîsandinê vegere"; "Common.Controls.Status.ContentWarning" = "Hişyariya naverokê"; @@ -112,6 +116,7 @@ Jkx girêdana înternetê xwe kontrol bike."; "Common.Controls.Status.Tag.Link" = "Girêdan"; "Common.Controls.Status.Tag.Mention" = "Qalkirin"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Ji bo dîtinê bitikîne"; "Common.Controls.Status.UserReblogged" = "%@ ji nû ve nivîsand"; "Common.Controls.Status.UserRepliedTo" = "Bersiv da %@"; "Common.Controls.Status.Visibility.Direct" = "Tenê bikarhênerê qalkirî dikare vê şandiyê bibîne."; @@ -196,6 +201,10 @@ Profîla te ji wan ra wiha xuya dike."; "Scene.ConfirmEmail.Subtitle" = "Me tenê e-nameyek ji %@ re şand, girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.ConfirmEmail.Title" = "Tiştekî dawî."; +"Scene.Discovery.Tabs.ForYou" = "Ji bo te"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtag"; +"Scene.Discovery.Tabs.News" = "Nûçe"; +"Scene.Discovery.Tabs.Posts" = "Şandî"; "Scene.Favorite.Title" = "Bijarteyên te"; "Scene.Follower.Footer" = "Şopîner ji rajekerên din nayê dîtin."; "Scene.Following.Footer" = "Şopandin ji rajekerên din nayê dîtin."; @@ -217,6 +226,10 @@ girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.Preview.Keyboard.ClosePreview" = "Pêşdîtin bigire"; "Scene.Preview.Keyboard.ShowNext" = "A pêş nîşan bide"; "Scene.Preview.Keyboard.ShowPrevious" = "A paş nîşan bide"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Ducaran bitikîne bo vekirina listeyê"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Wêneya avatar serrast bike"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Wêneya avatar nîşan bide"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Wêneya paşrûyê nîşan bide"; "Scene.Profile.Dashboard.Followers" = "şopîner"; "Scene.Profile.Dashboard.Following" = "dişopîne"; "Scene.Profile.Dashboard.Posts" = "şandî"; @@ -357,6 +370,7 @@ Her kîjan rajekar be."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Min agahdar bike gava"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Avatarên anîmasyonî neçalak bike"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Emojiyên anîmasyonî neçalak bike"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Girêdanan di Mastodon de veke"; "Scene.Settings.Section.Preference.Title" = "Sazkarî"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Moda tarî ya reş a rastîn"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Ji bo vekirina girêdanan geroka berdest bi kar bîne"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.stringsdict index 8ae1b812..0fa7d821 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld ji nû ve nivîsandin + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 bersiv + other + %ld bersiv + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings index f9e04622..31148373 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.strings @@ -97,6 +97,10 @@ "Common.Controls.Status.Actions.Menu" = "Menu"; "Common.Controls.Status.Actions.Reblog" = "Delen"; "Common.Controls.Status.Actions.Reply" = "Reageren"; +"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Show image"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; "Common.Controls.Status.Actions.Unfavorite" = "Verwijderen uit Favorieten"; "Common.Controls.Status.Actions.Unreblog" = "Delen ongedaan maken"; "Common.Controls.Status.ContentWarning" = "Inhoudswaarschuwing"; @@ -111,6 +115,7 @@ "Common.Controls.Status.Tag.Link" = "Link"; "Common.Controls.Status.Tag.Mention" = "Vermelden"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Tap to reveal"; "Common.Controls.Status.UserReblogged" = "%@ gedeeld"; "Common.Controls.Status.UserRepliedTo" = "Reactie op %@"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; @@ -190,6 +195,10 @@ Uw profiel ziet er zo uit voor hen."; "Scene.ConfirmEmail.Subtitle" = "We hebben een e-mail gestuurd naar %@, klik op de link om uw account te bevestigen."; "Scene.ConfirmEmail.Title" = "Nog één ding."; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "Scene.Favorite.Title" = "Uw favorieten"; "Scene.Follower.Footer" = "Followers from other servers are not displayed."; "Scene.Following.Footer" = "Follows from other servers are not displayed."; @@ -211,6 +220,10 @@ klik op de link om uw account te bevestigen."; "Scene.Preview.Keyboard.ClosePreview" = "Voorbeeldweergave Sluiten"; "Scene.Preview.Keyboard.ShowNext" = "Volgende"; "Scene.Preview.Keyboard.ShowPrevious" = "Vorige"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; "Scene.Profile.Dashboard.Followers" = "volgers"; "Scene.Profile.Dashboard.Following" = "volgend"; "Scene.Profile.Dashboard.Posts" = "berichten"; @@ -350,6 +363,7 @@ klik op de link om uw account te bevestigen."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Stuur mij een melding wanneer"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Geanimeerde avatars uitschakelen"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Geanimeerde emojis uitschakelen"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; "Scene.Settings.Section.Preference.Title" = "Instellingen"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Echt zwarte donker uiterlijk"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Gebruik de standaard browser om links te openen"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.stringsdict index 8b6ab05c..5ae33cbe 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/nl.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld gedeelde berichten + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings index aa05910c..d7ed34c3 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.strings @@ -4,7 +4,7 @@ "Common.Alerts.CleanCache.Title" = "Очистка кэша"; "Common.Alerts.Common.PleaseTryAgain" = "Пожалуйста, попробуйте ещё раз."; "Common.Alerts.Common.PleaseTryAgainLater" = "Пожалуйста, попробуйте позже."; -"Common.Alerts.DeletePost.Message" = "Are you sure you want to delete this post?"; +"Common.Alerts.DeletePost.Message" = "Вы уверены, что хотите удалить этот пост?"; "Common.Alerts.DeletePost.Title" = "Вы уверены, что хотите удалить этот пост?"; "Common.Alerts.DiscardPostContent.Message" = "Вы действительно хотите удалить набранное содержимое поста?"; "Common.Alerts.DiscardPostContent.Title" = "Удалить черновик"; @@ -69,7 +69,7 @@ "Common.Controls.Friendship.Follow" = "Подписаться"; "Common.Controls.Friendship.Following" = "В подписках"; "Common.Controls.Friendship.Mute" = "Игнорировать"; -"Common.Controls.Friendship.MuteUser" = "Добавить %@ в игнорируемые"; +"Common.Controls.Friendship.MuteUser" = "Игнорировать %@"; "Common.Controls.Friendship.Muted" = "В игнорируемых"; "Common.Controls.Friendship.Pending" = "Отправлен"; "Common.Controls.Friendship.Request" = "Отправить запрос"; @@ -94,10 +94,14 @@ "Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Добавить или убрать из избранного"; "Common.Controls.Keyboard.Timeline.ToggleReblog" = "Продвинуть или убрать продвижение"; "Common.Controls.Status.Actions.Favorite" = "Добавить в избранное"; -"Common.Controls.Status.Actions.Hide" = "Hide"; +"Common.Controls.Status.Actions.Hide" = "Скрыть"; "Common.Controls.Status.Actions.Menu" = "Меню"; "Common.Controls.Status.Actions.Reblog" = "Продвинуть"; "Common.Controls.Status.Actions.Reply" = "Ответить"; +"Common.Controls.Status.Actions.ShowGif" = "Показать GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Показать изображение"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Показать видеопроигрыватель"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Нажмите и удерживайте, чтобы показать меню"; "Common.Controls.Status.Actions.Unfavorite" = "Убрать из избранного"; "Common.Controls.Status.Actions.Unreblog" = "Убрать продвижение"; "Common.Controls.Status.ContentWarning" = "Предупреждение о содержании"; @@ -112,6 +116,7 @@ "Common.Controls.Status.Tag.Link" = "Ссылка"; "Common.Controls.Status.Tag.Mention" = "Упоминание"; "Common.Controls.Status.Tag.Url" = "Ссылка"; +"Common.Controls.Status.TapToReveal" = "Нажмите, чтобы показать"; "Common.Controls.Status.UserReblogged" = "%@ продвинул(а)"; "Common.Controls.Status.UserRepliedTo" = "Ответил(а) %@"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; @@ -193,7 +198,7 @@ "Scene.Compose.Visibility.Public" = "Публичный"; "Scene.Compose.Visibility.Unlisted" = "Скрытый"; "Scene.ConfirmEmail.Button.OpenEmailApp" = "Открыть приложение почты"; -"Scene.ConfirmEmail.Button.Resend" = "Resend"; +"Scene.ConfirmEmail.Button.Resend" = "Отправить заново"; "Scene.ConfirmEmail.DontReceiveEmail.Description" = "Проверьте, правильно ли указан ваш e-mail адрес, а также папку «спам», если ещё не сделали этого."; "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Отправить ещё раз"; "Scene.ConfirmEmail.DontReceiveEmail.Title" = "Проверьте свой e-mail адрес"; @@ -206,6 +211,10 @@ Нажмите на ссылку в нём, чтобы подтвердить свою учётную запись."; "Scene.ConfirmEmail.Title" = "И ещё кое-что."; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "Scene.Favorite.Title" = "Ваше избранное"; "Scene.Follower.Footer" = "Followers from other servers are not displayed."; "Scene.Following.Footer" = "Follows from other servers are not displayed."; @@ -227,6 +236,10 @@ "Scene.Preview.Keyboard.ClosePreview" = "Закрыть предпросмотр"; "Scene.Preview.Keyboard.ShowNext" = "Следующее изображение"; "Scene.Preview.Keyboard.ShowPrevious" = "Предыдущее изображение"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; "Scene.Profile.Dashboard.Followers" = "подписчики"; "Scene.Profile.Dashboard.Following" = "подписки"; "Scene.Profile.Dashboard.Posts" = "посты"; @@ -244,7 +257,7 @@ "Scene.Profile.SegmentedControl.About" = "About"; "Scene.Profile.SegmentedControl.Media" = "Медиа"; "Scene.Profile.SegmentedControl.Posts" = "Посты"; -"Scene.Profile.SegmentedControl.PostsAndReplies" = "Posts and Replies"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Посты и ответы"; "Scene.Profile.SegmentedControl.Replies" = "Ответы"; "Scene.Register.Error.Item.Agreement" = "Соглашение"; "Scene.Register.Error.Item.Email" = "E-mail"; @@ -289,7 +302,7 @@ "Scene.Report.Step2" = "Шаг 2 из 2"; "Scene.Report.TextPlaceholder" = "Дополнительные комментарии"; "Scene.Report.Title" = "Пожаловаться на %@"; -"Scene.Report.TitleReport" = "Report"; +"Scene.Report.TitleReport" = "Жалоба"; "Scene.Search.Recommend.Accounts.Description" = "Возможно, вы захотите подписаться на эти профили"; "Scene.Search.Recommend.Accounts.Follow" = "Подписаться"; "Scene.Search.Recommend.Accounts.Title" = "Вам может понравится"; @@ -367,6 +380,7 @@ "Scene.Settings.Section.Notifications.Trigger.Title" = "Уведомлять меня, когда"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Отключить анимацию аватарок"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Отключить анимацию эмодзи"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Открывать ссылки в Мастодоне"; "Scene.Settings.Section.Preference.Title" = "Предпочтения"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Полноценно чёрный режим"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Использовать браузер по умолчанию для открытия ссылок"; @@ -378,10 +392,10 @@ "Scene.SuggestionAccount.Title" = "Подпишитесь на людей"; "Scene.Thread.BackTitle" = "Пост"; "Scene.Thread.Title" = "Пост %@"; -"Scene.Welcome.GetStarted" = "Get Started"; +"Scene.Welcome.GetStarted" = "Присоединиться"; "Scene.Welcome.LogIn" = "Вход"; "Scene.Welcome.Slogan" = "Социальная сеть под вашим контролем."; "Scene.Wizard.AccessibilityHint" = "Double tap to dismiss this wizard"; "Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Switch between multiple accounts by holding the profile button."; -"Scene.Wizard.NewInMastodon" = "New in Mastodon"; \ No newline at end of file +"Scene.Wizard.NewInMastodon" = "Новое в Мастодоне"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.stringsdict index 96afce4e..626a10d7 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ru.lproj/Localizable.stringsdict @@ -142,6 +142,26 @@ %ld продвинули + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + few + %ld replies + many + %ld replies + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.strings index 738a9ac0..434303e3 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.strings @@ -98,6 +98,10 @@ Tarkista internet-yhteytesi."; "Common.Controls.Status.Actions.Menu" = "Valikko"; "Common.Controls.Status.Actions.Reblog" = "Jaa edelleen"; "Common.Controls.Status.Actions.Reply" = "Vastaa"; +"Common.Controls.Status.Actions.ShowGif" = "Show GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Show image"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Show video player"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Tap then hold to show menu"; "Common.Controls.Status.Actions.Unfavorite" = "Unfavorite"; "Common.Controls.Status.Actions.Unreblog" = "Peru edelleen jako"; "Common.Controls.Status.ContentWarning" = "Sisältövaroitus"; @@ -112,6 +116,7 @@ Tarkista internet-yhteytesi."; "Common.Controls.Status.Tag.Link" = "Linkki"; "Common.Controls.Status.Tag.Mention" = "Mention"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Tap to reveal"; "Common.Controls.Status.UserReblogged" = "%@ jakoi edelleen"; "Common.Controls.Status.UserRepliedTo" = "Vastasi %@:lle"; "Common.Controls.Status.Visibility.Direct" = "Only mentioned user can see this post."; @@ -195,6 +200,10 @@ uploaded to Mastodon."; "Scene.ConfirmEmail.OpenEmailApp.Title" = "Tarkasta postilaatikkosi."; "Scene.ConfirmEmail.Subtitle" = "Lähetimme juuri sähköpostin osoitteeseen %@, napauta siinä olevaa linkkiä vahvistaaksesi tilisi."; "Scene.ConfirmEmail.Title" = "Viimeinen asia."; +"Scene.Discovery.Tabs.ForYou" = "For You"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtags"; +"Scene.Discovery.Tabs.News" = "News"; +"Scene.Discovery.Tabs.Posts" = "Posts"; "Scene.Favorite.Title" = "Omat suosikit"; "Scene.Follower.Footer" = "Seuraajia muilta palvelimilta ei näytetä."; "Scene.Following.Footer" = "Seurauksia muilta palvelimilta ei näytetä."; @@ -216,6 +225,10 @@ uploaded to Mastodon."; "Scene.Preview.Keyboard.ClosePreview" = "Sulje esikatselu"; "Scene.Preview.Keyboard.ShowNext" = "Näytä seuraava"; "Scene.Preview.Keyboard.ShowPrevious" = "Näytä edellinen"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; "Scene.Profile.Dashboard.Followers" = "seuraajat"; "Scene.Profile.Dashboard.Following" = "seurataan"; "Scene.Profile.Dashboard.Posts" = "julkaisut"; @@ -356,6 +369,7 @@ mikä tahansa palvelin."; "Scene.Settings.Section.Notifications.Trigger.Title" = "Ilmoita minulle, kun"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Poista käytöstä animoidut avatarit"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Poista käytöstä animoidut emojit"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Open links in Mastodon"; "Scene.Settings.Section.Preference.Title" = "Lisäasetukset"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Todellinen mustan tumma tila"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Käytä oletusselainta linkkien avaamiseen"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.stringsdict index eec977a6..43231214 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/sv_FI.lproj/Localizable.stringsdict @@ -114,6 +114,22 @@ %ld edelleen jakoa + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings index c79bb681..5dfb5134 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings @@ -98,6 +98,10 @@ "Common.Controls.Status.Actions.Menu" = "เมนู"; "Common.Controls.Status.Actions.Reblog" = "ดัน"; "Common.Controls.Status.Actions.Reply" = "ตอบกลับ"; +"Common.Controls.Status.Actions.ShowGif" = "แสดง GIF"; +"Common.Controls.Status.Actions.ShowImage" = "แสดงภาพ"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "แสดงตัวเล่นวิดีโอ"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "แตะค้างไว้เพื่อแสดงเมนู"; "Common.Controls.Status.Actions.Unfavorite" = "เลิกชื่นชอบ"; "Common.Controls.Status.Actions.Unreblog" = "เลิกทำการดัน"; "Common.Controls.Status.ContentWarning" = "คำเตือนเนื้อหา"; @@ -112,6 +116,7 @@ "Common.Controls.Status.Tag.Link" = "ลิงก์"; "Common.Controls.Status.Tag.Mention" = "กล่าวถึง"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "แตะเพื่อเปิดเผย"; "Common.Controls.Status.UserReblogged" = "%@ ได้ดัน"; "Common.Controls.Status.UserRepliedTo" = "ตอบกลับ %@"; "Common.Controls.Status.Visibility.Direct" = "เฉพาะผู้ใช้ที่กล่าวถึงเท่านั้นที่สามารถเห็นโพสต์นี้"; @@ -195,6 +200,10 @@ "Scene.ConfirmEmail.OpenEmailApp.Title" = "ตรวจสอบกล่องขาเข้าของคุณ"; "Scene.ConfirmEmail.Subtitle" = "แตะลิงก์ที่เราส่งอีเมลถึงคุณเพื่อยืนยันบัญชีของคุณ"; "Scene.ConfirmEmail.Title" = "หนึ่งสิ่งสุดท้าย"; +"Scene.Discovery.Tabs.ForYou" = "สำหรับคุณ"; +"Scene.Discovery.Tabs.Hashtags" = "แฮชแท็ก"; +"Scene.Discovery.Tabs.News" = "ข่าว"; +"Scene.Discovery.Tabs.Posts" = "โพสต์"; "Scene.Favorite.Title" = "รายการโปรดของคุณ"; "Scene.Follower.Footer" = "ไม่ได้แสดงผู้ติดตามจากเซิร์ฟเวอร์อื่น ๆ"; "Scene.Following.Footer" = "ไม่ได้แสดงการติดตามจากเซิร์ฟเวอร์อื่น ๆ"; @@ -216,6 +225,10 @@ "Scene.Preview.Keyboard.ClosePreview" = "ปิดตัวอย่าง"; "Scene.Preview.Keyboard.ShowNext" = "แสดงถัดไป"; "Scene.Preview.Keyboard.ShowPrevious" = "แสดงก่อนหน้า"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "แตะสองครั้งเพื่อเปิดรายการ"; +"Scene.Profile.Accessibility.EditAvatarImage" = "แก้ไขภาพประจำตัว"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "แสดงภาพประจำตัว"; +"Scene.Profile.Accessibility.ShowBannerImage" = "แสดงภาพแบนเนอร์"; "Scene.Profile.Dashboard.Followers" = "ผู้ติดตาม"; "Scene.Profile.Dashboard.Following" = "กำลังติดตาม"; "Scene.Profile.Dashboard.Posts" = "โพสต์"; @@ -355,6 +368,7 @@ "Scene.Settings.Section.Notifications.Trigger.Title" = "แจ้งเตือนฉันเมื่อ"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "ปิดใช้งานภาพประจำตัวแบบเคลื่อนไหว"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "ปิดใช้งานอีโมจิแบบเคลื่อนไหว"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "เปิดลิงก์ใน Mastodon"; "Scene.Settings.Section.Preference.Title" = "การกำหนดลักษณะ"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "โหมดมืดดำสนิท"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "ใช้เบราว์เซอร์เริ่มต้นเพื่อเปิดลิงก์"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict index 8971821f..8ae8feb7 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.stringsdict @@ -100,6 +100,20 @@ %ld การดัน + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld การตอบกลับ + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings new file mode 100644 index 00000000..407c4f9c --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.strings @@ -0,0 +1,389 @@ +"Common.Alerts.BlockDomain.BlockEntireDomain" = "Chặn máy chủ"; +"Common.Alerts.BlockDomain.Title" = "Bạn thật sự muốn ẩn toàn bộ nội dung từ %@? Sẽ hợp lý hơn nếu bạn chỉ chặn hoặc ẩn một vài tài khoản cụ thể. Ẩn toàn bộ nội dung từ máy chủ sẽ khiến bạn không còn thấy nội dung từ máy chủ đó ở bất kỳ nơi nào, kể cả thông báo. Người theo dõi bạn từ máy chủ đó cũng sẽ bị xóa luôn."; +"Common.Alerts.CleanCache.Message" = "Đã xóa %@ bộ nhớ đệm."; +"Common.Alerts.CleanCache.Title" = "Xóa bộ nhớ đệm"; +"Common.Alerts.Common.PleaseTryAgain" = "Vui lòng thử lại."; +"Common.Alerts.Common.PleaseTryAgainLater" = "Vui lòng thử lại sau."; +"Common.Alerts.DeletePost.Message" = "Bạn có chắc muốn xóa tút này không?"; +"Common.Alerts.DeletePost.Title" = "Xóa tút"; +"Common.Alerts.DiscardPostContent.Message" = "Xác nhận bỏ qua nội dung tút đã viết."; +"Common.Alerts.DiscardPostContent.Title" = "Hủy bản nháp"; +"Common.Alerts.EditProfileFailure.Message" = "Không thể chỉnh sửa hồ sơ. Vui lòng thử lại."; +"Common.Alerts.EditProfileFailure.Title" = "Lỗi chỉnh sửa hồ sơ"; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Không thể đính kèm nhiều video."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Không thể đính kèm video cùng với hình ảnh."; +"Common.Alerts.PublishPostFailure.Message" = "Không thể đăng tút. +Vui lòng kiểm tra kết nối mạng."; +"Common.Alerts.PublishPostFailure.Title" = "Đăng tút không thành công"; +"Common.Alerts.SavePhotoFailure.Message" = "Vui lòng cho phép quyền truy cập thư viện hình ảnh để lưu hình ảnh về máy."; +"Common.Alerts.SavePhotoFailure.Title" = "Lưu hình ảnh không thành công"; +"Common.Alerts.ServerError.Title" = "Lỗi máy chủ"; +"Common.Alerts.SignOut.Confirm" = "Đăng xuất"; +"Common.Alerts.SignOut.Message" = "Bạn có chắc muốn đăng xuất không?"; +"Common.Alerts.SignOut.Title" = "Đăng xuất"; +"Common.Alerts.SignUpFailure.Title" = "Đăng ký không thành công"; +"Common.Alerts.VoteFailure.PollEnded" = "Cuộc bình chọn đã kết thúc"; +"Common.Alerts.VoteFailure.Title" = "Bình chọn không thành công"; +"Common.Controls.Actions.Add" = "Thêm"; +"Common.Controls.Actions.Back" = "Quay lại"; +"Common.Controls.Actions.BlockDomain" = "Chặn %@"; +"Common.Controls.Actions.Cancel" = "Hủy bỏ"; +"Common.Controls.Actions.Compose" = "Viết tút"; +"Common.Controls.Actions.Confirm" = "Xác nhận"; +"Common.Controls.Actions.Continue" = "Tiếp tục"; +"Common.Controls.Actions.CopyPhoto" = "Sao chép ảnh"; +"Common.Controls.Actions.Delete" = "Xóa"; +"Common.Controls.Actions.Discard" = "Bỏ qua"; +"Common.Controls.Actions.Done" = "Xong"; +"Common.Controls.Actions.Edit" = "Sửa"; +"Common.Controls.Actions.FindPeople" = "Đề xuất theo dõi"; +"Common.Controls.Actions.ManuallySearch" = "Tự tìm kiếm thủ công"; +"Common.Controls.Actions.Next" = "Kế tiếp"; +"Common.Controls.Actions.Ok" = "OK"; +"Common.Controls.Actions.Open" = "Mở"; +"Common.Controls.Actions.OpenInBrowser" = "Mở trong trình duyệt"; +"Common.Controls.Actions.OpenInSafari" = "Mở bằng Safari"; +"Common.Controls.Actions.Preview" = "Xem trước"; +"Common.Controls.Actions.Previous" = "Trước đó"; +"Common.Controls.Actions.Remove" = "Xóa"; +"Common.Controls.Actions.Reply" = "Trả lời"; +"Common.Controls.Actions.ReportUser" = "Báo cáo %@"; +"Common.Controls.Actions.Save" = "Lưu"; +"Common.Controls.Actions.SavePhoto" = "Lưu ảnh"; +"Common.Controls.Actions.SeeMore" = "Xem thêm"; +"Common.Controls.Actions.Settings" = "Cài đặt"; +"Common.Controls.Actions.Share" = "Chia sẻ"; +"Common.Controls.Actions.SharePost" = "Chia sẻ tút"; +"Common.Controls.Actions.ShareUser" = "Chia sẻ %@"; +"Common.Controls.Actions.SignIn" = "Đăng nhập"; +"Common.Controls.Actions.SignUp" = "Đăng ký"; +"Common.Controls.Actions.Skip" = "Bỏ qua"; +"Common.Controls.Actions.TakePhoto" = "Chụp ảnh"; +"Common.Controls.Actions.TryAgain" = "Thử lại"; +"Common.Controls.Actions.UnblockDomain" = "Bỏ chặn %@"; +"Common.Controls.Friendship.Block" = "Chặn"; +"Common.Controls.Friendship.BlockDomain" = "Chặn %@"; +"Common.Controls.Friendship.BlockUser" = "Chặn %@"; +"Common.Controls.Friendship.Blocked" = "Đã chặn"; +"Common.Controls.Friendship.EditInfo" = "Chỉnh sửa"; +"Common.Controls.Friendship.Follow" = "Theo dõi"; +"Common.Controls.Friendship.Following" = "Đang theo dõi"; +"Common.Controls.Friendship.Mute" = "Ẩn"; +"Common.Controls.Friendship.MuteUser" = "Ẩn %@"; +"Common.Controls.Friendship.Muted" = "Đã ẩn"; +"Common.Controls.Friendship.Pending" = "Đang chờ"; +"Common.Controls.Friendship.Request" = "Yêu cầu"; +"Common.Controls.Friendship.Unblock" = "Bỏ chặn"; +"Common.Controls.Friendship.UnblockUser" = "Bỏ chặn %@"; +"Common.Controls.Friendship.Unmute" = "Bỏ ẩn"; +"Common.Controls.Friendship.UnmuteUser" = "Bỏ ẩn %@"; +"Common.Controls.Keyboard.Common.ComposeNewPost" = "Viết tút mới"; +"Common.Controls.Keyboard.Common.OpenSettings" = "Mở cài đặt"; +"Common.Controls.Keyboard.Common.ShowFavorites" = "Hiện lượt thích"; +"Common.Controls.Keyboard.Common.SwitchToTab" = "Chuyển thành %@"; +"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Tới phần tiếp theo"; +"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Tới phần trước"; +"Common.Controls.Keyboard.Timeline.NextStatus" = "Tút sau"; +"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Mở trang người viết tút"; +"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Mở trang người đăng lại tút"; +"Common.Controls.Keyboard.Timeline.OpenStatus" = "Mở tút"; +"Common.Controls.Keyboard.Timeline.PreviewImage" = "Xem trước hình ảnh"; +"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Tút trước"; +"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Trả lời tút"; +"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Chọn nội dung ẩn"; +"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Chọn thích tút"; +"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Chọn đăng lại tút"; +"Common.Controls.Status.Actions.Favorite" = "Thích"; +"Common.Controls.Status.Actions.Hide" = "Ẩn"; +"Common.Controls.Status.Actions.Menu" = "Menu"; +"Common.Controls.Status.Actions.Reblog" = "Đăng lại"; +"Common.Controls.Status.Actions.Reply" = "Trả lời"; +"Common.Controls.Status.Actions.ShowGif" = "Hiển thị GIF"; +"Common.Controls.Status.Actions.ShowImage" = "Hiển thị hình ảnh"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "Hiện trình phát video"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "Nhấn giữ để hiện menu"; +"Common.Controls.Status.Actions.Unfavorite" = "Bỏ thích"; +"Common.Controls.Status.Actions.Unreblog" = "Hủy đăng lại"; +"Common.Controls.Status.ContentWarning" = "Nội dung ẩn"; +"Common.Controls.Status.MediaContentWarning" = "Nhấn để hiển thị"; +"Common.Controls.Status.Poll.Closed" = "Kết thúc"; +"Common.Controls.Status.Poll.Vote" = "Bình chọn"; +"Common.Controls.Status.ShowPost" = "Xem tút"; +"Common.Controls.Status.ShowUserProfile" = "Xem trang hồ sơ"; +"Common.Controls.Status.Tag.Email" = "Email"; +"Common.Controls.Status.Tag.Emoji" = "Emoji"; +"Common.Controls.Status.Tag.Hashtag" = "Hashtag"; +"Common.Controls.Status.Tag.Link" = "Liên kết"; +"Common.Controls.Status.Tag.Mention" = "Nhắc đến"; +"Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "Nhấn để hiển thị"; +"Common.Controls.Status.UserReblogged" = "%@ đăng lại"; +"Common.Controls.Status.UserRepliedTo" = "Trả lời %@"; +"Common.Controls.Status.Visibility.Direct" = "Chỉ người được nhắc đến có thể thấy tút."; +"Common.Controls.Status.Visibility.Private" = "Chỉ người theo dõi của họ có thể thấy tút này."; +"Common.Controls.Status.Visibility.PrivateFromMe" = "Chỉ người theo dõi tôi có thể thấy tút này."; +"Common.Controls.Status.Visibility.Unlisted" = "Ai cũng thấy tút này nhưng không hiện trên bảng tin máy chủ."; +"Common.Controls.Tabs.Home" = "Bảng tin"; +"Common.Controls.Tabs.Notification" = "Thông báo"; +"Common.Controls.Tabs.Profile" = "Trang hồ sơ"; +"Common.Controls.Tabs.Search" = "Tìm kiếm"; +"Common.Controls.Timeline.Filtered" = "Bộ lọc"; +"Common.Controls.Timeline.Header.BlockedWarning" = "Bạn không thể xem trang người này +cho tới khi họ bỏ chặn bạn."; +"Common.Controls.Timeline.Header.BlockingWarning" = "Bạn không thể xem trang người này +cho tới khi bạn bỏ chặn họ. +Họ sẽ thấy trang của bạn như thế này."; +"Common.Controls.Timeline.Header.NoStatusFound" = "Không tìm thấy tút"; +"Common.Controls.Timeline.Header.SuspendedWarning" = "Người dùng đã bị vô hiệu hóa."; +"Common.Controls.Timeline.Header.UserBlockedWarning" = "Bạn không thể xem trang %@ +cho tới khi họ bỏ chặn bạn."; +"Common.Controls.Timeline.Header.UserBlockingWarning" = "Bạn không thể xem trang %@ +cho tới khi bạn bỏ chặn họ. +Họ sẽ thấy trang của bạn như thế này."; +"Common.Controls.Timeline.Header.UserSuspendedWarning" = "%@ đã bị vô hiệu hóa."; +"Common.Controls.Timeline.Loader.LoadMissingPosts" = "Tải tút chưa đọc"; +"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Đang tải tút chưa đọc..."; +"Common.Controls.Timeline.Loader.ShowMoreReplies" = "Xem lượt trả lời"; +"Common.Controls.Timeline.Timestamp.Now" = "Vừa xong"; +"Scene.AccountList.AddAccount" = "Thêm tài khoản"; +"Scene.AccountList.DismissAccountSwitcher" = "Bỏ qua chuyển đổi tài khoản"; +"Scene.AccountList.TabBarHint" = "Đang dùng tài khoản: %@. Nhấn hai lần và giữ để đổi sang tài khoản khác"; +"Scene.Compose.Accessibility.AppendAttachment" = "Thêm media"; +"Scene.Compose.Accessibility.AppendPoll" = "Tạo bình chọn"; +"Scene.Compose.Accessibility.CustomEmojiPicker" = "Chọn emoji"; +"Scene.Compose.Accessibility.DisableContentWarning" = "Tắt nội dung ẩn"; +"Scene.Compose.Accessibility.EnableContentWarning" = "Bật nội dung ẩn"; +"Scene.Compose.Accessibility.PostVisibilityMenu" = "Menu hiển thị tút"; +"Scene.Compose.Accessibility.RemovePoll" = "Xóa bình chọn"; +"Scene.Compose.Attachment.AttachmentBroken" = "%@ này bị lỗi và không thể +tải lên Mastodon."; +"Scene.Compose.Attachment.DescriptionPhoto" = "Mô tả hình ảnh cho người khiếm thị..."; +"Scene.Compose.Attachment.DescriptionVideo" = "Mô tả video cho người khiếm thị..."; +"Scene.Compose.Attachment.Photo" = "ảnh"; +"Scene.Compose.Attachment.Video" = "video"; +"Scene.Compose.AutoComplete.SpaceToAdd" = "Khoảng cách để thêm"; +"Scene.Compose.ComposeAction" = "Đăng"; +"Scene.Compose.ContentInputPlaceholder" = "Cho thế giới biết bạn đang nghĩ gì"; +"Scene.Compose.ContentWarning.Placeholder" = "Viết nội dung ẩn của bạn ở đây..."; +"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Thêm media - %@"; +"Scene.Compose.Keyboard.DiscardPost" = "Hủy đăng tút"; +"Scene.Compose.Keyboard.PublishPost" = "Đăng tút"; +"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Thay đổi quyền riêng tư - %@"; +"Scene.Compose.Keyboard.ToggleContentWarning" = "Mở nội dung ẩn"; +"Scene.Compose.Keyboard.TogglePoll" = "Mở bình chọn"; +"Scene.Compose.MediaSelection.Browse" = "Chọn"; +"Scene.Compose.MediaSelection.Camera" = "Chụp ảnh"; +"Scene.Compose.MediaSelection.PhotoLibrary" = "Thư viện hình ảnh"; +"Scene.Compose.Poll.DurationTime" = "Thời gian: %@"; +"Scene.Compose.Poll.OneDay" = "1 ngày"; +"Scene.Compose.Poll.OneHour" = "1 giờ"; +"Scene.Compose.Poll.OptionNumber" = "Lựa chọn %ld"; +"Scene.Compose.Poll.SevenDays" = "7 ngày"; +"Scene.Compose.Poll.SixHours" = "6 giờ"; +"Scene.Compose.Poll.ThirtyMinutes" = "30 phút"; +"Scene.Compose.Poll.ThreeDays" = "3 ngày"; +"Scene.Compose.ReplyingToUser" = "trả lời đến %@"; +"Scene.Compose.Title.NewPost" = "Viết tút"; +"Scene.Compose.Title.NewReply" = "Viết trả lời"; +"Scene.Compose.Visibility.Direct" = "Chỉ người được nhắc đến"; +"Scene.Compose.Visibility.Private" = "Riêng tư"; +"Scene.Compose.Visibility.Public" = "Công khai"; +"Scene.Compose.Visibility.Unlisted" = "Hạn chế"; +"Scene.ConfirmEmail.Button.OpenEmailApp" = "Mở ứng dụng email"; +"Scene.ConfirmEmail.Button.Resend" = "Gửi lại"; +"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Kiểm tra địa chỉ email của bạn đúng chưa hoặc có bị chuyển vào thư rác."; +"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Gửi lại email"; +"Scene.ConfirmEmail.DontReceiveEmail.Title" = "Kiểm tra email"; +"Scene.ConfirmEmail.OpenEmailApp.Description" = "Chúng tôi vừa gửi email cho bạn. Kiểm tra thư rác nếu bạn không thấy."; +"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Email"; +"Scene.ConfirmEmail.OpenEmailApp.OpenEmailClient" = "Mở ứng dụng email"; +"Scene.ConfirmEmail.OpenEmailApp.Title" = "Kiểm tra hộp thư của bạn."; +"Scene.ConfirmEmail.Subtitle" = "Nhấn vào liên kết chúng tôi gửi qua email để xác thực tài khoản."; +"Scene.ConfirmEmail.Title" = "Còn điều này nữa."; +"Scene.Discovery.Tabs.ForYou" = "Dành cho bạn"; +"Scene.Discovery.Tabs.Hashtags" = "Hashtag"; +"Scene.Discovery.Tabs.News" = "Tin tức"; +"Scene.Discovery.Tabs.Posts" = "Tút"; +"Scene.Favorite.Title" = "Lượt thích"; +"Scene.Follower.Footer" = "Không hiển thị người theo dõi từ máy chủ khác."; +"Scene.Following.Footer" = "Không hiển thị người bạn theo dõi từ máy chủ khác."; +"Scene.HomeTimeline.NavigationBarState.NewPosts" = "Đọc những tút mới"; +"Scene.HomeTimeline.NavigationBarState.Offline" = "Ngoại tuyến"; +"Scene.HomeTimeline.NavigationBarState.Published" = "Đã đăng!"; +"Scene.HomeTimeline.NavigationBarState.Publishing" = "Đang đăng tút..."; +"Scene.HomeTimeline.Title" = "Bảng tin"; +"Scene.Notification.Keyobard.ShowEverything" = "Hiện mọi thứ"; +"Scene.Notification.Keyobard.ShowMentions" = "Hiện lượt nhắc"; +"Scene.Notification.NotificationDescription.FavoritedYourPost" = "thích tút của bạn"; +"Scene.Notification.NotificationDescription.FollowedYou" = "đã theo dõi bạn"; +"Scene.Notification.NotificationDescription.MentionedYou" = "nhắc đến bạn"; +"Scene.Notification.NotificationDescription.PollHasEnded" = "cuộc bình chọn đã kết thúc"; +"Scene.Notification.NotificationDescription.RebloggedYourPost" = "đăng lại tút của bạn"; +"Scene.Notification.NotificationDescription.RequestToFollowYou" = "yêu cầu theo dõi bạn"; +"Scene.Notification.Title.Everything" = "Mọi thứ"; +"Scene.Notification.Title.Mentions" = "Lượt nhắc đến"; +"Scene.Preview.Keyboard.ClosePreview" = "Đóng xem trước"; +"Scene.Preview.Keyboard.ShowNext" = "Hiện kế tiếp"; +"Scene.Preview.Keyboard.ShowPrevious" = "Hiện trước đó"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Nhấn hai lần để mở danh sách"; +"Scene.Profile.Accessibility.EditAvatarImage" = "Sửa ảnh đại diện"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "Hiển thị ảnh đại diện"; +"Scene.Profile.Accessibility.ShowBannerImage" = "Hiển thị ảnh bìa"; +"Scene.Profile.Dashboard.Followers" = "người theo dõi"; +"Scene.Profile.Dashboard.Following" = "theo dõi"; +"Scene.Profile.Dashboard.Posts" = "tút"; +"Scene.Profile.Fields.AddRow" = "Thêm hàng"; +"Scene.Profile.Fields.Placeholder.Content" = "Nội dung"; +"Scene.Profile.Fields.Placeholder.Label" = "Nhãn"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Message" = "Xác nhận chặn %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmBlockUser.Title" = "Chặn người dùng"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Message" = "Xác nhận ẩn %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmMuteUser.Title" = "Ẩn người dùng"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Message" = "Xác nhận bỏ chặn %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.Title" = "Bỏ chặn người dùng"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Message" = "Xác nhận bỏ ẩn %@"; +"Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.Title" = "Bỏ ẩn người dùng"; +"Scene.Profile.SegmentedControl.About" = "Giới thiệu"; +"Scene.Profile.SegmentedControl.Media" = "Media"; +"Scene.Profile.SegmentedControl.Posts" = "Tút"; +"Scene.Profile.SegmentedControl.PostsAndReplies" = "Tút và trả lời"; +"Scene.Profile.SegmentedControl.Replies" = "Trả lời"; +"Scene.Register.Error.Item.Agreement" = "Thoả thuận"; +"Scene.Register.Error.Item.Email" = "Email"; +"Scene.Register.Error.Item.Locale" = "Cục bộ"; +"Scene.Register.Error.Item.Password" = "Mật khẩu"; +"Scene.Register.Error.Item.Reason" = "Lý do"; +"Scene.Register.Error.Item.Username" = "Tên người dùng"; +"Scene.Register.Error.Reason.Accepted" = "%@ phải được đồng ý"; +"Scene.Register.Error.Reason.Blank" = "%@ là bắt buộc"; +"Scene.Register.Error.Reason.Blocked" = "%@ dùng dịch vụ email bị cấm"; +"Scene.Register.Error.Reason.Inclusion" = "%@ chứa ký tự không được hỗ trợ"; +"Scene.Register.Error.Reason.Invalid" = "%@ không hợp lệ"; +"Scene.Register.Error.Reason.Reserved" = "%@ là một từ khóa hạn chế"; +"Scene.Register.Error.Reason.Taken" = "%@ đã tồn tại"; +"Scene.Register.Error.Reason.TooLong" = "%@ quá dài"; +"Scene.Register.Error.Reason.TooShort" = "%@ quá ngắn"; +"Scene.Register.Error.Reason.Unreachable" = "%@ không tồn tại"; +"Scene.Register.Error.Special.EmailInvalid" = "Đây không phải là một địa chỉ email khả dụng"; +"Scene.Register.Error.Special.PasswordTooShort" = "Mật khẩu của bạn quá ngắn, phải có ít nhất 8 ký tự"; +"Scene.Register.Error.Special.UsernameInvalid" = "Tên người dùng chỉ có thể chứa các ký tự chữ và số và dấu gạch dưới"; +"Scene.Register.Error.Special.UsernameTooLong" = "Tên người dùng không thể dài hơn 30 ký tự"; +"Scene.Register.Input.Avatar.Delete" = "Xóa"; +"Scene.Register.Input.DisplayName.Placeholder" = "tên hiển thị"; +"Scene.Register.Input.Email.Placeholder" = "email"; +"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Vì sao bạn muốn tham gia?"; +"Scene.Register.Input.Password.Accessibility.Checked" = "đã ổn"; +"Scene.Register.Input.Password.Accessibility.Unchecked" = "chưa ổn"; +"Scene.Register.Input.Password.CharacterLimit" = "8 ký tự"; +"Scene.Register.Input.Password.Hint" = "Mật khẩu của bạn phải dài tối thiểu 8 ký tự"; +"Scene.Register.Input.Password.Placeholder" = "mật khẩu"; +"Scene.Register.Input.Password.Require" = "Mật khẩu phải tối thiểu:"; +"Scene.Register.Input.Username.DuplicatePrompt" = "Tên người dùng đã tồn tại."; +"Scene.Register.Input.Username.Placeholder" = "tên người dùng"; +"Scene.Register.Title" = "Hãy để tôi đăng ký trên %@"; +"Scene.Report.Content1" = "Bạn muốn thêm tút nào vào báo cáo nữa không?"; +"Scene.Report.Content2" = "Kiểm duyệt viên cần biết gì về báo cáo này?"; +"Scene.Report.ReportSentTitle" = "Cảm ơn đã báo cáo, chúng tôi sẽ xem xét kỹ."; +"Scene.Report.Reported" = "ĐÃ BÁO CÁO"; +"Scene.Report.Send" = "Gửi báo cáo"; +"Scene.Report.SkipToSend" = "Gửi không ghi chú"; +"Scene.Report.Step1" = "Bước 1 trong 2"; +"Scene.Report.Step2" = "Bước 2 trong 2"; +"Scene.Report.TextPlaceholder" = "Nhập hoặc bổ sung chú thích"; +"Scene.Report.Title" = "Báo cáo %@"; +"Scene.Report.TitleReport" = "Báo cáo"; +"Scene.Search.Recommend.Accounts.Description" = "Bạn có thể muốn theo dõi những người này"; +"Scene.Search.Recommend.Accounts.Follow" = "Theo dõi"; +"Scene.Search.Recommend.Accounts.Title" = "Những người bạn có thể thích"; +"Scene.Search.Recommend.ButtonText" = "Xem tất cả"; +"Scene.Search.Recommend.HashTag.Description" = "Những hashtag đang được sử dụng nhiều nhất"; +"Scene.Search.Recommend.HashTag.PeopleTalking" = "%@ người đang thảo luận"; +"Scene.Search.Recommend.HashTag.Title" = "Xu hướng trên Mastodon"; +"Scene.Search.SearchBar.Cancel" = "Hủy bỏ"; +"Scene.Search.SearchBar.Placeholder" = "Tìm hashtag và người dùng"; +"Scene.Search.Searching.Clear" = "Xóa"; +"Scene.Search.Searching.EmptyState.NoResults" = "Không có kết quả"; +"Scene.Search.Searching.RecentSearch" = "Tìm kiếm gần đây"; +"Scene.Search.Searching.Segment.All" = "Tất cả"; +"Scene.Search.Searching.Segment.Hashtags" = "Hashtag"; +"Scene.Search.Searching.Segment.People" = "Người dùng"; +"Scene.Search.Searching.Segment.Posts" = "Tút"; +"Scene.Search.Title" = "Tìm kiếm"; +"Scene.ServerPicker.Button.Category.Academia" = "học thuật"; +"Scene.ServerPicker.Button.Category.Activism" = "hoạt động xã hội"; +"Scene.ServerPicker.Button.Category.All" = "Toàn bộ"; +"Scene.ServerPicker.Button.Category.AllAccessiblityDescription" = "Phân loại: Toàn bộ"; +"Scene.ServerPicker.Button.Category.Art" = "nghệ thuật"; +"Scene.ServerPicker.Button.Category.Food" = "ăn uống"; +"Scene.ServerPicker.Button.Category.Furry" = "furry"; +"Scene.ServerPicker.Button.Category.Games" = "trò chơi"; +"Scene.ServerPicker.Button.Category.General" = "chung"; +"Scene.ServerPicker.Button.Category.Journalism" = "tin tức"; +"Scene.ServerPicker.Button.Category.Lgbt" = "lgbt"; +"Scene.ServerPicker.Button.Category.Music" = "âm nhạc"; +"Scene.ServerPicker.Button.Category.Regional" = "khu vực"; +"Scene.ServerPicker.Button.Category.Tech" = "công nghệ"; +"Scene.ServerPicker.Button.SeeLess" = "Ẩn bớt"; +"Scene.ServerPicker.Button.SeeMore" = "Nhiều hơn"; +"Scene.ServerPicker.EmptyState.BadNetwork" = "Đã xảy ra lỗi. Hãy thử lại hoặc kiểm tra kết nối internet của bạn."; +"Scene.ServerPicker.EmptyState.FindingServers" = "Đang tìm máy chủ hoạt động..."; +"Scene.ServerPicker.EmptyState.NoResults" = "Không có kết quả"; +"Scene.ServerPicker.Input.Placeholder" = "Tìm cộng đồng"; +"Scene.ServerPicker.Label.Category" = "PHÂN LOẠI"; +"Scene.ServerPicker.Label.Language" = "NGÔN NGỮ"; +"Scene.ServerPicker.Label.Users" = "NGƯỜI DÙNG"; +"Scene.ServerPicker.Subtitle" = "Chọn một cộng đồng dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn."; +"Scene.ServerPicker.SubtitleExtend" = "Chọn một cộng đồng dựa theo sở thích, tôn giáo, hoặc ý muốn của bạn. Mỗi cộng đồng có thể được vận hành bởi một tổ chức độc lập hoặc một cá nhân."; +"Scene.ServerPicker.Title" = "Mastodon gồm nhiều cộng đồng với nhiều thành viên khác nhau."; +"Scene.ServerRules.Button.Confirm" = "Tôi đồng ý"; +"Scene.ServerRules.PrivacyPolicy" = "chính sách bảo mật"; +"Scene.ServerRules.Prompt" = "Tiếp tục nghĩa là bạn đồng ý điều khoản dịch vụ và chính sách bảo mật của %@."; +"Scene.ServerRules.Subtitle" = "Được ban hành và áp dụng bởi quản trị viên %@"; +"Scene.ServerRules.TermsOfService" = "điều khoản dịch vụ"; +"Scene.ServerRules.Title" = "Quy tắc máy chủ."; +"Scene.Settings.Footer.MastodonDescription" = "Mastodon là phần mềm mã nguồn mở. Bạn có thể báo lỗi trên GitHub tại %@ (%@)"; +"Scene.Settings.Keyboard.CloseSettingsWindow" = "Đóng cửa sổ cài đặt"; +"Scene.Settings.Section.Appearance.Automatic" = "Tự động"; +"Scene.Settings.Section.Appearance.Dark" = "Tối"; +"Scene.Settings.Section.Appearance.Light" = "Sáng"; +"Scene.Settings.Section.Appearance.Title" = "Giao diện"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Cài đặt tài khoản"; +"Scene.Settings.Section.BoringZone.Privacy" = "Chính sách bảo mật"; +"Scene.Settings.Section.BoringZone.Terms" = "Điều khoản dịch vụ"; +"Scene.Settings.Section.BoringZone.Title" = "Nhàm chán"; +"Scene.Settings.Section.LookAndFeel.Light" = "Sáng"; +"Scene.Settings.Section.LookAndFeel.ReallyDark" = "Tối Mạnh"; +"Scene.Settings.Section.LookAndFeel.SortaDark" = "Tối Nhẹ"; +"Scene.Settings.Section.LookAndFeel.Title" = "Giao diện"; +"Scene.Settings.Section.LookAndFeel.UseSystem" = "Mặc định hệ thống"; +"Scene.Settings.Section.Notifications.Boosts" = "Đăng lại tút của tôi"; +"Scene.Settings.Section.Notifications.Favorites" = "Thích tút của tôi"; +"Scene.Settings.Section.Notifications.Follows" = "Theo dõi tôi"; +"Scene.Settings.Section.Notifications.Mentions" = "Nhắc đến tôi"; +"Scene.Settings.Section.Notifications.Title" = "Thông báo"; +"Scene.Settings.Section.Notifications.Trigger.Anyone" = "ai đó"; +"Scene.Settings.Section.Notifications.Trigger.Follow" = "người tôi theo dõi"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "người theo dõi"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "không một ai"; +"Scene.Settings.Section.Notifications.Trigger.Title" = "Thông báo khi"; +"Scene.Settings.Section.Preference.DisableAvatarAnimation" = "Tắt ảnh đại diện GIF"; +"Scene.Settings.Section.Preference.DisableEmojiAnimation" = "Tắt emoji dạng GIF"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "Mở liên kết trong Mastodon"; +"Scene.Settings.Section.Preference.Title" = "Chung"; +"Scene.Settings.Section.Preference.TrueBlackDarkMode" = "Chế độ tối chân thật"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Dùng trình duyệt mặc định"; +"Scene.Settings.Section.SpicyZone.Clear" = "Xóa bộ nhớ đệm"; +"Scene.Settings.Section.SpicyZone.Signout" = "Đăng xuất"; +"Scene.Settings.Section.SpicyZone.Title" = "Thú vị"; +"Scene.Settings.Title" = "Cài đặt"; +"Scene.SuggestionAccount.FollowExplain" = "Khi theo dõi ai đó, bạn sẽ thấy tút của họ trong bảng tin."; +"Scene.SuggestionAccount.Title" = "Đề xuất theo dõi"; +"Scene.Thread.BackTitle" = "Tút"; +"Scene.Thread.Title" = "Tút của %@"; +"Scene.Welcome.GetStarted" = "Bắt đầu"; +"Scene.Welcome.LogIn" = "Đăng nhập"; +"Scene.Welcome.Slogan" = "Mạng xã hội +do bạn kiểm soát."; +"Scene.Wizard.AccessibilityHint" = "Nhấn hai lần để bỏ qua"; +"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Chuyển đổi giữa nhiều tài khoản bằng cách đè giữ nút tài khoản."; +"Scene.Wizard.NewInMastodon" = "Mới trên Mastodon"; \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.stringsdict new file mode 100644 index 00000000..71ba1951 --- /dev/null +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/vi.lproj/Localizable.stringsdict @@ -0,0 +1,356 @@ + + + + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld thông báo chưa đọc + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Giới hạn nhập tối đa %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ký tự + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Giới hạn nhập còn lại %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ký tự + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + tút + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld tút + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld lượt thích + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld đăng lại + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld trả lời + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld bình chọn + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld người bình chọn + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld người đang thảo luận + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld đang theo dõi + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld người theo dõi + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld năm còn lại + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld tháng còn lại + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ngày còn lại + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld giờ còn lại + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld phút còn lại + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld giây còn lại + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld năm trước + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld tháng trước + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld ngày trước + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ldh + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ldm + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %lds + + + + diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings index 7ad984f1..f9ec487c 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.strings @@ -98,6 +98,10 @@ "Common.Controls.Status.Actions.Menu" = "菜单"; "Common.Controls.Status.Actions.Reblog" = "转发"; "Common.Controls.Status.Actions.Reply" = "回复"; +"Common.Controls.Status.Actions.ShowGif" = "显示 GIF"; +"Common.Controls.Status.Actions.ShowImage" = "显示图片"; +"Common.Controls.Status.Actions.ShowVideoPlayer" = "显示视频播放器"; +"Common.Controls.Status.Actions.TapThenHoldToShowMenu" = "长按以显示菜单"; "Common.Controls.Status.Actions.Unfavorite" = "取消喜欢"; "Common.Controls.Status.Actions.Unreblog" = "取消转发"; "Common.Controls.Status.ContentWarning" = "内容警告"; @@ -112,6 +116,7 @@ "Common.Controls.Status.Tag.Link" = "链接"; "Common.Controls.Status.Tag.Mention" = "提及"; "Common.Controls.Status.Tag.Url" = "URL"; +"Common.Controls.Status.TapToReveal" = "点击以显示"; "Common.Controls.Status.UserReblogged" = "%@ 转发"; "Common.Controls.Status.UserRepliedTo" = "回复给 %@"; "Common.Controls.Status.Visibility.Direct" = "只有提到的用户才能看到此帖子。"; @@ -196,6 +201,10 @@ "Scene.ConfirmEmail.Subtitle" = "我们刚刚向 %@ 发送了一封电子邮件, 点击链接确认你的帐户。"; "Scene.ConfirmEmail.Title" = "最后一件事。"; +"Scene.Discovery.Tabs.ForYou" = "为你推荐"; +"Scene.Discovery.Tabs.Hashtags" = "话题"; +"Scene.Discovery.Tabs.News" = "新闻"; +"Scene.Discovery.Tabs.Posts" = "帖子"; "Scene.Favorite.Title" = "你的喜欢"; "Scene.Follower.Footer" = "不会显示来自其它服务器的关注者"; "Scene.Following.Footer" = "不会显示来自其它服务器的关注"; @@ -217,6 +226,10 @@ "Scene.Preview.Keyboard.ClosePreview" = "关闭预览"; "Scene.Preview.Keyboard.ShowNext" = "显示下一个"; "Scene.Preview.Keyboard.ShowPrevious" = "显示前一个"; +"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "双击打开列表"; +"Scene.Profile.Accessibility.EditAvatarImage" = "编辑头像"; +"Scene.Profile.Accessibility.ShowAvatarImage" = "显示头像"; +"Scene.Profile.Accessibility.ShowBannerImage" = "显示顶部横幅图片"; "Scene.Profile.Dashboard.Followers" = "关注者"; "Scene.Profile.Dashboard.Following" = "正在关注"; "Scene.Profile.Dashboard.Posts" = "帖子"; @@ -357,6 +370,7 @@ "Scene.Settings.Section.Notifications.Trigger.Title" = "提示通知来自"; "Scene.Settings.Section.Preference.DisableAvatarAnimation" = "禁用动画头像"; "Scene.Settings.Section.Preference.DisableEmojiAnimation" = "禁用动画表情"; +"Scene.Settings.Section.Preference.OpenLinksInMastodon" = "在 Mastodon 中打开链接"; "Scene.Settings.Section.Preference.Title" = "偏好"; "Scene.Settings.Section.Preference.TrueBlackDarkMode" = "纯黑模式"; "Scene.Settings.Section.Preference.UsingDefaultBrowser" = "使用默认浏览器打开链接"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict index 12b8b5f6..6c2661ee 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/zh-Hans.lproj/Localizable.stringsdict @@ -100,6 +100,20 @@ %ld 条转发 + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + %ld 条回复 + + plural.count.vote NSStringLocalizedFormatKey diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Suggestions.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Suggestions.swift index 55808964..4c424f3b 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Suggestions.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Suggestions.swift @@ -27,7 +27,7 @@ extension Mastodon.API.Suggestions { /// - query: query /// - authorization: User token. /// - Returns: `AnyPublisher` contains `Accounts` nested in the response - public static func get( + public static func accounts( session: URLSession, domain: String, query: Mastodon.API.Suggestions.Query?, diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Trends.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Trends.swift index 385e3d75..d2dca824 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Trends.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Trends.swift @@ -9,6 +9,7 @@ import Combine import Foundation extension Mastodon.API.Trends { + static func trendsURL(domain: String) -> URL { Mastodon.API.endpointURL(domain: domain).appendingPathComponent("trends") } @@ -27,10 +28,10 @@ extension Mastodon.API.Trends { /// - query: query /// - Returns: `AnyPublisher` contains `Hashtags` nested in the response - public static func get( + public static func hashtags( session: URLSession, domain: String, - query: Mastodon.API.Trends.Query? + query: Mastodon.API.Trends.HashtagQuery? ) -> AnyPublisher, Error> { let request = Mastodon.API.get( url: trendsURL(domain: domain), @@ -44,10 +45,8 @@ extension Mastodon.API.Trends { } .eraseToAnyPublisher() } -} -extension Mastodon.API.Trends { - public struct Query: Codable, GetQuery { + public struct HashtagQuery: Codable, GetQuery { public init(limit: Int?) { self.limit = limit } @@ -61,4 +60,113 @@ extension Mastodon.API.Trends { return items } } + +} + +extension Mastodon.API.Trends { + + static func trendStatusesURL(domain: String) -> URL { + Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("trends") + .appendingPathComponent("statuses") + } + + /// Trending status + /// + /// TBD + /// + /// Version history: + /// 3.?.? + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/instance/trends/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - query: query + /// - Returns: `[Status]` nested in the response + + public static func statuses( + session: URLSession, + domain: String, + query: Mastodon.API.Trends.StatusQuery? + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: trendStatusesURL(domain: domain), + query: query, + authorization: nil + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Status].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + public struct StatusQuery: Codable, GetQuery { + + public let offset: Int? + public let limit: Int? // Maximum number of results to return. Defaults to 10. + + public init( + offset: Int?, + limit: Int? + ) { + self.offset = offset + self.limit = limit + } + + var queryItems: [URLQueryItem]? { + var items: [URLQueryItem] = [] + offset.flatMap { items.append(URLQueryItem(name: "offset", value: String($0))) } + limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) } + guard !items.isEmpty else { return nil } + return items + } + } + +} + +extension Mastodon.API.Trends { + + static func trendLinksURL(domain: String) -> URL { + Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("trends") + .appendingPathComponent("links") + } + + /// Trending links + /// + /// TBD + /// + /// Version history: + /// 3.?.? + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/instance/trends/) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - query: query + /// - Returns: `[Link]` nested in the response + + public static func links( + session: URLSession, + domain: String, + query: Mastodon.API.Trends.LinkQuery? + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: trendLinksURL(domain: domain), + query: query, + authorization: nil + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Link].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + public typealias LinkQuery = StatusQuery + } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Suggestions.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Suggestions.swift index 9e6876b4..ed680dba 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Suggestions.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+V2+Suggestions.swift @@ -20,7 +20,7 @@ extension Mastodon.API.V2.Suggestions { /// - query: query /// - authorization: User token. /// - Returns: `AnyPublisher` contains `AccountsSuggestion` nested in the response - public static func get( + public static func accounts( session: URLSession, domain: String, query: Mastodon.API.Suggestions.Query?, diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Link.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Link.swift new file mode 100644 index 00000000..d1d7bd67 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Link.swift @@ -0,0 +1,54 @@ +// +// Mastodon+Entity+Link.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation + +extension Mastodon.Entity { + /// History + /// + /// - Since: 3.5.0 + /// - Version: 3.5.1 + /// # Last Update + /// 2022/4/13 + /// # Reference + /// [Document](TBD) + public struct Link: Codable { + public let url: String + public let title: String + public let description: String + public let providerName: String + public let providerURL: String + public let image: String + public let width: Int + public let height: Int + public let blurhash: String + public let history: [History] + + enum CodingKeys: String, CodingKey { + case url + case title + case description + case providerName = "provider_name" + case providerURL = "provider_url" + case image + case width + case height + case blurhash + case history + } + } +} + +extension Mastodon.Entity.Link: Hashable { + public static func == (lhs: Mastodon.Entity.Link, rhs: Mastodon.Entity.Link) -> Bool { + return lhs.url == rhs.url + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(url) + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift index b017d155..84875359 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Tag.swift @@ -37,6 +37,7 @@ extension Mastodon.Entity { public func hash(into hasher: inout Hasher) { hasher.combine(name) + hasher.combine(url) } } } diff --git a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift index db42169d..6cf95752 100644 --- a/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift +++ b/MastodonSDK/Sources/MastodonSDK/Response/Mastodon+Response+Content.swift @@ -106,6 +106,7 @@ extension Mastodon.Response { public struct Link { public let maxID: Mastodon.Entity.Status.ID? public let minID: Mastodon.Entity.Status.ID? + public let offset: Int? init(link: String) { self.maxID = { @@ -125,6 +126,15 @@ extension Mastodon.Response { let id = link[range] return String(id) }() + + self.offset = { + guard let regex = try? NSRegularExpression(pattern: "offset=([[:digit:]]+)", options: []) else { return nil } + let results = regex.matches(in: link, options: [], range: NSRange(link.startIndex.. URL? { + return URL(string: header) + } + + public func headerImageURLWithFallback(domain: String) -> URL { + return URL(string: header) ?? URL(string: "https://\(domain)/headers/original/missing.png")! + } + + public func avatarImageURL() -> URL? { + let string = UserDefaults.shared.preferredStaticAvatar ? avatarStatic ?? avatar : avatar + return URL(string: string) + } + + public func avatarImageURLWithFallback(domain: String) -> URL { + return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")! + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Link.swift b/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Link.swift new file mode 100644 index 00000000..8f00911f --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Link.swift @@ -0,0 +1,21 @@ +// +// Mastodon+Entity+Link.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation +import MastodonSDK + +extension Mastodon.Entity.Link { + + /// the sum of recent 2 days + public var talkingPeopleCount: Int? { + return history + .prefix(2) + .compactMap { Int($0.accounts) } + .reduce(0, +) + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Tag.swift b/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Tag.swift new file mode 100644 index 00000000..4d58145e --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/MastodonSDK/Mastodon+Entity+Tag.swift @@ -0,0 +1,21 @@ +// +// Mastodon+Entity+Tag.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import Foundation +import MastodonSDK + +extension Mastodon.Entity.Tag { + + /// the sum of recent 2 days + public var talkingPeopleCount: Int? { + return history? + .prefix(2) + .compactMap { Int($0.accounts) } + .reduce(0, +) + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift b/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift index 119e9e03..24a4027f 100644 --- a/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift +++ b/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift @@ -20,6 +20,8 @@ extension MetaLabel { case notificationTitle case profileFieldName case profileFieldValue + case profileCardName + case profileCardUsername case recommendAccountName case titleView case settingTableFooter @@ -51,7 +53,7 @@ extension MetaLabel { textColor = Asset.Colors.Label.secondary.color case .statusName: - font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .bold)) + font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)) textColor = Asset.Colors.Label.primary.color case .statusUsername: @@ -80,6 +82,14 @@ extension MetaLabel { font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) textColor = Asset.Colors.Label.primary.color + case .profileCardName: + font = .systemFont(ofSize: 17, weight: .semibold) + textColor = Asset.Colors.Label.primary.color + + case .profileCardUsername: + font = .systemFont(ofSize: 15, weight: .regular) + textColor = Asset.Colors.Label.secondary.color + case .titleView: font = .systemFont(ofSize: 17, weight: .semibold) textColor = Asset.Colors.Label.primary.color diff --git a/MastodonSDK/Sources/MastodonUI/Extension/UIView.swift b/MastodonSDK/Sources/MastodonUI/Extension/UIView.swift new file mode 100644 index 00000000..0489965b --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/UIView.swift @@ -0,0 +1,34 @@ +// +// UIView.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit + +extension UIView { + + static let separatorColor: UIColor = { + UIColor(dynamicProvider: { collection in + switch collection.userInterfaceStyle { + case .dark: + return ThemeService.shared.currentTheme.value.separator + default: + return .separator + } + }) + }() + + + public static var separatorLine: UIView { + let line = UIView() + line.backgroundColor = UIView.separatorColor + return line + } + + public static func separatorLineHeight(of view: UIView) -> CGFloat { + return 1.0 / view.traitCollection.displayScale + } + +} diff --git a/Mastodon/Helper/MastodonMetricFormatter.swift b/MastodonSDK/Sources/MastodonUI/Helper/MastodonMetricFormatter.swift similarity index 95% rename from Mastodon/Helper/MastodonMetricFormatter.swift rename to MastodonSDK/Sources/MastodonUI/Helper/MastodonMetricFormatter.swift index 3c9c4dd7..50fca8cc 100644 --- a/Mastodon/Helper/MastodonMetricFormatter.swift +++ b/MastodonSDK/Sources/MastodonUI/Helper/MastodonMetricFormatter.swift @@ -7,8 +7,8 @@ import Foundation -final public class MastodonMetricFormatter: Formatter { - +public final class MastodonMetricFormatter: Formatter { + public func string(from number: Int) -> String? { let isPositive = number >= 0 let symbol = isPositive ? "" : "-" diff --git a/Mastodon/Service/ThemeService/MastodonTheme.swift b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/MastodonTheme.swift similarity index 98% rename from Mastodon/Service/ThemeService/MastodonTheme.swift rename to MastodonSDK/Sources/MastodonUI/Service/ThemeService/MastodonTheme.swift index 0dad463b..76173590 100644 --- a/Mastodon/Service/ThemeService/MastodonTheme.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/MastodonTheme.swift @@ -7,6 +7,7 @@ import UIKit import MastodonAsset +import MastodonCommon struct MastodonTheme: Theme { diff --git a/Mastodon/Service/ThemeService/SystemTheme.swift b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/SystemTheme.swift similarity index 98% rename from Mastodon/Service/ThemeService/SystemTheme.swift rename to MastodonSDK/Sources/MastodonUI/Service/ThemeService/SystemTheme.swift index 7796fde7..cea10a28 100644 --- a/Mastodon/Service/ThemeService/SystemTheme.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/SystemTheme.swift @@ -7,6 +7,7 @@ import UIKit import MastodonAsset +import MastodonCommon struct SystemTheme: Theme { diff --git a/Mastodon/Service/ThemeService/Theme.swift b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/Theme.swift similarity index 82% rename from Mastodon/Service/ThemeService/Theme.swift rename to MastodonSDK/Sources/MastodonUI/Service/ThemeService/Theme.swift index 1a3b3c5d..ae555da0 100644 --- a/Mastodon/Service/ThemeService/Theme.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/Theme.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonCommon public protocol Theme { @@ -42,17 +43,3 @@ public protocol Theme { var notificationStatusBorderColor: UIColor { get } } - -public enum ThemeName: String, CaseIterable { - case system - case mastodon -} - -extension ThemeName { - public var theme: Theme { - switch self { - case .system: return SystemTheme() - case .mastodon: return MastodonTheme() - } - } -} diff --git a/Mastodon/Service/ThemeService/ThemeService.swift b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/ThemeService.swift similarity index 57% rename from Mastodon/Service/ThemeService/ThemeService.swift rename to MastodonSDK/Sources/MastodonUI/Service/ThemeService/ThemeService.swift index b356d346..b782c413 100644 --- a/Mastodon/Service/ThemeService/ThemeService.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/ThemeService/ThemeService.swift @@ -8,16 +8,17 @@ import UIKit import Combine import AppShared +import MastodonCommon // ref: https://zamzam.io/protocol-oriented-themes-for-ios-apps/ -final class ThemeService { +public final class ThemeService { - static let tintColor: UIColor = .label + public static let tintColor: UIColor = .label // MARK: - Singleton public static let shared = ThemeService() - let currentTheme: CurrentValueSubject + public let currentTheme: CurrentValueSubject private init() { let theme = ThemeName(rawValue: UserDefaults.shared.currentThemeNameRawValue)?.theme ?? ThemeName.mastodon.theme @@ -25,3 +26,12 @@ final class ThemeService { } } + +extension ThemeName { + public var theme: Theme { + switch self { + case .system: return SystemTheme() + case .mastodon: return MastodonTheme() + } + } +} diff --git a/Mastodon/Vender/CurveAlgorithm.swift b/MastodonSDK/Sources/MastodonUI/Vendor/CurveAlgorithm.swift similarity index 100% rename from Mastodon/Vender/CurveAlgorithm.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/CurveAlgorithm.swift diff --git a/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift b/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift index cb9c53f3..e3359fb5 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Container/MediaGridContainerView.swift @@ -48,22 +48,7 @@ public final class MediaGridContainerView: UIView { return mediaViews }() - -// let sensitiveToggleButtonBlurVisualEffectView: UIVisualEffectView = { -// let visualEffectView = UIVisualEffectView(effect: ContentWarningOverlayView.blurVisualEffect) -// visualEffectView.layer.masksToBounds = true -// visualEffectView.layer.cornerRadius = MediaGridContainerView.sensitiveToggleButtonSize.width / 2 -// visualEffectView.layer.cornerCurve = .continuous -// return visualEffectView -// }() -// let sensitiveToggleButtonVibrancyVisualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: ContentWarningOverlayView.blurVisualEffect)) -// let sensitiveToggleButton: HitTestExpandedButton = { -// let button = HitTestExpandedButton(type: .system) -// button.contentEdgeInsets = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4) -// button.imageView?.contentMode = .scaleAspectFit -// button.setImage(UIImage(systemName: "eye.slash.fill"), for: .normal) -// return button -// }() + let contentWarningOverlay = ContentWarningOverlayView() public override init(frame: CGRect) { super.init(frame: frame) @@ -86,7 +71,8 @@ public final class MediaGridContainerView: UIView { extension MediaGridContainerView { private func _init() { -// sensitiveToggleButton.addTarget(self, action: #selector(MediaGridContainerView.sensitiveToggleButtonDidPressed(_:)), for: .touchUpInside) + contentWarningOverlay.isUserInteractionEnabled = false + contentWarningOverlay.isHidden = true } } @@ -112,8 +98,8 @@ extension MediaGridContainerView { let mediaView = _mediaViews[0] layout.layout(in: self, mediaView: mediaView) -// layoutSensitiveToggleButton() -// bringSubviewToFront(sensitiveToggleButtonBlurVisualEffectView) + layoutContentWarningOverlay() + bringSubviewToFront(contentWarningOverlay) return mediaView } @@ -124,8 +110,8 @@ extension MediaGridContainerView { let mediaViews = Array(_mediaViews[0..() + + public let relationshipViewModel = RelationshipViewModel() + + @Published public var userInterfaceStyle: UIUserInterfaceStyle? + @Published public var backgroundColor: UIColor? + + // Author + @Published public var authorBannerImageURL: URL? + @Published public var authorAvatarImageURL: URL? + @Published public var authorName: MetaContent? + @Published public var authorUsername: String? + + @Published public var bioContent: MetaContent? + + @Published public var statusesCount: Int? + @Published public var followingCount: Int? + @Published public var followersCount: Int? + + @Published public var isUpdating = false + @Published public var isFollowedBy = false + @Published public var isMuting = false + @Published public var isBlocking = false + @Published public var isBlockedBy = false + + @Published public var groupedAccessibilityLabel = "" + + init() { + backgroundColor = ThemeService.shared.currentTheme.value.systemBackgroundColor + Publishers.CombineLatest( + ThemeService.shared.currentTheme, + $userInterfaceStyle + ) + .sink { [weak self] theme, userInterfaceStyle in + guard let self = self else { return } + guard let userInterfaceStyle = userInterfaceStyle else { return } + switch userInterfaceStyle { + case .dark: + self.backgroundColor = theme.systemBackgroundColor + case .light, .unspecified: + self.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color + @unknown default: + self.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color + assertionFailure() + // do nothing + } + } + .store(in: &disposeBag) + } + } +} + +extension ProfileCardView.ViewModel { + func bind(view: ProfileCardView) { + bindAppearacne(view: view) + bindHeader(view: view) + bindUser(view: view) + bindBio(view: view) + bindRelationship(view: view) + bindDashboard(view: view) + bindAccessibility(view: view) + } + + private func bindAppearacne(view: ProfileCardView) { + userInterfaceStyle = view.traitCollection.userInterfaceStyle + + $backgroundColor + .assign(to: \.backgroundColor, on: view.container) + .store(in: &disposeBag) + $backgroundColor + .assign(to: \.backgroundColor, on: view.avatarButtonBackgroundView) + .store(in: &disposeBag) + } + + + private func bindHeader(view: ProfileCardView) { + $authorBannerImageURL + .sink { url in + guard let url = url else { return } + view.bannerImageView.af.setImage( + withURL: url, + placeholderImage: .placeholder(color: .systemGray3), + imageTransition: .crossDissolve(0.3) + ) + } + .store(in: &disposeBag) + } + + private func bindUser(view: ProfileCardView) { + $authorAvatarImageURL + .sink { url in + view.avatarButton.avatarImageView.configure( + configuration: .init( + url: url, + placeholder: .placeholder(color: .systemGray3) + ) + ) + view.avatarButton.avatarImageView.configure( + cornerConfiguration: .init(corner: .fixed(radius: 12)) + ) + } + .store(in: &disposeBag) + + // name + $authorName + .sink { metaContent in + let metaContent = metaContent ?? PlaintextMetaContent(string: " ") + view.authorNameLabel.configure(content: metaContent) + } + .store(in: &disposeBag) + // username + $authorUsername + .map { text -> String in + guard let text = text else { return "" } + return "@\(text)" + } + .sink { username in + let metaContent = PlaintextMetaContent(string: username) + view.authorUsernameLabel.configure(content: metaContent) + } + .store(in: &disposeBag) + } + + private func bindBio(view: ProfileCardView) { + $bioContent + .sink { metaContent in + let metaContent = metaContent ?? PlaintextMetaContent(string: " ") + view.bioMetaText.configure(content: metaContent) + } + .store(in: &disposeBag) + } + + private func bindRelationship(view: ProfileCardView) { + relationshipViewModel.$optionSet + .receive(on: DispatchQueue.main) + .sink { relationshipActionSet in + let relationshipActionSet = relationshipActionSet ?? .follow + view.relationshipActionButton.configure(actionOptionSet: relationshipActionSet) + } + .store(in: &disposeBag) + } + + private func bindDashboard(view: ProfileCardView) { + $statusesCount + .receive(on: DispatchQueue.main) + .sink { count in + let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" + view.statusDashboardView.postDashboardMeterView.numberLabel.text = text + view.statusDashboardView.postDashboardMeterView.isAccessibilityElement = true + view.statusDashboardView.postDashboardMeterView.accessibilityLabel = L10n.Plural.Count.post(count ?? 0) + } + .store(in: &disposeBag) + $followingCount + .receive(on: DispatchQueue.main) + .sink { count in + let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" + view.statusDashboardView.followingDashboardMeterView.numberLabel.text = text + view.statusDashboardView.followingDashboardMeterView.isAccessibilityElement = true + view.statusDashboardView.followingDashboardMeterView.accessibilityLabel = L10n.Plural.Count.following(count ?? 0) + } + .store(in: &disposeBag) + $followersCount + .receive(on: DispatchQueue.main) + .sink { count in + let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-" + view.statusDashboardView.followersDashboardMeterView.numberLabel.text = text + view.statusDashboardView.followersDashboardMeterView.isAccessibilityElement = true + view.statusDashboardView.followersDashboardMeterView.accessibilityLabel = L10n.Plural.Count.follower(count ?? 0) + } + .store(in: &disposeBag) + } + + private func bindAccessibility(view: ProfileCardView) { + let authorAccessibilityLabel = Publishers.CombineLatest( + $authorName, + $bioContent + ) + .map { authorName, bioContent -> String? in + var strings: [String?] = [] + strings.append(authorName?.string) + strings.append(bioContent?.string) + return strings.compactMap { $0 }.joined(separator: ", ") + } + + authorAccessibilityLabel + .map { $0 ?? "" } + .assign(to: &$groupedAccessibilityLabel) + + $groupedAccessibilityLabel + .sink { accessibilityLabel in + view.accessibilityLabel = accessibilityLabel + } + .store(in: &disposeBag) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift new file mode 100644 index 00000000..07f44150 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift @@ -0,0 +1,285 @@ +// +// ProfileCardView.swift +// +// +// Created by MainasuK on 2022-4-14. +// + +import os.log +import UIKit +import Combine +import MetaTextKit +import MastodonAsset + +public protocol ProfileCardViewDelegate: AnyObject { + func profileCardView(_ profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) +} + +public final class ProfileCardView: UIView { + + static let avatarSize = CGSize(width: 56, height: 56) + static let friendshipActionButtonSize = CGSize(width: 108, height: 34) + static let contentMargin: CGFloat = 16 + + weak var delegate: ProfileCardViewDelegate? + private var _disposeBag = Set() + var disposeBag = Set() + + let container = UIStackView() + + let bannerImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.layer.masksToBounds = true + imageView.layer.cornerRadius = 3 + imageView.layer.cornerCurve = .continuous + return imageView + }() + + // avatar + public let avatarButtonBackgroundView = UIView() + public let avatarButton = AvatarButton() + + // author name + public let authorNameLabel = MetaLabel(style: .profileCardName) + + // author username + public let authorUsernameLabel = MetaLabel(style: .profileCardUsername) + + // bio + let bioMetaTextAdaptiveMarginContainerView = AdaptiveMarginContainerView() + let bioMetaText: MetaText = { + let metaText = MetaText() + metaText.textView.backgroundColor = .clear + metaText.textView.isEditable = false + metaText.textView.isSelectable = true + metaText.textView.isScrollEnabled = false + //metaText.textView.textContainer.lineFragmentPadding = 0 + //metaText.textView.textContainerInset = .zero + metaText.textView.layer.masksToBounds = false + metaText.textView.textDragInteraction?.isEnabled = false // disable drag for link and attachment + + metaText.textView.layer.masksToBounds = true + metaText.textView.layer.cornerCurve = .continuous + metaText.textView.layer.cornerRadius = 10 + + metaText.paragraphStyle = { + let style = NSMutableParagraphStyle() + style.lineSpacing = 5 + style.paragraphSpacing = 8 + return style + }() + metaText.textAttributes = [ + .font: UIFont.preferredFont(forTextStyle: .body), + .foregroundColor: Asset.Colors.Label.primary.color, + ] + metaText.linkAttributes = [ + .font: UIFont.preferredFont(forTextStyle: .body), + .foregroundColor: Asset.Colors.brandBlue.color, + ] + return metaText + }() + + let infoContainerAdaptiveMarginContainerView = AdaptiveMarginContainerView() + let infoContainer = UIStackView() + + let statusDashboardView = ProfileStatusDashboardView() + + let relationshipActionButtonShadowContainer = ShadowBackgroundContainer() + let relationshipActionButton: ProfileRelationshipActionButton = { + let button = ProfileRelationshipActionButton() + button.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold) + button.titleLabel?.adjustsFontSizeToFitWidth = true + button.titleLabel?.minimumScaleFactor = 0.5 + return button + }() + + public private(set) lazy var viewModel: ViewModel = { + let viewModel = ViewModel() + viewModel.bind(view: self) + return viewModel + }() + + public func prepareForReuse() { + disposeBag.removeAll() + bannerImageView.af.cancelImageRequest() + bannerImageView.image = nil + } + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension ProfileCardView { + private func _init() { + avatarButton.isUserInteractionEnabled = false + authorNameLabel.isUserInteractionEnabled = false + authorUsernameLabel.isUserInteractionEnabled = false + bioMetaText.textView.isUserInteractionEnabled = false + statusDashboardView.isUserInteractionEnabled = false + + // container: V - [ bannerContainer | authorContainer | bioMetaText | infoContainer ] + container.axis = .vertical + container.spacing = 8 + container.translatesAutoresizingMaskIntoConstraints = false + addSubview(container) + NSLayoutConstraint.activate([ + container.topAnchor.constraint(equalTo: topAnchor), + container.leadingAnchor.constraint(equalTo: leadingAnchor), + container.trailingAnchor.constraint(equalTo: trailingAnchor), + container.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + + // bannerContainer + let bannerContainer = UIView() + bannerContainer.translatesAutoresizingMaskIntoConstraints = false + container.addArrangedSubview(bannerContainer) + container.setCustomSpacing(6, after: bannerContainer) + + // bannerImageView + bannerImageView.translatesAutoresizingMaskIntoConstraints = false + bannerContainer.addSubview(bannerImageView) + NSLayoutConstraint.activate([ + bannerImageView.topAnchor.constraint(equalTo: bannerContainer.topAnchor, constant: 4), + bannerImageView.leadingAnchor.constraint(equalTo: bannerContainer.leadingAnchor, constant: 4), + bannerContainer.trailingAnchor.constraint(equalTo: bannerImageView.trailingAnchor, constant: 4), + bannerImageView.bottomAnchor.constraint(equalTo: bannerContainer.bottomAnchor), + bannerImageView.widthAnchor.constraint(equalTo: bannerImageView.heightAnchor, multiplier: 335.0/128.0).priority(.required - 1), + ]) + + // authorContainer: H - [ avatarPlaceholder | authorInfoContainer ] + let authorContainer = UIStackView() + authorContainer.axis = .horizontal + authorContainer.spacing = 16 + let authorContainerAdaptiveMarginContainerView = AdaptiveMarginContainerView() + authorContainerAdaptiveMarginContainerView.contentView = authorContainer + authorContainerAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin + container.addArrangedSubview(authorContainerAdaptiveMarginContainerView) + + // avatarPlaceholder + let avatarPlaceholder = UIView() + avatarPlaceholder.translatesAutoresizingMaskIntoConstraints = false + authorContainer.addArrangedSubview(avatarPlaceholder) + NSLayoutConstraint.activate([ + avatarPlaceholder.widthAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.width).priority(.required - 1), + avatarPlaceholder.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height - 14).priority(.required - 1), + ]) + + avatarButton.translatesAutoresizingMaskIntoConstraints = false + authorContainer.addSubview(avatarButton) + NSLayoutConstraint.activate([ + avatarButton.leadingAnchor.constraint(equalTo: avatarPlaceholder.leadingAnchor), + avatarButton.trailingAnchor.constraint(equalTo: avatarPlaceholder.trailingAnchor), + avatarButton.bottomAnchor.constraint(equalTo: avatarPlaceholder.bottomAnchor), + avatarButton.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height).priority(.required - 1), + ]) + + avatarButtonBackgroundView.layer.masksToBounds = true + avatarButtonBackgroundView.layer.cornerCurve = .continuous + avatarButtonBackgroundView.layer.cornerRadius = 12 + 1 + avatarButtonBackgroundView.translatesAutoresizingMaskIntoConstraints = false + authorContainer.insertSubview(avatarButtonBackgroundView, belowSubview: avatarButton) + NSLayoutConstraint.activate([ + avatarButtonBackgroundView.centerXAnchor.constraint(equalTo: avatarButton.centerXAnchor), + avatarButtonBackgroundView.centerYAnchor.constraint(equalTo: avatarButton.centerYAnchor), + avatarButtonBackgroundView.widthAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.width + 4).priority(.required - 1), + avatarButtonBackgroundView.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height + 4).priority(.required - 1), + ]) + + // authorInfoContainer: V - [ authorNameLabel | authorUsernameLabel ] + let authorInfoContainer = UIStackView() + authorInfoContainer.axis = .vertical + // authorInfoContainer.spacing = 2 + authorContainer.addArrangedSubview(authorInfoContainer) + + authorInfoContainer.addArrangedSubview(authorNameLabel) + authorInfoContainer.addArrangedSubview(authorUsernameLabel) + + // bioMetaText + bioMetaTextAdaptiveMarginContainerView.contentView = bioMetaText.textView + bioMetaTextAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin + bioMetaText.textView.setContentHuggingPriority(.required - 1, for: .vertical) + bioMetaText.textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) + container.addArrangedSubview(bioMetaTextAdaptiveMarginContainerView) + container.setCustomSpacing(16, after: bioMetaTextAdaptiveMarginContainerView) + + // infoContainer: H - [ statusDashboardView | (spacer) | relationshipActionButton ] + infoContainer.axis = .horizontal + infoContainer.spacing = 8 + infoContainerAdaptiveMarginContainerView.contentView = infoContainer + infoContainerAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin + container.addArrangedSubview(infoContainerAdaptiveMarginContainerView) + + infoContainer.addArrangedSubview(statusDashboardView) + let infoContainerSpacer = UIView() + infoContainer.addArrangedSubview(UIView()) + infoContainerSpacer.setContentHuggingPriority(.defaultLow - 100, for: .vertical) + infoContainerSpacer.setContentHuggingPriority(.defaultLow - 100, for: .horizontal) + let relationshipActionButtonShadowContainer = ShadowBackgroundContainer() + relationshipActionButtonShadowContainer.translatesAutoresizingMaskIntoConstraints = false + infoContainer.addArrangedSubview(relationshipActionButtonShadowContainer) + + relationshipActionButton.translatesAutoresizingMaskIntoConstraints = false + relationshipActionButtonShadowContainer.addSubview(relationshipActionButton) + NSLayoutConstraint.activate([ + relationshipActionButton.topAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.topAnchor), + relationshipActionButton.leadingAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.leadingAnchor), + relationshipActionButton.trailingAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.trailingAnchor), + relationshipActionButton.bottomAnchor.constraint(equalTo: relationshipActionButtonShadowContainer.bottomAnchor), + relationshipActionButtonShadowContainer.widthAnchor.constraint(greaterThanOrEqualToConstant: ProfileCardView.friendshipActionButtonSize.width).priority(.required - 1), + relationshipActionButtonShadowContainer.heightAnchor.constraint(equalToConstant: ProfileCardView.friendshipActionButtonSize.height).priority(.required - 1), + ]) + + let bottomPadding = UIView() + bottomPadding.translatesAutoresizingMaskIntoConstraints = false + container.addArrangedSubview(bottomPadding) + NSLayoutConstraint.activate([ + bottomPadding.heightAnchor.constraint(equalToConstant: 16).priority(.required - 10), + ]) + + relationshipActionButton.addTarget(self, action: #selector(ProfileCardView.relationshipActionButtonDidPressed(_:)), for: .touchUpInside) + } + + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + viewModel.userInterfaceStyle = traitCollection.userInterfaceStyle + } + + public override func layoutSubviews() { + updateInfoContainerLayout() + super.layoutSubviews() + } + +} + +extension ProfileCardView { + public func setupLayoutFrame(_ rect: CGRect) { + frame.size.width = rect.width + bioMetaTextAdaptiveMarginContainerView.frame.size.width = frame.width + bioMetaTextAdaptiveMarginContainerView.contentView?.frame.size.width = frame.width - 2 * bioMetaTextAdaptiveMarginContainerView.margin + infoContainerAdaptiveMarginContainerView.frame.size.width = frame.width + infoContainerAdaptiveMarginContainerView.contentView?.frame.size.width = frame.width - 2 * infoContainerAdaptiveMarginContainerView.margin + } + + private func updateInfoContainerLayout() { + let isCompactAdaptive = bounds.width < 350 + infoContainer.axis = isCompactAdaptive ? .vertical : .horizontal + } +} + +extension ProfileCardView { + @objc private func relationshipActionButtonDidPressed(_ sender: UIButton) { + os_log(.debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + assert(sender === relationshipActionButton) + delegate?.profileCardView(self, relationshipButtonDidPressed: relationshipActionButton) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index f848b37e..44f7bb9a 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -14,6 +14,7 @@ import MastodonSDK import MastodonAsset import MastodonLocalization import MastodonExtension +import MastodonCommon import CoreDataStack extension StatusView { @@ -73,11 +74,9 @@ extension StatusView { // Sensitive @Published public var isContentSensitive: Bool = false - @Published public var isContentSensitiveToggled: Bool = false @Published public var isMediaSensitive: Bool = false - @Published public var isMediaSensitiveToggled: Bool = false - - @Published public var isSensitive: Bool = false // isContentSensitive || isMediaSensitive + @Published public var isSensitiveToggled = false + @Published public var isContentReveal: Bool = true @Published public var isMediaReveal: Bool = true @@ -129,9 +128,8 @@ extension StatusView { authorAvatarImageURL = nil isContentSensitive = false - isContentSensitiveToggled = false isMediaSensitive = false - isMediaSensitiveToggled = false + isSensitiveToggled = false activeFilters = [] filterContext = nil @@ -160,28 +158,18 @@ extension StatusView { $spoilerContent .map { $0 != nil } .assign(to: &$isContentSensitive) - // isSensitive - Publishers.CombineLatest( + // isReveal + Publishers.CombineLatest3( $isContentSensitive, - $isMediaSensitive - ) - .map { $0 || $1 } - .assign(to: &$isSensitive) - // $isContentReveal - Publishers.CombineLatest( - $isContentSensitive, - $isContentSensitiveToggled - ) - .map { $0 ? $1 : true } - .assign(to: &$isContentReveal) - // $isMediaReveal - Publishers.CombineLatest( $isMediaSensitive, - $isMediaSensitiveToggled + $isSensitiveToggled ) - .map { $1 ? !$0 : $0 } - .map { !$0 } - .assign(to: &$isMediaReveal) + .sink { [weak self] isContentSensitive, isMediaSensitive, isSensitiveToggled in + guard let self = self else { return } + self.isContentReveal = isContentSensitive ? isSensitiveToggled : true + self.isMediaReveal = isMediaSensitive ? isSensitiveToggled : true + } + .store(in: &disposeBag) } } } @@ -321,43 +309,26 @@ extension StatusView.ViewModel { statusView.setSpoilerOverlayViewHidden(isHidden: isContentReveal) - let image = isContentReveal ? UIImage(systemName: "eye.slash.fill") : UIImage(systemName: "eye.fill") - statusView.contentSensitiveeToggleButton.setImage(image, for: .normal) - self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): isContentReveal: \(isContentReveal)") } .store(in: &disposeBag) - $isSensitive + $isMediaSensitive .sink { isSensitive in guard isSensitive else { return } statusView.setContentSensitiveeToggleButtonDisplay() } .store(in: &disposeBag) -// // visibility -// Publishers.CombineLatest( -// $visibility, -// $isMyself -// ) -// .sink { visibility, isMyself in -// switch visibility { -// case .public: -// break -// case .unlisted: -// statusView.statusVisibilityView.label.text = "Everyone can see this post but not display in the public timeline." -// statusView.setVisibilityDisplay() -// case .private: -// statusView.statusVisibilityView.label.text = isMyself ? "Only my followers can see this post." : "Only their followers can see this post." -// statusView.setVisibilityDisplay() -// case .direct: -// statusView.statusVisibilityView.label.text = "Only mentioned user can see this post." -// statusView.setVisibilityDisplay() -// case ._other: -// break -// } -// } -// .store(in: &disposeBag) + $isSensitiveToggled + .sink { isSensitiveToggled in + // The button indicator go-to state for button action direction + // eye: when media is hidden + // eye-slash: when media display + let image = isSensitiveToggled ? UIImage(systemName: "eye.slash.fill") : UIImage(systemName: "eye.fill") + statusView.contentSensitiveeToggleButton.setImage(image, for: .normal) + } + .store(in: &disposeBag) } private func bindMedia(statusView: StatusView) { @@ -414,6 +385,7 @@ extension StatusView.ViewModel { $isMediaReveal .sink { isMediaReveal in + statusView.mediaGridContainerView.contentWarningOverlay.isHidden = isMediaReveal statusView.mediaGridContainerView.viewModel.isSensitiveToggleButtonDisplay = isMediaReveal } .store(in: &disposeBag) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift index b938f2b9..7b96fe0b 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift @@ -141,11 +141,11 @@ public final class StatusView: UIView { return style }() metaText.textAttributes = [ - .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)), + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)), .foregroundColor: Asset.Colors.Label.primary.color, ] metaText.linkAttributes = [ - .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)), + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)), .foregroundColor: Asset.Colors.brandBlue.color, ] return metaText @@ -508,6 +508,7 @@ extension StatusView.Style { // status content statusView.contentContainer.addArrangedSubview(statusView.contentMetaText.textView) + statusView.containerStackView.setCustomSpacing(16, after: statusView.contentMetaText.textView) statusView.spoilerOverlayView.translatesAutoresizingMaskIntoConstraints = false statusView.containerStackView.addSubview(statusView.spoilerOverlayView) @@ -757,7 +758,7 @@ extension StatusView: UITextViewDelegate { // MARK: - MetaTextViewDelegate extension StatusView: MetaTextViewDelegate { public func metaTextView(_ metaTextView: MetaTextView, didSelectMeta meta: Meta) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): meta: \(String(describing: meta))") switch metaTextView { case contentMetaText.textView: delegate?.statusView(self, metaText: contentMetaText, didSelectMeta: meta) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/TrendView+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/Content/TrendView+Configuration.swift new file mode 100644 index 00000000..fd5ddb24 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/TrendView+Configuration.swift @@ -0,0 +1,35 @@ +// +// TrendView+Configuration.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit +import MastodonSDK +import MastodonLocalization + +extension TrendView { + public func configure(tag: Mastodon.Entity.Tag) { + let primaryLabelText = "#" + tag.name + let secondaryLabelText = L10n.Plural.peopleTalking(tag.talkingPeopleCount ?? 0) + + primaryLabel.text = primaryLabelText + secondaryLabel.text = secondaryLabelText + + lineChartView.data = (tag.history ?? []) + .sorted(by: { $0.day < $1.day }) // latest last + .map { entry in + guard let point = Int(entry.accounts) else { + return .zero + } + return CGFloat(point) + } + + isAccessibilityElement = true + accessibilityLabel = [ + primaryLabelText, + secondaryLabelText + ].joined(separator: ", ") + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/TrendView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/TrendView.swift new file mode 100644 index 00000000..ff1b9b70 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/TrendView.swift @@ -0,0 +1,100 @@ +// +// TrendView.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit +import MastodonAsset + +public final class TrendView: UIView { + + let container: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 16 + return stackView + }() + + let infoContainer: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + return stackView + }() + + let lineChartContainer: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + return stackView + }() + + let primaryLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) + label.textColor = Asset.Colors.Label.primary.color + return label + }() + + let secondaryLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) + label.textColor = Asset.Colors.Label.secondary.color + return label + }() + + let lineChartView = LineChartView() + + public override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension TrendView { + private func _init() { + container.translatesAutoresizingMaskIntoConstraints = false + addSubview(container) + NSLayoutConstraint.activate([ + container.topAnchor.constraint(equalTo: topAnchor, constant: 11), + container.leadingAnchor.constraint(equalTo: leadingAnchor), + container.trailingAnchor.constraint(equalTo: trailingAnchor), + bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 11), + ]) + + // container: H - [ info container | padding | line chart container ] + container.addArrangedSubview(infoContainer) + + // info container: V - [ primary | secondary ] + infoContainer.addArrangedSubview(primaryLabel) + infoContainer.addArrangedSubview(secondaryLabel) + + // padding + let padding = UIView() + container.addArrangedSubview(padding) + + // line chart + container.addArrangedSubview(lineChartContainer) + + let lineChartViewTopPadding = UIView() + let lineChartViewBottomPadding = UIView() + lineChartViewTopPadding.translatesAutoresizingMaskIntoConstraints = false + lineChartViewBottomPadding.translatesAutoresizingMaskIntoConstraints = false + lineChartView.translatesAutoresizingMaskIntoConstraints = false + lineChartContainer.addArrangedSubview(lineChartViewTopPadding) + lineChartContainer.addArrangedSubview(lineChartView) + lineChartContainer.addArrangedSubview(lineChartViewBottomPadding) + NSLayoutConstraint.activate([ + lineChartView.widthAnchor.constraint(equalToConstant: 50), + lineChartView.heightAnchor.constraint(equalToConstant: 26), + lineChartViewTopPadding.heightAnchor.constraint(equalTo: lineChartViewBottomPadding.heightAnchor), + ]) + } +} + diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift index 449254d2..c3a9b96f 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift @@ -114,7 +114,7 @@ extension ActionToolbarContainer { container.addArrangedSubview(favoriteButton) container.addArrangedSubview(shareButton) NSLayoutConstraint.activate([ - replyButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh), + replyButton.heightAnchor.constraint(equalToConstant: 36).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: reblogButton.heightAnchor).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: favoriteButton.heightAnchor).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).priority(.defaultHigh), diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ContentWarningOverlayView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ContentWarningOverlayView.swift index d559e4e0..70be5bbc 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ContentWarningOverlayView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ContentWarningOverlayView.swift @@ -7,28 +7,24 @@ import os.log import UIKit - -public protocol ContentWarningOverlayViewDelegate: AnyObject { - func contentWarningOverlayViewDidPressed(_ contentWarningOverlayView: ContentWarningOverlayView) -} +import MastodonLocalization public final class ContentWarningOverlayView: UIView { - - public static let blurVisualEffect = UIBlurEffect(style: .systemUltraThinMaterial) - + let logger = Logger(subsystem: "ContentWarningOverlayView", category: "View") - public weak var delegate: ContentWarningOverlayViewDelegate? - - public let blurVisualEffectView = UIVisualEffectView(effect: ContentWarningOverlayView.blurVisualEffect) - public let vibrancyVisualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: ContentWarningOverlayView.blurVisualEffect)) -// let alertImageView: UIImageView = { -// let imageView = UIImageView() -// imageView.image = Asset.Indices.exclamationmarkTriangleLarge.image.withRenderingMode(.alwaysTemplate) -// return imageView -// }() - - public let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer + let hintLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 18, weight: .regular) + label.text = L10n.Common.Controls.Status.tapToReveal + label.textAlignment = .center + label.textColor = .white.withAlphaComponent(0.7) + label.layer.shadowOpacity = 0.3 + label.layer.shadowOffset = CGSize(width: 0, height: 2) + label.layer.shadowRadius = 2 + label.layer.shadowColor = UIColor.black.cgColor + return label + }() override init(frame: CGRect) { super.init(frame: frame) @@ -44,40 +40,12 @@ public final class ContentWarningOverlayView: UIView { extension ContentWarningOverlayView { private func _init() { - // overlay - blurVisualEffectView.translatesAutoresizingMaskIntoConstraints = false - addSubview(blurVisualEffectView) + hintLabel.translatesAutoresizingMaskIntoConstraints = false + addSubview(hintLabel) NSLayoutConstraint.activate([ - blurVisualEffectView.topAnchor.constraint(equalTo: topAnchor), - blurVisualEffectView.leadingAnchor.constraint(equalTo: leadingAnchor), - blurVisualEffectView.trailingAnchor.constraint(equalTo: trailingAnchor), - blurVisualEffectView.bottomAnchor.constraint(equalTo: bottomAnchor), + hintLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8), + trailingAnchor.constraint(equalTo: hintLabel.trailingAnchor, constant: 8), + centerYAnchor.constraint(equalTo: hintLabel.centerYAnchor, constant: 10), ]) - - vibrancyVisualEffectView.translatesAutoresizingMaskIntoConstraints = false - blurVisualEffectView.contentView.addSubview(vibrancyVisualEffectView) - NSLayoutConstraint.activate([ - vibrancyVisualEffectView.topAnchor.constraint(equalTo: blurVisualEffectView.contentView.topAnchor), - vibrancyVisualEffectView.leadingAnchor.constraint(equalTo: blurVisualEffectView.contentView.leadingAnchor), - vibrancyVisualEffectView.trailingAnchor.constraint(equalTo: blurVisualEffectView.contentView.trailingAnchor), - vibrancyVisualEffectView.bottomAnchor.constraint(equalTo: blurVisualEffectView.contentView.bottomAnchor), - ]) - -// alertImageView.translatesAutoresizingMaskIntoConstraints = false -// vibrancyVisualEffectView.contentView.addSubview(alertImageView) -// NSLayoutConstraint.activate([ -// alertImageView.centerXAnchor.constraint(equalTo: vibrancyVisualEffectView.contentView.centerXAnchor), -// alertImageView.centerYAnchor.constraint(equalTo: vibrancyVisualEffectView.contentView.centerYAnchor), -// ]) - - tapGestureRecognizer.addTarget(self, action: #selector(ContentWarningOverlayView.tapGestureRecognizerHandler(_:))) - addGestureRecognizer(tapGestureRecognizer) - } -} - -extension ContentWarningOverlayView { - @objc private func tapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - delegate?.contentWarningOverlayViewDidPressed(self) } } diff --git a/Mastodon/Scene/Search/Search/View/LineChartView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/LineChartView.swift similarity index 85% rename from Mastodon/Scene/Search/Search/View/LineChartView.swift rename to MastodonSDK/Sources/MastodonUI/View/Control/LineChartView.swift index cd76fb0c..c90b59f0 100644 --- a/Mastodon/Scene/Search/Search/View/LineChartView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/LineChartView.swift @@ -7,12 +7,11 @@ import UIKit import Accelerate -import simd import MastodonAsset -final class LineChartView: UIView { +public final class LineChartView: UIView { - var data: [CGFloat] = [] { + public var data: [CGFloat] = [] { didSet { setNeedsLayout() } @@ -20,14 +19,13 @@ final class LineChartView: UIView { let lineShapeLayer = CAShapeLayer() let gradientLayer = CAGradientLayer() -// let dotShapeLayer = CAShapeLayer() - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } @@ -38,10 +36,8 @@ extension LineChartView { private func _init() { lineShapeLayer.frame = bounds gradientLayer.frame = bounds -// dotShapeLayer.frame = bounds layer.addSublayer(lineShapeLayer) layer.addSublayer(gradientLayer) -// layer.addSublayer(dotShapeLayer) gradientLayer.colors = [ Asset.Colors.brandBlue.color.withAlphaComponent(0.5).cgColor, // set the same alpha to fill @@ -51,16 +47,14 @@ extension LineChartView { gradientLayer.endPoint = CGPoint(x: 0.5, y: 1) } - override func layoutSubviews() { + public override func layoutSubviews() { super.layoutSubviews() lineShapeLayer.frame = bounds gradientLayer.frame = bounds -// dotShapeLayer.frame = bounds guard data.count > 1 else { lineShapeLayer.path = nil -// dotShapeLayer.path = nil gradientLayer.isHidden = true return } @@ -113,9 +107,5 @@ extension LineChartView { maskLayer.strokeColor = UIColor.clear.cgColor maskLayer.lineWidth = 0.0 gradientLayer.mask = maskLayer - -// dotShapeLayer.lineWidth = 3 -// dotShapeLayer.fillColor = Asset.Colors.brandBlue.color.cgColor -// dotShapeLayer.path = dotPath.cgPath } } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift similarity index 80% rename from Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift rename to MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift index 87c189a4..bdf696e7 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift @@ -6,23 +6,23 @@ // import UIKit -import MastodonUI import MastodonAsset +import MastodonLocalization -final class ProfileRelationshipActionButton: RoundedEdgesButton { +public final class ProfileRelationshipActionButton: RoundedEdgesButton { - let activityIndicatorView: UIActivityIndicatorView = { + public let activityIndicatorView: UIActivityIndicatorView = { let activityIndicatorView = UIActivityIndicatorView(style: .medium) activityIndicatorView.color = Asset.Colors.Label.primaryReverse.color return activityIndicatorView }() - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } @@ -47,7 +47,7 @@ extension ProfileRelationshipActionButton { configureAppearance() } - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) configureAppearance() @@ -55,7 +55,7 @@ extension ProfileRelationshipActionButton { } extension ProfileRelationshipActionButton { - func configure(actionOptionSet: ProfileViewModel.RelationshipActionOptionSet) { + public func configure(actionOptionSet: RelationshipActionOptionSet) { setTitle(actionOptionSet.title, for: .normal) configureAppearance() @@ -87,9 +87,5 @@ extension ProfileRelationshipActionButton { setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedLight.color), for: .highlighted) setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedLight.color), for: .disabled) } -// setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor), for: .normal) -// setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor.withAlphaComponent(0.5)), for: .highlighted) -// setBackgroundImage(.placeholder(color: actionOptionSet.backgroundColor.withAlphaComponent(0.5)), for: .disabled) } } - diff --git a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardMeterView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift similarity index 91% rename from Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardMeterView.swift rename to MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift index 9176d7a3..0c9d243c 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardMeterView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardMeterView.swift @@ -9,9 +9,9 @@ import UIKit import MastodonAsset import MastodonLocalization -final class ProfileStatusDashboardMeterView: UIView { +public final class ProfileStatusDashboardMeterView: UIView { - let numberLabel: UILabel = { + public let numberLabel: UILabel = { let label = UILabel() label.font = { let font = UIFont.systemFont(ofSize: 20, weight: .semibold) @@ -25,7 +25,7 @@ final class ProfileStatusDashboardMeterView: UIView { return label }() - let textLabel: UILabel = { + public let textLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 13, weight: .regular) label.textColor = Asset.Colors.Label.primary.color @@ -41,12 +41,12 @@ final class ProfileStatusDashboardMeterView: UIView { return label }() - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift similarity index 79% rename from Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift rename to MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift index 9448f196..a45e8ef6 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileStatusDashboardView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileStatusDashboardView.swift @@ -10,24 +10,24 @@ import UIKit import MastodonAsset import MastodonLocalization -protocol ProfileStatusDashboardViewDelegate: AnyObject { +public protocol ProfileStatusDashboardViewDelegate: AnyObject { func profileStatusDashboardView(_ dashboardView: ProfileStatusDashboardView, dashboardMeterViewDidPressed dashboardMeterView: ProfileStatusDashboardMeterView, meter: ProfileStatusDashboardView.Meter) } -final class ProfileStatusDashboardView: UIView { +public final class ProfileStatusDashboardView: UIView { - let postDashboardMeterView = ProfileStatusDashboardMeterView() - let followingDashboardMeterView = ProfileStatusDashboardMeterView() - let followersDashboardMeterView = ProfileStatusDashboardMeterView() + public let postDashboardMeterView = ProfileStatusDashboardMeterView() + public let followingDashboardMeterView = ProfileStatusDashboardMeterView() + public let followersDashboardMeterView = ProfileStatusDashboardMeterView() - weak var delegate: ProfileStatusDashboardViewDelegate? + public weak var delegate: ProfileStatusDashboardViewDelegate? - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } @@ -35,7 +35,7 @@ final class ProfileStatusDashboardView: UIView { } extension ProfileStatusDashboardView { - enum Meter: Hashable { + public enum Meter: Hashable { case post case following case follower @@ -55,7 +55,7 @@ extension ProfileStatusDashboardView { containerStackView.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh), ]) - let spacing: CGFloat = UIView.isZoomedMode ? 4 : 16 + let spacing: CGFloat = UIView.isZoomedMode ? 4 : 12 containerStackView.spacing = spacing containerStackView.axis = .horizontal containerStackView.distribution = .fillEqually @@ -75,15 +75,15 @@ extension ProfileStatusDashboardView { tapGestureRecognizer.addTarget(self, action: #selector(ProfileStatusDashboardView.tapGestureRecognizerHandler(_:))) meterView.addGestureRecognizer(tapGestureRecognizer) } - - followingDashboardMeterView.accessibilityHint = "Double tap to open the list" // TODO: i18n - followersDashboardMeterView.accessibilityHint = "Double tap to open the list" + + followingDashboardMeterView.accessibilityHint = L10n.Scene.Profile.Accessibility.doubleTapToOpenTheList + followersDashboardMeterView.accessibilityHint = L10n.Scene.Profile.Accessibility.doubleTapToOpenTheList } } extension ProfileStatusDashboardView { @objc private func tapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + os_log(.debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) guard let sourceView = sender.view as? ProfileStatusDashboardMeterView else { assertionFailure() return diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/NewsTableViewCell.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/NewsTableViewCell.swift new file mode 100644 index 00000000..f0b2aec8 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/NewsTableViewCell.swift @@ -0,0 +1,61 @@ +// +// NewsTableViewCell.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit + +public final class NewsTableViewCell: UITableViewCell { + + public let newsView = NewsView() + + let separatorLine = UIView.separatorLine + + public override func prepareForReuse() { + super.prepareForReuse() + + newsView.prepareForReuse() + } + + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension NewsTableViewCell { + + private func _init() { + newsView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(newsView) + NSLayoutConstraint.activate([ + newsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), + newsView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + newsView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: newsView.bottomAnchor, constant: 16), + ]) + + separatorLine.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(separatorLine) + NSLayoutConstraint.activate([ + separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), + ]) + + isAccessibilityElement = true + accessibilityElements = [ + newsView + ] + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell+Configuration.swift new file mode 100644 index 00000000..061af0f4 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell+Configuration.swift @@ -0,0 +1,30 @@ +// +// ProfileCardTableViewCell+Configuration.swift +// +// +// Created by MainasuK on 2022-4-19. +// + +import UIKit +import CoreDataStack + +extension ProfileCardTableViewCell { + + public func configure( + tableView: UITableView, + user: MastodonUser, + profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? + ) { + if profileCardView.frame == .zero { + // set content view width + assert(layoutMarginsGuide.layoutFrame.width > .zero) + shadowBackgroundContainer.frame.size.width = layoutMarginsGuide.layoutFrame.width + profileCardView.setupLayoutFrame(layoutMarginsGuide.layoutFrame) + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): did layout for new cell") + } + + profileCardView.configure(user: user) + delegate = profileCardTableViewCellDelegate + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift new file mode 100644 index 00000000..aff7b6fe --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift @@ -0,0 +1,92 @@ +// +// ProfileCardTableViewCell.swift +// +// +// Created by MainasuK on 2022-4-14. +// + +import os.log +import UIKit +import Combine + +public protocol ProfileCardTableViewCellDelegate: AnyObject { + func profileCardTableViewCell(_ cell: ProfileCardTableViewCell, profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) +} + +public final class ProfileCardTableViewCell: UITableViewCell { + + let logger = Logger(subsystem: "ProfileCardTableViewCell", category: "Cell") + + public weak var delegate: ProfileCardTableViewCellDelegate? + public var disposeBag = Set() + + public let shadowBackgroundContainer = ShadowBackgroundContainer() + + public let profileCardView: ProfileCardView = { + let profileCardView = ProfileCardView() + profileCardView.layer.masksToBounds = true + profileCardView.layer.cornerRadius = 6 + profileCardView.layer.cornerCurve = .continuous + return profileCardView + }() + + public override func prepareForReuse() { + super.prepareForReuse() + + disposeBag.removeAll() + profileCardView.prepareForReuse() + } + + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension ProfileCardTableViewCell { + + private func _init() { + selectionStyle = .none + + shadowBackgroundContainer.cornerRadius = 6 + shadowBackgroundContainer.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(shadowBackgroundContainer) + NSLayoutConstraint.activate([ + shadowBackgroundContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).priority(.required - 1), + shadowBackgroundContainer.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + shadowBackgroundContainer.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: shadowBackgroundContainer.bottomAnchor, constant: 10).priority(.required - 1), + ]) + + profileCardView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(profileCardView) + NSLayoutConstraint.activate([ + profileCardView.topAnchor.constraint(equalTo: shadowBackgroundContainer.topAnchor), + profileCardView.leadingAnchor.constraint(equalTo: shadowBackgroundContainer.leadingAnchor), + profileCardView.trailingAnchor.constraint(equalTo: shadowBackgroundContainer.trailingAnchor), + profileCardView.bottomAnchor.constraint(equalTo: shadowBackgroundContainer.bottomAnchor), + ]) + + profileCardView.delegate = self + + profileCardView.isAccessibilityElement = true + accessibilityElements = [ + profileCardView, + profileCardView.relationshipActionButton + ] + } + +} + +// MARK: - ProfileCardViewDelegate +extension ProfileCardTableViewCell: ProfileCardViewDelegate { + public func profileCardView(_ profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) { + delegate?.profileCardTableViewCell(self, profileCardView: profileCardView, relationshipButtonDidPressed: button) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TrendTableViewCell.swift b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TrendTableViewCell.swift new file mode 100644 index 00000000..8c1cebff --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/TableViewCell/TrendTableViewCell.swift @@ -0,0 +1,86 @@ +// +// TrendTableViewCell.swift +// +// +// Created by MainasuK on 2022-4-13. +// + +import UIKit + +public final class TrendTableViewCell: UITableViewCell { + + public let trendView = TrendView() + + let separatorLine = UIView.separatorLine + + public override func prepareForReuse() { + super.prepareForReuse() + + configureSeparator(style: .inset) + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension TrendTableViewCell { + + private func _init() { + trendView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(trendView) + NSLayoutConstraint.activate([ + trendView.topAnchor.constraint(equalTo: contentView.topAnchor), + trendView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + trendView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + trendView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + ]) + + configureSeparator(style: .inset) + + accessibilityElements = [trendView] + } + +} + +extension TrendTableViewCell { + + public enum SeparatorStyle { + case edge + case inset + } + + public func configureSeparator(style: SeparatorStyle) { + separatorLine.removeFromSuperview() + separatorLine.removeConstraints(separatorLine.constraints) + + switch style { + case .edge: + separatorLine.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(separatorLine) + NSLayoutConstraint.activate([ + separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), + ]) + case .inset: + separatorLine.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(separatorLine) + NSLayoutConstraint.activate([ + separatorLine.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)), + ]) + } + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift b/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift new file mode 100644 index 00000000..cee31c14 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/ViewModel/RelationshipViewModel.swift @@ -0,0 +1,253 @@ +// +// RelationshipViewModel.swift +// +// +// Created by MainasuK on 2022-4-14. +// + +import UIKit +import Combine +import MastodonAsset +import MastodonLocalization +import CoreDataStack + +public enum RelationshipAction: Int, CaseIterable { + case isMyself + case followingBy + case blockingBy + case none // set hide from UI + case follow + case request + case pending + case following + case muting + case blocked + case blocking + case suspended + case edit + case editing + case updating + + public var option: RelationshipActionOptionSet { + return RelationshipActionOptionSet(rawValue: 1 << rawValue) + } +} + +// construct option set on the enum for safe iterator +public struct RelationshipActionOptionSet: OptionSet { + + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let isMyself = RelationshipAction.isMyself.option + public static let followingBy = RelationshipAction.followingBy.option + public static let blockingBy = RelationshipAction.blockingBy.option + public static let none = RelationshipAction.none.option + public static let follow = RelationshipAction.follow.option + public static let request = RelationshipAction.request.option + public static let pending = RelationshipAction.pending.option + public static let following = RelationshipAction.following.option + public static let muting = RelationshipAction.muting.option + public static let blocked = RelationshipAction.blocked.option + public static let blocking = RelationshipAction.blocking.option + public static let suspended = RelationshipAction.suspended.option + public static let edit = RelationshipAction.edit.option + public static let editing = RelationshipAction.editing.option + public static let updating = RelationshipAction.updating.option + + public static let editOptions: RelationshipActionOptionSet = [.edit, .editing, .updating] + + public func highPriorityAction(except: RelationshipActionOptionSet) -> RelationshipAction? { + let set = subtracting(except) + for action in RelationshipAction.allCases.reversed() where set.contains(action.option) { + return action + } + + return nil + } + + public var title: String { + guard let highPriorityAction = self.highPriorityAction(except: []) else { + assertionFailure() + return " " + } + switch highPriorityAction { + case .isMyself: return "" + case .followingBy: return " " + case .blockingBy: return " " + case .none: return " " + case .follow: return L10n.Common.Controls.Friendship.follow + case .request: return L10n.Common.Controls.Friendship.request + case .pending: return L10n.Common.Controls.Friendship.pending + case .following: return L10n.Common.Controls.Friendship.following + case .muting: return L10n.Common.Controls.Friendship.muted + case .blocked: return L10n.Common.Controls.Friendship.follow // blocked by user + case .blocking: return L10n.Common.Controls.Friendship.blocked + case .suspended: return L10n.Common.Controls.Friendship.follow + case .edit: return L10n.Common.Controls.Friendship.editInfo + case .editing: return L10n.Common.Controls.Actions.done + case .updating: return " " + } + } + +} + +public final class RelationshipViewModel { + + var disposeBag = Set() + + public var userObserver: AnyCancellable? + public var meObserver: AnyCancellable? + + // input + @Published public var user: MastodonUser? + @Published public var me: MastodonUser? + public let relationshipUpdatePublisher = CurrentValueSubject(Void()) // needs initial event + + // output + @Published public var isMyself = false + @Published public var optionSet: RelationshipActionOptionSet? + + @Published public var isFollowing = false + @Published public var isFollowingBy = false + @Published public var isMuting = false + @Published public var isBlocking = false + @Published public var isBlockingBy = false + + public init() { + Publishers.CombineLatest3( + $user, + $me, + relationshipUpdatePublisher + ) + .sink { [weak self] user, me, _ in + guard let self = self else { return } + self.update(user: user, me: me) + + guard let user = user, let me = me else { + self.userObserver = nil + self.meObserver = nil + return + } + + // do not modify object to prevent infinity loop + self.userObserver = RelationshipViewModel.createObjectChangePublisher(user: user) + .sink { [weak self] _ in + guard let self = self else { return } + self.relationshipUpdatePublisher.send() + } + + self.meObserver = RelationshipViewModel.createObjectChangePublisher(user: me) + .sink { [weak self] _ in + guard let self = self else { return } + self.relationshipUpdatePublisher.send() + } + } + .store(in: &disposeBag) + } + +} + +extension RelationshipViewModel { + + public static func createObjectChangePublisher(user: MastodonUser) -> AnyPublisher { + return ManagedObjectObserver + .observe(object: user) + .map { _ in Void() } + .catch { error in + return Just(Void()) + } + .eraseToAnyPublisher() + } + +} + +extension RelationshipViewModel { + private func update(user: MastodonUser?, me: MastodonUser?) { + guard let user = user, + let me = me + else { + reset() + return + } + + let optionSet = RelationshipViewModel.optionSet(user: user, me: me) + + self.isMyself = optionSet.contains(.isMyself) + self.isFollowingBy = optionSet.contains(.followingBy) + self.isFollowing = optionSet.contains(.following) + self.isMuting = optionSet.contains(.muting) + self.isBlockingBy = optionSet.contains(.blockingBy) + self.isBlocking = optionSet.contains(.blocking) + + + self.optionSet = optionSet + } + + private func reset() { + isMyself = false + isFollowingBy = false + isFollowing = false + isMuting = false + isBlockingBy = false + isBlocking = false + optionSet = nil + } +} + +extension RelationshipViewModel { + + public static func optionSet(user: MastodonUser, me: MastodonUser) -> RelationshipActionOptionSet { + let isMyself = user.id == me.id && user.domain == me.domain + guard !isMyself else { + return [.isMyself] + } + + let isProtected = user.locked + let isFollowingBy = me.followingBy.contains(user) + let isFollowing = user.followingBy.contains(me) + let isPending = user.followRequestedBy.contains(me) + let isMuting = user.mutingBy.contains(me) + let isBlockingBy = me.blockingBy.contains(user) + let isBlocking = user.blockingBy.contains(me) + + var optionSet: RelationshipActionOptionSet = [.follow] + + if isMyself { + optionSet.insert(.isMyself) + } + + if isProtected { + optionSet.insert(.request) + } + + if isFollowingBy { + optionSet.insert(.followingBy) + } + + if isFollowing { + optionSet.insert(.following) + } + + if isPending { + optionSet.insert(.pending) + } + + if isMuting { + optionSet.insert(.muting) + } + + if isBlockingBy { + optionSet.insert(.blockingBy) + } + + if isBlocking { + optionSet.insert(.blocking) + } + + return optionSet + } +} diff --git a/MastodonTests/Info.plist b/MastodonTests/Info.plist index 73f11cd2..920eaff8 100644 --- a/MastodonTests/Info.plist +++ b/MastodonTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.3.1 CFBundleVersion - 109 + 113 diff --git a/MastodonUITests/Info.plist b/MastodonUITests/Info.plist index 73f11cd2..920eaff8 100644 --- a/MastodonUITests/Info.plist +++ b/MastodonUITests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.3.1 CFBundleVersion - 109 + 113 diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index 215b572b..fa92e4a2 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.3.1 CFBundleVersion - 109 + 113 NSExtension NSExtensionPointIdentifier diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index 82ba5658..9ee97c1e 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.0 + 1.3.1 CFBundleVersion - 109 + 113 NSExtension NSExtensionAttributes diff --git a/ShareActionExtension/Scene/ShareViewController.swift b/ShareActionExtension/Scene/ShareViewController.swift index d45558f1..622e0106 100644 --- a/ShareActionExtension/Scene/ShareViewController.swift +++ b/ShareActionExtension/Scene/ShareViewController.swift @@ -12,6 +12,7 @@ import MastodonUI import SwiftUI import MastodonAsset import MastodonLocalization +import MastodonUI class ShareViewController: UIViewController { diff --git a/ShareActionExtension/Scene/ShareViewModel.swift b/ShareActionExtension/Scene/ShareViewModel.swift index fbad8220..c56f8ecf 100644 --- a/ShareActionExtension/Scene/ShareViewModel.swift +++ b/ShareActionExtension/Scene/ShareViewModel.swift @@ -16,6 +16,7 @@ import SwiftUI import UniformTypeIdentifiers import MastodonAsset import MastodonLocalization +import MastodonUI final class ShareViewModel { diff --git a/ShareActionExtension/Scene/View/ComposeToolbarView.swift b/ShareActionExtension/Scene/View/ComposeToolbarView.swift index 73caac73..a903d3eb 100644 --- a/ShareActionExtension/Scene/View/ComposeToolbarView.swift +++ b/ShareActionExtension/Scene/View/ComposeToolbarView.swift @@ -12,6 +12,7 @@ import MastodonSDK import MastodonUI import MastodonAsset import MastodonLocalization +import MastodonUI protocol ComposeToolbarViewDelegate: AnyObject { func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton)